@ekanos/cli 0.17.1 → 0.18.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.
- package/README.md +1 -0
- package/dist/commands/catalog.js +11 -7
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/names.d.ts +19 -0
- package/dist/commands/names.js +122 -0
- package/dist/commands/names.js.map +1 -0
- package/dist/commands/publish.d.ts +53 -3
- package/dist/commands/publish.js +58 -16
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/sources.js +15 -5
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +12 -12
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/teams-connect.js +1 -1
- package/dist/commands/teams-connect.js.map +1 -1
- package/dist/experience/boundary.d.ts +2 -2
- package/dist/experience/boundary.js +2 -11
- package/dist/experience/boundary.js.map +1 -1
- package/dist/experience/build.d.ts +3 -1
- package/dist/experience/build.js +15 -26
- package/dist/experience/build.js.map +1 -1
- package/dist/experience/clean-room.d.ts +40 -0
- package/dist/experience/clean-room.js +150 -0
- package/dist/experience/clean-room.js.map +1 -0
- package/dist/experience/definition-checks.d.ts +12 -1
- package/dist/experience/definition-checks.js +34 -11
- package/dist/experience/definition-checks.js.map +1 -1
- package/dist/experience/definition-shape.d.ts +20 -2
- package/dist/experience/definition-shape.js.map +1 -1
- package/dist/experience/publish.d.ts +42 -5
- package/dist/experience/publish.js +195 -11
- package/dist/experience/publish.js.map +1 -1
- package/dist/harness-scaffold.js +3 -1
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.js +25 -2
- package/dist/index.js.map +1 -1
- package/dist/names-api.d.ts +90 -0
- package/dist/names-api.js +123 -0
- package/dist/names-api.js.map +1 -0
- package/dist/product-kinds.d.ts +13 -0
- package/dist/product-kinds.js +17 -0
- package/dist/product-kinds.js.map +1 -0
- package/dist/publish-api.d.ts +38 -5
- package/dist/publish-api.js +59 -16
- package/dist/publish-api.js.map +1 -1
- package/dist/repo/publish.d.ts +6 -3
- package/dist/repo/publish.js +48 -14
- package/dist/repo/publish.js.map +1 -1
- package/dist/source-products-api.d.ts +3 -2
- package/dist/source-products-api.js +1 -1
- package/dist/source-products-api.js.map +1 -1
- package/dist/team-connect-api.d.ts +4 -0
- package/dist/team-connect-api.js +1 -1
- package/dist/team-connect-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +15 -0
- package/dist/toolchain-api.js +12 -1
- package/dist/toolchain-api.js.map +1 -1
- package/package.json +2 -2
- package/templates/experience/package.json.tmpl +7 -4
- package/templates/harness/app/experience/layout/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/layout.tsx.tmpl +3 -0
- package/templates/harness/app/experience/pages/[id]/[...path]/page.tsx.tmpl +1 -0
- package/templates/package.json.tmpl +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition-checks.js","sourceRoot":"","sources":["../../src/experience/definition-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ExperienceManifest,\n type Finding,\n type WorkflowTemplate,\n WorkflowTemplateSchema,\n canonicalJson,\n sha256Hex,\n validateAgainstSchema,\n workflowTemplateProblems,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type {\n LoadedExperienceDefinition,\n LoadedExperienceModule,\n} from './definition-shape';\nimport { declaredOperationKeys } from './integrations-codegen';\nimport { type PinnedIntegration, effectLookup } from './pins';\nimport type { ExperienceProject } from './project';\n\nexport const DEFINITION_CHECK = 'checks/experience-definition';\n\nconst err = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'error',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nconst warn = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'warn',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nexport interface DefinitionAnalysis {\n findings: Finding[];\n workflows: WorkflowTemplate[];\n}\n\n/**\n * Cross-check the loaded definition against `ekanos.json` and the pinned\n * contracts: every integration a widget, page, review section or workflow step names\n * must be declared, every operation must be in that integration's allowlist, the\n * review queue reads only, and each template serializes to the engine's DSL.\n */\nexport function analyzeDefinition(\n project: ExperienceProject,\n loaded: LoadedExperienceModule,\n pinned: readonly PinnedIntegration[],\n): DefinitionAnalysis {\n const { definition } = loaded;\n const findings: Finding[] = [];\n const workflows: WorkflowTemplate[] = [];\n const declaredIntegrations = new Map(\n project.integrations.map((u) => [u.name, u]),\n );\n const declared = new Map(\n project.integrations.map((u) => [\n u.name,\n new Set(declaredOperationKeys(u)),\n ]),\n );\n const contractOps = new Map(\n pinned.map(({ use, contract }) => [\n use.name,\n new Map(contract.operations.map((op) => [`${op.kind}/${op.name}`, op])),\n ]),\n );\n\n if (definition.slug !== project.slug) {\n findings.push(\n err(\n `the definition's slug \"${definition.slug}\" differs from ekanos.json \"${project.slug}\".`,\n 'Make them equal.',\n project.entry,\n ),\n );\n }\n\n const checkIntegration = (integration: string, owner: string) => {\n if (!declaredIntegrations.has(integration)) {\n findings.push(\n err(\n `${owner} uses integration \"${integration}\", which ekanos.json \"integrations\" does not declare.`,\n `Add it with \"ekanos add ${integration}@<version>\".`,\n project.entry,\n ),\n );\n\n return false;\n }\n\n return true;\n };\n\n for (const widget of definition.widgets) {\n for (const integration of widget.integrations)\n checkIntegration(integration, `widget \"${widget.id}\"`);\n }\n for (const page of definition.pages) {\n for (const integration of page.integrations)\n checkIntegration(integration, `page \"${page.id}\"`);\n }\n\n for (const section of definition.reviewQueue?.sections ?? []) {\n if (\n !checkIntegration(section.integration, `review section \"${section.id}\"`)\n )\n continue;\n if (!declared.get(section.integration)?.has(section.read)) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which integration \"${section.integration}\" does not declare.`,\n 'Add the operation to the integration in ekanos.json, or read another one.',\n project.entry,\n ),\n );\n } else if (\n contractOps.get(section.integration)?.get(section.read)?.effect !== 'read'\n ) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which is not a read operation.`,\n 'The review queue is reads-only; writes belong behind a workflow approval.',\n project.entry,\n ),\n );\n }\n }\n\n const effectOf = effectLookup(pinned);\n for (const template of definition.workflows) {\n const where = `workflow \"${template.id}\"`;\n let steps: ReturnType<typeof template.build>;\n try {\n steps = template.build();\n } catch (error) {\n findings.push(\n err(\n `${where}: build() threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n\n let stepsOk = true;\n for (const step of steps) {\n if (step.type !== 'tool' || !step.integration || !step.op) continue;\n if (!checkIntegration(step.integration, `${where} step \"${step.id}\"`)) {\n stepsOk = false;\n } else if (!declared.get(step.integration)?.has(step.op)) {\n stepsOk = false;\n findings.push(\n err(\n `${where} step \"${step.id}\" calls ${step.integration} ${step.op}, which the integration does not declare.`,\n 'Add the operation to the integration\\'s \"operations\" in ekanos.json (ekanos add --ops).',\n project.entry,\n ),\n );\n }\n }\n if (!stepsOk) continue;\n\n let dsl: unknown;\n try {\n dsl = loaded.serializeWorkflowTemplate(template, { effectOf });\n } catch (error) {\n findings.push(\n err(\n `${where}: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n const parsed = WorkflowTemplateSchema.safeParse(dsl);\n if (!parsed.success) {\n findings.push(\n err(\n `${where} does not serialize to a valid workflow: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'Fix the step definitions.',\n project.entry,\n ),\n );\n continue;\n }\n for (const problem of workflowTemplateProblems(parsed.data)) {\n findings.push(\n err(`${where}: ${problem}.`, 'Fix the template.', project.entry),\n );\n }\n\n const gated = new Set<string>();\n const preds = new Map<string, string[]>();\n for (const e of parsed.data.edges)\n preds.set(e.to, [...(preds.get(e.to) ?? []), e.from]);\n const hasGate = (id: string, seen = new Set<string>()): boolean => {\n if (seen.has(id)) return false;\n seen.add(id);\n\n return (preds.get(id) ?? []).some(\n (p) =>\n parsed.data.steps.find((s) => s.id === p)?.type === 'approval_gate' ||\n hasGate(p, seen),\n );\n };\n for (const s of parsed.data.steps) {\n if (s.type === 'fusion_tool' && s.effect === 'write' && !hasGate(s.id)) {\n gated.add(s.id);\n }\n }\n for (const id of gated) {\n findings.push(\n warn(\n `${where}: write step \"${id}\" has no approval gate upstream.`,\n 'Put step.approval(...) before a write so a person confirms it.',\n project.entry,\n ),\n );\n }\n\n workflows.push(parsed.data);\n }\n\n return { findings, workflows };\n}\n\n// ─── fixtures ───────────────────────────────────────────────────────────────\n\nconst FIXTURE_NAME = /^(tool|action)-(.+)\\.(default|empty|error)\\.json$/;\n\n/** `fixtures/<integration>/<kind>-<name>.<variant>.json` must satisfy the pinned output schema. */\nexport function checkFixtures(\n project: ExperienceProject,\n pinned: readonly PinnedIntegration[],\n): Finding[] {\n const findings: Finding[] = [];\n const root = path.join(project.dir, 'fixtures');\n if (!fs.existsSync(root)) return findings;\n\n for (const { use, contract } of pinned) {\n const dir = path.join(root, use.name);\n if (!fs.existsSync(dir)) continue;\n for (const name of fs.readdirSync(dir)) {\n const rel = `fixtures/${use.name}/${name}`;\n const match = FIXTURE_NAME.exec(name);\n if (!match) {\n findings.push(\n warn(\n `${rel} is not <kind>-<name>.<default|empty|error>.json.`,\n 'Rename it so the harness can find it.',\n rel,\n ),\n );\n continue;\n }\n const [, kind, op, variant] = match;\n if (variant === 'error') continue;\n const operation = contract.operations.find(\n (o) => o.kind === kind && o.name === op,\n );\n if (!operation) {\n findings.push(\n err(\n `${rel}: ${use.name}@${use.version} has no ${kind}/${op}.`,\n 'Delete the fixture or fix its name.',\n rel,\n ),\n );\n continue;\n }\n if (operation.opaque.output) continue;\n let value: unknown;\n try {\n value = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));\n } catch {\n findings.push(err(`${rel} is not valid JSON.`, 'Fix the syntax.', rel));\n continue;\n }\n const problems = validateAgainstSchema(operation.output, value);\n if (problems.length > 0) {\n findings.push(\n err(\n `${rel} violates the pinned output schema: ${problems[0]}`,\n 'A fixture must satisfy the contract, or the harness would show data the integration can never return.',\n rel,\n ),\n );\n }\n }\n }\n\n return findings;\n}\n\n// ─── manifest ───────────────────────────────────────────────────────────────\n\nexport function buildManifest(input: {\n project: ExperienceProject;\n definition: LoadedExperienceDefinition;\n pinned: readonly PinnedIntegration[];\n workflows: readonly WorkflowTemplate[];\n version: string;\n sdk: string;\n}): ExperienceManifest {\n const { project, definition, pinned } = input;\n const body = {\n schemaVersion: 1 as const,\n slug: project.slug,\n form: 'composed' as const,\n version: input.version,\n sdk: input.sdk,\n integrations: project.integrations,\n contractRefs: pinned.map(({ use, contract }) => ({\n integration: use.name,\n version: use.version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n })),\n ...(definition.sidebar ? { sidebar: definition.sidebar } : {}),\n widgets: definition.widgets.map((w) => ({\n id: w.id,\n title: w.title,\n integrations: [...w.integrations],\n ...(w.width ? { width: w.width } : {}),\n ...(w.isCollapsible === undefined\n ? {}\n : { isCollapsible: w.isCollapsible }),\n ...(w.aiFooterEnabled === undefined\n ? {}\n : { aiFooterEnabled: w.aiFooterEnabled }),\n })),\n pages: definition.pages.map((p) => ({\n id: p.id,\n path: p.path,\n title: p.title,\n integrations: [...p.integrations],\n })),\n workflowTemplates: [...input.workflows],\n ...(definition.reviewQueue\n ? {\n reviewQueue: {\n readsOnly: true as const,\n sections: definition.reviewQueue.sections.map((s) => ({\n id: s.id,\n integration: s.integration,\n read: s.read,\n })),\n },\n }\n : {}),\n };\n\n return {\n ...body,\n digest: `sha256:${sha256Hex(canonicalJson(body))}`,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definition-checks.js","sourceRoot":"","sources":["../../src/experience/definition-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ExperienceManifest,\n type ExperienceSurfaces,\n ExperienceSurfacesSchema,\n type Finding,\n type WorkflowTemplate,\n WorkflowTemplateSchema,\n EKANOS_CHECK_STAGE_IDS,\n buildExperienceSurfaces,\n canonicalJson,\n sha256Hex,\n validateAgainstSchema,\n workflowTemplateProblems,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type {\n LoadedExperienceDefinition,\n LoadedExperienceModule,\n} from './definition-shape';\nimport { declaredOperationKeys } from './integrations-codegen';\nimport { type PinnedIntegration, effectLookup } from './pins';\nimport type { ExperienceProject } from './project';\n\nexport const DEFINITION_CHECK = 'checks/experience-definition';\nexport const SURFACES_CHECK = EKANOS_CHECK_STAGE_IDS.experienceSurfaces;\n\nconst err = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'error',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nconst warn = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'warn',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nexport interface DefinitionAnalysis {\n findings: Finding[];\n workflows: WorkflowTemplate[];\n}\n\n/**\n * Cross-check the loaded definition against `ekanos.json` and the pinned\n * contracts: every integration a widget, page, review section or workflow step names\n * must be declared, every operation must be in that integration's allowlist, the\n * review queue reads only, and each template serializes to the engine's DSL.\n */\nexport function analyzeDefinition(\n project: ExperienceProject,\n loaded: LoadedExperienceModule,\n pinned: readonly PinnedIntegration[],\n): DefinitionAnalysis {\n const { definition } = loaded;\n const findings: Finding[] = [];\n const workflows: WorkflowTemplate[] = [];\n const declaredIntegrations = new Map(\n project.integrations.map((u) => [u.name, u]),\n );\n const declared = new Map(\n project.integrations.map((u) => [\n u.name,\n new Set(declaredOperationKeys(u)),\n ]),\n );\n const contractOps = new Map(\n pinned.map(({ use, contract }) => [\n use.name,\n new Map(contract.operations.map((op) => [`${op.kind}/${op.name}`, op])),\n ]),\n );\n\n if (definition.slug !== project.slug) {\n findings.push(\n err(\n `the definition's slug \"${definition.slug}\" differs from ekanos.json \"${project.slug}\".`,\n 'Make them equal.',\n project.entry,\n ),\n );\n }\n\n const checkIntegration = (integration: string, owner: string) => {\n if (!declaredIntegrations.has(integration)) {\n findings.push(\n err(\n `${owner} uses integration \"${integration}\", which ekanos.json \"integrations\" does not declare.`,\n `Add it with \"ekanos add ${integration}@<version>\".`,\n project.entry,\n ),\n );\n\n return false;\n }\n\n return true;\n };\n\n for (const widget of definition.widgets) {\n for (const integration of widget.integrations)\n checkIntegration(integration, `widget \"${widget.id}\"`);\n }\n for (const page of definition.pages) {\n for (const integration of page.integrations ?? [])\n checkIntegration(integration, `page \"${page.id}\"`);\n }\n\n findings.push(...checkSurfaces(project, definition));\n\n for (const section of definition.reviewQueue?.sections ?? []) {\n if (\n !checkIntegration(section.integration, `review section \"${section.id}\"`)\n )\n continue;\n if (!declared.get(section.integration)?.has(section.read)) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which integration \"${section.integration}\" does not declare.`,\n 'Add the operation to the integration in ekanos.json, or read another one.',\n project.entry,\n ),\n );\n } else if (\n contractOps.get(section.integration)?.get(section.read)?.effect !== 'read'\n ) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which is not a read operation.`,\n 'The review queue is reads-only; writes belong behind a workflow approval.',\n project.entry,\n ),\n );\n }\n }\n\n const effectOf = effectLookup(pinned);\n for (const template of definition.workflows) {\n const where = `workflow \"${template.id}\"`;\n let steps: ReturnType<typeof template.build>;\n try {\n steps = template.build();\n } catch (error) {\n findings.push(\n err(\n `${where}: build() threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n\n let stepsOk = true;\n for (const step of steps) {\n if (step.type !== 'tool' || !step.integration || !step.op) continue;\n if (!checkIntegration(step.integration, `${where} step \"${step.id}\"`)) {\n stepsOk = false;\n } else if (!declared.get(step.integration)?.has(step.op)) {\n stepsOk = false;\n findings.push(\n err(\n `${where} step \"${step.id}\" calls ${step.integration} ${step.op}, which the integration does not declare.`,\n 'Add the operation to the integration\\'s \"operations\" in ekanos.json (ekanos add --ops).',\n project.entry,\n ),\n );\n }\n }\n if (!stepsOk) continue;\n\n let dsl: unknown;\n try {\n dsl = loaded.serializeWorkflowTemplate(template, { effectOf });\n } catch (error) {\n findings.push(\n err(\n `${where}: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n const parsed = WorkflowTemplateSchema.safeParse(dsl);\n if (!parsed.success) {\n findings.push(\n err(\n `${where} does not serialize to a valid workflow: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'Fix the step definitions.',\n project.entry,\n ),\n );\n continue;\n }\n for (const problem of workflowTemplateProblems(parsed.data)) {\n findings.push(\n err(`${where}: ${problem}.`, 'Fix the template.', project.entry),\n );\n }\n\n const gated = new Set<string>();\n const preds = new Map<string, string[]>();\n for (const e of parsed.data.edges)\n preds.set(e.to, [...(preds.get(e.to) ?? []), e.from]);\n const hasGate = (id: string, seen = new Set<string>()): boolean => {\n if (seen.has(id)) return false;\n seen.add(id);\n\n return (preds.get(id) ?? []).some(\n (p) =>\n parsed.data.steps.find((s) => s.id === p)?.type === 'approval_gate' ||\n hasGate(p, seen),\n );\n };\n for (const s of parsed.data.steps) {\n if (s.type === 'fusion_tool' && s.effect === 'write' && !hasGate(s.id)) {\n gated.add(s.id);\n }\n }\n for (const id of gated) {\n findings.push(\n warn(\n `${where}: write step \"${id}\" has no approval gate upstream.`,\n 'Put step.approval(...) before a write so a person confirms it.',\n project.entry,\n ),\n );\n }\n\n workflows.push(parsed.data);\n }\n\n return { findings, workflows };\n}\n\n// ─── surfaces ───────────────────────────────────────────────────────────────\n\n/**\n * The surfaces contract: page kinds, canvas widgets, routes and the suggested\n * layout must be well formed and refer only to what the experience declares.\n * The same rules `defineExperience` enforces, re-run on the loaded definition\n * so a definition built by an older SDK, or by hand, cannot slip past.\n */\nexport function checkSurfaces(\n project: ExperienceProject,\n definition: LoadedExperienceDefinition,\n): Finding[] {\n const built = buildSurfaces(project, definition);\n const parsed = ExperienceSurfacesSchema.safeParse(built);\n if (parsed.success) return [];\n\n return parsed.error.issues.map((issue) => ({\n check: SURFACES_CHECK,\n severity: 'error' as const,\n message: `${issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''}${issue.message}`,\n hint: 'Fix the page kinds, canvas widgets or suggestedLayout in the experience definition.',\n file: project.entry,\n }));\n}\n\n/** `dist/surfaces.json`: the surfaces and suggested layout as data, no code. */\nexport function buildSurfaces(\n project: ExperienceProject,\n definition: LoadedExperienceDefinition,\n): ExperienceSurfaces {\n return buildExperienceSurfaces(project.slug, definition);\n}\n\n// ─── fixtures ───────────────────────────────────────────────────────────────\n\nconst FIXTURE_NAME = /^(tool|action)-(.+)\\.(default|empty|error)\\.json$/;\n\n/** `fixtures/<integration>/<kind>-<name>.<variant>.json` must satisfy the pinned output schema. */\nexport function checkFixtures(\n project: ExperienceProject,\n pinned: readonly PinnedIntegration[],\n): Finding[] {\n const findings: Finding[] = [];\n const root = path.join(project.dir, 'fixtures');\n if (!fs.existsSync(root)) return findings;\n\n for (const { use, contract } of pinned) {\n const dir = path.join(root, use.name);\n if (!fs.existsSync(dir)) continue;\n for (const name of fs.readdirSync(dir)) {\n const rel = `fixtures/${use.name}/${name}`;\n const match = FIXTURE_NAME.exec(name);\n if (!match) {\n findings.push(\n warn(\n `${rel} is not <kind>-<name>.<default|empty|error>.json.`,\n 'Rename it so the harness can find it.',\n rel,\n ),\n );\n continue;\n }\n const [, kind, op, variant] = match;\n if (variant === 'error') continue;\n const operation = contract.operations.find(\n (o) => o.kind === kind && o.name === op,\n );\n if (!operation) {\n findings.push(\n err(\n `${rel}: ${use.name}@${use.version} has no ${kind}/${op}.`,\n 'Delete the fixture or fix its name.',\n rel,\n ),\n );\n continue;\n }\n if (operation.opaque.output) continue;\n let value: unknown;\n try {\n value = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));\n } catch {\n findings.push(err(`${rel} is not valid JSON.`, 'Fix the syntax.', rel));\n continue;\n }\n const problems = validateAgainstSchema(operation.output, value);\n if (problems.length > 0) {\n findings.push(\n err(\n `${rel} violates the pinned output schema: ${problems[0]}`,\n 'A fixture must satisfy the contract, or the harness would show data the integration can never return.',\n rel,\n ),\n );\n }\n }\n }\n\n return findings;\n}\n\n// ─── manifest ───────────────────────────────────────────────────────────────\n\nexport function buildManifest(input: {\n project: ExperienceProject;\n definition: LoadedExperienceDefinition;\n pinned: readonly PinnedIntegration[];\n workflows: readonly WorkflowTemplate[];\n version: string;\n sdk: string;\n surfaces: ExperienceSurfaces;\n}): ExperienceManifest {\n const { project, definition, pinned } = input;\n const body = {\n schemaVersion: 1 as const,\n slug: project.slug,\n form: 'composed' as const,\n version: input.version,\n sdk: input.sdk,\n integrations: project.integrations,\n contractRefs: pinned.map(({ use, contract }) => ({\n integration: use.name,\n version: use.version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n })),\n ...(definition.sidebar ? { sidebar: definition.sidebar } : {}),\n widgets: definition.widgets.map((w) => ({\n id: w.id,\n title: w.title,\n integrations: [...w.integrations],\n ...(w.width ? { width: w.width } : {}),\n ...(w.isCollapsible === undefined\n ? {}\n : { isCollapsible: w.isCollapsible }),\n ...(w.aiFooterEnabled === undefined\n ? {}\n : { aiFooterEnabled: w.aiFooterEnabled }),\n })),\n pages: definition.pages.map((p) => ({\n id: p.id,\n ...(p.path === undefined ? {} : { path: p.path }),\n title: p.title,\n integrations: [...(p.integrations ?? [])],\n ...(p.kind === undefined ? {} : { kind: p.kind }),\n ...(p.widgets === undefined ? {} : { widgets: [...p.widgets] }),\n ...(p.routes && p.routes.length > 0\n ? { routes: p.routes.map((r) => ({ path: r.path, title: r.title })) }\n : {}),\n })),\n surfacesDigest: input.surfaces.digest,\n workflowTemplates: [...input.workflows],\n ...(definition.reviewQueue\n ? {\n reviewQueue: {\n readsOnly: true as const,\n sections: definition.reviewQueue.sections.map((s) => ({\n id: s.id,\n integration: s.integration,\n read: s.read,\n })),\n },\n }\n : {}),\n };\n\n return {\n ...body,\n digest: `sha256:${sha256Hex(canonicalJson(body))}`,\n };\n}\n"]}
|
|
@@ -35,10 +35,28 @@ export interface LoadedExperienceDefinition {
|
|
|
35
35
|
}>;
|
|
36
36
|
pages: ReadonlyArray<{
|
|
37
37
|
id: string;
|
|
38
|
-
|
|
38
|
+
/** The SDK sets it; a definition from before page kinds has none (custom). */
|
|
39
|
+
kind?: 'overview' | 'canvas' | 'custom';
|
|
40
|
+
path?: string;
|
|
39
41
|
title: string;
|
|
40
|
-
integrations
|
|
42
|
+
integrations?: readonly string[];
|
|
43
|
+
widgets?: readonly string[];
|
|
44
|
+
routes?: ReadonlyArray<{
|
|
45
|
+
path: string;
|
|
46
|
+
title: string;
|
|
47
|
+
}>;
|
|
41
48
|
}>;
|
|
49
|
+
suggestedLayout?: {
|
|
50
|
+
workspaces: ReadonlyArray<{
|
|
51
|
+
slug: string;
|
|
52
|
+
name: string;
|
|
53
|
+
icon?: string;
|
|
54
|
+
items: ReadonlyArray<{
|
|
55
|
+
ref: string;
|
|
56
|
+
audiences?: readonly string[];
|
|
57
|
+
}>;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
42
60
|
workflows: readonly LoadedWorkflowTemplate[];
|
|
43
61
|
reviewQueue?: {
|
|
44
62
|
readsOnly: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition-shape.js","sourceRoot":"","sources":["../../src/experience/definition-shape.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The structural slice of an `ExperienceDefinition` the CLI reads. The CLI does\n * not depend on `@ekanos/sdk`: the definition is produced by whichever SDK the\n * project installed, and anything the CLI needs computed (workflow\n * serialization) is obtained from that same SDK at load time.\n */\nexport interface LoadedWorkflowTemplate {\n id: string;\n name: string;\n build(): ReadonlyArray<{\n type: string;\n id: string;\n integration?: string;\n op?: string;\n }>;\n}\n\nexport interface LoadedExperienceDefinition {\n kind: 'experience';\n slug: string;\n name: string;\n description?: string;\n sidebar?: { section: string; icon: string; label: string };\n widgets: ReadonlyArray<{\n id: string;\n title: string;\n integrations: readonly string[];\n width?: string;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n aiFooterEnabled?: boolean;\n }>;\n pages: ReadonlyArray<{\n id: string;\n path
|
|
1
|
+
{"version":3,"file":"definition-shape.js","sourceRoot":"","sources":["../../src/experience/definition-shape.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The structural slice of an `ExperienceDefinition` the CLI reads. The CLI does\n * not depend on `@ekanos/sdk`: the definition is produced by whichever SDK the\n * project installed, and anything the CLI needs computed (workflow\n * serialization) is obtained from that same SDK at load time.\n */\nexport interface LoadedWorkflowTemplate {\n id: string;\n name: string;\n build(): ReadonlyArray<{\n type: string;\n id: string;\n integration?: string;\n op?: string;\n }>;\n}\n\nexport interface LoadedExperienceDefinition {\n kind: 'experience';\n slug: string;\n name: string;\n description?: string;\n sidebar?: { section: string; icon: string; label: string };\n widgets: ReadonlyArray<{\n id: string;\n title: string;\n integrations: readonly string[];\n width?: string;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n aiFooterEnabled?: boolean;\n }>;\n pages: ReadonlyArray<{\n id: string;\n /** The SDK sets it; a definition from before page kinds has none (custom). */\n kind?: 'overview' | 'canvas' | 'custom';\n path?: string;\n title: string;\n integrations?: readonly string[];\n widgets?: readonly string[];\n routes?: ReadonlyArray<{ path: string; title: string }>;\n }>;\n suggestedLayout?: {\n workspaces: ReadonlyArray<{\n slug: string;\n name: string;\n icon?: string;\n items: ReadonlyArray<{ ref: string; audiences?: readonly string[] }>;\n }>;\n };\n workflows: readonly LoadedWorkflowTemplate[];\n reviewQueue?: {\n readsOnly: true;\n sections: ReadonlyArray<{ id: string; integration: string; read: string }>;\n };\n}\n\nexport interface SerializedWorkflowOptions {\n effectOf: (integration: string, op: string) => 'read' | 'write' | undefined;\n}\n\nexport interface LoadedExperienceModule {\n definition: LoadedExperienceDefinition;\n /** The project's own SDK serializer, so the output matches the SDK that authored it. */\n serializeWorkflowTemplate(\n template: LoadedWorkflowTemplate,\n options: SerializedWorkflowOptions,\n ): unknown;\n slotsUsedBy(definition: LoadedExperienceDefinition): string[];\n}\n"]}
|
|
@@ -6,10 +6,22 @@ export interface ExperiencePublishArgs {
|
|
|
6
6
|
dir: string;
|
|
7
7
|
dryRun: boolean;
|
|
8
8
|
env: Record<string, string | undefined>;
|
|
9
|
+
/** The host to submit to (a real run only). Falls back to ekanos.json, then the stored login. */
|
|
10
|
+
host?: string;
|
|
11
|
+
/** The source slug to submit for (a real run only). Falls back to ekanos.json "source". */
|
|
12
|
+
source?: string;
|
|
13
|
+
/** Skips the multi-seat confirmation when the source came from ekanos.json. */
|
|
14
|
+
yes?: boolean;
|
|
15
|
+
/** Whether stdin is a real terminal; injectable for tests. */
|
|
16
|
+
stdinIsTTY?: boolean;
|
|
17
|
+
/** The y/N prompt; injectable for tests. */
|
|
18
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
9
19
|
}
|
|
10
20
|
export interface ExperienceGate {
|
|
11
21
|
project: ExperienceProject;
|
|
12
22
|
result: ExperienceCheckResult;
|
|
23
|
+
/** The caller's environment (read for the clean-room switches). */
|
|
24
|
+
env?: Record<string, string | undefined>;
|
|
13
25
|
}
|
|
14
26
|
/**
|
|
15
27
|
* The refusals and the gate every experience publish passes, before anything
|
|
@@ -35,17 +47,42 @@ export interface PackedExperience {
|
|
|
35
47
|
* dry run puts it under `.ekanos/publish/`); otherwise it is discarded.
|
|
36
48
|
*/
|
|
37
49
|
export declare function packExperience(gate: ExperienceGate, artifactDir?: string): Promise<PackedExperience>;
|
|
50
|
+
/** What submitting a gated, packed experience came to. */
|
|
51
|
+
export type ExperienceSubmission = {
|
|
52
|
+
status: 'unsupported';
|
|
53
|
+
host: string;
|
|
54
|
+
} | {
|
|
55
|
+
status: 'submitted';
|
|
56
|
+
data: Record<string, unknown>;
|
|
57
|
+
human: string;
|
|
58
|
+
};
|
|
38
59
|
/**
|
|
39
|
-
*
|
|
60
|
+
* Submit a gated experience to a host that admits them.
|
|
61
|
+
*
|
|
62
|
+
* The host is asked first (the toolchain route's `capabilities`), because an
|
|
63
|
+
* older deployment answers a `kind=experience` submission as if it were an
|
|
64
|
+
* integration and would file it under the wrong gate: a host that does not
|
|
65
|
+
* advertise `experienceSubmissions` gets nothing sent and the caller keeps the
|
|
66
|
+
* dry-run-only message. Authorization is the same as an integration's (the
|
|
67
|
+
* developer seat on the source, checked server-side; the session refreshed
|
|
68
|
+
* once on a 401). The submission is the packed tarball plus the built
|
|
69
|
+
* `manifest.json`, which the host re-validates and cross-checks against the
|
|
70
|
+
* archive in its own gate.
|
|
71
|
+
*/
|
|
72
|
+
export declare function submitExperience(ctx: CliContext, args: ExperiencePublishArgs, gate: ExperienceGate): Promise<ExperienceSubmission>;
|
|
73
|
+
/**
|
|
74
|
+
* `ekanos publish` for an experience.
|
|
40
75
|
*
|
|
41
76
|
* 1. Active integration overrides refuse it (exit 3, naming each integration and path): what
|
|
42
77
|
* you ran is not what a host would resolve. An integration of the same repo is not an override.
|
|
43
78
|
* 2. A tracked ekanos.local.json, any `ignore` entry, or a failing gate refuse.
|
|
44
|
-
* 3.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
79
|
+
* 3. With --dry-run it builds and packs, and checks the tarball holds no
|
|
80
|
+
* integration code. Nothing is sent.
|
|
81
|
+
* 4. Without --dry-run it builds, packs and submits to a host that advertises
|
|
82
|
+
* experience submissions. A host that does not (an older deployment) gets
|
|
83
|
+
* nothing and the run ends with the dry-run-only message, as before.
|
|
47
84
|
*
|
|
48
|
-
*
|
|
85
|
+
* The refusals and the dry run run before any login: a dry run needs no session.
|
|
49
86
|
*/
|
|
50
87
|
export declare function runExperiencePublish(ctx: CliContext, args: ExperiencePublishArgs): Promise<ExitCode>;
|
|
51
88
|
export declare const EXPERIENCE_NOT_SUPPORTED = "Publishing an experience is not supported by the host yet.";
|
|
@@ -1,17 +1,33 @@
|
|
|
1
|
+
import { canonicalJson } from '@ekanos/integration-schema';
|
|
1
2
|
import { execFileSync } from 'node:child_process';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
6
|
+
import { requireSession, resolveAuthEnvironment } from '../auth/session.js';
|
|
7
|
+
import { promptYesNo, resolveSourceSlug, submitWithOneRefresh, verifyPublishTarget, } from '../commands/publish.js';
|
|
5
8
|
import { activeOverridesFor, harnessPinsResolver } from '../dev-experience.js';
|
|
6
9
|
import { gateFailedError, preconditionError, validationError } from '../errors.js';
|
|
10
|
+
import { MAX_ARCHIVE_BYTES } from '../pack.js';
|
|
11
|
+
import { MAX_MANIFEST_BYTES } from '../publish-api.js';
|
|
12
|
+
import { fetchToolchainReport } from '../toolchain-api.js';
|
|
13
|
+
import { shortHost } from '../ui.js';
|
|
7
14
|
import { emitExperience } from './build.js';
|
|
8
15
|
import { runExperienceChecks } from './checks.js';
|
|
16
|
+
import { CLEAN_ROOM_ENV, buildInCleanRoom, hasBuildableLockfile, } from './clean-room.js';
|
|
9
17
|
import { isOverridesFileTracked } from './overrides.js';
|
|
10
18
|
import { LOCAL_OVERRIDES_FILE, readExperienceProject, } from './project.js';
|
|
11
19
|
import { failOnErrors } from './report.js';
|
|
12
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* What a packed experience may contain: its own source, build output and pins,
|
|
22
|
+
* nothing else. The source ships because the host rebuilds `dist/` from it in
|
|
23
|
+
* a clean room and refuses a package whose bundle is not what that source
|
|
24
|
+
* produces; the lockfile ships so that rebuild resolves the same dependencies.
|
|
25
|
+
*/
|
|
13
26
|
const ALLOWED_PACK_PATHS = [
|
|
14
27
|
/^package\.json$/,
|
|
28
|
+
/^npm-shrinkwrap\.json$/,
|
|
29
|
+
/^tsconfig[^/]*\.json$/,
|
|
30
|
+
/^src\/.+/,
|
|
15
31
|
/^ekanos\.json$/,
|
|
16
32
|
/^manifest\.json$/,
|
|
17
33
|
/^ekanos\.lock\.json$/,
|
|
@@ -50,7 +66,22 @@ export async function gateExperience(args) {
|
|
|
50
66
|
inlineSdk: args.env.EKANOS_INLINE_SDK,
|
|
51
67
|
pinsResolver: await harnessPinsResolver(project.dir, args.env),
|
|
52
68
|
});
|
|
53
|
-
return { project, result };
|
|
69
|
+
return { project, result, env: args.env };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* npm never packs `package-lock.json`, but it does pack `npm-shrinkwrap.json`
|
|
73
|
+
* (and `npm ci` honours it). The host rebuilds the shipped source against the
|
|
74
|
+
* dependencies it was built with, so the lockfile the bundle was built from
|
|
75
|
+
* travels under that name for the pack and is removed afterwards. A project
|
|
76
|
+
* that keeps its own shrinkwrap is left as it is.
|
|
77
|
+
*/
|
|
78
|
+
function stageShrinkwrap(dir, content) {
|
|
79
|
+
const shrinkwrap = path.join(dir, 'npm-shrinkwrap.json');
|
|
80
|
+
if (content === null || fs.existsSync(shrinkwrap)) {
|
|
81
|
+
return { restore: () => { } };
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(shrinkwrap, content);
|
|
84
|
+
return { restore: () => fs.rmSync(shrinkwrap, { force: true }) };
|
|
54
85
|
}
|
|
55
86
|
/**
|
|
56
87
|
* Build the experience and pack it with `npm pack`, checking the tarball holds
|
|
@@ -59,11 +90,32 @@ export async function gateExperience(args) {
|
|
|
59
90
|
*/
|
|
60
91
|
export async function packExperience(gate, artifactDir) {
|
|
61
92
|
var _a;
|
|
93
|
+
var _b;
|
|
62
94
|
const { project, result } = gate;
|
|
95
|
+
if (!hasBuildableLockfile(project.dir)) {
|
|
96
|
+
throw preconditionError('There is no lockfile (package-lock.json, npm-shrinkwrap.json or pnpm-lock.yaml), so the host could not rebuild this experience from its source with the dependencies you built with.', 'Run "npm install --package-lock-only" (or install with pnpm in a workspace) and commit the lockfile, then re-run.');
|
|
97
|
+
}
|
|
63
98
|
const output = await emitExperience(project, result);
|
|
99
|
+
// The bundle that ships is the one built in a clean room from the lockfile
|
|
100
|
+
// that ships, the way the host's gate rebuilds it, not the one built in the
|
|
101
|
+
// working tree (a pnpm workspace's tree is not what a clean `npm ci` makes).
|
|
102
|
+
const ownLock = path.join(project.dir, 'package-lock.json');
|
|
103
|
+
let shippedLockfile = fs.existsSync(ownLock)
|
|
104
|
+
? fs.readFileSync(ownLock, 'utf8')
|
|
105
|
+
: null;
|
|
106
|
+
if (((_a = gate.env) === null || _a === void 0 ? void 0 : _a[CLEAN_ROOM_ENV]) !== 'off') {
|
|
107
|
+
const room = await buildInCleanRoom(project, gate.env);
|
|
108
|
+
fs.writeFileSync(path.join(project.dir, 'dist', 'experience.js'), room.bundle);
|
|
109
|
+
const written = JSON.parse(fs.readFileSync(path.join(project.dir, 'dist', 'surfaces.json'), 'utf8'));
|
|
110
|
+
if (canonicalJson(written) !== canonicalJson(room.surfaces)) {
|
|
111
|
+
throw validationError('The surfaces your working tree declares are not what the clean-room build declares (different dependency versions, or a stale build).', 'Reinstall dependencies from the lockfile, run "ekanos build", and re-run "ekanos publish".');
|
|
112
|
+
}
|
|
113
|
+
shippedLockfile = room.lockfile;
|
|
114
|
+
}
|
|
64
115
|
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'ekanos-exp-pack-'));
|
|
65
116
|
let packed;
|
|
66
117
|
let tarball;
|
|
118
|
+
const shrinkwrap = stageShrinkwrap(project.dir, shippedLockfile);
|
|
67
119
|
try {
|
|
68
120
|
const stdout = execFileSync('npm', ['pack', '--json', '--ignore-scripts', '--pack-destination', scratch], { cwd: project.dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
69
121
|
packed = JSON.parse(stdout);
|
|
@@ -75,21 +127,25 @@ export async function packExperience(gate, artifactDir) {
|
|
|
75
127
|
}
|
|
76
128
|
}
|
|
77
129
|
catch (error) {
|
|
78
|
-
throw preconditionError(`npm pack failed: ${error instanceof Error ? error.message : String(error)}`, 'Make sure npm is installed and package.json "files" lists dist, manifest.json, ekanos.json, ekanos.lock.json and
|
|
130
|
+
throw preconditionError(`npm pack failed: ${error instanceof Error ? error.message : String(error)}`, 'Make sure npm is installed and package.json "files" lists src, dist, manifest.json, ekanos.json, ekanos.lock.json, contracts, tsconfig.json and npm-shrinkwrap.json.');
|
|
79
131
|
}
|
|
80
132
|
finally {
|
|
133
|
+
shrinkwrap.restore();
|
|
81
134
|
fs.rmSync(scratch, { recursive: true, force: true });
|
|
82
135
|
}
|
|
83
136
|
const entry = packed[0];
|
|
84
|
-
const files = ((
|
|
137
|
+
const files = ((_b = entry === null || entry === void 0 ? void 0 : entry.files) !== null && _b !== void 0 ? _b : []).map((f) => f.path);
|
|
85
138
|
const stray = files.filter((f) => !ALLOWED_PACK_PATHS.some((re) => re.test(f)));
|
|
86
139
|
if (stray.length > 0) {
|
|
87
|
-
throw validationError(`The package would ship files an experience must not contain: ${stray.slice(0, 5).join(', ')}.`, 'Restrict package.json "files" to dist, manifest.json, ekanos.json, ekanos.lock.json and
|
|
140
|
+
throw validationError(`The package would ship files an experience must not contain: ${stray.slice(0, 5).join(', ')}.`, 'Restrict package.json "files" to src, dist, manifest.json, ekanos.json, ekanos.lock.json, contracts, tsconfig.json and npm-shrinkwrap.json.');
|
|
88
141
|
}
|
|
89
142
|
for (const required of [
|
|
90
143
|
'manifest.json',
|
|
91
144
|
'dist/experience.js',
|
|
145
|
+
'dist/surfaces.json',
|
|
92
146
|
'ekanos.lock.json',
|
|
147
|
+
'npm-shrinkwrap.json',
|
|
148
|
+
project.entry,
|
|
93
149
|
]) {
|
|
94
150
|
if (!files.includes(required)) {
|
|
95
151
|
throw validationError(`The package would be missing ${required}.`, 'Add it to package.json "files".');
|
|
@@ -106,16 +162,140 @@ export async function packExperience(gate, artifactDir) {
|
|
|
106
162
|
};
|
|
107
163
|
}
|
|
108
164
|
/**
|
|
109
|
-
*
|
|
165
|
+
* Submit a gated experience to a host that admits them.
|
|
166
|
+
*
|
|
167
|
+
* The host is asked first (the toolchain route's `capabilities`), because an
|
|
168
|
+
* older deployment answers a `kind=experience` submission as if it were an
|
|
169
|
+
* integration and would file it under the wrong gate: a host that does not
|
|
170
|
+
* advertise `experienceSubmissions` gets nothing sent and the caller keeps the
|
|
171
|
+
* dry-run-only message. Authorization is the same as an integration's (the
|
|
172
|
+
* developer seat on the source, checked server-side; the session refreshed
|
|
173
|
+
* once on a 401). The submission is the packed tarball plus the built
|
|
174
|
+
* `manifest.json`, which the host re-validates and cross-checks against the
|
|
175
|
+
* archive in its own gate.
|
|
176
|
+
*/
|
|
177
|
+
export async function submitExperience(ctx, args, gate) {
|
|
178
|
+
var _a;
|
|
179
|
+
var _b, _c;
|
|
180
|
+
const { project } = gate;
|
|
181
|
+
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
182
|
+
projectDir: project.dir,
|
|
183
|
+
});
|
|
184
|
+
const session = requireSession(env);
|
|
185
|
+
const resolution = await resolveSourceSlug(env, session, args.source, {
|
|
186
|
+
project: { source: project.ekanos.source },
|
|
187
|
+
});
|
|
188
|
+
const version = readVersion(gate);
|
|
189
|
+
const verified = await verifyPublishTarget(ctx, env, resolution.session, {
|
|
190
|
+
source: resolution.source,
|
|
191
|
+
explicit: resolution.explicit,
|
|
192
|
+
slug: project.slug,
|
|
193
|
+
version,
|
|
194
|
+
yes: args.yes === true,
|
|
195
|
+
stdinIsTTY: (_b = args.stdinIsTTY) !== null && _b !== void 0 ? _b : process.stdin.isTTY === true,
|
|
196
|
+
confirm: (_c = args.confirm) !== null && _c !== void 0 ? _c : promptYesNo,
|
|
197
|
+
});
|
|
198
|
+
const toolchain = await fetchToolchainReport(env.host, verified.session.accessToken);
|
|
199
|
+
if (toolchain.status !== 'ok' ||
|
|
200
|
+
((_a = toolchain.report.capabilities) === null || _a === void 0 ? void 0 : _a.experienceSubmissions) !== true) {
|
|
201
|
+
return { status: 'unsupported', host: env.host };
|
|
202
|
+
}
|
|
203
|
+
ctx.log(`Packing ${project.slug}@${version}…`);
|
|
204
|
+
const artifactDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ekanos-exp-submit-'));
|
|
205
|
+
let archive;
|
|
206
|
+
let digest;
|
|
207
|
+
let files;
|
|
208
|
+
try {
|
|
209
|
+
const packed = await packExperience(gate, artifactDir);
|
|
210
|
+
if (!packed.tarball) {
|
|
211
|
+
throw preconditionError('The packed experience was not kept.', 'This is a bug in @ekanos/cli; report it.');
|
|
212
|
+
}
|
|
213
|
+
archive = new Uint8Array(fs.readFileSync(packed.tarball));
|
|
214
|
+
digest = packed.digest;
|
|
215
|
+
files = packed.files;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
fs.rmSync(artifactDir, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
if (archive.length > MAX_ARCHIVE_BYTES) {
|
|
221
|
+
throw validationError(`The packed experience is ${archive.length} bytes, over the ${MAX_ARCHIVE_BYTES}-byte submission limit.`, 'Drop dependencies the bundle does not need, or split the experience.');
|
|
222
|
+
}
|
|
223
|
+
const manifest = fs.readFileSync(path.join(project.dir, 'manifest.json'), 'utf8');
|
|
224
|
+
if (Buffer.byteLength(manifest, 'utf8') > MAX_MANIFEST_BYTES) {
|
|
225
|
+
throw validationError(`manifest.json is larger than the ${MAX_MANIFEST_BYTES}-byte manifest limit.`, 'Trim the workflow templates or widget declarations it carries.');
|
|
226
|
+
}
|
|
227
|
+
if (!ctx.jsonMode) {
|
|
228
|
+
ctx.log(ctx.ui.ok(`${ctx.ui.glyphs.ok} packed ${project.slug}@${version} (${files.length} files).`));
|
|
229
|
+
}
|
|
230
|
+
const source = verified.sourceSeat.slug;
|
|
231
|
+
ctx.log(`Submitting to ${env.host} (source "${source}")…`);
|
|
232
|
+
const receipt = await submitWithOneRefresh(env, verified.session, {
|
|
233
|
+
source,
|
|
234
|
+
slug: project.slug,
|
|
235
|
+
kind: 'experience',
|
|
236
|
+
version,
|
|
237
|
+
manifest,
|
|
238
|
+
archive,
|
|
239
|
+
});
|
|
240
|
+
if (!ctx.jsonMode) {
|
|
241
|
+
ctx.log(ctx.ui.ok(`${ctx.ui.glyphs.ok} submitted ${receipt.slug}@${receipt.version} (state "${receipt.state}").`));
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
status: 'submitted',
|
|
245
|
+
data: {
|
|
246
|
+
host: env.host,
|
|
247
|
+
source: {
|
|
248
|
+
id: verified.sourceSeat.id,
|
|
249
|
+
slug: verified.sourceSeat.slug,
|
|
250
|
+
name: verified.sourceSeat.name,
|
|
251
|
+
},
|
|
252
|
+
kind: 'experience',
|
|
253
|
+
slug: receipt.slug,
|
|
254
|
+
version: receipt.version,
|
|
255
|
+
submissionId: receipt.submissionId,
|
|
256
|
+
state: receipt.state,
|
|
257
|
+
archiveSha256: receipt.archiveSha256,
|
|
258
|
+
digest,
|
|
259
|
+
files,
|
|
260
|
+
published: true,
|
|
261
|
+
},
|
|
262
|
+
human: ctx.ui.result({
|
|
263
|
+
title: 'Submitted',
|
|
264
|
+
rows: [
|
|
265
|
+
['Experience', `${receipt.slug}@${receipt.version}`],
|
|
266
|
+
['State', receipt.state],
|
|
267
|
+
['Source', source],
|
|
268
|
+
['Host', shortHost(env.host)],
|
|
269
|
+
],
|
|
270
|
+
notes: [],
|
|
271
|
+
}),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function readVersion(gate) {
|
|
275
|
+
try {
|
|
276
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(gate.project.dir, 'package.json'), 'utf8'));
|
|
277
|
+
if (typeof pkg.version === 'string' && pkg.version.length > 0) {
|
|
278
|
+
return pkg.version;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (_a) {
|
|
282
|
+
// Reported below with the fix.
|
|
283
|
+
}
|
|
284
|
+
throw preconditionError('package.json is missing or has no "version".', 'An experience is an npm package; set a semver "version" in package.json.');
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* `ekanos publish` for an experience.
|
|
110
288
|
*
|
|
111
289
|
* 1. Active integration overrides refuse it (exit 3, naming each integration and path): what
|
|
112
290
|
* you ran is not what a host would resolve. An integration of the same repo is not an override.
|
|
113
291
|
* 2. A tracked ekanos.local.json, any `ignore` entry, or a failing gate refuse.
|
|
114
|
-
* 3.
|
|
115
|
-
*
|
|
116
|
-
*
|
|
292
|
+
* 3. With --dry-run it builds and packs, and checks the tarball holds no
|
|
293
|
+
* integration code. Nothing is sent.
|
|
294
|
+
* 4. Without --dry-run it builds, packs and submits to a host that advertises
|
|
295
|
+
* experience submissions. A host that does not (an older deployment) gets
|
|
296
|
+
* nothing and the run ends with the dry-run-only message, as before.
|
|
117
297
|
*
|
|
118
|
-
*
|
|
298
|
+
* The refusals and the dry run run before any login: a dry run needs no session.
|
|
119
299
|
*/
|
|
120
300
|
export async function runExperiencePublish(ctx, args) {
|
|
121
301
|
const gate = await gateExperience(args);
|
|
@@ -124,7 +304,11 @@ export async function runExperiencePublish(ctx, args) {
|
|
|
124
304
|
if (failed !== null)
|
|
125
305
|
return failed;
|
|
126
306
|
if (!args.dryRun) {
|
|
127
|
-
|
|
307
|
+
const submission = await submitExperience(ctx, args, gate);
|
|
308
|
+
if (submission.status === 'unsupported') {
|
|
309
|
+
throw gateFailedError(EXPERIENCE_NOT_SUPPORTED, `Run "ekanos publish --dry-run" to build and pack it; ${submission.host} has no admission path for experiences until it ships one.`);
|
|
310
|
+
}
|
|
311
|
+
return ctx.succeed(submission.data, submission.human);
|
|
128
312
|
}
|
|
129
313
|
const packed = await packExperience(gate);
|
|
130
314
|
return ctx.succeed({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/experience/publish.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport type { CliContext } from '../context';\nimport { activeOverridesFor, harnessPinsResolver } from '../dev-experience';\nimport { gateFailedError, preconditionError, validationError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { emitExperience } from './build';\nimport { type ExperienceCheckResult, runExperienceChecks } from './checks';\nimport { isOverridesFileTracked } from './overrides';\nimport {\n type ExperienceProject,\n LOCAL_OVERRIDES_FILE,\n readExperienceProject,\n} from './project';\nimport { failOnErrors } from './report';\n\nexport interface ExperiencePublishArgs {\n dir: string;\n dryRun: boolean;\n env: Record<string, string | undefined>;\n}\n\n/** What a packed experience may contain: its own build output and pins, nothing else. */\nconst ALLOWED_PACK_PATHS = [\n /^package\\.json$/,\n /^ekanos\\.json$/,\n /^manifest\\.json$/,\n /^ekanos\\.lock\\.json$/,\n /^contracts\\/[^/]+\\.contract\\.json$/,\n /^contracts\\/\\.gitkeep$/,\n /^dist\\/[^/]+$/,\n /^README\\.md$/,\n /^LICENSE$/,\n];\n\nexport interface ExperienceGate {\n project: ExperienceProject;\n result: ExperienceCheckResult;\n}\n\n/**\n * The refusals and the gate every experience publish passes, before anything\n * is built: active overrides, a tracked overrides file, `ignore` entries, then\n * the experience checks. Throws a CliError for the first three; the findings\n * come back for the caller to render (one envelope for one package, a table\n * for a repo).\n */\nexport async function gateExperience(\n args: ExperiencePublishArgs,\n): Promise<ExperienceGate> {\n const project = readExperienceProject(args.dir);\n\n const overrides = await activeOverridesFor(project.dir, args.env);\n if (overrides.length > 0) {\n throw validationError(\n `Cannot publish with ${overrides.length} local integration override${overrides.length === 1 ? '' : 's'} active: ${overrides\n .map((o) => `${o.integration} -> ${o.path}`)\n .join(', ')}.`,\n `Delete ${LOCAL_OVERRIDES_FILE} (and unset EKANOS_INTEGRATION_OVERRIDE_*) so every integration resolves to its published version, then re-run.`,\n );\n }\n\n if (isOverridesFileTracked(project)) {\n throw validationError(\n `${LOCAL_OVERRIDES_FILE} is tracked by git.`,\n `It is per-developer: \"git rm --cached ${LOCAL_OVERRIDES_FILE}\" and add it to .gitignore.`,\n );\n }\n\n const ignored = project.ekanos.ignore ?? [];\n if (ignored.length > 0) {\n throw validationError(\n `ekanos.json \"ignore\" excludes ${ignored.join(', ')} from the boundary scan, so it cannot be published.`,\n 'Remove the \"ignore\" entries (move the archive out of the project) and re-run.',\n );\n }\n\n const result = await runExperienceChecks(project, {\n typecheck: false,\n loadDefinition: true,\n inlineSdk: args.env.EKANOS_INLINE_SDK,\n pinsResolver: await harnessPinsResolver(project.dir, args.env),\n });\n\n return { project, result };\n}\n\nexport interface PackedExperience {\n slug: string;\n version: string;\n digest: string;\n filename: string | undefined;\n files: string[];\n integrity: string | undefined;\n /** Where the tarball was kept, when `artifactDir` was given. */\n tarball: string | undefined;\n}\n\n/**\n * Build the experience and pack it with `npm pack`, checking the tarball holds\n * nothing but its own build output. `artifactDir` keeps the tarball (a repo\n * dry run puts it under `.ekanos/publish/`); otherwise it is discarded.\n */\nexport async function packExperience(\n gate: ExperienceGate,\n artifactDir?: string,\n): Promise<PackedExperience> {\n const { project, result } = gate;\n const output = await emitExperience(project, result);\n\n const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'ekanos-exp-pack-'));\n let packed: Array<{\n filename: string;\n files: Array<{ path: string; size: number }>;\n integrity?: string;\n }>;\n let tarball: string | undefined;\n try {\n const stdout = execFileSync(\n 'npm',\n ['pack', '--json', '--ignore-scripts', '--pack-destination', scratch],\n { cwd: project.dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },\n );\n packed = JSON.parse(stdout) as typeof packed;\n const first = packed[0];\n if (artifactDir && first) {\n fs.mkdirSync(artifactDir, { recursive: true });\n tarball = path.join(artifactDir, path.basename(first.filename));\n fs.copyFileSync(path.join(scratch, first.filename), tarball);\n }\n } catch (error) {\n throw preconditionError(\n `npm pack failed: ${error instanceof Error ? error.message : String(error)}`,\n 'Make sure npm is installed and package.json \"files\" lists dist, manifest.json, ekanos.json, ekanos.lock.json and contracts.',\n );\n } finally {\n fs.rmSync(scratch, { recursive: true, force: true });\n }\n\n const entry = packed[0];\n const files = (entry?.files ?? []).map((f) => f.path);\n const stray = files.filter(\n (f) => !ALLOWED_PACK_PATHS.some((re) => re.test(f)),\n );\n if (stray.length > 0) {\n throw validationError(\n `The package would ship files an experience must not contain: ${stray.slice(0, 5).join(', ')}.`,\n 'Restrict package.json \"files\" to dist, manifest.json, ekanos.json, ekanos.lock.json and contracts.',\n );\n }\n for (const required of [\n 'manifest.json',\n 'dist/experience.js',\n 'ekanos.lock.json',\n ]) {\n if (!files.includes(required)) {\n throw validationError(\n `The package would be missing ${required}.`,\n 'Add it to package.json \"files\".',\n );\n }\n }\n\n return {\n slug: project.slug,\n version: output.manifest.version,\n digest: output.manifest.digest,\n filename: entry?.filename,\n files,\n integrity: entry?.integrity,\n tarball,\n };\n}\n\n/**\n * `ekanos publish` for an experience. Defined, but gated:\n *\n * 1. Active integration overrides refuse it (exit 3, naming each integration and path): what\n * you ran is not what a host would resolve. An integration of the same repo is not an override.\n * 2. A tracked ekanos.local.json, any `ignore` entry, or a failing gate refuse.\n * 3. Without --dry-run it stops: the host cannot admit an experience yet.\n * 4. With --dry-run it builds and packs, and checks the tarball holds no\n * integration code.\n *\n * Runs before any login: a dry run needs no session.\n */\nexport async function runExperiencePublish(\n ctx: CliContext,\n args: ExperiencePublishArgs,\n): Promise<ExitCode> {\n const gate = await gateExperience(args);\n const { project, result } = gate;\n const failed = failOnErrors(\n ctx,\n 'publish',\n result.findings,\n { slug: project.slug },\n project.dir,\n );\n if (failed !== null) return failed;\n\n if (!args.dryRun) {\n throw gateFailedError(\n EXPERIENCE_NOT_SUPPORTED,\n 'Run \"ekanos publish --dry-run\" to build and pack it; the host has no admission path for experiences until it ships one.',\n );\n }\n\n const packed = await packExperience(gate);\n\n return ctx.succeed(\n {\n slug: packed.slug,\n version: packed.version,\n digest: packed.digest,\n dryRun: true,\n package: {\n filename: packed.filename,\n files: packed.files,\n integrity: packed.integrity,\n },\n published: false,\n },\n `Dry run: packed ${packed.slug}@${packed.version} (${packed.files.length} files, ${packed.digest.slice(0, 18)}…). Nothing was published.`,\n );\n}\n\nexport const EXPERIENCE_NOT_SUPPORTED =\n 'Publishing an experience is not supported by the host yet.';\n"]}
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/experience/publish.ts"],"names":[],"mappings":"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","sourcesContent":["import { canonicalJson } from '@ekanos/integration-schema';\nimport { execFileSync } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport { requireSession, resolveAuthEnvironment } from '../auth/session';\nimport {\n promptYesNo,\n resolveSourceSlug,\n submitWithOneRefresh,\n verifyPublishTarget,\n} from '../commands/publish';\nimport type { CliContext } from '../context';\nimport { activeOverridesFor, harnessPinsResolver } from '../dev-experience';\nimport { gateFailedError, preconditionError, validationError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { MAX_ARCHIVE_BYTES } from '../pack';\nimport { MAX_MANIFEST_BYTES } from '../publish-api';\nimport { fetchToolchainReport } from '../toolchain-api';\nimport { shortHost } from '../ui';\nimport { emitExperience } from './build';\nimport { type ExperienceCheckResult, runExperienceChecks } from './checks';\nimport {\n CLEAN_ROOM_ENV,\n buildInCleanRoom,\n hasBuildableLockfile,\n} from './clean-room';\nimport { isOverridesFileTracked } from './overrides';\nimport {\n type ExperienceProject,\n LOCAL_OVERRIDES_FILE,\n readExperienceProject,\n} from './project';\nimport { failOnErrors } from './report';\n\nexport interface ExperiencePublishArgs {\n dir: string;\n dryRun: boolean;\n env: Record<string, string | undefined>;\n /** The host to submit to (a real run only). Falls back to ekanos.json, then the stored login. */\n host?: string;\n /** The source slug to submit for (a real run only). Falls back to ekanos.json \"source\". */\n source?: string;\n /** Skips the multi-seat confirmation when the source came from ekanos.json. */\n yes?: boolean;\n /** Whether stdin is a real terminal; injectable for tests. */\n stdinIsTTY?: boolean;\n /** The y/N prompt; injectable for tests. */\n confirm?: (question: string) => Promise<boolean>;\n}\n\n/**\n * What a packed experience may contain: its own source, build output and pins,\n * nothing else. The source ships because the host rebuilds `dist/` from it in\n * a clean room and refuses a package whose bundle is not what that source\n * produces; the lockfile ships so that rebuild resolves the same dependencies.\n */\nconst ALLOWED_PACK_PATHS = [\n /^package\\.json$/,\n /^npm-shrinkwrap\\.json$/,\n /^tsconfig[^/]*\\.json$/,\n /^src\\/.+/,\n /^ekanos\\.json$/,\n /^manifest\\.json$/,\n /^ekanos\\.lock\\.json$/,\n /^contracts\\/[^/]+\\.contract\\.json$/,\n /^contracts\\/\\.gitkeep$/,\n /^dist\\/[^/]+$/,\n /^README\\.md$/,\n /^LICENSE$/,\n];\n\nexport interface ExperienceGate {\n project: ExperienceProject;\n result: ExperienceCheckResult;\n /** The caller's environment (read for the clean-room switches). */\n env?: Record<string, string | undefined>;\n}\n\n/**\n * The refusals and the gate every experience publish passes, before anything\n * is built: active overrides, a tracked overrides file, `ignore` entries, then\n * the experience checks. Throws a CliError for the first three; the findings\n * come back for the caller to render (one envelope for one package, a table\n * for a repo).\n */\nexport async function gateExperience(\n args: ExperiencePublishArgs,\n): Promise<ExperienceGate> {\n const project = readExperienceProject(args.dir);\n\n const overrides = await activeOverridesFor(project.dir, args.env);\n if (overrides.length > 0) {\n throw validationError(\n `Cannot publish with ${overrides.length} local integration override${overrides.length === 1 ? '' : 's'} active: ${overrides\n .map((o) => `${o.integration} -> ${o.path}`)\n .join(', ')}.`,\n `Delete ${LOCAL_OVERRIDES_FILE} (and unset EKANOS_INTEGRATION_OVERRIDE_*) so every integration resolves to its published version, then re-run.`,\n );\n }\n\n if (isOverridesFileTracked(project)) {\n throw validationError(\n `${LOCAL_OVERRIDES_FILE} is tracked by git.`,\n `It is per-developer: \"git rm --cached ${LOCAL_OVERRIDES_FILE}\" and add it to .gitignore.`,\n );\n }\n\n const ignored = project.ekanos.ignore ?? [];\n if (ignored.length > 0) {\n throw validationError(\n `ekanos.json \"ignore\" excludes ${ignored.join(', ')} from the boundary scan, so it cannot be published.`,\n 'Remove the \"ignore\" entries (move the archive out of the project) and re-run.',\n );\n }\n\n const result = await runExperienceChecks(project, {\n typecheck: false,\n loadDefinition: true,\n inlineSdk: args.env.EKANOS_INLINE_SDK,\n pinsResolver: await harnessPinsResolver(project.dir, args.env),\n });\n\n return { project, result, env: args.env };\n}\n\n/**\n * npm never packs `package-lock.json`, but it does pack `npm-shrinkwrap.json`\n * (and `npm ci` honours it). The host rebuilds the shipped source against the\n * dependencies it was built with, so the lockfile the bundle was built from\n * travels under that name for the pack and is removed afterwards. A project\n * that keeps its own shrinkwrap is left as it is.\n */\nfunction stageShrinkwrap(\n dir: string,\n content: string | null,\n): { restore: () => void } {\n const shrinkwrap = path.join(dir, 'npm-shrinkwrap.json');\n\n if (content === null || fs.existsSync(shrinkwrap)) {\n return { restore: () => {} };\n }\n fs.writeFileSync(shrinkwrap, content);\n\n return { restore: () => fs.rmSync(shrinkwrap, { force: true }) };\n}\n\nexport interface PackedExperience {\n slug: string;\n version: string;\n digest: string;\n filename: string | undefined;\n files: string[];\n integrity: string | undefined;\n /** Where the tarball was kept, when `artifactDir` was given. */\n tarball: string | undefined;\n}\n\n/**\n * Build the experience and pack it with `npm pack`, checking the tarball holds\n * nothing but its own build output. `artifactDir` keeps the tarball (a repo\n * dry run puts it under `.ekanos/publish/`); otherwise it is discarded.\n */\nexport async function packExperience(\n gate: ExperienceGate,\n artifactDir?: string,\n): Promise<PackedExperience> {\n const { project, result } = gate;\n if (!hasBuildableLockfile(project.dir)) {\n throw preconditionError(\n 'There is no lockfile (package-lock.json, npm-shrinkwrap.json or pnpm-lock.yaml), so the host could not rebuild this experience from its source with the dependencies you built with.',\n 'Run \"npm install --package-lock-only\" (or install with pnpm in a workspace) and commit the lockfile, then re-run.',\n );\n }\n const output = await emitExperience(project, result);\n\n // The bundle that ships is the one built in a clean room from the lockfile\n // that ships, the way the host's gate rebuilds it, not the one built in the\n // working tree (a pnpm workspace's tree is not what a clean `npm ci` makes).\n const ownLock = path.join(project.dir, 'package-lock.json');\n let shippedLockfile: string | null = fs.existsSync(ownLock)\n ? fs.readFileSync(ownLock, 'utf8')\n : null;\n if (gate.env?.[CLEAN_ROOM_ENV] !== 'off') {\n const room = await buildInCleanRoom(project, gate.env);\n fs.writeFileSync(\n path.join(project.dir, 'dist', 'experience.js'),\n room.bundle,\n );\n const written = JSON.parse(\n fs.readFileSync(path.join(project.dir, 'dist', 'surfaces.json'), 'utf8'),\n ) as unknown;\n if (canonicalJson(written) !== canonicalJson(room.surfaces)) {\n throw validationError(\n 'The surfaces your working tree declares are not what the clean-room build declares (different dependency versions, or a stale build).',\n 'Reinstall dependencies from the lockfile, run \"ekanos build\", and re-run \"ekanos publish\".',\n );\n }\n shippedLockfile = room.lockfile;\n }\n\n const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'ekanos-exp-pack-'));\n let packed: Array<{\n filename: string;\n files: Array<{ path: string; size: number }>;\n integrity?: string;\n }>;\n let tarball: string | undefined;\n const shrinkwrap = stageShrinkwrap(project.dir, shippedLockfile);\n try {\n const stdout = execFileSync(\n 'npm',\n ['pack', '--json', '--ignore-scripts', '--pack-destination', scratch],\n { cwd: project.dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },\n );\n packed = JSON.parse(stdout) as typeof packed;\n const first = packed[0];\n if (artifactDir && first) {\n fs.mkdirSync(artifactDir, { recursive: true });\n tarball = path.join(artifactDir, path.basename(first.filename));\n fs.copyFileSync(path.join(scratch, first.filename), tarball);\n }\n } catch (error) {\n throw preconditionError(\n `npm pack failed: ${error instanceof Error ? error.message : String(error)}`,\n 'Make sure npm is installed and package.json \"files\" lists src, dist, manifest.json, ekanos.json, ekanos.lock.json, contracts, tsconfig.json and npm-shrinkwrap.json.',\n );\n } finally {\n shrinkwrap.restore();\n fs.rmSync(scratch, { recursive: true, force: true });\n }\n\n const entry = packed[0];\n const files = (entry?.files ?? []).map((f) => f.path);\n const stray = files.filter(\n (f) => !ALLOWED_PACK_PATHS.some((re) => re.test(f)),\n );\n if (stray.length > 0) {\n throw validationError(\n `The package would ship files an experience must not contain: ${stray.slice(0, 5).join(', ')}.`,\n 'Restrict package.json \"files\" to src, dist, manifest.json, ekanos.json, ekanos.lock.json, contracts, tsconfig.json and npm-shrinkwrap.json.',\n );\n }\n for (const required of [\n 'manifest.json',\n 'dist/experience.js',\n 'dist/surfaces.json',\n 'ekanos.lock.json',\n 'npm-shrinkwrap.json',\n project.entry,\n ]) {\n if (!files.includes(required)) {\n throw validationError(\n `The package would be missing ${required}.`,\n 'Add it to package.json \"files\".',\n );\n }\n }\n\n return {\n slug: project.slug,\n version: output.manifest.version,\n digest: output.manifest.digest,\n filename: entry?.filename,\n files,\n integrity: entry?.integrity,\n tarball,\n };\n}\n\n/** What submitting a gated, packed experience came to. */\nexport type ExperienceSubmission =\n | { status: 'unsupported'; host: string }\n | { status: 'submitted'; data: Record<string, unknown>; human: string };\n\n/**\n * Submit a gated experience to a host that admits them.\n *\n * The host is asked first (the toolchain route's `capabilities`), because an\n * older deployment answers a `kind=experience` submission as if it were an\n * integration and would file it under the wrong gate: a host that does not\n * advertise `experienceSubmissions` gets nothing sent and the caller keeps the\n * dry-run-only message. Authorization is the same as an integration's (the\n * developer seat on the source, checked server-side; the session refreshed\n * once on a 401). The submission is the packed tarball plus the built\n * `manifest.json`, which the host re-validates and cross-checks against the\n * archive in its own gate.\n */\nexport async function submitExperience(\n ctx: CliContext,\n args: ExperiencePublishArgs,\n gate: ExperienceGate,\n): Promise<ExperienceSubmission> {\n const { project } = gate;\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: project.dir,\n });\n const session: StoredSession = requireSession(env);\n\n const resolution = await resolveSourceSlug(env, session, args.source, {\n project: { source: project.ekanos.source },\n });\n\n const version = readVersion(gate);\n const verified = await verifyPublishTarget(ctx, env, resolution.session, {\n source: resolution.source,\n explicit: resolution.explicit,\n slug: project.slug,\n version,\n yes: args.yes === true,\n stdinIsTTY: args.stdinIsTTY ?? process.stdin.isTTY === true,\n confirm: args.confirm ?? promptYesNo,\n });\n\n const toolchain = await fetchToolchainReport(\n env.host,\n verified.session.accessToken,\n );\n if (\n toolchain.status !== 'ok' ||\n toolchain.report.capabilities?.experienceSubmissions !== true\n ) {\n return { status: 'unsupported', host: env.host };\n }\n\n ctx.log(`Packing ${project.slug}@${version}…`);\n\n const artifactDir = fs.mkdtempSync(\n path.join(os.tmpdir(), 'ekanos-exp-submit-'),\n );\n let archive: Uint8Array;\n let digest: string;\n let files: string[];\n try {\n const packed = await packExperience(gate, artifactDir);\n if (!packed.tarball) {\n throw preconditionError(\n 'The packed experience was not kept.',\n 'This is a bug in @ekanos/cli; report it.',\n );\n }\n archive = new Uint8Array(fs.readFileSync(packed.tarball));\n digest = packed.digest;\n files = packed.files;\n } finally {\n fs.rmSync(artifactDir, { recursive: true, force: true });\n }\n\n if (archive.length > MAX_ARCHIVE_BYTES) {\n throw validationError(\n `The packed experience is ${archive.length} bytes, over the ${MAX_ARCHIVE_BYTES}-byte submission limit.`,\n 'Drop dependencies the bundle does not need, or split the experience.',\n );\n }\n\n const manifest = fs.readFileSync(\n path.join(project.dir, 'manifest.json'),\n 'utf8',\n );\n if (Buffer.byteLength(manifest, 'utf8') > MAX_MANIFEST_BYTES) {\n throw validationError(\n `manifest.json is larger than the ${MAX_MANIFEST_BYTES}-byte manifest limit.`,\n 'Trim the workflow templates or widget declarations it carries.',\n );\n }\n\n if (!ctx.jsonMode) {\n ctx.log(\n ctx.ui.ok(\n `${ctx.ui.glyphs.ok} packed ${project.slug}@${version} (${files.length} files).`,\n ),\n );\n }\n\n const source = verified.sourceSeat.slug;\n ctx.log(`Submitting to ${env.host} (source \"${source}\")…`);\n\n const receipt = await submitWithOneRefresh(env, verified.session, {\n source,\n slug: project.slug,\n kind: 'experience',\n version,\n manifest,\n archive,\n });\n\n if (!ctx.jsonMode) {\n ctx.log(\n ctx.ui.ok(\n `${ctx.ui.glyphs.ok} submitted ${receipt.slug}@${receipt.version} (state \"${receipt.state}\").`,\n ),\n );\n }\n\n return {\n status: 'submitted',\n data: {\n host: env.host,\n source: {\n id: verified.sourceSeat.id,\n slug: verified.sourceSeat.slug,\n name: verified.sourceSeat.name,\n },\n kind: 'experience',\n slug: receipt.slug,\n version: receipt.version,\n submissionId: receipt.submissionId,\n state: receipt.state,\n archiveSha256: receipt.archiveSha256,\n digest,\n files,\n published: true,\n },\n human: ctx.ui.result({\n title: 'Submitted',\n rows: [\n ['Experience', `${receipt.slug}@${receipt.version}`],\n ['State', receipt.state],\n ['Source', source],\n ['Host', shortHost(env.host)],\n ],\n notes: [],\n }),\n };\n}\n\nfunction readVersion(gate: ExperienceGate): string {\n try {\n const pkg = JSON.parse(\n fs.readFileSync(path.join(gate.project.dir, 'package.json'), 'utf8'),\n ) as { version?: unknown };\n if (typeof pkg.version === 'string' && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Reported below with the fix.\n }\n\n throw preconditionError(\n 'package.json is missing or has no \"version\".',\n 'An experience is an npm package; set a semver \"version\" in package.json.',\n );\n}\n\n/**\n * `ekanos publish` for an experience.\n *\n * 1. Active integration overrides refuse it (exit 3, naming each integration and path): what\n * you ran is not what a host would resolve. An integration of the same repo is not an override.\n * 2. A tracked ekanos.local.json, any `ignore` entry, or a failing gate refuse.\n * 3. With --dry-run it builds and packs, and checks the tarball holds no\n * integration code. Nothing is sent.\n * 4. Without --dry-run it builds, packs and submits to a host that advertises\n * experience submissions. A host that does not (an older deployment) gets\n * nothing and the run ends with the dry-run-only message, as before.\n *\n * The refusals and the dry run run before any login: a dry run needs no session.\n */\nexport async function runExperiencePublish(\n ctx: CliContext,\n args: ExperiencePublishArgs,\n): Promise<ExitCode> {\n const gate = await gateExperience(args);\n const { project, result } = gate;\n const failed = failOnErrors(\n ctx,\n 'publish',\n result.findings,\n { slug: project.slug },\n project.dir,\n );\n if (failed !== null) return failed;\n\n if (!args.dryRun) {\n const submission = await submitExperience(ctx, args, gate);\n\n if (submission.status === 'unsupported') {\n throw gateFailedError(\n EXPERIENCE_NOT_SUPPORTED,\n `Run \"ekanos publish --dry-run\" to build and pack it; ${submission.host} has no admission path for experiences until it ships one.`,\n );\n }\n\n return ctx.succeed(submission.data, submission.human);\n }\n\n const packed = await packExperience(gate);\n\n return ctx.succeed(\n {\n slug: packed.slug,\n version: packed.version,\n digest: packed.digest,\n dryRun: true,\n package: {\n filename: packed.filename,\n files: packed.files,\n integrity: packed.integrity,\n },\n published: false,\n },\n `Dry run: packed ${packed.slug}@${packed.version} (${packed.files.length} files, ${packed.digest.slice(0, 18)}…). Nothing was published.`,\n );\n}\n\nexport const EXPERIENCE_NOT_SUPPORTED =\n 'Publishing an experience is not supported by the host yet.';\n"]}
|