diff --git a/packages/create-fs/README.md b/packages/create-fs/README.md
new file mode 100644
index 00000000..b2435a92
--- /dev/null
+++ b/packages/create-fs/README.md
@@ -0,0 +1,17 @@
+
create-fs
+
+The file system used by create
. ๐๏ธ
+
+
+
+
+
+
+
+
+
+See **[create.bingo](https://create.bingo)** for documentation.
+Specifically:
+
+- [Packages > `create-fs`](https://www.create.bingo/engine/packages/create-fs): for this package's documentation
+- [Runtime > Creations > `files`](https://www.create.bingo/engine/runtime/creations#files): for where these are used
diff --git a/packages/create-fs/package.json b/packages/create-fs/package.json
new file mode 100644
index 00000000..a7d4d1a1
--- /dev/null
+++ b/packages/create-fs/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "create-fs",
+ "version": "0.1.0",
+ "description": "The file system used by create>. ๐๏ธ",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/JoshuaKGoldberg/create",
+ "directory": "packages/create-fs"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Josh Goldberg โจ",
+ "email": "npm@joshuakgoldberg.com"
+ },
+ "type": "module",
+ "main": "./lib/index.js",
+ "files": [
+ "lib/",
+ "package.json",
+ "LICENSE.md",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+}
diff --git a/packages/create-fs/src/createReadingFileSystem.ts b/packages/create-fs/src/createReadingFileSystem.ts
new file mode 100644
index 00000000..acfb824c
--- /dev/null
+++ b/packages/create-fs/src/createReadingFileSystem.ts
@@ -0,0 +1,11 @@
+import * as fs from "node:fs/promises";
+
+import { ReadingFileSystem } from "./types/system.js";
+
+export function createReadingFileSystem(): ReadingFileSystem {
+ return {
+ readDirectory: async (filePath: string) => await fs.readdir(filePath),
+ readFile: async (filePath: string) =>
+ (await fs.readFile(filePath)).toString(),
+ };
+}
diff --git a/packages/create-fs/src/createWritingFileSystem.ts b/packages/create-fs/src/createWritingFileSystem.ts
new file mode 100644
index 00000000..72a6efa0
--- /dev/null
+++ b/packages/create-fs/src/createWritingFileSystem.ts
@@ -0,0 +1,14 @@
+import * as fs from "node:fs/promises";
+
+import { createReadingFileSystem } from "./createReadingFileSystem.js";
+
+export function createWritingFileSystem() {
+ return {
+ ...createReadingFileSystem(),
+ writeDirectory: async (directoryPath: string) =>
+ void (await fs.mkdir(directoryPath, { recursive: true })),
+ writeFile: async (filePath: string, contents: string) => {
+ await fs.writeFile(filePath, contents);
+ },
+ };
+}
diff --git a/packages/create-fs/src/index.ts b/packages/create-fs/src/index.ts
new file mode 100644
index 00000000..f7c8e225
--- /dev/null
+++ b/packages/create-fs/src/index.ts
@@ -0,0 +1,5 @@
+export * from "./createReadingFileSystem.js";
+export * from "./createWritingFileSystem.js";
+export * from "./intake/intakeFromDirectory.js";
+export type * from "./types/files.js";
+export type * from "./types/system.js";
diff --git a/packages/create-fs/src/intake/intakeFromDirectory.test.ts b/packages/create-fs/src/intake/intakeFromDirectory.test.ts
new file mode 100644
index 00000000..6626f0c5
--- /dev/null
+++ b/packages/create-fs/src/intake/intakeFromDirectory.test.ts
@@ -0,0 +1,119 @@
+import { describe, expect, it, vi } from "vitest";
+
+import { intakeFromDirectory } from "./intakeFromDirectory.js";
+
+const mockReaddir = vi.fn();
+const mockReadFile = vi.fn();
+const mockStat = vi.fn();
+
+vi.mock("node:fs/promises", () => ({
+ get readdir() {
+ return mockReaddir;
+ },
+ get readFile() {
+ return mockReadFile;
+ },
+ get stat() {
+ return mockStat;
+ },
+}));
+
+describe("intakeFromDirectory", () => {
+ it("returns an empty object when the directory has no files", async () => {
+ mockReaddir.mockResolvedValueOnce([]);
+
+ const directory = await intakeFromDirectory("from");
+
+ expect(directory).toEqual({});
+ expect(mockReaddir.mock.calls).toEqual([["from"]]);
+ expect(mockStat).not.toHaveBeenCalled();
+ });
+
+ it("returns files when the directory contains files", async () => {
+ mockReaddir.mockResolvedValueOnce(["included-a", "included-b"]);
+ mockReadFile
+ .mockResolvedValueOnce("contents-a")
+ .mockResolvedValueOnce("contents-b");
+ mockStat
+ .mockResolvedValueOnce({
+ isDirectory: () => false,
+ mode: 123,
+ })
+ .mockResolvedValueOnce({
+ isDirectory: () => false,
+ mode: 456,
+ });
+
+ const directory = await intakeFromDirectory("from");
+
+ expect(directory).toEqual({
+ "included-a": ["contents-a", { mode: 123 }],
+ "included-b": ["contents-b", { mode: 456 }],
+ });
+ expect(mockReaddir.mock.calls).toEqual([["from"]]);
+ expect(mockStat.mock.calls).toEqual([
+ ["from/included-a"],
+ ["from/included-b"],
+ ]);
+ });
+
+ it("returns non-excluded files when the directory contains files and excludes is provided", async () => {
+ mockReaddir.mockResolvedValueOnce(["excluded", "included-a", "included-b"]);
+ mockReadFile
+ .mockResolvedValueOnce("contents-a")
+ .mockResolvedValueOnce("contents-b");
+ mockStat
+ .mockResolvedValueOnce({
+ isDirectory: () => false,
+ mode: 123,
+ })
+ .mockResolvedValueOnce({
+ isDirectory: () => false,
+ mode: 456,
+ });
+
+ const directory = await intakeFromDirectory("from", {
+ exclude: /excluded/,
+ });
+
+ expect(directory).toEqual({
+ "included-a": ["contents-a", { mode: 123 }],
+ "included-b": ["contents-b", { mode: 456 }],
+ });
+ expect(mockReaddir.mock.calls).toEqual([["from"]]);
+ expect(mockStat.mock.calls).toEqual([
+ ["from/included-a"],
+ ["from/included-b"],
+ ]);
+ });
+
+ it("returns a nested file when the directory contains a nested directory", async () => {
+ mockReaddir
+ .mockResolvedValueOnce(["middle"])
+ .mockResolvedValueOnce(["excluded", "included"]);
+ mockReadFile.mockResolvedValueOnce("contents");
+ mockStat
+ .mockResolvedValueOnce({
+ isDirectory: () => true,
+ })
+ .mockResolvedValueOnce({
+ isDirectory: () => false,
+ mode: 123,
+ });
+
+ const directory = await intakeFromDirectory("from", {
+ exclude: /excluded/,
+ });
+
+ expect(directory).toEqual({
+ middle: {
+ included: ["contents", { mode: 123 }],
+ },
+ });
+ expect(mockReaddir.mock.calls).toEqual([["from"], ["from/middle"]]);
+ expect(mockStat.mock.calls).toEqual([
+ ["from/middle"],
+ ["from/middle/included"],
+ ]);
+ });
+});
diff --git a/packages/create-fs/src/intake/intakeFromDirectory.ts b/packages/create-fs/src/intake/intakeFromDirectory.ts
new file mode 100644
index 00000000..4c8cc829
--- /dev/null
+++ b/packages/create-fs/src/intake/intakeFromDirectory.ts
@@ -0,0 +1,31 @@
+import * as fs from "node:fs/promises";
+import path from "node:path";
+
+import { CreatedDirectory } from "../types/files.js";
+
+export interface IntakeFromDirectorySettings {
+ exclude?: RegExp;
+}
+
+export async function intakeFromDirectory(
+ directoryPath: string,
+ settings: IntakeFromDirectorySettings = {},
+) {
+ const directory: CreatedDirectory = {};
+ const children = await fs.readdir(directoryPath);
+
+ for (const child of children) {
+ if (settings.exclude?.test(child)) {
+ continue;
+ }
+
+ const childPath = path.join(directoryPath, child);
+ const stat = await fs.stat(childPath);
+
+ directory[child] = stat.isDirectory()
+ ? await intakeFromDirectory(childPath, settings)
+ : [(await fs.readFile(childPath)).toString(), { mode: stat.mode }];
+ }
+
+ return directory;
+}
diff --git a/packages/create-fs/src/types/files.ts b/packages/create-fs/src/types/files.ts
new file mode 100644
index 00000000..456566a8
--- /dev/null
+++ b/packages/create-fs/src/types/files.ts
@@ -0,0 +1,18 @@
+export interface CreatedDirectory {
+ [i: string]: CreatedFileEntry | undefined;
+}
+
+export type CreatedFileEntry =
+ | [string, CreatedFileOptions]
+ | [string]
+ | CreatedDirectory
+ | false
+ | string;
+
+export interface CreatedFileOptions {
+ /**
+ * File mode (permission and sticky bits) per chmod().
+ * @example 0o777 for an executable file.
+ */
+ mode?: number;
+}
diff --git a/packages/create-fs/src/types/system.ts b/packages/create-fs/src/types/system.ts
new file mode 100644
index 00000000..578b3687
--- /dev/null
+++ b/packages/create-fs/src/types/system.ts
@@ -0,0 +1,30 @@
+export interface DirectoryChild {
+ name: string;
+ type: "directory" | "file";
+}
+
+export type ReadDirectory = (filePath: string) => Promise;
+
+export type ReadFile = (filePath: string) => Promise;
+
+export interface ReadingFileSystem {
+ readDirectory: ReadDirectory;
+ readFile: ReadFile;
+}
+
+export type WriteDirectory = (directoryPath: string) => Promise;
+
+export type WriteFile = (
+ filePath: string,
+ contents: string,
+ options?: WriteFileOptions,
+) => Promise;
+
+export interface WriteFileOptions {
+ mode?: number;
+}
+
+export interface WritingFileSystem extends ReadingFileSystem {
+ writeDirectory: WriteDirectory;
+ writeFile: WriteFile;
+}
diff --git a/packages/create-fs/tsconfig.json b/packages/create-fs/tsconfig.json
new file mode 100644
index 00000000..de061cbc
--- /dev/null
+++ b/packages/create-fs/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "src"
+ },
+ "extends": "../../tsconfig.base.json",
+ "include": ["src"]
+}
diff --git a/packages/create-fs/vitest.config.ts b/packages/create-fs/vitest.config.ts
new file mode 100644
index 00000000..5966133b
--- /dev/null
+++ b/packages/create-fs/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineProject } from "vitest/config";
+
+export default defineProject({
+ test: {
+ clearMocks: true,
+ exclude: ["lib", "node_modules"],
+ },
+});
diff --git a/packages/create-testers/package.json b/packages/create-testers/package.json
index 4132add6..001099ec 100644
--- a/packages/create-testers/package.json
+++ b/packages/create-testers/package.json
@@ -24,13 +24,17 @@
"build": "tsc"
},
"dependencies": {
- "octokit": "^4.1.0"
+ "diff": "^7.0.0",
+ "octokit": "^4.1.0",
+ "without-undefined-properties": "^0.1.1"
},
"devDependencies": {
+ "@types/diff": "^7.0.0",
"zod": "3.24.1"
},
"peerDependencies": {
- "create": "workspace:^"
+ "create": "workspace:^",
+ "create-fs": "workspace:^"
},
"engines": {
"node": ">=18"
diff --git a/packages/create-testers/src/createMockSystems.ts b/packages/create-testers/src/createMockSystems.ts
index 38d41018..16a265bd 100644
--- a/packages/create-testers/src/createMockSystems.ts
+++ b/packages/create-testers/src/createMockSystems.ts
@@ -1,4 +1,5 @@
-import { NativeSystem, TakeInput, WritingFileSystem } from "create";
+import { NativeSystem, TakeInput } from "create";
+import { WritingFileSystem } from "create-fs";
import { Octokit } from "octokit";
import { MockSystemOptions } from "./types.js";
@@ -20,6 +21,7 @@ export function createMockSystems(
};
const fs: WritingFileSystem = {
+ readDirectory: createFailingFunction("fs.readDirectory", "an input"),
readFile: createFailingFunction("fs.readFile", "an input"),
writeDirectory: createFailingFunction("fs.writeDirectory", "an input"),
writeFile: createFailingFunction("fs.writeFile", "an input"),
diff --git a/packages/create-testers/src/diffCreatedDirectory.test.ts b/packages/create-testers/src/diffCreatedDirectory.test.ts
new file mode 100644
index 00000000..2775b8dc
--- /dev/null
+++ b/packages/create-testers/src/diffCreatedDirectory.test.ts
@@ -0,0 +1,139 @@
+import { CreatedDirectory } from "create-fs";
+import { describe, expect, test } from "vitest";
+
+import {
+ diffCreatedDirectory,
+ DiffedCreatedDirectory,
+} from "./diffCreatedDirectory.js";
+
+describe("diffCreatedDirectory", () => {
+ test.each([
+ [{}, {}, undefined],
+ [{}, { a: "" }, { a: "" }],
+ [{}, { a: { b: "" } }, { a: { b: "" } }],
+ [{ a: "" }, { a: "" }, undefined],
+ [
+ { a: "" },
+ { a: "b\n" },
+ {
+ a: `@@ -0,0 +1,1 @@
++b
+`,
+ },
+ ],
+ [{ a: "b\n" }, { a: "b\n" }, undefined],
+ [
+ { a: "abc\n" },
+ { a: "bbc\n" },
+ {
+ a: `@@ -1,1 +1,1 @@
+-abc
++bbc
+`,
+ },
+ ],
+ [{ a: "b\n" }, {}, undefined],
+ [{ a: "" }, { a: [""] }, undefined],
+ [{ a: "" }, { a: ["", { mode: undefined }] }, undefined],
+ [{ a: "" }, { a: ["", { mode: 123 }] }, undefined],
+ [{ a: [""] }, { a: ["", { mode: 123 }] }, undefined],
+ [
+ { a: ["", { mode: undefined }] },
+ { a: ["", { mode: undefined }] },
+ undefined,
+ ],
+ [{ a: ["", { mode: undefined }] }, { a: ["", { mode: 123 }] }, undefined],
+ [{ a: ["", { mode: 123 }] }, { a: ["", { mode: 123 }] }, undefined],
+ [{ a: ["", { mode: 123 }] }, { a: [""] }, undefined],
+ [{ a: ["", { mode: 123 }] }, { a: ["", {}] }, undefined],
+ [{ a: ["", { mode: 123 }] }, { a: ["", { mode: undefined }] }, undefined],
+ [
+ { a: ["", { mode: 123 }] },
+ { a: ["", { mode: 456 }] },
+ {
+ a: [
+ undefined,
+ {
+ mode: `@@ -1,1 +1,1 @@
+-7b
+\\ No newline at end of file
++1c8
+\\ No newline at end of file
+`,
+ },
+ ],
+ },
+ ],
+ [
+ { a: "" },
+ { a: { b: {} } },
+ { a: "Mismatched a: actual is string; created is object." },
+ ],
+ [
+ { a: [""] },
+ { a: { b: {} } },
+ { a: "Mismatched a: actual is created file; created is object." },
+ ],
+ [{ a: [""] }, { a: "" }, undefined],
+ [
+ { a: ["b\n"] },
+ { a: "" },
+ {
+ a: `@@ -1,1 +0,0 @@
+-b
+`,
+ },
+ ],
+ [
+ { a: { b: {} } },
+ { a: "" },
+ { a: "Mismatched a: actual is object; created is string." },
+ ],
+ [
+ { a: { b: {} } },
+ { a: [""] },
+ { a: "Mismatched a: actual is object; created is created file." },
+ ],
+ [{ a: "" }, { a: [""] }, undefined],
+ [{ a: { b: "c\n" } }, {}, undefined],
+ [{ a: { b: "c\n" } }, { b: {} }, undefined],
+ [{ a: { b: "c\n" } }, { a: { b: "c\n" } }, undefined],
+ [
+ { a: { b: "c\n" } },
+ { a: { b: "d\n" } },
+ {
+ a: {
+ b: `@@ -1,1 +1,1 @@
+-c
++d
+`,
+ },
+ },
+ ],
+ [{ a: { b: "c\n" } }, { a: { d: "e\n" } }, { a: { d: "e\n" } }],
+ [
+ { a: { b: { c: undefined } } },
+ { a: { b: { c: { d: "e\n" } } } },
+ { a: { b: { c: { d: "e\n" } } } },
+ ],
+ [
+ { a: { b: { c: { d: "e\n" } } } },
+ { a: { b: { c: undefined } } },
+ undefined,
+ ],
+ [{ a: { b: "c\n" } }, { a: { d: "e\n" } }, { a: { d: "e\n" } }],
+ [
+ { a: { b: { c: { d: "e\n" } } } },
+ { a: { b: { f: "g\n" } } },
+ { a: { b: { f: "g\n" } } },
+ ],
+ ] satisfies [
+ CreatedDirectory,
+ CreatedDirectory,
+ DiffedCreatedDirectory | undefined,
+ ][])("%j and %j", (actual, created, expected) => {
+ expect(diffCreatedDirectory(actual, created, (text) => text)).toEqual(
+ expected,
+ );
+ });
+});
diff --git a/packages/create-testers/src/diffCreatedDirectory.ts b/packages/create-testers/src/diffCreatedDirectory.ts
new file mode 100644
index 00000000..172f3b77
--- /dev/null
+++ b/packages/create-testers/src/diffCreatedDirectory.ts
@@ -0,0 +1,203 @@
+import {
+ CreatedDirectory,
+ CreatedFileEntry,
+ CreatedFileOptions,
+} from "create-fs";
+import { createTwoFilesPatch } from "diff";
+import path from "node:path";
+import { withoutUndefinedProperties } from "without-undefined-properties";
+
+export interface DiffedCreatedDirectory {
+ [i: string]: DiffedCreatedDirectory | DiffedCreatedFileEntry | undefined;
+}
+
+export type DiffedCreatedFileEntry =
+ | [string]
+ | [string | undefined, DiffedCreatedFileOptions?]
+ | CreatedFileEntry
+ | DiffedCreatedDirectory
+ | string;
+
+export interface DiffedCreatedFileOptions {
+ mode?: string;
+}
+
+export type ProcessText = (text: string, filePath: string) => string;
+
+export function diffCreatedDirectory(
+ actual: CreatedDirectory,
+ created: CreatedDirectory,
+ processText: ProcessText,
+): DiffedCreatedDirectory | undefined {
+ const result = diffCreatedDirectoryWorker(actual, created, ".", processText);
+
+ return result && withoutUndefinedProperties(result);
+}
+
+function diffCreatedDirectoryChild(
+ childActual: CreatedFileEntry | undefined,
+ childCreated: CreatedFileEntry | undefined,
+ pathToChild: string,
+ processText: ProcessText,
+): DiffedCreatedFileEntry | undefined {
+ if (childActual === undefined) {
+ return childCreated;
+ }
+
+ if (childCreated === undefined) {
+ return undefined;
+ }
+
+ if (typeof childActual === "string") {
+ if (typeof childCreated === "string") {
+ return diffCreatedFileText(
+ childActual,
+ childCreated,
+ pathToChild,
+ processText,
+ );
+ }
+ }
+
+ if (Array.isArray(childActual)) {
+ if (Array.isArray(childCreated)) {
+ const fileDiff = diffCreatedFileText(
+ childActual[0],
+ childCreated[0],
+ pathToChild,
+ processText,
+ );
+ const optionsDiff = diffCreatedFileOptions(
+ childActual[1],
+ childCreated[1],
+ pathToChild,
+ );
+
+ return (fileDiff ?? optionsDiff) ? [fileDiff, optionsDiff] : undefined;
+ }
+
+ if (typeof childCreated === "string") {
+ return diffCreatedFileText(
+ childActual[0],
+ childCreated,
+ pathToChild,
+ processText,
+ );
+ }
+
+ return `Mismatched ${pathToChild}: actual is created file; created is ${typeof childCreated}.`;
+ }
+
+ if (Array.isArray(childCreated)) {
+ if (typeof childActual === "string") {
+ return diffCreatedFileText(
+ childActual,
+ childCreated[0],
+ pathToChild,
+ processText,
+ );
+ }
+
+ return `Mismatched ${pathToChild}: actual is ${typeof childActual}; created is created file.`;
+ }
+
+ if (typeof childActual === "object") {
+ if (typeof childCreated === "object") {
+ return diffCreatedDirectoryWorker(
+ childActual,
+ childCreated,
+ pathToChild,
+ processText,
+ );
+ }
+ }
+
+ return `Mismatched ${pathToChild}: actual is ${typeof childActual}; created is ${typeof childCreated}.`;
+}
+
+function diffCreatedDirectoryWorker(
+ actual: CreatedDirectory,
+ created: CreatedDirectory,
+ pathTo: string,
+ processText: ProcessText,
+): DiffedCreatedDirectory | undefined {
+ const result: DiffedCreatedDirectory = {};
+
+ for (const [childName, childCreated] of Object.entries(created)) {
+ if (!(childName in actual)) {
+ result[childName] = undefinedIfEmpty(childCreated);
+ continue;
+ }
+
+ const childActual = actual[childName];
+ const pathToChild = path.join(pathTo, childName);
+
+ const childDiffed = diffCreatedDirectoryChild(
+ childActual,
+ childCreated,
+ pathToChild,
+ processText,
+ );
+
+ if (childDiffed !== undefined) {
+ result[childName] = childDiffed;
+ }
+ }
+
+ return undefinedIfEmpty(withoutUndefinedProperties(result));
+}
+
+function diffCreatedFileText(
+ actual: string,
+ created: string,
+ pathToFile: string,
+ processText: ProcessText,
+) {
+ const actualProcessed = processText(created, pathToFile);
+ const createdProcessed = processText(actual, pathToFile);
+
+ return actualProcessed === createdProcessed
+ ? undefined
+ : createTwoFilesPatch(
+ pathToFile,
+ pathToFile,
+ createdProcessed,
+ actualProcessed,
+ ).replace(/^Index: .+\n=+\n-{3} .+\n\+{3} .+\n/gmu, "");
+}
+
+/**
+ * @todo
+ * Unclear yet how to represent a diff in the mode...
+ * Should a DiffedFileEntry type replace CreatedFileOptions.mode with string?
+ */
+function diffCreatedFileOptions(
+ actual: CreatedFileOptions | undefined,
+ created: CreatedFileOptions | undefined,
+ pathToFile: string,
+): DiffedCreatedFileOptions | undefined {
+ if (
+ actual?.mode === undefined ||
+ created?.mode === undefined ||
+ actual.mode === created.mode
+ ) {
+ return undefined;
+ }
+
+ return {
+ mode: diffCreatedFileText(
+ actual.mode.toString(16),
+ created.mode.toString(16),
+ pathToFile,
+ (text) => text,
+ ),
+ };
+}
+function undefinedIfEmpty(value: T) {
+ return !!value &&
+ typeof value === "object" &&
+ !Array.isArray(value) &&
+ Object.keys(value).length === 0
+ ? undefined
+ : value;
+}
diff --git a/packages/create-testers/src/index.ts b/packages/create-testers/src/index.ts
index 297b2e02..c1838ff4 100644
--- a/packages/create-testers/src/index.ts
+++ b/packages/create-testers/src/index.ts
@@ -1,3 +1,4 @@
+export { diffCreatedDirectory } from "./diffCreatedDirectory.js";
export { testBase } from "./testBase.js";
export { testBlock } from "./testBlock.js";
export { testInput } from "./testInput.js";
diff --git a/packages/create-testers/src/types.ts b/packages/create-testers/src/types.ts
index c2af93cb..42ba020d 100644
--- a/packages/create-testers/src/types.ts
+++ b/packages/create-testers/src/types.ts
@@ -1,4 +1,5 @@
-import { SystemRunner, TakeInput, WritingFileSystem } from "create";
+import { SystemRunner, TakeInput } from "create";
+import { WritingFileSystem } from "create-fs";
export interface MockSystemOptions {
fetch?: typeof fetch;
diff --git a/packages/create/package.json b/packages/create/package.json
index b787775e..29b9b207 100644
--- a/packages/create/package.json
+++ b/packages/create/package.json
@@ -28,6 +28,7 @@
"@clack/prompts": "^0.9.1",
"cached-factory": "^0.1.0",
"chalk": "^5.4.1",
+ "create-fs": "workspace:^",
"execa": "^9.5.2",
"get-github-auth-token": "^0.1.0",
"hash-object": "^5.0.1",
@@ -40,7 +41,8 @@
"zod": "^3.24.1"
},
"devDependencies": {
- "@types/hosted-git-info": "3.0.5"
+ "@types/hosted-git-info": "3.0.5",
+ "@types/node": "^22.10.5"
},
"engines": {
"node": ">=18"
diff --git a/packages/create/src/cli/prompts/promptForBaseOptions.test.ts b/packages/create/src/cli/prompts/promptForBaseOptions.test.ts
index 697fe463..0bef89c3 100644
--- a/packages/create/src/cli/prompts/promptForBaseOptions.test.ts
+++ b/packages/create/src/cli/prompts/promptForBaseOptions.test.ts
@@ -27,6 +27,7 @@ const system = {
octokit: {} as Octokit,
},
fs: {
+ readDirectory: vi.fn(),
readFile: vi.fn(),
writeDirectory: vi.fn(),
writeFile: vi.fn(),
diff --git a/packages/create/src/creators/createInput.test.ts b/packages/create/src/creators/createInput.test.ts
index 23c986fb..854e5931 100644
--- a/packages/create/src/creators/createInput.test.ts
+++ b/packages/create/src/creators/createInput.test.ts
@@ -14,7 +14,10 @@ describe("createInput", () => {
const actual = input({
fetchers: createSystemFetchers({ fetch: vi.fn() }),
- fs: { readFile: vi.fn() },
+ fs: {
+ readDirectory: vi.fn(),
+ readFile: vi.fn(),
+ },
runner: vi.fn(),
take: vi.fn(),
});
@@ -37,7 +40,10 @@ describe("createInput", () => {
offset: 1000,
},
fetchers: createSystemFetchers({ fetch: vi.fn() }),
- fs: { readFile: vi.fn() },
+ fs: {
+ readDirectory: vi.fn(),
+ readFile: vi.fn(),
+ },
runner: vi.fn(),
take: vi.fn(),
});
diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts
index 35076a9e..d6db9e6b 100644
--- a/packages/create/src/index.ts
+++ b/packages/create/src/index.ts
@@ -23,7 +23,6 @@ export * from "./runners/runPreset.js";
// TODO: These might be better as their own packages?
export * from "./runners/applyFilesToSystem.js";
export * from "./system/createSystemContext.js";
-export * from "./system/createWritingFileSystem.js";
export * from "./utils/awaitLazyProperties.js";
// Types
diff --git a/packages/create/src/mergers/mergeFileCreations.test.ts b/packages/create/src/mergers/mergeCreatedDirectories.test.ts
similarity index 68%
rename from packages/create/src/mergers/mergeFileCreations.test.ts
rename to packages/create/src/mergers/mergeCreatedDirectories.test.ts
index c73ee7b3..87c59fac 100644
--- a/packages/create/src/mergers/mergeFileCreations.test.ts
+++ b/packages/create/src/mergers/mergeCreatedDirectories.test.ts
@@ -1,7 +1,7 @@
+import { CreatedDirectory } from "create-fs";
import { describe, expect, test } from "vitest";
-import { CreatedFiles } from "../types/creations.js";
-import { mergeFileCreations } from "./mergeFileCreations.js";
+import { mergeCreatedDirectories } from "./mergeCreatedDirectories.js";
describe("mergeFileCreations", () => {
test.each([
@@ -27,13 +27,21 @@ describe("mergeFileCreations", () => {
{ a: "" },
"Conflicting created directory and file at path: 'a'.",
],
- ] satisfies [CreatedFiles, CreatedFiles, CreatedFiles | string][])(
+ ] satisfies [
+ CreatedDirectory,
+ CreatedDirectory,
+ CreatedDirectory | string,
+ ][])(
"%j with %j",
- (first: CreatedFiles, second: CreatedFiles, expected?: object | string) => {
+ (
+ first: CreatedDirectory,
+ second: CreatedDirectory,
+ expected?: object | string,
+ ) => {
if (typeof expected === "string") {
- expect(() => mergeFileCreations(first, second)).toThrow(expected);
+ expect(() => mergeCreatedDirectories(first, second)).toThrow(expected);
} else {
- expect(mergeFileCreations(first, second)).toEqual(expected);
+ expect(mergeCreatedDirectories(first, second)).toEqual(expected);
}
},
);
diff --git a/packages/create/src/mergers/mergeFileCreations.ts b/packages/create/src/mergers/mergeCreatedDirectories.ts
similarity index 60%
rename from packages/create/src/mergers/mergeFileCreations.ts
rename to packages/create/src/mergers/mergeCreatedDirectories.ts
index af8d7cdb..f5583a86 100644
--- a/packages/create/src/mergers/mergeFileCreations.ts
+++ b/packages/create/src/mergers/mergeCreatedDirectories.ts
@@ -1,19 +1,20 @@
-import { CreatedFiles } from "../types/creations.js";
+import { CreatedDirectory } from "create-fs";
+
import { mergeFileEntries } from "./mergeFileEntries.js";
-export function mergeFileCreations(
- firsts: CreatedFiles,
- seconds: CreatedFiles,
+export function mergeCreatedDirectories(
+ firsts: CreatedDirectory,
+ seconds: CreatedDirectory,
) {
- return mergeFileCreationsWorker(firsts, seconds, []);
+ return mergeCreatedDirectoriesWorker(firsts, seconds, []);
}
-function mergeFileCreationsWorker(
- firsts: CreatedFiles,
- seconds: CreatedFiles,
+function mergeCreatedDirectoriesWorker(
+ firsts: CreatedDirectory,
+ seconds: CreatedDirectory,
path: string[],
) {
- const result: CreatedFiles = { ...firsts };
+ const result: CreatedDirectory = { ...firsts };
for (const i in seconds) {
const second = seconds[i];
@@ -36,7 +37,11 @@ function mergeFileCreationsWorker(
}
result[i] = firstIsDirectory
- ? mergeFileCreationsWorker(first, second as CreatedFiles, nextPath)
+ ? mergeCreatedDirectoriesWorker(
+ first,
+ second as CreatedDirectory,
+ nextPath,
+ )
: mergeFileEntries(first, second, nextPath);
}
diff --git a/packages/create/src/mergers/mergeCreations.ts b/packages/create/src/mergers/mergeCreations.ts
index b326f690..e16097ce 100644
--- a/packages/create/src/mergers/mergeCreations.ts
+++ b/packages/create/src/mergers/mergeCreations.ts
@@ -3,7 +3,7 @@ import { withoutUndefinedProperties } from "without-undefined-properties";
import { Creation } from "../types/creations.js";
import { applyMerger } from "./applyMerger.js";
import { mergeAddons } from "./mergeAddons.js";
-import { mergeFileCreations } from "./mergeFileCreations.js";
+import { mergeCreatedDirectories } from "./mergeCreatedDirectories.js";
import { mergeRequests } from "./mergeRequests.js";
import { mergeScripts } from "./mergeScripts.js";
import { mergeSuggestions } from "./mergeSuggestions.js";
@@ -14,7 +14,7 @@ export function mergeCreations(
): Partial> {
return withoutUndefinedProperties({
addons: applyMerger(first.addons, second.addons, mergeAddons),
- files: applyMerger(first.files, second.files, mergeFileCreations),
+ files: applyMerger(first.files, second.files, mergeCreatedDirectories),
requests: applyMerger(first.requests, second.requests, mergeRequests),
scripts: applyMerger(first.scripts, second.scripts, mergeScripts),
suggestions: applyMerger(
diff --git a/packages/create/src/mergers/mergeFileEntries.test.ts b/packages/create/src/mergers/mergeFileEntries.test.ts
index 047297ab..2bda5c65 100644
--- a/packages/create/src/mergers/mergeFileEntries.test.ts
+++ b/packages/create/src/mergers/mergeFileEntries.test.ts
@@ -1,6 +1,6 @@
+import { CreatedFileEntry } from "create-fs";
import { describe, expect, test } from "vitest";
-import { CreatedFileEntry } from "../types/creations.js";
import { mergeFileEntries } from "./mergeFileEntries.js";
const path = ["test", "path"];
diff --git a/packages/create/src/mergers/mergeFileEntries.ts b/packages/create/src/mergers/mergeFileEntries.ts
index dad2d8cc..3336ea38 100644
--- a/packages/create/src/mergers/mergeFileEntries.ts
+++ b/packages/create/src/mergers/mergeFileEntries.ts
@@ -1,4 +1,4 @@
-import { CreatedFileEntry } from "../types/creations.js";
+import { CreatedFileEntry } from "create-fs";
export function mergeFileEntries(
first: CreatedFileEntry | undefined,
diff --git a/packages/create/src/producers/produceBase.test.ts b/packages/create/src/producers/produceBase.test.ts
index 1937adf1..572c29f1 100644
--- a/packages/create/src/producers/produceBase.test.ts
+++ b/packages/create/src/producers/produceBase.test.ts
@@ -11,6 +11,7 @@ const system = {
octokit: {} as Octokit,
},
fs: {
+ readDirectory: vi.fn(),
readFile: vi.fn(),
writeDirectory: vi.fn(),
writeFile: vi.fn(),
diff --git a/packages/create/src/producers/produceBlocks.test.ts b/packages/create/src/producers/produceBlocks.test.ts
index 67e93981..2b1485ea 100644
--- a/packages/create/src/producers/produceBlocks.test.ts
+++ b/packages/create/src/producers/produceBlocks.test.ts
@@ -12,7 +12,12 @@ const presetContext = {
log: vi.fn(),
},
fetchers: createSystemFetchers({ fetch: vi.fn() }),
- fs: { readFile: vi.fn(), writeDirectory: vi.fn(), writeFile: vi.fn() },
+ fs: {
+ readDirectory: vi.fn(),
+ readFile: vi.fn(),
+ writeDirectory: vi.fn(),
+ writeFile: vi.fn(),
+ },
runner: vi.fn(),
take: vi.fn(),
};
diff --git a/packages/create/src/producers/producePreset.test.ts b/packages/create/src/producers/producePreset.test.ts
index 812282d8..08d6afd7 100644
--- a/packages/create/src/producers/producePreset.test.ts
+++ b/packages/create/src/producers/producePreset.test.ts
@@ -19,6 +19,7 @@ const system = {
octokit: {} as Octokit,
},
fs: {
+ readDirectory: vi.fn(),
readFile: vi.fn(),
writeDirectory: vi.fn(),
writeFile: vi.fn(),
diff --git a/packages/create/src/runners/applyFilesToSystem.ts b/packages/create/src/runners/applyFilesToSystem.ts
index f65038d9..60ed9184 100644
--- a/packages/create/src/runners/applyFilesToSystem.ts
+++ b/packages/create/src/runners/applyFilesToSystem.ts
@@ -1,11 +1,9 @@
+import { CreatedDirectory, WritingFileSystem } from "create-fs";
import * as path from "node:path";
import prettier from "prettier";
-import { CreatedFiles } from "../types/creations.js";
-import { WritingFileSystem } from "../types/system.js";
-
export async function applyFilesToSystem(
- files: CreatedFiles,
+ files: CreatedDirectory,
system: WritingFileSystem,
directory: string,
) {
@@ -45,7 +43,7 @@ function inferParser(fileName: string, text: string) {
}
async function writeToSystemWorker(
- files: CreatedFiles,
+ files: CreatedDirectory,
system: WritingFileSystem,
basePath: string,
) {
diff --git a/packages/create/src/runners/runBlock.test.ts b/packages/create/src/runners/runBlock.test.ts
index 144bacd5..45e04bd3 100644
--- a/packages/create/src/runners/runBlock.test.ts
+++ b/packages/create/src/runners/runBlock.test.ts
@@ -18,6 +18,7 @@ function createSystem() {
octokit: {} as Octokit,
},
fs: {
+ readDirectory: noop("readDirectory"),
readFile: noop("readFile"),
writeDirectory: vi.fn(),
writeFile: vi.fn(),
diff --git a/packages/create/src/runners/runPreset.test.ts b/packages/create/src/runners/runPreset.test.ts
index b16e3840..c8b7bb4a 100644
--- a/packages/create/src/runners/runPreset.test.ts
+++ b/packages/create/src/runners/runPreset.test.ts
@@ -18,6 +18,7 @@ function createSystem() {
octokit: {} as Octokit,
},
fs: {
+ readDirectory: noop("readDirectory"),
readFile: noop("readFile"),
writeDirectory: vi.fn(),
writeFile: vi.fn(),
diff --git a/packages/create/src/system/createSystemContext.ts b/packages/create/src/system/createSystemContext.ts
index fd211fd9..e47b1724 100644
--- a/packages/create/src/system/createSystemContext.ts
+++ b/packages/create/src/system/createSystemContext.ts
@@ -1,10 +1,11 @@
+import { createWritingFileSystem } from "create-fs";
+
import { TakeInput } from "../types/inputs.js";
import { NativeSystem, SystemContext, SystemDisplay } from "../types/system.js";
import { createOfflineFetchers } from "./createOfflineFetchers.js";
import { createSystemDisplay } from "./createSystemDisplay.js";
import { createSystemFetchers } from "./createSystemFetchers.js";
import { createSystemRunner } from "./createSystemRunner.js";
-import { createWritingFileSystem } from "./createWritingFileSystem.js";
export interface SystemContextSettings extends Partial {
auth?: string;
diff --git a/packages/create/src/system/createWritingFileSystem.ts b/packages/create/src/system/createWritingFileSystem.ts
deleted file mode 100644
index 4480eb46..00000000
--- a/packages/create/src/system/createWritingFileSystem.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as nodeFS from "node:fs/promises";
-
-export function createWritingFileSystem() {
- return {
- readFile: async (filePath: string) =>
- (await nodeFS.readFile(filePath)).toString(),
- writeDirectory: async (directoryPath: string) =>
- void (await nodeFS.mkdir(directoryPath, { recursive: true })),
- writeFile: async (filePath: string, contents: string) => {
- await nodeFS.writeFile(filePath, contents);
- },
- };
-}
diff --git a/packages/create/src/types/creations.ts b/packages/create/src/types/creations.ts
index 4c497bad..c796bc34 100644
--- a/packages/create/src/types/creations.ts
+++ b/packages/create/src/types/creations.ts
@@ -1,3 +1,5 @@
+import { CreatedDirectory } from "create-fs";
+
import { BlockWithAddons } from "./blocks.js";
import { SystemFetchers } from "./system.js";
@@ -9,25 +11,6 @@ export interface CreatedBlockAddons<
block: BlockWithAddons;
}
-export type CreatedFileEntry =
- | [string, CreatedFileOptions]
- | [string]
- | CreatedFiles
- | false
- | string;
-
-export interface CreatedFileOptions {
- /**
- * File mode (permission and sticky bits) per chmod().
- * @example 0o777 for an executable file.
- */
- mode?: number;
-}
-
-export interface CreatedFiles {
- [i: string]: CreatedFileEntry | undefined;
-}
-
export interface CreatedRequest {
id: string;
send: CreatedRequestSender;
@@ -46,7 +29,7 @@ export type Creation = DirectCreation &
IndirectCreation;
export interface DirectCreation {
- files: CreatedFiles;
+ files: CreatedDirectory;
requests: CreatedRequest[];
scripts: CreatedScript[];
}
diff --git a/packages/create/src/types/inputs.ts b/packages/create/src/types/inputs.ts
index 8254e0bb..b57866c8 100644
--- a/packages/create/src/types/inputs.ts
+++ b/packages/create/src/types/inputs.ts
@@ -1,8 +1,8 @@
+import { ReadingFileSystem } from "create-fs";
+
import { TakeContext } from "./context.js";
import { SystemFetchers, SystemRunner } from "./system.js";
-export type FileSystemReadFile = (filePath: string) => Promise;
-
export type Input<
Result,
Args extends object | undefined = undefined,
@@ -15,7 +15,7 @@ export type InputArgsFor =
export interface InputContext extends TakeContext {
fetchers: SystemFetchers;
- fs: InputFileSystem;
+ fs: ReadingFileSystem;
runner: SystemRunner;
}
@@ -36,10 +36,6 @@ export interface InputContextWithArgs
args: Args;
}
-export interface InputFileSystem {
- readFile: FileSystemReadFile;
-}
-
export type InputWithArgs = (
context: InputContextWithArgs,
) => Result;
diff --git a/packages/create/src/types/system.ts b/packages/create/src/types/system.ts
index 4b7496b4..44fc833f 100644
--- a/packages/create/src/types/system.ts
+++ b/packages/create/src/types/system.ts
@@ -1,20 +1,8 @@
+import { WritingFileSystem } from "create-fs";
import { ExecaError, Result } from "execa";
import { Octokit } from "octokit";
import { TakeContext } from "./context.js";
-import { InputFileSystem } from "./inputs.js";
-
-export type FileSystemWriteDirectory = (directoryPath: string) => Promise;
-
-export type FileSystemWriteFile = (
- filePath: string,
- contents: string,
- options?: FileSystemWriteFileOptions,
-) => Promise;
-
-export interface FileSystemWriteFileOptions {
- mode?: number;
-}
export interface NativeSystem {
fetchers: SystemFetchers;
@@ -44,8 +32,3 @@ export interface SystemFetchers {
}
export type SystemRunner = (command: string) => Promise;
-
-export interface WritingFileSystem extends InputFileSystem {
- writeDirectory: FileSystemWriteDirectory;
- writeFile: FileSystemWriteFile;
-}
diff --git a/packages/site/astro.config.ts b/packages/site/astro.config.ts
index 45bddff4..effa898a 100644
--- a/packages/site/astro.config.ts
+++ b/packages/site/astro.config.ts
@@ -48,10 +48,19 @@ export default defineConfig({
{ label: "Creators", link: "engine/apis/creators" },
{ label: "Producers", link: "engine/apis/producers" },
{ label: "Runners", link: "engine/apis/runners" },
- { label: "Testers", link: "engine/apis/testers" },
],
label: "APIs",
},
+ {
+ items: [
+ { label: "create-fs", link: "engine/packages/create-fs" },
+ {
+ label: "create-testers",
+ link: "engine/packages/create-testers",
+ },
+ ],
+ label: "Packages",
+ },
],
label: "Templating Engine",
},
diff --git a/packages/site/src/content/docs/engine/packages/create-fs.mdx b/packages/site/src/content/docs/engine/packages/create-fs.mdx
new file mode 100644
index 00000000..213f51b4
--- /dev/null
+++ b/packages/site/src/content/docs/engine/packages/create-fs.mdx
@@ -0,0 +1,68 @@
+---
+title: create-fs
+---
+
+import { PackageManagers } from "starlight-package-managers";
+
+The file system used by create
. ๐๏ธ
+
+
+
+The separate `create-fs` package includes types and utility functions for the file system used in [Runtime > Creations > `files`](https://www.create.bingo/engine/runtime/creations#files).
+
+This file system is a simplified abstraction over the lower-level APIs in Node.js and other platforms.
+APIs and data are optimized for simplicity and ease of use, rather than completeness.
+
+For example, given a structure like:
+
+```plaintext
+/
+โโโ README.md
+โโโ src
+ โโโ index.ts
+```
+
+`create-fs` would represent that structure with an object like:
+
+```json
+{
+ "README.md": "...",
+ "src": {
+ "index.ts": "..."
+ }
+}
+```
+
+## APIs
+
+### `intakeFromDirectory`
+
+Given a directory path, reads in the directory as to the `create-fs` directory structure.
+
+```ts
+import { intakeFromDirectory } from "create-fs";
+
+// Result: { "index.ts": "..." }
+await intakeFromDirectory("src");
+```
+
+Parameters:
+
+1. `directoryPath: string` _(required)_
+2. `settings: IntakeFromDirectorySettings` _(optional)_:
+ - [`exclude: RegExp`](#intakefromdirectory-exclude)
+
+#### `exclude` {#intakefromdirectory-exclude}
+
+An optional regular expression to filter out directory children.
+
+For example, you may want to avoid `.git` and `node_modules` directories:
+
+```ts
+import { intakeFromDirectory } from "create-fs";
+
+// Result: { README.md: "...", src: { "index.ts": "..." }}
+await intakeFromDirectory(".", {
+ exclude: /node_modules|^\.git$/,
+});
+```
diff --git a/packages/site/src/content/docs/engine/apis/testers.mdx b/packages/site/src/content/docs/engine/packages/create-testers.mdx
similarity index 89%
rename from packages/site/src/content/docs/engine/apis/testers.mdx
rename to packages/site/src/content/docs/engine/packages/create-testers.mdx
index d6cd6696..a0fdf61d 100644
--- a/packages/site/src/content/docs/engine/apis/testers.mdx
+++ b/packages/site/src/content/docs/engine/packages/create-testers.mdx
@@ -1,19 +1,68 @@
---
-title: Tester APIs
+title: create-testers
---
+import { PackageManagers } from "starlight-package-managers";
+
+Test utilities for composable, testable, type-safe templates. โ๏ธ
+
+
+
The separate `create-testers` package includes testing utilities that run [Producers](./producers) in fully virtualized environments.
This is intended for use in unit tests that should mock out all [System Context](../runtime/contexts#system-contexts).
-```shell
-npm i create-testers -D
-```
-
:::tip
`create-testers` is test-framework-agnostic.
You can use it with any typical testing framework, including [Jest](https://jestjs.io) and [Vitest](https://vitest.dev).
:::
+## `diffCreatedDirectory`
+
+Produces a nested object diff comparing the [`files`](../runtime/creations#files) between an actual directory and produced results from a Creation.
+
+This is most commonly useful in conjunction with the [`create-fs` `intakeFromDirectory` API](./create-fs#intakefromdirectory).
+
+For example, this test snippet runs an integration test for a template repository, making sure its files on disk match its own `everything` Preset:
+
+```ts
+import { producePreset } from "create";
+import { intakeFromDirectory } from "create-fs";
+import { diffCreatedDirectory } from "create-testers";
+
+import { presetEverything } from "./presetEverything.js";
+
+const actual = await intakeFromDirectory(".", {
+ exclude: /node_modules|^\.git$/,
+});
+
+const created = await producePreset(presetEverything);
+
+expect(diffCreatedDirectory(actual, created)).toBeUndefined();
+```
+
+### DiffedCreatedDirectory
+
+`diffCreatedDirectory` will return an object matching a `DiffedCreatedDirectory` type.
+Any files that are different in the `created` argument compared to the `actual` argument will be included in that object.
+
+Differences are computed as:
+
+- If a file exists in `created` but not in `actual`, it will be included as-is
+- If a file exists in both but has different text content and/or `mode`, it will be included as a diff using [`diff`](https://www.npmjs.com/package/diff)'s `createTwoFilesPatch`, omitting headers before the `@@`
+
+For example, if a `src/index.ts` has content `abc` in `actual` but content `bbc` in `created`, the diff would look like:
+
+```js
+{
+ "src": {
+ "index.ts": `@@ -1,1 +1,1 @@
+-abc
++bbc
+`
+ }
+}
+```
+
## `testBase`
For [Bases](../concepts/bases), a `testBase` function is exported that is analogous to [`produceBase`](./producers#producebase).
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 94d364e2..c8f74f6b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,6 +116,9 @@ importers:
chalk:
specifier: ^5.4.1
version: 5.4.1
+ create-fs:
+ specifier: workspace:^
+ version: link:../create-fs
execa:
specifier: ^9.5.2
version: 9.5.2
@@ -150,16 +153,37 @@ importers:
'@types/hosted-git-info':
specifier: 3.0.5
version: 3.0.5
+ '@types/node':
+ specifier: ^22.10.5
+ version: 22.10.6
+
+ packages/create-fs:
+ devDependencies:
+ '@types/node':
+ specifier: ^22.10.5
+ version: 22.10.6
packages/create-testers:
dependencies:
create:
specifier: workspace:^
version: link:../create
+ create-fs:
+ specifier: workspace:^
+ version: link:../create-fs
+ diff:
+ specifier: ^7.0.0
+ version: 7.0.0
octokit:
specifier: ^4.1.0
version: 4.1.0
+ without-undefined-properties:
+ specifier: ^0.1.1
+ version: 0.1.1
devDependencies:
+ '@types/diff':
+ specifier: ^7.0.0
+ version: 7.0.0
zod:
specifier: 3.24.1
version: 3.24.1
@@ -1405,6 +1429,9 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ '@types/diff@7.0.0':
+ resolution: {integrity: sha512-sVpkpbnTJL9CYoDf4U+tHaQLe5HiTaHWY7m9FuYA7oMCHwC9ie0Vh9eIGapyzYrU3+pILlSY2fAc4elfw5m4dg==}
+
'@types/eslint-plugin-markdown@2.0.2':
resolution: {integrity: sha512-ImmEw5xBVb9vCaFfQ+5kUcVatUO4XPpTvryAmhpKzalUKhDb3EZmeuHvIUO6E1/WDOTw+/b9qlWsZhxULhZdfQ==}
@@ -2035,6 +2062,10 @@ packages:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
+ diff@7.0.0:
+ resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
+ engines: {node: '>=0.3.1'}
+
direction@2.0.1:
resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==}
hasBin: true
@@ -5498,6 +5529,8 @@ snapshots:
dependencies:
'@types/ms': 0.7.34
+ '@types/diff@7.0.0': {}
+
'@types/eslint-plugin-markdown@2.0.2':
dependencies:
'@types/eslint': 9.6.0
@@ -6277,6 +6310,8 @@ snapshots:
diff@5.2.0: {}
+ diff@7.0.0: {}
+
direction@2.0.1: {}
dlv@1.1.3: {}
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 90c8ebcc..e7aa95bb 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -6,6 +6,7 @@
"extends": "./tsconfig.base.json",
"files": [],
"references": [
+ { "path": "./packages/create-fs" },
{ "path": "./packages/create" },
{ "path": "./packages/create-testers" },
{ "path": "./packages/input-from-file" },
diff --git a/vitest.config.ts b/vitest.config.ts
index 8a48530f..faec7445 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -10,6 +10,7 @@ export default defineConfig({
"**/*.astro",
"**/vitest.*.ts",
"packages/*/src/index.ts",
+ "packages/create-fs/src/create*FileSystem.ts",
"packages/site/astro.config.ts",
"packages/site/src/content",
],