@ekanos/cli 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/auth/credential-store.d.ts +1 -1
- package/dist/auth/credential-store.js +3 -2
- package/dist/auth/credential-store.js.map +1 -1
- package/dist/auth/device-flow.d.ts +1 -1
- package/dist/auth/device-flow.js +11 -10
- package/dist/auth/device-flow.js.map +1 -1
- package/dist/auth/{fusion-api.d.ts → ekanos-api.d.ts} +9 -9
- package/dist/auth/{fusion-api.js → ekanos-api.js} +28 -28
- package/dist/auth/ekanos-api.js.map +1 -0
- package/dist/auth/session.d.ts +5 -5
- package/dist/auth/session.js +8 -7
- package/dist/auth/session.js.map +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/commands/dev.d.ts +2 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.js +4 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/publish.d.ts +2 -2
- package/dist/commands/publish.js +3 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.js +5 -7
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/use.js +1 -1
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/whoami.d.ts +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/compatibility.js.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/delegate.js.map +1 -1
- package/dist/did-you-mean.js +26 -15
- package/dist/did-you-mean.js.map +1 -1
- package/dist/exit-codes.d.ts +10 -10
- package/dist/findings-renderer.js +6 -4
- package/dist/findings-renderer.js.map +1 -1
- package/dist/generated/fa-free-names.d.ts +1 -1
- package/dist/generated/fa-free-names.js +1 -1
- package/dist/generated/fa-free-names.js.map +1 -1
- package/dist/harness-scaffold.d.ts +1 -1
- package/dist/harness-scaffold.js +4 -2
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/icon-findings.d.ts +1 -1
- package/dist/icon-findings.js +2 -2
- package/dist/icon-findings.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/merge-manifest.js.map +1 -1
- package/dist/pack.js +1 -1
- package/dist/pack.js.map +1 -1
- package/dist/project-checks.js.map +1 -1
- package/dist/project.d.ts +7 -84
- package/dist/project.js +3 -3
- package/dist/project.js.map +1 -1
- package/dist/publish-api.d.ts +5 -5
- package/dist/publish-api.js +27 -33
- package/dist/publish-api.js.map +1 -1
- package/dist/safe-fs.js.map +1 -1
- package/dist/schema-skew.js.map +1 -1
- package/dist/source-locator.js.map +1 -1
- package/dist/sources-api.d.ts +3 -3
- package/dist/sources-api.js +11 -14
- package/dist/sources-api.js.map +1 -1
- package/dist/templates.js.map +1 -1
- package/dist/test-report.js.map +1 -1
- package/dist/toolchain-api.d.ts +4 -4
- package/dist/toolchain-api.js +1 -1
- package/dist/toolchain-api.js.map +1 -1
- package/dist/toolchain-resolve.js.map +1 -1
- package/dist/toolchain.d.ts +5 -5
- package/dist/ui.js +2 -8
- package/dist/ui.js.map +1 -1
- package/dist/update-notice.d.ts +1 -1
- package/dist/update-notice.js.map +1 -1
- package/dist/validate-findings.js.map +1 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +15 -20
- package/templates/AGENTS.md.tmpl +13 -8
- package/templates/CLAUDE.md.tmpl +1 -1
- package/templates/claude-skill.md.tmpl +2 -2
- package/templates/harness/next.config.mjs.tmpl +1 -1
- package/templates/package.json.tmpl +6 -5
- package/templates/pnpm-workspace.yaml.tmpl +8 -0
- package/templates/skills/build-integration/SKILL.md +3 -3
- package/templates/skills/build-integration/references/api-wiring.md +2 -2
- package/templates/skills/build-integration/references/widget-design.md +1 -1
- package/dist/auth/fusion-api.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-scaffold.js","sourceRoot":"","sources":["../src/harness-scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { cliVersion, templatesFingerprint } from './compatibility';\nimport { invalidStateError, preconditionError } from './errors';\nimport {\n appendFileNoFollow,\n classifyPath,\n readFileNoFollow,\n writeFileNoFollow,\n} from './safe-fs';\nimport { readTemplate } from './templates';\n\n/** The dependency that owns every line of harness behaviour. */\nexport const HARNESS_PACKAGE = '@ekanos/harness';\n\n/** Where the generated shell lives, relative to the project root. */\nexport const HARNESS_DIR = path.join('.ekanos', 'harness');\n\n/** The single `.gitignore` line that covers the whole generated tree. */\nexport const GITIGNORE_ENTRY = '.ekanos/';\n\n/**\n * The staleness marker, inside the generated tree so deleting the tree also\n * deletes the marker. Holds the `@ekanos/harness` version that generated the\n * shell — a version bump means the shell may be wired against an older export\n * surface, so it is regenerated.\n */\nexport const VERSION_MARKER = '.ekanos-version';\n\n/** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */\nexport const HARNESS_CONFIG_FILENAME = 'harness.config.ts';\n\n/**\n * Every file in the generated shell: bundled template → path inside\n * `.ekanos/harness/`. The ten route files are mechanically derivable from the\n * export map alone — which is the test of whether the package boundary is in\n * the right place. If a shell file ever needs a judgment call, the boundary\n * has moved, not the template.\n *\n * `app/api/integrations/[slug]/[...path]/route.ts` is the shell's one SERVER\n * route: it injects the registry into `@ekanos/harness/api`, which serves the\n * live-mode OAuth loop, storage bridge and trigger route over a server-held\n * context. It is the third registry injection point, beside the two pages.\n *\n * `app/harness-shell.tsx` exists because a `HarnessIntegration` holds React\n * component references and MCP `run` functions. Next cannot serialize those\n * across a server→client boundary, so the `'use client'` mark has to sit ABOVE\n * the point where the registry is injected — hence a client shell of its own\n * rather than a client `RootLayout` called from a server layout. Keeping\n * `app/page.tsx` a server component is what lets `/` prerender static.\n */\nexport const SHELL_FILES: readonly { template: string; dest: string }[] = [\n { template: 'harness/app/layout.tsx.tmpl', dest: 'app/layout.tsx' },\n {\n template: 'harness/app/harness-shell.tsx.tmpl',\n dest: 'app/harness-shell.tsx',\n },\n { template: 'harness/app/page.tsx.tmpl', dest: 'app/page.tsx' },\n {\n template: 'harness/app/[slug]/layout.tsx.tmpl',\n dest: 'app/[slug]/layout.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/page.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/[widgetId]/page.tsx',\n },\n {\n template: 'harness/app/[slug]/tile/page.tsx.tmpl',\n dest: 'app/[slug]/tile/page.tsx',\n },\n {\n template: 'harness/app/[slug]/activation/page.tsx.tmpl',\n dest: 'app/[slug]/activation/page.tsx',\n },\n {\n template: 'harness/app/[slug]/triggers/page.tsx.tmpl',\n dest: 'app/[slug]/triggers/page.tsx',\n },\n {\n template: 'harness/app/api/integrations/[slug]/[...path]/route.ts.tmpl',\n dest: 'app/api/integrations/[slug]/[...path]/route.ts',\n },\n { template: 'harness/next.config.mjs.tmpl', dest: 'next.config.mjs' },\n { template: 'harness/postcss.config.mjs.tmpl', dest: 'postcss.config.mjs' },\n { template: 'harness/tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'harness/styles/globals.css.tmpl', dest: 'styles/globals.css' },\n { template: 'harness/package.json.tmpl', dest: 'package.json' },\n {\n template: 'harness/types/team-accounts.d.ts.tmpl',\n dest: 'types/team-accounts.d.ts',\n },\n];\n\n/**\n * Packages the shell needs to resolve upward out of the partner's own\n * `node_modules`. The shell declares no dependencies of its own by design, so\n * a missing one shows up as an opaque Next resolution error at dev-server\n * start; checking here turns that into one precondition with one fix.\n */\nexport const REQUIRED_DEPENDENCIES = [\n HARNESS_PACKAGE,\n 'next',\n 'react',\n 'react-dom',\n 'tailwindcss',\n '@tailwindcss/postcss',\n] as const;\n\nexport interface WrittenFiles {\n created: string[];\n updated: string[];\n unchanged: string[];\n}\n\n/**\n * Read an installed package's `package.json` out of a project's TOP-LEVEL\n * `node_modules`. Resolved by direct path join rather than `require.resolve`\n * on purpose, for two independent reasons:\n *\n * - The CLI runs from its own install and must read the version of the copy\n * in THE CALLER'S project, not one hoisted next to itself.\n * - Several of the packages this reads (`@ekanos/integration-schema`\n * included) declare an `exports` map with no `./package.json` subpath, so\n * `require.resolve('<pkg>/package.json')` throws\n * `ERR_PACKAGE_PATH_NOT_EXPORTED` under Node's ESM resolver. A plain file\n * read is not subject to the export map at all.\n *\n * Shared by every \"what does this project have installed?\" check in the\n * CLI — `readInstalledHarness` below, the schema-skew gate, and `upgrade`'s\n * current-toolchain report — so there is exactly one implementation of \"read\n * a package's on-disk version\", not a subtly different one per call site.\n */\nexport interface InstalledPackage {\n version: string;\n /** The raw package.json, for callers that need more than the version. */\n manifest: unknown;\n}\n\nexport function readInstalledPackageManifest(\n projectDir: string,\n packageName: string,\n): InstalledPackage | null {\n const manifestPath = path.join(\n projectDir,\n 'node_modules',\n ...packageName.split('/'),\n 'package.json',\n );\n if (!fs.existsSync(manifestPath)) return null;\n\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n if (typeof version !== 'string' || version.length === 0) return null;\n return { version, manifest: parsed };\n } catch (error) {\n throw preconditionError(\n `${packageName}'s package.json in node_modules is unreadable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Reinstall dependencies in ${projectDir} so ${packageName} resolves.`,\n );\n }\n}\n\n/** Read the installed `@ekanos/harness` version out of the partner's project. */\nexport type InstalledHarness = InstalledPackage;\n\nexport function readInstalledHarness(\n projectDir: string,\n): InstalledHarness | null {\n return readInstalledPackageManifest(projectDir, HARNESS_PACKAGE);\n}\n\n/** Just the installed version, for callers that need nothing else. */\nexport function readInstalledHarnessVersion(projectDir: string): string | null {\n return readInstalledHarness(projectDir)?.version ?? null;\n}\n\n/** Which of `REQUIRED_DEPENDENCIES` are not installed in the project root. */\nexport function missingDependencies(projectDir: string): string[] {\n return REQUIRED_DEPENDENCIES.filter(\n (name) =>\n !fs.existsSync(\n path.join(projectDir, 'node_modules', name, 'package.json'),\n ),\n );\n}\n\n/**\n * What generated an existing shell.\n *\n * All three fields matter, and recording only `harness` was a real hole: the\n * templates ship with the CLI, so a shell can be stale because the CLI changed\n * while the harness did not. `templates` is the authoritative one — it is a\n * hash of the template sources, so it moves whenever what we generate moves,\n * including for a CLI build that never shipped a version bump.\n */\nexport interface ShellMarker {\n harness: string;\n /** null for a legacy bare-version marker written before this was recorded. */\n cli: string | null;\n templates: string | null;\n}\n\n/**\n * Three outcomes, not two, because \"no marker\" and \"unreadable marker\" call\n * for opposite responses.\n *\n * A marker's PRESENCE is itself evidence the tree is ours — a squatter does\n * not write `.ekanos-version` — and the likeliest cause of an unreadable one\n * is our own interrupted write. Everything under `.ekanos/harness/` is\n * CLI-owned, gitignored and reproducible from the templates, so regenerating\n * destroys nothing: there is no value at risk to justify charging a partner a\n * confusing `--force` to recover from our own bad write.\n *\n * An ABSENT marker over a non-empty tree is the opposite: nothing says the\n * files are ours, so we refuse. That is the L1 rule and it stays.\n */\nexport type MarkerState =\n | { kind: 'missing' }\n | { kind: 'corrupt' }\n | { kind: 'ok'; marker: ShellMarker };\n\nexport function readShellMarkerState(harnessDir: string): MarkerState {\n const marker = path.join(harnessDir, VERSION_MARKER);\n if (classifyPath(marker) !== 'file') return { kind: 'missing' };\n\n const raw = (readFileNoFollow(harnessDir, marker) ?? '').trim();\n if (raw.length === 0) return { kind: 'corrupt' };\n\n const parsed = parseMarker(raw);\n return parsed === null ? { kind: 'corrupt' } : { kind: 'ok', marker: parsed };\n}\n\nfunction parseMarker(raw: string): ShellMarker | null {\n // v1 markers were a bare version string. Read them rather than crashing, and\n // report the unknown fields as null so the shell counts as stale and is\n // regenerated once — which is the correct outcome for an upgrade.\n //\n // It must actually LOOK like a version: without this, any garbage that does\n // not happen to start with `{` — a truncated write, a stray word — reads as\n // a legacy marker named after the garbage, and the corrupt-marker path never\n // fires. That is how a truncated `.ekanos-version` was silently reported as\n // \"@ekanos/harness truncat → 0.1.0\" instead of \"unreadable\".\n if (!raw.startsWith('{')) {\n return /^\\d+\\.\\d+/.test(raw)\n ? { harness: raw, cli: null, templates: null }\n : null;\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const harness = record.harness;\n if (typeof harness !== 'string') return null;\n return {\n harness,\n cli: typeof record.cli === 'string' ? record.cli : null,\n templates: typeof record.templates === 'string' ? record.templates : null,\n };\n } catch {\n return null;\n }\n}\n\n/** The parsed marker, or null when it is absent OR unreadable. */\nexport function readShellMarker(harnessDir: string): ShellMarker | null {\n const state = readShellMarkerState(harnessDir);\n return state.kind === 'ok' ? state.marker : null;\n}\n\n/** The `@ekanos/harness` version an existing shell was generated against. */\nexport function readShellVersion(harnessDir: string): string | null {\n return readShellMarker(harnessDir)?.harness ?? null;\n}\n\nfunction serializeMarker(marker: ShellMarker): string {\n return `${JSON.stringify(marker, null, 2)}\\n`;\n}\n\n/**\n * Write a generated file, but ONLY when its bytes would change.\n *\n * This is what makes a re-run cheap and a watcher quiet: an unchanged file is\n * not rewritten, so its mtime does not move and Next's dev server does not\n * rebuild. It also means a hand-edit inside the CLI-owned tree is silently\n * corrected on the next run, which is the intended contract for `.ekanos/`.\n */\nexport function writeIfChanged(\n root: string,\n destPath: string,\n contents: string,\n into: WrittenFiles,\n label: string,\n force: boolean,\n): void {\n // `classifyPath` uses lstat, so a symlink here is reported as a symlink\n // rather than as whatever it points at — including a DANGLING one, which\n // `existsSync` would call absent and a plain write would then materialize\n // outside the project.\n const kind = classifyPath(destPath);\n const exists = kind !== 'missing';\n\n if (\n !force &&\n kind === 'file' &&\n readFileNoFollow(root, destPath) === contents\n ) {\n into.unchanged.push(label);\n return;\n }\n\n writeFileNoFollow(root, destPath, contents);\n (exists ? into.updated : into.created).push(label);\n}\n\nexport interface ScaffoldShellArgs {\n projectDir: string;\n slug: string;\n harnessVersion: string;\n /** Rewrite every file even when its bytes already match. */\n force: boolean;\n /** Extra Tailwind @source globs from ekanos.json, already validated. */\n sourceGlobs?: readonly string[];\n}\n\nexport interface ScaffoldShellResult extends WrittenFiles {\n harnessDir: string;\n /** The version the shell carried before this run — null on a first run. */\n previousVersion: string | null;\n /** What generated the previous shell, or null on a first run. */\n previousMarker: ShellMarker | null;\n /** True when anything that generated the shell has since changed. */\n stale: boolean;\n /**\n * Which of harness / cli / templates moved. Empty on a first run; this is\n * what lets `dev` say WHY it regenerated rather than just that it did.\n */\n staleReasons: string[];\n}\n\n/**\n * Generate `.ekanos/harness/**` for `harnessVersion`. Every file here is\n * CLI-owned and gitignored, so regeneration needs no permission and asks no\n * question — the partner owns `harness.config.ts` and their integration\n * source, and nothing else.\n */\nexport function scaffoldShell(args: ScaffoldShellArgs): ScaffoldShellResult {\n const harnessDir = path.join(args.projectDir, HARNESS_DIR);\n assertShellDirIsOurs(harnessDir, args.force);\n\n const markerState = readShellMarkerState(harnessDir);\n const previousMarker = markerState.kind === 'ok' ? markerState.marker : null;\n const previousVersion = previousMarker?.harness ?? null;\n const marker: ShellMarker = {\n harness: args.harnessVersion,\n cli: cliVersion(),\n templates: templatesFingerprint(SHELL_FILES.map((f) => f.template)),\n };\n\n // A shell is current only if EVERYTHING that produced it is unchanged.\n // Checking the harness version alone certified shells generated by an older\n // CLI, against templates that had since moved, as up to date.\n const staleReasons: string[] = [];\n if (markerState.kind === 'corrupt') {\n staleReasons.push(\n `${VERSION_MARKER} was unreadable, so the existing shell could not be ` +\n 'certified',\n );\n }\n if (previousMarker !== null) {\n if (previousMarker.harness !== marker.harness) {\n staleReasons.push(\n `@ekanos/harness ${previousMarker.harness} → ${marker.harness}`,\n );\n }\n if (previousMarker.cli !== marker.cli) {\n staleReasons.push(\n `@ekanos/cli ${previousMarker.cli ?? 'unknown'} → ${marker.cli}`,\n );\n }\n if (previousMarker.templates !== marker.templates) {\n staleReasons.push('the shell templates changed');\n }\n }\n const stale = staleReasons.length > 0;\n\n const files: WrittenFiles = { created: [], updated: [], unchanged: [] };\n const vars = {\n SLUG: args.slug,\n SOURCE_GLOBS: renderSourceGlobs(args.sourceGlobs ?? []),\n };\n\n for (const { template, dest } of SHELL_FILES) {\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, dest),\n readTemplate(template, vars),\n files,\n dest,\n args.force,\n );\n }\n\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, VERSION_MARKER),\n serializeMarker(marker),\n files,\n VERSION_MARKER,\n args.force,\n );\n\n return {\n ...files,\n harnessDir,\n previousVersion,\n previousMarker,\n stale,\n staleReasons,\n };\n}\n\n/**\n * Turn validated `sourceGlobs` into the `@source` lines the generated\n * stylesheet carries, each relative to the shell's `styles/` directory.\n *\n * Tailwind never scans what it is not pointed at, and a glob that matches\n * nothing fails silently — so an integration living outside the default\n * layout renders completely unstyled with no error anywhere. That is what\n * `sourceGlobs` is for, and until now it was parsed and thrown away.\n */\nfunction renderSourceGlobs(globs: readonly string[]): string {\n if (globs.length === 0) return '';\n const lines = globs.map((glob) => `@source '${SHELL_TO_PROJECT}${glob}';`);\n return `\\n/* From \"sourceGlobs\" in ekanos.json. */\\n${lines.join('\\n')}\\n`;\n}\n\n/** From `.ekanos/harness/styles/` back up to the project root. */\nconst SHELL_TO_PROJECT = '../../../';\n\n/**\n * Establish that `.ekanos/harness` is a tree WE generated before writing a\n * single byte into it, and return the version marker it carried.\n *\n * Path location alone is not ownership. A directory that exists but carries no\n * `.ekanos-version` was put there by something else — a partner, another tool,\n * a symlink — and clobbering it because it happens to sit at the path we like\n * is the same class of mistake as following a symlink. An empty directory is\n * fine (that is just `mkdir -p` having run), and `--force` is the explicit\n * override.\n *\n * Runs BEFORE `bootstrapHarnessConfig` in `dev`, so a shell that cannot be\n * written leaves the project completely untouched rather than half-scaffolded.\n */\nexport function assertShellDirIsOurs(\n harnessDir: string,\n force: boolean,\n): string | null {\n const kind = classifyPath(harnessDir);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${harnessDir} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Delete ${harnessDir} and re-run \"ekanos dev\" — the shell is generated, ` +\n 'so nothing is lost.',\n );\n }\n\n if (kind !== 'missing' && kind !== 'directory') {\n throw invalidStateError(\n `${harnessDir} exists and is not a directory.`,\n `Move or delete ${harnessDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') return null;\n\n const state = readShellMarkerState(harnessDir);\n\n // A marker we wrote, however mangled, still identifies the tree as ours.\n if (state.kind === 'ok') return state.marker.harness;\n if (state.kind === 'corrupt' || force) return null;\n\n if (fs.readdirSync(harnessDir).length === 0) return null;\n\n throw invalidStateError(\n `${harnessDir} already contains files but no ${VERSION_MARKER} marker, ` +\n 'so it was not generated by \"ekanos dev\".',\n `Delete ${harnessDir} if you do not need it, or pass --force to ` +\n 'overwrite it. Everything \"ekanos dev\" generates is reproducible; ' +\n 'anything else in there is not.',\n );\n}\n\n/**\n * The module specifier `harness.config.ts` uses to import the partner's\n * integration: the `entry` from ekanos.json, made relative and extension-free.\n * \"src/integration.ts\" → \"src/integration\".\n */\nexport function entryImportSpecifier(entry: string): string {\n const normalized = entry.split(path.sep).join('/').replace(/^\\.\\//, '');\n return normalized.replace(/\\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, '');\n}\n\n/**\n * The same specifier as a JavaScript string LITERAL, ready to drop into a\n * generated `import` statement.\n *\n * `entry` is attacker-influenced data from `ekanos.json`, and a POSIX filename\n * may legally contain a quote or a newline. Interpolating it raw would let a\n * crafted-but-valid path close the string and append statements to the module\n * we generate. `JSON.stringify` produces a correctly escaped literal, which is\n * the actual fix; the explicit rejection below is a second line that also\n * gives the partner a comprehensible error instead of a bizarre filename\n * silently appearing, escaped, in their config.\n *\n * This does not cross a trust boundary on its own — a partner's own code runs\n * unsandboxed anyway — but \"the templates cannot be injected into\" is a claim\n * we make, and it should be true.\n */\nexport function entryImportLiteral(entry: string): string {\n const specifier = entryImportSpecifier(entry);\n\n // eslint-disable-next-line no-control-regex\n if (/[\"'`\\\\\\n\\r\\u0000-\\u001f]/.test(specifier)) {\n throw preconditionError(\n `The \"entry\" in ekanos.json contains characters that cannot appear in ` +\n `an import specifier: ${JSON.stringify(specifier)}.`,\n 'Rename the file so its path has no quotes, backslashes or control ' +\n 'characters, then update \"entry\" in ekanos.json to match.',\n );\n }\n\n return JSON.stringify(`./${specifier}`);\n}\n\nexport interface BootstrapConfigResult {\n /** True only when this run created the file. */\n bootstrapped: boolean;\n configPath: string;\n}\n\n/**\n * Write a starter `harness.config.ts` at the project root — ONCE.\n *\n * An existing file is left strictly alone, byte for byte, and no flag changes\n * that: it is the one harness file the partner owns, it is git-tracked, and\n * regenerating it would throw away their fixtures. `--force` regenerates the\n * shell, never this.\n */\nexport function bootstrapHarnessConfig(\n projectDir: string,\n integrations: readonly { slug: string; entry: string }[],\n): BootstrapConfigResult {\n const configPath = path.join(projectDir, HARNESS_CONFIG_FILENAME);\n const kind = classifyPath(configPath);\n\n // A symlink here — including a dangling one, which `existsSync` reports as\n // absent — would have us write through to its target. Since this file is the\n // partner's to own, the only safe answer is to touch nothing and say why.\n if (kind === 'symlink') {\n throw invalidStateError(\n `${configPath} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Replace ${configPath} with a real file (or delete it to have one ` +\n 'generated), then re-run \"ekanos dev\".',\n );\n }\n\n if (kind !== 'missing') {\n return { bootstrapped: false, configPath };\n }\n\n writeFileNoFollow(projectDir, configPath, renderHarnessConfig(integrations));\n\n return { bootstrapped: true, configPath };\n}\n\n/**\n * Render the starter registry for EVERY integration the project declares.\n *\n * The harness registry is an array and `ekanos.json` can now hold a list, so a\n * project's second integration reaches the harness the same way its first\n * does. Each entry imports the definition under a distinct local identifier\n * derived from its slug, because two `integration` imports in one module do\n * not compile.\n */\nfunction renderHarnessConfig(\n integrations: readonly { slug: string; entry: string }[],\n): string {\n const imports: string[] = [];\n const entries: string[] = [];\n\n for (const integration of integrations) {\n const ident = identifierForSlug(integration.slug);\n imports.push(\n `import { integration as ${ident} } from ${entryImportLiteral(\n integration.entry,\n )};`,\n );\n entries.push(\n readTemplate('harness.config.entry.ts.tmpl', {\n IDENT: ident,\n SLUG: integration.slug,\n }),\n );\n }\n\n return readTemplate('harness.config.ts.tmpl', {\n INTEGRATION_IMPORTS: imports.join('\\n'),\n INTEGRATION_ENTRIES: entries.join(''),\n });\n}\n\n/**\n * \"acme-crm\" → \"acmeCrm\". A slug is kebab-case by schema, so this only has to\n * handle hyphens, and a leading digit is impossible.\n */\nexport function identifierForSlug(slug: string): string {\n const camel = slug.replace(/-([a-z0-9])/g, (_, char: string) =>\n char.toUpperCase(),\n );\n return /^[0-9]/.test(camel) ? `i${camel}` : camel;\n}\n\n/**\n * Add `.ekanos/` to the project `.gitignore`, exactly once.\n *\n * Matching is line-wise over the spellings git treats as the same rule\n * (`.ekanos`, `.ekanos/`, `/.ekanos/`), so re-running never appends a\n * duplicate — the thing that makes a scaffold verb safe to run in a loop.\n * A negation (`!.ekanos/`) is deliberately NOT treated as a match: the\n * partner meant to un-ignore it, and we do not fight that silently.\n */\nexport function ensureGitignoreEntry(projectDir: string): boolean {\n const gitignorePath = path.join(projectDir, '.gitignore');\n const block = `# Ekanos dev harness — generated by \"ekanos dev\", never edited by hand\\n${GITIGNORE_ENTRY}\\n`;\n\n const kind = classifyPath(gitignorePath);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${gitignorePath} is a symlink, so \"ekanos dev\" will not append to it.`,\n `Add \"${GITIGNORE_ENTRY}\" to your real .gitignore yourself, or replace ` +\n `${gitignorePath} with a regular file, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') {\n writeFileNoFollow(projectDir, gitignorePath, block);\n return true;\n }\n\n const existing = readFileNoFollow(projectDir, gitignorePath) ?? '';\n if (gitignoreCovers(existing)) return false;\n\n const separator =\n existing.length === 0 || existing.endsWith('\\n') ? '' : '\\n';\n appendFileNoFollow(projectDir, gitignorePath, `${separator}\\n${block}`);\n return true;\n}\n\nfunction gitignoreCovers(source: string): boolean {\n const equivalents = new Set(['.ekanos', '.ekanos/', '/.ekanos', '/.ekanos/']);\n return source\n .split('\\n')\n .map((line) => line.trim())\n .some((line) => equivalents.has(line));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"harness-scaffold.js","sourceRoot":"","sources":["../src/harness-scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AAE3D,yEAAyE;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAEhD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkD;IACxE,EAAE,QAAQ,EAAE,6BAA6B,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACnE;QACE,QAAQ,EAAE,oCAAoC;QAC9C,IAAI,EAAE,uBAAuB;KAC9B;IACD,EAAE,QAAQ,EAAE,2BAA2B,EAAE,IAAI,EAAE,cAAc,EAAE;IAC/D;QACE,QAAQ,EAAE,oCAAoC;QAC9C,IAAI,EAAE,uBAAuB;KAC9B;IACD;QACE,QAAQ,EAAE,0CAA0C;QACpD,IAAI,EAAE,6BAA6B;KACpC;IACD;QACE,QAAQ,EAAE,qDAAqD;QAC/D,IAAI,EAAE,wCAAwC;KAC/C;IACD;QACE,QAAQ,EAAE,uCAAuC;QACjD,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,QAAQ,EAAE,6CAA6C;QACvD,IAAI,EAAE,gCAAgC;KACvC;IACD;QACE,QAAQ,EAAE,2CAA2C;QACrD,IAAI,EAAE,8BAA8B;KACrC;IACD;QACE,QAAQ,EAAE,6DAA6D;QACvE,IAAI,EAAE,gDAAgD;KACvD;IACD,EAAE,QAAQ,EAAE,8BAA8B,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACrE,EAAE,QAAQ,EAAE,iCAAiC,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC3E,EAAE,QAAQ,EAAE,4BAA4B,EAAE,IAAI,EAAE,eAAe,EAAE;IACjE,EAAE,QAAQ,EAAE,iCAAiC,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC3E,EAAE,QAAQ,EAAE,2BAA2B,EAAE,IAAI,EAAE,cAAc,EAAE;IAC/D;QACE,QAAQ,EAAE,uCAAuC;QACjD,IAAI,EAAE,0BAA0B;KACjC;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,eAAe;IACf,MAAM;IACN,OAAO;IACP,WAAW;IACX,aAAa;IACb,sBAAsB;CACd,CAAC;AAgCX,MAAM,UAAU,4BAA4B,CAC1C,UAAkB,EAClB,WAAmB;IAEnB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAC5B,UAAU,EACV,cAAc,EACd,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EACzB,cAAc,CACf,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,MAAM,OAAO,GACX,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAC3C,CAAC,CAAE,MAAgC,CAAC,OAAO;YAC3C,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,iBAAiB,CACrB,GAAG,WAAW,kDACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF,6BAA6B,UAAU,OAAO,WAAW,YAAY,CACtE,CAAC;IACJ,CAAC;AACH,CAAC;AAKD,MAAM,UAAU,oBAAoB,CAClC,UAAkB;IAElB,OAAO,4BAA4B,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AACnE,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,2BAA2B,CAAC,UAAkB;;;IAC5D,aAAO,MAAA,oBAAoB,CAAC,UAAU,CAAC,0CAAE,OAAO,mCAAI,IAAI,CAAC;AAC3D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,qBAAqB,CAAC,MAAM,CACjC,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,EAAE,CAAC,UAAU,CACZ,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,CAC5D,CACJ,CAAC;AACJ,CAAC;AAqCD,MAAM,UAAU,oBAAoB,CAAC,UAAkB;;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACrD,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAEhE,MAAM,GAAG,GAAG,OAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAEjD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAChF,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,6EAA6E;IAC7E,wEAAwE;IACxE,kEAAkE;IAClE,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,6DAA6D;IAC7D,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;YAC1B,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;YAC9C,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC7C,OAAO;YACL,OAAO;YACP,GAAG,EAAE,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;YACvD,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;SAC1E,CAAC;IACJ,CAAC;eAAO,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,KAAK,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,UAAkB;;;IACjD,aAAO,MAAA,eAAe,CAAC,UAAU,CAAC,0CAAE,OAAO,mCAAI,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CAAC,MAAmB;IAC1C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,QAAgB,EAChB,QAAgB,EAChB,IAAkB,EAClB,KAAa,EACb,KAAc;IAEd,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,uBAAuB;IACvB,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC;IAElC,IACE,CAAC,KAAK;QACN,IAAI,KAAK,MAAM;QACf,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,QAAQ,EAC7C,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IAED,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrD,CAAC;AA2BD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAuB;;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3D,oBAAoB,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAE7C,MAAM,WAAW,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,MAAM,eAAe,SAAG,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,OAAO,mCAAI,IAAI,CAAC;IACxD,MAAM,MAAM,GAAgB;QAC1B,OAAO,EAAE,IAAI,CAAC,cAAc;QAC5B,GAAG,EAAE,UAAU,EAAE;QACjB,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;KACpE,CAAC;IAEF,uEAAuE;IACvE,4EAA4E;IAC5E,8DAA8D;IAC9D,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACnC,YAAY,CAAC,IAAI,CACf,GAAG,cAAc,sDAAsD;YACrE,WAAW,CACd,CAAC;IACJ,CAAC;IACD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,cAAc,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9C,YAAY,CAAC,IAAI,CACf,mBAAmB,cAAc,CAAC,OAAO,MAAM,MAAM,CAAC,OAAO,EAAE,CAChE,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CACf,eAAe,MAAA,cAAc,CAAC,GAAG,mCAAI,SAAS,MAAM,MAAM,CAAC,GAAG,EAAE,CACjE,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;YAClD,YAAY,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAEtC,MAAM,KAAK,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,YAAY,EAAE,iBAAiB,OAAC,IAAI,CAAC,WAAW,mCAAI,EAAE,CAAC;KACxD,CAAC;IAEF,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7C,cAAc,CACZ,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAC3B,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC5B,KAAK,EACL,IAAI,EACJ,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,cAAc,CACZ,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACrC,eAAe,CAAC,MAAM,CAAC,EACvB,KAAK,EACL,cAAc,EACd,IAAI,CAAC,KAAK,CACX,CAAC;IAEF,uCACK,KAAK,KACR,UAAU;QACV,eAAe;QACf,cAAc;QACd,KAAK;QACL,YAAY,IACZ;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CAAC,KAAwB;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,gBAAgB,GAAG,IAAI,IAAI,CAAC,CAAC;IAC3E,OAAO,+CAA+C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAC7E,CAAC;AAED,kEAAkE;AAClE,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAErC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,KAAc;IAEd,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAEtC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,UAAU,2DAA2D,EACxE,UAAU,UAAU,qDAAqD;YACvE,qBAAqB,CACxB,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QAC/C,MAAM,iBAAiB,CACrB,GAAG,UAAU,iCAAiC,EAC9C,kBAAkB,UAAU,6BAA6B,CAC1D,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,KAAK,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAE/C,yEAAyE;IACzE,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;IACrD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAEnD,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,iBAAiB,CACrB,GAAG,UAAU,kCAAkC,cAAc,WAAW;QACtE,0CAA0C,EAC5C,UAAU,UAAU,6CAA6C;QAC/D,mEAAmE;QACnE,gCAAgC,CACnC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,UAAU,CAAC,OAAO,CAAC,oCAAoC,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE9C,4CAA4C;IAC5C,IAAI,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,MAAM,iBAAiB,CACrB,uEAAuE;YACrE,wBAAwB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EACtD,oEAAoE;YAClE,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC,CAAC;AAC1C,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,YAAwD;IAExD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAEtC,2EAA2E;IAC3E,6EAA6E;IAC7E,0EAA0E;IAC1E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,UAAU,2DAA2D,EACxE,WAAW,UAAU,8CAA8C;YACjE,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC;IAE7E,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAC1B,YAAwD;IAExD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,iBAAiB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CACV,2BAA2B,KAAK,WAAW,kBAAkB,CAC3D,WAAW,CAAC,KAAK,CAClB,GAAG,CACL,CAAC;QACF,OAAO,CAAC,IAAI,CACV,YAAY,CAAC,8BAA8B,EAAE;YAC3C,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,WAAW,CAAC,IAAI;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,YAAY,CAAC,wBAAwB,EAAE;QAC5C,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QACvC,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;KACtC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAC7D,IAAI,CAAC,WAAW,EAAE,CACnB,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;;IACrD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,2EAA2E,eAAe,IAAI,CAAC;IAE7G,MAAM,IAAI,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAEzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,aAAa,uDAAuD,EACvE,QAAQ,eAAe,iDAAiD;YACtE,GAAG,aAAa,iDAAiD,CACpE,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,SAAG,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,mCAAI,EAAE,CAAC;IACnE,IAAI,eAAe,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5C,MAAM,SAAS,GACb,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,kBAAkB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAC9E,OAAO,MAAM;SACV,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { cliVersion, templatesFingerprint } from './compatibility';\nimport { invalidStateError, preconditionError } from './errors';\nimport {\n appendFileNoFollow,\n classifyPath,\n readFileNoFollow,\n writeFileNoFollow,\n} from './safe-fs';\nimport { readTemplate } from './templates';\n\n/** The dependency that owns every line of harness behaviour. */\nexport const HARNESS_PACKAGE = '@ekanos/harness';\n\n/** Where the generated shell lives, relative to the project root. */\nexport const HARNESS_DIR = path.join('.ekanos', 'harness');\n\n/** The single `.gitignore` line that covers the whole generated tree. */\nexport const GITIGNORE_ENTRY = '.ekanos/';\n\n/**\n * The staleness marker, inside the generated tree so deleting the tree also\n * deletes the marker. Holds the `@ekanos/harness` version that generated the\n * shell — a version bump means the shell may be wired against an older export\n * surface, so it is regenerated.\n */\nexport const VERSION_MARKER = '.ekanos-version';\n\n/** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */\nexport const HARNESS_CONFIG_FILENAME = 'harness.config.ts';\n\n/**\n * Every file in the generated shell: bundled template → path inside\n * `.ekanos/harness/`. The ten route files are mechanically derivable from the\n * export map alone — which is the test of whether the package boundary is in\n * the right place. If a shell file ever needs a judgment call, the boundary\n * has moved, not the template.\n *\n * `app/api/integrations/[slug]/[...path]/route.ts` is the shell's one SERVER\n * route: it injects the registry into `@ekanos/harness/api`, which serves the\n * live-mode OAuth loop, storage bridge and trigger route over a server-held\n * context. It is the third registry injection point, beside the two pages.\n *\n * `app/harness-shell.tsx` exists because a `HarnessIntegration` holds React\n * component references and MCP `run` functions. Next cannot serialize those\n * across a server→client boundary, so the `'use client'` mark has to sit ABOVE\n * the point where the registry is injected — hence a client shell of its own\n * rather than a client `RootLayout` called from a server layout. Keeping\n * `app/page.tsx` a server component is what lets `/` prerender static.\n */\nexport const SHELL_FILES: readonly { template: string; dest: string }[] = [\n { template: 'harness/app/layout.tsx.tmpl', dest: 'app/layout.tsx' },\n {\n template: 'harness/app/harness-shell.tsx.tmpl',\n dest: 'app/harness-shell.tsx',\n },\n { template: 'harness/app/page.tsx.tmpl', dest: 'app/page.tsx' },\n {\n template: 'harness/app/[slug]/layout.tsx.tmpl',\n dest: 'app/[slug]/layout.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/page.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/[widgetId]/page.tsx',\n },\n {\n template: 'harness/app/[slug]/tile/page.tsx.tmpl',\n dest: 'app/[slug]/tile/page.tsx',\n },\n {\n template: 'harness/app/[slug]/activation/page.tsx.tmpl',\n dest: 'app/[slug]/activation/page.tsx',\n },\n {\n template: 'harness/app/[slug]/triggers/page.tsx.tmpl',\n dest: 'app/[slug]/triggers/page.tsx',\n },\n {\n template: 'harness/app/api/integrations/[slug]/[...path]/route.ts.tmpl',\n dest: 'app/api/integrations/[slug]/[...path]/route.ts',\n },\n { template: 'harness/next.config.mjs.tmpl', dest: 'next.config.mjs' },\n { template: 'harness/postcss.config.mjs.tmpl', dest: 'postcss.config.mjs' },\n { template: 'harness/tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'harness/styles/globals.css.tmpl', dest: 'styles/globals.css' },\n { template: 'harness/package.json.tmpl', dest: 'package.json' },\n {\n template: 'harness/types/team-accounts.d.ts.tmpl',\n dest: 'types/team-accounts.d.ts',\n },\n];\n\n/**\n * Packages the shell needs to resolve upward out of the partner's own\n * `node_modules`. The shell declares no dependencies of its own by design, so\n * a missing one shows up as an opaque Next resolution error at dev-server\n * start; checking here turns that into one precondition with one fix.\n */\nexport const REQUIRED_DEPENDENCIES = [\n HARNESS_PACKAGE,\n 'next',\n 'react',\n 'react-dom',\n 'tailwindcss',\n '@tailwindcss/postcss',\n] as const;\n\nexport interface WrittenFiles {\n created: string[];\n updated: string[];\n unchanged: string[];\n}\n\n/**\n * Read an installed package's `package.json` out of a project's TOP-LEVEL\n * `node_modules`. Resolved by direct path join rather than `require.resolve`\n * on purpose, for two independent reasons:\n *\n * - The CLI runs from its own install and must read the version of the copy\n * in THE CALLER'S project, not one hoisted next to itself.\n * - Several of the packages this reads (`@ekanos/integration-schema`\n * included) declare an `exports` map with no `./package.json` subpath, so\n * `require.resolve('<pkg>/package.json')` throws\n * `ERR_PACKAGE_PATH_NOT_EXPORTED` under Node's ESM resolver. A plain file\n * read is not subject to the export map at all.\n *\n * Shared by every \"what does this project have installed?\" check in the\n * CLI — `readInstalledHarness` below, the schema-skew gate, and `upgrade`'s\n * current-toolchain report — so there is exactly one implementation of \"read\n * a package's on-disk version\", not a subtly different one per call site.\n */\nexport interface InstalledPackage {\n version: string;\n /** The raw package.json, for callers that need more than the version. */\n manifest: unknown;\n}\n\nexport function readInstalledPackageManifest(\n projectDir: string,\n packageName: string,\n): InstalledPackage | null {\n const manifestPath = path.join(\n projectDir,\n 'node_modules',\n ...packageName.split('/'),\n 'package.json',\n );\n if (!fs.existsSync(manifestPath)) return null;\n\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n if (typeof version !== 'string' || version.length === 0) return null;\n return { version, manifest: parsed };\n } catch (error) {\n throw preconditionError(\n `${packageName}'s package.json in node_modules is unreadable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Reinstall dependencies in ${projectDir} so ${packageName} resolves.`,\n );\n }\n}\n\n/** Read the installed `@ekanos/harness` version out of the partner's project. */\nexport type InstalledHarness = InstalledPackage;\n\nexport function readInstalledHarness(\n projectDir: string,\n): InstalledHarness | null {\n return readInstalledPackageManifest(projectDir, HARNESS_PACKAGE);\n}\n\n/** Just the installed version, for callers that need nothing else. */\nexport function readInstalledHarnessVersion(projectDir: string): string | null {\n return readInstalledHarness(projectDir)?.version ?? null;\n}\n\n/** Which of `REQUIRED_DEPENDENCIES` are not installed in the project root. */\nexport function missingDependencies(projectDir: string): string[] {\n return REQUIRED_DEPENDENCIES.filter(\n (name) =>\n !fs.existsSync(\n path.join(projectDir, 'node_modules', name, 'package.json'),\n ),\n );\n}\n\n/**\n * What generated an existing shell.\n *\n * All three fields matter, and recording only `harness` was a real hole: the\n * templates ship with the CLI, so a shell can be stale because the CLI changed\n * while the harness did not. `templates` is the authoritative one — it is a\n * hash of the template sources, so it moves whenever what we generate moves,\n * including for a CLI build that never shipped a version bump.\n */\nexport interface ShellMarker {\n harness: string;\n /** null for a legacy bare-version marker written before this was recorded. */\n cli: string | null;\n templates: string | null;\n}\n\n/**\n * Three outcomes, not two, because \"no marker\" and \"unreadable marker\" call\n * for opposite responses.\n *\n * A marker's PRESENCE is itself evidence the tree is ours — a squatter does\n * not write `.ekanos-version` — and the likeliest cause of an unreadable one\n * is our own interrupted write. Everything under `.ekanos/harness/` is\n * CLI-owned, gitignored and reproducible from the templates, so regenerating\n * destroys nothing: there is no value at risk to justify charging a partner a\n * confusing `--force` to recover from our own bad write.\n *\n * An ABSENT marker over a non-empty tree is the opposite: nothing says the\n * files are ours, so we refuse. That is the L1 rule and it stays.\n */\nexport type MarkerState =\n | { kind: 'missing' }\n | { kind: 'corrupt' }\n | { kind: 'ok'; marker: ShellMarker };\n\nexport function readShellMarkerState(harnessDir: string): MarkerState {\n const marker = path.join(harnessDir, VERSION_MARKER);\n if (classifyPath(marker) !== 'file') return { kind: 'missing' };\n\n const raw = (readFileNoFollow(harnessDir, marker) ?? '').trim();\n if (raw.length === 0) return { kind: 'corrupt' };\n\n const parsed = parseMarker(raw);\n return parsed === null ? { kind: 'corrupt' } : { kind: 'ok', marker: parsed };\n}\n\nfunction parseMarker(raw: string): ShellMarker | null {\n // v1 markers were a bare version string. Read them rather than crashing, and\n // report the unknown fields as null so the shell counts as stale and is\n // regenerated once — which is the correct outcome for an upgrade.\n //\n // It must actually LOOK like a version: without this, any garbage that does\n // not happen to start with `{` — a truncated write, a stray word — reads as\n // a legacy marker named after the garbage, and the corrupt-marker path never\n // fires. That is how a truncated `.ekanos-version` was silently reported as\n // \"@ekanos/harness truncat → 0.1.0\" instead of \"unreadable\".\n if (!raw.startsWith('{')) {\n return /^\\d+\\.\\d+/.test(raw)\n ? { harness: raw, cli: null, templates: null }\n : null;\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const harness = record.harness;\n if (typeof harness !== 'string') return null;\n return {\n harness,\n cli: typeof record.cli === 'string' ? record.cli : null,\n templates: typeof record.templates === 'string' ? record.templates : null,\n };\n } catch {\n return null;\n }\n}\n\n/** The parsed marker, or null when it is absent OR unreadable. */\nexport function readShellMarker(harnessDir: string): ShellMarker | null {\n const state = readShellMarkerState(harnessDir);\n return state.kind === 'ok' ? state.marker : null;\n}\n\n/** The `@ekanos/harness` version an existing shell was generated against. */\nexport function readShellVersion(harnessDir: string): string | null {\n return readShellMarker(harnessDir)?.harness ?? null;\n}\n\nfunction serializeMarker(marker: ShellMarker): string {\n return `${JSON.stringify(marker, null, 2)}\\n`;\n}\n\n/**\n * Write a generated file, but ONLY when its bytes would change.\n *\n * This is what makes a re-run cheap and a watcher quiet: an unchanged file is\n * not rewritten, so its mtime does not move and Next's dev server does not\n * rebuild. It also means a hand-edit inside the CLI-owned tree is silently\n * corrected on the next run, which is the intended contract for `.ekanos/`.\n */\nexport function writeIfChanged(\n root: string,\n destPath: string,\n contents: string,\n into: WrittenFiles,\n label: string,\n force: boolean,\n): void {\n // `classifyPath` uses lstat, so a symlink here is reported as a symlink\n // rather than as whatever it points at — including a DANGLING one, which\n // `existsSync` would call absent and a plain write would then materialize\n // outside the project.\n const kind = classifyPath(destPath);\n const exists = kind !== 'missing';\n\n if (\n !force &&\n kind === 'file' &&\n readFileNoFollow(root, destPath) === contents\n ) {\n into.unchanged.push(label);\n return;\n }\n\n writeFileNoFollow(root, destPath, contents);\n (exists ? into.updated : into.created).push(label);\n}\n\nexport interface ScaffoldShellArgs {\n projectDir: string;\n slug: string;\n harnessVersion: string;\n /** Rewrite every file even when its bytes already match. */\n force: boolean;\n /** Extra Tailwind @source globs from ekanos.json, already validated. */\n sourceGlobs?: readonly string[];\n}\n\nexport interface ScaffoldShellResult extends WrittenFiles {\n harnessDir: string;\n /** The version the shell carried before this run — null on a first run. */\n previousVersion: string | null;\n /** What generated the previous shell, or null on a first run. */\n previousMarker: ShellMarker | null;\n /** True when anything that generated the shell has since changed. */\n stale: boolean;\n /**\n * Which of harness / cli / templates moved. Empty on a first run; this is\n * what lets `dev` say WHY it regenerated rather than just that it did.\n */\n staleReasons: string[];\n}\n\n/**\n * Generate `.ekanos/harness/**` for `harnessVersion`. Every file here is\n * CLI-owned and gitignored, so regeneration needs no permission and asks no\n * question — the partner owns `harness.config.ts` and their integration\n * source, and nothing else.\n */\nexport function scaffoldShell(args: ScaffoldShellArgs): ScaffoldShellResult {\n const harnessDir = path.join(args.projectDir, HARNESS_DIR);\n assertShellDirIsOurs(harnessDir, args.force);\n\n const markerState = readShellMarkerState(harnessDir);\n const previousMarker = markerState.kind === 'ok' ? markerState.marker : null;\n const previousVersion = previousMarker?.harness ?? null;\n const marker: ShellMarker = {\n harness: args.harnessVersion,\n cli: cliVersion(),\n templates: templatesFingerprint(SHELL_FILES.map((f) => f.template)),\n };\n\n // A shell is current only if EVERYTHING that produced it is unchanged.\n // Checking the harness version alone certified shells generated by an older\n // CLI, against templates that had since moved, as up to date.\n const staleReasons: string[] = [];\n if (markerState.kind === 'corrupt') {\n staleReasons.push(\n `${VERSION_MARKER} was unreadable, so the existing shell could not be ` +\n 'certified',\n );\n }\n if (previousMarker !== null) {\n if (previousMarker.harness !== marker.harness) {\n staleReasons.push(\n `@ekanos/harness ${previousMarker.harness} → ${marker.harness}`,\n );\n }\n if (previousMarker.cli !== marker.cli) {\n staleReasons.push(\n `@ekanos/cli ${previousMarker.cli ?? 'unknown'} → ${marker.cli}`,\n );\n }\n if (previousMarker.templates !== marker.templates) {\n staleReasons.push('the shell templates changed');\n }\n }\n const stale = staleReasons.length > 0;\n\n const files: WrittenFiles = { created: [], updated: [], unchanged: [] };\n const vars = {\n SLUG: args.slug,\n SOURCE_GLOBS: renderSourceGlobs(args.sourceGlobs ?? []),\n };\n\n for (const { template, dest } of SHELL_FILES) {\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, dest),\n readTemplate(template, vars),\n files,\n dest,\n args.force,\n );\n }\n\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, VERSION_MARKER),\n serializeMarker(marker),\n files,\n VERSION_MARKER,\n args.force,\n );\n\n return {\n ...files,\n harnessDir,\n previousVersion,\n previousMarker,\n stale,\n staleReasons,\n };\n}\n\n/**\n * Turn validated `sourceGlobs` into the `@source` lines the generated\n * stylesheet carries, each relative to the shell's `styles/` directory.\n *\n * Tailwind never scans what it is not pointed at, and a glob that matches\n * nothing fails silently — so an integration living outside the default\n * layout renders completely unstyled with no error anywhere. That is what\n * `sourceGlobs` is for, and until now it was parsed and thrown away.\n */\nfunction renderSourceGlobs(globs: readonly string[]): string {\n if (globs.length === 0) return '';\n const lines = globs.map((glob) => `@source '${SHELL_TO_PROJECT}${glob}';`);\n return `\\n/* From \"sourceGlobs\" in ekanos.json. */\\n${lines.join('\\n')}\\n`;\n}\n\n/** From `.ekanos/harness/styles/` back up to the project root. */\nconst SHELL_TO_PROJECT = '../../../';\n\n/**\n * Establish that `.ekanos/harness` is a tree WE generated before writing a\n * single byte into it, and return the version marker it carried.\n *\n * Path location alone is not ownership. A directory that exists but carries no\n * `.ekanos-version` was put there by something else — a partner, another tool,\n * a symlink — and clobbering it because it happens to sit at the path we like\n * is the same class of mistake as following a symlink. An empty directory is\n * fine (that is just `mkdir -p` having run), and `--force` is the explicit\n * override.\n *\n * Runs BEFORE `bootstrapHarnessConfig` in `dev`, so a shell that cannot be\n * written leaves the project completely untouched rather than half-scaffolded.\n */\nexport function assertShellDirIsOurs(\n harnessDir: string,\n force: boolean,\n): string | null {\n const kind = classifyPath(harnessDir);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${harnessDir} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Delete ${harnessDir} and re-run \"ekanos dev\" — the shell is generated, ` +\n 'so nothing is lost.',\n );\n }\n\n if (kind !== 'missing' && kind !== 'directory') {\n throw invalidStateError(\n `${harnessDir} exists and is not a directory.`,\n `Move or delete ${harnessDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') return null;\n\n const state = readShellMarkerState(harnessDir);\n\n // A marker we wrote, however mangled, still identifies the tree as ours.\n if (state.kind === 'ok') return state.marker.harness;\n if (state.kind === 'corrupt' || force) return null;\n\n if (fs.readdirSync(harnessDir).length === 0) return null;\n\n throw invalidStateError(\n `${harnessDir} already contains files but no ${VERSION_MARKER} marker, ` +\n 'so it was not generated by \"ekanos dev\".',\n `Delete ${harnessDir} if you do not need it, or pass --force to ` +\n 'overwrite it. Everything \"ekanos dev\" generates is reproducible; ' +\n 'anything else in there is not.',\n );\n}\n\n/**\n * The module specifier `harness.config.ts` uses to import the partner's\n * integration: the `entry` from ekanos.json, made relative and extension-free.\n * \"src/integration.ts\" → \"src/integration\".\n */\nexport function entryImportSpecifier(entry: string): string {\n const normalized = entry.split(path.sep).join('/').replace(/^\\.\\//, '');\n return normalized.replace(/\\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, '');\n}\n\n/**\n * The same specifier as a JavaScript string LITERAL, ready to drop into a\n * generated `import` statement.\n *\n * `entry` is attacker-influenced data from `ekanos.json`, and a POSIX filename\n * may legally contain a quote or a newline. Interpolating it raw would let a\n * crafted-but-valid path close the string and append statements to the module\n * we generate. `JSON.stringify` produces a correctly escaped literal, which is\n * the actual fix; the explicit rejection below is a second line that also\n * gives the partner a comprehensible error instead of a bizarre filename\n * silently appearing, escaped, in their config.\n *\n * This does not cross a trust boundary on its own — a partner's own code runs\n * unsandboxed anyway — but \"the templates cannot be injected into\" is a claim\n * we make, and it should be true.\n */\nexport function entryImportLiteral(entry: string): string {\n const specifier = entryImportSpecifier(entry);\n\n // eslint-disable-next-line no-control-regex\n if (/[\"'`\\\\\\n\\r\\u0000-\\u001f]/.test(specifier)) {\n throw preconditionError(\n `The \"entry\" in ekanos.json contains characters that cannot appear in ` +\n `an import specifier: ${JSON.stringify(specifier)}.`,\n 'Rename the file so its path has no quotes, backslashes or control ' +\n 'characters, then update \"entry\" in ekanos.json to match.',\n );\n }\n\n return JSON.stringify(`./${specifier}`);\n}\n\nexport interface BootstrapConfigResult {\n /** True only when this run created the file. */\n bootstrapped: boolean;\n configPath: string;\n}\n\n/**\n * Write a starter `harness.config.ts` at the project root — ONCE.\n *\n * An existing file is left strictly alone, byte for byte, and no flag changes\n * that: it is the one harness file the partner owns, it is git-tracked, and\n * regenerating it would throw away their fixtures. `--force` regenerates the\n * shell, never this.\n */\nexport function bootstrapHarnessConfig(\n projectDir: string,\n integrations: readonly { slug: string; entry: string }[],\n): BootstrapConfigResult {\n const configPath = path.join(projectDir, HARNESS_CONFIG_FILENAME);\n const kind = classifyPath(configPath);\n\n // A symlink here — including a dangling one, which `existsSync` reports as\n // absent — would have us write through to its target. Since this file is the\n // partner's to own, the only safe answer is to touch nothing and say why.\n if (kind === 'symlink') {\n throw invalidStateError(\n `${configPath} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Replace ${configPath} with a real file (or delete it to have one ` +\n 'generated), then re-run \"ekanos dev\".',\n );\n }\n\n if (kind !== 'missing') {\n return { bootstrapped: false, configPath };\n }\n\n writeFileNoFollow(projectDir, configPath, renderHarnessConfig(integrations));\n\n return { bootstrapped: true, configPath };\n}\n\n/**\n * Render the starter registry for EVERY integration the project declares.\n *\n * The harness registry is an array and `ekanos.json` can now hold a list, so a\n * project's second integration reaches the harness the same way its first\n * does. Each entry imports the definition under a distinct local identifier\n * derived from its slug, because two `integration` imports in one module do\n * not compile.\n */\nfunction renderHarnessConfig(\n integrations: readonly { slug: string; entry: string }[],\n): string {\n const imports: string[] = [];\n const entries: string[] = [];\n\n for (const integration of integrations) {\n const ident = identifierForSlug(integration.slug);\n imports.push(\n `import { integration as ${ident} } from ${entryImportLiteral(\n integration.entry,\n )};`,\n );\n entries.push(\n readTemplate('harness.config.entry.ts.tmpl', {\n IDENT: ident,\n SLUG: integration.slug,\n }),\n );\n }\n\n return readTemplate('harness.config.ts.tmpl', {\n INTEGRATION_IMPORTS: imports.join('\\n'),\n INTEGRATION_ENTRIES: entries.join(''),\n });\n}\n\n/**\n * \"acme-crm\" → \"acmeCrm\". A slug is kebab-case by schema, so this only has to\n * handle hyphens, and a leading digit is impossible.\n */\nexport function identifierForSlug(slug: string): string {\n const camel = slug.replace(/-([a-z0-9])/g, (_, char: string) =>\n char.toUpperCase(),\n );\n return /^[0-9]/.test(camel) ? `i${camel}` : camel;\n}\n\n/**\n * Add `.ekanos/` to the project `.gitignore`, exactly once.\n *\n * Matching is line-wise over the spellings git treats as the same rule\n * (`.ekanos`, `.ekanos/`, `/.ekanos/`), so re-running never appends a\n * duplicate — the thing that makes a scaffold verb safe to run in a loop.\n * A negation (`!.ekanos/`) is deliberately NOT treated as a match: the\n * partner meant to un-ignore it, and we do not fight that silently.\n */\nexport function ensureGitignoreEntry(projectDir: string): boolean {\n const gitignorePath = path.join(projectDir, '.gitignore');\n const block = `# Ekanos dev harness — generated by \"ekanos dev\", never edited by hand\\n${GITIGNORE_ENTRY}\\n`;\n\n const kind = classifyPath(gitignorePath);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${gitignorePath} is a symlink, so \"ekanos dev\" will not append to it.`,\n `Add \"${GITIGNORE_ENTRY}\" to your real .gitignore yourself, or replace ` +\n `${gitignorePath} with a regular file, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') {\n writeFileNoFollow(projectDir, gitignorePath, block);\n return true;\n }\n\n const existing = readFileNoFollow(projectDir, gitignorePath) ?? '';\n if (gitignoreCovers(existing)) return false;\n\n const separator =\n existing.length === 0 || existing.endsWith('\\n') ? '' : '\\n';\n appendFileNoFollow(projectDir, gitignorePath, `${separator}\\n${block}`);\n return true;\n}\n\nfunction gitignoreCovers(source: string): boolean {\n const equivalents = new Set(['.ekanos', '.ekanos/', '/.ekanos', '/.ekanos/']);\n return source\n .split('\\n')\n .map((line) => line.trim())\n .some((line) => equivalents.has(line));\n}\n"]}
|
package/dist/icon-findings.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { Finding } from '@ekanos/integration-schema';
|
|
|
17
17
|
* needed source text rather than a loaded value.
|
|
18
18
|
*
|
|
19
19
|
* WARN, not error: a Pro-only name is broken UX in a Free-only host, not an
|
|
20
|
-
* unbuildable submission — plenty of
|
|
20
|
+
* unbuildable submission — plenty of Ekanos-hosted partners run under Pro,
|
|
21
21
|
* where the exact same name renders correctly.
|
|
22
22
|
*/
|
|
23
23
|
export declare function collectIconFindings(projectDir: string): Finding[];
|
package/dist/icon-findings.js
CHANGED
|
@@ -19,7 +19,7 @@ import { FA_FREE_ICON_NAMES, FA_PRO_TO_FREE_HINTS, } from './generated/fa-free-n
|
|
|
19
19
|
* needed source text rather than a loaded value.
|
|
20
20
|
*
|
|
21
21
|
* WARN, not error: a Pro-only name is broken UX in a Free-only host, not an
|
|
22
|
-
* unbuildable submission — plenty of
|
|
22
|
+
* unbuildable submission — plenty of Ekanos-hosted partners run under Pro,
|
|
23
23
|
* where the exact same name renders correctly.
|
|
24
24
|
*/
|
|
25
25
|
export function collectIconFindings(projectDir) {
|
|
@@ -105,7 +105,7 @@ function collectIconFindingsInFile(file) {
|
|
|
105
105
|
severity: 'warn',
|
|
106
106
|
file,
|
|
107
107
|
line,
|
|
108
|
-
message: `"fa-${iconName}" is not in the Font Awesome Free set.
|
|
108
|
+
message: `"fa-${iconName}" is not in the Font Awesome Free set. Ekanos itself ` +
|
|
109
109
|
'loads Font Awesome Pro, so this renders fine there, but the ' +
|
|
110
110
|
'generated harness (and any other Free-only host) shows the ' +
|
|
111
111
|
'circle-question placeholder instead of the intended glyph.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon-findings.js","sourceRoot":"","sources":["../src/icon-findings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EACL,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,QAAQ,CAAC,CAAC,WAAW,CAAC,GAAW;IAC/B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC;QACb,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,cAAc,GAClB,2DAA2D,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,iBAAiB,cAAc,6CAA6C,UAAU,EAAE,CAAC;AAClG,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,kBAAkB,GAAG,IAAI,MAAM,CACnC,uCAAuC,aAAa,CAAC,CAAC,CAAC,EAAE;IACvD,wCAAwC,aAAa,CAAC,CAAC,CAAC,EAAE,EAC5D,GAAG,CACJ,CAAC;AAEF,SAAS,yBAAyB,CAAC,IAAY;;IAC7C,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"icon-findings.js","sourceRoot":"","sources":["../src/icon-findings.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 {\n FA_FREE_ICON_NAMES,\n FA_PRO_TO_FREE_HINTS,\n} from './generated/fa-free-names';\n\n/**\n * Flags Font Awesome **Pro**-only `fa-*` icon names in a project's widget\n * source — today this is a silent, runtime-only failure: `@ekanos/ui`'s\n * `Icon` warns once via `console.warn` in dev, but ships nothing to\n * `validate`, so a partner only discovers a Pro-only name when the generated\n * harness (Font Awesome **Free**-only, see `@ekanos/harness`) renders the\n * circle-question fallback in place of the intended glyph — or never\n * discovers it, if nobody was watching the console.\n *\n * `validate` has no live component tree to inspect the way the runtime\n * warning does, and the definition's `component`/`icon` fields are live\n * React values by the time `loadDefinition` resolves them (function\n * references, not paths) — there is no file path to walk back to from an\n * already-imported component. So this scans the project's own `src/**\\/*.{ts,tsx}`\n * on disk instead, the same fallback `collectProjectFindings` would use if it\n * needed source text rather than a loaded value.\n *\n * WARN, not error: a Pro-only name is broken UX in a Free-only host, not an\n * unbuildable submission — plenty of Ekanos-hosted partners run under Pro,\n * where the exact same name renders correctly.\n */\nexport function collectIconFindings(projectDir: string): Finding[] {\n const srcDir = path.join(projectDir, 'src');\n if (!fs.existsSync(srcDir)) return [];\n\n const findings: Finding[] = [];\n for (const file of walkTsFiles(srcDir)) {\n findings.push(...collectIconFindingsInFile(file));\n }\n return findings;\n}\n\nfunction* walkTsFiles(dir: string): Generator<string> {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkTsFiles(full);\n } else if (/\\.(ts|tsx)$/.test(entry.name)) {\n yield full;\n }\n }\n}\n\n/**\n * One Font Awesome style-family token, e.g. the `fa-solid` in\n * `\"fa-solid fa-house\"` (the host's icon helper produces exactly this\n * two-token shape). Kept in sync by hand with the host UI package's\n * `FA_ICON_STYLES` — a style name is never itself an icon name, so it must\n * not be treated as one, but that list lives in a browser package this one\n * may not import.\n */\nconst FA_STYLE_TOKEN =\n '(?:solid|regular|light|thin|duotone|sharp|brands|classic)';\n\n/**\n * A quoted string literal whose ENTIRE contents are a bare `fa-<name>` value\n * or a two-token `fa-<style> fa-<name>` value — anchored to the quote\n * boundaries (the backreference to the opening quote must follow the name\n * pattern immediately) so this cannot match a `fa-`-prefixed SUBSTRING inside\n * a longer, unrelated string.\n *\n * Parameterized on its own backreference number because it is spliced into\n * `FA_ICON_CONTEXT_RE` twice — each copy opens its own capture group, so a\n * hardcoded `\\1` in the second copy would wrongly point at the FIRST copy's\n * quote group instead of its own.\n */\nfunction faIconLiteral(quoteGroup: number): string {\n return `(['\"\\`])(?:fa-${FA_STYLE_TOKEN}\\\\s+)?fa-([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\\\\${quoteGroup}`;\n}\n\n/**\n * Only literals in a position that plausibly HOLDS an icon name: the `name`/\n * `icon` prop or object key an `<Icon>`, a widget's `category.icon`, or a\n * config object typically uses, or a direct argument to `resolveIcon`/\n * `renderIcon` (`@ekanos/ui`'s runtime helpers).\n *\n * This is a heuristic, not a parser — it will miss an icon name built up any\n * other way (a variable, a template with interpolation, an indirect prop\n * name), and in principle a `name`/`icon` key on some unrelated object could\n * coincidentally hold an `fa-`-shaped string and get flagged. Both failure\n * modes are accepted: a scan that required full TS type information to be\n * exact is not worth the cost here, and this project's own convention names\n * icon props/keys `name` or `icon` specifically to keep that collision rare.\n * Group numbers: 1 = quote, 2 = name for the `name`/`icon` branch; 3 = quote,\n * 4 = name for the `resolveIcon`/`renderIcon` branch.\n */\nconst FA_ICON_CONTEXT_RE = new RegExp(\n `\\\\b(?:name|icon)\\\\s*[:=]\\\\s*\\\\{?\\\\s*${faIconLiteral(1)}` +\n `|\\\\b(?:resolveIcon|renderIcon)\\\\(\\\\s*${faIconLiteral(3)}`,\n 'g',\n);\n\nfunction collectIconFindingsInFile(file: string): Finding[] {\n const source = fs.readFileSync(file, 'utf8');\n const findings: Finding[] = [];\n const seen = new Set<string>();\n\n for (const match of source.matchAll(FA_ICON_CONTEXT_RE)) {\n const iconName = match[2] ?? match[4];\n if (!iconName || FA_FREE_ICON_NAMES.has(iconName)) continue;\n\n const dedupeKey = `${iconName}@${match.index}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n const line = source.slice(0, match.index).split('\\n').length;\n const alternative = FA_PRO_TO_FREE_HINTS.get(iconName);\n\n findings.push({\n check: 'icons/pro-only',\n severity: 'warn',\n file,\n line,\n message:\n `\"fa-${iconName}\" is not in the Font Awesome Free set. Ekanos itself ` +\n 'loads Font Awesome Pro, so this renders fine there, but the ' +\n 'generated harness (and any other Free-only host) shows the ' +\n 'circle-question placeholder instead of the intended glyph.',\n hint: alternative\n ? `Use \"fa-${alternative}\" instead — the nearest Free equivalent.`\n : 'Pick a Free icon name — Pro names render as placeholder discs ' +\n 'outside Pro-licensed hosts.',\n });\n }\n\n return findings;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -108,7 +108,7 @@ const USAGE = [
|
|
|
108
108
|
' For a vitest test script, also renders a curated failures',
|
|
109
109
|
' appendix from a JSON reporter report; --raw skips that and',
|
|
110
110
|
' the reporter injection entirely.',
|
|
111
|
-
' login Authenticate against
|
|
111
|
+
' login Authenticate against an Ekanos host in a browser (--host <url>)',
|
|
112
112
|
' logout Revoke and delete the stored session (--host <url> | --all)',
|
|
113
113
|
' whoami Print the identity the stored session authenticates as, plus',
|
|
114
114
|
' a compact list of the sources you hold a seat on',
|
|
@@ -118,7 +118,7 @@ const USAGE = [
|
|
|
118
118
|
" project's current version reaches a gate verdict)",
|
|
119
119
|
' sources List every source you hold a developer/source-admin seat on',
|
|
120
120
|
' use Set this project\'s publish target ("ekanos use <source-slug>")',
|
|
121
|
-
' publish Validate, pack and submit the integration to
|
|
121
|
+
' publish Validate, pack and submit the integration to an Ekanos host',
|
|
122
122
|
' upgrade Move this toolchain to a coherent target version, with',
|
|
123
123
|
' verify-and-rollback (--check --yes --no-verify --host <url>)',
|
|
124
124
|
'',
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take a positional argument, and what it means. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, string> = {\n use: 'source-slug',\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against a Fusion host in a browser (--host <url>)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to a Fusion host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalName = POSITIONAL_COMMANDS[command];\n\n if (!positionalName && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (positionalName && parsed.positionals.length > 1) {\n throw usageError(\n `\"${command}\" takes at most one positional argument (a ` +\n `${positionalName}), got ${parsed.positionals.length}.`,\n `Pass exactly one, e.g. \"ekanos ${command} <${positionalName}>\". ` +\n `See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed({ usage: USAGE, commands: COMMANDS }, USAGE);\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAkB,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,YAAY,GAAc;IAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CAC7B,CAAC;AAEF,MAAM,aAAa,GAA8B;IAC/C,IAAI,EAAE;QACJ,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QACxB,sEAAsE;QACtE,uCAAuC;QACvC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE;KAC3C;IACD,QAAQ,EAAE;QACR,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACxB;IACD,GAAG,EAAE;QACH,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KAC3B;IACD,IAAI,EAAE;QACJ,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,oEAAoE;QACpE,oEAAoE;QACpE,uEAAuE;QACvE,6CAA6C;QAC7C,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KACzB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KAC3B;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KACzB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACzB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC7B,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KAC3B;IACD,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACxB;IACD,GAAG,EAAE;QACH,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACxB;IACD,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KACzB;IACD,OAAO,EAAE;QACP,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACvB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QACxB,qEAAqE;QACrE,wEAAwE;QACxE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KAC5B;CACF,CAAC;AAEF,2GAA2G;AAC3G,MAAM,mBAAmB,GAA2B;IAClD,GAAG,EAAE,aAAa;CACnB,CAAC;AAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAE5C,MAAM,KAAK,GAAG;IACZ,mCAAmC;IACnC,EAAE;IACF,WAAW;IACX,qFAAqF;IACrF,yEAAyE;IACzE,qEAAqE;IACrE,4EAA4E;IAC5E,wEAAwE;IACxE,yEAAyE;IACzE,+CAA+C;IAC/C,8EAA8E;IAC9E,0EAA0E;IAC1E,2EAA2E;IAC3E,+DAA+D;IAC/D,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,gEAAgE;IAChE,0EAA0E;IAC1E,8EAA8E;IAC9E,0EAA0E;IAC1E,qEAAqE;IACrE,2EAA2E;IAC3E,EAAE;IACF,gCAAgC;IAChC,sEAAsE;IACtE,4EAA4E;IAC5E,sEAAsE;IACtE,4EAA4E;IAC5E,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,EAAE;IACF,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,mEAAmE;IACnE,0EAA0E;IAC1E,qEAAqE;IACrE,EAAE;IACF,0DAA0D;IAC1D,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,uCAAuC;IACvC,EAAE;IACF,iBAAiB;IACjB,0EAA0E;IAC1E,uDAAuD;IACvD,oCAAoC;IACpC,6BAA6B;CAC9B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AA8Bb;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,IAAuB,EACvB,OAAO,GAAe,EAAE;;IAExB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC;QACzB,QAAQ;QACR,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAC;IACH,MAAM,GAAG,SAAG,OAAO,CAAC,GAAG,mCAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,GAAG,SAAG,OAAO,CAAC,GAAG,mCAAI,OAAO,CAAC,GAAG,CAAC;IAEvC,IAAI,CAAC;QACH,2EAA2E;QAC3E,oEAAoE;QACpE,wDAAwD;QACxD,IAAI,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;YACtC,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAE7C,+BAA+B;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,6DAA6D;YAC7D,GAAG,CAAC,kBAAkB,CAAC;gBACrB,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,iBAAiB;gBACxB,QAAQ,EAAE,QAAQ;gBAClB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;YACH,MAAM,UAAU,CACd,4BAA4B,IAAI,CAAC,CAAC,CAAC,IAAI,EACvC,eAAe,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAC3D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,kBAAkB,CAAC;gBACrB,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,iBAAiB;gBACxB,KAAK,EAAE,OAAO;gBACd,QAAQ,EAAE,QAAQ;gBAClB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;YACH,MAAM,UAAU,CACd,oBAAoB,OAAO,IAAI,EAC/B,eAAe,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAC3D,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,mCAAmB,YAAY,GAAK,aAAa,CAAC,OAAO,CAAC,CAAE,CAAC;QACxE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAEtC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC/B,OAAO,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,cAAc,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAEpD,IAAI,CAAC,cAAc,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,UAAU,CACd,IAAI,OAAO,yCAAyC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,EAC9E,2DAA2D,OAAO,WAAW,CAC9E,CAAC;QACJ,CAAC;QAED,IAAI,cAAc,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,MAAM,UAAU,CACd,IAAI,OAAO,6CAA6C;gBACtD,GAAG,cAAc,UAAU,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,EACzD,kCAAkC,OAAO,KAAK,cAAc,MAAM;gBAChE,eAAe,OAAO,WAAW,CACpC,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,SAAI,MAAM,CAAC,KAAK,CAAC,GAA0B,mCAAI,GAAG,CAAC;QAE5D,QAAQ,OAAO,EAAE,CAAC;YAChB,KAAK,MAAM;gBACT,OAAO,MAAM,OAAO,CAAC,GAAG,EAAE;oBACxB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAyB;oBAC3C,GAAG;oBACH,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;oBAClC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;oBAC9B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA4B;oBAC/C,UAAU,EAAE,OAAO,CAAC,UAAU;iBAC/B,CAAC,CAAC;YACL,KAAK,UAAU;gBACb,OAAO,MAAM,WAAW,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9C,KAAK,KAAK;gBACR,OAAO,MAAM,MAAM,CAAC,GAAG,EAAE;oBACvB,GAAG;oBACH,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;oBAClC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAA4B;oBAChD,GAAG;iBACJ,CAAC,CAAC;YACL,KAAK,MAAM;gBACT,OAAO,MAAM,OAAO,CAAC,GAAG,EAAE;oBACxB,GAAG;oBACH,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;iBAC/B,CAAC,CAAC;YACL,KAAK,OAAO;gBACV,OAAO,MAAM,QAAQ,CAAC,GAAG,EAAE;oBACzB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;oBAClC,GAAG;oBACH,GAAG;oBACH,UAAU,EAAE,OAAO,CAAC,UAAU;iBAC/B,CAAC,CAAC;YACL,KAAK,QAAQ;gBACX,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE;oBAC1B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;oBAC9B,GAAG;oBACH,GAAG;iBACJ,CAAC,CAAC;YACL,KAAK,QAAQ;gBACX,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE;oBAC1B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG;oBACH,GAAG;iBACJ,CAAC,CAAC;YACL,KAAK,QAAQ;gBACX,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE;oBAC1B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG;oBACH,GAAG;oBACH,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,KAAK,IAAI;oBACxC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;oBAC9B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;oBAClC,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,GAAG,EAAE,OAAO,CAAC,GAAG;iBACjB,CAAC,CAAC;YACL,KAAK,SAAS;gBACZ,OAAO,MAAM,UAAU,CAAC,GAAG,EAAE;oBAC3B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG;oBACH,GAAG;iBACJ,CAAC,CAAC;YACL,KAAK,KAAK;gBACR,OAAO,MAAM,MAAM,CAAC,GAAG,EAAE;oBACvB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG;oBACH,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;oBAC3B,GAAG;iBACJ,CAAC,CAAC;YACL,KAAK,SAAS;gBACZ,OAAO,MAAM,UAAU,CAAC,GAAG,EAAE;oBAC3B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAA4B;oBACjD,GAAG;oBACH,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;oBAC9B,GAAG;oBACH,UAAU,EAAE,OAAO,CAAC,UAAU;oBAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;iBACzB,CAAC,CAAC;YACL,KAAK,SAAS;gBACZ,OAAO,MAAM,UAAU,CAAC,GAAG,EAAE;oBAC3B,GAAG;oBACH,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAA0B;oBAC7C,GAAG;oBACH,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;oBAClC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI;oBAC9B,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK;oBACvC,UAAU,EAAE,OAAO,CAAC,UAAU;oBAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;iBACzB,CAAC,CAAC;YACL;gBACE,kEAAkE;gBAClE,MAAM,UAAU,CACd,oBAAoB,OAAO,IAAI,EAC/B,eAAe,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtC,CAAC;QACN,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY,CAAC,IAAuB;IAC3C,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM;QAC1B,IAAI,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,KAAK,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC;IAC1C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAuB,EAAE,IAAY;IAC7D,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM;QAC1B,IAAI,KAAK,KAAK,KAAK,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;IACzC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,IAAuB;IAI3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACvB,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM;QAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;aAClD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW;IAClB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtE,OAAO,OAAO,MAAM,KAAK,QAAQ;QAC/B,MAAM,KAAK,IAAI;QACf,SAAS,IAAI,MAAM;QACnB,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAClC,CAAC,CAAC,MAAM,CAAC,OAAO;QAChB,CAAC,CAAC,OAAO,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,GAAe;IAClC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAE9B,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,QAAQ,CAAC,GAAe;IAC/B,GAAG,CAAC,kBAAkB,CAAC;QACrB,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,UAAU;KACtB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,eAAe,CAAC,GAAe,EAAE,OAAe;;IACvD,MAAM,KAAK,SAAG,aAAa,CAAC,OAAO,CAAC,mCAAI,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,iBAAiB,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/D,GAAG,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take a positional argument, and what it means. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, string> = {\n use: 'source-slug',\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against an Ekanos host in a browser (--host <url>)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to an Ekanos host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalName = POSITIONAL_COMMANDS[command];\n\n if (!positionalName && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (positionalName && parsed.positionals.length > 1) {\n throw usageError(\n `\"${command}\" takes at most one positional argument (a ` +\n `${positionalName}), got ${parsed.positionals.length}.`,\n `Pass exactly one, e.g. \"ekanos ${command} <${positionalName}>\". ` +\n `See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed({ usage: USAGE, commands: COMMANDS }, USAGE);\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-manifest.js","sourceRoot":"","sources":["../src/merge-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAwBH,MAAM,aAAa,GAAG,CAAC,cAAc,EAAE,iBAAiB,EAAE,SAAS,CAAU,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAsC;IAC1D,cAAc,EAAE,CAAC,2CAA2C,CAAC;CAC9D,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,aAAa,GAAG,QAAQ,CAAC;AAE/B,SAAS,aAAa,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;;IAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"merge-manifest.js","sourceRoot":"","sources":["../src/merge-manifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Merging the scaffold's package.json into one a partner already has.\n *\n * The ordinary way to start a project is `npm init -y`, and doing that before\n * `ekanos init` used to cost the partner every dependency, every script, and\n * leave `\"type\": \"commonjs\"` under ESM/TypeScript sources — reported only as\n * `skipped package.json (already exists)`. A skip that expensive has to either\n * do the work or say precisely what it did not do.\n *\n * The rule here is ADDITIVE ONLY: a value the partner already set is never\n * changed, because their pinned version or their own `test` script is a\n * decision, not an omission. Anything we would have set but cannot becomes a\n * note carrying its own remediation.\n */\n\nexport interface Manifest {\n [key: string]: unknown;\n}\n\nexport interface ManifestNote {\n field: string;\n message: string;\n hint: string;\n}\n\nexport interface MergeResult {\n manifest: Manifest;\n /** Dotted paths this merge added, e.g. `dependencies.@ekanos/sdk`. */\n added: string[];\n /** Fields left alone because the partner had already set them. */\n kept: string[];\n /** Things a partner must decide or do themselves. */\n notes: ManifestNote[];\n /** False when nothing changed, so the caller can skip the write entirely. */\n changed: boolean;\n}\n\nconst RECORD_FIELDS = ['dependencies', 'devDependencies', 'scripts'] as const;\n\n/**\n * Values `npm init -y` writes that are placeholders, not decisions.\n *\n * Treating npm's stub `test` script as a real choice left the ordinary\n * `npm init -y` → `ekanos init` path with a `test` script that exits 1, so\n * `ekanos test` — the second command a partner runs — failed on a fresh\n * scaffold. This is the one narrow exception to additive-only, and it is safe\n * because the string is npm's own fixed literal, not something a partner types.\n */\nconst NPM_PLACEHOLDERS: Record<string, readonly string[]> = {\n 'scripts.test': ['echo \"Error: no test specified\" && exit 1'],\n};\n\n/**\n * `npm init -y` writes `\"type\": \"commonjs\"` EXPLICITLY (npm 11.6.2), so\n * \"only set it when absent\" never fires for the ordinary way a partner starts\n * a project — it is the mainline, not an edge case.\n *\n * It is npm boilerplate by exactly the same argument as the stub `test` script\n * above, and treating one as boilerplate and the other as a considered choice\n * was inconsistent. The decisive point: the files this scaffolder writes are\n * ESM TypeScript and cannot load under `commonjs`, so leaving it would mean\n * writing files that do not run. A scaffolder that does that has not\n * scaffolded anything.\n *\n * So it is overwritten, loudly — with a note saying what changed and what to\n * do if the project really does have CommonJS sources.\n */\nconst REQUIRED_TYPE = 'module';\n\nfunction isPlaceholder(field: string, name: string, value: string): boolean {\n return (NPM_PLACEHOLDERS[`${field}.${name}`] ?? []).includes(value);\n}\n\nfunction asRecord(value: unknown): Record<string, string> {\n if (typeof value !== 'object' || value === null) return {};\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry === 'string') out[key] = entry;\n }\n return out;\n}\n\n/**\n * Merge `template` into `existing`, adding only what is missing.\n *\n * `type` is the one field that cannot be resolved additively: the scaffold's\n * sources are ESM TypeScript, so they need `\"type\": \"module\"`, but flipping an\n * explicit `\"commonjs\"` could break code the partner already has. Absent, we\n * set it; set to anything else, we leave it and say what will break.\n */\nexport function mergeManifest(\n existing: Manifest,\n template: Manifest,\n): MergeResult {\n const manifest: Manifest = { ...existing };\n const added: string[] = [];\n const kept: string[] = [];\n const notes: ManifestNote[] = [];\n\n for (const field of RECORD_FIELDS) {\n const wanted = asRecord(template[field]);\n if (Object.keys(wanted).length === 0) continue;\n\n const current = asRecord(existing[field]);\n const merged = { ...current };\n let replacedPlaceholder = false;\n for (const [name, value] of Object.entries(wanted)) {\n const existingValue = current[name];\n if (existingValue !== undefined) {\n if (!isPlaceholder(field, name, existingValue)) {\n kept.push(`${field}.${name}`);\n continue;\n }\n replacedPlaceholder = true;\n }\n merged[name] = value;\n added.push(`${field}.${name}`);\n }\n\n if (\n replacedPlaceholder ||\n Object.keys(merged).length > Object.keys(current).length\n ) {\n manifest[field] = sortKeys(merged);\n }\n }\n\n const existingType = existing.type;\n if (existingType === REQUIRED_TYPE) {\n kept.push('type');\n } else {\n manifest.type = REQUIRED_TYPE;\n added.push('type');\n\n if (existingType !== undefined) {\n notes.push({\n field: 'type',\n message:\n `Changed \"type\" from ${JSON.stringify(existingType)} to \"module\" in ` +\n 'package.json. The scaffolded sources are ES modules and Node ' +\n 'cannot load them otherwise, so leaving it would have written files ' +\n 'that do not run.',\n hint:\n 'If this project has CommonJS sources of its own, revert that line ' +\n 'and put your integration in its own package instead — the two ' +\n 'module systems cannot share one package.json.',\n });\n }\n }\n\n return { manifest, added, kept, notes, changed: added.length > 0 };\n}\n\nfunction sortKeys(record: Record<string, string>): Record<string, string> {\n return Object.fromEntries(\n Object.entries(record).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n}\n\n/** Serialize a manifest the way npm does — 2-space indent, trailing newline. */\nexport function serializeManifest(manifest: Manifest): string {\n return `${JSON.stringify(manifest, null, 2)}\\n`;\n}\n"]}
|
package/dist/pack.js
CHANGED
|
@@ -8,7 +8,7 @@ import { VITEST_CONFIG_NAMES } from './project-checks.js';
|
|
|
8
8
|
*
|
|
9
9
|
* A minimal ustar writer over Node's built-ins — no `tar` dependency. The
|
|
10
10
|
* selection is deliberately a WHITELIST, not "everything minus a blocklist":
|
|
11
|
-
* the submission is reviewed by an operator and installed into
|
|
11
|
+
* the submission is reviewed by an operator and installed into Ekanos, so the
|
|
12
12
|
* archive must contain exactly the files the toolchain knows about and
|
|
13
13
|
* nothing a working directory happens to hold.
|
|
14
14
|
*
|
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 { 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 Fusion, 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 * - 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` and\n * `checks/tests` 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) {\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":"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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 { 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 * - 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` and\n * `checks/tests` 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) {\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"]}
|