@ebay-dt/bonfire-node 0.2.0-alpha.10 → 0.2.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/deployment-provider.d.ts +1 -1
- package/dist/deployment-provider.d.ts.map +1 -1
- package/dist/deployment-provider.js +1 -1
- package/dist/deployment-provider.js.map +1 -1
- package/dist/duplicate.d.ts.map +1 -1
- package/dist/duplicate.js +103 -47
- package/dist/duplicate.js.map +1 -1
- package/dist/git-repository.d.ts +14 -1
- package/dist/git-repository.d.ts.map +1 -1
- package/dist/git-repository.js +99 -3
- package/dist/git-repository.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/prototype-templates.d.ts +0 -1
- package/dist/internal/prototype-templates.d.ts.map +1 -1
- package/dist/internal/prototype-templates.js +0 -5
- package/dist/internal/prototype-templates.js.map +1 -1
- package/dist/lifecycle/deployment-worktree.d.ts +5 -4
- package/dist/lifecycle/deployment-worktree.d.ts.map +1 -1
- package/dist/lifecycle/deployment-worktree.js +269 -68
- package/dist/lifecycle/deployment-worktree.js.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.d.ts +13 -1
- package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.js +634 -110
- package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
- package/dist/lifecycle/preparation-marker.d.ts +12 -0
- package/dist/lifecycle/preparation-marker.d.ts.map +1 -0
- package/dist/lifecycle/preparation-marker.js +76 -0
- package/dist/lifecycle/preparation-marker.js.map +1 -0
- package/dist/lifecycle/prototype-path-policy.d.ts +18 -0
- package/dist/lifecycle/prototype-path-policy.d.ts.map +1 -0
- package/dist/lifecycle/prototype-path-policy.js +429 -0
- package/dist/lifecycle/prototype-path-policy.js.map +1 -0
- package/dist/lifecycle/repository-summary.d.ts +5 -1
- package/dist/lifecycle/repository-summary.d.ts.map +1 -1
- package/dist/lifecycle/repository-summary.js +6 -0
- package/dist/lifecycle/repository-summary.js.map +1 -1
- package/dist/lifecycle/repository-synchronization.d.ts +13 -9
- package/dist/lifecycle/repository-synchronization.d.ts.map +1 -1
- package/dist/lifecycle/repository-synchronization.js +670 -173
- package/dist/lifecycle/repository-synchronization.js.map +1 -1
- package/package.json +2 -2
package/dist/duplicate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplicate.js","sourceRoot":"","sources":["../src/duplicate.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport {\n lstat,\n mkdir,\n open,\n readdir,\n rm,\n rmdir,\n writeFile as writeFileAsync,\n} from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport type { Dirent } from \"node:fs\";\nimport {\n definePrototypeRef,\n parsePrototypeMetadata,\n type PrototypeMetadata,\n type PrototypeRef,\n type ThemeRegistry,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n buildCssModulesDeclaration,\n buildMetadataJson,\n buildTsConfig,\n COPYABLE_IMPLEMENTATION_FILES,\n} from \"./internal/prototype-templates.js\";\nimport {\n captureOwnedDestination,\n type OwnedDestinationResult,\n} from \"./internal/owned-destination.js\";\nimport type { ResolvedPrototypeCatalogPaths } from \"./prototype-paths.js\";\nimport { isFileNotFound } from \"./internal/utils.js\";\n\nconst FILE_OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0);\nconst BLOCKED_COMPONENT_NAMES = new Set([\".next\", \"node_modules\"]);\n\ntype SafeWriteFile = (\n path: string,\n content: Uint8Array | string,\n options: { flag: \"wx\" },\n) => Promise<void>;\n\nexport type DuplicatePrototypeOptions = {\n paths: Pick<\n ResolvedPrototypeCatalogPaths,\n \"repositoryRoot\" | \"prototypeRoot\"\n >;\n source: PrototypeRef;\n destination: PrototypeRef;\n title: string;\n description: string | null;\n authorName: string | null;\n themeRegistry?: ThemeRegistry;\n clock?: () => Date;\n writeFile?: SafeWriteFile;\n};\n\nexport type PrototypeDuplicateErrorKind =\n \"source-not-found\" | \"invalid-source\" | \"collision\" | \"filesystem\";\n\nexport class PrototypeDuplicateError extends Error {\n readonly kind: PrototypeDuplicateErrorKind;\n\n constructor(kind: PrototypeDuplicateErrorKind) {\n super(`Prototype duplication failed: ${kind}`);\n this.name = \"PrototypeDuplicateError\";\n this.kind = kind;\n }\n}\n\ntype OwnedPath = {\n path: string;\n device: number;\n inode: number;\n};\n\nfunction sourcePathFor(root: string, ref: PrototypeRef) {\n return join(root, ref.login, ref.slug);\n}\n\nasync function ownedPath(path: string): Promise<OwnedPath> {\n const stats = await lstat(path);\n return { path, device: stats.dev, inode: stats.ino };\n}\n\nasync function assertDirectory(\n path: string,\n kind: \"source-not-found\" | \"invalid-source\" | \"filesystem\",\n) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(kind);\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (isFileNotFound(error)) throw new PrototypeDuplicateError(kind);\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function readNoFollow(\n path: string,\n kind: \"invalid-source\" | \"filesystem\",\n) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new PrototypeDuplicateError(kind);\n }\n const file = await open(path, FILE_OPEN_FLAGS);\n try {\n return await file.readFile();\n } finally {\n await file.close();\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (isFileNotFound(error)) return null;\n throw new PrototypeDuplicateError(kind);\n }\n}\n\nasync function readRequiredSourceFile(path: string) {\n const content = await readNoFollow(path, \"invalid-source\");\n if (content === null) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n return content;\n}\n\nasync function listComponentFiles(\n root: string,\n current: string,\n): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(current, { withFileTypes: true });\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const files: string[] = [];\n for (const entry of entries.sort((left, right) =>\n left.name.localeCompare(right.name),\n )) {\n const path = join(current, entry.name);\n let stats;\n try {\n stats = await lstat(path);\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n if (stats.isSymbolicLink()) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n if (\n entry.name === \".gitkeep\" ||\n BLOCKED_COMPONENT_NAMES.has(entry.name) ||\n entry.name.startsWith(\".env\")\n ) {\n continue;\n }\n if (stats.isDirectory()) {\n files.push(...(await listComponentFiles(root, path)));\n continue;\n }\n if (!stats.isFile()) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n files.push(relative(root, path));\n }\n return files;\n}\n\nasync function inspectSource(\n sourcePath: string,\n themeRegistry?: ThemeRegistry,\n) {\n await assertDirectory(sourcePath, \"source-not-found\");\n\n try {\n await lstat(join(sourcePath, \"page.module.css\"));\n throw new PrototypeDuplicateError(\"invalid-source\");\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n }\n\n const metadataBytes = await readRequiredSourceFile(\n join(sourcePath, \"metadata.json\"),\n );\n let metadata: PrototypeMetadata;\n try {\n metadata = parsePrototypeMetadata(\n JSON.parse(metadataBytes.toString(\"utf8\")),\n { themeRegistry },\n );\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const implementationFiles = new Map<string, Uint8Array>();\n for (const name of COPYABLE_IMPLEMENTATION_FILES) {\n implementationFiles.set(\n name,\n await readRequiredSourceFile(join(sourcePath, name)),\n );\n }\n\n const componentsPath = join(sourcePath, \"components\");\n let componentFiles: string[] = [];\n try {\n const stats = await lstat(componentsPath);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n componentFiles = await listComponentFiles(componentsPath, componentsPath);\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n }\n\n return { metadata, implementationFiles, componentFiles };\n}\n\nasync function reserveDestination(\n path: string,\n paths: Pick<\n ResolvedPrototypeCatalogPaths,\n \"repositoryRoot\" | \"prototypeRoot\"\n >,\n) {\n try {\n await mkdir(path);\n } catch (error) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"EEXIST\"\n ) {\n throw new PrototypeDuplicateError(\"collision\");\n }\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n try {\n return await captureOwnedDestination({\n path,\n repositoryRoot: paths.repositoryRoot,\n prototypeRoot: paths.prototypeRoot,\n });\n } catch {\n // The newly created path is retained because ownership was not proven.\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function ensureDirectory(path: string) {\n try {\n await mkdir(path, { recursive: true });\n await assertDirectory(path, \"filesystem\");\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function createDirectory(path: string) {\n try {\n await mkdir(path);\n await assertDirectory(path, \"filesystem\");\n return await ownedPath(path);\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function createNestedDirectories(path: string, existingRoot: string) {\n const missing: string[] = [];\n let current = path;\n while (current !== existingRoot) {\n try {\n const stats = await lstat(current);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n break;\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n missing.push(current);\n const parent = dirname(current);\n if (parent === current) throw new PrototypeDuplicateError(\"filesystem\");\n current = parent;\n }\n }\n\n const owned: OwnedPath[] = [];\n for (const directory of missing.reverse()) {\n owned.push(await createDirectory(directory));\n }\n return owned;\n}\n\nasync function writeExclusive(\n path: string,\n content: Uint8Array | string,\n writeFile: SafeWriteFile,\n) {\n try {\n await writeFile(path, content, { flag: \"wx\" });\n return await ownedPath(path);\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function removeOwnedPaths(\n files: readonly OwnedPath[],\n directories: readonly OwnedPath[],\n) {\n try {\n for (const owned of [...files].reverse()) {\n const current = await lstat(owned.path).catch(() => null);\n if (\n current !== null &&\n !current.isSymbolicLink() &&\n current.dev === owned.device &&\n current.ino === owned.inode\n ) {\n await rm(owned.path, { force: true });\n }\n }\n for (const owned of [...directories].reverse()) {\n const current = await lstat(owned.path).catch(() => null);\n if (\n current !== null &&\n current.isDirectory() &&\n !current.isSymbolicLink() &&\n current.dev === owned.device &&\n current.ino === owned.inode\n ) {\n await rmdir(owned.path);\n }\n }\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nexport async function duplicatePrototype({\n paths,\n source,\n destination: destinationRef,\n title,\n description,\n authorName,\n themeRegistry,\n clock = () => new Date(),\n writeFile = writeFileAsync,\n}: DuplicatePrototypeOptions): Promise<OwnedDestinationResult> {\n const normalizedSource = definePrototypeRef(source);\n const normalizedDestination = definePrototypeRef(destinationRef);\n const sourcePath = sourcePathFor(paths.prototypeRoot, normalizedSource);\n const destination = sourcePathFor(paths.prototypeRoot, normalizedDestination);\n const sourceDetails = await inspectSource(sourcePath, themeRegistry);\n if (sourceDetails.metadata.authorLogin !== normalizedSource.login) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const timestamp = clock().toISOString();\n const metadata = buildMetadataJson(\n title,\n description,\n normalizedDestination.login,\n authorName,\n timestamp,\n sourceDetails.metadata.theme,\n );\n parsePrototypeMetadata(JSON.parse(metadata), { themeRegistry });\n\n await ensureDirectory(paths.prototypeRoot);\n await ensureDirectory(dirname(destination));\n const ownedFiles: OwnedPath[] = [];\n const receipt = await reserveDestination(destination, paths);\n const ownedDirectories: OwnedPath[] = [receipt];\n\n try {\n ownedDirectories.push(\n await createDirectory(join(destination, \"components\")),\n );\n\n for (const name of COPYABLE_IMPLEMENTATION_FILES) {\n ownedFiles.push(\n await writeExclusive(\n join(destination, name),\n sourceDetails.implementationFiles.get(name)!,\n writeFile,\n ),\n );\n }\n\n if (sourceDetails.componentFiles.length === 0) {\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"components/.gitkeep\"),\n \"\",\n writeFile,\n ),\n );\n } else {\n for (const file of sourceDetails.componentFiles) {\n const sourceContent = await readRequiredSourceFile(\n join(sourcePath, \"components\", file),\n );\n const target = join(destination, \"components\", file);\n ownedDirectories.push(\n ...(await createNestedDirectories(\n dirname(target),\n join(destination, \"components\"),\n )),\n );\n ownedFiles.push(await writeExclusive(target, sourceContent, writeFile));\n }\n }\n\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"metadata.json\"),\n metadata,\n writeFile,\n ),\n );\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"tsconfig.json\"),\n buildTsConfig(paths.repositoryRoot, destination),\n writeFile,\n ),\n );\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"css-modules.d.ts\"),\n buildCssModulesDeclaration(),\n writeFile,\n ),\n );\n\n const writtenMetadata = await readNoFollow(\n join(destination, \"metadata.json\"),\n \"filesystem\",\n );\n if (writtenMetadata === null) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n parsePrototypeMetadata(JSON.parse(writtenMetadata.toString(\"utf8\")), {\n themeRegistry,\n });\n const expectedFiles = [\n ...COPYABLE_IMPLEMENTATION_FILES,\n ...sourceDetails.componentFiles.map((file) => join(\"components\", file)),\n ...(sourceDetails.componentFiles.length === 0\n ? [\"components/.gitkeep\"]\n : []),\n \"metadata.json\",\n \"tsconfig.json\",\n \"css-modules.d.ts\",\n ];\n return {\n destination,\n receipt,\n expectedFiles: Object.freeze(expectedFiles),\n };\n } catch (error) {\n try {\n await removeOwnedPaths(ownedFiles, ownedDirectories);\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"duplicate.js","sourceRoot":"","sources":["../src/duplicate.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport {\n lstat,\n mkdir,\n open,\n readdir,\n rm,\n rmdir,\n writeFile as writeFileAsync,\n} from \"node:fs/promises\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport {\n definePrototypeRef,\n parsePrototypeMetadata,\n type PrototypeMetadata,\n type PrototypeRef,\n type ThemeRegistry,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n buildCssModulesDeclaration,\n buildMetadataJson,\n buildTsConfig,\n} from \"./internal/prototype-templates.js\";\nimport {\n captureOwnedDestination,\n type OwnedDestinationResult,\n} from \"./internal/owned-destination.js\";\nimport type { ResolvedPrototypeCatalogPaths } from \"./prototype-paths.js\";\nimport { isFileNotFound } from \"./internal/utils.js\";\nimport { isProtectedPrototypePath } from \"./lifecycle/prototype-path-policy.js\";\n\nconst FILE_OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0);\n// These are preparation context, rather than authored prototype content. The\n// credential/runtime exclusions are shared with lifecycle path operations.\nconst FRESH_CONTEXT_NAMES = new Set([\".claude\", \"AGENTS.md\", \"CLAUDE.md\"]);\n\nconst GENERATED_ROOT_FILES = new Set([\n \"metadata.json\",\n \"tsconfig.json\",\n \"css-modules.d.ts\",\n]);\n\ntype SafeWriteFile = (\n path: string,\n content: Uint8Array | string,\n options: { flag: \"wx\" },\n) => Promise<void>;\n\nexport type DuplicatePrototypeOptions = {\n paths: Pick<\n ResolvedPrototypeCatalogPaths,\n \"repositoryRoot\" | \"prototypeRoot\"\n >;\n source: PrototypeRef;\n destination: PrototypeRef;\n title: string;\n description: string | null;\n authorName: string | null;\n themeRegistry?: ThemeRegistry;\n clock?: () => Date;\n writeFile?: SafeWriteFile;\n};\n\nexport type PrototypeDuplicateErrorKind =\n \"source-not-found\" | \"invalid-source\" | \"collision\" | \"filesystem\";\n\nexport class PrototypeDuplicateError extends Error {\n readonly kind: PrototypeDuplicateErrorKind;\n\n constructor(kind: PrototypeDuplicateErrorKind) {\n super(`Prototype duplication failed: ${kind}`);\n this.name = \"PrototypeDuplicateError\";\n this.kind = kind;\n }\n}\n\ntype OwnedPath = {\n path: string;\n device: number;\n inode: number;\n};\n\nfunction sourcePathFor(root: string, ref: PrototypeRef) {\n return join(root, ref.login, ref.slug);\n}\n\nasync function ownedPath(path: string): Promise<OwnedPath> {\n const stats = await lstat(path);\n return { path, device: stats.dev, inode: stats.ino };\n}\n\nasync function assertNoSymlinkedSourceParents(\n path: string,\n root: string,\n kind: \"invalid-source\" | \"filesystem\",\n) {\n const segments = relative(root, path).split(sep).filter(Boolean);\n const ancestors = [root];\n let current = root;\n for (const segment of segments.slice(0, -1)) {\n current = join(current, segment);\n ancestors.push(current);\n }\n\n for (const ancestor of ancestors) {\n try {\n if ((await lstat(ancestor)).isSymbolicLink()) {\n throw new PrototypeDuplicateError(kind);\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (isFileNotFound(error)) return;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n }\n}\n\nasync function assertDirectory(\n path: string,\n kind: \"source-not-found\" | \"invalid-source\" | \"filesystem\",\n sourceRoot?: string,\n) {\n try {\n if (sourceRoot !== undefined) {\n await assertNoSymlinkedSourceParents(path, sourceRoot, \"invalid-source\");\n }\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(kind);\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (isFileNotFound(error)) throw new PrototypeDuplicateError(kind);\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function readNoFollow(\n path: string,\n kind: \"invalid-source\" | \"filesystem\",\n) {\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new PrototypeDuplicateError(kind);\n }\n const file = await open(path, FILE_OPEN_FLAGS);\n try {\n return await file.readFile();\n } finally {\n await file.close();\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (isFileNotFound(error)) return null;\n throw new PrototypeDuplicateError(kind);\n }\n}\n\nfunction isPrunedPath(path: string) {\n const normalizedPath = path.split(sep).join(\"/\");\n const name = normalizedPath.slice(normalizedPath.lastIndexOf(\"/\") + 1);\n // Duplicate has historically excluded every `.env*` file; retain that\n // destination-validation safety rule in addition to the shared policy.\n return (\n name.startsWith(\".env\") ||\n isProtectedPrototypePath(normalizedPath) ||\n FRESH_CONTEXT_NAMES.has(name)\n );\n}\n\n/**\n * Take a no-following snapshot of the authored source files. In particular,\n * the directory walk must not use a permissive extension list: prototypes can\n * contain arbitrary text, data, and binary assets.\n */\nasync function discoverSourceFiles(root: string, current = root) {\n let entries;\n try {\n entries = await readdir(current);\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const files = new Map<string, Uint8Array>();\n for (const name of entries.sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n )) {\n const path = join(current, name);\n const relativePath = relative(root, path).split(sep).join(\"/\");\n // Prune before lstat/readdir so excluded trees are not inspected at all.\n if (isPrunedPath(relativePath)) continue;\n\n let stats;\n try {\n stats = await lstat(path);\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n if (stats.isSymbolicLink() || (!stats.isDirectory() && !stats.isFile())) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n if (stats.isDirectory()) {\n const nested = await discoverSourceFiles(root, path);\n for (const [nestedPath, content] of nested) {\n files.set(nestedPath, content);\n }\n continue;\n }\n\n // Only the root marker is regenerated; nested markers preserve authored\n // directories and are copied like any other regular file.\n if (relativePath === \"components/.gitkeep\") {\n continue;\n }\n const content = await readNoFollow(path, \"invalid-source\");\n if (content === null) throw new PrototypeDuplicateError(\"invalid-source\");\n files.set(relativePath, content);\n }\n return files;\n}\n\nasync function inspectSource(\n sourcePath: string,\n sourceRoot: string,\n themeRegistry?: ThemeRegistry,\n) {\n await assertDirectory(sourcePath, \"source-not-found\", sourceRoot);\n\n try {\n await lstat(join(sourcePath, \"page.module.css\"));\n throw new PrototypeDuplicateError(\"invalid-source\");\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n }\n\n const files = await discoverSourceFiles(sourcePath);\n const metadataBytes = files.get(\"metadata.json\");\n if (metadataBytes === undefined) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n let metadata: PrototypeMetadata;\n try {\n metadata = parsePrototypeMetadata(\n JSON.parse(Buffer.from(metadataBytes).toString(\"utf8\")),\n { themeRegistry },\n );\n } catch {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n for (const name of [\"page.tsx\", \"app.tsx\", \"app.module.css\"]) {\n if (!files.has(name)) throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const componentsPath = join(sourcePath, \"components\");\n try {\n const stats = await lstat(componentsPath);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n }\n\n const componentFiles = [...files.keys()]\n .filter((file) => file.startsWith(\"components/\"))\n .sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));\n // `components/` is deliberately represented by the normal empty marker when\n // it has no authored files; all other discovered files are copied as-is.\n return { metadata, files, componentFiles };\n}\n\nasync function reserveDestination(\n path: string,\n paths: Pick<\n ResolvedPrototypeCatalogPaths,\n \"repositoryRoot\" | \"prototypeRoot\"\n >,\n) {\n try {\n await mkdir(path);\n } catch (error) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"EEXIST\"\n ) {\n throw new PrototypeDuplicateError(\"collision\");\n }\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n try {\n return await captureOwnedDestination({\n path,\n repositoryRoot: paths.repositoryRoot,\n prototypeRoot: paths.prototypeRoot,\n });\n } catch {\n // The newly created path is retained because ownership was not proven.\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function ensureDirectory(path: string) {\n try {\n await mkdir(path, { recursive: true });\n await assertDirectory(path, \"filesystem\");\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function createDirectory(path: string) {\n try {\n await mkdir(path);\n await assertDirectory(path, \"filesystem\");\n return await ownedPath(path);\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function createNestedDirectories(path: string, existingRoot: string) {\n const missing: string[] = [];\n let current = path;\n while (current !== existingRoot) {\n try {\n const stats = await lstat(current);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n break;\n } catch (error) {\n if (error instanceof PrototypeDuplicateError) throw error;\n if (!isFileNotFound(error)) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n missing.push(current);\n const parent = dirname(current);\n if (parent === current) throw new PrototypeDuplicateError(\"filesystem\");\n current = parent;\n }\n }\n\n const owned: OwnedPath[] = [];\n for (const directory of missing.reverse()) {\n owned.push(await createDirectory(directory));\n }\n return owned;\n}\n\nasync function writeExclusive(\n path: string,\n content: Uint8Array | string,\n writeFile: SafeWriteFile,\n) {\n try {\n await writeFile(path, content, { flag: \"wx\" });\n return await ownedPath(path);\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nasync function removeOwnedPaths(\n files: readonly OwnedPath[],\n directories: readonly OwnedPath[],\n) {\n try {\n for (const owned of [...files].reverse()) {\n const current = await lstat(owned.path).catch(() => null);\n if (\n current !== null &&\n !current.isSymbolicLink() &&\n current.dev === owned.device &&\n current.ino === owned.inode\n ) {\n await rm(owned.path, { force: true });\n }\n }\n for (const owned of [...directories].reverse()) {\n const current = await lstat(owned.path).catch(() => null);\n if (\n current !== null &&\n current.isDirectory() &&\n !current.isSymbolicLink() &&\n current.dev === owned.device &&\n current.ino === owned.inode\n ) {\n await rmdir(owned.path);\n }\n }\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n\nexport async function duplicatePrototype({\n paths,\n source,\n destination: destinationRef,\n title,\n description,\n authorName,\n themeRegistry,\n clock = () => new Date(),\n writeFile = writeFileAsync,\n}: DuplicatePrototypeOptions): Promise<OwnedDestinationResult> {\n const normalizedSource = definePrototypeRef(source);\n const normalizedDestination = definePrototypeRef(destinationRef);\n const sourcePath = sourcePathFor(paths.prototypeRoot, normalizedSource);\n const destination = sourcePathFor(paths.prototypeRoot, normalizedDestination);\n const sourceDetails = await inspectSource(\n sourcePath,\n paths.prototypeRoot,\n themeRegistry,\n );\n if (sourceDetails.metadata.authorLogin !== normalizedSource.login) {\n throw new PrototypeDuplicateError(\"invalid-source\");\n }\n\n const timestamp = clock().toISOString();\n const metadata = buildMetadataJson(\n title,\n description,\n normalizedDestination.login,\n authorName,\n timestamp,\n sourceDetails.metadata.theme,\n );\n parsePrototypeMetadata(JSON.parse(metadata), { themeRegistry });\n\n await ensureDirectory(paths.prototypeRoot);\n await ensureDirectory(dirname(destination));\n const ownedFiles: OwnedPath[] = [];\n const receipt = await reserveDestination(destination, paths);\n const ownedDirectories: OwnedPath[] = [receipt];\n\n try {\n ownedDirectories.push(\n await createDirectory(join(destination, \"components\")),\n );\n\n const copiedFiles: string[] = [];\n for (const [name, content] of sourceDetails.files) {\n // These files describe the source context and are always regenerated for\n // the destination. Only root files are special; nested files with the\n // same names remain ordinary authored content.\n if (name.includes(\"/\") === false && GENERATED_ROOT_FILES.has(name)) {\n continue;\n }\n const target = join(destination, name);\n ownedDirectories.push(\n ...(await createNestedDirectories(dirname(target), destination)),\n );\n ownedFiles.push(await writeExclusive(target, content, writeFile));\n copiedFiles.push(name);\n }\n\n if (sourceDetails.componentFiles.length === 0) {\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"components/.gitkeep\"),\n \"\",\n writeFile,\n ),\n );\n }\n\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"metadata.json\"),\n metadata,\n writeFile,\n ),\n );\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"tsconfig.json\"),\n buildTsConfig(paths.repositoryRoot, destination),\n writeFile,\n ),\n );\n ownedFiles.push(\n await writeExclusive(\n join(destination, \"css-modules.d.ts\"),\n buildCssModulesDeclaration(),\n writeFile,\n ),\n );\n\n const writtenMetadata = await readNoFollow(\n join(destination, \"metadata.json\"),\n \"filesystem\",\n );\n if (writtenMetadata === null) {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n parsePrototypeMetadata(JSON.parse(writtenMetadata.toString(\"utf8\")), {\n themeRegistry,\n });\n const expectedFiles = [\n ...copiedFiles,\n ...(sourceDetails.componentFiles.length === 0\n ? [\"components/.gitkeep\"]\n : []),\n \"metadata.json\",\n \"tsconfig.json\",\n \"css-modules.d.ts\",\n ];\n return {\n destination,\n receipt,\n expectedFiles: Object.freeze(expectedFiles),\n };\n } catch (error) {\n try {\n await removeOwnedPaths(ownedFiles, ownedDirectories);\n } catch {\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n if (error instanceof PrototypeDuplicateError) throw error;\n throw new PrototypeDuplicateError(\"filesystem\");\n }\n}\n"]}
|
package/dist/git-repository.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const UPLOAD_INACTIVITY_TIMEOUT_MS: number;
|
|
2
|
+
export declare const UPLOAD_WALL_TIMEOUT_MS: number;
|
|
1
3
|
export type GitCommitIdentity = {
|
|
2
4
|
name: string;
|
|
3
5
|
email: string;
|
|
@@ -37,6 +39,12 @@ export type GitCommitResult = GitCommitSnapshot | null;
|
|
|
37
39
|
export type GitPushResult = {
|
|
38
40
|
remoteStatus: "pushed" | "local-only";
|
|
39
41
|
};
|
|
42
|
+
export type GitUploadProgress = {
|
|
43
|
+
stage: string;
|
|
44
|
+
progress: number;
|
|
45
|
+
processed: number;
|
|
46
|
+
total: number;
|
|
47
|
+
};
|
|
40
48
|
export type GitRepositoryErrorKind = "missing-git" | "timeout" | "conflict" | "non-fast-forward" | "command-failure" | "malformed-output" | "invalid-configuration" | "unexpected";
|
|
41
49
|
export declare class GitRepositoryError extends Error {
|
|
42
50
|
readonly kind: GitRepositoryErrorKind;
|
|
@@ -56,9 +64,13 @@ export interface GitRepository {
|
|
|
56
64
|
add(pathspecs: readonly string[]): Promise<void>;
|
|
57
65
|
commit(message: string, pathspecs?: readonly string[]): Promise<GitCommitResult>;
|
|
58
66
|
push(remote: string, branch: string, options?: readonly string[]): Promise<void>;
|
|
67
|
+
/** Push using the bounded, upload-specific timeout policy. */
|
|
68
|
+
pushUpload?(remote: string, branch: string, options?: readonly string[], onProgress?: (progress: GitUploadProgress) => void): Promise<void>;
|
|
59
69
|
restore(pathspecs: readonly string[]): Promise<void>;
|
|
60
70
|
clean(mode: string, pathspecs: readonly string[]): Promise<void>;
|
|
61
71
|
raw(args: readonly string[]): Promise<string>;
|
|
72
|
+
/** Execute a read/write Git command with a private environment. */
|
|
73
|
+
rawWithEnv?(args: readonly string[], environment: Readonly<Record<string, string>>): Promise<string>;
|
|
62
74
|
}
|
|
63
75
|
export type PrepareIdentityBranchOptions = {
|
|
64
76
|
identityBranch: string;
|
|
@@ -69,7 +81,8 @@ export declare function prepareIdentityBranch(repository: GitRepository, options
|
|
|
69
81
|
export declare function commitPath(repository: GitRepository, pathspec: string, message: string): Promise<GitCommitResult>;
|
|
70
82
|
export declare function discardPath(repository: GitRepository, pathspec: string): Promise<void>;
|
|
71
83
|
export declare function restorePathFromRef(repository: GitRepository, ref: string, pathspec: string): Promise<void>;
|
|
72
|
-
export declare function
|
|
84
|
+
export declare function remoteBranchCommit(repository: GitRepository, remote: string, branch: string): Promise<string | null>;
|
|
85
|
+
export declare function pushCommitToBranch(repository: GitRepository, remote: string, commit: string, branch: string, upload?: boolean): Promise<GitPushResult>;
|
|
73
86
|
export declare function pushBranch(repository: GitRepository, remote: string, branch: string): Promise<GitPushResult>;
|
|
74
87
|
export declare function createGitRepository(options: GitRepositoryOptions): GitRepository;
|
|
75
88
|
//# sourceMappingURL=git-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-repository.d.ts","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"non-fast-forward\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (\n normalized.includes(\"non-fast-forward\") ||\n normalized.includes(\"fetch first\") ||\n normalized.includes(\"stale info\")\n ) {\n return \"non-fast-forward\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#git = simpleGit(options.repositoryRoot, {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function restorePathFromRef(\n repository: GitRepository,\n ref: string,\n pathspec: string,\n): Promise<void> {\n assertValidArguments([ref], \"restore-ref\");\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\n \"restore\",\n \"--source\",\n ref,\n \"--staged\",\n \"--worktree\",\n \"--\",\n validatedPathspec,\n ]);\n}\n\nexport async function pushCommitToBranch(\n repository: GitRepository,\n remote: string,\n commit: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit\");\n }\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.raw([\"push\", remote, `${commit}:refs/heads/${branch}`]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git-repository.d.ts","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nexport const UPLOAD_INACTIVITY_TIMEOUT_MS = 5 * 60_000;\nexport const UPLOAD_WALL_TIMEOUT_MS = 30 * 60_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitUploadProgress = {\n stage: string;\n progress: number;\n processed: number;\n total: number;\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"non-fast-forward\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n /** Push using the bounded, upload-specific timeout policy. */\n pushUpload?(\n remote: string,\n branch: string,\n options?: readonly string[],\n onProgress?: (progress: GitUploadProgress) => void,\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n /** Execute a read/write Git command with a private environment. */\n rawWithEnv?(\n args: readonly string[],\n environment: Readonly<Record<string, string>>,\n ): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (\n normalized.includes(\"non-fast-forward\") ||\n normalized.includes(\"fetch first\") ||\n normalized.includes(\"stale info\")\n ) {\n return \"non-fast-forward\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n readonly #gitOptions;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#gitOptions = {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n };\n this.#git = simpleGit(options.repositoryRoot, {\n ...this.#gitOptions,\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async pushUpload(\n remote: string,\n branch: string,\n options: readonly string[] = [],\n onProgress?: (progress: GitUploadProgress) => void,\n ) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n const abort = new AbortController();\n const git = simpleGit(this.#gitOptions.baseDir, {\n ...this.#gitOptions,\n abort: abort.signal,\n progress:\n onProgress === undefined\n ? undefined\n : (progress) =>\n onProgress({\n stage: progress.stage,\n progress: progress.progress,\n processed: progress.processed,\n total: progress.total,\n }),\n timeout: { block: UPLOAD_INACTIVITY_TIMEOUT_MS },\n });\n let wallTimer: ReturnType<typeof setTimeout> | undefined;\n const push = git.push(remote, branch, validatedOptions);\n try {\n await Promise.race([\n push,\n new Promise<never>((_, reject) => {\n wallTimer = setTimeout(() => {\n abort.abort();\n reject(new GitRepositoryError(\"timeout\", \"upload\"));\n }, UPLOAD_WALL_TIMEOUT_MS);\n }),\n ]);\n } catch (error) {\n throw normalizeError(\"upload\", error);\n } finally {\n if (wallTimer !== undefined) clearTimeout(wallTimer);\n }\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n\n async rawWithEnv(\n args: readonly string[],\n environment: Readonly<Record<string, string>>,\n ) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n if (\n Object.keys(environment).some(\n (key) =>\n !/^[A-Z_][A-Z0-9_]*$/u.test(key) || environment[key] === undefined,\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"raw-environment\");\n }\n const git = simpleGit(this.#gitOptions.baseDir, {\n ...this.#gitOptions,\n timeout: { block: DEFAULT_TIMEOUT_MS },\n }).env(environment);\n const result = await this.#execute(\"raw\", () => git.raw(validatedArgs));\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function restorePathFromRef(\n repository: GitRepository,\n ref: string,\n pathspec: string,\n): Promise<void> {\n assertValidArguments([ref], \"restore-ref\");\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\n \"restore\",\n \"--source\",\n ref,\n \"--staged\",\n \"--worktree\",\n \"--\",\n validatedPathspec,\n ]);\n}\n\nexport async function remoteBranchCommit(\n repository: GitRepository,\n remote: string,\n branch: string,\n) {\n const output = await repository.raw([\n \"ls-remote\",\n remote,\n `refs/heads/${branch}`,\n ]);\n const value = output.trim().split(/\\s+/u)[0];\n return value !== undefined && /^[0-9a-f]{40,64}$/iu.test(value)\n ? value.toLowerCase()\n : null;\n}\n\nexport async function pushCommitToBranch(\n repository: GitRepository,\n remote: string,\n commit: string,\n branch: string,\n upload = false,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit\");\n }\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n try {\n const canUseUploadPolicy = upload && repository.pushUpload !== undefined;\n if (canUseUploadPolicy) {\n await repository.pushUpload!(remote, `${commit}:refs/heads/${branch}`);\n } else {\n await repository.raw([\"push\", remote, `${commit}:refs/heads/${branch}`]);\n }\n } catch (error) {\n try {\n if (\n (await remoteBranchCommit(repository, remote, branch)) ===\n commit.toLowerCase()\n ) {\n return { remoteStatus: \"pushed\" };\n }\n } catch {\n // Preserve the original push classification when confirmation is also\n // unavailable. The caller can surface this as upload-unconfirmed.\n }\n throw error;\n }\n\n if (\n (await remoteBranchCommit(repository, remote, branch)) !==\n commit.toLowerCase()\n ) {\n throw new GitRepositoryError(\"conflict\", \"source-confirmation\");\n }\n return { remoteStatus: \"pushed\" };\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
|
package/dist/git-repository.js
CHANGED
|
@@ -2,6 +2,8 @@ import { isAbsolute } from "node:path";
|
|
|
2
2
|
import { simpleGit } from "simple-git";
|
|
3
3
|
import { asFailure, isNonEmptyString, isPositiveInteger, isRecord, } from "./internal/utils.js";
|
|
4
4
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
export const UPLOAD_INACTIVITY_TIMEOUT_MS = 5 * 60_000;
|
|
6
|
+
export const UPLOAD_WALL_TIMEOUT_MS = 30 * 60_000;
|
|
5
7
|
const DEFAULT_MAX_CONCURRENT_PROCESSES = 1;
|
|
6
8
|
const DEFAULT_COMMIT_IDENTITY = {
|
|
7
9
|
name: "Bonfire",
|
|
@@ -110,15 +112,19 @@ function isNothingToCommit(error) {
|
|
|
110
112
|
class SimpleGitRepository {
|
|
111
113
|
repositoryRoot;
|
|
112
114
|
#git;
|
|
115
|
+
#gitOptions;
|
|
113
116
|
constructor(options) {
|
|
114
117
|
this.repositoryRoot = options.repositoryRoot;
|
|
115
|
-
this.#
|
|
118
|
+
this.#gitOptions = {
|
|
116
119
|
baseDir: options.repositoryRoot,
|
|
117
120
|
maxConcurrentProcesses: options.maxConcurrentProcesses,
|
|
118
121
|
config: [
|
|
119
122
|
`user.name=${options.commitIdentity.name}`,
|
|
120
123
|
`user.email=${options.commitIdentity.email}`,
|
|
121
124
|
],
|
|
125
|
+
};
|
|
126
|
+
this.#git = simpleGit(options.repositoryRoot, {
|
|
127
|
+
...this.#gitOptions,
|
|
122
128
|
timeout: { block: options.timeoutMs },
|
|
123
129
|
});
|
|
124
130
|
}
|
|
@@ -278,6 +284,45 @@ class SimpleGitRepository {
|
|
|
278
284
|
const validatedOptions = assertValidArguments(options, "push");
|
|
279
285
|
await this.#execute("push", () => this.#git.push(remote, branch, validatedOptions));
|
|
280
286
|
}
|
|
287
|
+
async pushUpload(remote, branch, options = [], onProgress) {
|
|
288
|
+
assertValidBranchName(remote, "remote");
|
|
289
|
+
assertValidBranchName(branch, "branch");
|
|
290
|
+
const validatedOptions = assertValidArguments(options, "push");
|
|
291
|
+
const abort = new AbortController();
|
|
292
|
+
const git = simpleGit(this.#gitOptions.baseDir, {
|
|
293
|
+
...this.#gitOptions,
|
|
294
|
+
abort: abort.signal,
|
|
295
|
+
progress: onProgress === undefined
|
|
296
|
+
? undefined
|
|
297
|
+
: (progress) => onProgress({
|
|
298
|
+
stage: progress.stage,
|
|
299
|
+
progress: progress.progress,
|
|
300
|
+
processed: progress.processed,
|
|
301
|
+
total: progress.total,
|
|
302
|
+
}),
|
|
303
|
+
timeout: { block: UPLOAD_INACTIVITY_TIMEOUT_MS },
|
|
304
|
+
});
|
|
305
|
+
let wallTimer;
|
|
306
|
+
const push = git.push(remote, branch, validatedOptions);
|
|
307
|
+
try {
|
|
308
|
+
await Promise.race([
|
|
309
|
+
push,
|
|
310
|
+
new Promise((_, reject) => {
|
|
311
|
+
wallTimer = setTimeout(() => {
|
|
312
|
+
abort.abort();
|
|
313
|
+
reject(new GitRepositoryError("timeout", "upload"));
|
|
314
|
+
}, UPLOAD_WALL_TIMEOUT_MS);
|
|
315
|
+
}),
|
|
316
|
+
]);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
throw normalizeError("upload", error);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
if (wallTimer !== undefined)
|
|
323
|
+
clearTimeout(wallTimer);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
281
326
|
async restore(pathspecs) {
|
|
282
327
|
const validatedPathspecs = assertValidPathspecs(pathspecs);
|
|
283
328
|
await this.#execute("restore", () => this.#git.checkout(["--", ...validatedPathspecs]));
|
|
@@ -297,6 +342,21 @@ class SimpleGitRepository {
|
|
|
297
342
|
}
|
|
298
343
|
return result;
|
|
299
344
|
}
|
|
345
|
+
async rawWithEnv(args, environment) {
|
|
346
|
+
const validatedArgs = assertValidArguments(args, "raw");
|
|
347
|
+
if (Object.keys(environment).some((key) => !/^[A-Z_][A-Z0-9_]*$/u.test(key) || environment[key] === undefined)) {
|
|
348
|
+
throw new GitRepositoryError("invalid-configuration", "raw-environment");
|
|
349
|
+
}
|
|
350
|
+
const git = simpleGit(this.#gitOptions.baseDir, {
|
|
351
|
+
...this.#gitOptions,
|
|
352
|
+
timeout: { block: DEFAULT_TIMEOUT_MS },
|
|
353
|
+
}).env(environment);
|
|
354
|
+
const result = await this.#execute("raw", () => git.raw(validatedArgs));
|
|
355
|
+
if (typeof result !== "string") {
|
|
356
|
+
throw new GitRepositoryError("malformed-output", "raw");
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
300
360
|
}
|
|
301
361
|
export async function prepareIdentityBranch(repository, options) {
|
|
302
362
|
assertValidBranchName(options.identityBranch, "identity-branch");
|
|
@@ -378,7 +438,18 @@ export async function restorePathFromRef(repository, ref, pathspec) {
|
|
|
378
438
|
validatedPathspec,
|
|
379
439
|
]);
|
|
380
440
|
}
|
|
381
|
-
export async function
|
|
441
|
+
export async function remoteBranchCommit(repository, remote, branch) {
|
|
442
|
+
const output = await repository.raw([
|
|
443
|
+
"ls-remote",
|
|
444
|
+
remote,
|
|
445
|
+
`refs/heads/${branch}`,
|
|
446
|
+
]);
|
|
447
|
+
const value = output.trim().split(/\s+/u)[0];
|
|
448
|
+
return value !== undefined && /^[0-9a-f]{40,64}$/iu.test(value)
|
|
449
|
+
? value.toLowerCase()
|
|
450
|
+
: null;
|
|
451
|
+
}
|
|
452
|
+
export async function pushCommitToBranch(repository, remote, commit, branch, upload = false) {
|
|
382
453
|
assertValidBranchName(remote, "remote");
|
|
383
454
|
assertValidBranchName(branch, "branch");
|
|
384
455
|
if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {
|
|
@@ -387,7 +458,32 @@ export async function pushCommitToBranch(repository, remote, commit, branch) {
|
|
|
387
458
|
const hasRemote = (await repository.remotes()).some((configuredRemote) => configuredRemote.name === remote);
|
|
388
459
|
if (!hasRemote)
|
|
389
460
|
return { remoteStatus: "local-only" };
|
|
390
|
-
|
|
461
|
+
try {
|
|
462
|
+
const canUseUploadPolicy = upload && repository.pushUpload !== undefined;
|
|
463
|
+
if (canUseUploadPolicy) {
|
|
464
|
+
await repository.pushUpload(remote, `${commit}:refs/heads/${branch}`);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
await repository.raw(["push", remote, `${commit}:refs/heads/${branch}`]);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
try {
|
|
472
|
+
if ((await remoteBranchCommit(repository, remote, branch)) ===
|
|
473
|
+
commit.toLowerCase()) {
|
|
474
|
+
return { remoteStatus: "pushed" };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
// Preserve the original push classification when confirmation is also
|
|
479
|
+
// unavailable. The caller can surface this as upload-unconfirmed.
|
|
480
|
+
}
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
if ((await remoteBranchCommit(repository, remote, branch)) !==
|
|
484
|
+
commit.toLowerCase()) {
|
|
485
|
+
throw new GitRepositoryError("conflict", "source-confirmation");
|
|
486
|
+
}
|
|
391
487
|
return { remoteStatus: "pushed" };
|
|
392
488
|
}
|
|
393
489
|
export async function pushBranch(repository, remote, branch) {
|