@ekanos/cli 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -8
- package/dist/auth/ekanos-api.d.ts +23 -0
- package/dist/auth/ekanos-api.js +33 -6
- package/dist/auth/ekanos-api.js.map +1 -1
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +118 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/agents.js +3 -2
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apps.d.ts +11 -3
- package/dist/commands/apps.js +86 -28
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/audiences.d.ts +31 -0
- package/dist/commands/audiences.js +239 -0
- package/dist/commands/audiences.js.map +1 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.js +41 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/catalog.d.ts +4 -2
- package/dist/commands/catalog.js +166 -35
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +43 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/codegen.d.ts +2 -0
- package/dist/commands/codegen.js +49 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/contract.d.ts +52 -0
- package/dist/commands/contract.js +300 -0
- package/dist/commands/contract.js.map +1 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.js +186 -12
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/index-experience.d.ts +31 -0
- package/dist/commands/index-experience.js +21 -0
- package/dist/commands/index-experience.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +17 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.js +39 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/publish.d.ts +59 -0
- package/dist/commands/publish.js +118 -65
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/runs.d.ts +7 -3
- package/dist/commands/runs.js +21 -5
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/scope-notice.d.ts +13 -0
- package/dist/commands/scope-notice.js +12 -4
- package/dist/commands/scope-notice.js.map +1 -1
- package/dist/commands/sources-roles.d.ts +5 -0
- package/dist/commands/sources-roles.js +235 -0
- package/dist/commands/sources-roles.js.map +1 -0
- package/dist/commands/sources.d.ts +5 -0
- package/dist/commands/sources.js +8 -1
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +150 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/team-agent-readiness.d.ts +18 -2
- package/dist/commands/team-agent-readiness.js +39 -39
- package/dist/commands/team-agent-readiness.js.map +1 -1
- package/dist/commands/team-agents-agents.d.ts +5 -2
- package/dist/commands/team-agents-agents.js +152 -15
- package/dist/commands/team-agents-agents.js.map +1 -1
- package/dist/commands/team-agents-define.d.ts +31 -0
- package/dist/commands/team-agents-define.js +176 -0
- package/dist/commands/team-agents-define.js.map +1 -0
- package/dist/commands/team-agents-mcp.js +112 -10
- package/dist/commands/team-agents-mcp.js.map +1 -1
- package/dist/commands/team-agents-triggers.d.ts +1 -1
- package/dist/commands/team-agents-triggers.js +137 -5
- package/dist/commands/team-agents-triggers.js.map +1 -1
- package/dist/commands/team-agents.d.ts +4 -1
- package/dist/commands/team-agents.js +17 -4
- package/dist/commands/team-agents.js.map +1 -1
- package/dist/commands/team-llm-key.d.ts +14 -0
- package/dist/commands/team-llm-key.js +122 -0
- package/dist/commands/team-llm-key.js.map +1 -0
- package/dist/commands/teams-audience.d.ts +12 -0
- package/dist/commands/teams-audience.js +144 -0
- package/dist/commands/teams-audience.js.map +1 -0
- package/dist/commands/teams-connect.js +123 -31
- package/dist/commands/teams-connect.js.map +1 -1
- package/dist/commands/teams-lifecycle.d.ts +90 -0
- package/dist/commands/teams-lifecycle.js +457 -0
- package/dist/commands/teams-lifecycle.js.map +1 -0
- package/dist/commands/teams-onboard.d.ts +14 -0
- package/dist/commands/teams-onboard.js +62 -13
- package/dist/commands/teams-onboard.js.map +1 -1
- package/dist/commands/teams-overview.js +6 -2
- package/dist/commands/teams-overview.js.map +1 -1
- package/dist/commands/teams-scope.js +4 -0
- package/dist/commands/teams-scope.js.map +1 -1
- package/dist/commands/teams.d.ts +25 -5
- package/dist/commands/teams.js +106 -39
- package/dist/commands/teams.js.map +1 -1
- package/dist/commands/workflows.d.ts +37 -0
- package/dist/commands/workflows.js +589 -0
- package/dist/commands/workflows.js.map +1 -0
- package/dist/compatibility.d.ts +10 -1
- package/dist/compatibility.js +10 -1
- package/dist/compatibility.js.map +1 -1
- package/dist/connect-fields.d.ts +22 -0
- package/dist/connect-fields.js +115 -0
- package/dist/connect-fields.js.map +1 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +32 -9
- package/dist/context.js.map +1 -1
- package/dist/dev-experience.d.ts +125 -0
- package/dist/dev-experience.js +128 -0
- package/dist/dev-experience.js.map +1 -0
- package/dist/experience/boundary.d.ts +12 -0
- package/dist/experience/boundary.js +260 -0
- package/dist/experience/boundary.js.map +1 -0
- package/dist/experience/build.d.ts +16 -0
- package/dist/experience/build.js +90 -0
- package/dist/experience/build.js.map +1 -0
- package/dist/experience/checks.d.ts +29 -0
- package/dist/experience/checks.js +133 -0
- package/dist/experience/checks.js.map +1 -0
- package/dist/experience/contract-sources.d.ts +36 -0
- package/dist/experience/contract-sources.js +111 -0
- package/dist/experience/contract-sources.js.map +1 -0
- package/dist/experience/definition-checks.d.ts +26 -0
- package/dist/experience/definition-checks.js +201 -0
- package/dist/experience/definition-checks.js.map +1 -0
- package/dist/experience/definition-shape.d.ts +60 -0
- package/dist/experience/definition-shape.js +2 -0
- package/dist/experience/definition-shape.js.map +1 -0
- package/dist/experience/init.d.ts +10 -0
- package/dist/experience/init.js +64 -0
- package/dist/experience/init.js.map +1 -0
- package/dist/experience/integrations-codegen.d.ts +17 -0
- package/dist/experience/integrations-codegen.js +51 -0
- package/dist/experience/integrations-codegen.js.map +1 -0
- package/dist/experience/json-schema-to-ts.d.ts +1 -0
- package/dist/experience/json-schema-to-ts.js +90 -0
- package/dist/experience/json-schema-to-ts.js.map +1 -0
- package/dist/experience/load-experience.d.ts +23 -0
- package/dist/experience/load-experience.js +115 -0
- package/dist/experience/load-experience.js.map +1 -0
- package/dist/experience/overrides.d.ts +8 -0
- package/dist/experience/overrides.js +21 -0
- package/dist/experience/overrides.js.map +1 -0
- package/dist/experience/pin-integration.d.ts +8 -0
- package/dist/experience/pin-integration.js +17 -0
- package/dist/experience/pin-integration.js.map +1 -0
- package/dist/experience/pin-ops.d.ts +12 -0
- package/dist/experience/pin-ops.js +36 -0
- package/dist/experience/pin-ops.js.map +1 -0
- package/dist/experience/pins.d.ts +35 -0
- package/dist/experience/pins.js +91 -0
- package/dist/experience/pins.js.map +1 -0
- package/dist/experience/project.d.ts +52 -0
- package/dist/experience/project.js +149 -0
- package/dist/experience/project.js.map +1 -0
- package/dist/experience/publish.d.ts +51 -0
- package/dist/experience/publish.js +144 -0
- package/dist/experience/publish.js.map +1 -0
- package/dist/experience/regenerate.d.ts +10 -0
- package/dist/experience/regenerate.js +33 -0
- package/dist/experience/regenerate.js.map +1 -0
- package/dist/experience/report.d.ts +9 -0
- package/dist/experience/report.js +23 -0
- package/dist/experience/report.js.map +1 -0
- package/dist/harness-scaffold.d.ts +52 -18
- package/dist/harness-scaffold.js +189 -9
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +494 -127
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +31 -0
- package/dist/invocation.js +58 -0
- package/dist/invocation.js.map +1 -0
- package/dist/project-checks.d.ts +12 -0
- package/dist/project-checks.js +46 -0
- package/dist/project-checks.js.map +1 -1
- package/dist/project.d.ts +14 -0
- package/dist/project.js +10 -0
- package/dist/project.js.map +1 -1
- package/dist/repo/publish.d.ts +42 -0
- package/dist/repo/publish.js +225 -0
- package/dist/repo/publish.js.map +1 -0
- package/dist/repo/scaffold.d.ts +30 -0
- package/dist/repo/scaffold.js +165 -0
- package/dist/repo/scaffold.js.map +1 -0
- package/dist/runs-api.js +8 -3
- package/dist/runs-api.js.map +1 -1
- package/dist/safe-fs.d.ts +18 -0
- package/dist/safe-fs.js +57 -0
- package/dist/safe-fs.js.map +1 -1
- package/dist/secret-form.d.ts +48 -0
- package/dist/secret-form.js +212 -0
- package/dist/secret-form.js.map +1 -0
- package/dist/secret-input.d.ts +96 -0
- package/dist/secret-input.js +299 -0
- package/dist/secret-input.js.map +1 -0
- package/dist/team-connect-api.d.ts +78 -3
- package/dist/team-connect-api.js +17 -2
- package/dist/team-connect-api.js.map +1 -1
- package/dist/team-lifecycle-api.d.ts +435 -0
- package/dist/team-lifecycle-api.js +301 -0
- package/dist/team-lifecycle-api.js.map +1 -0
- package/dist/teams-api.d.ts +49 -1
- package/dist/teams-api.js +149 -27
- package/dist/teams-api.js.map +1 -1
- package/dist/unknown-command.d.ts +18 -0
- package/dist/unknown-command.js +50 -0
- package/dist/unknown-command.js.map +1 -0
- package/dist/validate-findings.js +2 -1
- package/dist/validate-findings.js.map +1 -1
- package/dist/workflows-api.d.ts +69 -0
- package/dist/workflows-api.js +182 -0
- package/dist/workflows-api.js.map +1 -0
- package/package.json +3 -3
- package/templates/AGENTS.md.tmpl +6 -4
- package/templates/claude-skill.md.tmpl +4 -1
- package/templates/ekanos.json.tmpl +1 -0
- package/templates/experience/AGENTS.md.tmpl +22 -0
- package/templates/experience/ekanos.json.tmpl +7 -0
- package/templates/experience/ekanos.lock.json.tmpl +4 -0
- package/templates/experience/eslint.config.mjs.tmpl +25 -0
- package/templates/experience/gitignore.tmpl +17 -0
- package/templates/experience/package.json.tmpl +41 -0
- package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
- package/templates/experience/src/experience.ts.tmpl +30 -0
- package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
- package/templates/experience/tsconfig.json.tmpl +20 -0
- package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
- package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
- package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
- package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
- package/templates/harness/app/experience/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
- package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
- package/templates/harness/app/page.experience.tsx.tmpl +5 -0
- package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
- package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
- package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
- package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
- package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
- package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
- package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
- package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
- package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
- package/templates/harness/styles/globals.css.tmpl +15 -1
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +43 -10
- package/templates/skills/build-agent/references/agent-json.md +4 -1
- package/templates/skills/build-agent/references/app-setup.md +14 -7
- package/templates/skills/build-agent/references/rollout.md +26 -2
- package/templates/skills/build-agent/references/set-up-an-integration.md +46 -29
- package/templates/skills/build-integration/references/widget-design.md +1 -1
- package/templates/skills/ekanos-platform/SKILL.md +66 -14
- package/templates/skills/polish-widgets/references/patterns.md +3 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ExperienceManifestSchema, } from '@ekanos/integration-schema';
|
|
2
|
+
import { build as esbuild } from 'esbuild';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { preconditionError } from '../errors.js';
|
|
6
|
+
import { ALLOWED_EXTERNALS } from './boundary.js';
|
|
7
|
+
import { buildManifest } from './definition-checks.js';
|
|
8
|
+
import { formatJson, writeIfChanged } from './project.js';
|
|
9
|
+
export const DIST_ENTRY = 'dist/experience.js';
|
|
10
|
+
export const DIST_TYPES = 'dist/experience.d.ts';
|
|
11
|
+
export const MANIFEST_FILE = 'manifest.json';
|
|
12
|
+
function readPackageJson(project) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(path.join(project.dir, 'package.json'), 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
catch (_a) {
|
|
17
|
+
throw preconditionError('package.json is missing or unreadable.', 'An experience is an npm package; run "ekanos init --kind experience" or add a package.json.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function sdkVersion(project, declared) {
|
|
21
|
+
var _a;
|
|
22
|
+
try {
|
|
23
|
+
const installed = JSON.parse(fs.readFileSync(path.join(project.dir, 'node_modules', '@ekanos', 'sdk', 'package.json'), 'utf8'));
|
|
24
|
+
if (installed.version)
|
|
25
|
+
return installed.version;
|
|
26
|
+
}
|
|
27
|
+
catch (_b) {
|
|
28
|
+
// not installed: fall back to the declared range
|
|
29
|
+
}
|
|
30
|
+
return ((_a = declared === null || declared === void 0 ? void 0 : declared['@ekanos/sdk']) !== null && _a !== void 0 ? _a : 'unknown').replace(/^[\^~]/, '');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Emit `dist/experience.js` (browser ESM, runtime packages left external),
|
|
34
|
+
* `dist/experience.d.ts` and `manifest.json`. Assumes the checks passed.
|
|
35
|
+
*/
|
|
36
|
+
export async function emitExperience(project, result) {
|
|
37
|
+
var _a;
|
|
38
|
+
var _b, _c, _d;
|
|
39
|
+
const loaded = result.loaded;
|
|
40
|
+
if (!loaded) {
|
|
41
|
+
throw preconditionError('The experience definition was not loaded.', 'Run "ekanos check" to see why.');
|
|
42
|
+
}
|
|
43
|
+
const pkg = readPackageJson(project);
|
|
44
|
+
const version = (_b = pkg.version) !== null && _b !== void 0 ? _b : '';
|
|
45
|
+
if (!/^\d+\.\d+\.\d+/.test(version)) {
|
|
46
|
+
throw preconditionError(`package.json "version" is "${version}", not semver.`, 'Set a version such as 0.1.0.');
|
|
47
|
+
}
|
|
48
|
+
const manifest = buildManifest({
|
|
49
|
+
project,
|
|
50
|
+
definition: loaded.definition,
|
|
51
|
+
pinned: result.pinned,
|
|
52
|
+
workflows: result.workflows,
|
|
53
|
+
version,
|
|
54
|
+
sdk: sdkVersion(project, pkg.dependencies),
|
|
55
|
+
});
|
|
56
|
+
const parsed = ExperienceManifestSchema.safeParse(manifest);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
throw preconditionError(`The generated manifest is invalid: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`, 'This is a bug in @ekanos/cli or an unsupported definition; report it.');
|
|
59
|
+
}
|
|
60
|
+
const bundle = await esbuild({
|
|
61
|
+
entryPoints: [path.join(project.dir, project.entry)],
|
|
62
|
+
bundle: true,
|
|
63
|
+
write: false,
|
|
64
|
+
format: 'esm',
|
|
65
|
+
platform: 'browser',
|
|
66
|
+
jsx: 'automatic',
|
|
67
|
+
logLevel: 'silent',
|
|
68
|
+
absWorkingDir: project.dir,
|
|
69
|
+
external: [
|
|
70
|
+
...ALLOWED_EXTERNALS,
|
|
71
|
+
...((_c = project.ekanos.allowImports) !== null && _c !== void 0 ? _c : []),
|
|
72
|
+
].flatMap((n) => [n, `${n}/*`]),
|
|
73
|
+
loader: { '.css': 'empty', '.svg': 'dataurl', '.png': 'dataurl' },
|
|
74
|
+
});
|
|
75
|
+
const js = (_d = (_a = bundle.outputFiles[0]) === null || _a === void 0 ? void 0 : _a.text) !== null && _d !== void 0 ? _d : '';
|
|
76
|
+
const dts = "import type { ExperienceDefinition } from '@ekanos/sdk/experience';\n" +
|
|
77
|
+
'declare const experience: ExperienceDefinition;\n' +
|
|
78
|
+
'export default experience;\n';
|
|
79
|
+
const changed = [
|
|
80
|
+
writeIfChanged(path.join(project.dir, DIST_ENTRY), js),
|
|
81
|
+
writeIfChanged(path.join(project.dir, DIST_TYPES), dts),
|
|
82
|
+
writeIfChanged(path.join(project.dir, MANIFEST_FILE), formatJson(manifest)),
|
|
83
|
+
].some(Boolean);
|
|
84
|
+
return {
|
|
85
|
+
manifest,
|
|
86
|
+
files: [DIST_ENTRY, DIST_TYPES, MANIFEST_FILE],
|
|
87
|
+
changed,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/experience/build.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ExperienceManifest,\n ExperienceManifestSchema,\n} from '@ekanos/integration-schema';\nimport { build as esbuild } from 'esbuild';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { preconditionError } from '../errors';\nimport { ALLOWED_EXTERNALS } from './boundary';\nimport type { ExperienceCheckResult } from './checks';\nimport { buildManifest } from './definition-checks';\nimport { type ExperienceProject, formatJson, writeIfChanged } from './project';\n\nexport const DIST_ENTRY = 'dist/experience.js';\nexport const DIST_TYPES = 'dist/experience.d.ts';\nexport const MANIFEST_FILE = 'manifest.json';\n\nfunction readPackageJson(project: ExperienceProject): {\n version?: string;\n dependencies?: Record<string, string>;\n} {\n try {\n return JSON.parse(\n fs.readFileSync(path.join(project.dir, 'package.json'), 'utf8'),\n ) as { version?: string; dependencies?: Record<string, string> };\n } catch {\n throw preconditionError(\n 'package.json is missing or unreadable.',\n 'An experience is an npm package; run \"ekanos init --kind experience\" or add a package.json.',\n );\n }\n}\n\nfunction sdkVersion(\n project: ExperienceProject,\n declared: Record<string, string> | undefined,\n): string {\n try {\n const installed = JSON.parse(\n fs.readFileSync(\n path.join(\n project.dir,\n 'node_modules',\n '@ekanos',\n 'sdk',\n 'package.json',\n ),\n 'utf8',\n ),\n ) as { version?: string };\n if (installed.version) return installed.version;\n } catch {\n // not installed: fall back to the declared range\n }\n\n return (declared?.['@ekanos/sdk'] ?? 'unknown').replace(/^[\\^~]/, '');\n}\n\nexport interface BuildOutput {\n manifest: ExperienceManifest;\n files: string[];\n changed: boolean;\n}\n\n/**\n * Emit `dist/experience.js` (browser ESM, runtime packages left external),\n * `dist/experience.d.ts` and `manifest.json`. Assumes the checks passed.\n */\nexport async function emitExperience(\n project: ExperienceProject,\n result: ExperienceCheckResult,\n): Promise<BuildOutput> {\n const loaded = result.loaded;\n if (!loaded) {\n throw preconditionError(\n 'The experience definition was not loaded.',\n 'Run \"ekanos check\" to see why.',\n );\n }\n const pkg = readPackageJson(project);\n const version = pkg.version ?? '';\n if (!/^\\d+\\.\\d+\\.\\d+/.test(version)) {\n throw preconditionError(\n `package.json \"version\" is \"${version}\", not semver.`,\n 'Set a version such as 0.1.0.',\n );\n }\n\n const manifest = buildManifest({\n project,\n definition: loaded.definition,\n pinned: result.pinned,\n workflows: result.workflows,\n version,\n sdk: sdkVersion(project, pkg.dependencies),\n });\n const parsed = ExperienceManifestSchema.safeParse(manifest);\n if (!parsed.success) {\n throw preconditionError(\n `The generated manifest is invalid: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'This is a bug in @ekanos/cli or an unsupported definition; report it.',\n );\n }\n\n const bundle = await esbuild({\n entryPoints: [path.join(project.dir, project.entry)],\n bundle: true,\n write: false,\n format: 'esm',\n platform: 'browser',\n jsx: 'automatic',\n logLevel: 'silent',\n absWorkingDir: project.dir,\n external: [\n ...ALLOWED_EXTERNALS,\n ...(project.ekanos.allowImports ?? []),\n ].flatMap((n) => [n, `${n}/*`]),\n loader: { '.css': 'empty', '.svg': 'dataurl', '.png': 'dataurl' },\n });\n const js = bundle.outputFiles[0]?.text ?? '';\n\n const dts =\n \"import type { ExperienceDefinition } from '@ekanos/sdk/experience';\\n\" +\n 'declare const experience: ExperienceDefinition;\\n' +\n 'export default experience;\\n';\n\n const changed = [\n writeIfChanged(path.join(project.dir, DIST_ENTRY), js),\n writeIfChanged(path.join(project.dir, DIST_TYPES), dts),\n writeIfChanged(path.join(project.dir, MANIFEST_FILE), formatJson(manifest)),\n ].some(Boolean);\n\n return {\n manifest,\n files: [DIST_ENTRY, DIST_TYPES, MANIFEST_FILE],\n changed,\n };\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Finding, WorkflowTemplate } from '@ekanos/integration-schema';
|
|
2
|
+
import type { LoadedExperienceModule } from './definition-shape.js';
|
|
3
|
+
import { type PinnedIntegration, type PinsResolver } from './pins.js';
|
|
4
|
+
import { type ExperienceProject } from './project.js';
|
|
5
|
+
export declare const GENERATED_TYPES_CHECK = "checks/generated-types";
|
|
6
|
+
export declare const LOCAL_OVERRIDES_CHECK = "checks/local-overrides";
|
|
7
|
+
export declare const TYPECHECK_CHECK = "checks/typecheck";
|
|
8
|
+
export interface ExperienceCheckOptions {
|
|
9
|
+
typecheck: boolean;
|
|
10
|
+
/** Skip loading the entry (used by commands that only need the static stages). */
|
|
11
|
+
loadDefinition: boolean;
|
|
12
|
+
inlineSdk?: string;
|
|
13
|
+
/** The harness's pins resolver (`harnessPinsResolver`); absent = committed snapshots only. */
|
|
14
|
+
pinsResolver?: PinsResolver | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ExperienceCheckResult {
|
|
17
|
+
findings: Finding[];
|
|
18
|
+
pinned: PinnedIntegration[];
|
|
19
|
+
loaded?: LoadedExperienceModule;
|
|
20
|
+
workflows: WorkflowTemplate[];
|
|
21
|
+
}
|
|
22
|
+
/** The dts the pinned contracts generate: shared by `codegen` and the freshness check. */
|
|
23
|
+
export declare function expectedIntegrationsDts(pinned: readonly PinnedIntegration[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* The experience gate, in dependency order. Stages that need an earlier stage's
|
|
26
|
+
* output are skipped when it failed, so one root cause yields one finding, not
|
|
27
|
+
* a cascade.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runExperienceChecks(project: ExperienceProject, options: ExperienceCheckOptions): Promise<ExperienceCheckResult>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { checkExperienceBoundary } from './boundary.js';
|
|
5
|
+
import { analyzeDefinition, checkFixtures, } from './definition-checks.js';
|
|
6
|
+
import { generateIntegrationsDts } from './integrations-codegen.js';
|
|
7
|
+
import { loadExperience } from './load-experience.js';
|
|
8
|
+
import { isOverridesFileTracked } from './overrides.js';
|
|
9
|
+
import { checkIntegrationPins, } from './pins.js';
|
|
10
|
+
import { LOCAL_OVERRIDES_FILE, INTEGRATIONS_DTS, LEGACY_SLOTS_DTS, readLockfile, } from './project.js';
|
|
11
|
+
export const GENERATED_TYPES_CHECK = 'checks/generated-types';
|
|
12
|
+
export const LOCAL_OVERRIDES_CHECK = 'checks/local-overrides';
|
|
13
|
+
export const TYPECHECK_CHECK = 'checks/typecheck';
|
|
14
|
+
/** The dts the pinned contracts generate: shared by `codegen` and the freshness check. */
|
|
15
|
+
export function expectedIntegrationsDts(pinned) {
|
|
16
|
+
return generateIntegrationsDts(pinned.map(({ use, contract }) => ({
|
|
17
|
+
integration: use.name,
|
|
18
|
+
contract,
|
|
19
|
+
operations: use.operations
|
|
20
|
+
.map((id) => { var _a; return (_a = /\/((?:tool|action)\/.+)$/.exec(id)) === null || _a === void 0 ? void 0 : _a[1]; })
|
|
21
|
+
.filter((key) => key !== undefined),
|
|
22
|
+
})));
|
|
23
|
+
}
|
|
24
|
+
function checkGeneratedTypes(project, pinned) {
|
|
25
|
+
const file = path.join(project.dir, INTEGRATIONS_DTS);
|
|
26
|
+
const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
27
|
+
const findings = [];
|
|
28
|
+
if (fs.existsSync(path.join(project.dir, LEGACY_SLOTS_DTS))) {
|
|
29
|
+
findings.push({
|
|
30
|
+
check: GENERATED_TYPES_CHECK,
|
|
31
|
+
severity: 'warn',
|
|
32
|
+
message: `${LEGACY_SLOTS_DTS} is the previous name of ${INTEGRATIONS_DTS}.`,
|
|
33
|
+
hint: `Delete it and list ${INTEGRATIONS_DTS} in tsconfig.json "include"; until then "ekanos sync" keeps it as a re-export.`,
|
|
34
|
+
file: LEGACY_SLOTS_DTS,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (current === expectedIntegrationsDts(pinned))
|
|
38
|
+
return findings;
|
|
39
|
+
return [
|
|
40
|
+
...findings,
|
|
41
|
+
{
|
|
42
|
+
check: GENERATED_TYPES_CHECK,
|
|
43
|
+
severity: 'error',
|
|
44
|
+
message: `${INTEGRATIONS_DTS} is ${current === null ? 'missing' : 'out of date'} for the pinned contracts.`,
|
|
45
|
+
hint: 'Run "ekanos codegen" and commit the result.',
|
|
46
|
+
file: INTEGRATIONS_DTS,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function runTypecheck(project) {
|
|
51
|
+
const tsc = path.join(project.dir, 'node_modules', '.bin', 'tsc');
|
|
52
|
+
if (!fs.existsSync(tsc) ||
|
|
53
|
+
!fs.existsSync(path.join(project.dir, 'tsconfig.json'))) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
check: TYPECHECK_CHECK,
|
|
57
|
+
severity: 'warn',
|
|
58
|
+
message: 'typecheck skipped: no local TypeScript or tsconfig.json.',
|
|
59
|
+
hint: 'Install typescript and keep a tsconfig.json to type-check against the generated integrations.',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
const result = spawnSync(tsc, ['--noEmit', '-p', 'tsconfig.json'], {
|
|
64
|
+
cwd: project.dir,
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
});
|
|
67
|
+
if (result.status === 0)
|
|
68
|
+
return [];
|
|
69
|
+
const lines = `${result.stdout}${result.stderr}`
|
|
70
|
+
.split('\n')
|
|
71
|
+
.filter((l) => l.trim() !== '');
|
|
72
|
+
return lines.slice(0, 20).map((line) => {
|
|
73
|
+
const m = /^(.+?)\((\d+),\d+\): (.*)$/.exec(line);
|
|
74
|
+
return Object.assign({ check: TYPECHECK_CHECK, severity: 'error', message: m ? m[3] : line, hint: 'Fix the type error; it is what the generated integration types caught.' }, (m ? { file: m[1], line: Number(m[2]) } : {}));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The experience gate, in dependency order. Stages that need an earlier stage's
|
|
79
|
+
* output are skipped when it failed, so one root cause yields one finding, not
|
|
80
|
+
* a cascade.
|
|
81
|
+
*/
|
|
82
|
+
export async function runExperienceChecks(project, options) {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
const findings = [];
|
|
85
|
+
const lock = readLockfile(project);
|
|
86
|
+
const pins = checkIntegrationPins(project, lock, (_a = options.pinsResolver) !== null && _a !== void 0 ? _a : undefined);
|
|
87
|
+
findings.push(...pins.findings);
|
|
88
|
+
if (isOverridesFileTracked(project)) {
|
|
89
|
+
findings.push({
|
|
90
|
+
check: LOCAL_OVERRIDES_CHECK,
|
|
91
|
+
severity: 'error',
|
|
92
|
+
message: `${LOCAL_OVERRIDES_FILE} is tracked by git.`,
|
|
93
|
+
hint: `It is per-developer and must stay untracked: "git rm --cached ${LOCAL_OVERRIDES_FILE}" and add it to .gitignore.`,
|
|
94
|
+
file: LOCAL_OVERRIDES_FILE,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const pinsOk = pins.findings.length === 0;
|
|
98
|
+
if (pinsOk || pins.pinned.length > 0) {
|
|
99
|
+
findings.push(...checkGeneratedTypes(project, pins.pinned));
|
|
100
|
+
}
|
|
101
|
+
findings.push(...(await checkExperienceBoundary(project)));
|
|
102
|
+
findings.push(...checkFixtures(project, pins.pinned));
|
|
103
|
+
let loaded;
|
|
104
|
+
let analysis;
|
|
105
|
+
if (options.loadDefinition && pinsOk) {
|
|
106
|
+
const result = await loadExperience(project, {
|
|
107
|
+
inlineSdk: options.inlineSdk,
|
|
108
|
+
});
|
|
109
|
+
if (result.ok) {
|
|
110
|
+
loaded = result;
|
|
111
|
+
analysis = analyzeDefinition(project, result, pins.pinned);
|
|
112
|
+
findings.push(...analysis.findings);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
findings.push({
|
|
116
|
+
check: 'checks/experience-definition',
|
|
117
|
+
severity: 'error',
|
|
118
|
+
message: result.message,
|
|
119
|
+
hint: 'Fix the entry so it exports defineExperience({...}) as default.',
|
|
120
|
+
file: project.entry,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (options.typecheck)
|
|
125
|
+
findings.push(...runTypecheck(project));
|
|
126
|
+
return {
|
|
127
|
+
findings,
|
|
128
|
+
pinned: pins.pinned,
|
|
129
|
+
loaded,
|
|
130
|
+
workflows: (_b = analysis === null || analysis === void 0 ? void 0 : analysis.workflows) !== null && _b !== void 0 ? _b : [],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/experience/checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding, WorkflowTemplate } from '@ekanos/integration-schema';\nimport { spawnSync } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { checkExperienceBoundary } from './boundary';\nimport {\n type DefinitionAnalysis,\n analyzeDefinition,\n checkFixtures,\n} from './definition-checks';\nimport type { LoadedExperienceModule } from './definition-shape';\nimport { generateIntegrationsDts } from './integrations-codegen';\nimport { loadExperience } from './load-experience';\nimport { isOverridesFileTracked } from './overrides';\nimport {\n type PinnedIntegration,\n type PinsResolver,\n checkIntegrationPins,\n} from './pins';\nimport {\n LOCAL_OVERRIDES_FILE,\n type ExperienceProject,\n INTEGRATIONS_DTS,\n LEGACY_SLOTS_DTS,\n readLockfile,\n} from './project';\n\nexport const GENERATED_TYPES_CHECK = 'checks/generated-types';\nexport const LOCAL_OVERRIDES_CHECK = 'checks/local-overrides';\nexport const TYPECHECK_CHECK = 'checks/typecheck';\n\nexport interface ExperienceCheckOptions {\n typecheck: boolean;\n /** Skip loading the entry (used by commands that only need the static stages). */\n loadDefinition: boolean;\n inlineSdk?: string;\n /** The harness's pins resolver (`harnessPinsResolver`); absent = committed snapshots only. */\n pinsResolver?: PinsResolver | null;\n}\n\nexport interface ExperienceCheckResult {\n findings: Finding[];\n pinned: PinnedIntegration[];\n loaded?: LoadedExperienceModule;\n workflows: WorkflowTemplate[];\n}\n\n/** The dts the pinned contracts generate: shared by `codegen` and the freshness check. */\nexport function expectedIntegrationsDts(\n pinned: readonly PinnedIntegration[],\n): string {\n return generateIntegrationsDts(\n pinned.map(({ use, contract }) => ({\n integration: use.name,\n contract,\n operations: use.operations\n .map((id) => /\\/((?:tool|action)\\/.+)$/.exec(id)?.[1])\n .filter((key): key is string => key !== undefined),\n })),\n );\n}\n\nfunction checkGeneratedTypes(\n project: ExperienceProject,\n pinned: readonly PinnedIntegration[],\n): Finding[] {\n const file = path.join(project.dir, INTEGRATIONS_DTS);\n const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;\n const findings: Finding[] = [];\n if (fs.existsSync(path.join(project.dir, LEGACY_SLOTS_DTS))) {\n findings.push({\n check: GENERATED_TYPES_CHECK,\n severity: 'warn',\n message: `${LEGACY_SLOTS_DTS} is the previous name of ${INTEGRATIONS_DTS}.`,\n hint: `Delete it and list ${INTEGRATIONS_DTS} in tsconfig.json \"include\"; until then \"ekanos sync\" keeps it as a re-export.`,\n file: LEGACY_SLOTS_DTS,\n });\n }\n if (current === expectedIntegrationsDts(pinned)) return findings;\n\n return [\n ...findings,\n {\n check: GENERATED_TYPES_CHECK,\n severity: 'error',\n message: `${INTEGRATIONS_DTS} is ${current === null ? 'missing' : 'out of date'} for the pinned contracts.`,\n hint: 'Run \"ekanos codegen\" and commit the result.',\n file: INTEGRATIONS_DTS,\n },\n ];\n}\n\nfunction runTypecheck(project: ExperienceProject): Finding[] {\n const tsc = path.join(project.dir, 'node_modules', '.bin', 'tsc');\n if (\n !fs.existsSync(tsc) ||\n !fs.existsSync(path.join(project.dir, 'tsconfig.json'))\n ) {\n return [\n {\n check: TYPECHECK_CHECK,\n severity: 'warn',\n message: 'typecheck skipped: no local TypeScript or tsconfig.json.',\n hint: 'Install typescript and keep a tsconfig.json to type-check against the generated integrations.',\n },\n ];\n }\n const result = spawnSync(tsc, ['--noEmit', '-p', 'tsconfig.json'], {\n cwd: project.dir,\n encoding: 'utf8',\n });\n if (result.status === 0) return [];\n\n const lines = `${result.stdout}${result.stderr}`\n .split('\\n')\n .filter((l) => l.trim() !== '');\n\n return lines.slice(0, 20).map((line) => {\n const m = /^(.+?)\\((\\d+),\\d+\\): (.*)$/.exec(line);\n\n return {\n check: TYPECHECK_CHECK,\n severity: 'error' as const,\n message: m ? (m[3] as string) : line,\n hint: 'Fix the type error; it is what the generated integration types caught.',\n ...(m ? { file: m[1] as string, line: Number(m[2]) } : {}),\n };\n });\n}\n\n/**\n * The experience gate, in dependency order. Stages that need an earlier stage's\n * output are skipped when it failed, so one root cause yields one finding, not\n * a cascade.\n */\nexport async function runExperienceChecks(\n project: ExperienceProject,\n options: ExperienceCheckOptions,\n): Promise<ExperienceCheckResult> {\n const findings: Finding[] = [];\n const lock = readLockfile(project);\n\n const pins = checkIntegrationPins(\n project,\n lock,\n options.pinsResolver ?? undefined,\n );\n findings.push(...pins.findings);\n\n if (isOverridesFileTracked(project)) {\n findings.push({\n check: LOCAL_OVERRIDES_CHECK,\n severity: 'error',\n message: `${LOCAL_OVERRIDES_FILE} is tracked by git.`,\n hint: `It is per-developer and must stay untracked: \"git rm --cached ${LOCAL_OVERRIDES_FILE}\" and add it to .gitignore.`,\n file: LOCAL_OVERRIDES_FILE,\n });\n }\n\n const pinsOk = pins.findings.length === 0;\n if (pinsOk || pins.pinned.length > 0) {\n findings.push(...checkGeneratedTypes(project, pins.pinned));\n }\n\n findings.push(...(await checkExperienceBoundary(project)));\n findings.push(...checkFixtures(project, pins.pinned));\n\n let loaded: LoadedExperienceModule | undefined;\n let analysis: DefinitionAnalysis | undefined;\n if (options.loadDefinition && pinsOk) {\n const result = await loadExperience(project, {\n inlineSdk: options.inlineSdk,\n });\n if (result.ok) {\n loaded = result;\n analysis = analyzeDefinition(project, result, pins.pinned);\n findings.push(...analysis.findings);\n } else {\n findings.push({\n check: 'checks/experience-definition',\n severity: 'error',\n message: result.message,\n hint: 'Fix the entry so it exports defineExperience({...}) as default.',\n file: project.entry,\n });\n }\n }\n\n if (options.typecheck) findings.push(...runTypecheck(project));\n\n return {\n findings,\n pinned: pins.pinned,\n loaded,\n workflows: analysis?.workflows ?? [],\n };\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Contract, compatLine } from '@ekanos/integration-schema';
|
|
2
|
+
import type { ExperienceProject } from './project.js';
|
|
3
|
+
/** Where an integration's contract came from, for messages and `sync` bookkeeping. */
|
|
4
|
+
export interface ResolvedContract {
|
|
5
|
+
contract: Contract;
|
|
6
|
+
origin: string;
|
|
7
|
+
kind: 'dir' | 'path' | 'snapshot';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A pluggable contract source. The dir/path/snapshot sources below are the
|
|
11
|
+
* offline defaults; the harness resolver (catalog, published packages) plugs
|
|
12
|
+
* in here as another source, so `add`/`sync` never learn where contracts live.
|
|
13
|
+
*/
|
|
14
|
+
export interface ContractSource {
|
|
15
|
+
name: string;
|
|
16
|
+
resolve(integration: string, line: string): Promise<ResolvedContract | null> | ResolvedContract | null;
|
|
17
|
+
}
|
|
18
|
+
export interface SourceOptions {
|
|
19
|
+
/** A directory of `*.contract.json` (the monorepo contracts folder or a registry mirror). */
|
|
20
|
+
fromDir?: string;
|
|
21
|
+
/** An integration project directory; its `contract.json` is read. */
|
|
22
|
+
fromPath?: string;
|
|
23
|
+
env?: Record<string, string | undefined>;
|
|
24
|
+
}
|
|
25
|
+
/** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */
|
|
26
|
+
export declare function contractsInDir(dir: string, integration: string, line: string): Contract[];
|
|
27
|
+
export declare function dirSource(dir: string): ContractSource;
|
|
28
|
+
export declare function pathSource(projectPath: string): ContractSource;
|
|
29
|
+
/** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */
|
|
30
|
+
export declare function snapshotFile(project: ExperienceProject, integration: string, line: string): string;
|
|
31
|
+
export declare function snapshotSource(project: ExperienceProject): ContractSource;
|
|
32
|
+
/** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */
|
|
33
|
+
export declare function defaultSources(project: ExperienceProject, options: SourceOptions, extra?: readonly ContractSource[], includeSnapshot?: boolean): ContractSource[];
|
|
34
|
+
export declare function resolveContract(sources: readonly ContractSource[], integration: string, line: string): Promise<ResolvedContract>;
|
|
35
|
+
/** Line for a package version, re-exported so callers need no schema import. */
|
|
36
|
+
export { compatLine };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { compareSemver, compatLine, verifyContract, } from '@ekanos/integration-schema';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { notFoundError, validationError } from '../errors.js';
|
|
5
|
+
function readContractFile(file) {
|
|
6
|
+
let json;
|
|
7
|
+
try {
|
|
8
|
+
json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
catch (_a) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const verified = verifyContract(json);
|
|
14
|
+
if (!verified.contract || verified.problems.length > 0) {
|
|
15
|
+
const first = verified.problems[0];
|
|
16
|
+
throw validationError(`${file} is not a valid contract: ${first ? `${first.rule}: ${first.message}` : 'unreadable'}`, 'Regenerate it with "ekanos contract" in the integration, or point at a valid contracts directory.');
|
|
17
|
+
}
|
|
18
|
+
return verified.contract;
|
|
19
|
+
}
|
|
20
|
+
/** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */
|
|
21
|
+
export function contractsInDir(dir, integration, line) {
|
|
22
|
+
let names;
|
|
23
|
+
try {
|
|
24
|
+
names = fs.readdirSync(dir).filter((n) => n.endsWith('.contract.json'));
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const found = [];
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
if (!name.startsWith(`${integration}@`) &&
|
|
32
|
+
!name.startsWith(`${integration}.`)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const contract = readContractFile(path.join(dir, name));
|
|
36
|
+
if (contract &&
|
|
37
|
+
contract.integration === integration &&
|
|
38
|
+
contract.compatLine === line) {
|
|
39
|
+
found.push(contract);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return found.sort((a, b) => compareSemver(b.contractVersion, a.contractVersion));
|
|
43
|
+
}
|
|
44
|
+
export function dirSource(dir) {
|
|
45
|
+
return {
|
|
46
|
+
name: `dir ${dir}`,
|
|
47
|
+
resolve(integration, line) {
|
|
48
|
+
const [best] = contractsInDir(dir, integration, line);
|
|
49
|
+
return best ? { contract: best, origin: dir, kind: 'dir' } : null;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function pathSource(projectPath) {
|
|
54
|
+
return {
|
|
55
|
+
name: `path ${projectPath}`,
|
|
56
|
+
resolve(integration, line) {
|
|
57
|
+
const file = path.join(path.resolve(projectPath), 'contract.json');
|
|
58
|
+
if (!fs.existsSync(file)) {
|
|
59
|
+
throw notFoundError(`${file} does not exist.`, 'Run "ekanos contract" in that integration to derive its contract.json.');
|
|
60
|
+
}
|
|
61
|
+
const contract = readContractFile(file);
|
|
62
|
+
if (!contract ||
|
|
63
|
+
contract.integration !== integration ||
|
|
64
|
+
contract.compatLine !== line) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return { contract, origin: file, kind: 'path' };
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */
|
|
72
|
+
export function snapshotFile(project, integration, line) {
|
|
73
|
+
return path.join(project.contractsDir, `${integration}@${line}.contract.json`);
|
|
74
|
+
}
|
|
75
|
+
export function snapshotSource(project) {
|
|
76
|
+
return {
|
|
77
|
+
name: 'committed snapshot',
|
|
78
|
+
resolve(integration, line) {
|
|
79
|
+
const file = snapshotFile(project, integration, line);
|
|
80
|
+
const contract = fs.existsSync(file) ? readContractFile(file) : null;
|
|
81
|
+
return contract ? { contract, origin: file, kind: 'snapshot' } : null;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */
|
|
86
|
+
export function defaultSources(project, options, extra = [], includeSnapshot = true) {
|
|
87
|
+
var _a;
|
|
88
|
+
const sources = [];
|
|
89
|
+
if (options.fromPath)
|
|
90
|
+
sources.push(pathSource(options.fromPath));
|
|
91
|
+
if (options.fromDir)
|
|
92
|
+
sources.push(dirSource(path.resolve(options.fromDir)));
|
|
93
|
+
const envDir = (_a = options.env) === null || _a === void 0 ? void 0 : _a.EKANOS_CONTRACTS_DIR;
|
|
94
|
+
if (!options.fromDir && envDir)
|
|
95
|
+
sources.push(dirSource(path.resolve(envDir)));
|
|
96
|
+
sources.push(...extra);
|
|
97
|
+
if (includeSnapshot)
|
|
98
|
+
sources.push(snapshotSource(project));
|
|
99
|
+
return sources;
|
|
100
|
+
}
|
|
101
|
+
export async function resolveContract(sources, integration, line) {
|
|
102
|
+
for (const source of sources) {
|
|
103
|
+
const hit = await source.resolve(integration, line);
|
|
104
|
+
if (hit)
|
|
105
|
+
return hit;
|
|
106
|
+
}
|
|
107
|
+
throw notFoundError(`No contract for ${integration}@${line} (looked in: ${sources.map((s) => s.name).join(', ') || 'nothing'}).`, 'Pass --from-dir <contracts folder> or --from-path <integration project>, or set EKANOS_CONTRACTS_DIR.');
|
|
108
|
+
}
|
|
109
|
+
/** Line for a package version, re-exported so callers need no schema import. */
|
|
110
|
+
export { compatLine };
|
|
111
|
+
//# sourceMappingURL=contract-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-sources.js","sourceRoot":"","sources":["../../src/experience/contract-sources.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Contract,\n compareSemver,\n compatLine,\n verifyContract,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { notFoundError, validationError } from '../errors';\nimport type { ExperienceProject } from './project';\n\n/** Where an integration's contract came from, for messages and `sync` bookkeeping. */\nexport interface ResolvedContract {\n contract: Contract;\n origin: string;\n kind: 'dir' | 'path' | 'snapshot';\n}\n\n/**\n * A pluggable contract source. The dir/path/snapshot sources below are the\n * offline defaults; the harness resolver (catalog, published packages) plugs\n * in here as another source, so `add`/`sync` never learn where contracts live.\n */\nexport interface ContractSource {\n name: string;\n resolve(\n integration: string,\n line: string,\n ): Promise<ResolvedContract | null> | ResolvedContract | null;\n}\n\nexport interface SourceOptions {\n /** A directory of `*.contract.json` (the monorepo contracts folder or a registry mirror). */\n fromDir?: string;\n /** An integration project directory; its `contract.json` is read. */\n fromPath?: string;\n env?: Record<string, string | undefined>;\n}\n\nfunction readContractFile(file: string): Contract | null {\n let json: unknown;\n try {\n json = JSON.parse(fs.readFileSync(file, 'utf8'));\n } catch {\n return null;\n }\n const verified = verifyContract(json);\n if (!verified.contract || verified.problems.length > 0) {\n const first = verified.problems[0];\n throw validationError(\n `${file} is not a valid contract: ${first ? `${first.rule}: ${first.message}` : 'unreadable'}`,\n 'Regenerate it with \"ekanos contract\" in the integration, or point at a valid contracts directory.',\n );\n }\n\n return verified.contract;\n}\n\n/** Every `*.contract.json` of `dir` for `integration`, highest contract version within `line` first. */\nexport function contractsInDir(\n dir: string,\n integration: string,\n line: string,\n): Contract[] {\n let names: string[];\n try {\n names = fs.readdirSync(dir).filter((n) => n.endsWith('.contract.json'));\n } catch {\n return [];\n }\n const found: Contract[] = [];\n for (const name of names) {\n if (\n !name.startsWith(`${integration}@`) &&\n !name.startsWith(`${integration}.`)\n ) {\n continue;\n }\n const contract = readContractFile(path.join(dir, name));\n if (\n contract &&\n contract.integration === integration &&\n contract.compatLine === line\n ) {\n found.push(contract);\n }\n }\n\n return found.sort((a, b) =>\n compareSemver(b.contractVersion, a.contractVersion),\n );\n}\n\nexport function dirSource(dir: string): ContractSource {\n return {\n name: `dir ${dir}`,\n resolve(integration, line) {\n const [best] = contractsInDir(dir, integration, line);\n\n return best ? { contract: best, origin: dir, kind: 'dir' } : null;\n },\n };\n}\n\nexport function pathSource(projectPath: string): ContractSource {\n return {\n name: `path ${projectPath}`,\n resolve(integration, line) {\n const file = path.join(path.resolve(projectPath), 'contract.json');\n if (!fs.existsSync(file)) {\n throw notFoundError(\n `${file} does not exist.`,\n 'Run \"ekanos contract\" in that integration to derive its contract.json.',\n );\n }\n const contract = readContractFile(file);\n if (\n !contract ||\n contract.integration !== integration ||\n contract.compatLine !== line\n ) {\n return null;\n }\n\n return { contract, origin: file, kind: 'path' };\n },\n };\n}\n\n/** The committed pin: `<contractsDir>/<integration>@<version>.contract.json`. */\nexport function snapshotFile(\n project: ExperienceProject,\n integration: string,\n line: string,\n): string {\n return path.join(\n project.contractsDir,\n `${integration}@${line}.contract.json`,\n );\n}\n\nexport function snapshotSource(project: ExperienceProject): ContractSource {\n return {\n name: 'committed snapshot',\n resolve(integration, line) {\n const file = snapshotFile(project, integration, line);\n const contract = fs.existsSync(file) ? readContractFile(file) : null;\n\n return contract ? { contract, origin: file, kind: 'snapshot' } : null;\n },\n };\n}\n\n/** Sources in resolution order: explicit flags, env, then (optionally) the snapshot. */\nexport function defaultSources(\n project: ExperienceProject,\n options: SourceOptions,\n extra: readonly ContractSource[] = [],\n includeSnapshot = true,\n): ContractSource[] {\n const sources: ContractSource[] = [];\n if (options.fromPath) sources.push(pathSource(options.fromPath));\n if (options.fromDir) sources.push(dirSource(path.resolve(options.fromDir)));\n const envDir = options.env?.EKANOS_CONTRACTS_DIR;\n if (!options.fromDir && envDir) sources.push(dirSource(path.resolve(envDir)));\n sources.push(...extra);\n if (includeSnapshot) sources.push(snapshotSource(project));\n\n return sources;\n}\n\nexport async function resolveContract(\n sources: readonly ContractSource[],\n integration: string,\n line: string,\n): Promise<ResolvedContract> {\n for (const source of sources) {\n const hit = await source.resolve(integration, line);\n if (hit) return hit;\n }\n\n throw notFoundError(\n `No contract for ${integration}@${line} (looked in: ${sources.map((s) => s.name).join(', ') || 'nothing'}).`,\n 'Pass --from-dir <contracts folder> or --from-path <integration project>, or set EKANOS_CONTRACTS_DIR.',\n );\n}\n\n/** Line for a package version, re-exported so callers need no schema import. */\nexport { compatLine };\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ExperienceManifest, type Finding, type WorkflowTemplate } from '@ekanos/integration-schema';
|
|
2
|
+
import type { LoadedExperienceDefinition, LoadedExperienceModule } from './definition-shape.js';
|
|
3
|
+
import { type PinnedIntegration } from './pins.js';
|
|
4
|
+
import type { ExperienceProject } from './project.js';
|
|
5
|
+
export declare const DEFINITION_CHECK = "checks/experience-definition";
|
|
6
|
+
export interface DefinitionAnalysis {
|
|
7
|
+
findings: Finding[];
|
|
8
|
+
workflows: WorkflowTemplate[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Cross-check the loaded definition against `ekanos.json` and the pinned
|
|
12
|
+
* contracts: every integration a widget, page, review section or workflow step names
|
|
13
|
+
* must be declared, every operation must be in that integration's allowlist, the
|
|
14
|
+
* review queue reads only, and each template serializes to the engine's DSL.
|
|
15
|
+
*/
|
|
16
|
+
export declare function analyzeDefinition(project: ExperienceProject, loaded: LoadedExperienceModule, pinned: readonly PinnedIntegration[]): DefinitionAnalysis;
|
|
17
|
+
/** `fixtures/<integration>/<kind>-<name>.<variant>.json` must satisfy the pinned output schema. */
|
|
18
|
+
export declare function checkFixtures(project: ExperienceProject, pinned: readonly PinnedIntegration[]): Finding[];
|
|
19
|
+
export declare function buildManifest(input: {
|
|
20
|
+
project: ExperienceProject;
|
|
21
|
+
definition: LoadedExperienceDefinition;
|
|
22
|
+
pinned: readonly PinnedIntegration[];
|
|
23
|
+
workflows: readonly WorkflowTemplate[];
|
|
24
|
+
version: string;
|
|
25
|
+
sdk: string;
|
|
26
|
+
}): ExperienceManifest;
|