From 994fd783e4e977d6faf7f8f56fa6a730896b36d4 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 14 Jan 2025 11:07:47 -0500 Subject: [PATCH 1/5] feat: split file system into create-fs, with diffCreatedDirectory tester --- packages/create-fs/README.md | 17 ++ packages/create-fs/package.json | 39 ++++ .../create-fs/src/createReadingFileSystem.ts | 11 + .../create-fs/src/createWritingFileSystem.ts | 14 ++ packages/create-fs/src/index.ts | 5 + .../src/intake/intakeFromDirectory.ts | 31 +++ packages/create-fs/src/types/files.ts | 21 ++ packages/create-fs/src/types/system.ts | 30 +++ packages/create-fs/tsconfig.json | 8 + packages/create-fs/vitest.config.ts | 8 + packages/create-testers/package.json | 8 +- .../create-testers/src/createMockSystems.ts | 4 +- .../src/diffCreatedDirectory.ts | 198 ++++++++++++++++++ packages/create-testers/src/index.ts | 1 + packages/create-testers/src/types.ts | 3 +- packages/create/package.json | 4 +- .../cli/prompts/promptForBaseOptions.test.ts | 1 + .../create/src/creators/createInput.test.ts | 10 +- packages/create/src/index.ts | 1 - ...est.ts => mergeCreatedDirectories.test.ts} | 20 +- ...reations.ts => mergeCreatedDirectories.ts} | 25 ++- packages/create/src/mergers/mergeCreations.ts | 4 +- .../src/mergers/mergeFileEntries.test.ts | 2 +- .../create/src/mergers/mergeFileEntries.ts | 2 +- .../create/src/producers/produceBase.test.ts | 1 + .../src/producers/produceBlocks.test.ts | 7 +- .../src/producers/producePreset.test.ts | 1 + .../create/src/runners/applyFilesToSystem.ts | 8 +- packages/create/src/runners/runBlock.test.ts | 1 + packages/create/src/runners/runPreset.test.ts | 1 + .../create/src/system/createSystemContext.ts | 3 +- .../src/system/createWritingFileSystem.ts | 13 -- packages/create/src/types/creations.ts | 24 +-- packages/create/src/types/inputs.ts | 10 +- packages/create/src/types/system.ts | 19 +- packages/site/astro.config.ts | 11 +- .../docs/engine/packages/create-fs.mdx | 40 ++++ .../create-testers.mdx} | 12 +- pnpm-lock.yaml | 54 ++++- tsconfig.build.json | 1 + 40 files changed, 571 insertions(+), 102 deletions(-) create mode 100644 packages/create-fs/README.md create mode 100644 packages/create-fs/package.json create mode 100644 packages/create-fs/src/createReadingFileSystem.ts create mode 100644 packages/create-fs/src/createWritingFileSystem.ts create mode 100644 packages/create-fs/src/index.ts create mode 100644 packages/create-fs/src/intake/intakeFromDirectory.ts create mode 100644 packages/create-fs/src/types/files.ts create mode 100644 packages/create-fs/src/types/system.ts create mode 100644 packages/create-fs/tsconfig.json create mode 100644 packages/create-fs/vitest.config.ts create mode 100644 packages/create-testers/src/diffCreatedDirectory.ts rename packages/create/src/mergers/{mergeFileCreations.test.ts => mergeCreatedDirectories.test.ts} (68%) rename packages/create/src/mergers/{mergeFileCreations.ts => mergeCreatedDirectories.ts} (60%) delete mode 100644 packages/create/src/system/createWritingFileSystem.ts create mode 100644 packages/site/src/content/docs/engine/packages/create-fs.mdx rename packages/site/src/content/docs/engine/{apis/testers.mdx => packages/create-testers.mdx} (98%) 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. ๐Ÿ—„๏ธ

+ +

+ ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ +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..2f35edf9 --- /dev/null +++ b/packages/create-fs/package.json @@ -0,0 +1,39 @@ +{ + "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" + }, + "dependencies": { + "octokit": "^4.0.2" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "zod": "3.24.1" + }, + "peerDependencies": { + "create": "workspace:^" + }, + "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.ts b/packages/create-fs/src/intake/intakeFromDirectory.ts new file mode 100644 index 00000000..724dfe53 --- /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 files: 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); + + files[child] = stat.isDirectory() + ? await intakeFromDirectory(childPath, settings) + : [(await fs.readFile(childPath)).toString(), { mode: stat.mode }]; + } + + return files; +} diff --git a/packages/create-fs/src/types/files.ts b/packages/create-fs/src/types/files.ts new file mode 100644 index 00000000..ad193841 --- /dev/null +++ b/packages/create-fs/src/types/files.ts @@ -0,0 +1,21 @@ +// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair +/* eslint-disable @typescript-eslint/consistent-indexed-object-style */ + +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 1f65f5da..81b65035 100644 --- a/packages/create-testers/package.json +++ b/packages/create-testers/package.json @@ -24,13 +24,17 @@ "build": "tsc" }, "dependencies": { - "octokit": "^4.0.2" + "diff": "^7.0.0", + "octokit": "^4.0.2", + "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.ts b/packages/create-testers/src/diffCreatedDirectory.ts new file mode 100644 index 00000000..e8e0f13b --- /dev/null +++ b/packages/create-testers/src/diffCreatedDirectory.ts @@ -0,0 +1,198 @@ +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, DiffedCreatedFileOptions] + | [string] + | 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); +} + +export 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] = 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; + } + } + + const trimmed = withoutUndefinedProperties(result); + + return Object.keys(trimmed).length === 0 ? undefined : 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 a 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 a 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 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, + actualProcessed, + createdProcessed, + ).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, + ), + }; +} 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 8abbf069..a2915168 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -28,6 +28,7 @@ "@clack/prompts": "^0.9.0", "cached-factory": "^0.1.0", "chalk": "^5.4.0", + "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 a6c30488..c796bc34 100644 --- a/packages/create/src/types/creations.ts +++ b/packages/create/src/types/creations.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @eslint-community/eslint-comments/disable-enable-pair -/* eslint-disable @typescript-eslint/consistent-indexed-object-style */ +import { CreatedDirectory } from "create-fs"; import { BlockWithAddons } from "./blocks.js"; import { SystemFetchers } from "./system.js"; @@ -12,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; @@ -49,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..57eb0ec5 --- /dev/null +++ b/packages/site/src/content/docs/engine/packages/create-fs.mdx @@ -0,0 +1,40 @@ +--- +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` + +Reads in the 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 98% 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..28dc3c4e 100644 --- a/packages/site/src/content/docs/engine/apis/testers.mdx +++ b/packages/site/src/content/docs/engine/packages/create-testers.mdx @@ -1,14 +1,16 @@ --- -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). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2199078..0dd38316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: chalk: specifier: ^5.4.0 version: 5.4.1 + create-fs: + specifier: workspace:^ + version: link:../create-fs execa: specifier: ^9.5.2 version: 9.5.2 @@ -150,16 +153,47 @@ importers: '@types/hosted-git-info': specifier: 3.0.5 version: 3.0.5 + '@types/node': + specifier: ^22.10.5 + version: 22.10.5 + + packages/create-fs: + dependencies: + create: + specifier: workspace:^ + version: link:../create + octokit: + specifier: ^4.0.2 + version: 4.0.2 + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.10.5 + zod: + specifier: 3.24.1 + version: 3.24.1 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.0.2 version: 4.0.2 + 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 @@ -1475,6 +1509,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==} @@ -1529,6 +1566,9 @@ packages: '@types/node@22.10.3': resolution: {integrity: sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==} + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2286,6 +2326,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 @@ -6311,6 +6355,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 @@ -6369,11 +6415,15 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/node@22.10.5': + dependencies: + undici-types: 6.20.0 + '@types/normalize-package-data@2.4.4': {} '@types/sax@1.2.7': dependencies: - '@types/node': 22.10.3 + '@types/node': 22.10.5 '@types/semver@7.5.8': {} @@ -7281,6 +7331,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..72ded47f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,6 +7,7 @@ "files": [], "references": [ { "path": "./packages/create" }, + { "path": "./packages/create-fs" }, { "path": "./packages/create-testers" }, { "path": "./packages/input-from-file" }, { "path": "./packages/input-from-file-json" }, From dc69b141f7d217d3a2f8c7c68fb2ea4616d8e7ca Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 14 Jan 2025 11:59:56 -0500 Subject: [PATCH 2/5] tests --- .../src/intake/intakeFromDirectory.test.ts | 93 ++++++++++++++ .../src/intake/intakeFromDirectory.ts | 6 +- .../src/diffCreatedDirectory.test.ts | 121 ++++++++++++++++++ .../src/diffCreatedDirectory.ts | 27 ++-- 4 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 packages/create-fs/src/intake/intakeFromDirectory.test.ts create mode 100644 packages/create-testers/src/diffCreatedDirectory.test.ts 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..0a5d4004 --- /dev/null +++ b/packages/create-fs/src/intake/intakeFromDirectory.test.ts @@ -0,0 +1,93 @@ +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", { + exclude: /excluded/, + }); + + expect(directory).toEqual({}); + expect(mockReaddir.mock.calls).toEqual([["from"]]); + expect(mockStat).not.toHaveBeenCalled(); + }); + + it("returns non-excluded files when the directory contains files", 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 index 724dfe53..90198af4 100644 --- a/packages/create-fs/src/intake/intakeFromDirectory.ts +++ b/packages/create-fs/src/intake/intakeFromDirectory.ts @@ -11,7 +11,7 @@ export async function intakeFromDirectory( directoryPath: string, settings: IntakeFromDirectorySettings, ) { - const files: CreatedDirectory = {}; + const directory: CreatedDirectory = {}; const children = await fs.readdir(directoryPath); for (const child of children) { @@ -22,10 +22,10 @@ export async function intakeFromDirectory( const childPath = path.join(directoryPath, child); const stat = await fs.stat(childPath); - files[child] = stat.isDirectory() + directory[child] = stat.isDirectory() ? await intakeFromDirectory(childPath, settings) : [(await fs.readFile(childPath)).toString(), { mode: stat.mode }]; } - return files; + return directory; } diff --git a/packages/create-testers/src/diffCreatedDirectory.test.ts b/packages/create-testers/src/diffCreatedDirectory.test.ts new file mode 100644 index 00000000..45b73977 --- /dev/null +++ b/packages/create-testers/src/diffCreatedDirectory.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it, test, vi } from "vitest"; + +import { diffCreatedDirectory } 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: "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" } } }, + ], + ])("%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 index e8e0f13b..5ce2bce7 100644 --- a/packages/create-testers/src/diffCreatedDirectory.ts +++ b/packages/create-testers/src/diffCreatedDirectory.ts @@ -12,8 +12,8 @@ export interface DiffedCreatedDirectory { } export type DiffedCreatedFileEntry = - | [string, DiffedCreatedFileOptions] | [string] + | [string | undefined, DiffedCreatedFileOptions?] | CreatedFileEntry | DiffedCreatedDirectory | string; @@ -44,7 +44,7 @@ export function diffCreatedDirectoryWorker( for (const [childName, childCreated] of Object.entries(created)) { if (!(childName in actual)) { - result[childName] = childCreated; + result[childName] = undefinedIfEmpty(childCreated); continue; } @@ -63,9 +63,7 @@ export function diffCreatedDirectoryWorker( } } - const trimmed = withoutUndefinedProperties(result); - - return Object.keys(trimmed).length === 0 ? undefined : result; + return undefinedIfEmpty(withoutUndefinedProperties(result)); } function diffCreatedDirectoryChild( @@ -106,9 +104,8 @@ function diffCreatedDirectoryChild( childCreated[1], pathToChild, ); - return fileDiff || optionsDiff - ? [fileDiff ?? "", optionsDiff ?? {}] - : undefined; + + return (fileDiff ?? optionsDiff) ? [fileDiff, optionsDiff] : undefined; } if (typeof childCreated === "string") { @@ -120,7 +117,7 @@ function diffCreatedDirectoryChild( ); } - return `Mismatched ${pathToChild}: actual is a created file; created is ${typeof childCreated}.`; + return `Mismatched ${pathToChild}: actual is created file; created is ${typeof childCreated}.`; } if (Array.isArray(childCreated)) { @@ -133,7 +130,7 @@ function diffCreatedDirectoryChild( ); } - return `Mismatched ${pathToChild}: actual is ${typeof childActual}; created is a created file.`; + return `Mismatched ${pathToChild}: actual is ${typeof childActual}; created is created file.`; } if (typeof childActual === "object") { @@ -164,8 +161,8 @@ function diffCreatedFileText( : createTwoFilesPatch( pathToFile, pathToFile, - actualProcessed, createdProcessed, + actualProcessed, ).replace(/^Index: .+\n=+\n-{3} .+\n\+{3} .+\n/gmu, ""); } @@ -196,3 +193,11 @@ function diffCreatedFileOptions( ), }; } +function undefinedIfEmpty(value: T) { + return !!value && + typeof value === "object" && + !Array.isArray(value) && + Object.keys(value).length === 0 + ? undefined + : value; +} From 1c7e815e75d36c0d180203ace23f615f4f29c5c5 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 14 Jan 2025 12:19:56 -0500 Subject: [PATCH 3/5] docs --- .../src/intake/intakeFromDirectory.test.ts | 34 ++++++++++++-- .../src/intake/intakeFromDirectory.ts | 6 +-- .../src/diffCreatedDirectory.test.ts | 12 ++++- .../docs/engine/packages/create-fs.mdx | 30 +++++++++++- .../docs/engine/packages/create-testers.mdx | 47 +++++++++++++++++++ 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/packages/create-fs/src/intake/intakeFromDirectory.test.ts b/packages/create-fs/src/intake/intakeFromDirectory.test.ts index 0a5d4004..6626f0c5 100644 --- a/packages/create-fs/src/intake/intakeFromDirectory.test.ts +++ b/packages/create-fs/src/intake/intakeFromDirectory.test.ts @@ -22,16 +22,42 @@ describe("intakeFromDirectory", () => { it("returns an empty object when the directory has no files", async () => { mockReaddir.mockResolvedValueOnce([]); - const directory = await intakeFromDirectory("from", { - exclude: /excluded/, - }); + const directory = await intakeFromDirectory("from"); expect(directory).toEqual({}); expect(mockReaddir.mock.calls).toEqual([["from"]]); expect(mockStat).not.toHaveBeenCalled(); }); - it("returns non-excluded files when the directory contains files", async () => { + 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") diff --git a/packages/create-fs/src/intake/intakeFromDirectory.ts b/packages/create-fs/src/intake/intakeFromDirectory.ts index 90198af4..4c8cc829 100644 --- a/packages/create-fs/src/intake/intakeFromDirectory.ts +++ b/packages/create-fs/src/intake/intakeFromDirectory.ts @@ -4,18 +4,18 @@ import path from "node:path"; import { CreatedDirectory } from "../types/files.js"; export interface IntakeFromDirectorySettings { - exclude: RegExp; + exclude?: RegExp; } export async function intakeFromDirectory( directoryPath: string, - settings: IntakeFromDirectorySettings, + settings: IntakeFromDirectorySettings = {}, ) { const directory: CreatedDirectory = {}; const children = await fs.readdir(directoryPath); for (const child of children) { - if (settings.exclude.test(child)) { + if (settings.exclude?.test(child)) { continue; } diff --git a/packages/create-testers/src/diffCreatedDirectory.test.ts b/packages/create-testers/src/diffCreatedDirectory.test.ts index 45b73977..9a09f055 100644 --- a/packages/create-testers/src/diffCreatedDirectory.test.ts +++ b/packages/create-testers/src/diffCreatedDirectory.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, test, vi } from "vitest"; +import { describe, expect, test } from "vitest"; import { diffCreatedDirectory } from "./diffCreatedDirectory.js"; @@ -18,6 +18,16 @@ describe("diffCreatedDirectory", () => { }, ], [{ 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], diff --git a/packages/site/src/content/docs/engine/packages/create-fs.mdx b/packages/site/src/content/docs/engine/packages/create-fs.mdx index 57eb0ec5..213f51b4 100644 --- a/packages/site/src/content/docs/engine/packages/create-fs.mdx +++ b/packages/site/src/content/docs/engine/packages/create-fs.mdx @@ -37,4 +37,32 @@ For example, given a structure like: ### `intakeFromDirectory` -Reads in the +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/packages/create-testers.mdx b/packages/site/src/content/docs/engine/packages/create-testers.mdx index 28dc3c4e..a0fdf61d 100644 --- a/packages/site/src/content/docs/engine/packages/create-testers.mdx +++ b/packages/site/src/content/docs/engine/packages/create-testers.mdx @@ -16,6 +16,53 @@ This is intended for use in unit tests that should mock out all [System Context] 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). From 5c71675b9e8ea6c31f44bebbabde0b90ff45b8ba Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 14 Jan 2025 12:22:26 -0500 Subject: [PATCH 4/5] lint and package touchups --- packages/create-fs/package.json | 9 +-- .../src/diffCreatedDirectory.ts | 64 +++++++++---------- pnpm-lock.yaml | 59 ++++++----------- 3 files changed, 54 insertions(+), 78 deletions(-) diff --git a/packages/create-fs/package.json b/packages/create-fs/package.json index 2f35edf9..a7d4d1a1 100644 --- a/packages/create-fs/package.json +++ b/packages/create-fs/package.json @@ -23,15 +23,8 @@ "scripts": { "build": "tsc" }, - "dependencies": { - "octokit": "^4.0.2" - }, "devDependencies": { - "@types/node": "^22.10.5", - "zod": "3.24.1" - }, - "peerDependencies": { - "create": "workspace:^" + "@types/node": "^22.10.5" }, "engines": { "node": ">=18" diff --git a/packages/create-testers/src/diffCreatedDirectory.ts b/packages/create-testers/src/diffCreatedDirectory.ts index 5ce2bce7..172f3b77 100644 --- a/packages/create-testers/src/diffCreatedDirectory.ts +++ b/packages/create-testers/src/diffCreatedDirectory.ts @@ -34,38 +34,6 @@ export function diffCreatedDirectory( return result && withoutUndefinedProperties(result); } -export 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 diffCreatedDirectoryChild( childActual: CreatedFileEntry | undefined, childCreated: CreatedFileEntry | undefined, @@ -147,6 +115,38 @@ function diffCreatedDirectoryChild( 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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0dd38316..597e0667 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,20 +158,10 @@ importers: version: 22.10.5 packages/create-fs: - dependencies: - create: - specifier: workspace:^ - version: link:../create - octokit: - specifier: ^4.0.2 - version: 4.0.2 devDependencies: '@types/node': specifier: ^22.10.5 version: 22.10.5 - zod: - specifier: 3.24.1 - version: 3.24.1 packages/create-testers: dependencies: @@ -247,19 +237,19 @@ importers: version: 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.4.2)(typescript@5.7.2) '@astrojs/starlight': specifier: ^0.30.3 - version: 0.30.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) + version: 0.30.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) '@types/node': specifier: ^22.10.3 - version: 22.10.3 + version: 22.10.5 astro: specifier: ^5.1.1 - version: 5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) + version: 5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) remark-custom-heading-id: specifier: ^2.0.0 version: 2.0.0 starlight-package-managers: specifier: ^0.8.1 - version: 0.8.1(@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))) + version: 0.8.1(@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -1563,9 +1553,6 @@ packages: '@types/node@22.10.2': resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} - '@types/node@22.10.3': - resolution: {integrity: sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==} - '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} @@ -5223,12 +5210,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.0.2(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))': + '@astrojs/mdx@4.0.2(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))': dependencies: '@astrojs/markdown-remark': 6.0.1 '@mdx-js/mdx': 3.1.0(acorn@8.14.0) acorn: 8.14.0 - astro: 5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) + astro: 5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.4 @@ -5252,16 +5239,16 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.24.1 - '@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))': + '@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))': dependencies: - '@astrojs/mdx': 4.0.2(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) + '@astrojs/mdx': 4.0.2(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) '@astrojs/sitemap': 3.1.6 '@pagefind/default-ui': 1.1.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) - astro-expressive-code: 0.38.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) + astro: 5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) + astro-expressive-code: 0.38.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.2 @@ -6411,10 +6398,6 @@ snapshots: dependencies: undici-types: 6.20.0 - '@types/node@22.10.3': - dependencies: - undici-types: 6.20.0 - '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -6708,12 +6691,12 @@ snapshots: astring@1.8.6: {} - astro-expressive-code@0.38.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)): + astro-expressive-code@0.38.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)): dependencies: - astro: 5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) + astro: 5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1) rehype-expressive-code: 0.38.3 - astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1): + astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.2 @@ -6765,8 +6748,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.14.2 vfile: 6.0.3 - vite: 6.0.6(@types/node@22.10.3)(jiti@2.4.1)(yaml@2.6.1) - vitefu: 1.0.4(vite@6.0.6(@types/node@22.10.3)(jiti@2.4.1)(yaml@2.6.1)) + vite: 6.0.6(@types/node@22.10.5)(jiti@2.4.1)(yaml@2.6.1) + vitefu: 1.0.4(vite@6.0.6(@types/node@22.10.5)(jiti@2.4.1)(yaml@2.6.1)) which-pm: 3.0.0 xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -10174,9 +10157,9 @@ snapshots: stackback@0.0.2: {} - starlight-package-managers@0.8.1(@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))): + starlight-package-managers@0.8.1(@astrojs/starlight@0.30.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1))): dependencies: - '@astrojs/starlight': 0.30.3(astro@5.1.1(@types/node@22.10.3)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) + '@astrojs/starlight': 0.30.3(astro@5.1.1(@types/node@22.10.5)(jiti@2.4.1)(rollup@4.29.1)(typescript@5.7.2)(yaml@2.6.1)) std-env@3.8.0: {} @@ -10519,20 +10502,20 @@ snapshots: '@types/node': 22.10.2 fsevents: 2.3.3 - vite@6.0.6(@types/node@22.10.3)(jiti@2.4.1)(yaml@2.6.1): + vite@6.0.6(@types/node@22.10.5)(jiti@2.4.1)(yaml@2.6.1): dependencies: esbuild: 0.24.2 postcss: 8.4.49 rollup: 4.29.1 optionalDependencies: - '@types/node': 22.10.3 + '@types/node': 22.10.5 fsevents: 2.3.3 jiti: 2.4.1 yaml: 2.6.1 - vitefu@1.0.4(vite@6.0.6(@types/node@22.10.3)(jiti@2.4.1)(yaml@2.6.1)): + vitefu@1.0.4(vite@6.0.6(@types/node@22.10.5)(jiti@2.4.1)(yaml@2.6.1)): optionalDependencies: - vite: 6.0.6(@types/node@22.10.3)(jiti@2.4.1)(yaml@2.6.1) + vite: 6.0.6(@types/node@22.10.5)(jiti@2.4.1)(yaml@2.6.1) vitest@2.1.8(@types/node@22.10.2): dependencies: From b8385fb9c76c40b19fa400c406c3083f63e2ec46 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 14 Jan 2025 13:07:25 -0500 Subject: [PATCH 5/5] Exclude file system creators from coverage --- vitest.config.ts | 1 + 1 file changed, 1 insertion(+) 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", ],