@ekanos/cli 0.15.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.
Files changed (287) hide show
  1. package/README.md +103 -8
  2. package/dist/auth/ekanos-api.d.ts +23 -0
  3. package/dist/auth/ekanos-api.js +33 -6
  4. package/dist/auth/ekanos-api.js.map +1 -1
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +118 -0
  7. package/dist/commands/add.js.map +1 -0
  8. package/dist/commands/agents.js +3 -2
  9. package/dist/commands/agents.js.map +1 -1
  10. package/dist/commands/apps.d.ts +11 -3
  11. package/dist/commands/apps.js +86 -28
  12. package/dist/commands/apps.js.map +1 -1
  13. package/dist/commands/audiences.d.ts +31 -0
  14. package/dist/commands/audiences.js +239 -0
  15. package/dist/commands/audiences.js.map +1 -0
  16. package/dist/commands/build.d.ts +2 -0
  17. package/dist/commands/build.js +41 -0
  18. package/dist/commands/build.js.map +1 -0
  19. package/dist/commands/catalog.d.ts +4 -2
  20. package/dist/commands/catalog.js +166 -35
  21. package/dist/commands/catalog.js.map +1 -1
  22. package/dist/commands/check.d.ts +2 -0
  23. package/dist/commands/check.js +43 -0
  24. package/dist/commands/check.js.map +1 -0
  25. package/dist/commands/codegen.d.ts +2 -0
  26. package/dist/commands/codegen.js +49 -0
  27. package/dist/commands/codegen.js.map +1 -0
  28. package/dist/commands/contract.d.ts +52 -0
  29. package/dist/commands/contract.js +300 -0
  30. package/dist/commands/contract.js.map +1 -0
  31. package/dist/commands/dev.d.ts +7 -0
  32. package/dist/commands/dev.js +186 -12
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/index-experience.d.ts +31 -0
  35. package/dist/commands/index-experience.js +21 -0
  36. package/dist/commands/index-experience.js.map +1 -0
  37. package/dist/commands/init.d.ts +7 -0
  38. package/dist/commands/init.js +12 -0
  39. package/dist/commands/init.js.map +1 -1
  40. package/dist/commands/login.d.ts +2 -0
  41. package/dist/commands/login.js +17 -0
  42. package/dist/commands/login.js.map +1 -1
  43. package/dist/commands/new.d.ts +2 -0
  44. package/dist/commands/new.js +39 -0
  45. package/dist/commands/new.js.map +1 -0
  46. package/dist/commands/publish.d.ts +59 -0
  47. package/dist/commands/publish.js +118 -65
  48. package/dist/commands/publish.js.map +1 -1
  49. package/dist/commands/runs.d.ts +7 -3
  50. package/dist/commands/runs.js +21 -5
  51. package/dist/commands/runs.js.map +1 -1
  52. package/dist/commands/scope-notice.d.ts +13 -0
  53. package/dist/commands/scope-notice.js +12 -4
  54. package/dist/commands/scope-notice.js.map +1 -1
  55. package/dist/commands/sources-roles.d.ts +5 -0
  56. package/dist/commands/sources-roles.js +235 -0
  57. package/dist/commands/sources-roles.js.map +1 -0
  58. package/dist/commands/sources.d.ts +5 -0
  59. package/dist/commands/sources.js +8 -1
  60. package/dist/commands/sources.js.map +1 -1
  61. package/dist/commands/sync.d.ts +2 -0
  62. package/dist/commands/sync.js +150 -0
  63. package/dist/commands/sync.js.map +1 -0
  64. package/dist/commands/team-agent-readiness.d.ts +18 -2
  65. package/dist/commands/team-agent-readiness.js +39 -39
  66. package/dist/commands/team-agent-readiness.js.map +1 -1
  67. package/dist/commands/team-agents-agents.d.ts +5 -2
  68. package/dist/commands/team-agents-agents.js +152 -15
  69. package/dist/commands/team-agents-agents.js.map +1 -1
  70. package/dist/commands/team-agents-define.d.ts +31 -0
  71. package/dist/commands/team-agents-define.js +176 -0
  72. package/dist/commands/team-agents-define.js.map +1 -0
  73. package/dist/commands/team-agents-mcp.js +112 -10
  74. package/dist/commands/team-agents-mcp.js.map +1 -1
  75. package/dist/commands/team-agents-triggers.d.ts +1 -1
  76. package/dist/commands/team-agents-triggers.js +137 -5
  77. package/dist/commands/team-agents-triggers.js.map +1 -1
  78. package/dist/commands/team-agents.d.ts +4 -1
  79. package/dist/commands/team-agents.js +17 -4
  80. package/dist/commands/team-agents.js.map +1 -1
  81. package/dist/commands/team-llm-key.d.ts +14 -0
  82. package/dist/commands/team-llm-key.js +122 -0
  83. package/dist/commands/team-llm-key.js.map +1 -0
  84. package/dist/commands/teams-audience.d.ts +12 -0
  85. package/dist/commands/teams-audience.js +144 -0
  86. package/dist/commands/teams-audience.js.map +1 -0
  87. package/dist/commands/teams-connect.js +123 -31
  88. package/dist/commands/teams-connect.js.map +1 -1
  89. package/dist/commands/teams-lifecycle.d.ts +90 -0
  90. package/dist/commands/teams-lifecycle.js +457 -0
  91. package/dist/commands/teams-lifecycle.js.map +1 -0
  92. package/dist/commands/teams-onboard.d.ts +14 -0
  93. package/dist/commands/teams-onboard.js +62 -13
  94. package/dist/commands/teams-onboard.js.map +1 -1
  95. package/dist/commands/teams-overview.js +6 -2
  96. package/dist/commands/teams-overview.js.map +1 -1
  97. package/dist/commands/teams-scope.js +4 -0
  98. package/dist/commands/teams-scope.js.map +1 -1
  99. package/dist/commands/teams.d.ts +25 -5
  100. package/dist/commands/teams.js +106 -39
  101. package/dist/commands/teams.js.map +1 -1
  102. package/dist/commands/workflows.d.ts +37 -0
  103. package/dist/commands/workflows.js +589 -0
  104. package/dist/commands/workflows.js.map +1 -0
  105. package/dist/compatibility.d.ts +10 -1
  106. package/dist/compatibility.js +10 -1
  107. package/dist/compatibility.js.map +1 -1
  108. package/dist/connect-fields.d.ts +22 -0
  109. package/dist/connect-fields.js +115 -0
  110. package/dist/connect-fields.js.map +1 -0
  111. package/dist/context.d.ts +12 -0
  112. package/dist/context.js +32 -9
  113. package/dist/context.js.map +1 -1
  114. package/dist/dev-experience.d.ts +125 -0
  115. package/dist/dev-experience.js +128 -0
  116. package/dist/dev-experience.js.map +1 -0
  117. package/dist/experience/boundary.d.ts +12 -0
  118. package/dist/experience/boundary.js +260 -0
  119. package/dist/experience/boundary.js.map +1 -0
  120. package/dist/experience/build.d.ts +16 -0
  121. package/dist/experience/build.js +90 -0
  122. package/dist/experience/build.js.map +1 -0
  123. package/dist/experience/checks.d.ts +29 -0
  124. package/dist/experience/checks.js +133 -0
  125. package/dist/experience/checks.js.map +1 -0
  126. package/dist/experience/contract-sources.d.ts +36 -0
  127. package/dist/experience/contract-sources.js +111 -0
  128. package/dist/experience/contract-sources.js.map +1 -0
  129. package/dist/experience/definition-checks.d.ts +26 -0
  130. package/dist/experience/definition-checks.js +201 -0
  131. package/dist/experience/definition-checks.js.map +1 -0
  132. package/dist/experience/definition-shape.d.ts +60 -0
  133. package/dist/experience/definition-shape.js +2 -0
  134. package/dist/experience/definition-shape.js.map +1 -0
  135. package/dist/experience/init.d.ts +10 -0
  136. package/dist/experience/init.js +64 -0
  137. package/dist/experience/init.js.map +1 -0
  138. package/dist/experience/integrations-codegen.d.ts +17 -0
  139. package/dist/experience/integrations-codegen.js +51 -0
  140. package/dist/experience/integrations-codegen.js.map +1 -0
  141. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  142. package/dist/experience/json-schema-to-ts.js +90 -0
  143. package/dist/experience/json-schema-to-ts.js.map +1 -0
  144. package/dist/experience/load-experience.d.ts +23 -0
  145. package/dist/experience/load-experience.js +115 -0
  146. package/dist/experience/load-experience.js.map +1 -0
  147. package/dist/experience/overrides.d.ts +8 -0
  148. package/dist/experience/overrides.js +21 -0
  149. package/dist/experience/overrides.js.map +1 -0
  150. package/dist/experience/pin-integration.d.ts +8 -0
  151. package/dist/experience/pin-integration.js +17 -0
  152. package/dist/experience/pin-integration.js.map +1 -0
  153. package/dist/experience/pin-ops.d.ts +12 -0
  154. package/dist/experience/pin-ops.js +36 -0
  155. package/dist/experience/pin-ops.js.map +1 -0
  156. package/dist/experience/pins.d.ts +35 -0
  157. package/dist/experience/pins.js +91 -0
  158. package/dist/experience/pins.js.map +1 -0
  159. package/dist/experience/project.d.ts +52 -0
  160. package/dist/experience/project.js +149 -0
  161. package/dist/experience/project.js.map +1 -0
  162. package/dist/experience/publish.d.ts +51 -0
  163. package/dist/experience/publish.js +144 -0
  164. package/dist/experience/publish.js.map +1 -0
  165. package/dist/experience/regenerate.d.ts +10 -0
  166. package/dist/experience/regenerate.js +33 -0
  167. package/dist/experience/regenerate.js.map +1 -0
  168. package/dist/experience/report.d.ts +9 -0
  169. package/dist/experience/report.js +23 -0
  170. package/dist/experience/report.js.map +1 -0
  171. package/dist/harness-scaffold.d.ts +52 -18
  172. package/dist/harness-scaffold.js +189 -9
  173. package/dist/harness-scaffold.js.map +1 -1
  174. package/dist/index.d.ts +13 -0
  175. package/dist/index.js +494 -127
  176. package/dist/index.js.map +1 -1
  177. package/dist/invocation.d.ts +31 -0
  178. package/dist/invocation.js +58 -0
  179. package/dist/invocation.js.map +1 -0
  180. package/dist/project-checks.d.ts +12 -0
  181. package/dist/project-checks.js +46 -0
  182. package/dist/project-checks.js.map +1 -1
  183. package/dist/project.d.ts +14 -0
  184. package/dist/project.js +10 -0
  185. package/dist/project.js.map +1 -1
  186. package/dist/repo/publish.d.ts +42 -0
  187. package/dist/repo/publish.js +225 -0
  188. package/dist/repo/publish.js.map +1 -0
  189. package/dist/repo/scaffold.d.ts +30 -0
  190. package/dist/repo/scaffold.js +165 -0
  191. package/dist/repo/scaffold.js.map +1 -0
  192. package/dist/runs-api.js +8 -3
  193. package/dist/runs-api.js.map +1 -1
  194. package/dist/safe-fs.d.ts +18 -0
  195. package/dist/safe-fs.js +57 -0
  196. package/dist/safe-fs.js.map +1 -1
  197. package/dist/secret-form.d.ts +48 -0
  198. package/dist/secret-form.js +212 -0
  199. package/dist/secret-form.js.map +1 -0
  200. package/dist/secret-input.d.ts +96 -0
  201. package/dist/secret-input.js +299 -0
  202. package/dist/secret-input.js.map +1 -0
  203. package/dist/team-connect-api.d.ts +78 -3
  204. package/dist/team-connect-api.js +17 -2
  205. package/dist/team-connect-api.js.map +1 -1
  206. package/dist/team-lifecycle-api.d.ts +435 -0
  207. package/dist/team-lifecycle-api.js +301 -0
  208. package/dist/team-lifecycle-api.js.map +1 -0
  209. package/dist/teams-api.d.ts +49 -1
  210. package/dist/teams-api.js +149 -27
  211. package/dist/teams-api.js.map +1 -1
  212. package/dist/unknown-command.d.ts +18 -0
  213. package/dist/unknown-command.js +50 -0
  214. package/dist/unknown-command.js.map +1 -0
  215. package/dist/validate-findings.js +2 -1
  216. package/dist/validate-findings.js.map +1 -1
  217. package/dist/workflows-api.d.ts +69 -0
  218. package/dist/workflows-api.js +182 -0
  219. package/dist/workflows-api.js.map +1 -0
  220. package/package.json +3 -3
  221. package/templates/AGENTS.md.tmpl +6 -4
  222. package/templates/claude-skill.md.tmpl +4 -1
  223. package/templates/ekanos.json.tmpl +1 -0
  224. package/templates/experience/AGENTS.md.tmpl +22 -0
  225. package/templates/experience/ekanos.json.tmpl +7 -0
  226. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  227. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  228. package/templates/experience/gitignore.tmpl +17 -0
  229. package/templates/experience/package.json.tmpl +41 -0
  230. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  231. package/templates/experience/src/experience.ts.tmpl +30 -0
  232. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  233. package/templates/experience/tsconfig.json.tmpl +20 -0
  234. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  235. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  236. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  237. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  238. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  239. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  240. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  241. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  242. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  243. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  244. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  245. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  246. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  247. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  248. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  249. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  250. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  251. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  252. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  253. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  254. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  255. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  256. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  257. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  258. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  259. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  260. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  261. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  262. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  263. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  264. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  265. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  266. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  267. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  268. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  269. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  270. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  271. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  272. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  273. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  274. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  275. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  276. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  277. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  278. package/templates/harness/styles/globals.css.tmpl +15 -1
  279. package/templates/package.json.tmpl +4 -4
  280. package/templates/skills/build-agent/SKILL.md +43 -10
  281. package/templates/skills/build-agent/references/agent-json.md +4 -1
  282. package/templates/skills/build-agent/references/app-setup.md +14 -7
  283. package/templates/skills/build-agent/references/rollout.md +26 -2
  284. package/templates/skills/build-agent/references/set-up-an-integration.md +46 -29
  285. package/templates/skills/build-integration/references/widget-design.md +1 -1
  286. package/templates/skills/ekanos-platform/SKILL.md +66 -14
  287. package/templates/skills/polish-widgets/references/patterns.md +3 -3
@@ -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[]>;
@@ -0,0 +1,260 @@
1
+ import { build } from 'esbuild';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { isContained } from '../safe-fs.js';
5
+ export const BOUNDARY_CHECK = 'checks/experience-boundary';
6
+ /** Packages that may stay external at runtime (the host provides them). */
7
+ export const ALLOWED_EXTERNALS = [
8
+ 'react',
9
+ 'react-dom',
10
+ '@ekanos/sdk',
11
+ '@ekanos/ui',
12
+ 'zod',
13
+ '@tanstack/react-query',
14
+ 'next-intl',
15
+ 'react-hook-form',
16
+ '@hookform/resolvers',
17
+ ];
18
+ const INTEGRATION_PACKAGE = /^@companydotcom\/integration-[a-z0-9-]+/;
19
+ /** A pnpm-store or plain node_modules path of an integration package. */
20
+ const INTEGRATION_IN_PATH = /@companydotcom[/+]integration-[a-z0-9-]+/;
21
+ const HOST_INTERNAL = /^@kit\//;
22
+ const SOURCE_EXT = /\.(?:[cm]?[jt]sx?)$/;
23
+ const SKIP_DIRS = new Set(['node_modules', 'dist', '.ekanos', '.git']);
24
+ const finding = (message, hint, file, line) => (Object.assign(Object.assign({ check: BOUNDARY_CHECK, severity: 'error', message,
25
+ hint }, (file ? { file } : {})), (line ? { line } : {})));
26
+ const INTEGRATION_HINT = 'An experience reaches an integration only through its pinned contract: useOperation / operation() / step.tool. Add the integration with "ekanos add <integration>@<version>".';
27
+ /** Nearest `ekanos.json` at or above `start`, with its kind, or null. */
28
+ export function nearestEkanosKind(start) {
29
+ var _a;
30
+ let current = fs.existsSync(start) && fs.statSync(start).isDirectory()
31
+ ? start
32
+ : path.dirname(start);
33
+ for (;;) {
34
+ const file = path.join(current, 'ekanos.json');
35
+ if (fs.existsSync(file)) {
36
+ try {
37
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
38
+ return { dir: current, kind: (_a = parsed.kind) !== null && _a !== void 0 ? _a : 'integration' };
39
+ }
40
+ catch (_b) {
41
+ return { dir: current, kind: 'integration' };
42
+ }
43
+ }
44
+ const parent = path.dirname(current);
45
+ if (parent === current)
46
+ return null;
47
+ current = parent;
48
+ }
49
+ }
50
+ // ─── 1. package.json ────────────────────────────────────────────────────────
51
+ const DEP_FIELDS = [
52
+ 'dependencies',
53
+ 'devDependencies',
54
+ 'peerDependencies',
55
+ 'optionalDependencies',
56
+ ];
57
+ function checkPackageJson(project) {
58
+ const file = path.join(project.dir, 'package.json');
59
+ if (!fs.existsSync(file))
60
+ return [];
61
+ const findings = [];
62
+ const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
63
+ const specs = [];
64
+ for (const field of DEP_FIELDS) {
65
+ const block = pkg[field];
66
+ if (block && typeof block === 'object') {
67
+ specs.push(...Object.entries(block));
68
+ }
69
+ }
70
+ const pnpm = pkg.pnpm;
71
+ for (const block of [
72
+ pnpm === null || pnpm === void 0 ? void 0 : pnpm.overrides,
73
+ pkg.overrides,
74
+ ]) {
75
+ if (block && typeof block === 'object')
76
+ specs.push(...Object.entries(block));
77
+ }
78
+ for (const [name, spec] of specs) {
79
+ if (INTEGRATION_PACKAGE.test(name)) {
80
+ findings.push(finding(`package.json depends on the integration package ${name}.`, INTEGRATION_HINT, 'package.json'));
81
+ continue;
82
+ }
83
+ const linked = /^(?:file|link|workspace):(.*)$/.exec(String(spec));
84
+ if ((linked === null || linked === void 0 ? void 0 : linked[1]) !== undefined && linked[1] !== '*' && linked[1] !== '^') {
85
+ const target = path.resolve(project.dir, linked[1]);
86
+ const kind = nearestEkanosKind(target);
87
+ if (kind &&
88
+ kind.kind === 'integration' &&
89
+ path.resolve(kind.dir) !== project.dir) {
90
+ findings.push(finding(`package.json links ${name} to ${linked[1]}, an integration project.`, INTEGRATION_HINT, 'package.json'));
91
+ }
92
+ }
93
+ // A resolved dependency that is itself an integration, whatever its name.
94
+ const installed = path.join(project.dir, 'node_modules', name, 'ekanos.json');
95
+ if (fs.existsSync(installed)) {
96
+ try {
97
+ const kind = JSON.parse(fs.readFileSync(installed, 'utf8')).kind;
98
+ if ((kind !== null && kind !== void 0 ? kind : 'integration') === 'integration') {
99
+ findings.push(finding(`package.json depends on ${name}, which is an integration (its ekanos.json is kind "integration").`, INTEGRATION_HINT, 'package.json'));
100
+ }
101
+ }
102
+ catch (_a) {
103
+ // unreadable ekanos.json in a dependency: the bundle scan still applies
104
+ }
105
+ }
106
+ }
107
+ return findings;
108
+ }
109
+ // ─── 2. source scan ─────────────────────────────────────────────────────────
110
+ const IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s+|\brequire\s*\(\s*)(['"`])([^'"`\n]+)\1/g;
111
+ const COMPUTED_IMPORT = /\bimport\s*\(\s*(?!['"`])/g;
112
+ function* walkSources(dir, ignore, root) {
113
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
114
+ if (entry.isSymbolicLink())
115
+ continue;
116
+ const full = path.join(dir, entry.name);
117
+ if (entry.isDirectory()) {
118
+ if (SKIP_DIRS.has(entry.name))
119
+ continue;
120
+ const rel = path.relative(root, full).split(path.sep).join('/');
121
+ if (ignore.includes(rel))
122
+ continue;
123
+ yield* walkSources(full, ignore, root);
124
+ }
125
+ else if (SOURCE_EXT.test(entry.name)) {
126
+ yield full;
127
+ }
128
+ }
129
+ }
130
+ const lineOf = (text, index) => text.slice(0, index).split('\n').length;
131
+ function checkSources(project) {
132
+ var _a, _b, _c;
133
+ const findings = [];
134
+ const srcDir = path.join(project.dir, 'src');
135
+ if (!fs.existsSync(srcDir))
136
+ return findings;
137
+ const ignore = (_a = project.ekanos.ignore) !== null && _a !== void 0 ? _a : [];
138
+ for (const file of walkSources(srcDir, ignore, project.dir)) {
139
+ const text = fs.readFileSync(file, 'utf8');
140
+ const rel = path.relative(project.dir, file).split(path.sep).join('/');
141
+ for (const match of text.matchAll(IMPORT_PATTERN)) {
142
+ const spec = match[2];
143
+ const line = lineOf(text, (_b = match.index) !== null && _b !== void 0 ? _b : 0);
144
+ if (INTEGRATION_PACKAGE.test(spec) || INTEGRATION_IN_PATH.test(spec)) {
145
+ findings.push(finding(`${rel}:${line} imports the integration "${spec}".`, INTEGRATION_HINT, rel, line));
146
+ }
147
+ else if (HOST_INTERNAL.test(spec)) {
148
+ findings.push(finding(`${rel}:${line} imports "${spec}", which is host-internal.`, 'Build with @ekanos/ui and @ekanos/sdk; @kit/* does not exist for a partner.', rel, line));
149
+ }
150
+ else if (spec.startsWith('.')) {
151
+ const target = path.resolve(path.dirname(file), spec);
152
+ if (!isContained(project.dir, target)) {
153
+ findings.push(finding(`${rel}:${line} imports "${spec}", a path outside the project.`, `${INTEGRATION_HINT} Code outside this project is not part of the experience.`, rel, line));
154
+ }
155
+ else {
156
+ const owner = nearestEkanosKind(target);
157
+ if (owner && owner.kind === 'integration') {
158
+ findings.push(finding(`${rel}:${line} imports "${spec}", inside an integration project.`, INTEGRATION_HINT, rel, line));
159
+ }
160
+ }
161
+ }
162
+ }
163
+ for (const match of text.matchAll(COMPUTED_IMPORT)) {
164
+ const line = lineOf(text, (_c = match.index) !== null && _c !== void 0 ? _c : 0);
165
+ findings.push(finding(`${rel}:${line} uses a computed dynamic import(), which cannot be checked.`, 'Import a static specifier.', rel, line));
166
+ }
167
+ }
168
+ return findings;
169
+ }
170
+ // ─── 3. bundle scan ─────────────────────────────────────────────────────────
171
+ function externalsFor(project) {
172
+ var _a;
173
+ const names = [...ALLOWED_EXTERNALS, ...((_a = project.ekanos.allowImports) !== null && _a !== void 0 ? _a : [])];
174
+ return names.flatMap((name) => [name, `${name}/*`]);
175
+ }
176
+ /** Bundle the entry and fail on any input that is integration code. */
177
+ async function checkBundle(project) {
178
+ var _a;
179
+ const entry = path.join(project.dir, project.entry);
180
+ if (!fs.existsSync(entry)) {
181
+ return [
182
+ finding(`entry ${project.entry} does not exist.`, 'Fix "entry" in ekanos.json.', 'ekanos.json'),
183
+ ];
184
+ }
185
+ let result;
186
+ try {
187
+ result = await build({
188
+ entryPoints: [entry],
189
+ bundle: true,
190
+ write: false,
191
+ metafile: true,
192
+ platform: 'browser',
193
+ format: 'esm',
194
+ jsx: 'automatic',
195
+ logLevel: 'silent',
196
+ absWorkingDir: project.dir,
197
+ external: externalsFor(project),
198
+ loader: { '.css': 'empty', '.svg': 'dataurl', '.png': 'dataurl' },
199
+ });
200
+ }
201
+ catch (error) {
202
+ const errors = (_a = error.errors) !== null && _a !== void 0 ? _a : [];
203
+ if (errors.length === 0) {
204
+ return [
205
+ finding(`the entry could not be bundled: ${error instanceof Error ? error.message : String(error)}`, 'Fix the error and re-run.'),
206
+ ];
207
+ }
208
+ return errors.map((e) => { var _a, _b; return finding(`bundle: ${e.text}`, 'Install the dependency, fix the import, or add a reviewed specifier to ekanos.json "allowImports".', (_a = e.location) === null || _a === void 0 ? void 0 : _a.file, (_b = e.location) === null || _b === void 0 ? void 0 : _b.line); });
209
+ }
210
+ const findings = [];
211
+ const reported = new Set();
212
+ for (const input of Object.keys(result.metafile.inputs)) {
213
+ const absolute = path.resolve(project.dir, input);
214
+ let problem = null;
215
+ if (INTEGRATION_IN_PATH.test(absolute)) {
216
+ problem = 'is integration package code';
217
+ }
218
+ else if (!isContained(project.dir, absolute) &&
219
+ !absolute.includes(`${path.sep}node_modules${path.sep}`)) {
220
+ problem = 'is outside the project';
221
+ }
222
+ else if (absolute.includes(`${path.sep}node_modules${path.sep}`)) {
223
+ const owner = packageRootOf(absolute);
224
+ const kind = owner ? nearestEkanosKind(owner) : null;
225
+ if (kind && kind.dir === owner && kind.kind === 'integration') {
226
+ problem = 'is code of an integration package';
227
+ }
228
+ }
229
+ else {
230
+ const owner = nearestEkanosKind(absolute);
231
+ if (owner && owner.kind === 'integration')
232
+ problem = 'is inside an integration project';
233
+ }
234
+ if (problem && !reported.has(input)) {
235
+ reported.add(input);
236
+ findings.push(finding(`the bundle contains ${input}, which ${problem}.`, `${INTEGRATION_HINT} It reached the bundle through an import chain; the source scan cannot always see this.`, input));
237
+ }
238
+ }
239
+ return findings;
240
+ }
241
+ function packageRootOf(file) {
242
+ let current = path.dirname(file);
243
+ for (;;) {
244
+ if (fs.existsSync(path.join(current, 'package.json')))
245
+ return current;
246
+ const parent = path.dirname(current);
247
+ if (parent === current)
248
+ return null;
249
+ current = parent;
250
+ }
251
+ }
252
+ /** The authoritative boundary: package.json, source scan, bundle scan. */
253
+ export async function checkExperienceBoundary(project) {
254
+ return [
255
+ ...checkPackageJson(project),
256
+ ...checkSources(project),
257
+ ...(await checkBundle(project)),
258
+ ];
259
+ }
260
+ //# sourceMappingURL=boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/experience/boundary.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport { build } from 'esbuild';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { isContained } from '../safe-fs';\nimport type { ExperienceProject } from './project';\n\nexport const BOUNDARY_CHECK = 'checks/experience-boundary';\n\n/** Packages that may stay external at runtime (the host provides them). */\nexport const ALLOWED_EXTERNALS = [\n 'react',\n 'react-dom',\n '@ekanos/sdk',\n '@ekanos/ui',\n 'zod',\n '@tanstack/react-query',\n 'next-intl',\n 'react-hook-form',\n '@hookform/resolvers',\n] as const;\n\nconst INTEGRATION_PACKAGE = /^@companydotcom\\/integration-[a-z0-9-]+/;\n/** A pnpm-store or plain node_modules path of an integration package. */\nconst INTEGRATION_IN_PATH = /@companydotcom[/+]integration-[a-z0-9-]+/;\nconst HOST_INTERNAL = /^@kit\\//;\n\nconst SOURCE_EXT = /\\.(?:[cm]?[jt]sx?)$/;\nconst SKIP_DIRS = new Set(['node_modules', 'dist', '.ekanos', '.git']);\n\nconst finding = (\n message: string,\n hint: string,\n file?: string,\n line?: number,\n): Finding => ({\n check: BOUNDARY_CHECK,\n severity: 'error',\n message,\n hint,\n ...(file ? { file } : {}),\n ...(line ? { line } : {}),\n});\n\nconst INTEGRATION_HINT =\n 'An experience reaches an integration only through its pinned contract: useOperation / operation() / step.tool. Add the integration with \"ekanos add <integration>@<version>\".';\n\n/** Nearest `ekanos.json` at or above `start`, with its kind, or null. */\nexport function nearestEkanosKind(\n start: string,\n): { dir: string; kind: string } | null {\n let current =\n fs.existsSync(start) && fs.statSync(start).isDirectory()\n ? start\n : path.dirname(start);\n for (;;) {\n const file = path.join(current, 'ekanos.json');\n if (fs.existsSync(file)) {\n try {\n const parsed = JSON.parse(fs.readFileSync(file, 'utf8')) as {\n kind?: string;\n };\n\n return { dir: current, kind: parsed.kind ?? 'integration' };\n } catch {\n return { dir: current, kind: 'integration' };\n }\n }\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\n// ─── 1. package.json ────────────────────────────────────────────────────────\n\nconst DEP_FIELDS = [\n 'dependencies',\n 'devDependencies',\n 'peerDependencies',\n 'optionalDependencies',\n] as const;\n\nfunction checkPackageJson(project: ExperienceProject): Finding[] {\n const file = path.join(project.dir, 'package.json');\n if (!fs.existsSync(file)) return [];\n const findings: Finding[] = [];\n const pkg = JSON.parse(fs.readFileSync(file, 'utf8')) as Record<\n string,\n unknown\n >;\n\n const specs: Array<[string, string]> = [];\n for (const field of DEP_FIELDS) {\n const block = pkg[field];\n if (block && typeof block === 'object') {\n specs.push(...Object.entries(block as Record<string, string>));\n }\n }\n const pnpm = pkg.pnpm as { overrides?: Record<string, string> } | undefined;\n for (const block of [\n pnpm?.overrides,\n pkg.overrides as Record<string, string> | undefined,\n ]) {\n if (block && typeof block === 'object')\n specs.push(...Object.entries(block));\n }\n\n for (const [name, spec] of specs) {\n if (INTEGRATION_PACKAGE.test(name)) {\n findings.push(\n finding(\n `package.json depends on the integration package ${name}.`,\n INTEGRATION_HINT,\n 'package.json',\n ),\n );\n continue;\n }\n const linked = /^(?:file|link|workspace):(.*)$/.exec(String(spec));\n if (linked?.[1] !== undefined && linked[1] !== '*' && linked[1] !== '^') {\n const target = path.resolve(project.dir, linked[1]);\n const kind = nearestEkanosKind(target);\n if (\n kind &&\n kind.kind === 'integration' &&\n path.resolve(kind.dir) !== project.dir\n ) {\n findings.push(\n finding(\n `package.json links ${name} to ${linked[1]}, an integration project.`,\n INTEGRATION_HINT,\n 'package.json',\n ),\n );\n }\n }\n // A resolved dependency that is itself an integration, whatever its name.\n const installed = path.join(\n project.dir,\n 'node_modules',\n name,\n 'ekanos.json',\n );\n if (fs.existsSync(installed)) {\n try {\n const kind = (\n JSON.parse(fs.readFileSync(installed, 'utf8')) as { kind?: string }\n ).kind;\n if ((kind ?? 'integration') === 'integration') {\n findings.push(\n finding(\n `package.json depends on ${name}, which is an integration (its ekanos.json is kind \"integration\").`,\n INTEGRATION_HINT,\n 'package.json',\n ),\n );\n }\n } catch {\n // unreadable ekanos.json in a dependency: the bundle scan still applies\n }\n }\n }\n\n return findings;\n}\n\n// ─── 2. source scan ─────────────────────────────────────────────────────────\n\nconst IMPORT_PATTERN =\n /(?:\\bfrom\\s*|\\bimport\\s*\\(\\s*|\\bimport\\s+|\\brequire\\s*\\(\\s*)(['\"`])([^'\"`\\n]+)\\1/g;\nconst COMPUTED_IMPORT = /\\bimport\\s*\\(\\s*(?!['\"`])/g;\n\nfunction* walkSources(\n dir: string,\n ignore: readonly string[],\n root: string,\n): Generator<string> {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.isSymbolicLink()) continue;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n const rel = path.relative(root, full).split(path.sep).join('/');\n if (ignore.includes(rel)) continue;\n yield* walkSources(full, ignore, root);\n } else if (SOURCE_EXT.test(entry.name)) {\n yield full;\n }\n }\n}\n\nconst lineOf = (text: string, index: number) =>\n text.slice(0, index).split('\\n').length;\n\nfunction checkSources(project: ExperienceProject): Finding[] {\n const findings: Finding[] = [];\n const srcDir = path.join(project.dir, 'src');\n if (!fs.existsSync(srcDir)) return findings;\n const ignore = project.ekanos.ignore ?? [];\n\n for (const file of walkSources(srcDir, ignore, project.dir)) {\n const text = fs.readFileSync(file, 'utf8');\n const rel = path.relative(project.dir, file).split(path.sep).join('/');\n\n for (const match of text.matchAll(IMPORT_PATTERN)) {\n const spec = match[2] as string;\n const line = lineOf(text, match.index ?? 0);\n\n if (INTEGRATION_PACKAGE.test(spec) || INTEGRATION_IN_PATH.test(spec)) {\n findings.push(\n finding(\n `${rel}:${line} imports the integration \"${spec}\".`,\n INTEGRATION_HINT,\n rel,\n line,\n ),\n );\n } else if (HOST_INTERNAL.test(spec)) {\n findings.push(\n finding(\n `${rel}:${line} imports \"${spec}\", which is host-internal.`,\n 'Build with @ekanos/ui and @ekanos/sdk; @kit/* does not exist for a partner.',\n rel,\n line,\n ),\n );\n } else if (spec.startsWith('.')) {\n const target = path.resolve(path.dirname(file), spec);\n if (!isContained(project.dir, target)) {\n findings.push(\n finding(\n `${rel}:${line} imports \"${spec}\", a path outside the project.`,\n `${INTEGRATION_HINT} Code outside this project is not part of the experience.`,\n rel,\n line,\n ),\n );\n } else {\n const owner = nearestEkanosKind(target);\n if (owner && owner.kind === 'integration') {\n findings.push(\n finding(\n `${rel}:${line} imports \"${spec}\", inside an integration project.`,\n INTEGRATION_HINT,\n rel,\n line,\n ),\n );\n }\n }\n }\n }\n\n for (const match of text.matchAll(COMPUTED_IMPORT)) {\n const line = lineOf(text, match.index ?? 0);\n findings.push(\n finding(\n `${rel}:${line} uses a computed dynamic import(), which cannot be checked.`,\n 'Import a static specifier.',\n rel,\n line,\n ),\n );\n }\n }\n\n return findings;\n}\n\n// ─── 3. bundle scan ─────────────────────────────────────────────────────────\n\nfunction externalsFor(project: ExperienceProject): string[] {\n const names = [...ALLOWED_EXTERNALS, ...(project.ekanos.allowImports ?? [])];\n\n return names.flatMap((name) => [name, `${name}/*`]);\n}\n\n/** Bundle the entry and fail on any input that is integration code. */\nasync function checkBundle(project: ExperienceProject): Promise<Finding[]> {\n const entry = path.join(project.dir, project.entry);\n if (!fs.existsSync(entry)) {\n return [\n finding(\n `entry ${project.entry} does not exist.`,\n 'Fix \"entry\" in ekanos.json.',\n 'ekanos.json',\n ),\n ];\n }\n\n let result;\n try {\n result = await build({\n entryPoints: [entry],\n bundle: true,\n write: false,\n metafile: true,\n platform: 'browser',\n format: 'esm',\n jsx: 'automatic',\n logLevel: 'silent',\n absWorkingDir: project.dir,\n external: externalsFor(project),\n loader: { '.css': 'empty', '.svg': 'dataurl', '.png': 'dataurl' },\n });\n } catch (error) {\n const errors =\n (\n error as {\n errors?: Array<{\n text: string;\n location?: { file: string; line: number } | null;\n }>;\n }\n ).errors ?? [];\n if (errors.length === 0) {\n return [\n finding(\n `the entry could not be bundled: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the error and re-run.',\n ),\n ];\n }\n\n return errors.map((e) =>\n finding(\n `bundle: ${e.text}`,\n 'Install the dependency, fix the import, or add a reviewed specifier to ekanos.json \"allowImports\".',\n e.location?.file,\n e.location?.line,\n ),\n );\n }\n\n const findings: Finding[] = [];\n const reported = new Set<string>();\n for (const input of Object.keys(result.metafile.inputs)) {\n const absolute = path.resolve(project.dir, input);\n let problem: string | null = null;\n\n if (INTEGRATION_IN_PATH.test(absolute)) {\n problem = 'is integration package code';\n } else if (\n !isContained(project.dir, absolute) &&\n !absolute.includes(`${path.sep}node_modules${path.sep}`)\n ) {\n problem = 'is outside the project';\n } else if (absolute.includes(`${path.sep}node_modules${path.sep}`)) {\n const owner = packageRootOf(absolute);\n const kind = owner ? nearestEkanosKind(owner) : null;\n if (kind && kind.dir === owner && kind.kind === 'integration') {\n problem = 'is code of an integration package';\n }\n } else {\n const owner = nearestEkanosKind(absolute);\n if (owner && owner.kind === 'integration')\n problem = 'is inside an integration project';\n }\n\n if (problem && !reported.has(input)) {\n reported.add(input);\n findings.push(\n finding(\n `the bundle contains ${input}, which ${problem}.`,\n `${INTEGRATION_HINT} It reached the bundle through an import chain; the source scan cannot always see this.`,\n input,\n ),\n );\n }\n }\n\n return findings;\n}\n\nfunction packageRootOf(file: string): string | null {\n let current = path.dirname(file);\n for (;;) {\n if (fs.existsSync(path.join(current, 'package.json'))) return current;\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\n/** The authoritative boundary: package.json, source scan, bundle scan. */\nexport async function checkExperienceBoundary(\n project: ExperienceProject,\n): Promise<Finding[]> {\n return [\n ...checkPackageJson(project),\n ...checkSources(project),\n ...(await checkBundle(project)),\n ];\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { type ExperienceManifest } from '@ekanos/integration-schema';
2
+ import type { ExperienceCheckResult } from './checks.js';
3
+ import { type ExperienceProject } from './project.js';
4
+ export declare const DIST_ENTRY = "dist/experience.js";
5
+ export declare const DIST_TYPES = "dist/experience.d.ts";
6
+ export declare const MANIFEST_FILE = "manifest.json";
7
+ export interface BuildOutput {
8
+ manifest: ExperienceManifest;
9
+ files: string[];
10
+ changed: boolean;
11
+ }
12
+ /**
13
+ * Emit `dist/experience.js` (browser ESM, runtime packages left external),
14
+ * `dist/experience.d.ts` and `manifest.json`. Assumes the checks passed.
15
+ */
16
+ export declare function emitExperience(project: ExperienceProject, result: ExperienceCheckResult): Promise<BuildOutput>;