The remaining P1 from the #939 review, plus the P2 it shares a mechanism with. Nothing carried a version, so two tabs editing one namespace silently overwrote each other — reproduced as tab B's `reasoning` lost to tab A's older draft. The seam's per-namespace write queue orders writes; it cannot tell a fresh writer from one replaying a snapshot a predecessor superseded. Each namespace now carries a monotonic `revision` over its RAW section. A write may send `expectedRevision`, checked at the FRONT of the queue (not at call time, which would race the very predecessor it guards against); a mismatch rejects with `SettingsConflictError` → `settings-conflict` on the wire, carrying both revisions. The editor captures the revision it opened at and, on conflict, asks the user to reopen rather than replaying its snapshot. The same counter fixes the missing broadcast. `settings/updated` is gated on the resolved value — correct for consumers, wrong for configuration surfaces: storing an override equal to the composition base leaves the resolved value alone while changing what the document says (the field is now overridden, not inherited) and moving every open editor's revision. `settings/document-updated (ns, revision)` fires on any raw-section change, in-process or external, and `host/settings-changed` now rides it. That event also closes the stale model picker: editing a provider's `models` changes no route, so `llm/adapters-updated` never fired and an open picker kept serving the old catalog. A change to an exposed provider namespace now emits `host/models-changed` too — that namespace holds the catalog. Docs: both sides of the five touched README pairs, a type-equiv block for `SettingsPathOp`, and an Agent Note recording what the plane exposes and who may overwrite what. The deferred wire-redaction gaps (secrets behind union/intersection/transform, `.default(...)` in the served envelope, schema text in rejection messages, `new Function` rehydration, pi-ai's `headers`) are recorded as TODO(settings-wire-redaction) and in Known Limitations rather than half-fixed.
365 lines
19 KiB
TypeScript
365 lines
19 KiB
TypeScript
/**
|
|
* Generate committed Cordis artifacts from the Typert catalog projector and
|
|
* the independent vendored-core projector.
|
|
*/
|
|
|
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import {
|
|
projectCordisCatalog,
|
|
renderEvents,
|
|
renderServices,
|
|
} from '@deepseek-ai/dsh-typert-generator'
|
|
import type { CordisCatalogPolicy } from '@deepseek-ai/dsh-typert-generator'
|
|
import { renderCordisCoreApiPages } from './cordis-core-api.ts'
|
|
|
|
const root = resolve(import.meta.dirname, '..')
|
|
const OUT_EVENTS = 'docs/cordis-catalog/events.md'
|
|
const OUT_SERVICES = 'docs/cordis-catalog/services.md'
|
|
const OUT_RUNTIME_API = 'packages/cordis/tool-cordis/src/api-catalog.ts'
|
|
|
|
/** One primary core-data-structures page per project type used by a generated signature. */
|
|
export const LINK_MAP: Readonly<Record<string, string>> = {
|
|
Agent: 'core.md',
|
|
AgentCancelCause: 'core.md',
|
|
AgentOptions: 'core.md',
|
|
AgentStatus: 'core.md',
|
|
ContentBlock: 'core.md',
|
|
ContinuationDecision: 'core.md',
|
|
ContinuationStop: 'core.md',
|
|
GenerateOptions: 'core.md',
|
|
InboxItem: 'core.md',
|
|
InboxPlacement: 'core.md',
|
|
MessageId: 'core.md',
|
|
HookContext: 'core.md',
|
|
SettleReason: 'core.md',
|
|
AdapterRegistrationHandle: 'core.md',
|
|
LlmCallConfig: 'core.md',
|
|
LlmModelContext: 'core.md',
|
|
LlmModelReasoningInfo: 'core.md',
|
|
LlmResolvedModelInfo: 'core.md',
|
|
LlmFailure: 'llm-streaming.md',
|
|
LlmModelInfo: 'core.md',
|
|
LlmProviderInfo: 'core.md',
|
|
LlmConfigurableProvider: 'core.md',
|
|
ResolvedRetryPolicy: 'llm-streaming.md',
|
|
Message: 'core.md',
|
|
MessageSource: 'core.md',
|
|
UserMessage: 'session.md',
|
|
PromptDecision: 'core.md',
|
|
RequestError: 'core.md',
|
|
RequestErrorAction: 'core.md',
|
|
PreparedReferencedMessage: 'session-reference.md',
|
|
SessionReferenceCandidate: 'session-reference.md',
|
|
SessionReferenceInput: 'session-reference.md',
|
|
SessionEvent: 'core.md',
|
|
SessionId: 'core.md',
|
|
SessionStartSource: 'core.md',
|
|
SessionLogSnapshot: 'session-query.md',
|
|
SessionSurfaceSnapshot: 'session-query.md',
|
|
ApprovalOutcome: 'approval.md',
|
|
ApprovalPolicy: 'approval.md',
|
|
ApprovalRequest: 'approval.md',
|
|
ApprovalService: 'approval.md',
|
|
BashExecRequest: 'bash.md',
|
|
BashExecSpec: 'bash.md',
|
|
BashProcess: 'bash.md',
|
|
BashRunResult: 'bash.md',
|
|
DshEnvironment: 'subprocess.md',
|
|
SubprocessHandle: 'subprocess.md',
|
|
SubprocessOutcome: 'subprocess.md',
|
|
SubprocessOutputRead: 'subprocess.md',
|
|
SubprocessOutputReader: 'subprocess.md',
|
|
SubprocessSpawnSpec: 'subprocess.md',
|
|
CodeRunRequest: 'code-runtime.md',
|
|
CodeRunResult: 'code-runtime.md',
|
|
CompactionResult: 'compaction.md',
|
|
CompactionTrigger: 'compaction.md',
|
|
PruneResult: 'compaction.md',
|
|
FileReadOutcome: 'filesystem.md',
|
|
FsDirEntry: 'filesystem.md',
|
|
FsEditOutcome: 'filesystem.md',
|
|
FsEditRequest: 'filesystem.md',
|
|
FsInfo: 'filesystem.md',
|
|
FsPathInfo: 'filesystem.md',
|
|
FsPolicyExec: 'filesystem.md',
|
|
FsTarget: 'filesystem.md',
|
|
FsVersion: 'filesystem.md',
|
|
FsWriteIntent: 'filesystem.md',
|
|
FsWriteOutcome: 'filesystem.md',
|
|
CreateGoalRequest: 'goal.md',
|
|
EditGoalRequest: 'goal.md',
|
|
GoalBlockReason: 'goal.md',
|
|
GoalChanged: 'goal.md',
|
|
GoalRef: 'goal.md',
|
|
GoalView: 'goal.md',
|
|
CommandDefinition: 'commands.md',
|
|
CommandDescriptor: 'commands.md',
|
|
CommandResult: 'commands.md',
|
|
CommandSurface: 'commands.md',
|
|
LlmAdapter: 'llm-streaming.md',
|
|
PreparedLlmCall: 'llm-streaming.md',
|
|
LlmService: 'llm-streaming.md',
|
|
StreamChunk: 'llm-streaming.md',
|
|
SkillProviderControl: 'skills.md',
|
|
CreateSessionOptions: 'persistence.md',
|
|
SessionHeader: 'persistence.md',
|
|
SessionLocation: 'persistence.md',
|
|
SessionPersistenceSnapshot: 'persistence.md',
|
|
ConfinedArgv: 'sandbox.md',
|
|
SandboxExecutionPolicy: 'sandbox.md',
|
|
SandboxMode: 'sandbox.md',
|
|
SandboxPolicy: 'sandbox.md',
|
|
PtyBackend: 'pty.md',
|
|
PtyReadRequest: 'pty.md',
|
|
PtyReadResult: 'pty.md',
|
|
PtySendOperation: 'pty.md',
|
|
PtySendRequest: 'pty.md',
|
|
PtySessionId: 'pty.md',
|
|
PtySessionSnapshot: 'pty.md',
|
|
PtySignal: 'pty.md',
|
|
PtySignalResult: 'pty.md',
|
|
PtySpawnRequest: 'pty.md',
|
|
PtySpawnResult: 'pty.md',
|
|
SandboxPolicyRequest: 'sandbox.md',
|
|
ScopeKey: 'scope.md',
|
|
Scoped: 'scope.md',
|
|
EpochHeader: 'session.md',
|
|
Session: 'session.md',
|
|
SessionEventMap: 'session.md',
|
|
TurnEndReason: 'session.md',
|
|
TurnTrigger: 'session.md',
|
|
SessionEventReadRequest: 'session-query.md',
|
|
SessionEventRecord: 'session-query.md',
|
|
SessionEventResultFilter: 'session-query.md',
|
|
SessionEventSearchDocument: 'session-query.md',
|
|
SessionEventSearchHit: 'session-query.md',
|
|
SessionEventSearchPage: 'session-query.md',
|
|
SessionEventSearchRequest: 'session-query.md',
|
|
SessionEventTrace: 'session-query.md',
|
|
SessionEventTraceObservation: 'session-query.md',
|
|
SessionEventTraceRequest: 'session-query.md',
|
|
SessionEventWindow: 'session-query.md',
|
|
SessionLineageTrace: 'session-query.md',
|
|
SessionRecord: 'session-query.md',
|
|
SessionResultFilter: 'session-query.md',
|
|
SessionSearchExecContext: 'session-query.md',
|
|
SessionSearchHit: 'session-query.md',
|
|
SessionSearchPage: 'session-query.md',
|
|
SessionSearchRequest: 'session-query.md',
|
|
SessionTitleObservation: 'session-query.md',
|
|
SessionTitleObservationResult: 'session-query.md',
|
|
SessionTitleProvider: 'session-title.md',
|
|
SessionTitleSnapshot: 'session-title.md',
|
|
SkillCatalogSnapshot: 'skills.md',
|
|
SkillDefinition: 'skills.md',
|
|
SkillLookupOptions: 'skills.md',
|
|
SkillProvider: 'skills.md',
|
|
SkillProviderObservation: 'skills.md',
|
|
SkillRegistration: 'skills.md',
|
|
SkillSummary: 'skills.md',
|
|
SaveTextSpill: 'spill.md',
|
|
SpillRef: 'spill.md',
|
|
SubagentProvider: 'subagent.md',
|
|
SubagentRun: 'subagent.md',
|
|
SubagentService: 'subagent.md',
|
|
SubagentStartRequest: 'subagent.md',
|
|
AssembleContext: 'system-prompt.md',
|
|
PromptSection: 'system-prompt.md',
|
|
SystemPrompt: 'system-prompt.md',
|
|
ToolProviderResult: 'system-prompt.md',
|
|
TaskDoneListener: 'tasks.md',
|
|
TaskId: 'tasks.md',
|
|
TaskRead: 'tasks.md',
|
|
TaskSnapshot: 'tasks.md',
|
|
TaskStart: 'tasks.md',
|
|
TokenMeasurement: 'token-meter.md',
|
|
CodeDispatchLog: 'tools.md',
|
|
PostToolDecision: 'tools.md',
|
|
PreToolDecision: 'tools.md',
|
|
ToolDefinition: 'tools.md',
|
|
ToolExecution: 'tools.md',
|
|
ToolDispatchExecution: 'tools.md',
|
|
ToolExecutionInput: 'tools.md',
|
|
ToolExecutionMode: 'tools.md',
|
|
ToolExecutionResult: 'tools.md',
|
|
ToolExecutionToken: 'tools.md',
|
|
ToolGuard: 'tools.md',
|
|
ToolRegistry: 'tools.md',
|
|
ToolRestriction: 'tools.md',
|
|
ToolSchema: 'tools.md',
|
|
SettingsNamespace: 'settings.md',
|
|
SettingsRegisterOptions: 'settings.md',
|
|
SettingsScope: 'settings.md',
|
|
SettingsDescriptor: 'settings.md',
|
|
SettingsPathOp: 'settings.md',
|
|
SettingsDescribeOptions: 'settings.md',
|
|
SettingsUpdateSource: 'settings.md',
|
|
CredentialRef: 'credentials.md',
|
|
CredentialInfo: 'credentials.md',
|
|
ResolvedCredential: 'credentials.md',
|
|
AskUserQuestionAnswer: 'user-interaction.md',
|
|
AskUserQuestionRequest: 'user-interaction.md',
|
|
UserInteractionProvider: 'user-interaction.md',
|
|
WebFetchProvider: 'web.md',
|
|
WebFetchRequest: 'web.md',
|
|
WebFetchResult: 'web.md',
|
|
WebSearchProvider: 'web.md',
|
|
WebSearchRequest: 'web.md',
|
|
WebSearchResult: 'web.md',
|
|
WorkflowRun: 'workflow.md',
|
|
WorkflowRunInfo: 'workflow.md',
|
|
WorkflowStartRequest: 'workflow.md',
|
|
}
|
|
|
|
/** TypeScript lib and pinned framework types with no repository-owned data page. */
|
|
export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
|
|
'AbortSignal',
|
|
'AsyncIterable',
|
|
'Context',
|
|
'Error',
|
|
'Map',
|
|
'Partial',
|
|
'Pick',
|
|
'Promise',
|
|
'Readonly',
|
|
])
|
|
|
|
/** Project types deliberately documented outside the core-data catalog. */
|
|
export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
|
AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md',
|
|
z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
|
|
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
|
InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
|
ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
|
InsertTextRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
|
AgentHandle: 'agent ownership handle is owned by packages/core/agent/README.md',
|
|
BashEnvContributor: 'service-local extension type is owned by packages/bash/tool-bash/src/index.ts',
|
|
BashEnvVariableInfo: 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts',
|
|
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
|
|
DirectoryPickerCapability: 'picker interaction contract is owned by packages/host/directory-picker/README.md',
|
|
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
|
|
Domain: 'domain interface is owned by packages/storage/storage-domain/README.md',
|
|
DomainChanged: 'event-local snapshot is owned by packages/storage/storage-domain/src/events.ts',
|
|
DomainFacility: 'domain form facility is owned by packages/storage/storage-domain/README.md',
|
|
DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
|
|
DomainSpec: 'domain declaration contract is owned by packages/storage/storage-domain/README.md',
|
|
StorageBackend: 'backend contract is owned by packages/storage/storage/src/backend.ts',
|
|
StorageForms: 'merge-extensible form map is owned by packages/storage/storage/src/index.ts',
|
|
ProjectionDefinition: 'projection unit contract is owned by packages/session-projection/session-projection/README.md',
|
|
SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session-projection/session-projection/src/types.ts',
|
|
ProjectionChangeListener: 'change-feed listener contract is owned by packages/session-projection/session-projection/src/index.ts',
|
|
ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session-projection/session-projection/src/index.ts',
|
|
ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session-projection/session-projection/src/index.ts',
|
|
CommandExecution: 'executor return contract is owned by packages/ui/commands/src/index.ts',
|
|
TypertContribution: 'registry contribution contract is owned by packages/typert/registry/README.md',
|
|
TypertFace: 'registry face identity is owned by packages/typert/registry/README.md',
|
|
TypertPackageFilter: 'registry package query filter is owned by packages/typert/registry/README.md',
|
|
TypertPackageRecord: 'registry package record is owned by packages/typert/registry/README.md',
|
|
TypertSchemaFilter: 'registry schema query filter is owned by packages/typert/registry/README.md',
|
|
TypertSchemaRecord: 'registry schema record is owned by packages/typert/registry/README.md',
|
|
'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API',
|
|
'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API',
|
|
InvariantInstaller: 'service-local contribution contract is owned by packages/support/invariants/README.md',
|
|
LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts',
|
|
WebBootGraph: 'web boot graph wire shape is owned by packages/client/modules/src/client/index.ts',
|
|
WebRoute: 'route registration contract is owned by packages/host/webserver/src/index.ts',
|
|
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
|
|
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
|
|
TuiOverlayRequest: 'service-local extension contract is owned by packages/ui/tui/README.md',
|
|
TuiOverlaySession: 'service-local extension contract is owned by packages/ui/tui/README.md',
|
|
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
|
|
PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md',
|
|
PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md',
|
|
KnobState: 'projection unit state shape is owned by packages/ui/permission/README.md',
|
|
PermissionSelect: 'permissions projection payload is owned by packages/ui/permission/src/types.ts',
|
|
PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
|
|
ResumeAgentOptions: 'agent resume contract is owned by packages/core/agent/README.md',
|
|
SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts',
|
|
SubagentRunEndInfo: 'event-local snapshot is owned by packages/subagent/subagent/src/index.ts',
|
|
SubagentRunInfo: 'event-local snapshot is owned by packages/subagent/subagent/src/index.ts',
|
|
TelemetryRecord: 'seam-local record contract is owned by packages/telemetry/session-telemetry/src/index.ts',
|
|
WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
|
WorkflowAgentInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
|
WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
|
Workspace: 'workspace entity contract is owned by packages/workspace/workspace/README.md',
|
|
WorkspaceId: 'branded id is owned by packages/workspace/workspace/README.md',
|
|
}
|
|
|
|
/** Repository data policy consumed by the Cordis catalog projector. */
|
|
export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
|
|
linkedTypePages: LINK_MAP,
|
|
foundationTypeNames: FOUNDATION_TYPE_NAMES,
|
|
typeLinkExemptions: TYPE_LINK_EXEMPTIONS,
|
|
inheritedEvents: [
|
|
{ name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
|
|
{ name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
|
|
{ name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:332' },
|
|
{ name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:334' },
|
|
{ name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:336' },
|
|
{ name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:338' },
|
|
{ name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:340' },
|
|
{ name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:342' },
|
|
{ name: 'hmr/change', summary: 'A watched source file changed on disk.', source: 'vendor/hmr/src/index.ts:20' },
|
|
{ name: 'hmr/reload', summary: 'Plugins are being reloaded after a change.', source: 'vendor/hmr/src/index.ts:21' },
|
|
{ name: 'exit', summary: 'The process is exiting on a signal.', source: 'vendor/loader/src/index.ts:23' },
|
|
{ name: 'loader/config-update', summary: 'The loader config tree changed.', source: 'vendor/loader/src/index.ts:24' },
|
|
{ name: 'loader/entry-init', summary: 'A config entry is being initialized.', source: 'vendor/loader/src/index.ts:25' },
|
|
{ name: 'loader/partial-dispose', summary: 'An entry is being partially disposed on reload.', source: 'vendor/loader/src/index.ts:26' },
|
|
{ name: 'loader/patch-context', summary: 'A context is being patched during a reload.', source: 'vendor/loader/src/index.ts:27' },
|
|
],
|
|
inheritedServices: [
|
|
{ name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:34' },
|
|
{ name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).', source: 'vendor/cordis/src/events.ts:34' },
|
|
{ name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:164' },
|
|
{ name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.', source: 'vendor/cordis/src/fiber.ts:9' },
|
|
{ name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
|
|
{ name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
|
|
{ name: 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
|
|
{ name: 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
|
|
{ name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },
|
|
{ name: 'ctx.hmr', summary: 'The hot-module-reload watcher (present under the hmr plugin).', source: 'vendor/hmr/src/index.ts:15' },
|
|
],
|
|
}
|
|
|
|
/** CLI entry: default writes every artifact; `--check` reports stale files.
|
|
* @returns nothing; writes files or reports freshness through the process.
|
|
*/
|
|
export function main(): void {
|
|
const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY)
|
|
const outputs: [string, string][] = [
|
|
[OUT_EVENTS, renderEvents([...model.events], CORDIS_CATALOG_POLICY)],
|
|
[OUT_SERVICES, renderServices([...model.services], CORDIS_CATALOG_POLICY)],
|
|
[OUT_RUNTIME_API, projector.renderRuntimeApi(model)],
|
|
...renderCordisCoreApiPages(),
|
|
]
|
|
if (process.argv.includes('--check')) {
|
|
const stale: string[] = []
|
|
for (const [out, content] of outputs) {
|
|
let committed: string | null = null
|
|
try {
|
|
committed = readFileSync(resolve(root, out), 'utf8')
|
|
} catch {
|
|
// Only ENOENT is expected; either read failure has the same remedy.
|
|
committed = null
|
|
}
|
|
if (committed !== content) stale.push(out)
|
|
}
|
|
if (stale.length === 0) {
|
|
console.log(`gen-cordis-catalog: ${outputs.length} generated file(s) are up to date.`)
|
|
process.exit(0)
|
|
}
|
|
console.error(`gen-cordis-catalog: ${stale.join(' and ')} ${stale.length === 1 ? 'is' : 'are'} stale. Run \`pnpm run gen-cordis-catalog\` and commit the result.`)
|
|
process.exit(1)
|
|
}
|
|
|
|
for (const [out, content] of outputs) {
|
|
const destination = resolve(root, out)
|
|
mkdirSync(dirname(destination), { recursive: true })
|
|
writeFileSync(destination, content)
|
|
}
|
|
console.log(`gen-cordis-catalog: wrote ${outputs.length} generated file(s).`)
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) main()
|