diff --git a/package.json b/package.json index d3c40bd..9343430 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "private": true, "scripts": { "prepare-to-publish": "node scripts/prepare-to-publish.ts", - "sort-staged-imports": "npx @biomejs/biome check --formatter-enabled=false --linter-enabled=false --staged --write" + "sort-staged-imports": "npx @biomejs/biome check --formatter-enabled=false --linter-enabled=false --staged --write", + "test": "node --test scripts/lib.test.ts" }, "type": "module", "version": "0.0.0" diff --git a/scripts/lib.test.ts b/scripts/lib.test.ts new file mode 100644 index 0000000..d41d230 --- /dev/null +++ b/scripts/lib.test.ts @@ -0,0 +1,84 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { describe, it } from "node:test"; +import { copyFile, loadExamples } from "./lib.ts"; + +describe("copyFile", () => { + it("copies a file to the destination", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "aj-test-")); + try { + const src = path.join(tmpDir, "src.txt"); + const dst = path.join(tmpDir, "subdir", "dst.txt"); + await fs.writeFile(src, "hello"); + await copyFile(src, dst); + const content = await fs.readFile(dst, "utf-8"); + assert.equal(content, "hello"); + } finally { + await fs.rm(tmpDir, { recursive: true }); + } + }); + + it("creates intermediate directories", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "aj-test-")); + try { + const src = path.join(tmpDir, "src.txt"); + const dst = path.join(tmpDir, "a", "b", "c", "dst.txt"); + await fs.writeFile(src, "world"); + await copyFile(src, dst); + const content = await fs.readFile(dst, "utf-8"); + assert.equal(content, "world"); + } finally { + await fs.rm(tmpDir, { recursive: true }); + } + }); +}); + +describe("loadExamples", () => { + it("returns an empty array when examples dir is empty", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "aj-test-")); + try { + await fs.mkdir(path.join(tmpDir, "examples")); + const examples = await loadExamples(tmpDir); + assert.deepEqual(examples, []); + } finally { + await fs.rm(tmpDir, { recursive: true }); + } + }); + + it("returns workspace tuples for each example directory", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "aj-test-")); + try { + await fs.mkdir(path.join(tmpDir, "examples", "astro"), { + recursive: true, + }); + await fs.mkdir(path.join(tmpDir, "examples", "nextjs"), { + recursive: true, + }); + const examples = await loadExamples(tmpDir); + assert.deepEqual(examples, [ + ["@arcjet-examples/astro", path.join(tmpDir, "examples", "astro")], + ["@arcjet-examples/nextjs", path.join(tmpDir, "examples", "nextjs")], + ]); + } finally { + await fs.rm(tmpDir, { recursive: true }); + } + }); + + it("ignores non-directory entries", async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "aj-test-")); + try { + await fs.mkdir(path.join(tmpDir, "examples", "astro"), { + recursive: true, + }); + await fs.writeFile(path.join(tmpDir, "examples", "file.txt"), ""); + const examples = await loadExamples(tmpDir); + assert.deepEqual(examples, [ + ["@arcjet-examples/astro", path.join(tmpDir, "examples", "astro")], + ]); + } finally { + await fs.rm(tmpDir, { recursive: true }); + } + }); +}); diff --git a/scripts/lib.ts b/scripts/lib.ts new file mode 100644 index 0000000..a0f85ce --- /dev/null +++ b/scripts/lib.ts @@ -0,0 +1,44 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { SimpleGit } from "simple-git"; + +/** + * Copies a file from one location to another, creating the necessary directories. + */ +export async function copyFile(from: string, to: string): Promise { + await fs.mkdir(path.dirname(to), { recursive: true }); + await fs.copyFile(from, to); +} + +/** + * Lists all tracked files in a git repository at a given path. + */ +export async function listTrackedFiles( + git: SimpleGit, + filePath?: string, +): Promise { + const filesRaw = await git.raw( + typeof filePath === "string" ? ["ls-files", filePath] : ["ls-files"], + ); + return filesRaw + .split("\n") + .map((f) => f.trim()) + .filter(Boolean); +} + +/** + * Loads all example workspaces from the `examples/` directory. + * Returns an array of [workspaceName, workspacePath] tuples. + */ +export async function loadExamples( + baseDir: string, +): Promise<[string, string][]> { + const examplesDir = path.join(baseDir, "examples"); + const entries = await fs.readdir(examplesDir, { withFileTypes: true }); + return entries + .filter((entry) => entry.isDirectory()) + .map((entry) => [ + `@arcjet-examples/${entry.name}`, + path.join(examplesDir, entry.name), + ]); +} diff --git a/scripts/prepare-to-publish.ts b/scripts/prepare-to-publish.ts index 67832ed..dddf102 100644 --- a/scripts/prepare-to-publish.ts +++ b/scripts/prepare-to-publish.ts @@ -1,33 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { type SimpleGit, simpleGit } from "simple-git"; +import { simpleGit } from "simple-git"; +import { copyFile, listTrackedFiles, loadExamples } from "./lib.ts"; const BASE_PATH = path.join(import.meta.dirname, ".."); -/** - * Copies a file from one location to another, creating the necessary directories. - */ -async function copyFile(from: string, to: string): Promise { - await fs.mkdir(path.dirname(to), { recursive: true }); - await fs.copyFile(from, to); -} - -/** - * Lists all tracked files in a git repository at a given path. - */ -async function listTrackedFiles( - git: SimpleGit, - path?: string, -): Promise { - const filesRaw = await git.raw( - typeof path === "string" ? ["ls-files", path] : ["ls-files"], - ); - return filesRaw - .split("\n") - .map((f) => f.trim()) - .filter(Boolean); -} - const git = simpleGit({ baseDir: BASE_PATH, }); @@ -48,47 +25,7 @@ if (!status.isClean()) { process.exit(1); } -// TODO(#31): Add an improved loading mechanism for workspaces -const workspaces = [ - ["@arcjet-examples/astro", path.join(BASE_PATH, "./examples/astro")], - ["@arcjet-examples/deno", path.join(BASE_PATH, "./examples/deno")], - ["@arcjet-examples/expressjs", path.join(BASE_PATH, "./examples/expressjs")], - ["@arcjet-examples/fastapi", path.join(BASE_PATH, "./examples/fastapi")], - ["@arcjet-examples/fastify", path.join(BASE_PATH, "./examples/fastify")], - [ - "@arcjet-examples/firebase-functions", - path.join(BASE_PATH, "./examples/firebase-functions"), - ], - ["@arcjet-examples/flask", path.join(BASE_PATH, "./examples/flask")], - ["@arcjet-examples/nestjs", path.join(BASE_PATH, "./examples/nestjs")], - [ - "@arcjet-examples/nextjs-bot-protection", - path.join(BASE_PATH, "./examples/nextjs-bot-protection"), - ], - [ - "@arcjet-examples/nextjs-fly", - path.join(BASE_PATH, "./examples/nextjs-fly"), - ], - [ - "@arcjet-examples/nextjs-form", - path.join(BASE_PATH, "./examples/nextjs-form"), - ], - [ - "@arcjet-examples/nextjs-server-action", - path.join(BASE_PATH, "./examples/nextjs-server-action"), - ], - ["@arcjet-examples/nextjs", path.join(BASE_PATH, "./examples/nextjs")], - ["@arcjet-examples/nuxt", path.join(BASE_PATH, "./examples/nuxt")], - [ - "@arcjet-examples/react-router", - path.join(BASE_PATH, "./examples/react-router"), - ], - ["@arcjet-examples/sveltekit", path.join(BASE_PATH, "./examples/sveltekit")], - [ - "@arcjet-examples/tanstack-start", - path.join(BASE_PATH, "./examples/tanstack-start"), - ], -] satisfies [string, string][]; +const workspaces = await loadExamples(BASE_PATH); const BUILD_PATH = path.join(BASE_PATH, "dist"); @@ -118,8 +55,7 @@ for (const [workspaceName, workspacePath] of workspaces) { // Intentionally left blank } - // Fallback to reading example.json if package.json is not found - // TODO(#31): Rework this script + // Fallback to reading package.json if arcjet-example.json is not found if (!raw) { raw = await fs.readFile(path.join(workspacePath, "package.json"), "utf-8"); }