chore: import upstream snapshot with attribution
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* End-to-end coverage for `scaffold()`: the filesystem half of init.
|
||||
*
|
||||
* Each test runs against a fresh tempdir so writes don't collide. The
|
||||
* suite's job is to verify the file tree, the overwrite policy, and
|
||||
* the "the scaffold round-trips through the loader" invariant — the
|
||||
* scaffolder shouldn't produce a manifest that its own validator
|
||||
* rejects (when the user has supplied all required fields).
|
||||
*/
|
||||
|
||||
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { InitError, scaffold } from "../src/init/scaffold.js";
|
||||
import type { ScaffoldInputs } from "../src/init/templates.js";
|
||||
import { loadManifest } from "../src/manifest/load.js";
|
||||
|
||||
const FULL_INPUTS: ScaffoldInputs = {
|
||||
slug: "gallery",
|
||||
publisher: "did:plc:abc123def456",
|
||||
publisherHandle: "example.com",
|
||||
license: "MIT",
|
||||
author: { name: "Jane Doe" },
|
||||
security: { email: "security@example.com" },
|
||||
description: undefined,
|
||||
repo: undefined,
|
||||
};
|
||||
|
||||
const MINIMAL_INPUTS: ScaffoldInputs = {
|
||||
slug: "gallery",
|
||||
publisher: undefined,
|
||||
publisherHandle: undefined,
|
||||
license: undefined,
|
||||
author: undefined,
|
||||
security: undefined,
|
||||
description: undefined,
|
||||
repo: undefined,
|
||||
};
|
||||
|
||||
describe("scaffold", () => {
|
||||
let targetDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Each test gets its own tempdir, then immediately removes the
|
||||
// dir so the scaffolder is creating from scratch (matches what
|
||||
// a real `init` invocation does into a brand-new directory).
|
||||
const root = await mkdtemp(join(tmpdir(), "emdash-init-test-"));
|
||||
targetDir = join(root, "plugin");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// rm the parent root, not just targetDir, so we don't leak
|
||||
// the mkdtemp parent behind.
|
||||
const root = targetDir.replace(/\/plugin$/, "");
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("writes the expected file tree", async () => {
|
||||
const result = await scaffold({ targetDir, inputs: FULL_INPUTS, force: false });
|
||||
expect(result.written).toHaveLength(7);
|
||||
|
||||
// Spot-check the structure rather than pinning the array order.
|
||||
const fileSet = new Set(result.written.map((p) => p.replace(`${targetDir}/`, "")));
|
||||
expect(fileSet.has("emdash-plugin.jsonc")).toBe(true);
|
||||
expect(fileSet.has("package.json")).toBe(true);
|
||||
expect(fileSet.has("tsconfig.json")).toBe(true);
|
||||
expect(fileSet.has(".gitignore")).toBe(true);
|
||||
expect(fileSet.has("README.md")).toBe(true);
|
||||
expect(fileSet.has("src/plugin.ts")).toBe(true);
|
||||
expect(fileSet.has("tests/plugin.test.ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("produces a manifest that the loader accepts", async () => {
|
||||
await scaffold({ targetDir, inputs: FULL_INPUTS, force: false });
|
||||
const { manifest } = await loadManifest(targetDir);
|
||||
expect(manifest.slug).toBe("gallery");
|
||||
// `version` is intentionally omitted from the scaffold manifest;
|
||||
// the build reads it from package.json instead.
|
||||
expect(manifest.version).toBeUndefined();
|
||||
expect(manifest.publisher).toBe("did:plc:abc123def456");
|
||||
expect(manifest.license).toBe("MIT");
|
||||
});
|
||||
|
||||
it("produces a minimal manifest that round-trips through the loader (with empty publisher)", async () => {
|
||||
// Minimal scaffold writes TODO placeholders. The loader's JSONC
|
||||
// parse succeeds; the schema rejects on `publisher` being empty.
|
||||
// We catch that explicitly so the user knows what to fix.
|
||||
await scaffold({ targetDir, inputs: MINIMAL_INPUTS, force: false });
|
||||
await expect(loadManifest(targetDir)).rejects.toMatchObject({
|
||||
name: "ManifestError",
|
||||
code: "MANIFEST_VALIDATION_ERROR",
|
||||
});
|
||||
});
|
||||
|
||||
it("refuses to overwrite an existing file without --force", async () => {
|
||||
// Pre-create one of the target files with different content.
|
||||
const { mkdir } = await import("node:fs/promises");
|
||||
await mkdir(targetDir, { recursive: true });
|
||||
await writeFile(join(targetDir, "package.json"), "{}", "utf8");
|
||||
|
||||
await expect(scaffold({ targetDir, inputs: FULL_INPUTS, force: false })).rejects.toMatchObject({
|
||||
name: "InitError",
|
||||
code: "TARGET_FILE_EXISTS",
|
||||
conflicts: ["package.json"],
|
||||
});
|
||||
|
||||
// The original file must be untouched.
|
||||
const contents = await readFile(join(targetDir, "package.json"), "utf8");
|
||||
expect(contents).toBe("{}");
|
||||
});
|
||||
|
||||
it("does not partially write when a conflict is detected", async () => {
|
||||
// Same setup as above. The conflict check runs BEFORE any
|
||||
// write, so nothing else should appear in the target dir.
|
||||
const { mkdir } = await import("node:fs/promises");
|
||||
await mkdir(targetDir, { recursive: true });
|
||||
await writeFile(join(targetDir, "package.json"), "{}", "utf8");
|
||||
|
||||
await expect(scaffold({ targetDir, inputs: FULL_INPUTS, force: false })).rejects.toThrow(
|
||||
InitError,
|
||||
);
|
||||
|
||||
const entries = await readdir(targetDir);
|
||||
// Only the pre-existing file should be there.
|
||||
expect(entries).toEqual(["package.json"]);
|
||||
});
|
||||
|
||||
it("overwrites existing files when --force is set", async () => {
|
||||
const { mkdir } = await import("node:fs/promises");
|
||||
await mkdir(targetDir, { recursive: true });
|
||||
await writeFile(join(targetDir, "package.json"), "{}", "utf8");
|
||||
|
||||
await scaffold({ targetDir, inputs: FULL_INPUTS, force: true });
|
||||
const contents = await readFile(join(targetDir, "package.json"), "utf8");
|
||||
// The scaffold wrote the real package.json over the stub.
|
||||
const parsed = JSON.parse(contents) as { name: string };
|
||||
expect(parsed.name).toBe("gallery");
|
||||
});
|
||||
|
||||
it("creates intermediate directories (src/, tests/)", async () => {
|
||||
await scaffold({ targetDir, inputs: FULL_INPUTS, force: false });
|
||||
const srcStat = await stat(join(targetDir, "src"));
|
||||
const testsStat = await stat(join(targetDir, "tests"));
|
||||
expect(srcStat.isDirectory()).toBe(true);
|
||||
expect(testsStat.isDirectory()).toBe(true);
|
||||
});
|
||||
|
||||
it("invokes onFileWritten once per file in scaffold order", async () => {
|
||||
const calls: string[] = [];
|
||||
await scaffold({
|
||||
targetDir,
|
||||
inputs: FULL_INPUTS,
|
||||
force: false,
|
||||
onFileWritten: (rel) => calls.push(rel),
|
||||
});
|
||||
// The seven files, in some deterministic order (see FILES in
|
||||
// scaffold.ts). The exact order is part of the API surface
|
||||
// for CLI progress output.
|
||||
expect(calls).toEqual([
|
||||
"emdash-plugin.jsonc",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
".gitignore",
|
||||
"README.md",
|
||||
"src/plugin.ts",
|
||||
"tests/plugin.test.ts",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user