@agimon-ai/doompi 0.0.1-alpha.52 → 0.0.1-alpha.53
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/dist/src/adapters/bootstrapLocator.cjs +1 -1
- package/dist/src/adapters/bootstrapLocator.cjs.map +1 -1
- package/dist/src/adapters/bootstrapLocator.d.cts.map +1 -1
- package/dist/src/adapters/bootstrapLocator.d.mts.map +1 -1
- package/dist/src/adapters/bootstrapLocator.mjs +1 -1
- package/dist/src/adapters/bootstrapLocator.mjs.map +1 -1
- package/dist/src/adapters/syncDrift.cjs +1 -1
- package/dist/src/adapters/syncDrift.cjs.map +1 -1
- package/dist/src/adapters/syncDrift.d.cts +2 -0
- package/dist/src/adapters/syncDrift.d.cts.map +1 -1
- package/dist/src/adapters/syncDrift.d.mts +2 -0
- package/dist/src/adapters/syncDrift.d.mts.map +1 -1
- package/dist/src/adapters/syncDrift.mjs +1 -1
- package/dist/src/adapters/syncDrift.mjs.map +1 -1
- package/package.json +13 -13
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./repository/repository.cjs"),n=require("./syncRegistration.cjs");require("./syncStateContract.cjs");const r=require("./syncState.cjs");let i=require("node:url"),a=require("node:path");a=e.__toESM(a,1);let o=require("node:crypto");o=e.__toESM(o,1);let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=new Map;function u(e){try{return o.default.createHash(`sha256`).update(s.default.readFileSync(e)).digest(`hex`)}catch{return}}function d(e){let t;try{t=s.default.statSync(e)}catch{return}if(!t.isFile())return;let n=u(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function f(e){if(m(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function p(e){let t=[],n=o.default.createHash(`sha256`);for(let r of e){let e;try{e=s.default.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=l.get(r);if(i!==void 0)return i;let a=t.every(e=>u(e.path)===e.sha256);return l.set(r,a),a}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e){return m(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function g(e){let t=a.default.resolve(e);try{return s.default.realpathSync(t)}catch{let e=a.default.dirname(t);return e===t?t:a.default.join(g(e),a.default.basename(t))}}function _(e,t){let n=a.default.relative(g(e),g(t));return n===``||!n.startsWith(`..`)&&!a.default.isAbsolute(n)}function v(e=require("url").pathToFileURL(__filename).href){let t=e.endsWith(`.ts`)?`ts`:`mjs`,
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./repository/repository.cjs"),n=require("./syncRegistration.cjs");require("./syncStateContract.cjs");const r=require("./syncState.cjs");let i=require("node:url"),a=require("node:path");a=e.__toESM(a,1);let o=require("node:crypto");o=e.__toESM(o,1);let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=new Map;function u(e){try{return o.default.createHash(`sha256`).update(s.default.readFileSync(e)).digest(`hex`)}catch{return}}function d(e){let t;try{t=s.default.statSync(e)}catch{return}if(!t.isFile())return;let n=u(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function f(e){if(m(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function p(e){let t=[],n=o.default.createHash(`sha256`);for(let r of e){let e;try{e=s.default.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=l.get(r);if(i!==void 0)return i;let a=t.every(e=>u(e.path)===e.sha256);return l.set(r,a),a}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e){return m(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function g(e){let t=a.default.resolve(e);try{return s.default.realpathSync(t)}catch{let e=a.default.dirname(t);return e===t?t:a.default.join(g(e),a.default.basename(t))}}function _(e,t){let n=a.default.relative(g(e),g(t));return n===``||!n.startsWith(`..`)&&!a.default.isAbsolute(n)}function v(e=require("url").pathToFileURL(__filename).href){let t=process.env.DOOMPI_BOOTSTRAP_ENTRY;if(t)return t;let n=e.endsWith(`.ts`)?`ts`:`mjs`,r=a.default.dirname(a.default.dirname((0,i.fileURLToPath)(e)));return a.default.join(r,`extensions`,`entries`,`doom.${n}`)}function y(e,t){return n.readSyncRegistration(e,t)!==void 0}function b(e,n=c.default.homedir()){if(y(e,n))return g(e);let r=t.resolveDoomConfigurationRoot(e,n);return y(r,n)?g(r):void 0}function x(e,t=c.default.homedir()){let i=n.readSyncRegistration(e,t);if(!i)return;let a=i.statePath,o=i.generationRoot,s=r.readSyncState(e,t);if(!s)return;let l=typeof s.bootstrap==`string`?s.bootstrap:void 0;if(l&&!_(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(s.precompile!==void 0){let e=s.precompile;if(!m(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!m(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:h(s.bundles),precompile:u}}function S(e,t){if(_(t,e))try{let n=JSON.parse(s.default.readFileSync(e,`utf8`));if(!m(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=f(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!_(t,n.output)||i.some(e=>!_(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function C(e){return!s.default.existsSync(e.output)||e.artifacts.some(e=>!s.default.existsSync(e))?!1:p(e.inputs)}function w(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||g(e.precompile.bootstrapEntry)!==g(t))return;let n=S(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!C(n)||n.output!==e.bootstrap||n.entries.length!==1||g(n.entries[0]??``)!==g(t)))return n}function T(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();return r?{bootstrap:r.bootstrap,fresh:w(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function E(e,t,n=c.default.homedir()){let r=x(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=S(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&C(o))}}function D(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();if(!r?.bootstrap||!r.precompile||!w(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=S(n,r.generatedDirectory);return!!(i&&i.output===t&&C(i))});return{bootstrap:r.bootstrap,fresh:o}}exports.findSyncedRoot=b,exports.fingerprintInput=d,exports.inputsAreFresh=p,exports.parseInputFingerprint=f,exports.readBootstrapStatus=D,exports.readBundleStatus=E,exports.readStartupBootstrapStatus=T;
|
|
2
2
|
//# sourceMappingURL=bootstrapLocator.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrapLocator.cjs","names":["crypto","fs","path","fileURLToPath","readSyncRegistration","os","resolveDoomConfigurationRoot","readSyncState"],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bootstrapLocator.cjs","names":["crypto","fs","path","fileURLToPath","readSyncRegistration","os","resolveDoomConfigurationRoot","readSyncState"],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const configuredEntry = process.env[BOOTSTRAP_ENTRY_ENV];\n if (configuredEntry) return configuredEntry;\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrapLocator.d.cts","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"bootstrapLocator.d.cts","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"mappings":";;iBAuLgB,eAAe,aAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrapLocator.d.mts","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"bootstrapLocator.d.mts","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"mappings":";;iBAuLgB,eAAe,aAAa"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{resolveDoomConfigurationRoot as e}from"./repository/repository.mjs";import{readSyncRegistration as t}from"./syncRegistration.mjs";import"./syncStateContract.mjs";import{readSyncState as n}from"./syncState.mjs";import{fileURLToPath as r}from"node:url";import i from"node:path";import a from"node:crypto";import o from"node:fs";import s from"node:os";const c=new Map;function l(e){try{return a.createHash(`sha256`).update(o.readFileSync(e)).digest(`hex`)}catch{return}}function u(e){let t;try{t=o.statSync(e)}catch{return}if(!t.isFile())return;let n=l(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function d(e){if(p(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function f(e){let t=[],n=a.createHash(`sha256`);for(let r of e){let e;try{e=o.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=c.get(r);if(i!==void 0)return i;let s=t.every(e=>l(e.path)===e.sha256);return c.set(r,s),s}function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function m(e){return p(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function h(e){let t=i.resolve(e);try{return o.realpathSync(t)}catch{let e=i.dirname(t);return e===t?t:i.join(h(e),i.basename(t))}}function g(e,t){let n=i.relative(h(e),h(t));return n===``||!n.startsWith(`..`)&&!i.isAbsolute(n)}function _(e=import.meta.url){let t=e.endsWith(`.ts`)?`ts`:`mjs`,
|
|
1
|
+
import{resolveDoomConfigurationRoot as e}from"./repository/repository.mjs";import{readSyncRegistration as t}from"./syncRegistration.mjs";import"./syncStateContract.mjs";import{readSyncState as n}from"./syncState.mjs";import{fileURLToPath as r}from"node:url";import i from"node:path";import a from"node:crypto";import o from"node:fs";import s from"node:os";const c=new Map;function l(e){try{return a.createHash(`sha256`).update(o.readFileSync(e)).digest(`hex`)}catch{return}}function u(e){let t;try{t=o.statSync(e)}catch{return}if(!t.isFile())return;let n=l(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function d(e){if(p(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function f(e){let t=[],n=a.createHash(`sha256`);for(let r of e){let e;try{e=o.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=c.get(r);if(i!==void 0)return i;let s=t.every(e=>l(e.path)===e.sha256);return c.set(r,s),s}function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function m(e){return p(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function h(e){let t=i.resolve(e);try{return o.realpathSync(t)}catch{let e=i.dirname(t);return e===t?t:i.join(h(e),i.basename(t))}}function g(e,t){let n=i.relative(h(e),h(t));return n===``||!n.startsWith(`..`)&&!i.isAbsolute(n)}function _(e=import.meta.url){let t=process.env.DOOMPI_BOOTSTRAP_ENTRY;if(t)return t;let n=e.endsWith(`.ts`)?`ts`:`mjs`,a=i.dirname(i.dirname(r(e)));return i.join(a,`extensions`,`entries`,`doom.${n}`)}function v(e,n){return t(e,n)!==void 0}function y(t,n=s.homedir()){if(v(t,n))return h(t);let r=e(t,n);return v(r,n)?h(r):void 0}function b(e,r=s.homedir()){let i=t(e,r);if(!i)return;let a=i.statePath,o=i.generationRoot,c=n(e,r);if(!c)return;let l=typeof c.bootstrap==`string`?c.bootstrap:void 0;if(l&&!g(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(c.precompile!==void 0){let e=c.precompile;if(!p(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!p(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:m(c.bundles),precompile:u}}function x(e,t){if(g(t,e))try{let n=JSON.parse(o.readFileSync(e,`utf8`));if(!p(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=d(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!g(t,n.output)||i.some(e=>!g(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function S(e){return!o.existsSync(e.output)||e.artifacts.some(e=>!o.existsSync(e))?!1:f(e.inputs)}function C(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||h(e.precompile.bootstrapEntry)!==h(t))return;let n=x(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!S(n)||n.output!==e.bootstrap||n.entries.length!==1||h(n.entries[0]??``)!==h(t)))return n}function w(e,t,n=s.homedir()){let r=b(e,n),i=t??_();return r?{bootstrap:r.bootstrap,fresh:C(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function T(e,t,n=s.homedir()){let r=b(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=x(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&S(o))}}function E(e,t,n=s.homedir()){let r=b(e,n),i=t??_();if(!r?.bootstrap||!r.precompile||!C(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=x(n,r.generatedDirectory);return!!(i&&i.output===t&&S(i))});return{bootstrap:r.bootstrap,fresh:o}}export{y as findSyncedRoot,u as fingerprintInput,f as inputsAreFresh,d as parseInputFingerprint,E as readBootstrapStatus,T as readBundleStatus,w as readStartupBootstrapStatus};
|
|
2
2
|
//# sourceMappingURL=bootstrapLocator.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrapLocator.mjs","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bootstrapLocator.mjs","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const configuredEntry = process.env[BOOTSTRAP_ENTRY_ENV];\n if (configuredEntry) return configuredEntry;\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncRegistration.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);function s(e){let s=e.homeDirectory??o.default.homedir(),c=[],l,u;try{u=t.readSyncRegistration(e.repoRoot,s),l=u?n.readSyncState(e.repoRoot,s):void 0}catch{l=void 0,u=void 0}if(!l||!u)return{fresh:!1,reasons:[`never-synced`]};let d;try{d=n.computeInputsHash(e.repoRoot,l.selection,s)}catch{c.push(`configuration-changed`)}d!==void 0&&d!==l.inputsHash&&c.push(`configuration-changed`),l.webSourcesHash!==void 0&&n.computeWebSourcesHash(l.resolved)!==l.webSourcesHash&&c.push(`code-changed`);try{r.readBootstrapStatus(e.repoRoot,
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncRegistration.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);function s(e){let s=e.homeDirectory??o.default.homedir(),c=[],l,u;try{u=t.readSyncRegistration(e.repoRoot,s),l=u?n.readSyncState(e.repoRoot,s):void 0}catch{l=void 0,u=void 0}if(!l||!u)return{fresh:!1,reasons:[`never-synced`]};let d;try{d=n.computeInputsHash(e.repoRoot,l.selection,s)}catch{c.push(`configuration-changed`)}d!==void 0&&d!==l.inputsHash&&c.push(`configuration-changed`),l.webSourcesHash!==void 0&&n.computeWebSourcesHash(l.resolved)!==l.webSourcesHash&&c.push(`code-changed`);try{r.readBootstrapStatus(e.repoRoot,e.expectedBootstrapEntry,s).fresh||c.push(`runtime-stale`)}catch{c.push(`runtime-stale`)}return u.webDirectory!==null&&(!a.default.existsSync(i.default.join(u.webDirectory,`index.html`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`composition.js`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`manifest.json`)))&&c.push(`cockpit-bundle-missing`),a.default.existsSync(u.apiDirectory)||c.push(`package-apis-missing`),{fresh:c.length===0,reasons:c,recordedInputsHash:l.inputsHash,...d===void 0?{}:{currentInputsHash:d}}}function c(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}exports.describeSyncDrift=c,exports.readSyncDrift=s;
|
|
2
2
|
//# sourceMappingURL=syncDrift.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncDrift.cjs","names":["os","readSyncRegistration","readSyncState","computeInputsHash","computeWebSourcesHash","readBootstrapStatus","fs","path"],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot,
|
|
1
|
+
{"version":3,"file":"syncDrift.cjs","names":["os","readSyncRegistration","readSyncState","computeInputsHash","computeWebSourcesHash","readBootstrapStatus","fs","path"],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json')))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"6RA6CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAeC,EAAAA,qBAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAeC,EAAAA,cAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoBC,EAAAA,kBAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAaC,EAAAA,sBAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACGC,EAAAA,oBAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAWA,OATE,EAAa,eAAiB,OAC7B,CAACC,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,IAE/F,EAAQ,KAAK,wBAAwB,EAElCD,EAAAA,QAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncDrift.d.cts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;
|
|
1
|
+
{"version":3,"file":"syncDrift.d.cts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAgE9C,kBAAkB,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncDrift.d.mts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;
|
|
1
|
+
{"version":3,"file":"syncDrift.d.mts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAgE9C,kBAAkB,OAAO"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readSyncRegistration as e}from"./syncRegistration.mjs";import{computeInputsHash as t,computeWebSourcesHash as n,readSyncState as r}from"./syncState.mjs";import{readBootstrapStatus as i}from"./bootstrapLocator.mjs";import a from"node:path";import o from"node:fs";import s from"node:os";function c(c){let l=c.homeDirectory??s.homedir(),u=[],d,f;try{f=e(c.repoRoot,l),d=f?r(c.repoRoot,l):void 0}catch{d=void 0,f=void 0}if(!d||!f)return{fresh:!1,reasons:[`never-synced`]};let p;try{p=t(c.repoRoot,d.selection,l)}catch{u.push(`configuration-changed`)}p!==void 0&&p!==d.inputsHash&&u.push(`configuration-changed`),d.webSourcesHash!==void 0&&n(d.resolved)!==d.webSourcesHash&&u.push(`code-changed`);try{i(c.repoRoot,
|
|
1
|
+
import{readSyncRegistration as e}from"./syncRegistration.mjs";import{computeInputsHash as t,computeWebSourcesHash as n,readSyncState as r}from"./syncState.mjs";import{readBootstrapStatus as i}from"./bootstrapLocator.mjs";import a from"node:path";import o from"node:fs";import s from"node:os";function c(c){let l=c.homeDirectory??s.homedir(),u=[],d,f;try{f=e(c.repoRoot,l),d=f?r(c.repoRoot,l):void 0}catch{d=void 0,f=void 0}if(!d||!f)return{fresh:!1,reasons:[`never-synced`]};let p;try{p=t(c.repoRoot,d.selection,l)}catch{u.push(`configuration-changed`)}p!==void 0&&p!==d.inputsHash&&u.push(`configuration-changed`),d.webSourcesHash!==void 0&&n(d.resolved)!==d.webSourcesHash&&u.push(`code-changed`);try{i(c.repoRoot,c.expectedBootstrapEntry,l).fresh||u.push(`runtime-stale`)}catch{u.push(`runtime-stale`)}return f.webDirectory!==null&&(!o.existsSync(a.join(f.webDirectory,`index.html`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`composition.js`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`manifest.json`)))&&u.push(`cockpit-bundle-missing`),o.existsSync(f.apiDirectory)||u.push(`package-apis-missing`),{fresh:u.length===0,reasons:u,recordedInputsHash:d.inputsHash,...p===void 0?{}:{currentInputsHash:p}}}function l(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}export{l as describeSyncDrift,c as readSyncDrift};
|
|
2
2
|
//# sourceMappingURL=syncDrift.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncDrift.mjs","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot,
|
|
1
|
+
{"version":3,"file":"syncDrift.mjs","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json')))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"oSA6CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiB,EAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAe,EAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAe,EAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoB,EAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAa,EAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACG,EAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAWA,OATE,EAAa,eAAiB,OAC7B,CAAC,EAAG,WAAW,EAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,IAE/F,EAAQ,KAAK,wBAAwB,EAElC,EAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.53",
|
|
4
4
|
"description": "Opinionated, composable Pi distribution for scoped agent tools, skills, MCP servers, and developer workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -319,17 +319,17 @@
|
|
|
319
319
|
"rolldown": "1.2.6",
|
|
320
320
|
"typebox": "1.3.19",
|
|
321
321
|
"yaml": "2.9.0",
|
|
322
|
-
"@agimon-ai/doompi-autostop": "0.0.1-alpha.
|
|
323
|
-
"@agimon-ai/doompi-
|
|
324
|
-
"@agimon-ai/doompi-
|
|
325
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
326
|
-
"@agimon-ai/doompi-
|
|
327
|
-
"@agimon-ai/doompi-notification": "0.0.1-alpha.
|
|
328
|
-
"@agimon-ai/doompi-
|
|
329
|
-
"@agimon-ai/doompi-
|
|
330
|
-
"@agimon-ai/doompi-
|
|
331
|
-
"@agimon-ai/doompi-
|
|
332
|
-
"@agimon-ai/doompi-
|
|
322
|
+
"@agimon-ai/doompi-autostop": "0.0.1-alpha.30",
|
|
323
|
+
"@agimon-ai/doompi-domain": "0.0.1-alpha.31",
|
|
324
|
+
"@agimon-ai/doompi-cache": "0.0.1-alpha.19",
|
|
325
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.52",
|
|
326
|
+
"@agimon-ai/doompi-config": "0.0.1-alpha.52",
|
|
327
|
+
"@agimon-ai/doompi-notification": "0.0.1-alpha.30",
|
|
328
|
+
"@agimon-ai/doompi-profile": "0.0.1-alpha.31",
|
|
329
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.53",
|
|
330
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.52",
|
|
331
|
+
"@agimon-ai/doompi-skill": "0.0.1-alpha.31",
|
|
332
|
+
"@agimon-ai/doompi-major-mode": "0.0.1-alpha.31"
|
|
333
333
|
},
|
|
334
334
|
"devDependencies": {
|
|
335
335
|
"@earendil-works/pi-ai": "0.84.4",
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
"@vitest/coverage-v8": "4.1.11",
|
|
340
340
|
"typescript": "7.0.2",
|
|
341
341
|
"vitest": "4.1.11",
|
|
342
|
-
"@agimon-ai/doompi-runner": "0.0.1-alpha.
|
|
342
|
+
"@agimon-ai/doompi-runner": "0.0.1-alpha.53"
|
|
343
343
|
},
|
|
344
344
|
"peerDependencies": {
|
|
345
345
|
"@earendil-works/pi-tui": "0.84.4"
|