@ekanos/cli 0.18.0 → 0.19.0

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.
@@ -6,7 +6,7 @@ import { inContractOrder, readOperationIds, resolveOpTokens, } from '../experien
6
6
  import { DEPRECATED_USES } from '../experience/pins.js';
7
7
  import { readExperienceProject, writeIntegrations, } from '../experience/project.js';
8
8
  import { loadPins, regenerateIntegrations } from '../experience/regenerate.js';
9
- const SPEC = /^([a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
9
+ const SPEC = /^((?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/)?[a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
10
10
  export const addCommand = {
11
11
  name: 'add',
12
12
  usage: [
@@ -1 +1 @@
1
- {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IntegrationEntry } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { invalidStateError, usageError } from '../errors';\nimport {\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport {\n inContractOrder,\n readOperationIds,\n resolveOpTokens,\n} from '../experience/pin-ops';\nimport { DEPRECATED_USES } from '../experience/pins';\nimport {\n readExperienceProject,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport type { ExperienceCommand } from './index-experience';\n\nconst SPEC = /^([a-z][a-z0-9-]*)@(\\d+(?:\\.\\d+)?)$/;\n\nexport const addCommand: ExperienceCommand = {\n name: 'add',\n usage: [\n ' add Add an integration to an experience: pin its contract,',\n ' record it in ekanos.json + ekanos.lock.json, regenerate types',\n ' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',\n ],\n positionals: ['spec'],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n ops: {\n type: 'string',\n description:\n 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',\n },\n as: {\n type: 'string',\n description:\n 'Removed: an experience addresses an integration by its own name',\n },\n 'from-dir': {\n type: 'string',\n description:\n 'Directory of <integration>.contract.json files (the contract source)',\n },\n 'from-path': {\n type: 'string',\n description:\n 'An integration project directory whose contract.json to pin',\n },\n },\n async run(ctx, args) {\n const spec = args.positionals[0];\n const match = spec ? SPEC.exec(spec) : null;\n if (!match) {\n throw usageError(\n spec\n ? `\"${spec}\" is not <integration>@<version>.`\n : 'add needs <integration>@<version>.',\n 'e.g. \"ekanos add sanity@0.2 --from-dir ../contracts\".',\n );\n }\n const [, integration, version] = match as unknown as [\n string,\n string,\n string,\n ];\n if (args.flags.as !== undefined) {\n throw usageError(\n '--as is gone: an experience addresses an integration by its own name.',\n `Use \"${integration}\" in useOperation('${integration}', ...).`,\n );\n }\n\n const project = readExperienceProject(args.dir);\n if (project.deprecatedUses) ctx.warn(DEPRECATED_USES);\n const sources = defaultSources(\n project,\n {\n fromDir: args.flags['from-dir'] as string | undefined,\n fromPath: args.flags['from-path'] as string | undefined,\n env: args.env,\n },\n await harnessContractSources(project.dir, args.env),\n );\n const { contract, origin } = await resolveContract(\n sources,\n integration,\n version,\n );\n\n const existing = project.integrations.find((u) => u.name === integration);\n if (existing && existing.version !== version) {\n throw invalidStateError(\n `Integration \"${integration}\" is already pinned at ${existing.version}.`,\n `Move it to ${version} by editing its \"version\" in ekanos.json, then run \"ekanos sync\".`,\n );\n }\n\n const requested = ((args.flags.ops as string | undefined) ?? '')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const explicit = resolveOpTokens(contract, requested);\n const operations = inContractOrder(contract, [\n ...(existing ? existing.operations : readOperationIds(contract)),\n ...explicit,\n ]);\n if (operations.length === 0) {\n throw usageError(\n `${integration}@${version} has no read operations to allow by default.`,\n 'Pass --ops <tool/name,...> to choose operations.',\n );\n }\n\n const entry: IntegrationEntry = {\n name: integration,\n version,\n operations,\n required: existing?.required ?? 'required',\n modes: existing?.modes ?? ['team'],\n };\n const integrations = existing\n ? project.integrations.map((u) => (u.name === integration ? entry : u))\n : [...project.integrations, entry];\n const integrationsChanged = writeIntegrations(project, integrations);\n\n const updated = { ...project, integrations };\n const pins = pinIntegration(updated, entry, contract);\n const { pinned } = loadPins(updated);\n const typesChanged = regenerateIntegrations(updated, pinned);\n\n const changed =\n integrationsChanged ||\n pins.snapshotChanged ||\n pins.lockChanged ||\n typesChanged;\n const writes = operations.filter((id) =>\n contract.operations.find((op) => op.id === id && op.effect === 'write'),\n );\n\n return ctx.succeed(\n {\n integration,\n version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n operations,\n writes,\n origin,\n changed,\n files: {\n integrationsChanged,\n snapshotChanged: pins.snapshotChanged,\n lockChanged: pins.lockChanged,\n typesChanged,\n },\n },\n changed\n ? `Added integration \"${integration}\" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`\n : `Integration \"${integration}\" is already up to date (no files changed).`,\n );\n },\n};\n"]}
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IntegrationEntry } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { invalidStateError, usageError } from '../errors';\nimport {\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport {\n inContractOrder,\n readOperationIds,\n resolveOpTokens,\n} from '../experience/pin-ops';\nimport { DEPRECATED_USES } from '../experience/pins';\nimport {\n readExperienceProject,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport type { ExperienceCommand } from './index-experience';\n\nconst SPEC =\n /^((?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\\/)?[a-z][a-z0-9-]*)@(\\d+(?:\\.\\d+)?)$/;\n\nexport const addCommand: ExperienceCommand = {\n name: 'add',\n usage: [\n ' add Add an integration to an experience: pin its contract,',\n ' record it in ekanos.json + ekanos.lock.json, regenerate types',\n ' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',\n ],\n positionals: ['spec'],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n ops: {\n type: 'string',\n description:\n 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',\n },\n as: {\n type: 'string',\n description:\n 'Removed: an experience addresses an integration by its own name',\n },\n 'from-dir': {\n type: 'string',\n description:\n 'Directory of <integration>.contract.json files (the contract source)',\n },\n 'from-path': {\n type: 'string',\n description:\n 'An integration project directory whose contract.json to pin',\n },\n },\n async run(ctx, args) {\n const spec = args.positionals[0];\n const match = spec ? SPEC.exec(spec) : null;\n if (!match) {\n throw usageError(\n spec\n ? `\"${spec}\" is not <integration>@<version>.`\n : 'add needs <integration>@<version>.',\n 'e.g. \"ekanos add sanity@0.2 --from-dir ../contracts\".',\n );\n }\n const [, integration, version] = match as unknown as [\n string,\n string,\n string,\n ];\n if (args.flags.as !== undefined) {\n throw usageError(\n '--as is gone: an experience addresses an integration by its own name.',\n `Use \"${integration}\" in useOperation('${integration}', ...).`,\n );\n }\n\n const project = readExperienceProject(args.dir);\n if (project.deprecatedUses) ctx.warn(DEPRECATED_USES);\n const sources = defaultSources(\n project,\n {\n fromDir: args.flags['from-dir'] as string | undefined,\n fromPath: args.flags['from-path'] as string | undefined,\n env: args.env,\n },\n await harnessContractSources(project.dir, args.env),\n );\n const { contract, origin } = await resolveContract(\n sources,\n integration,\n version,\n );\n\n const existing = project.integrations.find((u) => u.name === integration);\n if (existing && existing.version !== version) {\n throw invalidStateError(\n `Integration \"${integration}\" is already pinned at ${existing.version}.`,\n `Move it to ${version} by editing its \"version\" in ekanos.json, then run \"ekanos sync\".`,\n );\n }\n\n const requested = ((args.flags.ops as string | undefined) ?? '')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const explicit = resolveOpTokens(contract, requested);\n const operations = inContractOrder(contract, [\n ...(existing ? existing.operations : readOperationIds(contract)),\n ...explicit,\n ]);\n if (operations.length === 0) {\n throw usageError(\n `${integration}@${version} has no read operations to allow by default.`,\n 'Pass --ops <tool/name,...> to choose operations.',\n );\n }\n\n const entry: IntegrationEntry = {\n name: integration,\n version,\n operations,\n required: existing?.required ?? 'required',\n modes: existing?.modes ?? ['team'],\n };\n const integrations = existing\n ? project.integrations.map((u) => (u.name === integration ? entry : u))\n : [...project.integrations, entry];\n const integrationsChanged = writeIntegrations(project, integrations);\n\n const updated = { ...project, integrations };\n const pins = pinIntegration(updated, entry, contract);\n const { pinned } = loadPins(updated);\n const typesChanged = regenerateIntegrations(updated, pinned);\n\n const changed =\n integrationsChanged ||\n pins.snapshotChanged ||\n pins.lockChanged ||\n typesChanged;\n const writes = operations.filter((id) =>\n contract.operations.find((op) => op.id === id && op.effect === 'write'),\n );\n\n return ctx.succeed(\n {\n integration,\n version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n operations,\n writes,\n origin,\n changed,\n files: {\n integrationsChanged,\n snapshotChanged: pins.snapshotChanged,\n lockChanged: pins.lockChanged,\n typesChanged,\n },\n },\n changed\n ? `Added integration \"${integration}\" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`\n : `Integration \"${integration}\" is already up to date (no files changed).`,\n );\n },\n};\n"]}
@@ -87,9 +87,9 @@ export const syncCommand = {
87
87
  const contract = candidate.get(use.name);
88
88
  if (!contract)
89
89
  return [];
90
- const have = new Set(contract.operations.map((op) => op.id));
90
+ const have = new Set(contract.operations.map((op) => shortOperation(op.id)));
91
91
  return use.operations
92
- .filter((id) => !have.has(id))
92
+ .filter((id) => !have.has(shortOperation(id)))
93
93
  .map((id) => ({
94
94
  check: 'checks/integration-pins',
95
95
  severity: 'error',
@@ -1 +1 @@
1
- {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { shortOperation } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { validationError } from '../errors';\nimport {\n type ResolvedContract,\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport { DEPRECATED_USES, renamesNotice } from '../experience/pins';\nimport {\n readExperienceProject,\n readLockfile,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const syncCommand: ExperienceCommand = {\n name: 'sync',\n usage: [\n \" sync Re-resolve every integration's contract, accept a new digest within the\",\n ' pinned line, and regenerate the lock + types (--check: report only)',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n check: {\n type: 'boolean',\n description:\n 'Report drift without writing anything; exit 3 when there is some',\n },\n 'from-dir': {\n type: 'string',\n description: 'Directory of contract files to resolve from',\n },\n 'contracts-dir': {\n type: 'string',\n description:\n 'Contracts directory the harness resolves from (same as \"ekanos dev --contracts-dir\"; default: EKANOS_CONTRACTS_DIR)',\n },\n 'from-path': {\n type: 'string',\n description: 'An integration project directory to resolve from',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const checkOnly = args.flags.check === true;\n const fromDir = args.flags['from-dir'] as string | undefined;\n const fromPath = args.flags['from-path'] as string | undefined;\n const contractsDir = args.flags['contracts-dir'] as string | undefined;\n const explicit = Boolean(\n fromDir ?? fromPath ?? contractsDir ?? args.env.EKANOS_CONTRACTS_DIR,\n );\n // The harness's own registry chain (session catalog, published-package\n // cache, contracts directory, then the committed snapshot) is what\n // `ekanos dev` resolves with, so it is what decides drift here too.\n const sources = defaultSources(\n project,\n { fromDir, fromPath, env: args.env },\n await harnessContractSources(project.dir, args.env, {\n ...((contractsDir ?? fromDir)\n ? { contractsDir: (contractsDir ?? fromDir) as string }\n : {}),\n onUnavailable: (reason) =>\n ctx.warn(\n `Could not ask the project's @ekanos/harness for contracts (${reason}); resolving from the committed snapshots only.`,\n ),\n }),\n );\n\n if (project.deprecatedUses) {\n ctx.warn(DEPRECATED_USES);\n const notice = renamesNotice(project.renames);\n if (notice) ctx.warn(notice);\n }\n\n const lock = readLockfile(project);\n const resolved = new Map<string, ResolvedContract>();\n const drift: string[] = [];\n const consulted = new Set<ResolvedContract['kind'] | 'harness'>();\n for (const use of project.integrations) {\n const hit = await resolveContract(sources, use.name, use.version);\n resolved.set(use.name, hit);\n consulted.add(isSnapshotOnly(hit) ? 'snapshot' : 'harness');\n const locked = lock.integrations[use.name];\n if (!locked || locked.digest !== hit.contract.digest) {\n drift.push(\n `${use.name}: ${locked ? `${locked.contractVersion} -> ${hit.contract.contractVersion}` : `new pin ${hit.contract.contractVersion}`}`,\n );\n }\n }\n\n const snapshotOnly = !explicit && !consulted.has('harness');\n const snapshotNotice =\n 'Only the committed snapshots were consulted (no contracts directory and nothing newer from the harness registry), so a running \"ekanos dev\" that resolves elsewhere can still see a drifted digest. Pass --contracts-dir <dir> or set EKANOS_CONTRACTS_DIR, as you do for \"ekanos dev\".';\n\n if (checkOnly) {\n if (drift.length > 0) {\n throw validationError(\n `${drift.length} integration${drift.length === 1 ? '' : 's'} drifted: ${drift.join('; ')}.`,\n 'Run \"ekanos sync\" to accept the new contracts.',\n );\n }\n\n if (snapshotOnly) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n { drift: [], changed: false, snapshotOnly },\n snapshotOnly\n ? 'All integrations match their committed snapshots (no other contract source was consulted).'\n : 'All integrations match their pinned contracts.',\n );\n }\n\n // Judge the incoming contracts before writing any of it: a new contract\n // that drops a declared operation must not silently replace the old pin.\n const candidate = new Map(\n [...resolved].map(([name, hit]) => [name, hit.contract]),\n );\n const incoming = project.integrations.flatMap((use) => {\n const contract = candidate.get(use.name);\n if (!contract) return [];\n const have = new Set(contract.operations.map((op) => op.id));\n\n return use.operations\n .filter((id) => !have.has(id))\n .map((id) => ({\n check: 'checks/integration-pins',\n severity: 'error' as const,\n message: `integration \"${use.name}\": ${use.name}@${use.version} (contract ${contract.contractVersion}) no longer provides ${shortOperation(id)}.`,\n hint: 'Remove the operation from \"integrations\", or keep the older pinned contract.',\n file: 'ekanos.json',\n }));\n });\n const failed = failOnErrors(ctx, 'sync', incoming, { drift }, project.dir);\n if (failed !== null) return failed;\n\n let changed = false;\n if (project.deprecatedUses) {\n changed = writeIntegrations(project, project.integrations) || changed;\n }\n for (const use of project.integrations) {\n const hit = resolved.get(use.name);\n if (!hit) continue;\n const result = pinIntegration(project, use, hit.contract);\n changed = changed || result.snapshotChanged || result.lockChanged;\n }\n const { pinned } = loadPins(project);\n const typesChanged = regenerateIntegrations(project, pinned);\n changed = changed || typesChanged;\n\n if (snapshotOnly && drift.length === 0) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n {\n drift,\n changed,\n snapshotOnly,\n explicitSource: explicit,\n migratedUses: project.deprecatedUses,\n renames: project.renames,\n integrations: project.integrations.map((u) => ({\n integration: u.name,\n contractVersion: resolved.get(u.name)?.contract.contractVersion,\n digest: resolved.get(u.name)?.contract.digest,\n origin: resolved.get(u.name)?.origin,\n })),\n },\n drift.length > 0\n ? `Synced ${project.integrations.length} integration${project.integrations.length === 1 ? '' : 's'}; accepted: ${drift.join('; ')}.`\n : changed\n ? 'Synced; lock or types were regenerated.'\n : snapshotOnly\n ? 'Nothing to sync against the committed snapshots; no other contract source was consulted.'\n : 'Nothing to sync: every integration matches its pinned contract.',\n );\n },\n};\n\n/**\n * The harness's registry source falls back to the committed snapshot when it\n * finds nothing newer, and says so in its origin (\"snapshot <dir>\"). A hit that\n * is only that fallback proves nothing about the registry.\n */\nfunction isSnapshotOnly(hit: ResolvedContract): boolean {\n return hit.kind === 'snapshot' || hit.origin.startsWith('snapshot ');\n}\n"]}
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { shortOperation } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { validationError } from '../errors';\nimport {\n type ResolvedContract,\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport { DEPRECATED_USES, renamesNotice } from '../experience/pins';\nimport {\n readExperienceProject,\n readLockfile,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const syncCommand: ExperienceCommand = {\n name: 'sync',\n usage: [\n \" sync Re-resolve every integration's contract, accept a new digest within the\",\n ' pinned line, and regenerate the lock + types (--check: report only)',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n check: {\n type: 'boolean',\n description:\n 'Report drift without writing anything; exit 3 when there is some',\n },\n 'from-dir': {\n type: 'string',\n description: 'Directory of contract files to resolve from',\n },\n 'contracts-dir': {\n type: 'string',\n description:\n 'Contracts directory the harness resolves from (same as \"ekanos dev --contracts-dir\"; default: EKANOS_CONTRACTS_DIR)',\n },\n 'from-path': {\n type: 'string',\n description: 'An integration project directory to resolve from',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const checkOnly = args.flags.check === true;\n const fromDir = args.flags['from-dir'] as string | undefined;\n const fromPath = args.flags['from-path'] as string | undefined;\n const contractsDir = args.flags['contracts-dir'] as string | undefined;\n const explicit = Boolean(\n fromDir ?? fromPath ?? contractsDir ?? args.env.EKANOS_CONTRACTS_DIR,\n );\n // The harness's own registry chain (session catalog, published-package\n // cache, contracts directory, then the committed snapshot) is what\n // `ekanos dev` resolves with, so it is what decides drift here too.\n const sources = defaultSources(\n project,\n { fromDir, fromPath, env: args.env },\n await harnessContractSources(project.dir, args.env, {\n ...((contractsDir ?? fromDir)\n ? { contractsDir: (contractsDir ?? fromDir) as string }\n : {}),\n onUnavailable: (reason) =>\n ctx.warn(\n `Could not ask the project's @ekanos/harness for contracts (${reason}); resolving from the committed snapshots only.`,\n ),\n }),\n );\n\n if (project.deprecatedUses) {\n ctx.warn(DEPRECATED_USES);\n const notice = renamesNotice(project.renames);\n if (notice) ctx.warn(notice);\n }\n\n const lock = readLockfile(project);\n const resolved = new Map<string, ResolvedContract>();\n const drift: string[] = [];\n const consulted = new Set<ResolvedContract['kind'] | 'harness'>();\n for (const use of project.integrations) {\n const hit = await resolveContract(sources, use.name, use.version);\n resolved.set(use.name, hit);\n consulted.add(isSnapshotOnly(hit) ? 'snapshot' : 'harness');\n const locked = lock.integrations[use.name];\n if (!locked || locked.digest !== hit.contract.digest) {\n drift.push(\n `${use.name}: ${locked ? `${locked.contractVersion} -> ${hit.contract.contractVersion}` : `new pin ${hit.contract.contractVersion}`}`,\n );\n }\n }\n\n const snapshotOnly = !explicit && !consulted.has('harness');\n const snapshotNotice =\n 'Only the committed snapshots were consulted (no contracts directory and nothing newer from the harness registry), so a running \"ekanos dev\" that resolves elsewhere can still see a drifted digest. Pass --contracts-dir <dir> or set EKANOS_CONTRACTS_DIR, as you do for \"ekanos dev\".';\n\n if (checkOnly) {\n if (drift.length > 0) {\n throw validationError(\n `${drift.length} integration${drift.length === 1 ? '' : 's'} drifted: ${drift.join('; ')}.`,\n 'Run \"ekanos sync\" to accept the new contracts.',\n );\n }\n\n if (snapshotOnly) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n { drift: [], changed: false, snapshotOnly },\n snapshotOnly\n ? 'All integrations match their committed snapshots (no other contract source was consulted).'\n : 'All integrations match their pinned contracts.',\n );\n }\n\n // Judge the incoming contracts before writing any of it: a new contract\n // that drops a declared operation must not silently replace the old pin.\n const candidate = new Map(\n [...resolved].map(([name, hit]) => [name, hit.contract]),\n );\n const incoming = project.integrations.flatMap((use) => {\n const contract = candidate.get(use.name);\n if (!contract) return [];\n const have = new Set(\n contract.operations.map((op) => shortOperation(op.id)),\n );\n\n return use.operations\n .filter((id) => !have.has(shortOperation(id)))\n .map((id) => ({\n check: 'checks/integration-pins',\n severity: 'error' as const,\n message: `integration \"${use.name}\": ${use.name}@${use.version} (contract ${contract.contractVersion}) no longer provides ${shortOperation(id)}.`,\n hint: 'Remove the operation from \"integrations\", or keep the older pinned contract.',\n file: 'ekanos.json',\n }));\n });\n const failed = failOnErrors(ctx, 'sync', incoming, { drift }, project.dir);\n if (failed !== null) return failed;\n\n let changed = false;\n if (project.deprecatedUses) {\n changed = writeIntegrations(project, project.integrations) || changed;\n }\n for (const use of project.integrations) {\n const hit = resolved.get(use.name);\n if (!hit) continue;\n const result = pinIntegration(project, use, hit.contract);\n changed = changed || result.snapshotChanged || result.lockChanged;\n }\n const { pinned } = loadPins(project);\n const typesChanged = regenerateIntegrations(project, pinned);\n changed = changed || typesChanged;\n\n if (snapshotOnly && drift.length === 0) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n {\n drift,\n changed,\n snapshotOnly,\n explicitSource: explicit,\n migratedUses: project.deprecatedUses,\n renames: project.renames,\n integrations: project.integrations.map((u) => ({\n integration: u.name,\n contractVersion: resolved.get(u.name)?.contract.contractVersion,\n digest: resolved.get(u.name)?.contract.digest,\n origin: resolved.get(u.name)?.origin,\n })),\n },\n drift.length > 0\n ? `Synced ${project.integrations.length} integration${project.integrations.length === 1 ? '' : 's'}; accepted: ${drift.join('; ')}.`\n : changed\n ? 'Synced; lock or types were regenerated.'\n : snapshotOnly\n ? 'Nothing to sync against the committed snapshots; no other contract source was consulted.'\n : 'Nothing to sync: every integration matches its pinned contract.',\n );\n },\n};\n\n/**\n * The harness's registry source falls back to the committed snapshot when it\n * finds nothing newer, and says so in its origin (\"snapshot <dir>\"). A hit that\n * is only that fallback proves nothing about the registry.\n */\nfunction isSnapshotOnly(hit: ResolvedContract): boolean {\n return hit.kind === 'snapshot' || hit.origin.startsWith('snapshot ');\n}\n"]}
@@ -22,11 +22,16 @@ export interface SourceOptions {
22
22
  fromPath?: string;
23
23
  env?: Record<string, string | undefined>;
24
24
  }
25
- /** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */
25
+ /**
26
+ * Every `*.contract.json` of `dir` for `integration` (a reference: `slug` or
27
+ * `ns/slug`; files are named by its token), highest contract version within
28
+ * `line` first. A namespaced reference never matches a global file of the same
29
+ * slug, nor the reverse.
30
+ */
26
31
  export declare function contractsInDir(dir: string, integration: string, line: string): Contract[];
27
32
  export declare function dirSource(dir: string): ContractSource;
28
33
  export declare function pathSource(projectPath: string): ContractSource;
29
- /** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */
34
+ /** The committed pin: `<contractsDir>/<token>@<version>.contract.json`, always flat. */
30
35
  export declare function snapshotFile(project: ExperienceProject, integration: string, line: string): string;
31
36
  export declare function snapshotSource(project: ExperienceProject): ContractSource;
32
37
  /** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */
@@ -1,4 +1,4 @@
1
- import { compareSemver, compatLine, verifyContract, } from '@ekanos/integration-schema';
1
+ import { compareSemver, compatLine, contractMatchesRef, integrationRefToken, snapshotFileName, verifyContract, } from '@ekanos/integration-schema';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { notFoundError, validationError } from '../errors.js';
@@ -17,8 +17,16 @@ function readContractFile(file) {
17
17
  }
18
18
  return verified.contract;
19
19
  }
20
- /** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */
20
+ /**
21
+ * Every `*.contract.json` of `dir` for `integration` (a reference: `slug` or
22
+ * `ns/slug`; files are named by its token), highest contract version within
23
+ * `line` first. A namespaced reference never matches a global file of the same
24
+ * slug, nor the reverse.
25
+ */
21
26
  export function contractsInDir(dir, integration, line) {
27
+ const token = integrationRefToken(integration);
28
+ if (token === null)
29
+ return [];
22
30
  let names;
23
31
  try {
24
32
  names = fs.readdirSync(dir).filter((n) => n.endsWith('.contract.json'));
@@ -28,13 +36,12 @@ export function contractsInDir(dir, integration, line) {
28
36
  }
29
37
  const found = [];
30
38
  for (const name of names) {
31
- if (!name.startsWith(`${integration}@`) &&
32
- !name.startsWith(`${integration}.`)) {
39
+ if (!name.startsWith(`${token}@`) && !name.startsWith(`${token}.`)) {
33
40
  continue;
34
41
  }
35
42
  const contract = readContractFile(path.join(dir, name));
36
43
  if (contract &&
37
- contract.integration === integration &&
44
+ contractMatchesRef(contract, integration) &&
38
45
  contract.compatLine === line) {
39
46
  found.push(contract);
40
47
  }
@@ -60,7 +67,7 @@ export function pathSource(projectPath) {
60
67
  }
61
68
  const contract = readContractFile(file);
62
69
  if (!contract ||
63
- contract.integration !== integration ||
70
+ !contractMatchesRef(contract, integration) ||
64
71
  contract.compatLine !== line) {
65
72
  return null;
66
73
  }
@@ -68,9 +75,9 @@ export function pathSource(projectPath) {
68
75
  },
69
76
  };
70
77
  }
71
- /** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */
78
+ /** The committed pin: `<contractsDir>/<token>@<version>.contract.json`, always flat. */
72
79
  export function snapshotFile(project, integration, line) {
73
- return path.join(project.contractsDir, `${integration}@${line}.contract.json`);
80
+ return path.join(project.contractsDir, snapshotFileName(integration, line));
74
81
  }
75
82
  export function snapshotSource(project) {
76
83
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"contract-sources.js","sourceRoot":"","sources":["../../src/experience/contract-sources.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Contract,\n compareSemver,\n compatLine,\n verifyContract,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { notFoundError, validationError } from '../errors';\nimport type { ExperienceProject } from './project';\n\n/** Where an integration's contract came from, for messages and `sync` bookkeeping. */\nexport interface ResolvedContract {\n contract: Contract;\n origin: string;\n kind: 'dir' | 'path' | 'snapshot';\n}\n\n/**\n * A pluggable contract source. The dir/path/snapshot sources below are the\n * offline defaults; the harness resolver (catalog, published packages) plugs\n * in here as another source, so `add`/`sync` never learn where contracts live.\n */\nexport interface ContractSource {\n name: string;\n resolve(\n integration: string,\n line: string,\n ): Promise<ResolvedContract | null> | ResolvedContract | null;\n}\n\nexport interface SourceOptions {\n /** A directory of `*.contract.json` (the monorepo contracts folder or a registry mirror). */\n fromDir?: string;\n /** An integration project directory; its `contract.json` is read. */\n fromPath?: string;\n env?: Record<string, string | undefined>;\n}\n\nfunction readContractFile(file: string): Contract | null {\n let json: unknown;\n try {\n json = JSON.parse(fs.readFileSync(file, 'utf8'));\n } catch {\n return null;\n }\n const verified = verifyContract(json);\n if (!verified.contract || verified.problems.length > 0) {\n const first = verified.problems[0];\n throw validationError(\n `${file} is not a valid contract: ${first ? `${first.rule}: ${first.message}` : 'unreadable'}`,\n 'Regenerate it with \"ekanos contract\" in the integration, or point at a valid contracts directory.',\n );\n }\n\n return verified.contract;\n}\n\n/** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */\nexport function contractsInDir(\n dir: string,\n integration: string,\n line: string,\n): Contract[] {\n let names: string[];\n try {\n names = fs.readdirSync(dir).filter((n) => n.endsWith('.contract.json'));\n } catch {\n return [];\n }\n const found: Contract[] = [];\n for (const name of names) {\n if (\n !name.startsWith(`${integration}@`) &&\n !name.startsWith(`${integration}.`)\n ) {\n continue;\n }\n const contract = readContractFile(path.join(dir, name));\n if (\n contract &&\n contract.integration === integration &&\n contract.compatLine === line\n ) {\n found.push(contract);\n }\n }\n\n return found.sort((a, b) =>\n compareSemver(b.contractVersion, a.contractVersion),\n );\n}\n\nexport function dirSource(dir: string): ContractSource {\n return {\n name: `dir ${dir}`,\n resolve(integration, line) {\n const [best] = contractsInDir(dir, integration, line);\n\n return best ? { contract: best, origin: dir, kind: 'dir' } : null;\n },\n };\n}\n\nexport function pathSource(projectPath: string): ContractSource {\n return {\n name: `path ${projectPath}`,\n resolve(integration, line) {\n const file = path.join(path.resolve(projectPath), 'contract.json');\n if (!fs.existsSync(file)) {\n throw notFoundError(\n `${file} does not exist.`,\n 'Run \"ekanos contract\" in that integration to derive its contract.json.',\n );\n }\n const contract = readContractFile(file);\n if (\n !contract ||\n contract.integration !== integration ||\n contract.compatLine !== line\n ) {\n return null;\n }\n\n return { contract, origin: file, kind: 'path' };\n },\n };\n}\n\n/** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */\nexport function snapshotFile(\n project: ExperienceProject,\n integration: string,\n line: string,\n): string {\n return path.join(\n project.contractsDir,\n `${integration}@${line}.contract.json`,\n );\n}\n\nexport function snapshotSource(project: ExperienceProject): ContractSource {\n return {\n name: 'committed snapshot',\n resolve(integration, line) {\n const file = snapshotFile(project, integration, line);\n const contract = fs.existsSync(file) ? readContractFile(file) : null;\n\n return contract ? { contract, origin: file, kind: 'snapshot' } : null;\n },\n };\n}\n\n/** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */\nexport function defaultSources(\n project: ExperienceProject,\n options: SourceOptions,\n extra: readonly ContractSource[] = [],\n includeSnapshot = true,\n): ContractSource[] {\n const sources: ContractSource[] = [];\n if (options.fromPath) sources.push(pathSource(options.fromPath));\n if (options.fromDir) sources.push(dirSource(path.resolve(options.fromDir)));\n const envDir = options.env?.EKANOS_CONTRACTS_DIR;\n if (!options.fromDir && envDir) sources.push(dirSource(path.resolve(envDir)));\n sources.push(...extra);\n if (includeSnapshot) sources.push(snapshotSource(project));\n\n return sources;\n}\n\nexport async function resolveContract(\n sources: readonly ContractSource[],\n integration: string,\n line: string,\n): Promise<ResolvedContract> {\n for (const source of sources) {\n const hit = await source.resolve(integration, line);\n if (hit) return hit;\n }\n\n throw notFoundError(\n `No contract for ${integration}@${line} (looked in: ${sources.map((s) => s.name).join(', ') || 'nothing'}).`,\n 'Pass --from-dir <contracts folder> or --from-path <integration project>, or set EKANOS_CONTRACTS_DIR.',\n );\n}\n\n/** Line for a package version, re-exported so callers need no schema import. */\nexport { compatLine };\n"]}
1
+ {"version":3,"file":"contract-sources.js","sourceRoot":"","sources":["../../src/experience/contract-sources.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AA+B3D,SAAS,gBAAgB,CAAC,IAAY;IACpC,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC;eAAO,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,eAAe,CACnB,GAAG,IAAI,6BAA6B,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,EAC9F,mGAAmG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC,QAAQ,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,WAAmB,EACnB,IAAY;IAEZ,MAAM,KAAK,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC1E,CAAC;eAAO,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QACxD,IACE,QAAQ;YACR,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC;YACzC,QAAQ,CAAC,UAAU,KAAK,IAAI,EAC5B,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACzB,aAAa,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,eAAe,CAAC,CACpD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO;QACL,IAAI,EAAE,OAAO,GAAG,EAAE;QAClB,OAAO,CAAC,WAAW,EAAE,IAAI;YACvB,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;YAEtD,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACpE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC5C,OAAO;QACL,IAAI,EAAE,QAAQ,WAAW,EAAE;QAC3B,OAAO,CAAC,WAAW,EAAE,IAAI;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC,CAAC;YACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,MAAM,aAAa,CACjB,GAAG,IAAI,kBAAkB,EACzB,wEAAwE,CACzE,CAAC;YACJ,CAAC;YACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACxC,IACE,CAAC,QAAQ;gBACT,CAAC,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC;gBAC1C,QAAQ,CAAC,UAAU,KAAK,IAAI,EAC5B,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAC1B,OAA0B,EAC1B,WAAmB,EACnB,IAAY;IAEZ,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAA0B;IACvD,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,OAAO,CAAC,WAAW,EAAE,IAAI;YACvB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;YACtD,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAErE,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,cAAc,CAC5B,OAA0B,EAC1B,OAAsB,EACtB,KAAK,GAA8B,EAAE,EACrC,eAAe,GAAG,IAAI;;IAEtB,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,IAAI,OAAO,CAAC,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,MAAA,OAAO,CAAC,GAAG,0CAAE,oBAAoB,CAAC;IACjD,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9E,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IACvB,IAAI,eAAe;QAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAE3D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAkC,EAClC,WAAmB,EACnB,IAAY;IAEZ,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACpD,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACtB,CAAC;IAED,MAAM,aAAa,CACjB,mBAAmB,WAAW,IAAI,IAAI,gBAAgB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,IAAI,EAC5G,uGAAuG,CACxG,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,OAAO,EAAE,UAAU,EAAE,CAAC","sourcesContent":["import {\n type Contract,\n compareSemver,\n compatLine,\n contractMatchesRef,\n integrationRefToken,\n snapshotFileName,\n verifyContract,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { notFoundError, validationError } from '../errors';\nimport type { ExperienceProject } from './project';\n\n/** Where an integration's contract came from, for messages and `sync` bookkeeping. */\nexport interface ResolvedContract {\n contract: Contract;\n origin: string;\n kind: 'dir' | 'path' | 'snapshot';\n}\n\n/**\n * A pluggable contract source. The dir/path/snapshot sources below are the\n * offline defaults; the harness resolver (catalog, published packages) plugs\n * in here as another source, so `add`/`sync` never learn where contracts live.\n */\nexport interface ContractSource {\n name: string;\n resolve(\n integration: string,\n line: string,\n ): Promise<ResolvedContract | null> | ResolvedContract | null;\n}\n\nexport interface SourceOptions {\n /** A directory of `*.contract.json` (the monorepo contracts folder or a registry mirror). */\n fromDir?: string;\n /** An integration project directory; its `contract.json` is read. */\n fromPath?: string;\n env?: Record<string, string | undefined>;\n}\n\nfunction readContractFile(file: string): Contract | null {\n let json: unknown;\n try {\n json = JSON.parse(fs.readFileSync(file, 'utf8'));\n } catch {\n return null;\n }\n const verified = verifyContract(json);\n if (!verified.contract || verified.problems.length > 0) {\n const first = verified.problems[0];\n throw validationError(\n `${file} is not a valid contract: ${first ? `${first.rule}: ${first.message}` : 'unreadable'}`,\n 'Regenerate it with \"ekanos contract\" in the integration, or point at a valid contracts directory.',\n );\n }\n\n return verified.contract;\n}\n\n/**\n * Every `*.contract.json` of `dir` for `integration` (a reference: `slug` or\n * `ns/slug`; files are named by its token), highest contract version within\n * `line` first. A namespaced reference never matches a global file of the same\n * slug, nor the reverse.\n */\nexport function contractsInDir(\n dir: string,\n integration: string,\n line: string,\n): Contract[] {\n const token = integrationRefToken(integration);\n if (token === null) return [];\n let names: string[];\n try {\n names = fs.readdirSync(dir).filter((n) => n.endsWith('.contract.json'));\n } catch {\n return [];\n }\n const found: Contract[] = [];\n for (const name of names) {\n if (!name.startsWith(`${token}@`) && !name.startsWith(`${token}.`)) {\n continue;\n }\n const contract = readContractFile(path.join(dir, name));\n if (\n contract &&\n contractMatchesRef(contract, integration) &&\n contract.compatLine === line\n ) {\n found.push(contract);\n }\n }\n\n return found.sort((a, b) =>\n compareSemver(b.contractVersion, a.contractVersion),\n );\n}\n\nexport function dirSource(dir: string): ContractSource {\n return {\n name: `dir ${dir}`,\n resolve(integration, line) {\n const [best] = contractsInDir(dir, integration, line);\n\n return best ? { contract: best, origin: dir, kind: 'dir' } : null;\n },\n };\n}\n\nexport function pathSource(projectPath: string): ContractSource {\n return {\n name: `path ${projectPath}`,\n resolve(integration, line) {\n const file = path.join(path.resolve(projectPath), 'contract.json');\n if (!fs.existsSync(file)) {\n throw notFoundError(\n `${file} does not exist.`,\n 'Run \"ekanos contract\" in that integration to derive its contract.json.',\n );\n }\n const contract = readContractFile(file);\n if (\n !contract ||\n !contractMatchesRef(contract, integration) ||\n contract.compatLine !== line\n ) {\n return null;\n }\n\n return { contract, origin: file, kind: 'path' };\n },\n };\n}\n\n/** The committed pin: `<contractsDir>/<token>@<version>.contract.json`, always flat. */\nexport function snapshotFile(\n project: ExperienceProject,\n integration: string,\n line: string,\n): string {\n return path.join(project.contractsDir, snapshotFileName(integration, line));\n}\n\nexport function snapshotSource(project: ExperienceProject): ContractSource {\n return {\n name: 'committed snapshot',\n resolve(integration, line) {\n const file = snapshotFile(project, integration, line);\n const contract = fs.existsSync(file) ? readContractFile(file) : null;\n\n return contract ? { contract, origin: file, kind: 'snapshot' } : null;\n },\n };\n}\n\n/** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */\nexport function defaultSources(\n project: ExperienceProject,\n options: SourceOptions,\n extra: readonly ContractSource[] = [],\n includeSnapshot = true,\n): ContractSource[] {\n const sources: ContractSource[] = [];\n if (options.fromPath) sources.push(pathSource(options.fromPath));\n if (options.fromDir) sources.push(dirSource(path.resolve(options.fromDir)));\n const envDir = options.env?.EKANOS_CONTRACTS_DIR;\n if (!options.fromDir && envDir) sources.push(dirSource(path.resolve(envDir)));\n sources.push(...extra);\n if (includeSnapshot) sources.push(snapshotSource(project));\n\n return sources;\n}\n\nexport async function resolveContract(\n sources: readonly ContractSource[],\n integration: string,\n line: string,\n): Promise<ResolvedContract> {\n for (const source of sources) {\n const hit = await source.resolve(integration, line);\n if (hit) return hit;\n }\n\n throw notFoundError(\n `No contract for ${integration}@${line} (looked in: ${sources.map((s) => s.name).join(', ') || 'nothing'}).`,\n 'Pass --from-dir <contracts folder> or --from-path <integration project>, or set EKANOS_CONTRACTS_DIR.',\n );\n}\n\n/** Line for a package version, re-exported so callers need no schema import. */\nexport { compatLine };\n"]}
@@ -95,9 +95,15 @@ export function readExperienceProject(dir) {
95
95
  raw: record,
96
96
  };
97
97
  }
98
- /** Operations as canonical `<integration>@<version>/<kind>/<name>` ids, for lookups against a contract. */
98
+ /**
99
+ * Operations as canonical `<slug>@<version>/<kind>/<name>` ids, for lookups
100
+ * against a contract. A contract's ids carry the bare slug even when the
101
+ * package is named under a namespace, so a `vastly/news` entry canonicalises to
102
+ * `news@0.1/...`.
103
+ */
99
104
  function withCanonicalOperations(entry) {
100
- return Object.assign(Object.assign({}, entry), { operations: entry.operations.map((id) => canonicalOperationId(entry, id)) });
105
+ const slug = entry.name.slice(entry.name.indexOf('/') + 1);
106
+ return Object.assign(Object.assign({}, entry), { operations: entry.operations.map((id) => canonicalOperationId({ name: slug, version: entry.version }, id)) });
101
107
  }
102
108
  /** 2-space JSON with a trailing newline: the format every written file uses. */
103
109
  export function formatJson(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/experience/project.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type EkanosJson,\n EkanosJsonSchema,\n type IntegrationEntry,\n type Lockfile,\n LockfileSchema,\n canonicalOperationId,\n experienceIntegrations,\n shortOperation,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { notFoundError, validationError } from '../errors';\n\nexport const EKANOS_JSON = 'ekanos.json';\nexport const LOCK_FILE = 'ekanos.lock.json';\nexport const LOCAL_OVERRIDES_FILE = 'ekanos.local.json';\nexport const DEFAULT_CONTRACTS_DIR = 'contracts';\nexport const INTEGRATIONS_DTS = 'src/.ekanos/integrations.d.ts';\n/** The previous name of the generated file, kept as a re-export for one version. */\nexport const LEGACY_SLOTS_DTS = 'src/.ekanos/slots.d.ts';\n\nexport interface ExperienceProject {\n /** Absolute project directory. */\n dir: string;\n ekanos: EkanosJson;\n slug: string;\n entry: string;\n /** The integrations it depends on; operations are canonical ids. */\n integrations: IntegrationEntry[];\n /** True when ekanos.json still uses the deprecated `uses` key. */\n deprecatedUses: boolean;\n /** Old `uses` alias -> integration, where they differ (for the sync notice). */\n renames: Record<string, string>;\n /** Absolute directory holding the pinned contract snapshots. */\n contractsDir: string;\n /** The raw ekanos.json, key order preserved, for rewrites. */\n raw: Record<string, unknown>;\n}\n\nfunction readJson(file: string): unknown {\n let text: string;\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n throw notFoundError(\n `No ${path.basename(file)} at ${file}.`,\n 'Run this from an Ekanos project directory, or pass --dir <path>.',\n );\n }\n try {\n return JSON.parse(text);\n } catch (error) {\n throw validationError(\n `${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the syntax error and re-run.',\n );\n }\n}\n\n/** `integration` when ekanos.json is absent or says so; used to route commands. */\nexport function readProjectKind(dir: string): 'integration' | 'experience' {\n try {\n const raw = JSON.parse(\n fs.readFileSync(path.join(dir, EKANOS_JSON), 'utf8'),\n ) as { kind?: unknown };\n\n return raw.kind === 'experience' ? 'experience' : 'integration';\n } catch {\n return 'integration';\n }\n}\n\n/**\n * Read and validate an experience's ekanos.json with the shared schema.\n *\n * A fresh scaffold has no integrations yet (you add them with `ekanos add`),\n * which the shared schema rejects (\"declares at least one integration\"). That\n * one rule is suspended here so `add` can bootstrap a project; `check` and\n * `build` report the empty state themselves.\n *\n * The deprecated `uses` shape is read too: its entries come back as\n * `integrations` (alias dropped) with `deprecatedUses` set and `renames`\n * mapping each old alias to its integration, so `ekanos sync` can rewrite the\n * file and say what code to update.\n */\nexport function readExperienceProject(dir: string): ExperienceProject {\n const absolute = path.resolve(dir);\n const raw = readJson(path.join(absolute, EKANOS_JSON));\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw validationError(`${EKANOS_JSON} must be a JSON object.`, 'Fix it.');\n }\n const record = raw as Record<string, unknown>;\n\n if (record.kind !== 'experience') {\n throw validationError(\n `${EKANOS_JSON} is not an experience (\"kind\": \"experience\").`,\n 'Run experience commands in an experience project; scaffold one with \"ekanos init --kind experience --slug <slug>\".',\n );\n }\n\n const declaredNone =\n !(Array.isArray(record.integrations) && record.integrations.length > 0) &&\n !(Array.isArray(record.uses) && record.uses.length > 0);\n const probe = declaredNone\n ? { ...record, integrations: [PLACEHOLDER_INTEGRATION], uses: undefined }\n : record;\n const parsed = EkanosJsonSchema.safeParse(probe);\n if (!parsed.success) {\n throw validationError(\n `${EKANOS_JSON}: ${parsed.error.issues\n .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ')}`,\n 'Fix ekanos.json; the experience fields are slug, entry, integrations, contractsDir, allowImports, ignore.',\n );\n }\n\n const ekanos = parsed.data;\n const declared = declaredNone\n ? { entries: [], deprecatedUses: false, renames: {} }\n : experienceIntegrations(ekanos);\n\n return {\n dir: absolute,\n ekanos,\n slug: ekanos.slug as string,\n entry: ekanos.entry as string,\n integrations: declared.entries.map(withCanonicalOperations),\n deprecatedUses: declared.deprecatedUses,\n renames: declared.renames,\n contractsDir: path.resolve(\n absolute,\n ekanos.contractsDir ?? DEFAULT_CONTRACTS_DIR,\n ),\n raw: record,\n };\n}\n\n/** Operations as canonical `<integration>@<version>/<kind>/<name>` ids, for lookups against a contract. */\nfunction withCanonicalOperations(entry: IntegrationEntry): IntegrationEntry {\n return {\n ...entry,\n operations: entry.operations.map((id) => canonicalOperationId(entry, id)),\n };\n}\n\n/** 2-space JSON with a trailing newline: the format every written file uses. */\nexport function formatJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** Write only when the bytes differ. Returns whether the file changed. */\nexport function writeIfChanged(file: string, content: string): boolean {\n try {\n if (fs.readFileSync(file, 'utf8') === content) return false;\n } catch {\n // missing: write below\n }\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, content);\n\n return true;\n}\n\nexport function readLockfile(project: ExperienceProject): Lockfile {\n const file = path.join(project.dir, LOCK_FILE);\n if (!fs.existsSync(file)) return { lockfileVersion: 1, integrations: {} };\n const parsed = LockfileSchema.safeParse(readJson(file));\n if (!parsed.success) {\n throw validationError(\n `${LOCK_FILE}: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'Regenerate it with \"ekanos sync\".',\n );\n }\n\n return parsed.data;\n}\n\nexport function writeLockfile(\n project: ExperienceProject,\n lock: Lockfile,\n): boolean {\n const integrations = Object.fromEntries(\n Object.entries(lock.integrations).sort(([a], [b]) => (a < b ? -1 : 1)),\n );\n\n return writeIfChanged(\n path.join(project.dir, LOCK_FILE),\n formatJson({ lockfileVersion: 1, integrations }),\n );\n}\n\nconst PLACEHOLDER_INTEGRATION = {\n name: 'pending',\n version: '0',\n operations: ['tool/pending'],\n} as const;\n\n/**\n * Rewrite `integrations` in ekanos.json (short operation form), leaving every\n * other key where it was and dropping the deprecated `uses`.\n */\nexport function writeIntegrations(\n project: ExperienceProject,\n integrations: IntegrationEntry[],\n): boolean {\n const { uses: _uses, ...rest } = project.raw;\n const next = {\n ...rest,\n integrations: integrations.map((entry) => ({\n name: entry.name,\n version: entry.version,\n operations: entry.operations.map(shortOperation),\n ...(entry.required === 'required' ? {} : { required: entry.required }),\n ...(entry.modes.length === 1 && entry.modes[0] === 'team'\n ? {}\n : { modes: entry.modes }),\n })),\n };\n\n return writeIfChanged(path.join(project.dir, EKANOS_JSON), formatJson(next));\n}\n"]}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/experience/project.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type EkanosJson,\n EkanosJsonSchema,\n type IntegrationEntry,\n type Lockfile,\n LockfileSchema,\n canonicalOperationId,\n experienceIntegrations,\n shortOperation,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { notFoundError, validationError } from '../errors';\n\nexport const EKANOS_JSON = 'ekanos.json';\nexport const LOCK_FILE = 'ekanos.lock.json';\nexport const LOCAL_OVERRIDES_FILE = 'ekanos.local.json';\nexport const DEFAULT_CONTRACTS_DIR = 'contracts';\nexport const INTEGRATIONS_DTS = 'src/.ekanos/integrations.d.ts';\n/** The previous name of the generated file, kept as a re-export for one version. */\nexport const LEGACY_SLOTS_DTS = 'src/.ekanos/slots.d.ts';\n\nexport interface ExperienceProject {\n /** Absolute project directory. */\n dir: string;\n ekanos: EkanosJson;\n slug: string;\n entry: string;\n /** The integrations it depends on; operations are canonical ids. */\n integrations: IntegrationEntry[];\n /** True when ekanos.json still uses the deprecated `uses` key. */\n deprecatedUses: boolean;\n /** Old `uses` alias -> integration, where they differ (for the sync notice). */\n renames: Record<string, string>;\n /** Absolute directory holding the pinned contract snapshots. */\n contractsDir: string;\n /** The raw ekanos.json, key order preserved, for rewrites. */\n raw: Record<string, unknown>;\n}\n\nfunction readJson(file: string): unknown {\n let text: string;\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n throw notFoundError(\n `No ${path.basename(file)} at ${file}.`,\n 'Run this from an Ekanos project directory, or pass --dir <path>.',\n );\n }\n try {\n return JSON.parse(text);\n } catch (error) {\n throw validationError(\n `${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the syntax error and re-run.',\n );\n }\n}\n\n/** `integration` when ekanos.json is absent or says so; used to route commands. */\nexport function readProjectKind(dir: string): 'integration' | 'experience' {\n try {\n const raw = JSON.parse(\n fs.readFileSync(path.join(dir, EKANOS_JSON), 'utf8'),\n ) as { kind?: unknown };\n\n return raw.kind === 'experience' ? 'experience' : 'integration';\n } catch {\n return 'integration';\n }\n}\n\n/**\n * Read and validate an experience's ekanos.json with the shared schema.\n *\n * A fresh scaffold has no integrations yet (you add them with `ekanos add`),\n * which the shared schema rejects (\"declares at least one integration\"). That\n * one rule is suspended here so `add` can bootstrap a project; `check` and\n * `build` report the empty state themselves.\n *\n * The deprecated `uses` shape is read too: its entries come back as\n * `integrations` (alias dropped) with `deprecatedUses` set and `renames`\n * mapping each old alias to its integration, so `ekanos sync` can rewrite the\n * file and say what code to update.\n */\nexport function readExperienceProject(dir: string): ExperienceProject {\n const absolute = path.resolve(dir);\n const raw = readJson(path.join(absolute, EKANOS_JSON));\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw validationError(`${EKANOS_JSON} must be a JSON object.`, 'Fix it.');\n }\n const record = raw as Record<string, unknown>;\n\n if (record.kind !== 'experience') {\n throw validationError(\n `${EKANOS_JSON} is not an experience (\"kind\": \"experience\").`,\n 'Run experience commands in an experience project; scaffold one with \"ekanos init --kind experience --slug <slug>\".',\n );\n }\n\n const declaredNone =\n !(Array.isArray(record.integrations) && record.integrations.length > 0) &&\n !(Array.isArray(record.uses) && record.uses.length > 0);\n const probe = declaredNone\n ? { ...record, integrations: [PLACEHOLDER_INTEGRATION], uses: undefined }\n : record;\n const parsed = EkanosJsonSchema.safeParse(probe);\n if (!parsed.success) {\n throw validationError(\n `${EKANOS_JSON}: ${parsed.error.issues\n .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ')}`,\n 'Fix ekanos.json; the experience fields are slug, entry, integrations, contractsDir, allowImports, ignore.',\n );\n }\n\n const ekanos = parsed.data;\n const declared = declaredNone\n ? { entries: [], deprecatedUses: false, renames: {} }\n : experienceIntegrations(ekanos);\n\n return {\n dir: absolute,\n ekanos,\n slug: ekanos.slug as string,\n entry: ekanos.entry as string,\n integrations: declared.entries.map(withCanonicalOperations),\n deprecatedUses: declared.deprecatedUses,\n renames: declared.renames,\n contractsDir: path.resolve(\n absolute,\n ekanos.contractsDir ?? DEFAULT_CONTRACTS_DIR,\n ),\n raw: record,\n };\n}\n\n/**\n * Operations as canonical `<slug>@<version>/<kind>/<name>` ids, for lookups\n * against a contract. A contract's ids carry the bare slug even when the\n * package is named under a namespace, so a `vastly/news` entry canonicalises to\n * `news@0.1/...`.\n */\nfunction withCanonicalOperations(entry: IntegrationEntry): IntegrationEntry {\n const slug = entry.name.slice(entry.name.indexOf('/') + 1);\n\n return {\n ...entry,\n operations: entry.operations.map((id) =>\n canonicalOperationId({ name: slug, version: entry.version }, id),\n ),\n };\n}\n\n/** 2-space JSON with a trailing newline: the format every written file uses. */\nexport function formatJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** Write only when the bytes differ. Returns whether the file changed. */\nexport function writeIfChanged(file: string, content: string): boolean {\n try {\n if (fs.readFileSync(file, 'utf8') === content) return false;\n } catch {\n // missing: write below\n }\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, content);\n\n return true;\n}\n\nexport function readLockfile(project: ExperienceProject): Lockfile {\n const file = path.join(project.dir, LOCK_FILE);\n if (!fs.existsSync(file)) return { lockfileVersion: 1, integrations: {} };\n const parsed = LockfileSchema.safeParse(readJson(file));\n if (!parsed.success) {\n throw validationError(\n `${LOCK_FILE}: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'Regenerate it with \"ekanos sync\".',\n );\n }\n\n return parsed.data;\n}\n\nexport function writeLockfile(\n project: ExperienceProject,\n lock: Lockfile,\n): boolean {\n const integrations = Object.fromEntries(\n Object.entries(lock.integrations).sort(([a], [b]) => (a < b ? -1 : 1)),\n );\n\n return writeIfChanged(\n path.join(project.dir, LOCK_FILE),\n formatJson({ lockfileVersion: 1, integrations }),\n );\n}\n\nconst PLACEHOLDER_INTEGRATION = {\n name: 'pending',\n version: '0',\n operations: ['tool/pending'],\n} as const;\n\n/**\n * Rewrite `integrations` in ekanos.json (short operation form), leaving every\n * other key where it was and dropping the deprecated `uses`.\n */\nexport function writeIntegrations(\n project: ExperienceProject,\n integrations: IntegrationEntry[],\n): boolean {\n const { uses: _uses, ...rest } = project.raw;\n const next = {\n ...rest,\n integrations: integrations.map((entry) => ({\n name: entry.name,\n version: entry.version,\n operations: entry.operations.map(shortOperation),\n ...(entry.required === 'required' ? {} : { required: entry.required }),\n ...(entry.modes.length === 1 && entry.modes[0] === 'team'\n ? {}\n : { modes: entry.modes }),\n })),\n };\n\n return writeIfChanged(path.join(project.dir, EKANOS_JSON), formatJson(next));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekanos/cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "description": "The Ekanos partner toolchain CLI: scaffold, validate, and test an Ekanos integration against the published @ekanos packages. Agent-native — every verb speaks JSON with a stable exit-code taxonomy.",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@clack/prompts": "1.7.0",
38
- "@ekanos/integration-schema": "0.20.0",
38
+ "@ekanos/integration-schema": "0.21.0",
39
39
  "esbuild": "0.28.1",
40
40
  "picocolors": "1.1.1"
41
41
  },
@@ -21,16 +21,16 @@
21
21
  "build": "npx ekanos build"
22
22
  },
23
23
  "dependencies": {
24
- "@ekanos/sdk": "^0.20.0",
25
- "@ekanos/ui": "^0.20.0",
24
+ "@ekanos/sdk": "^0.21.0",
25
+ "@ekanos/ui": "^0.21.0",
26
26
  "@tanstack/react-query": "^5.101.4",
27
27
  "react": "^19.2.8",
28
28
  "react-dom": "^19.2.8",
29
29
  "zod": "^3.25.76"
30
30
  },
31
31
  "devDependencies": {
32
- "@ekanos/cli": "^0.18.0",
33
- "@ekanos/harness": "^0.20.0",
32
+ "@ekanos/cli": "^0.19.0",
33
+ "@ekanos/harness": "^0.21.0",
34
34
  "@tailwindcss/postcss": "^4.3.3",
35
35
  "@typescript-eslint/parser": "^8.46.2",
36
36
  "@types/react": "^19.2.0",
@@ -12,12 +12,12 @@
12
12
  "prepare": "node scripts/setup-hooks.mjs"
13
13
  },
14
14
  "dependencies": {
15
- "@ekanos/sdk": "^0.20.0",
16
- "@ekanos/ui": "^0.20.0"
15
+ "@ekanos/sdk": "^0.21.0",
16
+ "@ekanos/ui": "^0.21.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@ekanos/cli": "^0.18.0",
20
- "@ekanos/harness": "^0.20.0",
19
+ "@ekanos/cli": "^0.19.0",
20
+ "@ekanos/harness": "^0.21.0",
21
21
  "@hookform/resolvers": "^5.2.2",
22
22
  "@tailwindcss/postcss": "^4.3.3",
23
23
  "@tanstack/react-query": "^5.101.4",