@ekanos/cli 0.16.0 → 0.17.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 +62 -4
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +118 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/audiences.d.ts +31 -0
- package/dist/commands/audiences.js +239 -0
- package/dist/commands/audiences.js.map +1 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.js +41 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +43 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/codegen.d.ts +2 -0
- package/dist/commands/codegen.js +49 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/contract.d.ts +52 -0
- package/dist/commands/contract.js +300 -0
- package/dist/commands/contract.js.map +1 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.js +186 -12
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/index-experience.d.ts +31 -0
- package/dist/commands/index-experience.js +21 -0
- package/dist/commands/index-experience.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.js +39 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/publish.d.ts +59 -0
- package/dist/commands/publish.js +118 -65
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/scope-notice.d.ts +6 -0
- package/dist/commands/scope-notice.js +15 -0
- package/dist/commands/scope-notice.js.map +1 -1
- package/dist/commands/sources.js +2 -0
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +150 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/teams-audience.d.ts +12 -0
- package/dist/commands/teams-audience.js +144 -0
- package/dist/commands/teams-audience.js.map +1 -0
- package/dist/commands/teams-scope.js +4 -0
- package/dist/commands/teams-scope.js.map +1 -1
- package/dist/commands/teams.d.ts +1 -1
- package/dist/commands/teams.js +6 -1
- package/dist/commands/teams.js.map +1 -1
- package/dist/compatibility.d.ts +10 -1
- package/dist/compatibility.js +10 -1
- package/dist/compatibility.js.map +1 -1
- package/dist/dev-experience.d.ts +125 -0
- package/dist/dev-experience.js +128 -0
- package/dist/dev-experience.js.map +1 -0
- package/dist/experience/boundary.d.ts +12 -0
- package/dist/experience/boundary.js +260 -0
- package/dist/experience/boundary.js.map +1 -0
- package/dist/experience/build.d.ts +16 -0
- package/dist/experience/build.js +90 -0
- package/dist/experience/build.js.map +1 -0
- package/dist/experience/checks.d.ts +29 -0
- package/dist/experience/checks.js +133 -0
- package/dist/experience/checks.js.map +1 -0
- package/dist/experience/contract-sources.d.ts +36 -0
- package/dist/experience/contract-sources.js +111 -0
- package/dist/experience/contract-sources.js.map +1 -0
- package/dist/experience/definition-checks.d.ts +26 -0
- package/dist/experience/definition-checks.js +201 -0
- package/dist/experience/definition-checks.js.map +1 -0
- package/dist/experience/definition-shape.d.ts +60 -0
- package/dist/experience/definition-shape.js +2 -0
- package/dist/experience/definition-shape.js.map +1 -0
- package/dist/experience/init.d.ts +10 -0
- package/dist/experience/init.js +64 -0
- package/dist/experience/init.js.map +1 -0
- package/dist/experience/integrations-codegen.d.ts +17 -0
- package/dist/experience/integrations-codegen.js +51 -0
- package/dist/experience/integrations-codegen.js.map +1 -0
- package/dist/experience/json-schema-to-ts.d.ts +1 -0
- package/dist/experience/json-schema-to-ts.js +90 -0
- package/dist/experience/json-schema-to-ts.js.map +1 -0
- package/dist/experience/load-experience.d.ts +23 -0
- package/dist/experience/load-experience.js +115 -0
- package/dist/experience/load-experience.js.map +1 -0
- package/dist/experience/overrides.d.ts +8 -0
- package/dist/experience/overrides.js +21 -0
- package/dist/experience/overrides.js.map +1 -0
- package/dist/experience/pin-integration.d.ts +8 -0
- package/dist/experience/pin-integration.js +17 -0
- package/dist/experience/pin-integration.js.map +1 -0
- package/dist/experience/pin-ops.d.ts +12 -0
- package/dist/experience/pin-ops.js +36 -0
- package/dist/experience/pin-ops.js.map +1 -0
- package/dist/experience/pins.d.ts +35 -0
- package/dist/experience/pins.js +91 -0
- package/dist/experience/pins.js.map +1 -0
- package/dist/experience/project.d.ts +52 -0
- package/dist/experience/project.js +149 -0
- package/dist/experience/project.js.map +1 -0
- package/dist/experience/publish.d.ts +51 -0
- package/dist/experience/publish.js +144 -0
- package/dist/experience/publish.js.map +1 -0
- package/dist/experience/regenerate.d.ts +10 -0
- package/dist/experience/regenerate.js +33 -0
- package/dist/experience/regenerate.js.map +1 -0
- package/dist/experience/report.d.ts +9 -0
- package/dist/experience/report.js +23 -0
- package/dist/experience/report.js.map +1 -0
- package/dist/harness-scaffold.d.ts +52 -18
- package/dist/harness-scaffold.js +189 -9
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.js +139 -7
- package/dist/index.js.map +1 -1
- package/dist/project-checks.d.ts +12 -0
- package/dist/project-checks.js +46 -0
- package/dist/project-checks.js.map +1 -1
- package/dist/project.d.ts +14 -0
- package/dist/project.js +10 -0
- package/dist/project.js.map +1 -1
- package/dist/repo/publish.d.ts +42 -0
- package/dist/repo/publish.js +225 -0
- package/dist/repo/publish.js.map +1 -0
- package/dist/repo/scaffold.d.ts +30 -0
- package/dist/repo/scaffold.js +165 -0
- package/dist/repo/scaffold.js.map +1 -0
- package/dist/teams-api.d.ts +37 -1
- package/dist/teams-api.js +62 -1
- package/dist/teams-api.js.map +1 -1
- package/dist/validate-findings.js +2 -1
- package/dist/validate-findings.js.map +1 -1
- package/package.json +2 -2
- package/templates/ekanos.json.tmpl +1 -0
- package/templates/experience/AGENTS.md.tmpl +22 -0
- package/templates/experience/ekanos.json.tmpl +7 -0
- package/templates/experience/ekanos.lock.json.tmpl +4 -0
- package/templates/experience/eslint.config.mjs.tmpl +25 -0
- package/templates/experience/gitignore.tmpl +17 -0
- package/templates/experience/package.json.tmpl +41 -0
- package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
- package/templates/experience/src/experience.ts.tmpl +30 -0
- package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
- package/templates/experience/tsconfig.json.tmpl +20 -0
- package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
- package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
- package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
- package/templates/harness/app/experience/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
- package/templates/harness/app/page.experience.tsx.tmpl +5 -0
- package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
- package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
- package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
- package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
- package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
- package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
- package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
- package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
- package/templates/harness/styles/globals.css.tmpl +15 -1
- package/templates/package.json.tmpl +4 -4
package/dist/compatibility.d.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* shell must contain. Editing a comment in a template does not count — that is
|
|
32
32
|
* what the template fingerprint in the marker is for.
|
|
33
33
|
*/
|
|
34
|
-
export declare const SHELL_CONTRACT =
|
|
34
|
+
export declare const SHELL_CONTRACT = 5;
|
|
35
35
|
/**
|
|
36
36
|
* Contract history — what each bump changed, so a mismatch message can be
|
|
37
37
|
* read against it:
|
|
@@ -45,6 +45,15 @@ export declare const SHELL_CONTRACT = 3;
|
|
|
45
45
|
* 3 adds `app/[slug]/page.tsx`, re-exporting the new
|
|
46
46
|
* `IntegrationIndexPage` from `/routes` so `/<slug>` redirects to
|
|
47
47
|
* `/<slug>/widgets` instead of answering 404 under a working toolbar.
|
|
48
|
+
* 4 adds `app/%5F%5Fekanos/api/[...path]/route.ts`, serving
|
|
49
|
+
* `/__ekanos/api/*` from the new `createEkanosApiHandlers` in
|
|
50
|
+
* `@ekanos/harness/api` (integrations, operations, approvals, run log). A
|
|
51
|
+
* harness without that export cannot back the route.
|
|
52
|
+
* 5 moves the widgets, tile, activation and triggers surfaces to
|
|
53
|
+
* `app/integrations/[slug]/preview/**` (a `PreviewLayout` and
|
|
54
|
+
* `PreviewIndexPage` from `/routes`), and turns the old `app/[slug]/**`
|
|
55
|
+
* pages into `LegacyPreviewRedirect` re-exports. A harness without those
|
|
56
|
+
* exports cannot back the shell.
|
|
48
57
|
*/
|
|
49
58
|
/**
|
|
50
59
|
* The contract an installed `@ekanos/harness` declares it needs, from
|
package/dist/compatibility.js
CHANGED
|
@@ -36,7 +36,7 @@ import { readTemplateSource, templatesDir } from './templates.js';
|
|
|
36
36
|
* shell must contain. Editing a comment in a template does not count — that is
|
|
37
37
|
* what the template fingerprint in the marker is for.
|
|
38
38
|
*/
|
|
39
|
-
export const SHELL_CONTRACT =
|
|
39
|
+
export const SHELL_CONTRACT = 5;
|
|
40
40
|
/**
|
|
41
41
|
* Contract history — what each bump changed, so a mismatch message can be
|
|
42
42
|
* read against it:
|
|
@@ -50,6 +50,15 @@ export const SHELL_CONTRACT = 3;
|
|
|
50
50
|
* 3 adds `app/[slug]/page.tsx`, re-exporting the new
|
|
51
51
|
* `IntegrationIndexPage` from `/routes` so `/<slug>` redirects to
|
|
52
52
|
* `/<slug>/widgets` instead of answering 404 under a working toolbar.
|
|
53
|
+
* 4 adds `app/%5F%5Fekanos/api/[...path]/route.ts`, serving
|
|
54
|
+
* `/__ekanos/api/*` from the new `createEkanosApiHandlers` in
|
|
55
|
+
* `@ekanos/harness/api` (integrations, operations, approvals, run log). A
|
|
56
|
+
* harness without that export cannot back the route.
|
|
57
|
+
* 5 moves the widgets, tile, activation and triggers surfaces to
|
|
58
|
+
* `app/integrations/[slug]/preview/**` (a `PreviewLayout` and
|
|
59
|
+
* `PreviewIndexPage` from `/routes`), and turns the old `app/[slug]/**`
|
|
60
|
+
* pages into `LegacyPreviewRedirect` re-exports. A harness without those
|
|
61
|
+
* exports cannot back the shell.
|
|
53
62
|
*/
|
|
54
63
|
/**
|
|
55
64
|
* The contract an installed `@ekanos/harness` declares it needs, from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { preconditionError } from './errors';\nimport { readTemplateSource, templatesDir } from './templates';\n\n/**\n * The shell templates ship with `@ekanos/cli`; the code they import ships with\n * `@ekanos/harness`. The two version independently, so a partner can upgrade\n * one without the other — and the interesting failure is the quiet one:\n *\n * `pnpm up @ekanos/harness` → 0.2, which moves an export.\n * `ekanos dev` regenerates from the OLD CLI's templates and stamps \"0.2\"\n * into the marker. Every later run now considers the shell current. It is\n * not: it was generated against an obsolete contract, and what the partner\n * sees is a module-resolution error rather than \"these are incompatible\".\n *\n * That directly attacks the promise the package boundary exists to keep — ship\n * a harness fix as a version bump a partner picks up with `pnpm up`. So the\n * CLI declares which `@ekanos/harness` versions ITS templates are written\n * against, checks the installed one before generating, and names the\n * incompatibility instead of letting a resolution error stand in for it.\n */\n\n/**\n * The shell contract this CLI's templates GENERATE.\n *\n * An integer, not a semver range. A range can only INFER compatibility from\n * version numbers — it cannot tell a harness bugfix from a change to the shell\n * the harness requires, so it either blocks upgrades that were fine or waves\n * through ones that were not. The integer is declared by both sides and is\n * authoritative.\n *\n * **Bump this only when the generated shell's coupling to the package\n * changes**: an export renamed or moved between `/app` and `/routes`, the\n * props of `RootLayout` or `IndexPage`, the server/client split, the alias\n * specifier, which side owns `@import 'tailwindcss'`, or the set of files the\n * shell must contain. Editing a comment in a template does not count — that is\n * what the template fingerprint in the marker is for.\n */\nexport const SHELL_CONTRACT = 3;\n\n/**\n * Contract history — what each bump changed, so a mismatch message can be\n * read against it:\n *\n * 1 the original ten-file shell: pages re-exporting `/routes`, the client\n * shell + index page injecting the registry into `/app`.\n * 2 adds `app/api/integrations/[slug]/[...path]/route.ts`, injecting the\n * registry into the new `@ekanos/harness/api` server entry. A harness\n * without that export cannot back the route; a shell without the route\n * leaves that harness's live-mode OAuth loop unreachable.\n * 3 adds `app/[slug]/page.tsx`, re-exporting the new\n * `IntegrationIndexPage` from `/routes` so `/<slug>` redirects to\n * `/<slug>/widgets` instead of answering 404 under a working toolbar.\n */\n\n/**\n * The contract an installed `@ekanos/harness` declares it needs, from\n * `{ \"ekanos\": { \"shellContract\": N } }` in its package.json.\n *\n * A package that declares nothing is read as contract 1 — the contract that\n * shipped first — so the harness released before this existed keeps working.\n * That default is deliberately silent, which is why `@ekanos/harness` asserts\n * the field's presence in its own pack test rather than relying on it.\n */\nexport function declaredShellContract(harnessManifest: unknown): number {\n if (typeof harnessManifest !== 'object' || harnessManifest === null) return 1;\n const ekanos = (harnessManifest as { ekanos?: unknown }).ekanos;\n if (typeof ekanos !== 'object' || ekanos === null) return 1;\n const declared = (ekanos as { shellContract?: unknown }).shellContract;\n return Number.isInteger(declared) ? (declared as number) : 1;\n}\n\n/** `[major, minor, patch]`, with anything non-numeric treated as 0. */\nfunction parseVersion(version: string): [number, number, number] {\n const core = version.split('+')[0]!.split('-')[0]!;\n const parts = core.split('.').map((part) => Number.parseInt(part, 10));\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a);\n const right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left[i]! !== right[i]!) return left[i]! < right[i]! ? -1 : 1;\n }\n return 0;\n}\n\n/** This CLI's own version, read from its package.json at runtime. */\nexport function cliVersion(): string {\n const manifestPath = path.join(templatesDir(), '..', 'package.json');\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n return typeof version === 'string' ? version : '0.0.0';\n}\n\n/**\n * Fail unless this CLI's templates generate the shell the installed harness\n * expects.\n *\n * The templates ship with `@ekanos/cli`; the code they import ships with\n * `@ekanos/harness`. The two version independently, so a partner can upgrade\n * one without the other — and the failure that matters is the quiet one:\n * `pnpm up @ekanos/harness` moves an export, `ekanos dev` regenerates from the\n * OLD CLI's templates, and the partner sees a module-resolution error rather\n * than anything naming the incompatibility.\n *\n * Byte-comparing generated files cannot catch it: the old templates' bytes are\n * exactly what the old CLI intends, so nothing regenerates differently and the\n * shell stays wired to something that moved. Only a declared contract can.\n *\n * Runs before anything is written, so an incompatible pair produces no shell\n * at all — a broken shell that builds is worse than no shell.\n */\nexport function assertHarnessCompatible(params: {\n harnessVersion: string;\n harnessManifest: unknown;\n installHint: (packageSpec: string) => string;\n}): void {\n const required = declaredShellContract(params.harnessManifest);\n if (required === SHELL_CONTRACT) return;\n\n if (required > SHELL_CONTRACT) {\n throw preconditionError(\n `@ekanos/cli ${cliVersion()} is too old for @ekanos/harness ` +\n `${params.harnessVersion}: that harness needs shell contract ` +\n `${required}, and this CLI generates ${SHELL_CONTRACT}.`,\n `Upgrade the CLI: ${params.installHint('@ekanos/cli@latest')}. Then ` +\n 're-run \"ekanos dev\" and the shell is regenerated for you.',\n );\n }\n\n throw preconditionError(\n `@ekanos/harness ${params.harnessVersion} is too old for @ekanos/cli ` +\n `${cliVersion()}: this CLI generates shell contract ${SHELL_CONTRACT}, ` +\n `and that harness needs ${required}.`,\n `Upgrade it: ${params.installHint('@ekanos/harness@latest')}.`,\n );\n}\n\n/**\n * A fingerprint of the template SOURCES, independent of the slug they are\n * rendered with.\n *\n * The harness version alone cannot certify a shell: the templates can change\n * while it stays put (a CLI patch release), and it can change while they stay\n * put. Hashing the sources means any edit to what we generate invalidates\n * every shell generated before it, including one produced by a CLI version\n * that never shipped.\n */\nexport function templatesFingerprint(templateNames: readonly string[]): string {\n const hash = createHash('sha256');\n for (const name of [...templateNames].sort()) {\n hash.update(name);\n hash.update('\\0');\n hash.update(readTemplateSource(name));\n hash.update('\\0');\n }\n return hash.digest('hex').slice(0, 16);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { preconditionError } from './errors';\nimport { readTemplateSource, templatesDir } from './templates';\n\n/**\n * The shell templates ship with `@ekanos/cli`; the code they import ships with\n * `@ekanos/harness`. The two version independently, so a partner can upgrade\n * one without the other — and the interesting failure is the quiet one:\n *\n * `pnpm up @ekanos/harness` → 0.2, which moves an export.\n * `ekanos dev` regenerates from the OLD CLI's templates and stamps \"0.2\"\n * into the marker. Every later run now considers the shell current. It is\n * not: it was generated against an obsolete contract, and what the partner\n * sees is a module-resolution error rather than \"these are incompatible\".\n *\n * That directly attacks the promise the package boundary exists to keep — ship\n * a harness fix as a version bump a partner picks up with `pnpm up`. So the\n * CLI declares which `@ekanos/harness` versions ITS templates are written\n * against, checks the installed one before generating, and names the\n * incompatibility instead of letting a resolution error stand in for it.\n */\n\n/**\n * The shell contract this CLI's templates GENERATE.\n *\n * An integer, not a semver range. A range can only INFER compatibility from\n * version numbers — it cannot tell a harness bugfix from a change to the shell\n * the harness requires, so it either blocks upgrades that were fine or waves\n * through ones that were not. The integer is declared by both sides and is\n * authoritative.\n *\n * **Bump this only when the generated shell's coupling to the package\n * changes**: an export renamed or moved between `/app` and `/routes`, the\n * props of `RootLayout` or `IndexPage`, the server/client split, the alias\n * specifier, which side owns `@import 'tailwindcss'`, or the set of files the\n * shell must contain. Editing a comment in a template does not count — that is\n * what the template fingerprint in the marker is for.\n */\nexport const SHELL_CONTRACT = 5;\n\n/**\n * Contract history — what each bump changed, so a mismatch message can be\n * read against it:\n *\n * 1 the original ten-file shell: pages re-exporting `/routes`, the client\n * shell + index page injecting the registry into `/app`.\n * 2 adds `app/api/integrations/[slug]/[...path]/route.ts`, injecting the\n * registry into the new `@ekanos/harness/api` server entry. A harness\n * without that export cannot back the route; a shell without the route\n * leaves that harness's live-mode OAuth loop unreachable.\n * 3 adds `app/[slug]/page.tsx`, re-exporting the new\n * `IntegrationIndexPage` from `/routes` so `/<slug>` redirects to\n * `/<slug>/widgets` instead of answering 404 under a working toolbar.\n * 4 adds `app/%5F%5Fekanos/api/[...path]/route.ts`, serving\n * `/__ekanos/api/*` from the new `createEkanosApiHandlers` in\n * `@ekanos/harness/api` (integrations, operations, approvals, run log). A\n * harness without that export cannot back the route.\n * 5 moves the widgets, tile, activation and triggers surfaces to\n * `app/integrations/[slug]/preview/**` (a `PreviewLayout` and\n * `PreviewIndexPage` from `/routes`), and turns the old `app/[slug]/**`\n * pages into `LegacyPreviewRedirect` re-exports. A harness without those\n * exports cannot back the shell.\n */\n\n/**\n * The contract an installed `@ekanos/harness` declares it needs, from\n * `{ \"ekanos\": { \"shellContract\": N } }` in its package.json.\n *\n * A package that declares nothing is read as contract 1 — the contract that\n * shipped first — so the harness released before this existed keeps working.\n * That default is deliberately silent, which is why `@ekanos/harness` asserts\n * the field's presence in its own pack test rather than relying on it.\n */\nexport function declaredShellContract(harnessManifest: unknown): number {\n if (typeof harnessManifest !== 'object' || harnessManifest === null) return 1;\n const ekanos = (harnessManifest as { ekanos?: unknown }).ekanos;\n if (typeof ekanos !== 'object' || ekanos === null) return 1;\n const declared = (ekanos as { shellContract?: unknown }).shellContract;\n return Number.isInteger(declared) ? (declared as number) : 1;\n}\n\n/** `[major, minor, patch]`, with anything non-numeric treated as 0. */\nfunction parseVersion(version: string): [number, number, number] {\n const core = version.split('+')[0]!.split('-')[0]!;\n const parts = core.split('.').map((part) => Number.parseInt(part, 10));\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n}\n\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a);\n const right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left[i]! !== right[i]!) return left[i]! < right[i]! ? -1 : 1;\n }\n return 0;\n}\n\n/** This CLI's own version, read from its package.json at runtime. */\nexport function cliVersion(): string {\n const manifestPath = path.join(templatesDir(), '..', 'package.json');\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n return typeof version === 'string' ? version : '0.0.0';\n}\n\n/**\n * Fail unless this CLI's templates generate the shell the installed harness\n * expects.\n *\n * The templates ship with `@ekanos/cli`; the code they import ships with\n * `@ekanos/harness`. The two version independently, so a partner can upgrade\n * one without the other — and the failure that matters is the quiet one:\n * `pnpm up @ekanos/harness` moves an export, `ekanos dev` regenerates from the\n * OLD CLI's templates, and the partner sees a module-resolution error rather\n * than anything naming the incompatibility.\n *\n * Byte-comparing generated files cannot catch it: the old templates' bytes are\n * exactly what the old CLI intends, so nothing regenerates differently and the\n * shell stays wired to something that moved. Only a declared contract can.\n *\n * Runs before anything is written, so an incompatible pair produces no shell\n * at all — a broken shell that builds is worse than no shell.\n */\nexport function assertHarnessCompatible(params: {\n harnessVersion: string;\n harnessManifest: unknown;\n installHint: (packageSpec: string) => string;\n}): void {\n const required = declaredShellContract(params.harnessManifest);\n if (required === SHELL_CONTRACT) return;\n\n if (required > SHELL_CONTRACT) {\n throw preconditionError(\n `@ekanos/cli ${cliVersion()} is too old for @ekanos/harness ` +\n `${params.harnessVersion}: that harness needs shell contract ` +\n `${required}, and this CLI generates ${SHELL_CONTRACT}.`,\n `Upgrade the CLI: ${params.installHint('@ekanos/cli@latest')}. Then ` +\n 're-run \"ekanos dev\" and the shell is regenerated for you.',\n );\n }\n\n throw preconditionError(\n `@ekanos/harness ${params.harnessVersion} is too old for @ekanos/cli ` +\n `${cliVersion()}: this CLI generates shell contract ${SHELL_CONTRACT}, ` +\n `and that harness needs ${required}.`,\n `Upgrade it: ${params.installHint('@ekanos/harness@latest')}.`,\n );\n}\n\n/**\n * A fingerprint of the template SOURCES, independent of the slug they are\n * rendered with.\n *\n * The harness version alone cannot certify a shell: the templates can change\n * while it stays put (a CLI patch release), and it can change while they stay\n * put. Hashing the sources means any edit to what we generate invalidates\n * every shell generated before it, including one produced by a CLI version\n * that never shipped.\n */\nexport function templatesFingerprint(templateNames: readonly string[]): string {\n const hash = createHash('sha256');\n for (const name of [...templateNames].sort()) {\n hash.update(name);\n hash.update('\\0');\n hash.update(readTemplateSource(name));\n hash.update('\\0');\n }\n return hash.digest('hex').slice(0, 16);\n}\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { type Contract, type SourceCatalog } from '@ekanos/integration-schema';
|
|
2
|
+
import type { ContractSource } from './experience/contract-sources.js';
|
|
3
|
+
/**
|
|
4
|
+
* `ekanos dev` for an EXPERIENCE project: kind detection, loading the
|
|
5
|
+
* harness's integration resolver from the project's own install, the CLI session
|
|
6
|
+
* catalog hand-off, and the override helper `publish` uses.
|
|
7
|
+
*/
|
|
8
|
+
export type DevKind = 'integration' | 'experience';
|
|
9
|
+
/** `kind` from the raw `ekanos.json`; anything unreadable is `integration`. */
|
|
10
|
+
export declare function detectKind(projectDir: string): DevKind;
|
|
11
|
+
export interface ExperienceProject {
|
|
12
|
+
slug: string;
|
|
13
|
+
entry: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function readExperienceProject(projectDir: string): ExperienceProject;
|
|
16
|
+
/** The slice of `@ekanos/harness/resolver` the CLI uses. */
|
|
17
|
+
export interface HarnessResolver {
|
|
18
|
+
resolveIntegrations(options: {
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
21
|
+
contractsDir?: string;
|
|
22
|
+
catalog?: SourceCatalog | null;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
integrations: unknown[];
|
|
25
|
+
overrides: Array<{
|
|
26
|
+
integration: string;
|
|
27
|
+
path: string;
|
|
28
|
+
via: 'file' | 'env';
|
|
29
|
+
}>;
|
|
30
|
+
notices: string[];
|
|
31
|
+
errors: Array<{
|
|
32
|
+
integration: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}>;
|
|
35
|
+
}>;
|
|
36
|
+
formatProvenanceLines(resolution: never): string[];
|
|
37
|
+
createRegistryContractSource(options: {
|
|
38
|
+
projectRoot: string;
|
|
39
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
40
|
+
contractsDir?: string;
|
|
41
|
+
}): ContractSource;
|
|
42
|
+
createPinsResolver?(options: {
|
|
43
|
+
projectRoot: string;
|
|
44
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
45
|
+
contractsDir?: string;
|
|
46
|
+
}): Promise<(integration: {
|
|
47
|
+
name: string;
|
|
48
|
+
version: string;
|
|
49
|
+
}) => {
|
|
50
|
+
contract: Contract;
|
|
51
|
+
lines: string[];
|
|
52
|
+
} | null>;
|
|
53
|
+
runOperationOnce?(options: {
|
|
54
|
+
projectRoot: string;
|
|
55
|
+
op: string;
|
|
56
|
+
input?: unknown;
|
|
57
|
+
mode?: 'fixtures' | 'live';
|
|
58
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
59
|
+
deriveContract?: (args: {
|
|
60
|
+
definition: unknown;
|
|
61
|
+
projectDir: string;
|
|
62
|
+
packageVersion: string;
|
|
63
|
+
}) => Contract | null;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
ok: true;
|
|
66
|
+
data: unknown;
|
|
67
|
+
} | {
|
|
68
|
+
ok: false;
|
|
69
|
+
reason: string;
|
|
70
|
+
message: string;
|
|
71
|
+
detail?: string;
|
|
72
|
+
}>;
|
|
73
|
+
activeOverrides(projectRoot: string, env?: Readonly<Record<string, string | undefined>>): Array<{
|
|
74
|
+
integration: string;
|
|
75
|
+
path: string;
|
|
76
|
+
via: 'file' | 'env';
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Import the resolver FROM THE PROJECT'S install of `@ekanos/harness`, so the
|
|
81
|
+
* integrations are resolved by exactly the harness version that will serve them.
|
|
82
|
+
*/
|
|
83
|
+
export declare function loadHarnessResolver(projectDir: string): Promise<HarnessResolver>;
|
|
84
|
+
export declare const SESSION_CATALOG_FILE: string;
|
|
85
|
+
/**
|
|
86
|
+
* Hand the harness the dev's Source catalog (no token in it) so it can answer
|
|
87
|
+
* availability and develop permission. Removes a stale file when there is no
|
|
88
|
+
* catalog, so yesterday's answer is never trusted today.
|
|
89
|
+
*/
|
|
90
|
+
export declare function writeSessionCatalog(projectDir: string, catalog: SourceCatalog | null): void;
|
|
91
|
+
/**
|
|
92
|
+
* Local overrides in force for a project, for `ekanos publish` to refuse on.
|
|
93
|
+
* Reads the harness's own definition of an override (file and env var), so
|
|
94
|
+
* the two can never disagree. Falls back to none when the harness is absent
|
|
95
|
+
* (an integration project has no overrides).
|
|
96
|
+
*/
|
|
97
|
+
export declare function activeOverridesFor(projectDir: string, env: Readonly<Record<string, string | undefined>>): Promise<Array<{
|
|
98
|
+
integration: string;
|
|
99
|
+
path: string;
|
|
100
|
+
via: 'file' | 'env';
|
|
101
|
+
}>>;
|
|
102
|
+
/**
|
|
103
|
+
* The harness's pins resolver for `ekanos check` / `publish`: integration pins are
|
|
104
|
+
* checked against what the registry chain resolves (catalog, published-package
|
|
105
|
+
* cache, contracts directory), local overrides ignored. Null when the project
|
|
106
|
+
* has no installed harness that offers one; the committed snapshots then decide.
|
|
107
|
+
*/
|
|
108
|
+
export declare function harnessPinsResolver(projectDir: string, env: Readonly<Record<string, string | undefined>>): Promise<((integration: {
|
|
109
|
+
name: string;
|
|
110
|
+
version: string;
|
|
111
|
+
}) => {
|
|
112
|
+
contract: Contract;
|
|
113
|
+
lines: string[];
|
|
114
|
+
} | null) | null>;
|
|
115
|
+
/**
|
|
116
|
+
* The harness resolver as an extra `add`/`sync` contract source (catalog,
|
|
117
|
+
* published-package cache, contracts directory). Empty when the project has
|
|
118
|
+
* no installed harness that offers one — the offline sources still apply.
|
|
119
|
+
*/
|
|
120
|
+
export declare function harnessContractSources(projectDir: string, env: Readonly<Record<string, string | undefined>>, options?: {
|
|
121
|
+
/** `--contracts-dir`: wins over `EKANOS_CONTRACTS_DIR`, as in `ekanos dev`. */
|
|
122
|
+
contractsDir?: string;
|
|
123
|
+
/** Told once when the project's harness cannot be asked at all. */
|
|
124
|
+
onUnavailable?: (reason: string) => void;
|
|
125
|
+
}): Promise<ContractSource[]>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { SourceCatalogSchema, } from '@ekanos/integration-schema';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { preconditionError } from './errors.js';
|
|
7
|
+
/** `kind` from the raw `ekanos.json`; anything unreadable is `integration`. */
|
|
8
|
+
export function detectKind(projectDir) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = JSON.parse(fs.readFileSync(path.join(projectDir, 'ekanos.json'), 'utf8'));
|
|
11
|
+
return raw.kind === 'experience' ? 'experience' : 'integration';
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
return 'integration';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function readExperienceProject(projectDir) {
|
|
18
|
+
const file = path.join(projectDir, 'ekanos.json');
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw preconditionError(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, 'Fix ekanos.json, then re-run "ekanos dev".');
|
|
25
|
+
}
|
|
26
|
+
if (typeof raw.slug !== 'string' || typeof raw.entry !== 'string') {
|
|
27
|
+
throw preconditionError('An experience ekanos.json needs a "slug" and an "entry".', 'Add them (see "ekanos init --kind experience"), then re-run "ekanos dev".');
|
|
28
|
+
}
|
|
29
|
+
return { slug: raw.slug, entry: raw.entry };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Import the resolver FROM THE PROJECT'S install of `@ekanos/harness`, so the
|
|
33
|
+
* integrations are resolved by exactly the harness version that will serve them.
|
|
34
|
+
*/
|
|
35
|
+
export async function loadHarnessResolver(projectDir) {
|
|
36
|
+
let resolved = null;
|
|
37
|
+
// An experience installs the harness itself; an integration repo's lives in
|
|
38
|
+
// the generated shell under `.ekanos/harness`.
|
|
39
|
+
for (const base of [
|
|
40
|
+
projectDir,
|
|
41
|
+
path.join(projectDir, '.ekanos', 'harness'),
|
|
42
|
+
]) {
|
|
43
|
+
try {
|
|
44
|
+
resolved = createRequire(path.join(base, 'package.json')).resolve('@ekanos/harness/resolver');
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
resolved = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!resolved) {
|
|
52
|
+
throw preconditionError('The installed @ekanos/harness has no "./resolver" export, which an experience needs to resolve its integrations.', 'Upgrade @ekanos/harness and @ekanos/cli together, then re-run "ekanos dev".');
|
|
53
|
+
}
|
|
54
|
+
return (await import(pathToFileURL(resolved).href));
|
|
55
|
+
}
|
|
56
|
+
export const SESSION_CATALOG_FILE = path.join('.ekanos', 'session-catalog.json');
|
|
57
|
+
/**
|
|
58
|
+
* Hand the harness the dev's Source catalog (no token in it) so it can answer
|
|
59
|
+
* availability and develop permission. Removes a stale file when there is no
|
|
60
|
+
* catalog, so yesterday's answer is never trusted today.
|
|
61
|
+
*/
|
|
62
|
+
export function writeSessionCatalog(projectDir, catalog) {
|
|
63
|
+
const file = path.join(projectDir, SESSION_CATALOG_FILE);
|
|
64
|
+
if (!catalog) {
|
|
65
|
+
fs.rmSync(file, { force: true });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const parsed = SourceCatalogSchema.safeParse(catalog);
|
|
69
|
+
if (!parsed.success)
|
|
70
|
+
return;
|
|
71
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
72
|
+
fs.writeFileSync(file, `${JSON.stringify(parsed.data)}\n`, { mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Local overrides in force for a project, for `ekanos publish` to refuse on.
|
|
76
|
+
* Reads the harness's own definition of an override (file and env var), so
|
|
77
|
+
* the two can never disagree. Falls back to none when the harness is absent
|
|
78
|
+
* (an integration project has no overrides).
|
|
79
|
+
*/
|
|
80
|
+
export async function activeOverridesFor(projectDir, env) {
|
|
81
|
+
if (detectKind(projectDir) !== 'experience')
|
|
82
|
+
return [];
|
|
83
|
+
const resolver = await loadHarnessResolver(projectDir);
|
|
84
|
+
return resolver.activeOverrides(projectDir, env);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The harness's pins resolver for `ekanos check` / `publish`: integration pins are
|
|
88
|
+
* checked against what the registry chain resolves (catalog, published-package
|
|
89
|
+
* cache, contracts directory), local overrides ignored. Null when the project
|
|
90
|
+
* has no installed harness that offers one; the committed snapshots then decide.
|
|
91
|
+
*/
|
|
92
|
+
export async function harnessPinsResolver(projectDir, env) {
|
|
93
|
+
try {
|
|
94
|
+
const resolver = await loadHarnessResolver(projectDir);
|
|
95
|
+
if (typeof resolver.createPinsResolver !== 'function')
|
|
96
|
+
return null;
|
|
97
|
+
const contractsDir = env.EKANOS_CONTRACTS_DIR;
|
|
98
|
+
return await resolver.createPinsResolver(Object.assign({ projectRoot: projectDir, env }, (contractsDir ? { contractsDir: path.resolve(contractsDir) } : {})));
|
|
99
|
+
}
|
|
100
|
+
catch (_a) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The harness resolver as an extra `add`/`sync` contract source (catalog,
|
|
106
|
+
* published-package cache, contracts directory). Empty when the project has
|
|
107
|
+
* no installed harness that offers one — the offline sources still apply.
|
|
108
|
+
*/
|
|
109
|
+
export async function harnessContractSources(projectDir, env, options = {}) {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
var _c;
|
|
112
|
+
try {
|
|
113
|
+
const resolver = await loadHarnessResolver(projectDir);
|
|
114
|
+
if (typeof resolver.createRegistryContractSource !== 'function') {
|
|
115
|
+
(_a = options.onUnavailable) === null || _a === void 0 ? void 0 : _a.call(options, 'the installed @ekanos/harness has no registry contract source');
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const contractsDir = (_c = options.contractsDir) !== null && _c !== void 0 ? _c : env.EKANOS_CONTRACTS_DIR;
|
|
119
|
+
return [
|
|
120
|
+
resolver.createRegistryContractSource(Object.assign({ projectRoot: projectDir, env }, (contractsDir ? { contractsDir: path.resolve(contractsDir) } : {}))),
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
(_b = options.onUnavailable) === null || _b === void 0 ? void 0 : _b.call(options, error instanceof Error ? error.message : String(error));
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=dev-experience.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-experience.js","sourceRoot":"","sources":["../src/dev-experience.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SourceCatalogSchema,\n type Contract,\n type SourceCatalog,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { preconditionError } from './errors';\nimport type { ContractSource } from './experience/contract-sources';\n\n/**\n * `ekanos dev` for an EXPERIENCE project: kind detection, loading the\n * harness's integration resolver from the project's own install, the CLI session\n * catalog hand-off, and the override helper `publish` uses.\n */\n\nexport type DevKind = 'integration' | 'experience';\n\n/** `kind` from the raw `ekanos.json`; anything unreadable is `integration`. */\nexport function detectKind(projectDir: string): DevKind {\n try {\n const raw = JSON.parse(\n fs.readFileSync(path.join(projectDir, 'ekanos.json'), 'utf8'),\n ) as { kind?: unknown };\n return raw.kind === 'experience' ? 'experience' : 'integration';\n } catch {\n return 'integration';\n }\n}\n\nexport interface ExperienceProject {\n slug: string;\n entry: string;\n}\n\nexport function readExperienceProject(projectDir: string): ExperienceProject {\n const file = path.join(projectDir, 'ekanos.json');\n let raw: { slug?: unknown; entry?: unknown };\n try {\n raw = JSON.parse(fs.readFileSync(file, 'utf8')) as typeof raw;\n } catch (error) {\n throw preconditionError(\n `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix ekanos.json, then re-run \"ekanos dev\".',\n );\n }\n if (typeof raw.slug !== 'string' || typeof raw.entry !== 'string') {\n throw preconditionError(\n 'An experience ekanos.json needs a \"slug\" and an \"entry\".',\n 'Add them (see \"ekanos init --kind experience\"), then re-run \"ekanos dev\".',\n );\n }\n return { slug: raw.slug, entry: raw.entry };\n}\n\n/** The slice of `@ekanos/harness/resolver` the CLI uses. */\nexport interface HarnessResolver {\n resolveIntegrations(options: {\n projectRoot: string;\n env?: Readonly<Record<string, string | undefined>>;\n contractsDir?: string;\n catalog?: SourceCatalog | null;\n }): Promise<{\n integrations: unknown[];\n overrides: Array<{\n integration: string;\n path: string;\n via: 'file' | 'env';\n }>;\n notices: string[];\n errors: Array<{ integration: string; message: string }>;\n }>;\n formatProvenanceLines(resolution: never): string[];\n createRegistryContractSource(options: {\n projectRoot: string;\n env?: Readonly<Record<string, string | undefined>>;\n contractsDir?: string;\n }): ContractSource;\n createPinsResolver?(options: {\n projectRoot: string;\n env?: Readonly<Record<string, string | undefined>>;\n contractsDir?: string;\n }): Promise<\n (integration: { name: string; version: string }) => {\n contract: Contract;\n lines: string[];\n } | null\n >;\n runOperationOnce?(options: {\n projectRoot: string;\n op: string;\n input?: unknown;\n mode?: 'fixtures' | 'live';\n env?: Readonly<Record<string, string | undefined>>;\n deriveContract?: (args: {\n definition: unknown;\n projectDir: string;\n packageVersion: string;\n }) => Contract | null;\n }): Promise<\n | { ok: true; data: unknown }\n | { ok: false; reason: string; message: string; detail?: string }\n >;\n activeOverrides(\n projectRoot: string,\n env?: Readonly<Record<string, string | undefined>>,\n ): Array<{ integration: string; path: string; via: 'file' | 'env' }>;\n}\n\n/**\n * Import the resolver FROM THE PROJECT'S install of `@ekanos/harness`, so the\n * integrations are resolved by exactly the harness version that will serve them.\n */\nexport async function loadHarnessResolver(\n projectDir: string,\n): Promise<HarnessResolver> {\n let resolved: string | null = null;\n // An experience installs the harness itself; an integration repo's lives in\n // the generated shell under `.ekanos/harness`.\n for (const base of [\n projectDir,\n path.join(projectDir, '.ekanos', 'harness'),\n ]) {\n try {\n resolved = createRequire(path.join(base, 'package.json')).resolve(\n '@ekanos/harness/resolver',\n );\n break;\n } catch {\n resolved = null;\n }\n }\n if (!resolved) {\n throw preconditionError(\n 'The installed @ekanos/harness has no \"./resolver\" export, which an experience needs to resolve its integrations.',\n 'Upgrade @ekanos/harness and @ekanos/cli together, then re-run \"ekanos dev\".',\n );\n }\n return (await import(pathToFileURL(resolved).href)) as HarnessResolver;\n}\n\nexport const SESSION_CATALOG_FILE = path.join(\n '.ekanos',\n 'session-catalog.json',\n);\n\n/**\n * Hand the harness the dev's Source catalog (no token in it) so it can answer\n * availability and develop permission. Removes a stale file when there is no\n * catalog, so yesterday's answer is never trusted today.\n */\nexport function writeSessionCatalog(\n projectDir: string,\n catalog: SourceCatalog | null,\n): void {\n const file = path.join(projectDir, SESSION_CATALOG_FILE);\n if (!catalog) {\n fs.rmSync(file, { force: true });\n return;\n }\n const parsed = SourceCatalogSchema.safeParse(catalog);\n if (!parsed.success) return;\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify(parsed.data)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Local overrides in force for a project, for `ekanos publish` to refuse on.\n * Reads the harness's own definition of an override (file and env var), so\n * the two can never disagree. Falls back to none when the harness is absent\n * (an integration project has no overrides).\n */\nexport async function activeOverridesFor(\n projectDir: string,\n env: Readonly<Record<string, string | undefined>>,\n): Promise<Array<{ integration: string; path: string; via: 'file' | 'env' }>> {\n if (detectKind(projectDir) !== 'experience') return [];\n const resolver = await loadHarnessResolver(projectDir);\n return resolver.activeOverrides(projectDir, env);\n}\n\n/**\n * The harness's pins resolver for `ekanos check` / `publish`: integration pins are\n * checked against what the registry chain resolves (catalog, published-package\n * cache, contracts directory), local overrides ignored. Null when the project\n * has no installed harness that offers one; the committed snapshots then decide.\n */\nexport async function harnessPinsResolver(\n projectDir: string,\n env: Readonly<Record<string, string | undefined>>,\n) {\n try {\n const resolver = await loadHarnessResolver(projectDir);\n if (typeof resolver.createPinsResolver !== 'function') return null;\n const contractsDir = env.EKANOS_CONTRACTS_DIR;\n return await resolver.createPinsResolver({\n projectRoot: projectDir,\n env,\n ...(contractsDir ? { contractsDir: path.resolve(contractsDir) } : {}),\n });\n } catch {\n return null;\n }\n}\n\n/**\n * The harness resolver as an extra `add`/`sync` contract source (catalog,\n * published-package cache, contracts directory). Empty when the project has\n * no installed harness that offers one — the offline sources still apply.\n */\nexport async function harnessContractSources(\n projectDir: string,\n env: Readonly<Record<string, string | undefined>>,\n options: {\n /** `--contracts-dir`: wins over `EKANOS_CONTRACTS_DIR`, as in `ekanos dev`. */\n contractsDir?: string;\n /** Told once when the project's harness cannot be asked at all. */\n onUnavailable?: (reason: string) => void;\n } = {},\n): Promise<ContractSource[]> {\n try {\n const resolver = await loadHarnessResolver(projectDir);\n if (typeof resolver.createRegistryContractSource !== 'function') {\n options.onUnavailable?.(\n 'the installed @ekanos/harness has no registry contract source',\n );\n return [];\n }\n const contractsDir = options.contractsDir ?? env.EKANOS_CONTRACTS_DIR;\n return [\n resolver.createRegistryContractSource({\n projectRoot: projectDir,\n env,\n ...(contractsDir ? { contractsDir: path.resolve(contractsDir) } : {}),\n }),\n ];\n } catch (error) {\n options.onUnavailable?.(\n error instanceof Error ? error.message : String(error),\n );\n return [];\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from '@ekanos/integration-schema';
|
|
2
|
+
import type { ExperienceProject } from './project.js';
|
|
3
|
+
export declare const BOUNDARY_CHECK = "checks/experience-boundary";
|
|
4
|
+
/** Packages that may stay external at runtime (the host provides them). */
|
|
5
|
+
export declare const ALLOWED_EXTERNALS: readonly ['react', 'react-dom', '@ekanos/sdk', '@ekanos/ui', 'zod', '@tanstack/react-query', 'next-intl', 'react-hook-form', '@hookform/resolvers'];
|
|
6
|
+
/** Nearest `ekanos.json` at or above `start`, with its kind, or null. */
|
|
7
|
+
export declare function nearestEkanosKind(start: string): {
|
|
8
|
+
dir: string;
|
|
9
|
+
kind: string;
|
|
10
|
+
} | null;
|
|
11
|
+
/** The authoritative boundary: package.json, source scan, bundle scan. */
|
|
12
|
+
export declare function checkExperienceBoundary(project: ExperienceProject): Promise<Finding[]>;
|