@ekanos/cli 0.16.0 → 0.17.1

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 (192) hide show
  1. package/README.md +62 -4
  2. package/dist/commands/add.d.ts +2 -0
  3. package/dist/commands/add.js +118 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/audiences.d.ts +31 -0
  6. package/dist/commands/audiences.js +239 -0
  7. package/dist/commands/audiences.js.map +1 -0
  8. package/dist/commands/build.d.ts +2 -0
  9. package/dist/commands/build.js +41 -0
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/check.d.ts +2 -0
  12. package/dist/commands/check.js +43 -0
  13. package/dist/commands/check.js.map +1 -0
  14. package/dist/commands/codegen.d.ts +2 -0
  15. package/dist/commands/codegen.js +49 -0
  16. package/dist/commands/codegen.js.map +1 -0
  17. package/dist/commands/contract.d.ts +52 -0
  18. package/dist/commands/contract.js +300 -0
  19. package/dist/commands/contract.js.map +1 -0
  20. package/dist/commands/dev.d.ts +7 -0
  21. package/dist/commands/dev.js +186 -12
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/index-experience.d.ts +31 -0
  24. package/dist/commands/index-experience.js +21 -0
  25. package/dist/commands/index-experience.js.map +1 -0
  26. package/dist/commands/init.d.ts +7 -0
  27. package/dist/commands/init.js +12 -0
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/new.d.ts +2 -0
  30. package/dist/commands/new.js +39 -0
  31. package/dist/commands/new.js.map +1 -0
  32. package/dist/commands/publish.d.ts +59 -0
  33. package/dist/commands/publish.js +118 -65
  34. package/dist/commands/publish.js.map +1 -1
  35. package/dist/commands/scope-notice.d.ts +6 -0
  36. package/dist/commands/scope-notice.js +15 -0
  37. package/dist/commands/scope-notice.js.map +1 -1
  38. package/dist/commands/sources.js +2 -0
  39. package/dist/commands/sources.js.map +1 -1
  40. package/dist/commands/sync.d.ts +2 -0
  41. package/dist/commands/sync.js +150 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/teams-audience.d.ts +12 -0
  44. package/dist/commands/teams-audience.js +144 -0
  45. package/dist/commands/teams-audience.js.map +1 -0
  46. package/dist/commands/teams-scope.js +4 -0
  47. package/dist/commands/teams-scope.js.map +1 -1
  48. package/dist/commands/teams.d.ts +1 -1
  49. package/dist/commands/teams.js +6 -1
  50. package/dist/commands/teams.js.map +1 -1
  51. package/dist/compatibility.d.ts +10 -1
  52. package/dist/compatibility.js +10 -1
  53. package/dist/compatibility.js.map +1 -1
  54. package/dist/dev-experience.d.ts +125 -0
  55. package/dist/dev-experience.js +128 -0
  56. package/dist/dev-experience.js.map +1 -0
  57. package/dist/experience/boundary.d.ts +12 -0
  58. package/dist/experience/boundary.js +260 -0
  59. package/dist/experience/boundary.js.map +1 -0
  60. package/dist/experience/build.d.ts +16 -0
  61. package/dist/experience/build.js +90 -0
  62. package/dist/experience/build.js.map +1 -0
  63. package/dist/experience/checks.d.ts +29 -0
  64. package/dist/experience/checks.js +133 -0
  65. package/dist/experience/checks.js.map +1 -0
  66. package/dist/experience/contract-sources.d.ts +36 -0
  67. package/dist/experience/contract-sources.js +111 -0
  68. package/dist/experience/contract-sources.js.map +1 -0
  69. package/dist/experience/definition-checks.d.ts +26 -0
  70. package/dist/experience/definition-checks.js +201 -0
  71. package/dist/experience/definition-checks.js.map +1 -0
  72. package/dist/experience/definition-shape.d.ts +60 -0
  73. package/dist/experience/definition-shape.js +2 -0
  74. package/dist/experience/definition-shape.js.map +1 -0
  75. package/dist/experience/init.d.ts +10 -0
  76. package/dist/experience/init.js +64 -0
  77. package/dist/experience/init.js.map +1 -0
  78. package/dist/experience/integrations-codegen.d.ts +17 -0
  79. package/dist/experience/integrations-codegen.js +51 -0
  80. package/dist/experience/integrations-codegen.js.map +1 -0
  81. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  82. package/dist/experience/json-schema-to-ts.js +90 -0
  83. package/dist/experience/json-schema-to-ts.js.map +1 -0
  84. package/dist/experience/load-experience.d.ts +23 -0
  85. package/dist/experience/load-experience.js +115 -0
  86. package/dist/experience/load-experience.js.map +1 -0
  87. package/dist/experience/overrides.d.ts +8 -0
  88. package/dist/experience/overrides.js +21 -0
  89. package/dist/experience/overrides.js.map +1 -0
  90. package/dist/experience/pin-integration.d.ts +8 -0
  91. package/dist/experience/pin-integration.js +17 -0
  92. package/dist/experience/pin-integration.js.map +1 -0
  93. package/dist/experience/pin-ops.d.ts +12 -0
  94. package/dist/experience/pin-ops.js +36 -0
  95. package/dist/experience/pin-ops.js.map +1 -0
  96. package/dist/experience/pins.d.ts +35 -0
  97. package/dist/experience/pins.js +91 -0
  98. package/dist/experience/pins.js.map +1 -0
  99. package/dist/experience/project.d.ts +52 -0
  100. package/dist/experience/project.js +149 -0
  101. package/dist/experience/project.js.map +1 -0
  102. package/dist/experience/publish.d.ts +51 -0
  103. package/dist/experience/publish.js +144 -0
  104. package/dist/experience/publish.js.map +1 -0
  105. package/dist/experience/regenerate.d.ts +10 -0
  106. package/dist/experience/regenerate.js +33 -0
  107. package/dist/experience/regenerate.js.map +1 -0
  108. package/dist/experience/report.d.ts +9 -0
  109. package/dist/experience/report.js +23 -0
  110. package/dist/experience/report.js.map +1 -0
  111. package/dist/harness-scaffold.d.ts +52 -18
  112. package/dist/harness-scaffold.js +189 -9
  113. package/dist/harness-scaffold.js.map +1 -1
  114. package/dist/index.js +139 -7
  115. package/dist/index.js.map +1 -1
  116. package/dist/pack.js +5 -0
  117. package/dist/pack.js.map +1 -1
  118. package/dist/project-checks.d.ts +19 -0
  119. package/dist/project-checks.js +93 -0
  120. package/dist/project-checks.js.map +1 -1
  121. package/dist/project.d.ts +14 -0
  122. package/dist/project.js +10 -0
  123. package/dist/project.js.map +1 -1
  124. package/dist/repo/publish.d.ts +42 -0
  125. package/dist/repo/publish.js +225 -0
  126. package/dist/repo/publish.js.map +1 -0
  127. package/dist/repo/scaffold.d.ts +30 -0
  128. package/dist/repo/scaffold.js +165 -0
  129. package/dist/repo/scaffold.js.map +1 -0
  130. package/dist/teams-api.d.ts +37 -1
  131. package/dist/teams-api.js +62 -1
  132. package/dist/teams-api.js.map +1 -1
  133. package/dist/validate-findings.js +3 -1
  134. package/dist/validate-findings.js.map +1 -1
  135. package/package.json +2 -2
  136. package/templates/ekanos.json.tmpl +1 -0
  137. package/templates/experience/AGENTS.md.tmpl +22 -0
  138. package/templates/experience/ekanos.json.tmpl +7 -0
  139. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  140. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  141. package/templates/experience/gitignore.tmpl +17 -0
  142. package/templates/experience/package.json.tmpl +41 -0
  143. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  144. package/templates/experience/src/experience.ts.tmpl +30 -0
  145. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  146. package/templates/experience/tsconfig.json.tmpl +20 -0
  147. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  148. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  149. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  150. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  151. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  152. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  153. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  154. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  155. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  156. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  157. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  158. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  159. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  160. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  161. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  162. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  163. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  164. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  165. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  166. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  167. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  168. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  169. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  170. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  171. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  172. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  173. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  174. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  175. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  176. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  177. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  178. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  179. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  180. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  181. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  182. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  183. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  184. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  185. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  186. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  187. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  188. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  189. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  190. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  191. package/templates/harness/styles/globals.css.tmpl +15 -1
  192. package/templates/package.json.tmpl +5 -4
package/dist/pack.js CHANGED
@@ -14,6 +14,8 @@ import { ESLINT_CONFIG_NAMES, VITEST_CONFIG_NAMES } from './project-checks.js';
14
14
  *
15
15
  * - `ekanos.json`, `package.json` (required)
16
16
  * - `README.md` (when present)
17
+ * - `contract.json` (when present): the hosted gate's `checks/contract-emit`
18
+ * compares it to what the code derives and refuses an archive without it
17
19
  * - `tsconfig*.json` at the project root (when present)
18
20
  * - a vitest/vite config at the project root (when present — see
19
21
  * `VITEST_CONFIG_NAMES`)
@@ -86,6 +88,9 @@ export function selectProjectFiles(projectDir) {
86
88
  if (isRegularFile(path.join(projectDir, 'README.md'))) {
87
89
  selected.push('README.md');
88
90
  }
91
+ if (isRegularFile(path.join(projectDir, 'contract.json'))) {
92
+ selected.push('contract.json');
93
+ }
89
94
  selected.push(...selectRootConfigFiles(projectDir));
90
95
  const srcDir = path.join(projectDir, 'src');
91
96
  // lstat, not stat: `src` itself as a symlink must not pull a foreign tree
package/dist/pack.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as zlib from 'node:zlib';\n\nimport { preconditionError, validationError } from './errors';\nimport { ESLINT_CONFIG_NAMES, VITEST_CONFIG_NAMES } from './project-checks';\n\n/**\n * Deterministic project packing for `ekanos publish`.\n *\n * A minimal ustar writer over Node's built-ins — no `tar` dependency. The\n * selection is deliberately a WHITELIST, not \"everything minus a blocklist\":\n * the submission is reviewed by an operator and installed into Ekanos, so the\n * archive must contain exactly the files the toolchain knows about and\n * nothing a working directory happens to hold.\n *\n * - `ekanos.json`, `package.json` (required)\n * - `README.md` (when present)\n * - `tsconfig*.json` at the project root (when present)\n * - a vitest/vite config at the project root (when present — see\n * `VITEST_CONFIG_NAMES`)\n * - an ESLint flat config at the project root (when present — see\n * `ESLINT_CONFIG_NAMES`): the gate's `checks/capability-lint` runs the\n * capability preset from it, and refuses an archive without one\n * - everything under `src/`, recursively\n *\n * The tsconfig/vitest entries exist because the hosted gate's\n * `checks/typecheck` and `checks/tests` stages run the project's OWN `tsc`\n * and `vitest` — over the ARCHIVE, not the partner's working directory — so\n * without them there is no `tsconfig.json` to compile against (`tsc --noEmit`\n * falls back to printing its help text and exiting 1, not a type error) and\n * no test config to load the SDK's server-inline block. `ekanos init`\n * scaffolds both at the project root, so a project published straight from\n * `init` always has them; this walk just stops silently dropping them.\n *\n * Excluded everywhere: dotfiles and dot-directories (which covers `.ekanos`,\n * `.git`, `.env`), `node_modules`, and anything that is not a regular file\n * (symlinks are skipped, not followed — a link pointing outside the project\n * must never pull foreign bytes into a submission).\n *\n * Determinism: entries are sorted by path and every header carries a fixed\n * mtime, so packing the same tree twice yields byte-identical archives and a\n * stable sha256.\n */\n\nconst BLOCK_SIZE = 512;\n\n/** Fixed header mtime — determinism over provenance (the VCS owns history). */\nconst FIXED_MTIME_SECONDS = 0;\n\n/** The server refuses archives over 5 MiB; fail fast on the client too. */\n// 4 MiB — mirrors the server route's cap, which itself sits under Vercel's\n// ~4.5 MB request-body ceiling so oversize is caught here with a clear error\n// instead of by an opaque platform 413.\nexport const MAX_ARCHIVE_BYTES = 4 * 1024 * 1024;\n\nexport interface PackedProject {\n /** The gzipped tarball. */\n archive: Buffer;\n /** Project-relative POSIX paths, in the order they were packed. */\n files: string[];\n}\n\nexport function packProject(projectDir: string): PackedProject {\n const files = selectProjectFiles(projectDir);\n\n const blocks: Buffer[] = [];\n\n for (const relative of files) {\n const absolute = path.join(projectDir, relative);\n const content = fs.readFileSync(absolute);\n\n blocks.push(tarHeader(relative, content.length));\n blocks.push(content);\n\n const remainder = content.length % BLOCK_SIZE;\n if (remainder !== 0) {\n blocks.push(Buffer.alloc(BLOCK_SIZE - remainder));\n }\n }\n\n // End-of-archive: two zero blocks.\n blocks.push(Buffer.alloc(BLOCK_SIZE * 2));\n\n const archive = zlib.gzipSync(Buffer.concat(blocks), { level: 9 });\n\n if (archive.length > MAX_ARCHIVE_BYTES) {\n throw validationError(\n `The packed archive is ${archive.length} bytes, over the ` +\n `${MAX_ARCHIVE_BYTES}-byte submission limit.`,\n 'Reduce the project to source only — large binary assets do not ' +\n 'belong in an integration submission.',\n );\n }\n\n return { archive, files };\n}\n\n/**\n * The whitelist walk. Exported for tests: the selection rules are the part\n * with security consequences, so they are asserted directly.\n */\nexport function selectProjectFiles(projectDir: string): string[] {\n for (const required of ['ekanos.json', 'package.json']) {\n if (!isRegularFile(path.join(projectDir, required))) {\n throw preconditionError(\n `Cannot pack ${projectDir}: ${required} is missing.`,\n `Run \"ekanos publish\" from an integration project (or pass --dir), ` +\n `and run \"ekanos init\" first if this is a new project.`,\n );\n }\n }\n\n const selected = ['ekanos.json', 'package.json'];\n\n if (isRegularFile(path.join(projectDir, 'README.md'))) {\n selected.push('README.md');\n }\n\n selected.push(...selectRootConfigFiles(projectDir));\n\n const srcDir = path.join(projectDir, 'src');\n // lstat, not stat: `src` itself as a symlink must not pull a foreign tree\n // into the archive — the same rule walk() applies to every entry inside.\n if (fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()) {\n selected.push(...walk(projectDir, 'src'));\n }\n\n return selected.sort();\n}\n\n/** `tsconfig.json`, plus any sibling a root `tsconfig.json` might `extends`\n * (e.g. `tsconfig.build.json`) — matched by name, not content, since packing\n * runs before anything parses JSON with comments. Root-level only: this is\n * about the build/test config the gate's own stages need, not a recursive\n * search. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * The project-root config files the hosted gate's `checks/typecheck`,\n * `checks/tests` and `checks/capability-lint` stages need to run the project's OWN `tsc`/`vitest`\n * meaningfully — see the module doc comment above. Each is included only\n * when present as a regular file (never a symlink, matching every other\n * entry point in this walk), so a project without one of these (unusual, but\n * not invalid) packs exactly as before.\n */\nfunction selectRootConfigFiles(projectDir: string): string[] {\n const selected: string[] = [];\n\n for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {\n if (!TSCONFIG_PATTERN.test(entry.name)) continue;\n if (!isRegularFile(path.join(projectDir, entry.name))) continue;\n selected.push(entry.name);\n }\n\n for (const name of [...VITEST_CONFIG_NAMES, ...ESLINT_CONFIG_NAMES]) {\n if (isRegularFile(path.join(projectDir, name))) selected.push(name);\n }\n\n return selected;\n}\n\nfunction walk(projectDir: string, relativeDir: string): string[] {\n const absoluteDir = path.join(projectDir, relativeDir);\n const collected: string[] = [];\n\n for (const entry of fs.readdirSync(absoluteDir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue;\n if (entry.name === 'node_modules') continue;\n\n const relative = `${relativeDir}/${entry.name}`;\n\n if (entry.isDirectory()) {\n collected.push(...walk(projectDir, relative));\n continue;\n }\n\n // Regular files only. `isFile()` is false for symlinks under\n // `withFileTypes`, so a link — wherever it points — is skipped here.\n if (entry.isFile()) {\n collected.push(relative);\n }\n }\n\n return collected;\n}\n\nfunction isRegularFile(absolute: string): boolean {\n try {\n return fs.lstatSync(absolute).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * One 512-byte ustar header. Long paths use the ustar `prefix` field\n * (155 + '/' + 100); a path that fits neither is refused with a clear error\n * rather than silently truncated.\n */\nfunction tarHeader(relativePath: string, size: number): Buffer {\n const { name, prefix } = splitUstarPath(relativePath);\n const header = Buffer.alloc(BLOCK_SIZE);\n\n header.write(name, 0, 100, 'utf8');\n writeOctal(header, 100, 8, 0o644); // mode\n writeOctal(header, 108, 8, 0); // uid\n writeOctal(header, 116, 8, 0); // gid\n writeOctal(header, 124, 12, size);\n writeOctal(header, 136, 12, FIXED_MTIME_SECONDS);\n header.fill(' ', 148, 156); // checksum placeholder: eight spaces\n header.write('0', 156, 1, 'utf8'); // typeflag: regular file\n header.write('ustar', 257, 5, 'utf8');\n header.write('00', 263, 2, 'utf8'); // version\n header.write(prefix, 345, 155, 'utf8');\n\n let checksum = 0;\n for (const byte of header) checksum += byte;\n\n // Six octal digits, NUL, space — the historical checksum encoding.\n header.write(`${checksum.toString(8).padStart(6, '0')}\\0 `, 148, 8, 'latin1');\n\n return header;\n}\n\nfunction splitUstarPath(relativePath: string): {\n name: string;\n prefix: string;\n} {\n if (Buffer.byteLength(relativePath, 'utf8') <= 100) {\n return { name: relativePath, prefix: '' };\n }\n\n // Split at a '/' so that name ≤ 100 bytes and prefix ≤ 155 bytes.\n for (let i = relativePath.length - 1; i > 0; i--) {\n if (relativePath[i] !== '/') continue;\n\n const prefix = relativePath.slice(0, i);\n const name = relativePath.slice(i + 1);\n\n if (\n Buffer.byteLength(name, 'utf8') <= 100 &&\n Buffer.byteLength(prefix, 'utf8') <= 155\n ) {\n return { name, prefix };\n }\n }\n\n throw validationError(\n `Cannot pack \"${relativePath}\": the path is too long for a tar entry.`,\n 'Shorten the file path (tar limits one segment to 100 bytes and the ' +\n 'directory prefix to 155).',\n );\n}\n\nfunction writeOctal(\n header: Buffer,\n offset: number,\n length: number,\n value: number,\n): void {\n header.write(\n `${value.toString(8).padStart(length - 1, '0')}\\0`,\n offset,\n length,\n 'latin1',\n );\n}\n"]}
1
+ {"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,+EAA+E;AAC/E,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,wCAAwC;AACxC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AASjD,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAE1C,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAErB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;QAC9C,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAEnE,IAAI,OAAO,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACvC,MAAM,eAAe,CACnB,yBAAyB,OAAO,CAAC,MAAM,mBAAmB;YACxD,GAAG,iBAAiB,yBAAyB,EAC/C,iEAAiE;YAC/D,sCAAsC,CACzC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,KAAK,MAAM,QAAQ,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YACpD,MAAM,iBAAiB,CACrB,eAAe,UAAU,KAAK,QAAQ,cAAc,EACpD,oEAAoE;gBAClE,uDAAuD,CAC1D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAEjD,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QACtD,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACjC,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,yEAAyE;IACzE,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAChE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;;;aAIa;AACb,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AAEvD;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACxE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QACjD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAChE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,CAAC,EAAE,CAAC;QACpE,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,IAAI,CAAC,UAAkB,EAAE,WAAmB;IACnD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACvD,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;YAAE,SAAS;QAE5C,MAAM,QAAQ,GAAG,GAAG,WAAW,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAEhD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC9C,SAAS;QACX,CAAC;QAED,6DAA6D;QAC7D,qEAAqE;QACrE,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;IACzC,CAAC;eAAO,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,YAAoB,EAAE,IAAY;IACnD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAExC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO;IAC1C,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM;IACrC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM;IACrC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IAClC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAC;IACjD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,qCAAqC;IACjE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,yBAAyB;IAC5D,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU;IAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAEvC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,MAAM;QAAE,QAAQ,IAAI,IAAI,CAAC;IAE5C,mEAAmE;IACnE,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE9E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,cAAc,CAAC,YAAoB;IAI1C,IAAI,MAAM,CAAC,UAAU,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACnD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC5C,CAAC;IAED,kEAAkE;IAClE,KAAK,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,SAAS;QAEtC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAEvC,IACE,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG;YACtC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,EACxC,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,eAAe,CACnB,gBAAgB,YAAY,0CAA0C,EACtE,qEAAqE;QACnE,2BAA2B,CAC9B,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,MAAc,EACd,MAAc,EACd,MAAc,EACd,KAAa;IAEb,MAAM,CAAC,KAAK,CACV,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAClD,MAAM,EACN,MAAM,EACN,QAAQ,CACT,CAAC;AACJ,CAAC","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as zlib from 'node:zlib';\n\nimport { preconditionError, validationError } from './errors';\nimport { ESLINT_CONFIG_NAMES, VITEST_CONFIG_NAMES } from './project-checks';\n\n/**\n * Deterministic project packing for `ekanos publish`.\n *\n * A minimal ustar writer over Node's built-ins — no `tar` dependency. The\n * selection is deliberately a WHITELIST, not \"everything minus a blocklist\":\n * the submission is reviewed by an operator and installed into Ekanos, so the\n * archive must contain exactly the files the toolchain knows about and\n * nothing a working directory happens to hold.\n *\n * - `ekanos.json`, `package.json` (required)\n * - `README.md` (when present)\n * - `contract.json` (when present): the hosted gate's `checks/contract-emit`\n * compares it to what the code derives and refuses an archive without it\n * - `tsconfig*.json` at the project root (when present)\n * - a vitest/vite config at the project root (when present — see\n * `VITEST_CONFIG_NAMES`)\n * - an ESLint flat config at the project root (when present — see\n * `ESLINT_CONFIG_NAMES`): the gate's `checks/capability-lint` runs the\n * capability preset from it, and refuses an archive without one\n * - everything under `src/`, recursively\n *\n * The tsconfig/vitest entries exist because the hosted gate's\n * `checks/typecheck` and `checks/tests` stages run the project's OWN `tsc`\n * and `vitest` — over the ARCHIVE, not the partner's working directory — so\n * without them there is no `tsconfig.json` to compile against (`tsc --noEmit`\n * falls back to printing its help text and exiting 1, not a type error) and\n * no test config to load the SDK's server-inline block. `ekanos init`\n * scaffolds both at the project root, so a project published straight from\n * `init` always has them; this walk just stops silently dropping them.\n *\n * Excluded everywhere: dotfiles and dot-directories (which covers `.ekanos`,\n * `.git`, `.env`), `node_modules`, and anything that is not a regular file\n * (symlinks are skipped, not followed — a link pointing outside the project\n * must never pull foreign bytes into a submission).\n *\n * Determinism: entries are sorted by path and every header carries a fixed\n * mtime, so packing the same tree twice yields byte-identical archives and a\n * stable sha256.\n */\n\nconst BLOCK_SIZE = 512;\n\n/** Fixed header mtime — determinism over provenance (the VCS owns history). */\nconst FIXED_MTIME_SECONDS = 0;\n\n/** The server refuses archives over 5 MiB; fail fast on the client too. */\n// 4 MiB — mirrors the server route's cap, which itself sits under Vercel's\n// ~4.5 MB request-body ceiling so oversize is caught here with a clear error\n// instead of by an opaque platform 413.\nexport const MAX_ARCHIVE_BYTES = 4 * 1024 * 1024;\n\nexport interface PackedProject {\n /** The gzipped tarball. */\n archive: Buffer;\n /** Project-relative POSIX paths, in the order they were packed. */\n files: string[];\n}\n\nexport function packProject(projectDir: string): PackedProject {\n const files = selectProjectFiles(projectDir);\n\n const blocks: Buffer[] = [];\n\n for (const relative of files) {\n const absolute = path.join(projectDir, relative);\n const content = fs.readFileSync(absolute);\n\n blocks.push(tarHeader(relative, content.length));\n blocks.push(content);\n\n const remainder = content.length % BLOCK_SIZE;\n if (remainder !== 0) {\n blocks.push(Buffer.alloc(BLOCK_SIZE - remainder));\n }\n }\n\n // End-of-archive: two zero blocks.\n blocks.push(Buffer.alloc(BLOCK_SIZE * 2));\n\n const archive = zlib.gzipSync(Buffer.concat(blocks), { level: 9 });\n\n if (archive.length > MAX_ARCHIVE_BYTES) {\n throw validationError(\n `The packed archive is ${archive.length} bytes, over the ` +\n `${MAX_ARCHIVE_BYTES}-byte submission limit.`,\n 'Reduce the project to source only — large binary assets do not ' +\n 'belong in an integration submission.',\n );\n }\n\n return { archive, files };\n}\n\n/**\n * The whitelist walk. Exported for tests: the selection rules are the part\n * with security consequences, so they are asserted directly.\n */\nexport function selectProjectFiles(projectDir: string): string[] {\n for (const required of ['ekanos.json', 'package.json']) {\n if (!isRegularFile(path.join(projectDir, required))) {\n throw preconditionError(\n `Cannot pack ${projectDir}: ${required} is missing.`,\n `Run \"ekanos publish\" from an integration project (or pass --dir), ` +\n `and run \"ekanos init\" first if this is a new project.`,\n );\n }\n }\n\n const selected = ['ekanos.json', 'package.json'];\n\n if (isRegularFile(path.join(projectDir, 'README.md'))) {\n selected.push('README.md');\n }\n\n if (isRegularFile(path.join(projectDir, 'contract.json'))) {\n selected.push('contract.json');\n }\n\n selected.push(...selectRootConfigFiles(projectDir));\n\n const srcDir = path.join(projectDir, 'src');\n // lstat, not stat: `src` itself as a symlink must not pull a foreign tree\n // into the archive — the same rule walk() applies to every entry inside.\n if (fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()) {\n selected.push(...walk(projectDir, 'src'));\n }\n\n return selected.sort();\n}\n\n/** `tsconfig.json`, plus any sibling a root `tsconfig.json` might `extends`\n * (e.g. `tsconfig.build.json`) — matched by name, not content, since packing\n * runs before anything parses JSON with comments. Root-level only: this is\n * about the build/test config the gate's own stages need, not a recursive\n * search. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * The project-root config files the hosted gate's `checks/typecheck`,\n * `checks/tests` and `checks/capability-lint` stages need to run the project's OWN `tsc`/`vitest`\n * meaningfully — see the module doc comment above. Each is included only\n * when present as a regular file (never a symlink, matching every other\n * entry point in this walk), so a project without one of these (unusual, but\n * not invalid) packs exactly as before.\n */\nfunction selectRootConfigFiles(projectDir: string): string[] {\n const selected: string[] = [];\n\n for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {\n if (!TSCONFIG_PATTERN.test(entry.name)) continue;\n if (!isRegularFile(path.join(projectDir, entry.name))) continue;\n selected.push(entry.name);\n }\n\n for (const name of [...VITEST_CONFIG_NAMES, ...ESLINT_CONFIG_NAMES]) {\n if (isRegularFile(path.join(projectDir, name))) selected.push(name);\n }\n\n return selected;\n}\n\nfunction walk(projectDir: string, relativeDir: string): string[] {\n const absoluteDir = path.join(projectDir, relativeDir);\n const collected: string[] = [];\n\n for (const entry of fs.readdirSync(absoluteDir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue;\n if (entry.name === 'node_modules') continue;\n\n const relative = `${relativeDir}/${entry.name}`;\n\n if (entry.isDirectory()) {\n collected.push(...walk(projectDir, relative));\n continue;\n }\n\n // Regular files only. `isFile()` is false for symlinks under\n // `withFileTypes`, so a link — wherever it points — is skipped here.\n if (entry.isFile()) {\n collected.push(relative);\n }\n }\n\n return collected;\n}\n\nfunction isRegularFile(absolute: string): boolean {\n try {\n return fs.lstatSync(absolute).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * One 512-byte ustar header. Long paths use the ustar `prefix` field\n * (155 + '/' + 100); a path that fits neither is refused with a clear error\n * rather than silently truncated.\n */\nfunction tarHeader(relativePath: string, size: number): Buffer {\n const { name, prefix } = splitUstarPath(relativePath);\n const header = Buffer.alloc(BLOCK_SIZE);\n\n header.write(name, 0, 100, 'utf8');\n writeOctal(header, 100, 8, 0o644); // mode\n writeOctal(header, 108, 8, 0); // uid\n writeOctal(header, 116, 8, 0); // gid\n writeOctal(header, 124, 12, size);\n writeOctal(header, 136, 12, FIXED_MTIME_SECONDS);\n header.fill(' ', 148, 156); // checksum placeholder: eight spaces\n header.write('0', 156, 1, 'utf8'); // typeflag: regular file\n header.write('ustar', 257, 5, 'utf8');\n header.write('00', 263, 2, 'utf8'); // version\n header.write(prefix, 345, 155, 'utf8');\n\n let checksum = 0;\n for (const byte of header) checksum += byte;\n\n // Six octal digits, NUL, space — the historical checksum encoding.\n header.write(`${checksum.toString(8).padStart(6, '0')}\\0 `, 148, 8, 'latin1');\n\n return header;\n}\n\nfunction splitUstarPath(relativePath: string): {\n name: string;\n prefix: string;\n} {\n if (Buffer.byteLength(relativePath, 'utf8') <= 100) {\n return { name: relativePath, prefix: '' };\n }\n\n // Split at a '/' so that name ≤ 100 bytes and prefix ≤ 155 bytes.\n for (let i = relativePath.length - 1; i > 0; i--) {\n if (relativePath[i] !== '/') continue;\n\n const prefix = relativePath.slice(0, i);\n const name = relativePath.slice(i + 1);\n\n if (\n Buffer.byteLength(name, 'utf8') <= 100 &&\n Buffer.byteLength(prefix, 'utf8') <= 155\n ) {\n return { name, prefix };\n }\n }\n\n throw validationError(\n `Cannot pack \"${relativePath}\": the path is too long for a tar entry.`,\n 'Shorten the file path (tar limits one segment to 100 bytes and the ' +\n 'directory prefix to 155).',\n );\n}\n\nfunction writeOctal(\n header: Buffer,\n offset: number,\n length: number,\n value: number,\n): void {\n header.write(\n `${value.toString(8).padStart(length - 1, '0')}\\0`,\n offset,\n length,\n 'latin1',\n );\n}\n"]}
@@ -68,3 +68,22 @@ export declare function collectDocsFindings(project: EkanosProject, projectDir:
68
68
  * host-rules sibling) already report that loudly on their own.
69
69
  */
70
70
  export declare function collectHostCompilerRulesFindings(projectDir: string): Finding[];
71
+ /**
72
+ * The hosted gate's `checks/capability-lint` runs THE PROJECT'S OWN ESLint
73
+ * (its pinned `node_modules/.bin/eslint`, its `eslint.config.*` spreading
74
+ * `@ekanos/sdk/eslint`) and refuses a submission without them: a control the
75
+ * submission can leave out is not a control, so the gate does not fall back to
76
+ * an ESLint of its own. `ekanos init` scaffolds both; a repo created before
77
+ * that, or by hand, has neither and is refused at publish time. Say so here,
78
+ * locally, with the exact fix, as a warning (the refusal is the gate's).
79
+ *
80
+ * Silent when there is no `package.json` (nothing installable to inspect).
81
+ */
82
+ export declare function collectLintToolingFindings(projectDir: string): Finding[];
83
+ /**
84
+ * The hosted gate installs the archive's `package.json` on its own, so
85
+ * `@types/node` hoisted from a repo root or pulled in transitively is not
86
+ * there. A vitest config that imports `node:*` (the scaffold's does) then
87
+ * fails `checks/typecheck` with TS2307 unless the project declares it.
88
+ */
89
+ export declare function collectNodeTypesFindings(projectDir: string): Finding[];
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { installCommand, detectPackageManager } from './package-manager.js';
3
4
  import { isDocUrl } from './project.js';
4
5
  /**
5
6
  * Every vitest/vite config basename `ekanos init` might have scaffolded (or a
@@ -282,4 +283,96 @@ function vitestInlinesSdk(source) {
282
283
  }
283
284
  return false;
284
285
  }
286
+ /**
287
+ * The hosted gate's `checks/capability-lint` runs THE PROJECT'S OWN ESLint
288
+ * (its pinned `node_modules/.bin/eslint`, its `eslint.config.*` spreading
289
+ * `@ekanos/sdk/eslint`) and refuses a submission without them: a control the
290
+ * submission can leave out is not a control, so the gate does not fall back to
291
+ * an ESLint of its own. `ekanos init` scaffolds both; a repo created before
292
+ * that, or by hand, has neither and is refused at publish time. Say so here,
293
+ * locally, with the exact fix, as a warning (the refusal is the gate's).
294
+ *
295
+ * Silent when there is no `package.json` (nothing installable to inspect).
296
+ */
297
+ export function collectLintToolingFindings(projectDir) {
298
+ if (!fs.existsSync(path.join(projectDir, 'package.json')))
299
+ return [];
300
+ const hasEslint = fs.existsSync(path.join(projectDir, 'node_modules', 'eslint', 'package.json'));
301
+ const hasConfig = ESLINT_CONFIG_NAMES.some((name) => fs.existsSync(path.join(projectDir, name)));
302
+ if (hasEslint && hasConfig)
303
+ return [];
304
+ const missing = [
305
+ ...(hasEslint ? [] : ['eslint is not installed']),
306
+ ...(hasConfig ? [] : ['there is no eslint.config.mjs']),
307
+ ];
308
+ const add = installCommand(detectPackageManager(projectDir), [
309
+ 'eslint',
310
+ '@typescript-eslint/parser',
311
+ ]);
312
+ return [
313
+ {
314
+ check: 'project.capability-lint-tooling',
315
+ severity: 'warn',
316
+ message: `${missing.join(' and ')}, so the publish gate's capability lint ` +
317
+ '(@ekanos/sdk/eslint) cannot run on this project and will refuse it.',
318
+ hint: `Run "${add}" (as devDependencies), then add an eslint.config.mjs ` +
319
+ 'that spreads the presets — "ekanos init" prints the scaffold, or ' +
320
+ 'copy templates/eslint.config.mjs.tmpl from @ekanos/cli:\n\n' +
321
+ "import capabilityPreset from '@ekanos/sdk/eslint';\n" +
322
+ "import tsParser from '@typescript-eslint/parser';\n" +
323
+ 'export default [\n' +
324
+ " { ignores: ['dist/**', 'node_modules/**', '.ekanos/**'] },\n" +
325
+ " { files: ['src/**/*.ts', 'src/**/*.tsx'], languageOptions: { parser: tsParser, ecmaVersion: 'latest', sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } } } },\n" +
326
+ ' ...capabilityPreset,\n' +
327
+ '];',
328
+ },
329
+ ];
330
+ }
331
+ const NODE_IMPORT_PATTERN = /\bfrom\s+['"]node:/;
332
+ /**
333
+ * The hosted gate installs the archive's `package.json` on its own, so
334
+ * `@types/node` hoisted from a repo root or pulled in transitively is not
335
+ * there. A vitest config that imports `node:*` (the scaffold's does) then
336
+ * fails `checks/typecheck` with TS2307 unless the project declares it.
337
+ */
338
+ export function collectNodeTypesFindings(projectDir) {
339
+ var _a, _b;
340
+ const manifestPath = path.join(projectDir, 'package.json');
341
+ if (!fs.existsSync(manifestPath))
342
+ return [];
343
+ let manifest;
344
+ try {
345
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
346
+ }
347
+ catch (_c) {
348
+ return [];
349
+ }
350
+ if (((_a = manifest.dependencies) === null || _a === void 0 ? void 0 : _a['@types/node']) ||
351
+ ((_b = manifest.devDependencies) === null || _b === void 0 ? void 0 : _b['@types/node'])) {
352
+ return [];
353
+ }
354
+ const importer = [
355
+ ...VITEST_CONFIG_NAMES,
356
+ ...fs.readdirSync(projectDir).filter((name) => /\.[cm]?tsx?$/.test(name)),
357
+ ].find((name) => {
358
+ const file = path.join(projectDir, name);
359
+ return (fs.existsSync(file) &&
360
+ fs.statSync(file).isFile() &&
361
+ NODE_IMPORT_PATTERN.test(fs.readFileSync(file, 'utf8')));
362
+ });
363
+ if (!importer)
364
+ return [];
365
+ return [
366
+ {
367
+ check: 'project.node-types',
368
+ severity: 'error',
369
+ file: path.join(projectDir, importer),
370
+ message: `${importer} imports a "node:" module but package.json declares no ` +
371
+ `@types/node. The publish gate installs this package on its own, so ` +
372
+ `types hoisted from a repo root or pulled in transitively are not ` +
373
+ `there and typecheck fails with TS2307.`,
374
+ hint: 'Add @types/node to devDependencies (e.g. "@types/node": "^22.10.0").',
375
+ },
376
+ ];
377
+ }
285
378
  //# sourceMappingURL=project-checks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { type EkanosProject, isDocUrl } from './project';\n\n/**\n * Every vitest/vite config basename `ekanos init` might have scaffolded (or a\n * partner might have renamed to). Exported so `pack.ts` can pack exactly the\n * same set it checks here — one list, so packing and validating this project\n * property can never disagree on what \"the test config\" even is.\n */\nexport const VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/**\n * The ESLint flat-config basenames the hosted gate's `checks/capability-lint`\n * looks for in the ARCHIVE — the same four, so a project that lints locally\n * is never refused for a config `ekanos publish` left behind.\n */\nexport const ESLINT_CONFIG_NAMES = [\n 'eslint.config.mjs',\n 'eslint.config.js',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * The host's own compiler-strictness overlay a project's tsconfig should\n * already satisfy — mirrors the exact flags (`HOST_TSC_FLAGS`) the gate's\n * own SECOND `tsc --noEmit` pass checks, read from the host's real tsconfig\n * (source of truth for the values themselves). Hardcoded here for the same\n * reason it is hardcoded there: this package ships to a partner's local\n * machine, so it cannot read a file inside the host's own repo at runtime.\n * Rules-as-data via `/api/partner/toolchain` supersedes this hardcoded copy\n * later — until then, keep both copies in sync by hand.\n */\nexport const HOST_COMPILER_RULES: Record<string, boolean> = {\n strict: true,\n noUncheckedIndexedAccess: true,\n};\n\n/** Matches `tsconfig.json`, `tsconfig.build.json`, etc. — mirrors the pattern\n * `pack.ts` and the gate's `typecheck.ts` use to find a project's tsconfig. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * Advisory check on the `docs` array: a relative entry that resolves to\n * nothing on disk gets a WARNING — never an error, because a missing doc file\n * must not block a publish (the gate refuses only error-severity findings).\n * URLs are never checked: `validate` does no network, ever, and an agent\n * dereferences them at authoring time instead.\n */\nexport function collectDocsFindings(\n project: EkanosProject,\n projectDir: string,\n): Finding[] {\n const findings: Finding[] = [];\n\n for (const entry of project.docs ?? []) {\n if (isDocUrl(entry)) continue;\n\n const resolved = path.resolve(projectDir, entry);\n if (fs.existsSync(resolved)) continue;\n\n findings.push({\n check: 'docs/missing-file',\n severity: 'warn',\n file: resolved,\n message:\n `ekanos.json lists \"${entry}\" under \"docs\", but no file exists ` +\n `there (resolved to ${resolved}).`,\n hint:\n `Create ${entry}, or fix the path in ekanos.json's \"docs\" array so ` +\n 'it points at real API documentation.',\n });\n }\n\n return findings;\n}\n\n/**\n * WARN when the project's tsconfig does not already set the host's\n * compiler-strictness overlay (`HOST_COMPILER_RULES`). This exists because a\n * project can pass its own `checks/typecheck` cleanly, publish, and only THEN\n * discover `checks/typecheck-host-rules` (the gate's second, stricter pass)\n * fails on the exact same source — e.g. `noUncheckedIndexedAccess` catching a\n * possibly-`undefined` array/record access `strict` alone allows. WARN, not\n * ERROR: this check reads a partner's local tsconfig, not the gate's own\n * verdict, so it must never block a publish that is actually fine — a\n * project whose tsconfig extends something odd, or omits `compilerOptions`\n * intentionally, still gets to publish and find out from the real gate.\n * `publish` escalates this specific check to ERROR locally (see its\n * severity-escalation list) so a submission that WOULD fail the gate never\n * gets uploaded in the first place.\n *\n * Absent tsconfig is not this check's concern — `checks/typecheck` (and its\n * host-rules sibling) already report that loudly on their own.\n */\nexport function collectHostCompilerRulesFindings(\n projectDir: string,\n): Finding[] {\n const tsconfigPath = findTsconfigPath(projectDir);\n if (!tsconfigPath) return [];\n\n const raw = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigName = path.basename(tsconfigPath);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripJsonComments(raw));\n } catch {\n // An unparseable tsconfig is a real defect, but not one THIS check can\n // reason about — `tsc` itself will refuse it far more usefully. info\n // severity: surfaced, but never blocks anything.\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'info',\n file: tsconfigPath,\n message:\n `${tsconfigName} could not be parsed as JSON (even tolerating ` +\n `comments and trailing commas), so this check could not verify it ` +\n `sets the host's compiler rules ` +\n `(${Object.keys(HOST_COMPILER_RULES).join(', ')}).`,\n hint: `Fix ${tsconfigName}'s syntax, then re-run validate.`,\n },\n ];\n }\n\n const compilerOptions =\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof (parsed as { compilerOptions?: unknown }).compilerOptions ===\n 'object' &&\n (parsed as { compilerOptions?: unknown }).compilerOptions !== null\n ? ((parsed as { compilerOptions: Record<string, unknown> })\n .compilerOptions ?? {})\n : {};\n\n const missing = Object.entries(HOST_COMPILER_RULES)\n .filter(([name, required]) => compilerOptions[name] !== required)\n .map(([name]) => name);\n\n if (missing.length === 0) return [];\n\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'warn',\n file: tsconfigPath,\n message:\n `${tsconfigName} does not set ${missing.join(' and ')} in ` +\n `compilerOptions. The gate re-checks your submission under the ` +\n `host's own stricter compiler rules — a project that typechecks ` +\n `cleanly under its own laxer tsconfig can still fail that pass.`,\n hint:\n `Add ${missing.map((name) => `\"${name}\": true`).join(', ')} to ` +\n `compilerOptions in ${tsconfigName}.`,\n },\n ];\n}\n\n/** `tsconfig.json` if present (matching what a bare `tsc --noEmit` would\n * auto-discover), else the first `tsconfig.*.json` found. */\nfunction findTsconfigPath(projectDir: string): string | undefined {\n let names: string[];\n try {\n names = fs\n .readdirSync(projectDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && TSCONFIG_PATTERN.test(entry.name))\n .map((entry) => entry.name);\n } catch {\n return undefined;\n }\n if (names.length === 0) return undefined;\n const name = names.includes('tsconfig.json')\n ? 'tsconfig.json'\n : [...names].sort()[0];\n return name ? path.join(projectDir, name) : undefined;\n}\n\n/**\n * A minimal, string-literal-aware strip of `//` and `/* *\\/` comments —\n * tsconfig.json is conventionally JSONC, but nothing in this package's\n * dependency graph parses it as such. Not a full JSONC parser: it does not\n * touch trailing commas beyond the one cleanup pass below, and it makes no\n * attempt to validate the result — `JSON.parse` does that, and a project\n * with genuinely malformed JSON falls through to this check's `info`-severity\n * catch, never a crash.\n */\nfunction stripJsonComments(source: string): string {\n let out = '';\n let inString = false;\n let quote = '';\n\n for (let i = 0; i < source.length; i++) {\n const char = source[i] ?? '';\n\n if (inString) {\n out += char;\n if (char === '\\\\') {\n out += source[++i] ?? '';\n continue;\n }\n if (char === quote) inString = false;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n inString = true;\n quote = char;\n out += char;\n continue;\n }\n\n if (char === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') i++;\n out += '\\n';\n continue;\n }\n\n if (char === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i++; // consume the closing '/'\n continue;\n }\n\n out += char;\n }\n\n // Trailing commas before a closing brace/bracket.\n return out.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n"]}
1
+ {"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { installCommand, detectPackageManager } from './package-manager';\nimport { type EkanosProject, isDocUrl } from './project';\n\n/**\n * Every vitest/vite config basename `ekanos init` might have scaffolded (or a\n * partner might have renamed to). Exported so `pack.ts` can pack exactly the\n * same set it checks here — one list, so packing and validating this project\n * property can never disagree on what \"the test config\" even is.\n */\nexport const VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/**\n * The ESLint flat-config basenames the hosted gate's `checks/capability-lint`\n * looks for in the ARCHIVE — the same four, so a project that lints locally\n * is never refused for a config `ekanos publish` left behind.\n */\nexport const ESLINT_CONFIG_NAMES = [\n 'eslint.config.mjs',\n 'eslint.config.js',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * The host's own compiler-strictness overlay a project's tsconfig should\n * already satisfy — mirrors the exact flags (`HOST_TSC_FLAGS`) the gate's\n * own SECOND `tsc --noEmit` pass checks, read from the host's real tsconfig\n * (source of truth for the values themselves). Hardcoded here for the same\n * reason it is hardcoded there: this package ships to a partner's local\n * machine, so it cannot read a file inside the host's own repo at runtime.\n * Rules-as-data via `/api/partner/toolchain` supersedes this hardcoded copy\n * later — until then, keep both copies in sync by hand.\n */\nexport const HOST_COMPILER_RULES: Record<string, boolean> = {\n strict: true,\n noUncheckedIndexedAccess: true,\n};\n\n/** Matches `tsconfig.json`, `tsconfig.build.json`, etc. — mirrors the pattern\n * `pack.ts` and the gate's `typecheck.ts` use to find a project's tsconfig. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * Advisory check on the `docs` array: a relative entry that resolves to\n * nothing on disk gets a WARNING — never an error, because a missing doc file\n * must not block a publish (the gate refuses only error-severity findings).\n * URLs are never checked: `validate` does no network, ever, and an agent\n * dereferences them at authoring time instead.\n */\nexport function collectDocsFindings(\n project: EkanosProject,\n projectDir: string,\n): Finding[] {\n const findings: Finding[] = [];\n\n for (const entry of project.docs ?? []) {\n if (isDocUrl(entry)) continue;\n\n const resolved = path.resolve(projectDir, entry);\n if (fs.existsSync(resolved)) continue;\n\n findings.push({\n check: 'docs/missing-file',\n severity: 'warn',\n file: resolved,\n message:\n `ekanos.json lists \"${entry}\" under \"docs\", but no file exists ` +\n `there (resolved to ${resolved}).`,\n hint:\n `Create ${entry}, or fix the path in ekanos.json's \"docs\" array so ` +\n 'it points at real API documentation.',\n });\n }\n\n return findings;\n}\n\n/**\n * WARN when the project's tsconfig does not already set the host's\n * compiler-strictness overlay (`HOST_COMPILER_RULES`). This exists because a\n * project can pass its own `checks/typecheck` cleanly, publish, and only THEN\n * discover `checks/typecheck-host-rules` (the gate's second, stricter pass)\n * fails on the exact same source — e.g. `noUncheckedIndexedAccess` catching a\n * possibly-`undefined` array/record access `strict` alone allows. WARN, not\n * ERROR: this check reads a partner's local tsconfig, not the gate's own\n * verdict, so it must never block a publish that is actually fine — a\n * project whose tsconfig extends something odd, or omits `compilerOptions`\n * intentionally, still gets to publish and find out from the real gate.\n * `publish` escalates this specific check to ERROR locally (see its\n * severity-escalation list) so a submission that WOULD fail the gate never\n * gets uploaded in the first place.\n *\n * Absent tsconfig is not this check's concern — `checks/typecheck` (and its\n * host-rules sibling) already report that loudly on their own.\n */\nexport function collectHostCompilerRulesFindings(\n projectDir: string,\n): Finding[] {\n const tsconfigPath = findTsconfigPath(projectDir);\n if (!tsconfigPath) return [];\n\n const raw = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigName = path.basename(tsconfigPath);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripJsonComments(raw));\n } catch {\n // An unparseable tsconfig is a real defect, but not one THIS check can\n // reason about — `tsc` itself will refuse it far more usefully. info\n // severity: surfaced, but never blocks anything.\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'info',\n file: tsconfigPath,\n message:\n `${tsconfigName} could not be parsed as JSON (even tolerating ` +\n `comments and trailing commas), so this check could not verify it ` +\n `sets the host's compiler rules ` +\n `(${Object.keys(HOST_COMPILER_RULES).join(', ')}).`,\n hint: `Fix ${tsconfigName}'s syntax, then re-run validate.`,\n },\n ];\n }\n\n const compilerOptions =\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof (parsed as { compilerOptions?: unknown }).compilerOptions ===\n 'object' &&\n (parsed as { compilerOptions?: unknown }).compilerOptions !== null\n ? ((parsed as { compilerOptions: Record<string, unknown> })\n .compilerOptions ?? {})\n : {};\n\n const missing = Object.entries(HOST_COMPILER_RULES)\n .filter(([name, required]) => compilerOptions[name] !== required)\n .map(([name]) => name);\n\n if (missing.length === 0) return [];\n\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'warn',\n file: tsconfigPath,\n message:\n `${tsconfigName} does not set ${missing.join(' and ')} in ` +\n `compilerOptions. The gate re-checks your submission under the ` +\n `host's own stricter compiler rules — a project that typechecks ` +\n `cleanly under its own laxer tsconfig can still fail that pass.`,\n hint:\n `Add ${missing.map((name) => `\"${name}\": true`).join(', ')} to ` +\n `compilerOptions in ${tsconfigName}.`,\n },\n ];\n}\n\n/** `tsconfig.json` if present (matching what a bare `tsc --noEmit` would\n * auto-discover), else the first `tsconfig.*.json` found. */\nfunction findTsconfigPath(projectDir: string): string | undefined {\n let names: string[];\n try {\n names = fs\n .readdirSync(projectDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && TSCONFIG_PATTERN.test(entry.name))\n .map((entry) => entry.name);\n } catch {\n return undefined;\n }\n if (names.length === 0) return undefined;\n const name = names.includes('tsconfig.json')\n ? 'tsconfig.json'\n : [...names].sort()[0];\n return name ? path.join(projectDir, name) : undefined;\n}\n\n/**\n * A minimal, string-literal-aware strip of `//` and `/* *\\/` comments —\n * tsconfig.json is conventionally JSONC, but nothing in this package's\n * dependency graph parses it as such. Not a full JSONC parser: it does not\n * touch trailing commas beyond the one cleanup pass below, and it makes no\n * attempt to validate the result — `JSON.parse` does that, and a project\n * with genuinely malformed JSON falls through to this check's `info`-severity\n * catch, never a crash.\n */\nfunction stripJsonComments(source: string): string {\n let out = '';\n let inString = false;\n let quote = '';\n\n for (let i = 0; i < source.length; i++) {\n const char = source[i] ?? '';\n\n if (inString) {\n out += char;\n if (char === '\\\\') {\n out += source[++i] ?? '';\n continue;\n }\n if (char === quote) inString = false;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n inString = true;\n quote = char;\n out += char;\n continue;\n }\n\n if (char === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') i++;\n out += '\\n';\n continue;\n }\n\n if (char === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i++; // consume the closing '/'\n continue;\n }\n\n out += char;\n }\n\n // Trailing commas before a closing brace/bracket.\n return out.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n\n/**\n * The hosted gate's `checks/capability-lint` runs THE PROJECT'S OWN ESLint\n * (its pinned `node_modules/.bin/eslint`, its `eslint.config.*` spreading\n * `@ekanos/sdk/eslint`) and refuses a submission without them: a control the\n * submission can leave out is not a control, so the gate does not fall back to\n * an ESLint of its own. `ekanos init` scaffolds both; a repo created before\n * that, or by hand, has neither and is refused at publish time. Say so here,\n * locally, with the exact fix, as a warning (the refusal is the gate's).\n *\n * Silent when there is no `package.json` (nothing installable to inspect).\n */\nexport function collectLintToolingFindings(projectDir: string): Finding[] {\n if (!fs.existsSync(path.join(projectDir, 'package.json'))) return [];\n\n const hasEslint = fs.existsSync(\n path.join(projectDir, 'node_modules', 'eslint', 'package.json'),\n );\n const hasConfig = ESLINT_CONFIG_NAMES.some((name) =>\n fs.existsSync(path.join(projectDir, name)),\n );\n if (hasEslint && hasConfig) return [];\n\n const missing = [\n ...(hasEslint ? [] : ['eslint is not installed']),\n ...(hasConfig ? [] : ['there is no eslint.config.mjs']),\n ];\n const add = installCommand(detectPackageManager(projectDir), [\n 'eslint',\n '@typescript-eslint/parser',\n ]);\n\n return [\n {\n check: 'project.capability-lint-tooling',\n severity: 'warn',\n message:\n `${missing.join(' and ')}, so the publish gate's capability lint ` +\n '(@ekanos/sdk/eslint) cannot run on this project and will refuse it.',\n hint:\n `Run \"${add}\" (as devDependencies), then add an eslint.config.mjs ` +\n 'that spreads the presets — \"ekanos init\" prints the scaffold, or ' +\n 'copy templates/eslint.config.mjs.tmpl from @ekanos/cli:\\n\\n' +\n \"import capabilityPreset from '@ekanos/sdk/eslint';\\n\" +\n \"import tsParser from '@typescript-eslint/parser';\\n\" +\n 'export default [\\n' +\n \" { ignores: ['dist/**', 'node_modules/**', '.ekanos/**'] },\\n\" +\n \" { files: ['src/**/*.ts', 'src/**/*.tsx'], languageOptions: { parser: tsParser, ecmaVersion: 'latest', sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } } } },\\n\" +\n ' ...capabilityPreset,\\n' +\n '];',\n },\n ];\n}\n\nconst NODE_IMPORT_PATTERN = /\\bfrom\\s+['\"]node:/;\n\n/**\n * The hosted gate installs the archive's `package.json` on its own, so\n * `@types/node` hoisted from a repo root or pulled in transitively is not\n * there. A vitest config that imports `node:*` (the scaffold's does) then\n * fails `checks/typecheck` with TS2307 unless the project declares it.\n */\nexport function collectNodeTypesFindings(projectDir: string): Finding[] {\n const manifestPath = path.join(projectDir, 'package.json');\n if (!fs.existsSync(manifestPath)) return [];\n\n let manifest: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n } catch {\n return [];\n }\n if (\n manifest.dependencies?.['@types/node'] ||\n manifest.devDependencies?.['@types/node']\n ) {\n return [];\n }\n\n const importer = [\n ...VITEST_CONFIG_NAMES,\n ...fs.readdirSync(projectDir).filter((name) => /\\.[cm]?tsx?$/.test(name)),\n ].find((name) => {\n const file = path.join(projectDir, name);\n return (\n fs.existsSync(file) &&\n fs.statSync(file).isFile() &&\n NODE_IMPORT_PATTERN.test(fs.readFileSync(file, 'utf8'))\n );\n });\n if (!importer) return [];\n\n return [\n {\n check: 'project.node-types',\n severity: 'error',\n file: path.join(projectDir, importer),\n message:\n `${importer} imports a \"node:\" module but package.json declares no ` +\n `@types/node. The publish gate installs this package on its own, so ` +\n `types hoisted from a repo root or pulled in transitively are not ` +\n `there and typecheck fails with TS2307.`,\n hint: 'Add @types/node to devDependencies (e.g. \"@types/node\": \"^22.10.0\").',\n },\n ];\n}\n"]}
package/dist/project.d.ts CHANGED
@@ -18,12 +18,26 @@ declare const IntegrationEntrySchema: z.ZodObject<{
18
18
  export type IntegrationEntry = z.infer<typeof IntegrationEntrySchema>;
19
19
  export declare const EkanosProjectSchema: z.ZodObject<{
20
20
  $schema: z.ZodOptional<z.ZodString>;
21
+ kind: z.ZodOptional<z.ZodEnum<{
22
+ experience: "experience";
23
+ integration: "integration";
24
+ }>>;
25
+ contract: z.ZodOptional<z.ZodObject<{
26
+ version: z.ZodString;
27
+ }, z.core.$strict>>;
21
28
  slug: z.ZodOptional<z.ZodString>;
22
29
  entry: z.ZodOptional<z.ZodString>;
23
30
  integrations: z.ZodOptional<z.ZodArray<z.ZodObject<{
24
31
  slug: z.ZodString;
25
32
  entry: z.ZodString;
26
33
  }, z.core.$strict>>>;
34
+ availability: z.ZodOptional<z.ZodObject<{
35
+ mode: z.ZodEnum<{
36
+ global: "global";
37
+ restricted: "restricted";
38
+ }>;
39
+ sources: z.ZodOptional<z.ZodArray<z.ZodString>>;
40
+ }, z.core.$strict>>;
27
41
  source: z.ZodOptional<z.ZodString>;
28
42
  host: z.ZodOptional<z.ZodString>;
29
43
  docs: z.ZodOptional<z.ZodArray<z.ZodString>>;
package/dist/project.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AvailabilitySchema } from '@ekanos/integration-schema';
1
2
  import * as fs from 'node:fs';
2
3
  import * as path from 'node:path';
3
4
  import { z } from 'zod';
@@ -150,9 +151,18 @@ const HostSchema = z
150
151
  export const EkanosProjectSchema = z
151
152
  .object({
152
153
  $schema: z.string().optional(),
154
+ /** `integration` (default) or `experience`; see `EkanosJsonSchema`. */
155
+ kind: z.enum(['integration', 'experience']).optional(),
156
+ /** The authored contract version of an integration (`ekanos contract`). */
157
+ contract: z
158
+ .object({ version: z.string().regex(/^\d+\.\d+\.\d+/) })
159
+ .strict()
160
+ .optional(),
153
161
  slug: SlugSchema.optional(),
154
162
  entry: EntrySchema.optional(),
155
163
  integrations: z.array(IntegrationEntrySchema).optional(),
164
+ /** Who may use the published package: every Source, or only the named ones. */
165
+ availability: AvailabilitySchema.optional(),
156
166
  /**
157
167
  * Written by the first successful `ekanos publish --source <slug>` so
158
168
  * later publishes need no flag. Read only by `publish`.
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nimport { notFoundError, preconditionError } from './errors';\n\nconst SlugSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message:\n 'slug must be kebab-case ([a-z0-9] segments separated by single hyphens), e.g. \"acme-crm\".',\n});\n\nconst EntrySchema = z\n .string()\n .min(1, { message: 'entry must be a path to the integration module.' });\n\n/**\n * Extra Tailwind content globs, for an integration that does not live under\n * the default layout. They are interpolated into the generated\n * `styles/globals.css` as `@source` lines, so they are validated as\n * DATA-IN-CSS: project-relative, no escaping the project, and none of the\n * characters that could close the string or the surrounding comment.\n *\n * Being strict here is deliberate. A glob that matches nothing costs a partner\n * a silently unstyled integration; a glob that is rejected costs them one\n * clear error.\n */\nconst SOURCE_GLOB_PATTERN = /^[A-Za-z0-9_\\-./*{},[\\]!]+$/;\n\nconst SourceGlobSchema = z\n .string()\n .min(1, { message: 'a sourceGlobs entry must not be empty.' })\n .max(200, { message: 'a sourceGlobs entry must be under 200 characters.' })\n .regex(SOURCE_GLOB_PATTERN, {\n message:\n 'a sourceGlobs entry may contain only letters, digits and _-./*{},[]! ' +\n '— it is written verbatim into the generated stylesheet.',\n })\n .refine((glob) => !glob.startsWith('/'), {\n message: 'a sourceGlobs entry must be relative to the project root.',\n })\n .refine((glob) => !glob.split('/').includes('..'), {\n message: 'a sourceGlobs entry must not escape the project root with \"..\".',\n });\n\n/** Anything with a scheme prefix — `https:`, `file:`, a Windows drive `C:`. */\nconst URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;\n\n/** True when a `docs` entry is a URL (never touched by local checks). */\nexport function isDocUrl(entry: string): boolean {\n return /^https?:\\/\\//i.test(entry);\n}\n\n/**\n * Why a `docs` entry is invalid, or null when it is fine. One rule, shared by\n * the schema below and `init --docs`, so \"valid on the command line\" and\n * \"valid in ekanos.json\" cannot drift apart: an entry is either an http(s)\n * URL or a path relative to the project root. Absolute paths are rejected\n * because they are meaningless on anyone else's machine, and non-http(s)\n * schemes because nothing here will ever dereference them.\n */\nexport function docEntryProblem(entry: string): string | null {\n if (entry.trim() === '') return 'a docs entry must not be empty.';\n\n if (URL_SCHEME_PATTERN.test(entry)) {\n if (!isDocUrl(entry)) {\n return (\n `\"${entry}\" is neither an http(s) URL nor a relative path — a docs ` +\n 'entry is a URL to API documentation or a path relative to the ' +\n 'project root.'\n );\n }\n try {\n new URL(entry);\n return null;\n } catch {\n return `\"${entry}\" is not a valid http(s) URL.`;\n }\n }\n\n if (path.isAbsolute(entry)) {\n return (\n `\"${entry}\" is an absolute path — a docs entry that is a file must be ` +\n 'relative to the project root, so it means the same thing on every ' +\n 'machine that clones the project.'\n );\n }\n\n // Same rule as SourceGlobSchema, for the same reason: a `..` segment lets a\n // relative entry escape the project root, and `validate` resolves these\n // paths for the missing-file check.\n if (entry.split(/[\\\\/]/).includes('..')) {\n return (\n `\"${entry}\" escapes the project root with \"..\" — a docs entry that is ` +\n 'a file must stay inside the project.'\n );\n }\n\n return null;\n}\n\n/**\n * One `docs` entry: where the target API's documentation lives. An http(s)\n * URL (an OpenAPI spec, the official docs site) or a project-relative path\n * (a vendored spec, `docs/api-notes.md`). Local paths are advisory-checked by\n * `validate` (`docs/missing-file`, a warning); URLs are never fetched.\n */\nconst DocEntrySchema = z.string().superRefine((entry, ctx) => {\n const problem = docEntryProblem(entry);\n if (problem) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem });\n }\n});\n\n/** One integration: the pair that addresses a definition module. */\nconst IntegrationEntrySchema = z\n .object({ slug: SlugSchema, entry: EntrySchema })\n .strict();\n\nexport type IntegrationEntry = z.infer<typeof IntegrationEntrySchema>;\n\n/**\n * `ekanos.json` — the project contract. Written by `init`, read by `validate`,\n * `dev` and `test`.\n *\n * Two shapes, because a project can hold more than one integration and the\n * single-integration form is what `init` writes and what most projects keep:\n *\n * ```json\n * { \"slug\": \"acme-crm\", \"entry\": \"src/integration.ts\" }\n *\n * { \"integrations\": [\n * { \"slug\": \"acme-crm\", \"entry\": \"src/crm.ts\" },\n * { \"slug\": \"acme-billing\", \"entry\": \"src/billing.ts\" }\n * ] }\n * ```\n *\n * A second integration used to be invisible to the CLI entirely — the harness\n * registry is an array and `collectCollisionFindings` exists precisely to\n * cross-check several definitions against each other, but `validate` could\n * only ever see one. Both forms normalise to the same list, so nothing\n * downstream has to know which was written.\n */\n/**\n * The Ekanos source this project publishes to, by slug. Ekanos source slugs\n * are opaque server-side identifiers, so the only local claims are \"one\n * token, sanely sized\" — the server resolves and authorizes it.\n */\nconst SourceSlugSchema = z\n .string()\n .min(1, { message: 'source must not be empty.' })\n .max(255, { message: 'source must be at most 255 characters.' })\n .regex(/^\\S+$/, { message: 'source must not contain whitespace.' });\n\n/**\n * The Ekanos host this project publishes to. Origin-shaped: the same\n * normalization the credential store applies (`normalizeHost`) accepts it, so\n * \"valid here\" and \"usable as a session key\" cannot drift apart.\n */\nconst HostSchema = z\n .string()\n .min(1, { message: 'host must not be empty.' })\n .max(255, { message: 'host must be at most 255 characters.' })\n .refine(\n (value) => {\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:';\n } catch {\n return false;\n }\n },\n {\n message:\n 'host must be a full http(s) origin, e.g. \"https://app.example.com\".',\n },\n );\n\nexport const EkanosProjectSchema = z\n .object({\n $schema: z.string().optional(),\n slug: SlugSchema.optional(),\n entry: EntrySchema.optional(),\n integrations: z.array(IntegrationEntrySchema).optional(),\n /**\n * Written by the first successful `ekanos publish --source <slug>` so\n * later publishes need no flag. Read only by `publish`.\n */\n source: SourceSlugSchema.optional(),\n /**\n * The Ekanos host this project publishes to — an http(s) origin, e.g.\n * \"https://app.example.com\". Written by the first successful `ekanos\n * publish` (alongside `source`), read by every host-taking verb as the\n * fallback after `--host` and `EKANOS_HOST`.\n */\n host: HostSchema.optional(),\n /**\n * Where the target API's documentation lives — http(s) URLs and\n * project-relative paths, in the order an agent should try them. Written\n * by `init --docs`, read by agents (see AGENTS.md's \"API documentation\")\n * and advisory-checked by `validate`. Optional and allowed empty: a\n * project that predates the field stays valid.\n */\n docs: z.array(DocEntrySchema).optional(),\n // There is deliberately no `harness` field. One existed, accepted by the\n // schema and read by nothing — the same accept-and-ignore that made\n // `sourceGlobs` cost a partner a silently unstyled integration. `.strict()`\n // now rejects it by name, which is the honest answer until something\n // actually consumes it.\n sourceGlobs: z.array(SourceGlobSchema).optional(),\n })\n .strict()\n .superRefine((value, ctx) => {\n const hasSingle = value.slug !== undefined || value.entry !== undefined;\n const hasList = value.integrations !== undefined;\n\n if (hasSingle && hasList) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'use either the single form ({ slug, entry }) or the list form ' +\n '({ integrations: [...] }), not both — two places to declare the ' +\n 'same thing is how they drift apart.',\n });\n return;\n }\n\n if (hasList) {\n if (value.integrations!.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations'],\n message: 'integrations must list at least one integration.',\n });\n }\n const seen = new Set<string>();\n value.integrations!.forEach((entry, index) => {\n if (seen.has(entry.slug)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations', index, 'slug'],\n message:\n `duplicate slug \"${entry.slug}\" — every integration in a ` +\n 'project needs its own, since the slug addresses it everywhere.',\n });\n }\n seen.add(entry.slug);\n });\n return;\n }\n\n if (value.slug === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slug'],\n message: 'slug is required (or use \"integrations\": [...]).',\n });\n }\n if (value.entry === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['entry'],\n message: 'entry is required (or use \"integrations\": [...]).',\n });\n }\n });\n\nexport type EkanosProject = z.infer<typeof EkanosProjectSchema>;\n\n/** Both `ekanos.json` shapes, flattened to the one the CLI works in. */\nexport function projectIntegrations(\n project: EkanosProject,\n): IntegrationEntry[] {\n if (project.integrations !== undefined) return [...project.integrations];\n return [{ slug: project.slug!, entry: project.entry! }];\n}\n\nexport const EKANOS_CONFIG_FILENAME = 'ekanos.json';\n\n/** One integration from `ekanos.json`, with its entry resolved on disk. */\nexport interface LoadedIntegration {\n slug: string;\n entry: string;\n /** Absolute path to the resolved entry module. */\n entryPath: string;\n}\n\nexport interface LoadedProject {\n project: EkanosProject;\n /** Absolute path to the directory containing ekanos.json (the project root). */\n projectDir: string;\n /** Absolute path to ekanos.json itself. */\n configPath: string;\n /** Every integration the project declares, in declaration order. */\n integrations: LoadedIntegration[];\n /**\n * The first integration. It names the generated harness shell and is what a\n * single-integration project means by \"the\" integration; nothing else\n * privileges it.\n */\n primary: LoadedIntegration;\n}\n\n/**\n * Read and validate ekanos.json from a directory. Missing file → not-found\n * (exit 6); malformed JSON or a schema violation → precondition failed\n * (exit 9), because a project the CLI cannot even read is a precondition for\n * every verb, not a definition-level validation finding.\n */\nexport function loadProject(dir: string): LoadedProject {\n const projectDir = path.resolve(dir);\n const configPath = path.join(projectDir, EKANOS_CONFIG_FILENAME);\n\n if (!fs.existsSync(configPath)) {\n throw notFoundError(\n `No ${EKANOS_CONFIG_FILENAME} found in ${projectDir}.`,\n `Run \"ekanos init\" here first, or change into the project directory ` +\n `that contains ${EKANOS_CONFIG_FILENAME}.`,\n );\n }\n\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n } catch (error) {\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Fix the JSON syntax in ${configPath}.`,\n );\n }\n\n const result = EkanosProjectSchema.safeParse(raw);\n if (!result.success) {\n const first = result.error.issues[0];\n const where =\n first && first.path.length > 0 ? first.path.join('.') : '(root)';\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is invalid at \"${where}\": ${\n first?.message ?? 'schema violation'\n }`,\n `Correct ${configPath} so it matches the ekanos.json schema ` +\n `({ slug, entry } or { integrations: [...] }, plus optional ` +\n `source, host, docs and sourceGlobs).`,\n );\n }\n\n const project = result.data;\n\n const integrations = projectIntegrations(project).map((declared) => {\n const entryPath = path.resolve(projectDir, declared.entry);\n if (!fs.existsSync(entryPath)) {\n throw notFoundError(\n `entry \"${declared.entry}\" for \"${declared.slug}\" resolves to ` +\n `${entryPath}, which does not exist.`,\n `Point it at your integration module (the file that exports the ` +\n `defineIntegration(...) result) in ${configPath}.`,\n );\n }\n return { slug: declared.slug, entry: declared.entry, entryPath };\n });\n\n assertEsmProject(projectDir);\n\n return {\n project,\n projectDir,\n configPath,\n integrations,\n primary: integrations[0]!,\n };\n}\n\n/**\n * Fail unless the project is an ES module package.\n *\n * `init` sets `\"type\": \"module\"` and says so, but a warning can be scrolled\n * past, and a partner who misses it gets a confusing module-resolution error\n * from deep inside Node or Next rather than a sentence naming the problem.\n * This is the backstop: one precondition, in the loader every verb after\n * `init` goes through, so the failure is loud and says what to do.\n *\n * `init` deliberately does NOT come through here — it is the thing that fixes\n * this state.\n *\n * A project with no package.json at all is left alone: `dev` reports missing\n * dependencies with its own better message, and `validate`/`test` have their\n * own preconditions. We only assert on a manifest that exists and disagrees.\n */\nexport function assertEsmProject(projectDir: string): void {\n const manifestPath = path.join(projectDir, 'package.json');\n if (!fs.existsSync(manifestPath)) return;\n\n let manifest: unknown;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n } catch {\n // Malformed JSON is npm's problem to report, not ours to guess at; the\n // partner will hit it on their next install with a better message.\n return;\n }\n\n const declared =\n typeof manifest === 'object' && manifest !== null\n ? (manifest as { type?: unknown }).type\n : undefined;\n\n if (declared === 'module') return;\n\n throw preconditionError(\n declared === undefined\n ? `${manifestPath} does not declare \"type\": \"module\", so Node treats ` +\n 'your integration as CommonJS and cannot load it.'\n : `${manifestPath} declares \"type\": ${JSON.stringify(declared)}, but the ` +\n 'integration sources are ES modules and Node cannot load them.',\n `Set \"type\": \"module\" in ${manifestPath}. If this project has CommonJS ` +\n 'sources of its own, put your integration in its own package instead — ' +\n 'the two module systems cannot share one package.json. Re-running ' +\n '\"ekanos init\" also fixes this.',\n );\n}\n\n/** Serialize an ekanos.json body with a trailing newline. */\nexport function serializeProject(project: EkanosProject): string {\n return `${JSON.stringify(project, null, 2)}\\n`;\n}\n"]}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE5D,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,4BAA4B,EAAE;IAChE,OAAO,EACL,2FAA2F;CAC9F,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,CAAC;KAClB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAAC,CAAC;AAE1E;;;;;;;;;;GAUG;AACH,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;AAE1D,MAAM,gBAAgB,GAAG,CAAC;KACvB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC;KAC7D,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC;KAC1E,KAAK,CAAC,mBAAmB,EAAE;IAC1B,OAAO,EACL,uEAAuE;QACvE,yDAAyD;CAC5D,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;IACvC,OAAO,EAAE,2DAA2D;CACrE,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;IACjD,OAAO,EAAE,iEAAiE;CAC3E,CAAC,CAAC;AAEL,+EAA+E;AAC/E,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAElD,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,iCAAiC,CAAC;IAElE,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,CACL,IAAI,KAAK,2DAA2D;gBACpE,gEAAgE;gBAChE,eAAe,CAChB,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;mBAAO,CAAC;YACP,OAAO,IAAI,KAAK,+BAA+B,CAAC;QAClD,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CACL,IAAI,KAAK,8DAA8D;YACvE,oEAAoE;YACpE,kCAAkC,CACnC,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,oCAAoC;IACpC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,OAAO,CACL,IAAI,KAAK,8DAA8D;YACvE,sCAAsC,CACvC,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC3D,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,OAAO,EAAE,CAAC;QACZ,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,sBAAsB,GAAG,CAAC;KAC7B,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;KAChD,MAAM,EAAE,CAAC;AAIZ;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,CAAC;KACvB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;KAChD,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC;KAC/D,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC;KACjB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;KAC9C,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC;KAC7D,MAAM,CACL,CAAC,KAAK,EAAE,EAAE;IACR,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/D,CAAC;eAAO,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,EACD;IACE,OAAO,EACL,qEAAqE;CACxE,CACF,CAAC;AAEJ,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC;KACjC,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,uEAAuE;IACvE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE;IACtD,2EAA2E;IAC3E,QAAQ,EAAE,CAAC;SACR,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;SACvD,MAAM,EAAE;SACR,QAAQ,EAAE;IACb,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE;IAC7B,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;IACxD,+EAA+E;IAC/E,YAAY,EAAE,kBAAkB,CAAC,QAAQ,EAAE;IAC3C;;;OAGG;IACH,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IACnC;;;;;OAKG;IACH,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE;IAC3B;;;;;;OAMG;IACH,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;IACxC,yEAAyE;IACzE,oEAAoE;IACpE,4EAA4E;IAC5E,qEAAqE;IACrE,wBAAwB;IACxB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE;CAClD,CAAC;KACD,MAAM,EAAE;KACR,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC;IACxE,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC;IAEjD,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;QACzB,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,OAAO,EACL,gEAAgE;gBAChE,kEAAkE;gBAClE,qCAAqC;SACxC,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,KAAK,CAAC,YAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;gBAC3B,IAAI,EAAE,CAAC,cAAc,CAAC;gBACtB,OAAO,EAAE,kDAAkD;aAC5D,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,CAAC,YAAa,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,GAAG,CAAC,QAAQ,CAAC;oBACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;oBAC3B,IAAI,EAAE,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC;oBACrC,OAAO,EACL,mBAAmB,KAAK,CAAC,IAAI,6BAA6B;wBAC1D,gEAAgE;iBACnE,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,IAAI,EAAE,CAAC,MAAM,CAAC;YACd,OAAO,EAAE,kDAAkD;SAC5D,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,IAAI,EAAE,CAAC,OAAO,CAAC;YACf,OAAO,EAAE,mDAAmD;SAC7D,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAIL,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CACjC,OAAsB;IAEtB,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACzE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAM,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,aAAa,CAAC;AA0BpD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;;IACrC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,sBAAsB,CAAC,CAAC;IAEjE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,aAAa,CACjB,MAAM,sBAAsB,aAAa,UAAU,GAAG,EACtD,qEAAqE;YACnE,iBAAiB,sBAAsB,GAAG,CAC7C,CAAC;IACJ,CAAC;IAED,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,iBAAiB,CACrB,GAAG,sBAAsB,uBACvB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF,0BAA0B,UAAU,GAAG,CACxC,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,KAAK,GACT,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACnE,MAAM,iBAAiB,CACrB,GAAG,sBAAsB,mBAAmB,KAAK,MAC/C,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,OAAO,mCAAI,kBACpB,EAAE,EACF,WAAW,UAAU,wCAAwC;YAC3D,6DAA6D;YAC7D,sCAAsC,CACzC,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;IAE5B,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QACjE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC3D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,MAAM,aAAa,CACjB,UAAU,QAAQ,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAI,gBAAgB;gBAC7D,GAAG,SAAS,yBAAyB,EACvC,iEAAiE;gBAC/D,qCAAqC,UAAU,GAAG,CACrD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE7B,OAAO;QACL,OAAO;QACP,UAAU;QACV,UAAU;QACV,YAAY;QACZ,OAAO,EAAE,YAAY,CAAC,CAAC,CAAE;KAC1B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IAC3D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IAEzC,IAAI,QAAiB,CAAC;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;eAAO,CAAC;QACP,uEAAuE;QACvE,mEAAmE;QACnE,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GACZ,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI;QAC/C,CAAC,CAAE,QAA+B,CAAC,IAAI;QACvC,CAAC,CAAC,SAAS,CAAC;IAEhB,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO;IAElC,MAAM,iBAAiB,CACrB,QAAQ,KAAK,SAAS;QACpB,CAAC,CAAC,GAAG,YAAY,qDAAqD;YAClE,kDAAkD;QACtD,CAAC,CAAC,GAAG,YAAY,qBAAqB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY;YACtE,+DAA+D,EACrE,2BAA2B,YAAY,iCAAiC;QACtE,wEAAwE;QACxE,mEAAmE;QACnE,gCAAgC,CACnC,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC","sourcesContent":["import { AvailabilitySchema } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nimport { notFoundError, preconditionError } from './errors';\n\nconst SlugSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message:\n 'slug must be kebab-case ([a-z0-9] segments separated by single hyphens), e.g. \"acme-crm\".',\n});\n\nconst EntrySchema = z\n .string()\n .min(1, { message: 'entry must be a path to the integration module.' });\n\n/**\n * Extra Tailwind content globs, for an integration that does not live under\n * the default layout. They are interpolated into the generated\n * `styles/globals.css` as `@source` lines, so they are validated as\n * DATA-IN-CSS: project-relative, no escaping the project, and none of the\n * characters that could close the string or the surrounding comment.\n *\n * Being strict here is deliberate. A glob that matches nothing costs a partner\n * a silently unstyled integration; a glob that is rejected costs them one\n * clear error.\n */\nconst SOURCE_GLOB_PATTERN = /^[A-Za-z0-9_\\-./*{},[\\]!]+$/;\n\nconst SourceGlobSchema = z\n .string()\n .min(1, { message: 'a sourceGlobs entry must not be empty.' })\n .max(200, { message: 'a sourceGlobs entry must be under 200 characters.' })\n .regex(SOURCE_GLOB_PATTERN, {\n message:\n 'a sourceGlobs entry may contain only letters, digits and _-./*{},[]! ' +\n '— it is written verbatim into the generated stylesheet.',\n })\n .refine((glob) => !glob.startsWith('/'), {\n message: 'a sourceGlobs entry must be relative to the project root.',\n })\n .refine((glob) => !glob.split('/').includes('..'), {\n message: 'a sourceGlobs entry must not escape the project root with \"..\".',\n });\n\n/** Anything with a scheme prefix — `https:`, `file:`, a Windows drive `C:`. */\nconst URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;\n\n/** True when a `docs` entry is a URL (never touched by local checks). */\nexport function isDocUrl(entry: string): boolean {\n return /^https?:\\/\\//i.test(entry);\n}\n\n/**\n * Why a `docs` entry is invalid, or null when it is fine. One rule, shared by\n * the schema below and `init --docs`, so \"valid on the command line\" and\n * \"valid in ekanos.json\" cannot drift apart: an entry is either an http(s)\n * URL or a path relative to the project root. Absolute paths are rejected\n * because they are meaningless on anyone else's machine, and non-http(s)\n * schemes because nothing here will ever dereference them.\n */\nexport function docEntryProblem(entry: string): string | null {\n if (entry.trim() === '') return 'a docs entry must not be empty.';\n\n if (URL_SCHEME_PATTERN.test(entry)) {\n if (!isDocUrl(entry)) {\n return (\n `\"${entry}\" is neither an http(s) URL nor a relative path — a docs ` +\n 'entry is a URL to API documentation or a path relative to the ' +\n 'project root.'\n );\n }\n try {\n new URL(entry);\n return null;\n } catch {\n return `\"${entry}\" is not a valid http(s) URL.`;\n }\n }\n\n if (path.isAbsolute(entry)) {\n return (\n `\"${entry}\" is an absolute path — a docs entry that is a file must be ` +\n 'relative to the project root, so it means the same thing on every ' +\n 'machine that clones the project.'\n );\n }\n\n // Same rule as SourceGlobSchema, for the same reason: a `..` segment lets a\n // relative entry escape the project root, and `validate` resolves these\n // paths for the missing-file check.\n if (entry.split(/[\\\\/]/).includes('..')) {\n return (\n `\"${entry}\" escapes the project root with \"..\" — a docs entry that is ` +\n 'a file must stay inside the project.'\n );\n }\n\n return null;\n}\n\n/**\n * One `docs` entry: where the target API's documentation lives. An http(s)\n * URL (an OpenAPI spec, the official docs site) or a project-relative path\n * (a vendored spec, `docs/api-notes.md`). Local paths are advisory-checked by\n * `validate` (`docs/missing-file`, a warning); URLs are never fetched.\n */\nconst DocEntrySchema = z.string().superRefine((entry, ctx) => {\n const problem = docEntryProblem(entry);\n if (problem) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem });\n }\n});\n\n/** One integration: the pair that addresses a definition module. */\nconst IntegrationEntrySchema = z\n .object({ slug: SlugSchema, entry: EntrySchema })\n .strict();\n\nexport type IntegrationEntry = z.infer<typeof IntegrationEntrySchema>;\n\n/**\n * `ekanos.json` — the project contract. Written by `init`, read by `validate`,\n * `dev` and `test`.\n *\n * Two shapes, because a project can hold more than one integration and the\n * single-integration form is what `init` writes and what most projects keep:\n *\n * ```json\n * { \"slug\": \"acme-crm\", \"entry\": \"src/integration.ts\" }\n *\n * { \"integrations\": [\n * { \"slug\": \"acme-crm\", \"entry\": \"src/crm.ts\" },\n * { \"slug\": \"acme-billing\", \"entry\": \"src/billing.ts\" }\n * ] }\n * ```\n *\n * A second integration used to be invisible to the CLI entirely — the harness\n * registry is an array and `collectCollisionFindings` exists precisely to\n * cross-check several definitions against each other, but `validate` could\n * only ever see one. Both forms normalise to the same list, so nothing\n * downstream has to know which was written.\n */\n/**\n * The Ekanos source this project publishes to, by slug. Ekanos source slugs\n * are opaque server-side identifiers, so the only local claims are \"one\n * token, sanely sized\" — the server resolves and authorizes it.\n */\nconst SourceSlugSchema = z\n .string()\n .min(1, { message: 'source must not be empty.' })\n .max(255, { message: 'source must be at most 255 characters.' })\n .regex(/^\\S+$/, { message: 'source must not contain whitespace.' });\n\n/**\n * The Ekanos host this project publishes to. Origin-shaped: the same\n * normalization the credential store applies (`normalizeHost`) accepts it, so\n * \"valid here\" and \"usable as a session key\" cannot drift apart.\n */\nconst HostSchema = z\n .string()\n .min(1, { message: 'host must not be empty.' })\n .max(255, { message: 'host must be at most 255 characters.' })\n .refine(\n (value) => {\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:';\n } catch {\n return false;\n }\n },\n {\n message:\n 'host must be a full http(s) origin, e.g. \"https://app.example.com\".',\n },\n );\n\nexport const EkanosProjectSchema = z\n .object({\n $schema: z.string().optional(),\n /** `integration` (default) or `experience`; see `EkanosJsonSchema`. */\n kind: z.enum(['integration', 'experience']).optional(),\n /** The authored contract version of an integration (`ekanos contract`). */\n contract: z\n .object({ version: z.string().regex(/^\\d+\\.\\d+\\.\\d+/) })\n .strict()\n .optional(),\n slug: SlugSchema.optional(),\n entry: EntrySchema.optional(),\n integrations: z.array(IntegrationEntrySchema).optional(),\n /** Who may use the published package: every Source, or only the named ones. */\n availability: AvailabilitySchema.optional(),\n /**\n * Written by the first successful `ekanos publish --source <slug>` so\n * later publishes need no flag. Read only by `publish`.\n */\n source: SourceSlugSchema.optional(),\n /**\n * The Ekanos host this project publishes to — an http(s) origin, e.g.\n * \"https://app.example.com\". Written by the first successful `ekanos\n * publish` (alongside `source`), read by every host-taking verb as the\n * fallback after `--host` and `EKANOS_HOST`.\n */\n host: HostSchema.optional(),\n /**\n * Where the target API's documentation lives — http(s) URLs and\n * project-relative paths, in the order an agent should try them. Written\n * by `init --docs`, read by agents (see AGENTS.md's \"API documentation\")\n * and advisory-checked by `validate`. Optional and allowed empty: a\n * project that predates the field stays valid.\n */\n docs: z.array(DocEntrySchema).optional(),\n // There is deliberately no `harness` field. One existed, accepted by the\n // schema and read by nothing — the same accept-and-ignore that made\n // `sourceGlobs` cost a partner a silently unstyled integration. `.strict()`\n // now rejects it by name, which is the honest answer until something\n // actually consumes it.\n sourceGlobs: z.array(SourceGlobSchema).optional(),\n })\n .strict()\n .superRefine((value, ctx) => {\n const hasSingle = value.slug !== undefined || value.entry !== undefined;\n const hasList = value.integrations !== undefined;\n\n if (hasSingle && hasList) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'use either the single form ({ slug, entry }) or the list form ' +\n '({ integrations: [...] }), not both — two places to declare the ' +\n 'same thing is how they drift apart.',\n });\n return;\n }\n\n if (hasList) {\n if (value.integrations!.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations'],\n message: 'integrations must list at least one integration.',\n });\n }\n const seen = new Set<string>();\n value.integrations!.forEach((entry, index) => {\n if (seen.has(entry.slug)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations', index, 'slug'],\n message:\n `duplicate slug \"${entry.slug}\" — every integration in a ` +\n 'project needs its own, since the slug addresses it everywhere.',\n });\n }\n seen.add(entry.slug);\n });\n return;\n }\n\n if (value.slug === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slug'],\n message: 'slug is required (or use \"integrations\": [...]).',\n });\n }\n if (value.entry === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['entry'],\n message: 'entry is required (or use \"integrations\": [...]).',\n });\n }\n });\n\nexport type EkanosProject = z.infer<typeof EkanosProjectSchema>;\n\n/** Both `ekanos.json` shapes, flattened to the one the CLI works in. */\nexport function projectIntegrations(\n project: EkanosProject,\n): IntegrationEntry[] {\n if (project.integrations !== undefined) return [...project.integrations];\n return [{ slug: project.slug!, entry: project.entry! }];\n}\n\nexport const EKANOS_CONFIG_FILENAME = 'ekanos.json';\n\n/** One integration from `ekanos.json`, with its entry resolved on disk. */\nexport interface LoadedIntegration {\n slug: string;\n entry: string;\n /** Absolute path to the resolved entry module. */\n entryPath: string;\n}\n\nexport interface LoadedProject {\n project: EkanosProject;\n /** Absolute path to the directory containing ekanos.json (the project root). */\n projectDir: string;\n /** Absolute path to ekanos.json itself. */\n configPath: string;\n /** Every integration the project declares, in declaration order. */\n integrations: LoadedIntegration[];\n /**\n * The first integration. It names the generated harness shell and is what a\n * single-integration project means by \"the\" integration; nothing else\n * privileges it.\n */\n primary: LoadedIntegration;\n}\n\n/**\n * Read and validate ekanos.json from a directory. Missing file → not-found\n * (exit 6); malformed JSON or a schema violation → precondition failed\n * (exit 9), because a project the CLI cannot even read is a precondition for\n * every verb, not a definition-level validation finding.\n */\nexport function loadProject(dir: string): LoadedProject {\n const projectDir = path.resolve(dir);\n const configPath = path.join(projectDir, EKANOS_CONFIG_FILENAME);\n\n if (!fs.existsSync(configPath)) {\n throw notFoundError(\n `No ${EKANOS_CONFIG_FILENAME} found in ${projectDir}.`,\n `Run \"ekanos init\" here first, or change into the project directory ` +\n `that contains ${EKANOS_CONFIG_FILENAME}.`,\n );\n }\n\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n } catch (error) {\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Fix the JSON syntax in ${configPath}.`,\n );\n }\n\n const result = EkanosProjectSchema.safeParse(raw);\n if (!result.success) {\n const first = result.error.issues[0];\n const where =\n first && first.path.length > 0 ? first.path.join('.') : '(root)';\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is invalid at \"${where}\": ${\n first?.message ?? 'schema violation'\n }`,\n `Correct ${configPath} so it matches the ekanos.json schema ` +\n `({ slug, entry } or { integrations: [...] }, plus optional ` +\n `source, host, docs and sourceGlobs).`,\n );\n }\n\n const project = result.data;\n\n const integrations = projectIntegrations(project).map((declared) => {\n const entryPath = path.resolve(projectDir, declared.entry);\n if (!fs.existsSync(entryPath)) {\n throw notFoundError(\n `entry \"${declared.entry}\" for \"${declared.slug}\" resolves to ` +\n `${entryPath}, which does not exist.`,\n `Point it at your integration module (the file that exports the ` +\n `defineIntegration(...) result) in ${configPath}.`,\n );\n }\n return { slug: declared.slug, entry: declared.entry, entryPath };\n });\n\n assertEsmProject(projectDir);\n\n return {\n project,\n projectDir,\n configPath,\n integrations,\n primary: integrations[0]!,\n };\n}\n\n/**\n * Fail unless the project is an ES module package.\n *\n * `init` sets `\"type\": \"module\"` and says so, but a warning can be scrolled\n * past, and a partner who misses it gets a confusing module-resolution error\n * from deep inside Node or Next rather than a sentence naming the problem.\n * This is the backstop: one precondition, in the loader every verb after\n * `init` goes through, so the failure is loud and says what to do.\n *\n * `init` deliberately does NOT come through here — it is the thing that fixes\n * this state.\n *\n * A project with no package.json at all is left alone: `dev` reports missing\n * dependencies with its own better message, and `validate`/`test` have their\n * own preconditions. We only assert on a manifest that exists and disagrees.\n */\nexport function assertEsmProject(projectDir: string): void {\n const manifestPath = path.join(projectDir, 'package.json');\n if (!fs.existsSync(manifestPath)) return;\n\n let manifest: unknown;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n } catch {\n // Malformed JSON is npm's problem to report, not ours to guess at; the\n // partner will hit it on their next install with a better message.\n return;\n }\n\n const declared =\n typeof manifest === 'object' && manifest !== null\n ? (manifest as { type?: unknown }).type\n : undefined;\n\n if (declared === 'module') return;\n\n throw preconditionError(\n declared === undefined\n ? `${manifestPath} does not declare \"type\": \"module\", so Node treats ` +\n 'your integration as CommonJS and cannot load it.'\n : `${manifestPath} declares \"type\": ${JSON.stringify(declared)}, but the ` +\n 'integration sources are ES modules and Node cannot load them.',\n `Set \"type\": \"module\" in ${manifestPath}. If this project has CommonJS ` +\n 'sources of its own, put your integration in its own package instead — ' +\n 'the two module systems cannot share one package.json. Re-running ' +\n '\"ekanos init\" also fixes this.',\n );\n}\n\n/** Serialize an ekanos.json body with a trailing newline. */\nexport function serializeProject(project: EkanosProject): string {\n return `${JSON.stringify(project, null, 2)}\\n`;\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { Finding } from '@ekanos/integration-schema';
2
+ import { type PublishArgs } from '../commands/publish.js';
3
+ import type { CliContext } from '../context.js';
4
+ import type { ExitCode } from '../exit-codes.js';
5
+ export declare const PUBLISH_ARTIFACT_DIR: string;
6
+ export interface RepoPublishArgs {
7
+ repoRoot: string;
8
+ /** Where the command was run: inside a package it narrows the selection. */
9
+ cwd: string;
10
+ /** Package names given on the command line. */
11
+ names: readonly string[];
12
+ all: boolean;
13
+ /** The single-project publish arguments, reused for every integration. */
14
+ publish: PublishArgs;
15
+ }
16
+ export type PackageStatus = 'ready' | 'built' | 'gate failed' | 'refused' | 'submitted' | 'failed' | 'not attempted' | 'not supported by the host';
17
+ export interface PackageRow {
18
+ name: string;
19
+ kind: 'integration' | 'experience';
20
+ version: string;
21
+ availability: string;
22
+ /** Artifact directory relative to the repo root, when one was built. */
23
+ artifact: string | null;
24
+ status: PackageStatus;
25
+ /** Why the status is not a success, one line. */
26
+ detail?: string;
27
+ findings?: Finding[];
28
+ /** Integration submissions: what the host answered. */
29
+ receipt?: Record<string, unknown>;
30
+ }
31
+ /**
32
+ * `ekanos publish [name...] | --all [--dry-run]` in a repo.
33
+ *
34
+ * The plan is the selected packages, integrations before the experiences that
35
+ * use them (a same-repo integration is not an override, so it is ordered
36
+ * first, never refused). Every package passes its own gate before anything is
37
+ * sent. `--dry-run` also builds each artifact under `.ekanos/publish/` and
38
+ * touches no network. A real run submits the integrations in order through the
39
+ * existing path; the host cannot admit an experience yet, so selected
40
+ * experiences end the run with that refusal.
41
+ */
42
+ export declare function runRepoPublish(ctx: CliContext, args: RepoPublishArgs): Promise<ExitCode>;