@descent-vtt/spec-brief 0.4.1 → 0.5.1
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/CHANGELOG.md +53 -0
- package/README.md +26 -7
- package/dist/archive.js +31 -18
- package/dist/archive.js.map +1 -1
- package/dist/brief.js +8 -5
- package/dist/brief.js.map +1 -1
- package/dist/cli.js +21 -13
- package/dist/cli.js.map +1 -1
- package/dist/collisions.d.ts +9 -0
- package/dist/collisions.js +50 -40
- package/dist/collisions.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +17 -6
- package/dist/config.js.map +1 -1
- package/dist/corpus.js +21 -15
- package/dist/corpus.js.map +1 -1
- package/dist/engine.d.ts +2 -1
- package/dist/engine.js +24 -15
- package/dist/engine.js.map +1 -1
- package/dist/frontmatter.js +1 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/fs.js +9 -5
- package/dist/fs.js.map +1 -1
- package/dist/git.js +7 -4
- package/dist/git.js.map +1 -1
- package/dist/glob.js +2 -0
- package/dist/glob.js.map +1 -1
- package/dist/links.js +2 -1
- package/dist/links.js.map +1 -1
- package/dist/lint.js +3 -4
- package/dist/lint.js.map +1 -1
- package/dist/plugins.js +10 -6
- package/dist/plugins.js.map +1 -1
- package/dist/report.d.ts +8 -1
- package/dist/report.js +22 -20
- package/dist/report.js.map +1 -1
- package/dist/rules.js +8 -4
- package/dist/rules.js.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/schedule.js +10 -20
- package/dist/schedule.js.map +1 -1
- package/dist/schema.js +9 -13
- package/dist/schema.js.map +1 -1
- package/dist/text.d.ts +4 -1
- package/dist/text.js +10 -6
- package/dist/text.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The engine: one repository's briefs, with the operations the CLI offers and\n * the same ones for a harness or an orchestrator that would rather call a\n * function than parse a terminal.\n *\n * This module and the CLI are where the pieces meet their implementations -\n * the real filesystem, the real git, the plugins a configuration names.\n * Everything they compose is a pure function that takes those as arguments,\n * so an engine can equally run over a `MemoryFileSystem` and no git at all.\n */\n\nimport { access } from 'node:fs/promises';\nimport { dirname, relative, resolve, sep } from 'node:path';\n\nimport { applyPlan } from './apply.js';\nimport {\n applyWaivers,\n planArchive,\n type Plan,\n planUnarchive,\n type PullRequest,\n type UnarchiveRequest,\n type Unmeasured,\n WAIVABLE,\n type Waiver,\n} from './archive.js';\nimport type { Brief } from './brief.js';\nimport { collisionFindings, collisions, type CollisionOptions, type CollisionReport } from './collisions.js';\nimport { type Config, ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig } from './config.js';\nimport { buildCorpus, type Corpus, findBriefs, isReady, pendingDependencies, type SourceFile } from './corpus.js';\nimport { canonicalPath, type FileSystem, NodeFileSystem } from './fs.js';\nimport { type CommitInfo, type FileChange, type Git, NodeGit, pullRequestUrl } from './git.js';\nimport { matchGlob, parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { lint, type Plugin } from './lint.js';\nimport { asWaivers, loadPlugins } from './plugins.js';\nimport { fileNameFor, nextId, renderNewBrief } from './scaffold.js';\nimport { planWaves, type Schedule, schedule, scheduleFindings, type ScheduleOptions } from './schedule.js';\nimport type { Finding, Phase } from './types.js';\n\nexport class EngineError extends Error {\n readonly code: 'not-found' | 'ambiguous' | 'invalid' | 'refused' | 'exists';\n\n constructor(code: EngineError['code'], message: string) {\n super(message);\n this.name = 'EngineError';\n this.code = code;\n }\n}\n\nexport interface EngineSetup {\n /** Absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly config: Config;\n /** Repository-relative configuration path, or `null` when the defaults apply. */\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n readonly plugins?: readonly Plugin[];\n}\n\nexport interface OpenOptions {\n /** Where discovery starts. Defaults to the process's directory. */\n readonly cwd?: string;\n /** An explicit configuration file; discovery is skipped. */\n readonly config?: string;\n /** Use the defaults even where a configuration file exists. */\n readonly noConfig?: boolean;\n /** `null` runs without git. Defaults to git when the root is inside a working tree. */\n readonly git?: Git | null;\n readonly fs?: FileSystem;\n}\n\nexport interface ArchiveOptions {\n readonly pr?: number | undefined;\n /** The commit the round landed as. */\n readonly commit?: string | undefined;\n /** The branch the round started from; the diff runs from its merge base. */\n readonly base?: string | undefined;\n readonly summary?: string | undefined;\n readonly date?: string | undefined;\n readonly allowDirty?: boolean | undefined;\n readonly strict?: boolean | undefined;\n /** Leave git out, even where there is a repository. */\n readonly noGit?: boolean | undefined;\n}\n\nexport interface NewOptions {\n readonly title: string;\n readonly id?: string | undefined;\n readonly type?: string | undefined;\n readonly wave?: number | undefined;\n readonly dependsOn?: readonly string[] | undefined;\n readonly date?: string | undefined;\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Today, in UTC. `SOURCE_DATE_EPOCH` wins where it is set, which is what makes\n * a banner written in a reproducible pipeline reproducible.\n */\nexport function today(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const epoch = env['SOURCE_DATE_EPOCH'];\n const when = epoch !== undefined && /^\\d+$/.test(epoch) ? new Date(Number(epoch) * 1000) : new Date();\n return when.toISOString().slice(0, 10);\n}\n\n/**\n * A deep copy of a value in which every object and array is frozen. Plain\n * data only, as `structuredClone` takes it; the recursion ends at the data's\n * own depth.\n */\nfunction frozenCopy<T>(value: T): T {\n const freeze = (item: unknown): void => {\n if (typeof item !== 'object' || item === null) return;\n for (const child of Object.values(item)) freeze(child);\n Object.freeze(item);\n };\n const copy = structuredClone(value);\n freeze(copy);\n return copy;\n}\n\nexport function isDate(text: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) return false;\n const parsed = new Date(`${text}T00:00:00Z`);\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === text;\n}\n\nexport class BriefEngine {\n readonly root: string;\n readonly config: Config;\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n private plugins: readonly Plugin[] | undefined;\n private loaded: Corpus | undefined;\n private missingBriefs = false;\n\n constructor(setup: EngineSetup) {\n this.root = setup.root;\n this.config = setup.config;\n this.configFile = setup.configFile;\n this.fs = setup.fs;\n this.git = setup.git;\n this.plugins = setup.plugins;\n }\n\n /**\n * Finds the configuration, the root it defines and the repository around it,\n * and reads every brief. The root is the directory holding the\n * configuration; without one it is the working tree's top, or `cwd`.\n */\n static async open(options: OpenOptions = {}): Promise<BriefEngine> {\n // Named as git names them, so the root and the work tree compare.\n const cwd = await canonicalPath(resolve(options.cwd ?? process.cwd()));\n const explicit = options.config === undefined ? undefined : await canonicalPath(resolve(cwd, options.config));\n const configPath = options.noConfig === true ? undefined : (explicit ?? (await locateConfig(cwd, exists)));\n // Asking git costs a process; without git there is nothing to ask.\n const toplevel = options.git === null ? null : await NodeGit.toplevel(cwd);\n const root = configPath === undefined ? (toplevel ?? cwd) : dirname(configPath);\n let config = DEFAULT_CONFIG;\n if (configPath !== undefined) {\n const fs = new NodeFileSystem(dirname(configPath));\n const name = configPath.slice(dirname(configPath).length + 1);\n const text = await fs.read(name);\n if (text === null) throw new EngineError('not-found', `${configPath} does not exist`);\n config = parseConfig(text, relative(cwd, configPath));\n }\n const inTree = toplevel !== null && !relative(toplevel, root).startsWith('..');\n const engine = new BriefEngine({\n root,\n config,\n configFile: configPath === undefined ? null : relative(root, configPath).split(sep).join('/'),\n fs: options.fs ?? new NodeFileSystem(root),\n git: options.git === undefined ? (inTree ? new NodeGit(root) : null) : options.git,\n });\n await engine.load();\n return engine;\n }\n\n get corpus(): Corpus {\n if (this.loaded === undefined) throw new Error('the engine has not loaded its briefs; call load() first');\n return this.loaded;\n }\n\n /** Reads every brief again, as after an archival. */\n async load(): Promise<Corpus> {\n const files: SourceFile[] = [];\n const accept = parseGlob(this.config.files);\n const reject = this.config.exclude.map((pattern) => parseGlob(pattern));\n const isBrief = (name: string): boolean =>\n accept.ok && matchGlob(accept.glob, name) && !reject.some((r) => r.ok && matchGlob(r.glob, name));\n const read = async (directory: string, phase: Phase): Promise<boolean> => {\n const dir = normalisePath(directory);\n const names = await this.fs.list(dir);\n for (const name of names ?? []) {\n if (!isBrief(name)) continue;\n const path = dir === '' ? name : `${dir}/${name}`;\n const text = await this.fs.read(path);\n // A file listed a moment ago and gone now was removed under the run; there is nothing to read.\n /* v8 ignore next */\n if (text !== null) files.push({ path, text, phase });\n }\n return names !== null;\n };\n this.missingBriefs = !(await read(this.config.briefs, 'live'));\n await read(this.config.archive, 'archived');\n this.loaded = buildCorpus(files, this.config);\n return this.loaded;\n }\n\n /**\n * Refuses to report on a briefs directory that does not exist. A check over\n * nothing looks exactly like a clean one, and the likelier cause is a\n * configuration pointing at the wrong place.\n */\n requireBriefs(): void {\n if (this.missingBriefs) {\n throw new EngineError('not-found', `${normalisePath(this.config.briefs)}/ does not exist; run \"spec-brief init\", or set \"briefs\" in the configuration`);\n }\n }\n\n /** The plugins the configuration names, loaded once. */\n async loadedPlugins(): Promise<readonly Plugin[]> {\n this.plugins ??= await loadPlugins(this.config.plugins, this.root);\n return this.plugins;\n }\n\n /** The one brief a reference names: an id, a file name or a path. */\n find(reference: string): Brief {\n const found = findBriefs(this.corpus, reference);\n if (found.length === 0) throw new EngineError('not-found', `no brief is named \"${reference}\"`);\n if (found.length > 1) {\n throw new EngineError('ambiguous', `\"${reference}\" names ${found.length} briefs: ${found.map((b) => b.file).join(', ')}`);\n }\n return found[0] as Brief;\n }\n\n /** The files git sees, or every file when there is no git; `null` when neither can be read. */\n async repoFiles(): Promise<string[] | null> {\n try {\n return this.git === null ? await this.fs.walk() : await this.git.files();\n } catch {\n return null;\n }\n }\n\n async lint(references: readonly string[] = []): Promise<Finding[]> {\n const only = references.length === 0 ? undefined : references.map((r) => this.find(r));\n return lint(this.corpus, { plugins: await this.loadedPlugins(), repoFiles: await this.repoFiles(), ...(only ? { only } : {}) });\n }\n\n async collisions(options: CollisionOptions = {}): Promise<{ report: CollisionReport; findings: Finding[] }> {\n const report = collisions(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { report, findings: collisionFindings(this.corpus, report) };\n }\n\n async schedule(options: ScheduleOptions = {}): Promise<{ schedule: Schedule; findings: Finding[] }> {\n const computed = schedule(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { schedule: computed, findings: scheduleFindings(this.corpus, computed) };\n }\n\n /**\n * Writes each brief's proposed wave into its front matter, every other line\n * as it was, as one transaction. Refused, with nothing written, when the\n * dependencies form a cycle or a front matter cannot be edited.\n */\n async writeWaves(computed: Schedule): Promise<{ written: string[]; refused: Finding[] }> {\n const plan = planWaves(computed);\n if (computed.cycles.length > 0 || plan.refused.length > 0) return { written: [], refused: [...plan.refused] };\n await applyPlan(this.fs, plan.ops);\n await this.load();\n return { written: plan.ops.map((op) => op.path), refused: [] };\n }\n\n /** Live briefs that can run now: every dependency archived, and neither a draft nor deferred. */\n ready(): Brief[] {\n return this.corpus.live.filter((b) => isReady(this.corpus, b));\n }\n\n /** The live dependencies holding a brief back. */\n waitingOn(brief: Brief): string[] {\n return pendingDependencies(this.corpus, brief);\n }\n\n async planArchive(reference: string, options: ArchiveOptions = {}): Promise<Plan> {\n const brief = this.find(reference);\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n if (options.noGit === true && (options.commit !== undefined || options.base !== undefined)) {\n throw new EngineError('invalid', 'a commit or a base needs git; drop --no-git, or drop --commit and --base');\n }\n const git = options.noGit === true ? null : this.git;\n const base = options.base ?? this.config.archiving.base ?? undefined;\n const findings = await this.lint();\n\n let commit: CommitInfo | undefined;\n let changes: FileChange[] | undefined;\n let unmeasured: Unmeasured | undefined;\n let dirty: string[] | undefined;\n let pr: PullRequest | undefined;\n if (git !== null) {\n const revision = options.commit ?? (base === undefined ? undefined : 'HEAD');\n if (revision !== undefined) {\n commit = (await git.commit(revision)) ?? undefined;\n if (commit === undefined) throw new EngineError('not-found', `\"${revision}\" names no commit`);\n let from: string | null = null;\n if (base !== undefined) {\n from = await git.mergeBase(base, commit.sha);\n // No merge base: a base with a history of its own is diffed from\n // directly, and one that names nothing is a typo to report, not a\n // diff for git to fail.\n if (from === null) {\n if ((await git.commit(base)) === null) {\n throw new EngineError('not-found', `the base \"${base}\" names no commit; check --base and \"archiving.base\"`);\n }\n from = base;\n }\n }\n // A commit that is its own merge base with the base branch is already\n // in it: the diff is empty whatever the round changed, and an empty\n // diff would pass every scope check without measuring one.\n if (from === commit.sha) unmeasured = { reason: 'merged', base: base as string, commit: commit.sha };\n else changes = await git.changes(from, commit.sha);\n } else {\n unmeasured = { reason: 'revision' };\n }\n dirty = await git.dirty();\n } else {\n unmeasured = { reason: 'git' };\n }\n if (options.pr !== undefined) {\n pr = { number: options.pr, url: git === null ? null : pullRequestUrl(await git.remoteUrl('origin'), options.pr) };\n }\n const plan = planArchive(this.corpus, brief, {\n date,\n summary: options.summary,\n pr,\n commit,\n changes,\n unmeasured,\n dirty,\n allowDirty: options.allowDirty,\n strict: options.strict,\n findings,\n });\n return this.waive(plan, base ?? null, commit?.sha ?? null, options.strict === true);\n }\n\n /**\n * Asks each plugin with a `waive` hook which of a plan's refusals its own\n * check lifts, and turns those into notes. Asked only when the plan has a\n * refusal a plugin may lift: a hook may read git and verify signatures, and a\n * plan with nothing to waive has no question for it. A hook that throws, or\n * answers in the wrong shape, stops the run as a plugin that fails to load\n * does: an archival decided without the check it asked for is not one to\n * trust.\n *\n * A hook is handed a frozen copy of the refusals, and its waivers are\n * matched against the plan's own. Handed the plan's list, a hook could drop\n * a refusal from it, or relabel an open task as a protected file and waive\n * that, and so lift what only spec-brief may; writing to the copy throws,\n * which stops the run like any failing hook.\n */\n private async waive(plan: Plan, base: string | null, commit: string | null, strict: boolean): Promise<Plan> {\n if (!plan.blocking.some((f) => WAIVABLE.includes(f.rule))) return plan;\n const context = frozenCopy({\n root: this.root,\n brief: { id: plan.brief.id, file: plan.brief.file, text: plan.brief.text },\n findings: plan.blocking,\n base,\n commit,\n });\n const waivers: { plugin: string; waiver: Waiver }[] = [];\n for (const plugin of await this.loadedPlugins()) {\n if (plugin.waive === undefined) continue;\n let answer: unknown;\n try {\n answer = await plugin.waive(context);\n } catch (error) {\n throw new ConfigError(`plugin \"${plugin.name}\"`, [`\"waive\" failed: ${error instanceof Error ? error.message : String(error)}`]);\n }\n for (const waiver of asWaivers(answer, plugin.name)) waivers.push({ plugin: plugin.name, waiver });\n }\n return applyWaivers(plan, waivers, strict);\n }\n\n planUnarchive(reference: string, options: UnarchiveRequest = {}): Plan {\n return planUnarchive(this.corpus, this.find(reference), options);\n }\n\n /** Executes a plan and reads the briefs again. A refused plan is never applied. */\n async apply(plan: Plan): Promise<void> {\n if (plan.blocking.length > 0) {\n throw new EngineError('refused', `${plan.action} of ${plan.brief.file} is refused: ${(plan.blocking[0] as Finding).message}`);\n }\n if (plan.done) return;\n await applyPlan(this.fs, plan.ops);\n await this.load();\n }\n\n /** Scaffolds a brief and writes it; the file is new, or nothing is written. */\n async create(options: NewOptions): Promise<{ file: string; content: string }> {\n const title = options.title.trim();\n if (title === '') throw new EngineError('invalid', 'a brief needs a title');\n const id = options.id ?? nextId(this.corpus);\n if (id === null) throw new EngineError('invalid', 'the ids here are not numbers, so there is no next one; pass --id');\n if (/[\\s/\\\\]/.test(id) || id.includes(this.config.id.separator)) {\n throw new EngineError('invalid', `\"${id}\" cannot be an id: no whitespace, slashes or \"${this.config.id.separator}\"`);\n }\n if (findBriefs(this.corpus, id).some((b) => b.id !== null)) throw new EngineError('exists', `the id \"${id}\" is taken`);\n if (options.type !== undefined && this.config.types[options.type] === undefined) {\n const names = Object.keys(this.config.types);\n throw new EngineError('invalid', `\"${options.type}\" is not a brief type here; use one of ${names.join(', ') || '(none)'}`);\n }\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n const template = this.config.template === null ? null : await this.fs.read(this.config.template);\n if (this.config.template !== null && template === null) {\n throw new EngineError('not-found', `the template ${this.config.template} does not exist`);\n }\n const content = renderNewBrief(\n this.config,\n { id, title, date, type: options.type, wave: options.wave, dependsOn: options.dependsOn },\n template,\n );\n const dir = normalisePath(this.config.briefs);\n const file = `${dir}/${fileNameFor(this.config, id, title)}`;\n if (await this.fs.exists(file)) throw new EngineError('exists', `${file} already exists`);\n await this.fs.write(file, content);\n await this.load();\n return { file, content };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The engine: one repository's briefs, with the operations the CLI offers and\n * the same ones for a harness or an orchestrator that would rather call a\n * function than parse a terminal.\n *\n * This module and the CLI are where the pieces meet their implementations -\n * the real filesystem, the real git, the plugins a configuration names.\n * Everything they compose is a pure function that takes those as arguments,\n * so an engine can equally run over a `MemoryFileSystem` and no git at all.\n */\n\nimport { access } from 'node:fs/promises';\nimport { basename, dirname, relative, resolve, sep } from 'node:path';\n\nimport { applyPlan } from './apply.js';\nimport {\n applyWaivers,\n planArchive,\n type Plan,\n planUnarchive,\n type PullRequest,\n type UnarchiveRequest,\n type Unmeasured,\n WAIVABLE,\n type Waiver,\n} from './archive.js';\nimport type { Brief } from './brief.js';\nimport { collisionFindings, collisions, type CollisionOptions, type CollisionReport } from './collisions.js';\nimport { type Config, ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig, typeSections } from './config.js';\nimport { buildCorpus, type Corpus, findBriefs, isReady, pendingDependencies, type SourceFile } from './corpus.js';\nimport { canonicalPath, type FileSystem, NodeFileSystem } from './fs.js';\nimport { type CommitInfo, type FileChange, type Git, NodeGit, pullRequestUrl } from './git.js';\nimport { matchGlob, parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { lint, type Plugin } from './lint.js';\nimport { asWaivers, loadPlugins } from './plugins.js';\nimport { fileNameFor, nextId, renderNewBrief } from './scaffold.js';\nimport { planWaves, type Schedule, schedule, scheduleFindings, type ScheduleOptions } from './schedule.js';\nimport type { Finding, Phase } from './types.js';\n\nexport class EngineError extends Error {\n readonly code: 'not-found' | 'ambiguous' | 'invalid' | 'refused' | 'exists';\n\n constructor(code: EngineError['code'], message: string) {\n super(message);\n this.name = 'EngineError';\n this.code = code;\n }\n}\n\nexport interface EngineSetup {\n /** Absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly config: Config;\n /** Repository-relative configuration path, or `null` when the defaults apply. */\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n readonly plugins?: readonly Plugin[];\n}\n\nexport interface OpenOptions {\n /** Where discovery starts. Defaults to the process's directory. */\n readonly cwd?: string;\n /** An explicit configuration file; discovery is skipped. */\n readonly config?: string | undefined;\n /** Use the defaults even where a configuration file exists. */\n readonly noConfig?: boolean;\n /** `null` runs without git. Defaults to git when the root is inside a working tree. */\n readonly git?: Git | null;\n readonly fs?: FileSystem;\n}\n\nexport interface ArchiveOptions {\n readonly pr?: number | undefined;\n /** The commit the round landed as. */\n readonly commit?: string | undefined;\n /** The branch the round started from; the diff runs from its merge base. */\n readonly base?: string | undefined;\n readonly summary?: string | undefined;\n readonly date?: string | undefined;\n readonly allowDirty?: boolean | undefined;\n readonly strict?: boolean | undefined;\n /** Leave git out, even where there is a repository. */\n readonly noGit?: boolean | undefined;\n}\n\nexport interface NewOptions {\n readonly title: string;\n readonly id?: string | undefined;\n readonly type?: string | undefined;\n readonly wave?: number | undefined;\n readonly dependsOn?: readonly string[] | undefined;\n readonly date?: string | undefined;\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n // Absent, or not to be reached: the search goes on either way. An empty\n // catch would answer `undefined`, which the search reads the same.\n return false;\n }\n}\n\n/**\n * Today, in UTC. `SOURCE_DATE_EPOCH` wins where it is set, which is what makes\n * a banner written in a reproducible pipeline reproducible.\n */\nexport function today(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const epoch = env['SOURCE_DATE_EPOCH'];\n // The check for a variable that is not set is the compiler's: unset, it would read as \"undefined\", which is no number.\n const when = epoch !== undefined && /^\\d+$/.test(epoch) ? new Date(Number(epoch) * 1000) : new Date();\n return when.toISOString().slice(0, 10);\n}\n\n/**\n * A deep copy of a value in which every object and array is frozen. Plain\n * data only, as `structuredClone` takes it; the recursion ends at the data's\n * own depth.\n */\nfunction frozenCopy<T>(value: T): T {\n const freeze = (item: unknown): void => {\n if (typeof item !== 'object' || item === null) return;\n for (const child of Object.values(item)) freeze(child);\n Object.freeze(item);\n };\n const copy = structuredClone(value);\n freeze(copy);\n return copy;\n}\n\nexport function isDate(text: string): boolean {\n // The shape keeps out the one other text that survives the round trip\n // below: a year past 9999 with a month, \"+010000-01\". Its anchors are for\n // the reader, since nothing longer than a date comes back as it went in.\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) return false;\n const parsed = new Date(`${text}T00:00:00Z`);\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === text;\n}\n\nexport class BriefEngine {\n readonly root: string;\n readonly config: Config;\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n private plugins: readonly Plugin[] | undefined;\n private loaded: Corpus | undefined;\n /** Known once the briefs are read, which every command does before it asks. */\n private missingBriefs: boolean | undefined;\n\n constructor(setup: EngineSetup) {\n this.root = setup.root;\n this.config = setup.config;\n this.configFile = setup.configFile;\n this.fs = setup.fs;\n this.git = setup.git;\n this.plugins = setup.plugins;\n }\n\n /**\n * Finds the configuration, the root it defines and the repository around it,\n * and reads every brief. The root is the directory holding the\n * configuration; without one it is the working tree's top, or `cwd`.\n */\n static async open(options: OpenOptions = {}): Promise<BriefEngine> {\n // Named as git names them, so the root and the work tree compare.\n const cwd = await canonicalPath(resolve(options.cwd ?? process.cwd()));\n const explicit = options.config === undefined ? undefined : await canonicalPath(resolve(cwd, options.config));\n const configPath = options.noConfig === true ? undefined : (explicit ?? (await locateConfig(cwd, exists)));\n // Asking git costs a process; without git there is nothing to ask.\n const toplevel = options.git === null ? null : await NodeGit.toplevel(cwd);\n const root = configPath === undefined ? (toplevel ?? cwd) : dirname(configPath);\n let config = DEFAULT_CONFIG;\n if (configPath !== undefined) {\n const text = await new NodeFileSystem(root).read(basename(configPath));\n if (text === null) throw new EngineError('not-found', `${configPath} does not exist`);\n config = parseConfig(text, relative(cwd, configPath));\n }\n // Outside the work tree is its parent or a path through it, \"..\", \"../x\":\n // a directory inside it may itself be named \"..drafts\".\n const fromTop = toplevel === null ? null : relative(toplevel, root);\n const inTree = fromTop !== null && fromTop !== '..' && !fromTop.startsWith(`..${sep}`);\n const engine = new BriefEngine({\n root,\n config,\n // The root is the configuration's directory, so its path from there is its name.\n configFile: configPath === undefined ? null : basename(configPath),\n fs: options.fs ?? new NodeFileSystem(root),\n git: options.git === undefined ? (inTree ? new NodeGit(root) : null) : options.git,\n });\n await engine.load();\n return engine;\n }\n\n get corpus(): Corpus {\n if (this.loaded === undefined) throw new Error('the engine has not loaded its briefs; call load() first');\n return this.loaded;\n }\n\n /** Reads every brief again, as after an archival. */\n async load(): Promise<Corpus> {\n const files: SourceFile[] = [];\n const accept = parseGlob(this.config.files);\n const reject = this.config.exclude.map((pattern) => parseGlob(pattern));\n const isBrief = (name: string): boolean =>\n accept.ok && matchGlob(accept.glob, name) && !reject.some((r) => r.ok && matchGlob(r.glob, name));\n const read = async (directory: string, phase: Phase): Promise<boolean> => {\n const dir = normalisePath(directory);\n const names = await this.fs.list(dir);\n if (names === null) return false;\n for (const name of names) {\n if (!isBrief(name)) continue;\n const path = dir === '' ? name : `${dir}/${name}`;\n const text = await this.fs.read(path);\n // A file listed a moment ago and gone now was removed under the run; there is nothing to read.\n if (text !== null) files.push({ path, text, phase });\n }\n return true;\n };\n this.missingBriefs = !(await read(this.config.briefs, 'live'));\n await read(this.config.archive, 'archived');\n this.loaded = buildCorpus(files, this.config);\n return this.loaded;\n }\n\n /**\n * Refuses to report on a briefs directory that does not exist. A check over\n * nothing looks exactly like a clean one, and the likelier cause is a\n * configuration pointing at the wrong place.\n */\n requireBriefs(): void {\n if (this.missingBriefs) {\n throw new EngineError('not-found', `${normalisePath(this.config.briefs)}/ does not exist; run \"spec-brief init\", or set \"briefs\" in the configuration`);\n }\n }\n\n /** The plugins the configuration names, loaded once. */\n async loadedPlugins(): Promise<readonly Plugin[]> {\n this.plugins ??= await loadPlugins(this.config.plugins, this.root);\n return this.plugins;\n }\n\n /** The one brief a reference names: an id, a file name or a path. */\n find(reference: string): Brief {\n const found = findBriefs(this.corpus, reference);\n if (found.length === 0) throw new EngineError('not-found', `no brief is named \"${reference}\"`);\n if (found.length > 1) {\n throw new EngineError('ambiguous', `\"${reference}\" names ${found.length} briefs: ${found.map((b) => b.file).join(', ')}`);\n }\n return found[0] as Brief;\n }\n\n /** The files git sees, or every file when there is no git; `null` when neither can be read. */\n async repoFiles(): Promise<string[] | null> {\n try {\n return this.git === null ? await this.fs.walk() : await this.git.files();\n } catch {\n return null;\n }\n }\n\n async lint(references: readonly string[] = []): Promise<Finding[]> {\n const only = references.length === 0 ? undefined : references.map((r) => this.find(r));\n return lint(this.corpus, { plugins: await this.loadedPlugins(), repoFiles: await this.repoFiles(), ...(only ? { only } : {}) });\n }\n\n async collisions(options: CollisionOptions = {}): Promise<{ report: CollisionReport; findings: Finding[] }> {\n const report = collisions(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { report, findings: collisionFindings(this.corpus, report) };\n }\n\n async schedule(options: ScheduleOptions = {}): Promise<{ schedule: Schedule; findings: Finding[] }> {\n const computed = schedule(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { schedule: computed, findings: scheduleFindings(this.corpus, computed) };\n }\n\n /**\n * Writes each brief's proposed wave into its front matter, every other line\n * as it was, as one transaction. Refused, with nothing written, when the\n * dependencies form a cycle or a front matter cannot be edited.\n */\n async writeWaves(computed: Schedule): Promise<{ written: string[]; refused: Finding[] }> {\n const plan = planWaves(computed);\n if (computed.cycles.length > 0 || plan.refused.length > 0) return { written: [], refused: [...plan.refused] };\n await applyPlan(this.fs, plan.ops);\n await this.load();\n return { written: plan.ops.map((op) => op.path), refused: [] };\n }\n\n /** Live briefs that can run now: every dependency archived, and neither a draft nor deferred. */\n ready(): Brief[] {\n return this.corpus.live.filter((b) => isReady(this.corpus, b));\n }\n\n /** The live dependencies holding a brief back. */\n waitingOn(brief: Brief): string[] {\n return pendingDependencies(this.corpus, brief);\n }\n\n async planArchive(reference: string, options: ArchiveOptions = {}): Promise<Plan> {\n const brief = this.find(reference);\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n if (options.noGit === true && (options.commit !== undefined || options.base !== undefined)) {\n throw new EngineError('invalid', 'a commit or a base needs git; drop --no-git, or drop --commit and --base');\n }\n const git = options.noGit === true ? null : this.git;\n const base = options.base ?? this.config.archiving.base ?? undefined;\n const findings = await this.lint();\n\n let commit: CommitInfo | undefined;\n let changes: FileChange[] | undefined;\n let unmeasured: Unmeasured | undefined;\n let dirty: string[] | undefined;\n let pr: PullRequest | undefined;\n if (git !== null) {\n const revision = options.commit ?? (base === undefined ? undefined : 'HEAD');\n if (revision !== undefined) {\n commit = (await git.commit(revision)) ?? undefined;\n if (commit === undefined) throw new EngineError('not-found', `\"${revision}\" names no commit`);\n let from: string | null = null;\n if (base !== undefined) {\n from = await git.mergeBase(base, commit.sha);\n // No merge base: a base with a history of its own is diffed from\n // directly, and one that names nothing is a typo to report, not a\n // diff for git to fail.\n if (from === null) {\n if ((await git.commit(base)) === null) {\n throw new EngineError('not-found', `the base \"${base}\" names no commit; check --base and \"archiving.base\"`);\n }\n from = base;\n }\n }\n // A commit that is its own merge base with the base branch is already\n // in it: the diff is empty whatever the round changed, and an empty\n // diff would pass every scope check without measuring one.\n if (from === commit.sha) unmeasured = { reason: 'merged', base: base as string, commit: commit.sha };\n else changes = await git.changes(from, commit.sha);\n }\n // With neither a commit nor a base, nothing was read, and the planner\n // says so itself of a request that brings no changes and no reason.\n dirty = await git.dirty();\n } else {\n unmeasured = { reason: 'git' };\n }\n if (options.pr !== undefined) {\n pr = { number: options.pr, url: git === null ? null : pullRequestUrl(await git.remoteUrl('origin'), options.pr) };\n }\n const plan = planArchive(this.corpus, brief, {\n date,\n summary: options.summary,\n pr,\n commit,\n changes,\n unmeasured,\n dirty,\n allowDirty: options.allowDirty,\n strict: options.strict,\n findings,\n });\n return this.waive(plan, base ?? null, commit?.sha ?? null, options.strict === true);\n }\n\n /**\n * Asks each plugin with a `waive` hook which of a plan's refusals its own\n * check lifts, and turns those into notes. Asked only when the plan has a\n * refusal a plugin may lift: a hook may read git and verify signatures, and a\n * plan with nothing to waive has no question for it. A hook that throws, or\n * answers in the wrong shape, stops the run as a plugin that fails to load\n * does: an archival decided without the check it asked for is not one to\n * trust.\n *\n * A hook is handed a frozen copy of the refusals, and its waivers are\n * matched against the plan's own. Handed the plan's list, a hook could drop\n * a refusal from it, or relabel an open task as a protected file and waive\n * that, and so lift what only spec-brief may; writing to the copy throws,\n * which stops the run like any failing hook.\n */\n private async waive(plan: Plan, base: string | null, commit: string | null, strict: boolean): Promise<Plan> {\n if (!plan.blocking.some((f) => WAIVABLE.includes(f.rule))) return plan;\n const context = frozenCopy({\n root: this.root,\n brief: { id: plan.brief.id, file: plan.brief.file, text: plan.brief.text },\n findings: plan.blocking,\n base,\n commit,\n });\n const waivers: { plugin: string; waiver: Waiver }[] = [];\n for (const plugin of await this.loadedPlugins()) {\n if (plugin.waive === undefined) continue;\n let answer: unknown;\n try {\n answer = await plugin.waive(context);\n } catch (error) {\n throw new ConfigError(`plugin \"${plugin.name}\"`, [`\"waive\" failed: ${error instanceof Error ? error.message : String(error)}`]);\n }\n for (const waiver of asWaivers(answer, plugin.name)) waivers.push({ plugin: plugin.name, waiver });\n }\n return applyWaivers(plan, waivers, strict);\n }\n\n planUnarchive(reference: string, options: UnarchiveRequest = {}): Plan {\n return planUnarchive(this.corpus, this.find(reference), options);\n }\n\n /** Executes a plan and reads the briefs again. A refused plan is never applied. */\n async apply(plan: Plan): Promise<void> {\n if (plan.blocking.length > 0) {\n throw new EngineError('refused', `${plan.action} of ${plan.brief.file} is refused: ${(plan.blocking[0] as Finding).message}`);\n }\n if (plan.done) return;\n await applyPlan(this.fs, plan.ops);\n await this.load();\n }\n\n /** Scaffolds a brief and writes it; the file is new, or nothing is written. */\n async create(options: NewOptions): Promise<{ file: string; content: string }> {\n const title = options.title.trim();\n if (title === '') throw new EngineError('invalid', 'a brief needs a title');\n const id = options.id ?? nextId(this.corpus);\n if (id === null) throw new EngineError('invalid', 'the ids here are not numbers, so there is no next one; pass --id');\n if (/[\\s/\\\\]/.test(id) || id.includes(this.config.id.separator)) {\n throw new EngineError('invalid', `\"${id}\" cannot be an id: no whitespace, slashes or \"${this.config.id.separator}\"`);\n }\n if (findBriefs(this.corpus, id).some((b) => b.id !== null)) throw new EngineError('exists', `the id \"${id}\" is taken`);\n if (options.type !== undefined && typeSections(this.config, options.type) === undefined) {\n const names = Object.keys(this.config.types);\n throw new EngineError('invalid', `\"${options.type}\" is not a brief type here; use one of ${names.join(', ') || '(none)'}`);\n }\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n const template = this.config.template === null ? null : await this.fs.read(this.config.template);\n if (this.config.template !== null && template === null) {\n throw new EngineError('not-found', `the template ${this.config.template} does not exist`);\n }\n const content = renderNewBrief(\n this.config,\n { id, title, date, type: options.type, wave: options.wave, dependsOn: options.dependsOn },\n template,\n );\n const dir = normalisePath(this.config.briefs);\n const file = `${dir}/${fileNameFor(this.config, id, title)}`;\n if (await this.fs.exists(file)) throw new EngineError('exists', `${file} already exists`);\n await this.fs.write(file, content);\n await this.load();\n return { file, content };\n }\n}\n"]}
|
package/dist/frontmatter.js
CHANGED
|
@@ -37,6 +37,6 @@ export function setEntryKeepingComment(lines, frontMatter, key, rendered) {
|
|
|
37
37
|
}
|
|
38
38
|
/** Front matter an edit can be written into: YAML, and closed. */
|
|
39
39
|
export function editable(frontMatter) {
|
|
40
|
-
return frontMatter.kind === 'yaml' && frontMatter.close
|
|
40
|
+
return frontMatter.kind === 'yaml' && frontMatter.close !== -1;
|
|
41
41
|
}
|
|
42
42
|
//# sourceMappingURL=frontmatter.js.map
|
package/dist/frontmatter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,eAAe,IAAI,QAAQ,EAAE,QAAQ,EAAoB,MAAM,sCAAsC,CAAC;AAC1H,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EACL,SAAS,EACT,MAAM,EACN,OAAO,EACP,WAAW,EACX,WAAW,EACX,YAAY,EACZ,QAAQ,GAMT,MAAM,sCAAsC,CAAC;AAE9C,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAwB,EAAE,WAA+B,EAAE,GAAW,EAAE,QAAgB;IAC7H,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IACvE,2DAA2D;IAC3D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7F,MAAM,IAAI,GAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAC7E,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,WAAwB;IAC/C,OAAO,WAAW,CAAC,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,eAAe,IAAI,QAAQ,EAAE,QAAQ,EAAoB,MAAM,sCAAsC,CAAC;AAC1H,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EACL,SAAS,EACT,MAAM,EACN,OAAO,EACP,WAAW,EACX,WAAW,EACX,YAAY,EACZ,QAAQ,GAMT,MAAM,sCAAsC,CAAC;AAE9C,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,OAAO,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAwB,EAAE,WAA+B,EAAE,GAAW,EAAE,QAAgB;IAC7H,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IACvE,2DAA2D;IAC3D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7F,MAAM,IAAI,GAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAC7E,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,WAAwB;IAC/C,OAAO,WAAW,CAAC,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;AACjE,CAAC","sourcesContent":["/**\n * A brief's front matter: spec-core's reader and editor, copied into\n * `src/vendor/spec-core/`, over a brief's lines.\n *\n * The flat subset of YAML briefs use: `key: value` lines, plain and quoted\n * scalars, inline and block lists, comments. Anything richer is reported as\n * unsupported rather than guessed at, TOML between `+++` lines included, and\n * every value keeps its line, so an edit leaves every other line of the file\n * as it was (ADR-0001). Offsets in what it reads count in the lines joined by\n * `\\n`, which for a brief are its `lines`, not its `text`.\n */\n\nimport { findEntry, readFrontMatter as readText, setEntry, type FrontMatter } from './vendor/spec-core/markdown/index.js';\nimport { textOfLines } from './text.js';\n\nexport {\n findEntry,\n isNull,\n keyName,\n parseInline,\n removeEntry,\n renderScalar,\n setEntry,\n type FrontMatter,\n type FrontMatterEntry,\n type FrontMatterProblem,\n type YamlScalar,\n type YamlValue,\n} from './vendor/spec-core/markdown/index.js';\n\n/** Reads the front matter at the top of `lines`, or `null` when there is none. */\nexport function readFrontMatter(lines: readonly string[]): FrontMatter | null {\n return readText(textOfLines(lines));\n}\n\n/**\n * Sets one key as `setEntry` does, and keeps a comment that followed the old\n * value on its line: `wave: 2 # after the review` becomes `wave: 3 # after\n * the review`. spec-core's editor writes the key and the value alone, and a\n * comment beside a value is often the only record of why it was chosen. A\n * value over several lines is rewritten as `setEntry` rewrites it.\n */\nexport function setEntryKeepingComment(lines: readonly string[], frontMatter: FrontMatter | null, key: string, rendered: string): string[] {\n const edited = setEntry(lines, frontMatter, key, rendered);\n const entry = findEntry(frontMatter, key);\n if (entry === undefined || entry.end !== entry.line + 1) return edited;\n // Offsets count in the lines joined by one character each.\n const lineStart = lines.slice(0, entry.line).reduce((sum, line) => sum + line.length + 1, 0);\n const rest = (lines[entry.line] as string).slice(entry.valueEnd - lineStart);\n if (!/^[ \\t]+#/.test(rest)) return edited;\n return edited.map((line, i) => (i === entry.line ? `${line}${rest}` : line));\n}\n\n/** Front matter an edit can be written into: YAML, and closed. */\nexport function editable(frontMatter: FrontMatter): boolean {\n return frontMatter.kind === 'yaml' && frontMatter.close !== -1;\n}\n"]}
|
package/dist/fs.js
CHANGED
|
@@ -52,8 +52,7 @@ export class NodeFileSystem {
|
|
|
52
52
|
this.root = resolve(root);
|
|
53
53
|
}
|
|
54
54
|
host(path) {
|
|
55
|
-
|
|
56
|
-
return normal === '' ? this.root : join(this.root, ...normal.split('/'));
|
|
55
|
+
return join(this.root, ...repoPath(path).split('/'));
|
|
57
56
|
}
|
|
58
57
|
async read(path) {
|
|
59
58
|
try {
|
|
@@ -70,7 +69,9 @@ export class NodeFileSystem {
|
|
|
70
69
|
await mkdir(dirname(target), { recursive: true });
|
|
71
70
|
// Written beside the target and renamed over it, so a reader never sees half a file.
|
|
72
71
|
const temporary = join(dirname(target), `.${randomBytes(6).toString('hex')}.spec-brief.tmp`);
|
|
73
|
-
|
|
72
|
+
// "wx" refuses a name that is taken rather than write over it. Six random
|
|
73
|
+
// bytes are not drawn twice in a test, so none shows the flag at work.
|
|
74
|
+
await writeFile(temporary, content, { flag: 'wx' });
|
|
74
75
|
try {
|
|
75
76
|
for (let attempt = 1;; attempt += 1) {
|
|
76
77
|
try {
|
|
@@ -78,14 +79,17 @@ export class NodeFileSystem {
|
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
catch (error) {
|
|
81
|
-
|
|
82
|
+
// An error with no code is not in the set either.
|
|
83
|
+
const code = error.code;
|
|
82
84
|
if (!TRANSIENT.has(code) || attempt >= RENAME_ATTEMPTS)
|
|
83
85
|
throw error;
|
|
86
|
+
// How long it waits changes when the answer comes, not what it is.
|
|
84
87
|
await pause(15 * attempt);
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
catch (error) {
|
|
92
|
+
// Forced, so that a temporary file something else removed does not hide the rename's error.
|
|
89
93
|
await rm(temporary, { force: true });
|
|
90
94
|
throw error;
|
|
91
95
|
}
|
|
@@ -102,7 +106,7 @@ export class NodeFileSystem {
|
|
|
102
106
|
const code = error.code;
|
|
103
107
|
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
104
108
|
return null;
|
|
105
|
-
|
|
109
|
+
// A directory that may not be read must not read as "no briefs".
|
|
106
110
|
throw error;
|
|
107
111
|
}
|
|
108
112
|
}
|
package/dist/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The filesystem, behind an interface.\n *\n * Every path crossing this boundary is repository-relative and POSIX; the\n * Node implementation is the only place a host path exists. The in-memory\n * implementation is not a test double bolted on afterwards: it is how a\n * harness asks \"what would archiving this do\" against files that were never\n * written, and how the transaction's rollback is exercised with a failure on\n * exactly the operation that matters.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\n\nimport { normalisePath as repoPath } from './links.js';\n\nexport interface FileSystem {\n /** Contents, or `null` when there is no such file. */\n read(path: string): Promise<string | null>;\n /** Writes a file, creating its directory. Replaces an existing file atomically where the platform allows. */\n write(path: string, content: string): Promise<void>;\n /** Removes a file. Removing a file that is absent is an error. */\n remove(path: string): Promise<void>;\n /** Names of the files directly inside a directory, or `null` when it does not exist. */\n list(directory: string): Promise<string[] | null>;\n /** Every file beneath the root, as sorted repository-relative paths, skipping {@link IGNORED_DIRECTORIES}. */\n walk(): Promise<string[]>;\n exists(path: string): Promise<boolean>;\n}\n\n/**\n * The path as the filesystem names it: links and junctions followed, and on\n * Windows short names such as `RUNNER~1` expanded. Git reports the top of a\n * work tree this way, so a root compared with it has to be named the same.\n * A path that does not exist is returned as given, for the caller to report.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/** Directories a walk never enters: build output and other tools' state. */\nexport const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'target',\n '.venv',\n '__pycache__',\n '.stryker-tmp',\n]);\n\n/** Errors a rename on Windows raises while another process briefly holds the file. */\nconst TRANSIENT = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_ATTEMPTS = 6;\n\nasync function pause(ms: number): Promise<void> {\n await new Promise((done) => setTimeout(done, ms));\n}\n\nexport class NodeFileSystem implements FileSystem {\n readonly root: string;\n\n constructor(root: string) {\n this.root = resolve(root);\n }\n\n private host(path: string): string {\n const normal = repoPath(path);\n return normal === '' ? this.root : join(this.root, ...normal.split('/'));\n }\n\n async read(path: string): Promise<string | null> {\n try {\n return await readFile(this.host(path), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n }\n\n async write(path: string, content: string): Promise<void> {\n const target = this.host(path);\n await mkdir(dirname(target), { recursive: true });\n // Written beside the target and renamed over it, so a reader never sees half a file.\n const temporary = join(dirname(target), `.${randomBytes(6).toString('hex')}.spec-brief.tmp`);\n await writeFile(temporary, content, { encoding: 'utf8', flag: 'wx' });\n try {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(temporary, target);\n return;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code ?? '';\n if (!TRANSIENT.has(code) || attempt >= RENAME_ATTEMPTS) throw error;\n await pause(15 * attempt);\n }\n }\n } catch (error) {\n await rm(temporary, { force: true });\n throw error;\n }\n }\n\n async remove(path: string): Promise<void> {\n await rm(this.host(path));\n }\n\n async list(directory: string): Promise<string[] | null> {\n try {\n const entries = await readdir(this.host(directory), { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') return null;\n /* v8 ignore next -- a permission error, which a portable test cannot provoke; it must not read as \"no briefs\". */\n throw error;\n }\n }\n\n async walk(): Promise<string[]> {\n const found: string[] = [];\n const visit = async (directory: string): Promise<void> => {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(full);\n } else if (entry.isFile()) {\n found.push(relative(this.root, full).split(sep).join('/'));\n }\n }\n };\n await visit(this.root);\n return found.sort();\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await stat(this.host(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n\nexport type FileOperation = 'write' | 'remove';\n\n/**\n * Files in a `Map`. `fail` lets a test make one chosen operation throw, which\n * is the only honest way to prove a rollback restores what it claims to.\n */\nexport class MemoryFileSystem implements FileSystem {\n readonly files: Map<string, string>;\n fail: ((operation: FileOperation, path: string) => boolean) | undefined;\n\n constructor(files: Readonly<Record<string, string>> = {}) {\n this.files = new Map(Object.entries(files).map(([path, content]) => [repoPath(path), content]));\n this.fail = undefined;\n }\n\n read(path: string): Promise<string | null> {\n return Promise.resolve(this.files.get(repoPath(path)) ?? null);\n }\n\n write(path: string, content: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('write', normal) === true) return Promise.reject(new Error(`injected failure writing ${normal}`));\n this.files.set(normal, content);\n return Promise.resolve();\n }\n\n remove(path: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('remove', normal) === true) return Promise.reject(new Error(`injected failure removing ${normal}`));\n if (!this.files.delete(normal)) return Promise.reject(new Error(`${normal} does not exist`));\n return Promise.resolve();\n }\n\n list(directory: string): Promise<string[] | null> {\n const prefix = repoPath(directory);\n const names: string[] = [];\n let isDirectory = prefix === '';\n for (const path of this.files.keys()) {\n if (prefix !== '' && !path.startsWith(`${prefix}/`)) continue;\n isDirectory = true;\n const rest = prefix === '' ? path : path.slice(prefix.length + 1);\n if (!rest.includes('/')) names.push(rest);\n }\n return Promise.resolve(isDirectory ? names.sort() : null);\n }\n\n walk(): Promise<string[]> {\n const paths = [...this.files.keys()].filter((path) => !path.split('/').some((part) => IGNORED_DIRECTORIES.has(part)));\n return Promise.resolve(paths.sort());\n }\n\n exists(path: string): Promise<boolean> {\n const normal = repoPath(path);\n return Promise.resolve(\n this.files.has(normal) || [...this.files.keys()].some((p) => normal === '' || p.startsWith(`${normal}/`)),\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The filesystem, behind an interface.\n *\n * Every path crossing this boundary is repository-relative and POSIX; the\n * Node implementation is the only place a host path exists. The in-memory\n * implementation is not a test double bolted on afterwards: it is how a\n * harness asks \"what would archiving this do\" against files that were never\n * written, and how the transaction's rollback is exercised with a failure on\n * exactly the operation that matters.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\n\nimport { normalisePath as repoPath } from './links.js';\n\nexport interface FileSystem {\n /** Contents, or `null` when there is no such file. */\n read(path: string): Promise<string | null>;\n /** Writes a file, creating its directory. Replaces an existing file atomically where the platform allows. */\n write(path: string, content: string): Promise<void>;\n /** Removes a file. Removing a file that is absent is an error. */\n remove(path: string): Promise<void>;\n /** Names of the files directly inside a directory, or `null` when it does not exist. */\n list(directory: string): Promise<string[] | null>;\n /** Every file beneath the root, as sorted repository-relative paths, skipping {@link IGNORED_DIRECTORIES}. */\n walk(): Promise<string[]>;\n exists(path: string): Promise<boolean>;\n}\n\n/**\n * The path as the filesystem names it: links and junctions followed, and on\n * Windows short names such as `RUNNER~1` expanded. Git reports the top of a\n * work tree this way, so a root compared with it has to be named the same.\n * A path that does not exist is returned as given, for the caller to report.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/** Directories a walk never enters: build output and other tools' state. */\nexport const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'target',\n '.venv',\n '__pycache__',\n '.stryker-tmp',\n]);\n\n/** Errors a rename on Windows raises while another process briefly holds the file. */\nconst TRANSIENT = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_ATTEMPTS = 6;\n\nasync function pause(ms: number): Promise<void> {\n await new Promise((done) => setTimeout(done, ms));\n}\n\nexport class NodeFileSystem implements FileSystem {\n readonly root: string;\n\n constructor(root: string) {\n this.root = resolve(root);\n }\n\n private host(path: string): string {\n return join(this.root, ...repoPath(path).split('/'));\n }\n\n async read(path: string): Promise<string | null> {\n try {\n return await readFile(this.host(path), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw error;\n }\n }\n\n async write(path: string, content: string): Promise<void> {\n const target = this.host(path);\n await mkdir(dirname(target), { recursive: true });\n // Written beside the target and renamed over it, so a reader never sees half a file.\n const temporary = join(dirname(target), `.${randomBytes(6).toString('hex')}.spec-brief.tmp`);\n // \"wx\" refuses a name that is taken rather than write over it. Six random\n // bytes are not drawn twice in a test, so none shows the flag at work.\n await writeFile(temporary, content, { flag: 'wx' });\n try {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(temporary, target);\n return;\n } catch (error) {\n // An error with no code is not in the set either.\n const code = (error as NodeJS.ErrnoException).code as string;\n if (!TRANSIENT.has(code) || attempt >= RENAME_ATTEMPTS) throw error;\n // How long it waits changes when the answer comes, not what it is.\n await pause(15 * attempt);\n }\n }\n } catch (error) {\n // Forced, so that a temporary file something else removed does not hide the rename's error.\n await rm(temporary, { force: true });\n throw error;\n }\n }\n\n async remove(path: string): Promise<void> {\n await rm(this.host(path));\n }\n\n async list(directory: string): Promise<string[] | null> {\n try {\n const entries = await readdir(this.host(directory), { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') return null;\n // A directory that may not be read must not read as \"no briefs\".\n throw error;\n }\n }\n\n async walk(): Promise<string[]> {\n const found: string[] = [];\n const visit = async (directory: string): Promise<void> => {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(full);\n } else if (entry.isFile()) {\n found.push(relative(this.root, full).split(sep).join('/'));\n }\n }\n };\n await visit(this.root);\n return found.sort();\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await stat(this.host(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n\nexport type FileOperation = 'write' | 'remove';\n\n/**\n * Files in a `Map`. `fail` lets a test make one chosen operation throw, which\n * is the only honest way to prove a rollback restores what it claims to.\n */\nexport class MemoryFileSystem implements FileSystem {\n readonly files: Map<string, string>;\n fail: ((operation: FileOperation, path: string) => boolean) | undefined;\n\n constructor(files: Readonly<Record<string, string>> = {}) {\n this.files = new Map(Object.entries(files).map(([path, content]) => [repoPath(path), content]));\n this.fail = undefined;\n }\n\n read(path: string): Promise<string | null> {\n return Promise.resolve(this.files.get(repoPath(path)) ?? null);\n }\n\n write(path: string, content: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('write', normal) === true) return Promise.reject(new Error(`injected failure writing ${normal}`));\n this.files.set(normal, content);\n return Promise.resolve();\n }\n\n remove(path: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('remove', normal) === true) return Promise.reject(new Error(`injected failure removing ${normal}`));\n if (!this.files.delete(normal)) return Promise.reject(new Error(`${normal} does not exist`));\n return Promise.resolve();\n }\n\n list(directory: string): Promise<string[] | null> {\n const prefix = repoPath(directory);\n const names: string[] = [];\n let isDirectory = prefix === '';\n for (const path of this.files.keys()) {\n if (prefix !== '' && !path.startsWith(`${prefix}/`)) continue;\n isDirectory = true;\n const rest = prefix === '' ? path : path.slice(prefix.length + 1);\n if (!rest.includes('/')) names.push(rest);\n }\n return Promise.resolve(isDirectory ? names.sort() : null);\n }\n\n walk(): Promise<string[]> {\n const paths = [...this.files.keys()].filter((path) => !path.split('/').some((part) => IGNORED_DIRECTORIES.has(part)));\n return Promise.resolve(paths.sort());\n }\n\n exists(path: string): Promise<boolean> {\n const normal = repoPath(path);\n return Promise.resolve(\n this.files.has(normal) || [...this.files.keys()].some((p) => normal === '' || p.startsWith(`${normal}/`)),\n );\n }\n}\n"]}
|
package/dist/git.js
CHANGED
|
@@ -13,7 +13,9 @@ import { canonicalPath } from './fs.js';
|
|
|
13
13
|
import { relativePath } from './links.js';
|
|
14
14
|
function run(cwd, args) {
|
|
15
15
|
return new Promise((done) => {
|
|
16
|
-
execFile('git', ['-c', 'core.quotepath=off', ...args],
|
|
16
|
+
execFile('git', ['-c', 'core.quotepath=off', ...args],
|
|
17
|
+
// windowsHide keeps a console window from flashing up on Windows; no other host has one to hide.
|
|
18
|
+
{ cwd, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => done({ ok: error === null, stdout, stderr }));
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
/** A revision that starts with "-" would be read as an option. */
|
|
@@ -29,7 +31,7 @@ function records(output) {
|
|
|
29
31
|
export function parseNumstat(output) {
|
|
30
32
|
// `show --format=` leaves the empty header's newline in front of the first record.
|
|
31
33
|
return records(output.replace(/^\n+/, '')).map((record) => {
|
|
32
|
-
const [added
|
|
34
|
+
const [added, deleted, ...path] = record.split('\t');
|
|
33
35
|
return {
|
|
34
36
|
path: path.join('\t'),
|
|
35
37
|
insertions: added === '-' ? null : Number(added),
|
|
@@ -78,7 +80,7 @@ export class NodeGit {
|
|
|
78
80
|
return null;
|
|
79
81
|
const hash = sha.stdout.trim();
|
|
80
82
|
const show = await run(this.cwd, ['show', '-s', '--format=%an%x00%aI', hash]);
|
|
81
|
-
const [author
|
|
83
|
+
const [author, date] = show.stdout.trim().split('\0');
|
|
82
84
|
return { sha: hash, author, date };
|
|
83
85
|
}
|
|
84
86
|
async mergeBase(a, b) {
|
|
@@ -106,7 +108,7 @@ export class NodeGit {
|
|
|
106
108
|
if (!result.ok)
|
|
107
109
|
throw new Error(`git status failed: ${result.stderr.trim()}`);
|
|
108
110
|
const prefix = await this.rootPrefix();
|
|
109
|
-
return parsePorcelain(result.stdout).map((path) =>
|
|
111
|
+
return parsePorcelain(result.stdout).map((path) => relativePath(prefix, path));
|
|
110
112
|
}
|
|
111
113
|
async files() {
|
|
112
114
|
const result = await run(this.cwd, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']);
|
|
@@ -121,6 +123,7 @@ export class NodeGit {
|
|
|
121
123
|
}
|
|
122
124
|
/** The web address of a pull request, for remotes on github.com. */
|
|
123
125
|
export function pullRequestUrl(remote, number) {
|
|
126
|
+
// Said for the reader and the compiler: no remote reads as the text "null", which is no address either.
|
|
124
127
|
if (remote === null)
|
|
125
128
|
return null;
|
|
126
129
|
const match = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(remote);
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git, read-only.\n *\n * spec-brief never stages, commits or rewrites history. It reads the commit a\n * round landed as, the files that commit touched, and whether the working tree\n * holds work the commit does not. What goes into a commit stays the decision\n * of whoever makes it, and archival is atomic over the files it writes rather\n * than over a repository state it would have to reverse.\n */\n\nimport { execFile } from 'node:child_process';\nimport { resolve } from 'node:path';\n\nimport { canonicalPath } from './fs.js';\nimport { relativePath } from './links.js';\n\nexport interface CommitInfo {\n readonly sha: string;\n readonly author: string;\n /** ISO 8601 author date. */\n readonly date: string;\n}\n\nexport interface FileChange {\n readonly path: string;\n /** `null` for a binary file, which has no line counts. */\n readonly insertions: number | null;\n readonly deletions: number | null;\n}\n\nexport interface Git {\n /** The commit a revision names, or `null` when it names none. */\n commit(revision: string): Promise<CommitInfo | null>;\n mergeBase(a: string, b: string): Promise<string | null>;\n /** Files changed from `from` to `to`; with no `from`, the files `to` changed against its first parent. */\n changes(from: string | null, to: string): Promise<FileChange[]>;\n /** Paths with uncommitted changes, untracked files included. */\n dirty(): Promise<string[]>;\n /** Every path git sees: tracked, or untracked and not ignored. A file a round just created counts. */\n files(): Promise<string[]>;\n remoteUrl(name: string): Promise<string | null>;\n}\n\ninterface Run {\n readonly ok: boolean;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nfunction run(cwd: string, args: readonly string[]): Promise<Run> {\n return new Promise((done) => {\n execFile(\n 'git',\n ['-c', 'core.quotepath=off', ...args],\n { cwd, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, windowsHide: true },\n (error, stdout, stderr) => done({ ok: error === null, stdout, stderr }),\n );\n });\n}\n\n/** A revision that starts with \"-\" would be read as an option. */\nfunction safeRevision(revision: string): string {\n if (revision.startsWith('-') || revision.trim() === '') throw new Error(`\"${revision}\" is not a revision`);\n return revision;\n}\n\nfunction records(output: string): string[] {\n return output.split('\\0').filter((r) => r !== '');\n}\n\n/** Parses `--numstat -z` output with renames off: `added<TAB>deleted<TAB>path<NUL>`. */\nexport function parseNumstat(output: string): FileChange[] {\n // `show --format=` leaves the empty header's newline in front of the first record.\n return records(output.replace(/^\\n+/, '')).map((record) => {\n const [added = '-', deleted = '-', ...path] = record.split('\\t');\n return {\n path: path.join('\\t'),\n insertions: added === '-' ? null : Number(added),\n deletions: deleted === '-' ? null : Number(deleted),\n };\n });\n}\n\n/** Parses `status --porcelain=v1 -z`, where a rename's original path is a record of its own. */\nexport function parsePorcelain(output: string): string[] {\n const fields = records(output);\n const paths: string[] = [];\n for (let i = 0; i < fields.length; i += 1) {\n const field = fields[i] as string;\n paths.push(field.slice(3));\n const x = field.charAt(0);\n if (x === 'R' || x === 'C') i += 1;\n }\n return paths;\n}\n\n/**\n * Git reports paths from the top of the work tree; spec-brief's paths start at\n * the root, which may be a directory below it. Every path that crosses this\n * boundary is made relative to the root.\n */\nexport class NodeGit implements Git {\n readonly cwd: string;\n private prefix: Promise<string> | undefined;\n\n constructor(cwd: string) {\n this.cwd = cwd;\n }\n\n /** The root's path from the top of the work tree, `sub/dir/` or empty. */\n private async rootPrefix(): Promise<string> {\n // Asked only after a status that succeeded, so the repository exists.\n this.prefix ??= run(this.cwd, ['rev-parse', '--show-prefix']).then((r) => r.stdout.trim());\n return this.prefix;\n }\n\n /** The working tree's top directory, canonical, or `null` outside a repository. */\n static async toplevel(cwd: string): Promise<string | null> {\n const result = await run(cwd, ['rev-parse', '--show-toplevel']);\n return result.ok ? canonicalPath(resolve(result.stdout.trim())) : null;\n }\n\n async commit(revision: string): Promise<CommitInfo | null> {\n const sha = await run(this.cwd, ['rev-parse', '--verify', '--quiet', `${safeRevision(revision)}^{commit}`]);\n if (!sha.ok) return null;\n const hash = sha.stdout.trim();\n const show = await run(this.cwd, ['show', '-s', '--format=%an%x00%aI', hash]);\n const [author = '', date = ''] = show.stdout.trim().split('\\0');\n return { sha: hash, author, date };\n }\n\n async mergeBase(a: string, b: string): Promise<string | null> {\n const result = await run(this.cwd, ['merge-base', safeRevision(a), safeRevision(b)]);\n return result.ok ? result.stdout.trim() : null;\n }\n\n async changes(from: string | null, to: string): Promise<FileChange[]> {\n const target = safeRevision(to);\n let result: Run;\n if (from !== null) {\n result = await run(this.cwd, ['diff', '--numstat', '-z', '--no-renames', '--relative', safeRevision(from), target]);\n } else {\n const parent = await run(this.cwd, ['rev-parse', '--verify', '--quiet', `${target}^`]);\n result = parent.ok\n ? await run(this.cwd, ['diff', '--numstat', '-z', '--no-renames', '--relative', `${target}^`, target])\n : await run(this.cwd, ['show', '--numstat', '-z', '--no-renames', '--relative', '--format=', target]);\n }\n if (!result.ok) throw new Error(`git could not diff ${from ?? `${to}^`}..${to}: ${result.stderr.trim()}`);\n return parseNumstat(result.stdout);\n }\n\n async dirty(): Promise<string[]> {\n const result = await run(this.cwd, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);\n if (!result.ok) throw new Error(`git status failed: ${result.stderr.trim()}`);\n const prefix = await this.rootPrefix();\n return parsePorcelain(result.stdout).map((path) => (prefix === '' ? path : relativePath(prefix, path)));\n }\n\n async files(): Promise<string[]> {\n const result = await run(this.cwd, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']);\n if (!result.ok) throw new Error(`git ls-files failed: ${result.stderr.trim()}`);\n return records(result.stdout).sort();\n }\n\n async remoteUrl(name: string): Promise<string | null> {\n const result = await run(this.cwd, ['remote', 'get-url', safeRevision(name)]);\n return result.ok ? result.stdout.trim() : null;\n }\n}\n\n/** The web address of a pull request, for remotes on github.com. */\nexport function pullRequestUrl(remote: string | null, number: number): string | null {\n if (remote === null) return null;\n const match = /github\\.com[:/]([^/\\s]+)\\/([^/\\s]+?)(?:\\.git)?\\/?$/.exec(remote);\n if (!match) return null;\n return `https://github.com/${match[1] as string}/${match[2] as string}/pull/${number}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git, read-only.\n *\n * spec-brief never stages, commits or rewrites history. It reads the commit a\n * round landed as, the files that commit touched, and whether the working tree\n * holds work the commit does not. What goes into a commit stays the decision\n * of whoever makes it, and archival is atomic over the files it writes rather\n * than over a repository state it would have to reverse.\n */\n\nimport { execFile } from 'node:child_process';\nimport { resolve } from 'node:path';\n\nimport { canonicalPath } from './fs.js';\nimport { relativePath } from './links.js';\n\nexport interface CommitInfo {\n readonly sha: string;\n readonly author: string;\n /** ISO 8601 author date. */\n readonly date: string;\n}\n\nexport interface FileChange {\n readonly path: string;\n /** `null` for a binary file, which has no line counts. */\n readonly insertions: number | null;\n readonly deletions: number | null;\n}\n\nexport interface Git {\n /** The commit a revision names, or `null` when it names none. */\n commit(revision: string): Promise<CommitInfo | null>;\n mergeBase(a: string, b: string): Promise<string | null>;\n /** Files changed from `from` to `to`; with no `from`, the files `to` changed against its first parent. */\n changes(from: string | null, to: string): Promise<FileChange[]>;\n /** Paths with uncommitted changes, untracked files included. */\n dirty(): Promise<string[]>;\n /** Every path git sees: tracked, or untracked and not ignored. A file a round just created counts. */\n files(): Promise<string[]>;\n remoteUrl(name: string): Promise<string | null>;\n}\n\ninterface Run {\n readonly ok: boolean;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nfunction run(cwd: string, args: readonly string[]): Promise<Run> {\n return new Promise((done) => {\n execFile(\n 'git',\n ['-c', 'core.quotepath=off', ...args],\n // windowsHide keeps a console window from flashing up on Windows; no other host has one to hide.\n { cwd, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, windowsHide: true },\n (error, stdout, stderr) => done({ ok: error === null, stdout, stderr }),\n );\n });\n}\n\n/** A revision that starts with \"-\" would be read as an option. */\nfunction safeRevision(revision: string): string {\n if (revision.startsWith('-') || revision.trim() === '') throw new Error(`\"${revision}\" is not a revision`);\n return revision;\n}\n\nfunction records(output: string): string[] {\n return output.split('\\0').filter((r) => r !== '');\n}\n\n/** Parses `--numstat -z` output with renames off: `added<TAB>deleted<TAB>path<NUL>`. */\nexport function parseNumstat(output: string): FileChange[] {\n // `show --format=` leaves the empty header's newline in front of the first record.\n return records(output.replace(/^\\n+/, '')).map((record) => {\n const [added, deleted, ...path] = record.split('\\t') as [string, string, ...string[]];\n return {\n path: path.join('\\t'),\n insertions: added === '-' ? null : Number(added),\n deletions: deleted === '-' ? null : Number(deleted),\n };\n });\n}\n\n/** Parses `status --porcelain=v1 -z`, where a rename's original path is a record of its own. */\nexport function parsePorcelain(output: string): string[] {\n const fields = records(output);\n const paths: string[] = [];\n for (let i = 0; i < fields.length; i += 1) {\n const field = fields[i] as string;\n paths.push(field.slice(3));\n const x = field.charAt(0);\n if (x === 'R' || x === 'C') i += 1;\n }\n return paths;\n}\n\n/**\n * Git reports paths from the top of the work tree; spec-brief's paths start at\n * the root, which may be a directory below it. Every path that crosses this\n * boundary is made relative to the root.\n */\nexport class NodeGit implements Git {\n readonly cwd: string;\n private prefix: Promise<string> | undefined;\n\n constructor(cwd: string) {\n this.cwd = cwd;\n }\n\n /** The root's path from the top of the work tree, `sub/dir/` or empty. */\n private async rootPrefix(): Promise<string> {\n // Asked only after a status that succeeded, so the repository exists.\n this.prefix ??= run(this.cwd, ['rev-parse', '--show-prefix']).then((r) => r.stdout.trim());\n return this.prefix;\n }\n\n /** The working tree's top directory, canonical, or `null` outside a repository. */\n static async toplevel(cwd: string): Promise<string | null> {\n const result = await run(cwd, ['rev-parse', '--show-toplevel']);\n return result.ok ? canonicalPath(resolve(result.stdout.trim())) : null;\n }\n\n async commit(revision: string): Promise<CommitInfo | null> {\n const sha = await run(this.cwd, ['rev-parse', '--verify', '--quiet', `${safeRevision(revision)}^{commit}`]);\n if (!sha.ok) return null;\n const hash = sha.stdout.trim();\n const show = await run(this.cwd, ['show', '-s', '--format=%an%x00%aI', hash]);\n const [author, date] = show.stdout.trim().split('\\0') as [string, string];\n return { sha: hash, author, date };\n }\n\n async mergeBase(a: string, b: string): Promise<string | null> {\n const result = await run(this.cwd, ['merge-base', safeRevision(a), safeRevision(b)]);\n return result.ok ? result.stdout.trim() : null;\n }\n\n async changes(from: string | null, to: string): Promise<FileChange[]> {\n const target = safeRevision(to);\n let result: Run;\n if (from !== null) {\n result = await run(this.cwd, ['diff', '--numstat', '-z', '--no-renames', '--relative', safeRevision(from), target]);\n } else {\n const parent = await run(this.cwd, ['rev-parse', '--verify', '--quiet', `${target}^`]);\n result = parent.ok\n ? await run(this.cwd, ['diff', '--numstat', '-z', '--no-renames', '--relative', `${target}^`, target])\n : await run(this.cwd, ['show', '--numstat', '-z', '--no-renames', '--relative', '--format=', target]);\n }\n if (!result.ok) throw new Error(`git could not diff ${from ?? `${to}^`}..${to}: ${result.stderr.trim()}`);\n return parseNumstat(result.stdout);\n }\n\n async dirty(): Promise<string[]> {\n const result = await run(this.cwd, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);\n if (!result.ok) throw new Error(`git status failed: ${result.stderr.trim()}`);\n const prefix = await this.rootPrefix();\n return parsePorcelain(result.stdout).map((path) => relativePath(prefix, path));\n }\n\n async files(): Promise<string[]> {\n const result = await run(this.cwd, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']);\n if (!result.ok) throw new Error(`git ls-files failed: ${result.stderr.trim()}`);\n return records(result.stdout).sort();\n }\n\n async remoteUrl(name: string): Promise<string | null> {\n const result = await run(this.cwd, ['remote', 'get-url', safeRevision(name)]);\n return result.ok ? result.stdout.trim() : null;\n }\n}\n\n/** The web address of a pull request, for remotes on github.com. */\nexport function pullRequestUrl(remote: string | null, number: number): string | null {\n // Said for the reader and the compiler: no remote reads as the text \"null\", which is no address either.\n if (remote === null) return null;\n const match = /github\\.com[:/]([^/\\s]+)\\/([^/\\s]+?)(?:\\.git)?\\/?$/.exec(remote);\n if (!match) return null;\n return `https://github.com/${match[1] as string}/${match[2] as string}/pull/${number}`;\n}\n"]}
|
package/dist/glob.js
CHANGED
|
@@ -81,6 +81,7 @@ const indexes = new WeakMap();
|
|
|
81
81
|
/** The index of a list. Built once per list, which every pattern of a run shares, as {@link treeOf} is. */
|
|
82
82
|
function indexOf(files) {
|
|
83
83
|
const known = indexes.get(files);
|
|
84
|
+
// Only time is kept here: an index built again is the same index.
|
|
84
85
|
if (known !== undefined)
|
|
85
86
|
return known;
|
|
86
87
|
const positions = new Map();
|
|
@@ -206,6 +207,7 @@ export function writtenAlternatives(pattern) {
|
|
|
206
207
|
let depth = 0;
|
|
207
208
|
let open = 0;
|
|
208
209
|
const stops = [];
|
|
210
|
+
// A step past the end reads `''`, which no branch acts on, so the bound could be one further.
|
|
209
211
|
for (let i = 0; i < pattern.length; i += 1) {
|
|
210
212
|
const ch = pattern.charAt(i);
|
|
211
213
|
if (ch === '\\') {
|
package/dist/glob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scopes as globs: spec-core's `path` dialect, read the way spec-brief reads a\n * brief.\n *\n * The syntax, the automaton and the witness search are spec-core's, copied\n * into `src/vendor/` and verified by hash, so the family's tools read one\n * pattern the same way (spec-core ADR-0003). What is decided here is what\n * spec-brief adds around it:\n *\n * - A pattern is relative to the repository root, so a leading `/` is refused\n * rather than rooted at the filesystem: on the pattern, and on a text its\n * braces give, since spec-core reads `{/docs,x}` as `/docs` or `x`, and\n * `docs` would fall out of the scope unsaid. A leading `./` goes with the\n * slashes after it, as POSIX reads them: `.//docs` is `docs`, and\n * `./{/docs,x}` is `docs` or `x`. A list entry's `!` has no list to belong\n * to in a scope, so it is refused rather than read.\n * - Paths compare case-sensitively on every host, as git's do.\n * - A path with no glob syntax is read as the tree reads it: a file the tree\n * holds is that file, a directory it holds is everything beneath it, and a\n * path it does not hold is a file, unless it is written with a trailing `/`.\n * The tree, not the spelling, knows that `Dockerfile` is a file and\n * `docs/v1.2` a directory (ADR-0005, amended 2026-09-26).\n * - A trailing `/**` is at least one segment, so `docs/**` is the directory's\n * contents and a collision witness is always a file. A trailing `/` means\n * the same on a brace alternative as on the whole pattern: `{src/,lib}` is\n * `src/` or `lib`, and the tree is asked about `lib` alone.\n *\n * Nothing here compiles to a `RegExp`. Matching costs at most the pattern's\n * size times the path's length, and a question about two scopes that cannot be\n * answered within the search's budget answers `undecided`, never a guess.\n */\n\nimport {\n globAlternatives,\n globCovers as coreCovers,\n globWitness as coreWitness,\n isGlobSyntax,\n parseGlob as coreParse,\n type Glob as CoreGlob,\n type LiteralReading,\n WITNESS_BUDGET,\n type Witness,\n} from './vendor/spec-core/pattern/index.js';\nimport { segments } from './vendor/spec-core/path/index.js';\n\nexport { isGlobSyntax, type LiteralReading, WITNESS_BUDGET, type Witness };\n\n/** A literal path a pattern names, and how it was read. */\nexport interface Literal {\n readonly path: string;\n readonly reading: LiteralReading;\n}\n\nexport interface Glob {\n readonly source: string;\n /** The compiled pattern: spec-core's `path` dialect, case-sensitive. */\n readonly compiled: CoreGlob;\n /**\n * Each path the pattern names with no glob syntax and no trailing `/`, one\n * per brace alternative; empty when it has none.\n */\n readonly literals: readonly Literal[];\n}\n\nexport type GlobParse = { readonly ok: true; readonly glob: Glob } | { readonly ok: false; readonly error: string };\n\nexport interface ParseOptions {\n /**\n * How a path with no glob syntax is read: `file`, that path alone;\n * `directory`, everything beneath it; `either`, both. A function is asked\n * per literal path, as {@link readingIn} answers for a tree. `file` when\n * unset: a path nothing says is a directory is not read as one.\n */\n readonly literal?: LiteralReading | ((path: string) => LiteralReading) | undefined;\n}\n\n/** Parses a scope pattern, or says why it cannot. */\nexport function parseGlob(source: string, options: ParseOptions = {}): GlobParse {\n const pattern = source.trim();\n if (pattern.startsWith('!')) return { ok: false, error: 'negated patterns are not supported; narrow the positive pattern' };\n // spec-core's own reading of where each alternative starts, a leading `./`\n // and `/` included (spec-core ADR-0003). A pattern it refuses, the core's\n // parse refuses in the same words, so either may say so. It comes first so\n // that a pattern led by `/`, which the core roots at the filesystem's root,\n // is refused in spec-brief's words.\n const read = globAlternatives(pattern);\n if (!read.ok) return { ok: false, error: read.error };\n if (read.rooted) return { ok: false, error: 'a pattern is relative to the repository root and cannot start with \"/\"' };\n const given = options.literal ?? 'file';\n const literals: Literal[] = [];\n const literal = (path: string): LiteralReading => {\n const reading = typeof given === 'function' ? given(path) : given;\n literals.push({ path, reading });\n return reading;\n };\n // A pattern too large to compile is refused by the core, with its reason,\n // as a malformed one is; what the reading throws is a defect, and passes on.\n const parsed = coreParse(pattern, { dialect: 'path', caseSensitive: true, literal });\n if (!parsed.ok) return parsed;\n // Once the core has parsed the pattern, so that an alternative naming no\n // path, `{/,x}`, is refused in the core's words. The text is named behind\n // the `/` that roots it, as spec-core gives it: it reads the same written\n // alone, so a `}` or `,` no group took is written as a class, `[}]`.\n const rooted = read.alternatives.find((alternative) => alternative.rooted);\n if (rooted !== undefined) {\n return { ok: false, error: `a pattern is relative to the repository root, and the braces expand to \"/${rooted.text}\", which starts with \"/\"` };\n }\n return { ok: true, glob: { source, compiled: parsed.glob, literals } };\n}\n\n/**\n * Whether a path matches. The path is read as a repository path: empty and\n * `.` segments are dropped, so `./src//a.ts` is `src/a.ts`.\n */\nexport function matchGlob(glob: Glob, path: string): boolean {\n const canonical = segments(path).join('/');\n return canonical !== '' && glob.compiled.match(canonical);\n}\n\n/** A list of files by path, so the files below a directory are one run of it. */\ninterface FileIndex {\n /** Each path the list holds, as {@link matchGlob} reads it, once, in code-unit order. */\n readonly paths: readonly string[];\n /** The positions in the list of each path's files. */\n readonly positions: ReadonlyMap<string, readonly number[]>;\n}\n\nconst indexes = new WeakMap<readonly string[], FileIndex>();\n\n/** The index of a list. Built once per list, which every pattern of a run shares, as {@link treeOf} is. */\nfunction indexOf(files: readonly string[]): FileIndex {\n const known = indexes.get(files);\n if (known !== undefined) return known;\n const positions = new Map<string, number[]>();\n files.forEach((file, position) => {\n const path = segments(file).join('/');\n const others = positions.get(path);\n if (others === undefined) positions.set(path, [position]);\n else others.push(position);\n });\n const index = { paths: [...positions.keys()].sort(), positions };\n indexes.set(files, index);\n return index;\n}\n\n/**\n * The positions of the files a glob can match: the run of the index below\n * each of its bases. spec-core names, for each brace alternative, the\n * directory every match of it lies below, or `''` where a match can lie\n * anywhere, so a file below none of them cannot match. Asking every pattern\n * about every file made `lint`, `matrix` and `schedule` grow with patterns\n * times files: over 240 briefs and 93,000 files a lint took 35 s, nine tenths\n * of it matching.\n */\nfunction* candidates(index: FileIndex, glob: Glob): Generator<number> {\n const { paths, positions } = index;\n for (const base of glob.compiled.bases) {\n const prefix = base === '' ? '' : `${base}/`;\n // The first path that sorts at or after the prefix. `<=` finds the same\n // one, an equivalent mutant: no path read so ends with `/`, so none\n // equals a prefix but `''`, which no glob matches.\n let low = 0;\n let high = paths.length;\n while (low < high) {\n const middle = (low + high) >>> 1;\n if ((paths[middle] as string) < prefix) low = middle + 1;\n else high = middle;\n }\n for (let place = low; place < paths.length && (paths[place] as string).startsWith(prefix); place += 1) {\n yield* positions.get(paths[place] as string) as readonly number[];\n }\n }\n}\n\n/**\n * The files of a list a glob matches, in the list's order: those\n * {@link matchGlob} matches, asked only of the files below one of the glob's\n * bases.\n */\nexport function filesMatching(glob: Glob, files: readonly string[]): string[] {\n const index = indexOf(files);\n const matched = new Set<number>();\n for (const position of candidates(index, glob)) {\n if (matchGlob(glob, files[position] as string)) matched.add(position);\n }\n return [...matched].sort((a, b) => a - b).map((position) => files[position] as string);\n}\n\n/** Whether a glob matches any file of a list, asked only of the files below one of its bases. */\nexport function matchesAny(glob: Glob, files: readonly string[]): boolean {\n const index = indexOf(files);\n for (const position of candidates(index, glob)) {\n if (matchGlob(glob, files[position] as string)) return true;\n }\n return false;\n}\n\n/**\n * A shortest path every glob in `include` matches and none in `exclude` does,\n * `none` as a proof that there is no such path, or `undecided` when the search\n * met its budget first.\n */\nexport function globWitness(include: readonly Glob[], exclude: readonly Glob[] = [], budget: number = WITNESS_BUDGET): Witness {\n return coreWitness(\n include.map((g) => g.compiled),\n exclude.map((g) => g.compiled),\n budget,\n );\n}\n\n/** Whether every path `inner` matches is matched by one of `outer`, or `undecided`. */\nexport function globCovers(outer: readonly Glob[], inner: Glob, budget: number = WITNESS_BUDGET): boolean | 'undecided' {\n return coreCovers(\n outer.map((g) => g.compiled),\n inner.compiled,\n budget,\n );\n}\n\n/**\n * A path both globs match, or `null` when there is none: the 0.1 interface,\n * kept for the library's callers. It cannot say `undecided`, so a search that\n * meets its budget throws; {@link globWitness} answers in three ways.\n */\nexport function intersectGlobs(a: Glob, b: Glob, budget: number = WITNESS_BUDGET): string | null {\n const witness = globWitness([a, b], [], budget);\n if (witness.kind === 'undecided') {\n throw new Error(`whether \"${a.source}\" and \"${b.source}\" meet is undecided within the search's budget`);\n }\n return witness.kind === 'found' ? witness.path : null;\n}\n\n/**\n * The directories a walk must enter to find every match, one per brace\n * alternative: the leading literal segments, or a literal file's directory.\n * `''` for an alternative that can match at the root.\n */\nexport function globBases(glob: Glob): readonly string[] {\n return glob.compiled.bases;\n}\n\n/** The first of {@link globBases}: the 0.1 interface, kept for the library's callers. */\nexport function globBase(glob: Glob): string {\n // One base per alternative, and a pattern has at least one.\n return glob.compiled.bases[0] as string;\n}\n\n/** A brace alternative as its pattern writes it, and the index in the pattern where it ends. */\nexport interface WrittenAlternative {\n readonly written: string;\n readonly end: number;\n}\n\n/**\n * Each alternative of a pattern as written, in the order the core expands\n * them, and where each ends, when a `/` written there ends that alternative\n * and no other: the pattern itself when it has no braces, or the alternatives\n * of braces that end the pattern and whose own braces end theirs -\n * `lib/{util,new}`, `{a,b/{c,d}}`. `null` when more of the pattern follows\n * braces, `{a,b}/new`, since every alternative they give ends where the\n * pattern does; and for a pattern with a class, which may hold a brace or a\n * comma of its own and which this does not read.\n *\n * spec-core expands braces and keeps nothing of where an alternative was\n * written, and a note's advice is built from what was written, as the core\n * builds its own. So this reads braces as the expansion does, for a pattern\n * that parses: a `\\` escapes the next character, and a `}` with no `{` open\n * is a literal.\n */\nexport function writtenAlternatives(pattern: string): WrittenAlternative[] | null {\n let depth = 0;\n let open = 0;\n const stops: number[] = [];\n for (let i = 0; i < pattern.length; i += 1) {\n const ch = pattern.charAt(i);\n if (ch === '\\\\') {\n i += 1;\n } else if (ch === '[') {\n return null;\n } else if (ch === '{') {\n if (depth === 0) open = i;\n depth += 1;\n } else if (ch === ',' && depth === 1) {\n stops.push(i);\n } else if (ch === '}' && depth > 0) {\n depth -= 1;\n if (depth === 0) return i === pattern.length - 1 ? optionsOf(pattern, open, [...stops, i]) : null;\n }\n }\n return [{ written: pattern, end: pattern.length }];\n}\n\n/** The alternatives of the braces opened at `open`, each option ending at one of `stops`. */\nfunction optionsOf(pattern: string, open: number, stops: readonly number[]): WrittenAlternative[] | null {\n const prefix = pattern.slice(0, open);\n const alternatives: WrittenAlternative[] = [];\n let start = open + 1;\n for (const stop of stops) {\n const inner = writtenAlternatives(pattern.slice(start, stop));\n if (inner === null) return null;\n for (const { written, end } of inner) alternatives.push({ written: prefix + written, end: start + end });\n start = stop + 1;\n }\n return alternatives;\n}\n\n/** A name with an extension, `login.ts` or `.eslintrc.json`, and not a dot-name such as `.github`. */\nexport function hasExtension(name: string): boolean {\n return /.\\.[^.]+$/.test(name);\n}\n\n/** The files a tree holds, and every directory above one. */\nexport interface Tree {\n readonly files: ReadonlySet<string>;\n readonly directories: ReadonlySet<string>;\n}\n\nconst trees = new WeakMap<readonly string[], Tree>();\n\n/** The tree a list of files describes. Built once per list, which every brief of a run shares. */\nexport function treeOf(files: readonly string[]): Tree {\n const known = trees.get(files);\n if (known !== undefined) return known;\n const directories = new Set<string>();\n for (const file of files) {\n for (let slash = file.indexOf('/'); slash !== -1; slash = file.indexOf('/', slash + 1)) directories.add(file.slice(0, slash));\n }\n const tree = { files: new Set(files), directories };\n trees.set(files, tree);\n return tree;\n}\n\n/** What the tree holds at a path: a file, a directory, or nothing. */\nexport function held(tree: Tree, path: string): 'file' | 'directory' | null {\n if (tree.files.has(path)) return 'file';\n return tree.directories.has(path) ? 'directory' : null;\n}\n\n/**\n * How a tree reads a literal path: a directory it holds is a directory, and\n * anything else is a file. Without a tree nothing is known to be a directory,\n * so every literal is a file; a trailing `/` says otherwise, and never reaches\n * this question.\n */\nexport function readingIn(files: readonly string[] | null): (path: string) => LiteralReading {\n if (files === null) return () => 'file';\n const tree = treeOf(files);\n return (path) => (held(tree, path) === 'directory' ? 'directory' : 'file');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scopes as globs: spec-core's `path` dialect, read the way spec-brief reads a\n * brief.\n *\n * The syntax, the automaton and the witness search are spec-core's, copied\n * into `src/vendor/` and verified by hash, so the family's tools read one\n * pattern the same way (spec-core ADR-0003). What is decided here is what\n * spec-brief adds around it:\n *\n * - A pattern is relative to the repository root, so a leading `/` is refused\n * rather than rooted at the filesystem: on the pattern, and on a text its\n * braces give, since spec-core reads `{/docs,x}` as `/docs` or `x`, and\n * `docs` would fall out of the scope unsaid. A leading `./` goes with the\n * slashes after it, as POSIX reads them: `.//docs` is `docs`, and\n * `./{/docs,x}` is `docs` or `x`. A list entry's `!` has no list to belong\n * to in a scope, so it is refused rather than read.\n * - Paths compare case-sensitively on every host, as git's do.\n * - A path with no glob syntax is read as the tree reads it: a file the tree\n * holds is that file, a directory it holds is everything beneath it, and a\n * path it does not hold is a file, unless it is written with a trailing `/`.\n * The tree, not the spelling, knows that `Dockerfile` is a file and\n * `docs/v1.2` a directory (ADR-0005, amended 2026-09-26).\n * - A trailing `/**` is at least one segment, so `docs/**` is the directory's\n * contents and a collision witness is always a file. A trailing `/` means\n * the same on a brace alternative as on the whole pattern: `{src/,lib}` is\n * `src/` or `lib`, and the tree is asked about `lib` alone.\n *\n * Nothing here compiles to a `RegExp`. Matching costs at most the pattern's\n * size times the path's length, and a question about two scopes that cannot be\n * answered within the search's budget answers `undecided`, never a guess.\n */\n\nimport {\n globAlternatives,\n globCovers as coreCovers,\n globWitness as coreWitness,\n isGlobSyntax,\n parseGlob as coreParse,\n type Glob as CoreGlob,\n type LiteralReading,\n WITNESS_BUDGET,\n type Witness,\n} from './vendor/spec-core/pattern/index.js';\nimport { segments } from './vendor/spec-core/path/index.js';\n\nexport { isGlobSyntax, type LiteralReading, WITNESS_BUDGET, type Witness };\n\n/** A literal path a pattern names, and how it was read. */\nexport interface Literal {\n readonly path: string;\n readonly reading: LiteralReading;\n}\n\nexport interface Glob {\n readonly source: string;\n /** The compiled pattern: spec-core's `path` dialect, case-sensitive. */\n readonly compiled: CoreGlob;\n /**\n * Each path the pattern names with no glob syntax and no trailing `/`, one\n * per brace alternative; empty when it has none.\n */\n readonly literals: readonly Literal[];\n}\n\nexport type GlobParse = { readonly ok: true; readonly glob: Glob } | { readonly ok: false; readonly error: string };\n\nexport interface ParseOptions {\n /**\n * How a path with no glob syntax is read: `file`, that path alone;\n * `directory`, everything beneath it; `either`, both. A function is asked\n * per literal path, as {@link readingIn} answers for a tree. `file` when\n * unset: a path nothing says is a directory is not read as one.\n */\n readonly literal?: LiteralReading | ((path: string) => LiteralReading) | undefined;\n}\n\n/** Parses a scope pattern, or says why it cannot. */\nexport function parseGlob(source: string, options: ParseOptions = {}): GlobParse {\n const pattern = source.trim();\n if (pattern.startsWith('!')) return { ok: false, error: 'negated patterns are not supported; narrow the positive pattern' };\n // spec-core's own reading of where each alternative starts, a leading `./`\n // and `/` included (spec-core ADR-0003). A pattern it refuses, the core's\n // parse refuses in the same words, so either may say so. It comes first so\n // that a pattern led by `/`, which the core roots at the filesystem's root,\n // is refused in spec-brief's words.\n const read = globAlternatives(pattern);\n if (!read.ok) return { ok: false, error: read.error };\n if (read.rooted) return { ok: false, error: 'a pattern is relative to the repository root and cannot start with \"/\"' };\n const given = options.literal ?? 'file';\n const literals: Literal[] = [];\n const literal = (path: string): LiteralReading => {\n const reading = typeof given === 'function' ? given(path) : given;\n literals.push({ path, reading });\n return reading;\n };\n // A pattern too large to compile is refused by the core, with its reason,\n // as a malformed one is; what the reading throws is a defect, and passes on.\n const parsed = coreParse(pattern, { dialect: 'path', caseSensitive: true, literal });\n if (!parsed.ok) return parsed;\n // Once the core has parsed the pattern, so that an alternative naming no\n // path, `{/,x}`, is refused in the core's words. The text is named behind\n // the `/` that roots it, as spec-core gives it: it reads the same written\n // alone, so a `}` or `,` no group took is written as a class, `[}]`.\n const rooted = read.alternatives.find((alternative) => alternative.rooted);\n if (rooted !== undefined) {\n return { ok: false, error: `a pattern is relative to the repository root, and the braces expand to \"/${rooted.text}\", which starts with \"/\"` };\n }\n return { ok: true, glob: { source, compiled: parsed.glob, literals } };\n}\n\n/**\n * Whether a path matches. The path is read as a repository path: empty and\n * `.` segments are dropped, so `./src//a.ts` is `src/a.ts`.\n */\nexport function matchGlob(glob: Glob, path: string): boolean {\n const canonical = segments(path).join('/');\n return canonical !== '' && glob.compiled.match(canonical);\n}\n\n/** A list of files by path, so the files below a directory are one run of it. */\ninterface FileIndex {\n /** Each path the list holds, as {@link matchGlob} reads it, once, in code-unit order. */\n readonly paths: readonly string[];\n /** The positions in the list of each path's files. */\n readonly positions: ReadonlyMap<string, readonly number[]>;\n}\n\nconst indexes = new WeakMap<readonly string[], FileIndex>();\n\n/** The index of a list. Built once per list, which every pattern of a run shares, as {@link treeOf} is. */\nfunction indexOf(files: readonly string[]): FileIndex {\n const known = indexes.get(files);\n // Only time is kept here: an index built again is the same index.\n if (known !== undefined) return known;\n const positions = new Map<string, number[]>();\n files.forEach((file, position) => {\n const path = segments(file).join('/');\n const others = positions.get(path);\n if (others === undefined) positions.set(path, [position]);\n else others.push(position);\n });\n const index = { paths: [...positions.keys()].sort(), positions };\n indexes.set(files, index);\n return index;\n}\n\n/**\n * The positions of the files a glob can match: the run of the index below\n * each of its bases. spec-core names, for each brace alternative, the\n * directory every match of it lies below, or `''` where a match can lie\n * anywhere, so a file below none of them cannot match. Asking every pattern\n * about every file made `lint`, `matrix` and `schedule` grow with patterns\n * times files: over 240 briefs and 93,000 files a lint took 35 s, nine tenths\n * of it matching.\n */\nfunction* candidates(index: FileIndex, glob: Glob): Generator<number> {\n const { paths, positions } = index;\n for (const base of glob.compiled.bases) {\n const prefix = base === '' ? '' : `${base}/`;\n // The first path that sorts at or after the prefix. `<=` finds the same\n // one, an equivalent mutant: no path read so ends with `/`, so none\n // equals a prefix but `''`, which no glob matches.\n let low = 0;\n let high = paths.length;\n while (low < high) {\n const middle = (low + high) >>> 1;\n if ((paths[middle] as string) < prefix) low = middle + 1;\n else high = middle;\n }\n for (let place = low; place < paths.length && (paths[place] as string).startsWith(prefix); place += 1) {\n yield* positions.get(paths[place] as string) as readonly number[];\n }\n }\n}\n\n/**\n * The files of a list a glob matches, in the list's order: those\n * {@link matchGlob} matches, asked only of the files below one of the glob's\n * bases.\n */\nexport function filesMatching(glob: Glob, files: readonly string[]): string[] {\n const index = indexOf(files);\n const matched = new Set<number>();\n for (const position of candidates(index, glob)) {\n if (matchGlob(glob, files[position] as string)) matched.add(position);\n }\n return [...matched].sort((a, b) => a - b).map((position) => files[position] as string);\n}\n\n/** Whether a glob matches any file of a list, asked only of the files below one of its bases. */\nexport function matchesAny(glob: Glob, files: readonly string[]): boolean {\n const index = indexOf(files);\n for (const position of candidates(index, glob)) {\n if (matchGlob(glob, files[position] as string)) return true;\n }\n return false;\n}\n\n/**\n * A shortest path every glob in `include` matches and none in `exclude` does,\n * `none` as a proof that there is no such path, or `undecided` when the search\n * met its budget first.\n */\nexport function globWitness(include: readonly Glob[], exclude: readonly Glob[] = [], budget: number = WITNESS_BUDGET): Witness {\n return coreWitness(\n include.map((g) => g.compiled),\n exclude.map((g) => g.compiled),\n budget,\n );\n}\n\n/** Whether every path `inner` matches is matched by one of `outer`, or `undecided`. */\nexport function globCovers(outer: readonly Glob[], inner: Glob, budget: number = WITNESS_BUDGET): boolean | 'undecided' {\n return coreCovers(\n outer.map((g) => g.compiled),\n inner.compiled,\n budget,\n );\n}\n\n/**\n * A path both globs match, or `null` when there is none: the 0.1 interface,\n * kept for the library's callers. It cannot say `undecided`, so a search that\n * meets its budget throws; {@link globWitness} answers in three ways.\n */\nexport function intersectGlobs(a: Glob, b: Glob, budget: number = WITNESS_BUDGET): string | null {\n const witness = globWitness([a, b], [], budget);\n if (witness.kind === 'undecided') {\n throw new Error(`whether \"${a.source}\" and \"${b.source}\" meet is undecided within the search's budget`);\n }\n return witness.kind === 'found' ? witness.path : null;\n}\n\n/**\n * The directories a walk must enter to find every match, one per brace\n * alternative: the leading literal segments, or a literal file's directory.\n * `''` for an alternative that can match at the root.\n */\nexport function globBases(glob: Glob): readonly string[] {\n return glob.compiled.bases;\n}\n\n/** The first of {@link globBases}: the 0.1 interface, kept for the library's callers. */\nexport function globBase(glob: Glob): string {\n // One base per alternative, and a pattern has at least one.\n return glob.compiled.bases[0] as string;\n}\n\n/** A brace alternative as its pattern writes it, and the index in the pattern where it ends. */\nexport interface WrittenAlternative {\n readonly written: string;\n readonly end: number;\n}\n\n/**\n * Each alternative of a pattern as written, in the order the core expands\n * them, and where each ends, when a `/` written there ends that alternative\n * and no other: the pattern itself when it has no braces, or the alternatives\n * of braces that end the pattern and whose own braces end theirs -\n * `lib/{util,new}`, `{a,b/{c,d}}`. `null` when more of the pattern follows\n * braces, `{a,b}/new`, since every alternative they give ends where the\n * pattern does; and for a pattern with a class, which may hold a brace or a\n * comma of its own and which this does not read.\n *\n * spec-core expands braces and keeps nothing of where an alternative was\n * written, and a note's advice is built from what was written, as the core\n * builds its own. So this reads braces as the expansion does, for a pattern\n * that parses: a `\\` escapes the next character, and a `}` with no `{` open\n * is a literal.\n */\nexport function writtenAlternatives(pattern: string): WrittenAlternative[] | null {\n let depth = 0;\n let open = 0;\n const stops: number[] = [];\n // A step past the end reads `''`, which no branch acts on, so the bound could be one further.\n for (let i = 0; i < pattern.length; i += 1) {\n const ch = pattern.charAt(i);\n if (ch === '\\\\') {\n i += 1;\n } else if (ch === '[') {\n return null;\n } else if (ch === '{') {\n if (depth === 0) open = i;\n depth += 1;\n } else if (ch === ',' && depth === 1) {\n stops.push(i);\n } else if (ch === '}' && depth > 0) {\n depth -= 1;\n if (depth === 0) return i === pattern.length - 1 ? optionsOf(pattern, open, [...stops, i]) : null;\n }\n }\n return [{ written: pattern, end: pattern.length }];\n}\n\n/** The alternatives of the braces opened at `open`, each option ending at one of `stops`. */\nfunction optionsOf(pattern: string, open: number, stops: readonly number[]): WrittenAlternative[] | null {\n const prefix = pattern.slice(0, open);\n const alternatives: WrittenAlternative[] = [];\n let start = open + 1;\n for (const stop of stops) {\n const inner = writtenAlternatives(pattern.slice(start, stop));\n if (inner === null) return null;\n for (const { written, end } of inner) alternatives.push({ written: prefix + written, end: start + end });\n start = stop + 1;\n }\n return alternatives;\n}\n\n/** A name with an extension, `login.ts` or `.eslintrc.json`, and not a dot-name such as `.github`. */\nexport function hasExtension(name: string): boolean {\n return /.\\.[^.]+$/.test(name);\n}\n\n/** The files a tree holds, and every directory above one. */\nexport interface Tree {\n readonly files: ReadonlySet<string>;\n readonly directories: ReadonlySet<string>;\n}\n\nconst trees = new WeakMap<readonly string[], Tree>();\n\n/** The tree a list of files describes. Built once per list, which every brief of a run shares. */\nexport function treeOf(files: readonly string[]): Tree {\n const known = trees.get(files);\n if (known !== undefined) return known;\n const directories = new Set<string>();\n for (const file of files) {\n for (let slash = file.indexOf('/'); slash !== -1; slash = file.indexOf('/', slash + 1)) directories.add(file.slice(0, slash));\n }\n const tree = { files: new Set(files), directories };\n trees.set(files, tree);\n return tree;\n}\n\n/** What the tree holds at a path: a file, a directory, or nothing. */\nexport function held(tree: Tree, path: string): 'file' | 'directory' | null {\n if (tree.files.has(path)) return 'file';\n return tree.directories.has(path) ? 'directory' : null;\n}\n\n/**\n * How a tree reads a literal path: a directory it holds is a directory, and\n * anything else is a file. Without a tree nothing is known to be a directory,\n * so every literal is a file; a trailing `/` says otherwise, and never reaches\n * this question.\n */\nexport function readingIn(files: readonly string[] | null): (path: string) => LiteralReading {\n if (files === null) return () => 'file';\n const tree = treeOf(files);\n return (path) => (held(tree, path) === 'directory' ? 'directory' : 'file');\n}\n"]}
|
package/dist/links.js
CHANGED
|
@@ -40,7 +40,7 @@ export function isInside(path, directory) {
|
|
|
40
40
|
}
|
|
41
41
|
export function dirOf(path) {
|
|
42
42
|
const slash = path.lastIndexOf('/');
|
|
43
|
-
return slash
|
|
43
|
+
return slash === -1 ? '' : path.slice(0, slash);
|
|
44
44
|
}
|
|
45
45
|
function parts(path) {
|
|
46
46
|
return path.split('/').filter((p) => p !== '' && p !== '.');
|
|
@@ -65,6 +65,7 @@ export function relativePath(fromDirectory, to) {
|
|
|
65
65
|
const from = parts(fromDirectory);
|
|
66
66
|
const target = parts(to);
|
|
67
67
|
let common = 0;
|
|
68
|
+
// One step past the end compares nothing with what is left of the target, and the slices below come out the same.
|
|
68
69
|
while (common < from.length && from[common] === target[common])
|
|
69
70
|
common += 1;
|
|
70
71
|
const up = from.slice(common).map(() => '..');
|