diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index a1e2ed2f67..bc16a18535 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3721,8 +3721,10 @@ JavaScript: filenames: - Jakefile interpreters: + - bun - chakra - d8 + - deno - gjs - js - node diff --git a/samples/JavaScript/make.js b/samples/JavaScript/make.js new file mode 100644 index 0000000000..dfb3e94412 --- /dev/null +++ b/samples/JavaScript/make.js @@ -0,0 +1,150 @@ +#!/usr/bin/env deno run --allow-read --allow-write --allow-env --allow-net --allow-run + +/** + * Repository: philc/sheetkeys on GitHub + * Original file: https://github.com/philc/sheetkeys/blob/master/make.js + * License: MIT (https://github.com/philc/sheetkeys/blob/master/MIT-LICENSE.txt) + */ + +// Usage: ./make.js command. Use -l to list commands. +// This is a set of tasks for building and testing SheetKeys in development. +import * as fs from "@std/fs"; +import * as path from "@std/path"; +import { desc, run, task } from "https://deno.land/x/drake@v1.5.1/mod.ts"; +import * as shoulda from "@philc/shoulda"; +import JSON5 from "json5"; + +const projectPath = new URL(".", import.meta.url).pathname; + +async function shell(procName, argsArray = []) { + // NOTE(philc): Does drake's `sh` function work on Windows? If so, that can replace this function. + if (Deno.build.os == "windows") { + // if win32, prefix arguments with "/c {original command}" + // e.g. "mkdir c:\git\sheetkeys" becomes "cmd.exe /c mkdir c:\git\sheetkeys" + optArray.unshift("/c", procName); + procName = "cmd.exe"; + } + const p = Deno.run({ cmd: [procName].concat(argsArray) }); + const status = await p.status(); + if (!status.success) { + throw new Error(`${procName} ${argsArray} exited with status ${status.code}`); + } +} + +function createFirefoxManifest(manifest) { + manifest = JSON.parse(JSON.stringify(manifest)); // Deep clone. + + // As of 2023-07-08 Firefox doesn't yet support background.service_worker. + delete manifest.background["service_worker"]; + Object.assign(manifest.background, { + "scripts": ["background_script.js"], + }); + + Object.assign(manifest, { + "browser_specific_settings": { + "gecko": { + // This ID was generated by the Firefox store upon first submission. It's needed in + // development mode, or many extension APIs don't work. + "id": "sheetkeys@github.com", + "strict_min_version": "112.0", + }, + }, + }); + + return manifest; +} + +async function parseManifestFile() { + // Chrome's manifest.json supports JavaScript comment syntax. However, the Chrome Store rejects + // manifests with JavaScript comments in them! So here we use the JSON5 library, rather than JSON + // library, to parse our manifest.json and remove its comments. + return JSON5.parse(await Deno.readTextFile("./manifest.json")); +} + +// Builds a zip file for submission to the Chrome and Firefox stores. The output is in dist/. +async function buildStorePackage() { + const excludeList = [ + "*.md", + ".*", + "CREDITS", + "MIT-LICENSE.txt", + "dist", + "make.js", + "harnesses", + "tests", + ]; + + const chromeManifest = await parseManifestFile(); + + // Ensure the manifest does not contain the options_page key. That is only used in development. + if (chromeManifest["options_page"]) { + throw new Error( + "The 'options_page' key in manifest.json should be commented out. It's used only in " + + "develompent for testing purposes.", + ); + } + + await shell("rm", ["-rf", "dist/sheetkeys"]); + await shell("mkdir", ["-p", "dist/sheetkeys", "dist/chrome-store", "dist/firefox"]); + const rsyncOptions = ["-r", ".", "dist/sheetkeys"].concat( + ...excludeList.map((item) => ["--exclude", item]), + ); + await shell("rsync", rsyncOptions); + + const version = chromeManifest["version"]; + const writeDistManifest = async (manifestObject) => { + await Deno.writeTextFile( + "dist/sheetkeys/manifest.json", + JSON.stringify(manifestObject, null, 2), + ); + }; + + // cd into "dist/sheetkeys" before building the zip, so that the files in the zip don't each have + // the path prefix "dist/sheetkeys". + // --filesync ensures that files in the archive which are no longer on disk are deleted. It's + // equivalent to removing the zip file before the build. + const zipCommand = "cd dist/sheetkeys && zip -r --filesync "; + + // Build the Firefox package + const firefoxManifest = createFirefoxManifest(chromeManifest); + await writeDistManifest(firefoxManifest); + await shell("bash", ["-c", `${zipCommand} ../firefox/sheetkeys-firefox-${version}.zip .`]); + + // Build the Chrome Store package. + writeDistManifest(chromeManifest); + await shell("bash", [ + "-c", + `${zipCommand} ../chrome-store/sheetkeys-chrome-store-${version}.zip .`, + ]); +} + +const runUnitTests = async () => { + // Import every test file. + const dir = path.join(projectPath, "tests/unit/"); + const files = Array.from(Deno.readDirSync(dir)).map((f) => f.name).sort(); + for (const f of files) { + if (f.endsWith("_test.js")) { + await import(path.join(dir, f)); + } + } + + await shoulda.run(); +}; + +desc("Run tests"); +task("test", [], async () => { + await runUnitTests(); +}); + +desc("Builds a zip file for submission to the Chrome store. The output is in dist/"); +task("package", [], async () => { + await buildStorePackage(); +}); + +desc("Replaces manifest.json with a Firefox-compatible version, for development"); +task("write-firefox-manifest", [], async () => { + const firefoxManifest = createFirefoxManifest(await parseManifestFile()); + await Deno.writeTextFile("./manifest.json", JSON.stringify(firefoxManifest, null, 2)); +}); + +run(); \ No newline at end of file diff --git a/samples/JavaScript/nbbun.js b/samples/JavaScript/nbbun.js new file mode 100644 index 0000000000..dc6bb7657b --- /dev/null +++ b/samples/JavaScript/nbbun.js @@ -0,0 +1,44 @@ +#!/usr/bin/env bun + +/* +Author: babashka on GitHub +Original source: https://github.com/babashka/nbb/blob/main/nbbun.js +License: Eclipse Public License 1.0 (https://github.com/babashka/nbb/blob/main/LICENSE) +*/ + +import { loadString, loadFile, addClassPath } from './index.mjs'; +import { existsSync } from "node:fs"; + +const prn = await loadString('prn'); +const nonNil = await loadString('some?'); + +var args = process.argv.slice(2); + +if (args[0] === '--classpath') { + addClassPath(args[[1]]); + args = args.slice(2); +} + +if (args[0] === '-e') { + let res = await(loadString(args[1])); + if (nonNil(res)) { + prn(res); + } + args = args.slice(2); +} + +if (args[0] === '--help') { + console.log(`Usage: nbbun + +Options: + +--classpath: set classpath. +-e : run expressions and print result. +--help: print this help. +`); +} + +if (args[0] && existsSync(args[0])) { + await(loadFile(args[0])); + args = args.slice(2); +} \ No newline at end of file diff --git a/samples/TypeScript/demo.ts b/samples/TypeScript/demo.ts new file mode 100644 index 0000000000..ab6f621240 --- /dev/null +++ b/samples/TypeScript/demo.ts @@ -0,0 +1,194 @@ +#!/usr/bin/env bun + +// @ts-nocheck + +/** + * Original source: https://github.com/photon-hq/imessage-kit/blob/main/demo.ts + * Author: photon-hq + * License: MIT License (see https://github.com/photon-hq/imessage-kit/blob/main/LICENSE) + */ + +/** + * šŸŽ¬ iMessage Kit - Scheduler Feature Demo + * + * This demo showcases the MessageScheduler feature: + * - Schedule messages for future delivery + * - Recurring messages (daily, hourly, custom intervals) + * - Cancel and reschedule on the fly + * - Export/import for persistence + * + * Run: bun run demo.ts + */ + +import { IMessageSDK, MessageScheduler } from './src' + +// ═══════════════════════════════════════════════════════════════════ +// šŸ“± CONFIGURE YOUR RECIPIENT HERE +// ═══════════════════════════════════════════════════════════════════ +const RECIPIENT = process.env.RECIPIENT || 'YOUR_PHONE_OR_EMAIL_HERE' +// Example: '+15551234567' or 'your@email.com' + +if (RECIPIENT === 'YOUR_PHONE_OR_EMAIL_HERE') { + console.log('\nāš ļø Please set your recipient first!\n') + console.log(' Option 1: Edit demo.ts and change RECIPIENT') + console.log(' Option 2: Run with: RECIPIENT="+1234567890" bun run demo.ts\n') + process.exit(1) +} + +// ═══════════════════════════════════════════════════════════════════ +// šŸš€ DEMO START +// ═══════════════════════════════════════════════════════════════════ + +console.log('\n') +console.log('╔══════════════════════════════════════════════════════════════╗') +console.log('ā•‘ šŸ“± iMessage Kit - Scheduler Feature Demo ā•‘') +console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•') +console.log() + +// Initialize SDK +const sdk = new IMessageSDK({ debug: false }) +console.log('āœ… SDK initialized\n') + +// Create scheduler with event handlers +const scheduler = new MessageScheduler( + sdk, + { checkInterval: 500, debug: false }, + { + onSent: (msg, result) => { + console.log('\nšŸŽ‰ MESSAGE SENT!') + console.log(` ID: ${msg.id}`) + console.log(` To: ${msg.to}`) + console.log(` Content: ${typeof msg.content === 'string' ? msg.content : msg.content.text}`) + console.log(` Sent at: ${result.sentAt.toLocaleTimeString()}`) + }, + onError: (msg, error) => { + console.error(`\nāŒ SEND FAILED: ${msg.id}`) + console.error(` Error: ${error.message}`) + }, + onComplete: (msg) => { + console.log(`\nšŸ Recurring message completed: ${msg.id}`) + console.log(` Total sends: ${msg.sendCount}`) + }, + } +) + +console.log(`šŸ“¬ Recipient: ${RECIPIENT}\n`) + +// ───────────────────────────────────────────────────────────────────── +// Demo 1: Schedule a message for 10 seconds from now +// ───────────────────────────────────────────────────────────────────── +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +console.log('šŸ“… DEMO 1: One-Time Scheduled Message') +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') + +const sendTime1 = new Date(Date.now() + 10 * 1000) +const msg1 = scheduler.schedule({ + id: 'demo-welcome', + to: RECIPIENT, + content: 'šŸ‘‹ Hello! This is a scheduled message from iMessage Kit!', + sendAt: sendTime1, +}) +console.log(` āœ“ Scheduled "${msg1}" for ${sendTime1.toLocaleTimeString()}`) +console.log(' ā±ļø Sending in 10 seconds...\n') + +// ───────────────────────────────────────────────────────────────────── +// Demo 2: Schedule another message for 20 seconds +// ───────────────────────────────────────────────────────────────────── +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +console.log('šŸ“… DEMO 2: Second Scheduled Message') +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') + +const sendTime2 = new Date(Date.now() + 20 * 1000) +const msg2 = scheduler.schedule({ + id: 'demo-followup', + to: RECIPIENT, + content: 'šŸš€ The scheduler supports multiple queued messages!', + sendAt: sendTime2, +}) +console.log(` āœ“ Scheduled "${msg2}" for ${sendTime2.toLocaleTimeString()}`) +console.log(' ā±ļø Sending in 20 seconds...\n') + +// ───────────────────────────────────────────────────────────────────── +// Demo 3: Recurring message (every 15 seconds, 3 times) +// ───────────────────────────────────────────────────────────────────── +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +console.log('šŸ”„ DEMO 3: Recurring Message (every 15s, stops after ~45s)') +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') + +const recurringStart = new Date(Date.now() + 30 * 1000) +const recurringEnd = new Date(Date.now() + 75 * 1000) +const msg3 = scheduler.scheduleRecurring({ + id: 'demo-recurring', + to: RECIPIENT, + content: 'šŸ”” Recurring reminder! (every 15 seconds)', + startAt: recurringStart, + interval: 15 * 1000, // 15 seconds + endAt: recurringEnd, +}) +console.log(` āœ“ Scheduled "${msg3}"`) +console.log(` šŸ“ Starts: ${recurringStart.toLocaleTimeString()}`) +console.log(` šŸ Ends: ${recurringEnd.toLocaleTimeString()}`) +console.log(' šŸ” Interval: every 15 seconds\n') + +// ───────────────────────────────────────────────────────────────────── +// Show pending messages +// ───────────────────────────────────────────────────────────────────── +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +console.log('šŸ“‹ PENDING MESSAGES') +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +for (const msg of scheduler.getPending()) { + const time = msg.type === 'recurring' ? msg.nextSendAt : msg.sendAt + const typeLabel = msg.type === 'recurring' ? 'šŸ”„' : 'šŸ“…' + console.log(` ${typeLabel} ${msg.id}: ${time.toLocaleTimeString()}`) +} +console.log() + +// ───────────────────────────────────────────────────────────────────── +// Export feature demo +// ───────────────────────────────────────────────────────────────────── +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +console.log('šŸ’¾ EXPORT FEATURE (for persistence)') +console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━') +const exported = scheduler.export() +console.log(` Exported ${exported.scheduled.length} one-time + ${exported.recurring.length} recurring messages`) +console.log(' This data can be saved to disk and restored later!\n') + +// ───────────────────────────────────────────────────────────────────── +// Wait for messages to send +// ───────────────────────────────────────────────────────────────────── +console.log('╔══════════════════════════════════════════════════════════════╗') +console.log('ā•‘ ā³ Demo running... Watch your Messages app! ā•‘') +console.log('ā•‘ šŸ“± Messages will arrive over the next ~75 seconds ā•‘') +console.log('ā•‘ šŸ›‘ Press Ctrl+C to stop early ā•‘') +console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•') +console.log() + +// Countdown timer +let remaining = 80 +const countdown = setInterval(() => { + remaining-- + if (remaining > 0 && remaining % 10 === 0) { + console.log(` ā±ļø ${remaining} seconds remaining...`) + } +}, 1000) + +// Auto-cleanup after demo +setTimeout(async () => { + clearInterval(countdown) + console.log('\n') + console.log('╔══════════════════════════════════════════════════════════════╗') + console.log('ā•‘ āœ… Demo complete! ā•‘') + console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•') + scheduler.destroy() + await sdk.close() + process.exit(0) +}, 80 * 1000) + +// Handle early exit +process.on('SIGINT', async () => { + clearInterval(countdown) + console.log('\n\nšŸ›‘ Demo stopped by user') + scheduler.destroy() + await sdk.close() + process.exit(0) +}) \ No newline at end of file diff --git a/samples/TypeScript/main.ts b/samples/TypeScript/main.ts new file mode 100644 index 0000000000..da30b6c8d9 --- /dev/null +++ b/samples/TypeScript/main.ts @@ -0,0 +1,214 @@ +#!/usr/bin/env deno + +// @ts-nocheck + +/** + * Author: hzrd149 on GitHub + * Original file: https://github.com/hzrd149/blossom-server/blob/master/main.ts + * License: MIT (https://github.com/hzrd149/blossom-server/blob/master/LICENSE.txt) + */ + +/** + * Blossom Server — Deno entry point. + * + * Startup order: + * 1. Load + validate config (YAML + env vars) + * 2. Init database (LibSQL embedded, run migrations) + * 3. Init storage adapter (local filesystem or S3) + * 4. Init hash worker pool (pre-warm N workers) + * 5. Build Hono app + * 6. Start Deno.serve() + */ + +import { loadConfig } from "./src/config/loader.ts"; +import { type DbConfig, initDb } from "./src/db/client.ts"; +import { maybeMigrateLegacyDb } from "./src/db/legacy-migration.ts"; +import type { IBlobStorage } from "./src/storage/interface.ts"; +import { LocalStorage } from "./src/storage/local.ts"; +import { S3Storage } from "./src/storage/s3.ts"; +import { initPool } from "./src/workers/pool.ts"; +import { buildApp } from "./src/server.ts"; +import { pruneStorage } from "./src/prune/prune.ts"; + +const configPath = Deno.args[0] ?? "config.yml"; +const config = await loadConfig(configPath); + +// Config schema resolves deprecated databasePath into config.database automatically. +const dbConfig: DbConfig = config.database; +const dbLabel = dbConfig.url ?? `file:${dbConfig.path}`; + +console.log("Blossom Server starting..."); +console.log(` Config: ${configPath}`); +console.log(` Database: ${dbLabel}`); +console.log(` Storage: ${config.storage.backend}`); +console.log(` Host: ${config.host}`); +console.log(` Port: ${config.port}`); + +// Migrate legacy Node.js database if present (no-op for remote or already-migrated DBs). +// Must run before initDb() opens the file. See src/db/legacy-migration.ts for details. +if (!dbConfig.url) { + await maybeMigrateLegacyDb(dbConfig.path, dbConfig); +} + +// Init database +const db = await initDb(dbConfig); +console.log(" Database: ready"); + +// Init storage +let storage: IBlobStorage; +if (config.storage.backend === "local") { + const storageDir = config.storage.local?.dir ?? "./data/blobs"; + const local = new LocalStorage(storageDir); + await local.setup(); + storage = local; + console.log(` Storage: local (${storageDir})`); +} else { + const s3Config = config.storage.s3; + if (!s3Config) { + console.error( + "S3 storage backend selected but no [storage.s3] config section found.", + ); + Deno.exit(1); + } + const s3 = new S3Storage({ + endpoint: s3Config.endpoint, + bucket: s3Config.bucket, + accessKey: s3Config.accessKey, + secretKey: s3Config.secretKey, + region: s3Config.region, + publicURL: s3Config.publicURL, + tmpDir: s3Config.tmpDir, + }); + console.log( + ` Storage: s3 — verifying bucket access (${s3Config.bucket} @ ${s3Config.endpoint})...`, + ); + await s3.setup(); + storage = s3; + console.log( + ` Storage: s3 ready (bucket=${s3Config.bucket} endpoint=${s3Config.endpoint})`, + ); +} + +// Init upload worker pool — dbConfig determines whether workers use MessageChannel +// (local SQLite) or open their own direct connections (remote libSQL / Turso). +const pool = initPool( + config.upload.workers, + config.upload.maxJobsPerWorker, + config.upload.throughputWindowMs, + db, + dbConfig, +); +console.log(` Workers: ${pool.size} upload workers`); + +// Resolve admin dashboard password (auto-generate if blank). +// The password is used directly by Hono's basicAuth middleware in the admin router. +if (config.dashboard.enabled) { + if (!config.dashboard.password) { + // Generate a random 20-char alphanumeric password and patch config in memory + const bytes = new Uint8Array(15); + crypto.getRandomValues(bytes); + const generated = btoa(String.fromCharCode(...bytes)) + .replace(/[+/=]/g, "") + .slice(0, 20); + (config.dashboard as { password: string }).password = generated; + console.log(` Admin: password auto-generated: ${generated}`); + } + console.log(" Admin: ready"); +} + +// Build Hono app (async — landing router bundles client JS at startup when enabled) +const app = await buildApp(db, storage, config); + +// Start prune loop — runs if any storage rules are configured or removeWhenNoOwners is set. +// Uses recursive setTimeout (not setInterval) so the next run starts only after the +// current one fully completes, preventing overlapping runs under slow I/O. +const pruneEnabled = config.storage.rules.length > 0 || + config.storage.removeWhenNoOwners; +let pruneTimeout: ReturnType | undefined; +if (pruneEnabled) { + const runPrune = async () => { + try { + const result = await pruneStorage( + db, + storage, + config.storage.rules, + config.storage.removeWhenNoOwners, + ); + if (result.deleted > 0 || result.errors > 0) { + console.log( + `[prune] deleted=${result.deleted} errors=${result.errors}`, + ); + } + } catch (err) { + console.error("[prune] Unexpected error in prune loop:", err); + } + pruneTimeout = setTimeout(runPrune, config.prune.intervalMs); + }; + pruneTimeout = setTimeout(runPrune, config.prune.initialDelayMs); +} + +// Start server +const server = Deno.serve( + { + hostname: config.host, + port: config.port, + onListen({ port, hostname }) { + console.log(`\nBlossom Server listening on http://${hostname}:${port}`); + console.log(" BUD-01: GET/HEAD /:sha256 ready"); + console.log( + " BUD-02: PUT /upload " + + (config.upload.enabled ? "ready" : "disabled"), + ); + console.log(" BUD-02: DELETE /:sha256 ready"); + console.log( + " BUD-04: PUT /mirror " + + (config.mirror.enabled ? "ready" : "disabled"), + ); + console.log( + " BUD-06: HEAD /upload " + + (config.upload.enabled ? "ready" : "disabled"), + ); + console.log( + " BUD-09: PUT /report " + + (config.report.enabled ? "ready" : "disabled"), + ); + console.log( + " BUD-11: Auth " + + (config.upload.requireAuth ? "required" : "optional"), + ); + console.log( + " Landing: GET / " + + (config.landing.enabled ? "ready" : "disabled"), + ); + console.log( + " Prune: storage rules " + + (pruneEnabled + ? `active (${config.storage.rules.length} rules, first run in ${ + config.prune.initialDelayMs / 1000 + }s)` + : "disabled (no rules configured)"), + ); + console.log( + " Admin: dashboard " + + (config.dashboard.enabled + ? `ready (user=${config.dashboard.username}) — http://${hostname}:${port}/admin` + : "disabled"), + ); + }, + }, + app.fetch, +); + +// Graceful shutdown +const shutdown = () => { + console.log("\nShutting down..."); + if (pruneTimeout !== undefined) clearTimeout(pruneTimeout); + pool.shutdown(); + server.shutdown(); + db.close(); + + Deno.exit(0); +}; + +Deno.addSignalListener("SIGINT", shutdown); +Deno.addSignalListener("SIGTERM", shutdown); \ No newline at end of file diff --git a/test/fixtures/TypeScript/main b/test/fixtures/TypeScript/main deleted file mode 100644 index dd69bd5bb3..0000000000 --- a/test/fixtures/TypeScript/main +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env deno -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -const { args, env, exit, readFile } = Deno; -import { parse } from "https://deno.land/std/flags/mod.ts"; - -function pathBase(p: string): string { - const parts = p.split("/"); - return parts[parts.length - 1]; -} - -async function main(): Promise { - const token = env()["GIST_TOKEN"]; - if (!token) { - console.error("GIST_TOKEN environmental variable not set."); - console.error("Get a token here: https://github.com/settings/tokens"); - exit(1); - } - - const parsedArgs = parse(args.slice(1)); - - if (parsedArgs._.length === 0) { - console.error( - "Usage: gist.ts --allow-env --allow-net [-t|--title Example] some_file [next_file]" - ); - exit(1); - } - - const files = {}; - for (const filename of parsedArgs._) { - const base = pathBase(filename); - const content = await readFile(filename); - const contentStr = new TextDecoder().decode(content); - files[base] = { content: contentStr }; - } - - const content = { - description: parsedArgs.title || parsedArgs.t || "Example", - public: false, - files: files - }; - const body = JSON.stringify(content); - - const res = await fetch("https://api.github.com/gists", { - method: "POST", - headers: [ - ["Content-Type", "application/json"], - ["User-Agent", "Deno-Gist"], - ["Authorization", `token ${token}`] - ], - body - }); - - if (res.ok) { - let resObj = await res.json(); - console.log("Success"); - console.log(resObj["html_url"]); - } - else { - let err = await res.text(); - console.error("Failure to POST", err); - } -} - -main(); diff --git a/test/test_language.rb b/test/test_language.rb index 6559d59088..918a7d5bc2 100644 --- a/test/test_language.rb +++ b/test/test_language.rb @@ -207,6 +207,8 @@ def test_find_by_filename def test_find_by_interpreter { + "bun" => ["JavaScript", "TypeScript"], + "deno" => ["JavaScript", "TypeScript"], "ruby" => "Ruby", "Rscript" => "R", "sh" => "Shell", @@ -217,7 +219,8 @@ def test_find_by_interpreter "sbcl" => "Common Lisp", "sclang" => "SuperCollider" }.each do |interpreter, language| - assert_equal [Language[language]], Language.find_by_interpreter(interpreter) + languages = Array(language).map { |lang| Language[lang] } + assert_equal languages, Language.find_by_interpreter(interpreter) end assert_equal [], Language.find_by_interpreter(nil)