@ekanos/cli 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +62 -4
  2. package/dist/commands/add.d.ts +2 -0
  3. package/dist/commands/add.js +118 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/audiences.d.ts +31 -0
  6. package/dist/commands/audiences.js +239 -0
  7. package/dist/commands/audiences.js.map +1 -0
  8. package/dist/commands/build.d.ts +2 -0
  9. package/dist/commands/build.js +41 -0
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/check.d.ts +2 -0
  12. package/dist/commands/check.js +43 -0
  13. package/dist/commands/check.js.map +1 -0
  14. package/dist/commands/codegen.d.ts +2 -0
  15. package/dist/commands/codegen.js +49 -0
  16. package/dist/commands/codegen.js.map +1 -0
  17. package/dist/commands/contract.d.ts +52 -0
  18. package/dist/commands/contract.js +300 -0
  19. package/dist/commands/contract.js.map +1 -0
  20. package/dist/commands/dev.d.ts +7 -0
  21. package/dist/commands/dev.js +186 -12
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/index-experience.d.ts +31 -0
  24. package/dist/commands/index-experience.js +21 -0
  25. package/dist/commands/index-experience.js.map +1 -0
  26. package/dist/commands/init.d.ts +7 -0
  27. package/dist/commands/init.js +12 -0
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/new.d.ts +2 -0
  30. package/dist/commands/new.js +39 -0
  31. package/dist/commands/new.js.map +1 -0
  32. package/dist/commands/publish.d.ts +59 -0
  33. package/dist/commands/publish.js +118 -65
  34. package/dist/commands/publish.js.map +1 -1
  35. package/dist/commands/scope-notice.d.ts +6 -0
  36. package/dist/commands/scope-notice.js +15 -0
  37. package/dist/commands/scope-notice.js.map +1 -1
  38. package/dist/commands/sources.js +2 -0
  39. package/dist/commands/sources.js.map +1 -1
  40. package/dist/commands/sync.d.ts +2 -0
  41. package/dist/commands/sync.js +150 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/teams-audience.d.ts +12 -0
  44. package/dist/commands/teams-audience.js +144 -0
  45. package/dist/commands/teams-audience.js.map +1 -0
  46. package/dist/commands/teams-scope.js +4 -0
  47. package/dist/commands/teams-scope.js.map +1 -1
  48. package/dist/commands/teams.d.ts +1 -1
  49. package/dist/commands/teams.js +6 -1
  50. package/dist/commands/teams.js.map +1 -1
  51. package/dist/compatibility.d.ts +10 -1
  52. package/dist/compatibility.js +10 -1
  53. package/dist/compatibility.js.map +1 -1
  54. package/dist/dev-experience.d.ts +125 -0
  55. package/dist/dev-experience.js +128 -0
  56. package/dist/dev-experience.js.map +1 -0
  57. package/dist/experience/boundary.d.ts +12 -0
  58. package/dist/experience/boundary.js +260 -0
  59. package/dist/experience/boundary.js.map +1 -0
  60. package/dist/experience/build.d.ts +16 -0
  61. package/dist/experience/build.js +90 -0
  62. package/dist/experience/build.js.map +1 -0
  63. package/dist/experience/checks.d.ts +29 -0
  64. package/dist/experience/checks.js +133 -0
  65. package/dist/experience/checks.js.map +1 -0
  66. package/dist/experience/contract-sources.d.ts +36 -0
  67. package/dist/experience/contract-sources.js +111 -0
  68. package/dist/experience/contract-sources.js.map +1 -0
  69. package/dist/experience/definition-checks.d.ts +26 -0
  70. package/dist/experience/definition-checks.js +201 -0
  71. package/dist/experience/definition-checks.js.map +1 -0
  72. package/dist/experience/definition-shape.d.ts +60 -0
  73. package/dist/experience/definition-shape.js +2 -0
  74. package/dist/experience/definition-shape.js.map +1 -0
  75. package/dist/experience/init.d.ts +10 -0
  76. package/dist/experience/init.js +64 -0
  77. package/dist/experience/init.js.map +1 -0
  78. package/dist/experience/integrations-codegen.d.ts +17 -0
  79. package/dist/experience/integrations-codegen.js +51 -0
  80. package/dist/experience/integrations-codegen.js.map +1 -0
  81. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  82. package/dist/experience/json-schema-to-ts.js +90 -0
  83. package/dist/experience/json-schema-to-ts.js.map +1 -0
  84. package/dist/experience/load-experience.d.ts +23 -0
  85. package/dist/experience/load-experience.js +115 -0
  86. package/dist/experience/load-experience.js.map +1 -0
  87. package/dist/experience/overrides.d.ts +8 -0
  88. package/dist/experience/overrides.js +21 -0
  89. package/dist/experience/overrides.js.map +1 -0
  90. package/dist/experience/pin-integration.d.ts +8 -0
  91. package/dist/experience/pin-integration.js +17 -0
  92. package/dist/experience/pin-integration.js.map +1 -0
  93. package/dist/experience/pin-ops.d.ts +12 -0
  94. package/dist/experience/pin-ops.js +36 -0
  95. package/dist/experience/pin-ops.js.map +1 -0
  96. package/dist/experience/pins.d.ts +35 -0
  97. package/dist/experience/pins.js +91 -0
  98. package/dist/experience/pins.js.map +1 -0
  99. package/dist/experience/project.d.ts +52 -0
  100. package/dist/experience/project.js +149 -0
  101. package/dist/experience/project.js.map +1 -0
  102. package/dist/experience/publish.d.ts +51 -0
  103. package/dist/experience/publish.js +144 -0
  104. package/dist/experience/publish.js.map +1 -0
  105. package/dist/experience/regenerate.d.ts +10 -0
  106. package/dist/experience/regenerate.js +33 -0
  107. package/dist/experience/regenerate.js.map +1 -0
  108. package/dist/experience/report.d.ts +9 -0
  109. package/dist/experience/report.js +23 -0
  110. package/dist/experience/report.js.map +1 -0
  111. package/dist/harness-scaffold.d.ts +52 -18
  112. package/dist/harness-scaffold.js +189 -9
  113. package/dist/harness-scaffold.js.map +1 -1
  114. package/dist/index.js +139 -7
  115. package/dist/index.js.map +1 -1
  116. package/dist/pack.js +5 -0
  117. package/dist/pack.js.map +1 -1
  118. package/dist/project-checks.d.ts +19 -0
  119. package/dist/project-checks.js +93 -0
  120. package/dist/project-checks.js.map +1 -1
  121. package/dist/project.d.ts +14 -0
  122. package/dist/project.js +10 -0
  123. package/dist/project.js.map +1 -1
  124. package/dist/repo/publish.d.ts +42 -0
  125. package/dist/repo/publish.js +225 -0
  126. package/dist/repo/publish.js.map +1 -0
  127. package/dist/repo/scaffold.d.ts +30 -0
  128. package/dist/repo/scaffold.js +165 -0
  129. package/dist/repo/scaffold.js.map +1 -0
  130. package/dist/teams-api.d.ts +37 -1
  131. package/dist/teams-api.js +62 -1
  132. package/dist/teams-api.js.map +1 -1
  133. package/dist/validate-findings.js +3 -1
  134. package/dist/validate-findings.js.map +1 -1
  135. package/package.json +2 -2
  136. package/templates/ekanos.json.tmpl +1 -0
  137. package/templates/experience/AGENTS.md.tmpl +22 -0
  138. package/templates/experience/ekanos.json.tmpl +7 -0
  139. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  140. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  141. package/templates/experience/gitignore.tmpl +17 -0
  142. package/templates/experience/package.json.tmpl +41 -0
  143. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  144. package/templates/experience/src/experience.ts.tmpl +30 -0
  145. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  146. package/templates/experience/tsconfig.json.tmpl +20 -0
  147. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  148. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  149. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  150. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  151. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  152. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  153. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  154. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  155. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  156. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  157. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  158. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  159. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  160. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  161. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  162. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  163. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  164. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  165. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  166. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  167. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  168. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  169. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  170. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  171. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  172. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  173. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  174. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  175. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  176. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  177. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  178. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  179. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  180. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  181. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  182. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  183. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  184. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  185. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  186. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  187. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  188. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  189. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  190. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  191. package/templates/harness/styles/globals.css.tmpl +15 -1
  192. package/templates/package.json.tmpl +5 -4
@@ -0,0 +1,225 @@
1
+ import { RepoManifestError, describeAvailability, discoverRepo, packageContaining, planRepoOrder, } from '@ekanos/integration-schema/repo';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { buildManifest, escalateForPublish, prepareIntegrationSubmission, readProjectVersion, submitPrepared, } from '../commands/publish.js';
5
+ import { CliError, gateFailedError, preconditionError, usageError, validationError, } from '../errors.js';
6
+ import { EXPERIENCE_NOT_SUPPORTED, gateExperience, packExperience, } from '../experience/publish.js';
7
+ import { packProject } from '../pack.js';
8
+ import { loadProject } from '../project.js';
9
+ import { shortHost } from '../ui.js';
10
+ import { collectAllFindings } from '../validate-findings.js';
11
+ export const PUBLISH_ARTIFACT_DIR = path.join('.ekanos', 'publish');
12
+ const AVAILABILITY_NOTE = 'availability is set by a super admin today; the host does not take it at submission';
13
+ function loadRepo(root) {
14
+ try {
15
+ return discoverRepo(root);
16
+ }
17
+ catch (error) {
18
+ if (error instanceof RepoManifestError) {
19
+ throw validationError(error.message, 'Fix the root ekanos.json: { "kind": "repo", "packages": ["integrations/*", "experiences/*"] }.');
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+ function select(repo, args) {
25
+ var _a;
26
+ var _b;
27
+ if (args.all && args.names.length > 0) {
28
+ throw usageError('Pass package names or --all, not both.', 'e.g. "ekanos publish acme-crm" or "ekanos publish --all".');
29
+ }
30
+ if (args.names.length > 0)
31
+ return [...args.names];
32
+ if (args.all)
33
+ return [];
34
+ const here = packageContaining(repo, args.cwd);
35
+ if (here)
36
+ return [here.name];
37
+ throw usageError('This is a repo: say what to publish.', `Name packages ("ekanos publish ${(_b = (_a = repo.packages[0]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '<package>'}"), pass --all, or run it from inside a package. Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none yet; "ekanos new integration <slug>")'}.`);
38
+ }
39
+ function artifactName(pkg) {
40
+ return `${pkg.name}-${pkg.version}`;
41
+ }
42
+ function renderTable(ctx, rows) {
43
+ return ctx.ui.table(['package', 'kind', 'version', 'availability', 'artifact', 'status'], rows.map((row) => { var _a; return [
44
+ row.name,
45
+ row.kind,
46
+ row.version,
47
+ row.availability,
48
+ (_a = row.artifact) !== null && _a !== void 0 ? _a : '-',
49
+ row.detail ? `${row.status}: ${row.detail}` : row.status,
50
+ ]; }));
51
+ }
52
+ /** Gate one integration and, when asked, build its artifact. No network. */
53
+ async function checkIntegration(pkg, repoRoot, build) {
54
+ const row = {
55
+ name: pkg.name,
56
+ kind: 'integration',
57
+ version: pkg.version,
58
+ availability: describeAvailability(pkg.availability),
59
+ artifact: null,
60
+ status: 'ready',
61
+ };
62
+ try {
63
+ readProjectVersion(pkg.dir);
64
+ const loaded = loadProject(pkg.dir);
65
+ const findings = escalateForPublish(await collectAllFindings(loaded));
66
+ const errors = findings.filter((f) => f.severity === 'error');
67
+ if (errors.length > 0) {
68
+ return Object.assign(Object.assign({}, row), { status: 'gate failed', detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run "ekanos validate" in ${pkg.relDir}`, findings });
69
+ }
70
+ if (!build)
71
+ return row;
72
+ const packed = packProject(loaded.projectDir);
73
+ const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));
74
+ fs.rmSync(dir, { recursive: true, force: true });
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ fs.writeFileSync(path.join(dir, `${artifactName(pkg)}.tgz`), packed.archive);
77
+ fs.writeFileSync(path.join(dir, 'manifest.json'), `${buildManifest(loaded, packed.files)}\n`);
78
+ const contract = path.join(pkg.dir, 'contract.json');
79
+ if (fs.existsSync(contract)) {
80
+ fs.copyFileSync(contract, path.join(dir, 'contract.json'));
81
+ }
82
+ return Object.assign(Object.assign(Object.assign({}, row), { status: 'built', artifact: path.relative(repoRoot, dir).split(path.sep).join('/') }), (findings.length > 0 ? { findings } : {}));
83
+ }
84
+ catch (error) {
85
+ if (error instanceof CliError) {
86
+ return Object.assign(Object.assign({}, row), { status: 'refused', detail: error.message });
87
+ }
88
+ throw error;
89
+ }
90
+ }
91
+ /** Gate one experience and, when asked, build and pack it. No network. */
92
+ async function checkExperience(pkg, repoRoot, build, env) {
93
+ const row = {
94
+ name: pkg.name,
95
+ kind: 'experience',
96
+ version: pkg.version,
97
+ availability: describeAvailability(pkg.availability),
98
+ artifact: null,
99
+ status: 'ready',
100
+ };
101
+ try {
102
+ const gate = await gateExperience({ dir: pkg.dir, dryRun: true, env });
103
+ const errors = gate.result.findings.filter((f) => f.severity === 'error');
104
+ if (errors.length > 0) {
105
+ return Object.assign(Object.assign({}, row), { status: 'gate failed', detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run "ekanos check" in ${pkg.relDir}`, findings: gate.result.findings });
106
+ }
107
+ if (!build)
108
+ return row;
109
+ const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));
110
+ fs.rmSync(dir, { recursive: true, force: true });
111
+ const packed = await packExperience(gate, dir);
112
+ const manifest = path.join(pkg.dir, 'manifest.json');
113
+ if (fs.existsSync(manifest)) {
114
+ fs.copyFileSync(manifest, path.join(dir, 'manifest.json'));
115
+ }
116
+ return Object.assign(Object.assign({}, row), { version: packed.version, status: 'built', artifact: path.relative(repoRoot, dir).split(path.sep).join('/') });
117
+ }
118
+ catch (error) {
119
+ if (error instanceof CliError) {
120
+ return Object.assign(Object.assign({}, row), { status: 'refused', detail: error.message });
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ /**
126
+ * `ekanos publish [name...] | --all [--dry-run]` in a repo.
127
+ *
128
+ * The plan is the selected packages, integrations before the experiences that
129
+ * use them (a same-repo integration is not an override, so it is ordered
130
+ * first, never refused). Every package passes its own gate before anything is
131
+ * sent. `--dry-run` also builds each artifact under `.ekanos/publish/` and
132
+ * touches no network. A real run submits the integrations in order through the
133
+ * existing path; the host cannot admit an experience yet, so selected
134
+ * experiences end the run with that refusal.
135
+ */
136
+ export async function runRepoPublish(ctx, args) {
137
+ const repo = loadRepo(args.repoRoot);
138
+ const names = select(repo, args);
139
+ const plan = planRepoOrder(repo, names);
140
+ if (plan.unknown.length > 0) {
141
+ throw usageError(`No package named ${plan.unknown.map((n) => `"${n}"`).join(', ')} in this repo.`, `Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none)'}.`);
142
+ }
143
+ if (args.all && repo.problems.length > 0) {
144
+ throw validationError(`${repo.problems.length} package${repo.problems.length === 1 ? ' is' : 's are'} unreadable: ${repo.problems.map((p) => `${p.where} (${p.message})`).join('; ')}`, 'Fix or remove them, then re-run "ekanos publish --all".');
145
+ }
146
+ if (plan.order.length === 0) {
147
+ throw preconditionError('This repo has no packages yet.', 'Add one with "ekanos new integration <slug>" or "ekanos new experience <slug>".');
148
+ }
149
+ const dryRun = args.publish.dryRun === true;
150
+ const warnings = plan.unselectedDependencies.map((dep) => `${dep.experience} uses ${dep.integration}, which is in this repo but not in this publish: publish ${dep.integration} first, or make sure it is already published at a compatible version.`);
151
+ const notes = plan.order.flatMap((pkg) => pkg.kind === 'integration' && pkg.availability
152
+ ? [
153
+ `${pkg.name}: ${describeAvailability(pkg.availability)} - ${AVAILABILITY_NOTE}.`,
154
+ ]
155
+ : []);
156
+ if (!ctx.jsonMode) {
157
+ ctx.log(`${dryRun ? 'Dry run: ' : ''}publishing ${plan.order.length} package${plan.order.length === 1 ? '' : 's'} in this order:`);
158
+ plan.order.forEach((pkg, index) => ctx.log(` ${index + 1}. ${pkg.name} (${pkg.kind} ${pkg.version}, ${describeAvailability(pkg.availability)})`));
159
+ for (const warning of warnings)
160
+ ctx.warn(` warning: ${warning}`);
161
+ }
162
+ // Gate (and, on a dry run, build) every package before anything is sent.
163
+ const rows = [];
164
+ for (const pkg of plan.order) {
165
+ rows.push(pkg.kind === 'integration'
166
+ ? await checkIntegration(pkg, repo.root, dryRun)
167
+ : await checkExperience(pkg, repo.root, dryRun, args.publish.env));
168
+ }
169
+ const data = (extra = {}) => (Object.assign({ repo: repo.root, dryRun, order: plan.order.map((p) => p.name), warnings,
170
+ notes, packages: rows }, extra));
171
+ const summary = (title) => [title, '', renderTable(ctx, rows), ...notes.map((n) => `note: ${n}`)].join('\n');
172
+ const failed = rows.filter((row) => row.status === 'gate failed' || row.status === 'refused');
173
+ if (failed.length > 0) {
174
+ if (!ctx.jsonMode)
175
+ ctx.log(summary('Nothing was published.'));
176
+ return ctx.fail(gateFailedError(`${failed.length} of ${rows.length} package${rows.length === 1 ? '' : 's'} did not pass: ${failed.map((r) => r.name).join(', ')}.`, 'Fix the packages named above (the status column says what), then re-run "ekanos publish". Nothing was submitted.'), data());
177
+ }
178
+ if (dryRun) {
179
+ return ctx.succeed(data({ published: false }), summary(`Dry run: ${rows.length} package${rows.length === 1 ? '' : 's'} built under ${PUBLISH_ARTIFACT_DIR}. Nothing was published.`));
180
+ }
181
+ // Real publish: integrations in plan order through the existing path.
182
+ let stop = null;
183
+ let host = null;
184
+ for (const [index, pkg] of plan.order.entries()) {
185
+ const row = rows[index];
186
+ if (pkg.kind === 'experience') {
187
+ row.status = 'not supported by the host';
188
+ continue;
189
+ }
190
+ if (stop) {
191
+ row.status = 'not attempted';
192
+ continue;
193
+ }
194
+ try {
195
+ const single = Object.assign(Object.assign({}, args.publish), { dir: pkg.dir });
196
+ const prepared = await prepareIntegrationSubmission(ctx, single);
197
+ host = prepared.env.host;
198
+ const submitted = await submitPrepared(ctx, single, prepared);
199
+ row.status = 'submitted';
200
+ row.receipt = submitted.data;
201
+ if (!ctx.jsonMode)
202
+ ctx.log(submitted.human);
203
+ }
204
+ catch (error) {
205
+ if (!(error instanceof CliError))
206
+ throw error;
207
+ row.status = 'failed';
208
+ row.detail = error.message;
209
+ stop = error;
210
+ }
211
+ }
212
+ const hostNote = host ? ` to ${shortHost(host)}` : '';
213
+ if (stop) {
214
+ if (!ctx.jsonMode)
215
+ ctx.log(summary('Publish stopped.'));
216
+ return ctx.fail(stop, data());
217
+ }
218
+ if (rows.some((row) => row.status === 'not supported by the host')) {
219
+ if (!ctx.jsonMode)
220
+ ctx.log(summary(`Submitted${hostNote}.`));
221
+ return ctx.fail(gateFailedError(EXPERIENCE_NOT_SUPPORTED, 'The integrations in this publish were submitted; run "ekanos publish --dry-run" to build and pack the experiences. The host has no admission path for experiences until it ships one.'), data({ published: true }));
222
+ }
223
+ return ctx.succeed(data({ published: true }), summary(`Submitted ${rows.length} package${rows.length === 1 ? '' : 's'}${hostNote}.`));
224
+ }
225
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/repo/publish.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport {\n type RepoDiscovery,\n type RepoPackage,\n RepoManifestError,\n describeAvailability,\n discoverRepo,\n packageContaining,\n planRepoOrder,\n} from '@ekanos/integration-schema/repo';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n type PublishArgs,\n buildManifest,\n escalateForPublish,\n prepareIntegrationSubmission,\n readProjectVersion,\n submitPrepared,\n} from '../commands/publish';\nimport type { CliContext } from '../context';\nimport {\n CliError,\n gateFailedError,\n preconditionError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n EXPERIENCE_NOT_SUPPORTED,\n gateExperience,\n packExperience,\n} from '../experience/publish';\nimport { packProject } from '../pack';\nimport { loadProject } from '../project';\nimport { shortHost } from '../ui';\nimport { collectAllFindings } from '../validate-findings';\n\nexport const PUBLISH_ARTIFACT_DIR = path.join('.ekanos', 'publish');\n\nexport interface RepoPublishArgs {\n repoRoot: string;\n /** Where the command was run: inside a package it narrows the selection. */\n cwd: string;\n /** Package names given on the command line. */\n names: readonly string[];\n all: boolean;\n /** The single-project publish arguments, reused for every integration. */\n publish: PublishArgs;\n}\n\nexport type PackageStatus =\n | 'ready'\n | 'built'\n | 'gate failed'\n | 'refused'\n | 'submitted'\n | 'failed'\n | 'not attempted'\n | 'not supported by the host';\n\nexport interface PackageRow {\n name: string;\n kind: 'integration' | 'experience';\n version: string;\n availability: string;\n /** Artifact directory relative to the repo root, when one was built. */\n artifact: string | null;\n status: PackageStatus;\n /** Why the status is not a success, one line. */\n detail?: string;\n findings?: Finding[];\n /** Integration submissions: what the host answered. */\n receipt?: Record<string, unknown>;\n}\n\nconst AVAILABILITY_NOTE =\n 'availability is set by a super admin today; the host does not take it at submission';\n\nfunction loadRepo(root: string): RepoDiscovery {\n try {\n return discoverRepo(root);\n } catch (error) {\n if (error instanceof RepoManifestError) {\n throw validationError(\n error.message,\n 'Fix the root ekanos.json: { \"kind\": \"repo\", \"packages\": [\"integrations/*\", \"experiences/*\"] }.',\n );\n }\n throw error;\n }\n}\n\nfunction select(repo: RepoDiscovery, args: RepoPublishArgs): string[] {\n if (args.all && args.names.length > 0) {\n throw usageError(\n 'Pass package names or --all, not both.',\n 'e.g. \"ekanos publish acme-crm\" or \"ekanos publish --all\".',\n );\n }\n if (args.names.length > 0) return [...args.names];\n if (args.all) return [];\n const here = packageContaining(repo, args.cwd);\n if (here) return [here.name];\n throw usageError(\n 'This is a repo: say what to publish.',\n `Name packages (\"ekanos publish ${repo.packages[0]?.name ?? '<package>'}\"), pass --all, or run it from inside a package. Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none yet; \"ekanos new integration <slug>\")'}.`,\n );\n}\n\nfunction artifactName(pkg: RepoPackage): string {\n return `${pkg.name}-${pkg.version}`;\n}\n\nfunction renderTable(ctx: CliContext, rows: readonly PackageRow[]): string {\n return ctx.ui.table(\n ['package', 'kind', 'version', 'availability', 'artifact', 'status'],\n rows.map((row) => [\n row.name,\n row.kind,\n row.version,\n row.availability,\n row.artifact ?? '-',\n row.detail ? `${row.status}: ${row.detail}` : row.status,\n ]),\n );\n}\n\n/** Gate one integration and, when asked, build its artifact. No network. */\nasync function checkIntegration(\n pkg: RepoPackage,\n repoRoot: string,\n build: boolean,\n): Promise<PackageRow> {\n const row: PackageRow = {\n name: pkg.name,\n kind: 'integration',\n version: pkg.version,\n availability: describeAvailability(pkg.availability),\n artifact: null,\n status: 'ready',\n };\n try {\n readProjectVersion(pkg.dir);\n const loaded = loadProject(pkg.dir);\n const findings = escalateForPublish(await collectAllFindings(loaded));\n const errors = findings.filter((f) => f.severity === 'error');\n if (errors.length > 0) {\n return {\n ...row,\n status: 'gate failed',\n detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run \"ekanos validate\" in ${pkg.relDir}`,\n findings,\n };\n }\n if (!build) return row;\n\n const packed = packProject(loaded.projectDir);\n const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));\n fs.rmSync(dir, { recursive: true, force: true });\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, `${artifactName(pkg)}.tgz`),\n packed.archive,\n );\n fs.writeFileSync(\n path.join(dir, 'manifest.json'),\n `${buildManifest(loaded, packed.files)}\\n`,\n );\n const contract = path.join(pkg.dir, 'contract.json');\n if (fs.existsSync(contract)) {\n fs.copyFileSync(contract, path.join(dir, 'contract.json'));\n }\n\n return {\n ...row,\n status: 'built',\n artifact: path.relative(repoRoot, dir).split(path.sep).join('/'),\n ...(findings.length > 0 ? { findings } : {}),\n };\n } catch (error) {\n if (error instanceof CliError) {\n return { ...row, status: 'refused', detail: error.message };\n }\n throw error;\n }\n}\n\n/** Gate one experience and, when asked, build and pack it. No network. */\nasync function checkExperience(\n pkg: RepoPackage,\n repoRoot: string,\n build: boolean,\n env: Record<string, string | undefined>,\n): Promise<PackageRow> {\n const row: PackageRow = {\n name: pkg.name,\n kind: 'experience',\n version: pkg.version,\n availability: describeAvailability(pkg.availability),\n artifact: null,\n status: 'ready',\n };\n try {\n const gate = await gateExperience({ dir: pkg.dir, dryRun: true, env });\n const errors = gate.result.findings.filter((f) => f.severity === 'error');\n if (errors.length > 0) {\n return {\n ...row,\n status: 'gate failed',\n detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run \"ekanos check\" in ${pkg.relDir}`,\n findings: gate.result.findings,\n };\n }\n if (!build) return row;\n\n const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));\n fs.rmSync(dir, { recursive: true, force: true });\n const packed = await packExperience(gate, dir);\n const manifest = path.join(pkg.dir, 'manifest.json');\n if (fs.existsSync(manifest)) {\n fs.copyFileSync(manifest, path.join(dir, 'manifest.json'));\n }\n\n return {\n ...row,\n version: packed.version,\n status: 'built',\n artifact: path.relative(repoRoot, dir).split(path.sep).join('/'),\n };\n } catch (error) {\n if (error instanceof CliError) {\n return { ...row, status: 'refused', detail: error.message };\n }\n throw error;\n }\n}\n\n/**\n * `ekanos publish [name...] | --all [--dry-run]` in a repo.\n *\n * The plan is the selected packages, integrations before the experiences that\n * use them (a same-repo integration is not an override, so it is ordered\n * first, never refused). Every package passes its own gate before anything is\n * sent. `--dry-run` also builds each artifact under `.ekanos/publish/` and\n * touches no network. A real run submits the integrations in order through the\n * existing path; the host cannot admit an experience yet, so selected\n * experiences end the run with that refusal.\n */\nexport async function runRepoPublish(\n ctx: CliContext,\n args: RepoPublishArgs,\n): Promise<ExitCode> {\n const repo = loadRepo(args.repoRoot);\n const names = select(repo, args);\n const plan = planRepoOrder(repo, names);\n\n if (plan.unknown.length > 0) {\n throw usageError(\n `No package named ${plan.unknown.map((n) => `\"${n}\"`).join(', ')} in this repo.`,\n `Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none)'}.`,\n );\n }\n if (args.all && repo.problems.length > 0) {\n throw validationError(\n `${repo.problems.length} package${repo.problems.length === 1 ? ' is' : 's are'} unreadable: ${repo.problems.map((p) => `${p.where} (${p.message})`).join('; ')}`,\n 'Fix or remove them, then re-run \"ekanos publish --all\".',\n );\n }\n if (plan.order.length === 0) {\n throw preconditionError(\n 'This repo has no packages yet.',\n 'Add one with \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n\n const dryRun = args.publish.dryRun === true;\n const warnings = plan.unselectedDependencies.map(\n (dep) =>\n `${dep.experience} uses ${dep.integration}, which is in this repo but not in this publish: publish ${dep.integration} first, or make sure it is already published at a compatible version.`,\n );\n const notes = plan.order.flatMap((pkg) =>\n pkg.kind === 'integration' && pkg.availability\n ? [\n `${pkg.name}: ${describeAvailability(pkg.availability)} - ${AVAILABILITY_NOTE}.`,\n ]\n : [],\n );\n\n if (!ctx.jsonMode) {\n ctx.log(\n `${dryRun ? 'Dry run: ' : ''}publishing ${plan.order.length} package${plan.order.length === 1 ? '' : 's'} in this order:`,\n );\n plan.order.forEach((pkg, index) =>\n ctx.log(\n ` ${index + 1}. ${pkg.name} (${pkg.kind} ${pkg.version}, ${describeAvailability(pkg.availability)})`,\n ),\n );\n for (const warning of warnings) ctx.warn(` warning: ${warning}`);\n }\n\n // Gate (and, on a dry run, build) every package before anything is sent.\n const rows: PackageRow[] = [];\n for (const pkg of plan.order) {\n rows.push(\n pkg.kind === 'integration'\n ? await checkIntegration(pkg, repo.root, dryRun)\n : await checkExperience(pkg, repo.root, dryRun, args.publish.env),\n );\n }\n\n const data = (extra: Record<string, unknown> = {}) => ({\n repo: repo.root,\n dryRun,\n order: plan.order.map((p) => p.name),\n warnings,\n notes,\n packages: rows,\n ...extra,\n });\n const summary = (title: string) =>\n [title, '', renderTable(ctx, rows), ...notes.map((n) => `note: ${n}`)].join(\n '\\n',\n );\n\n const failed = rows.filter(\n (row) => row.status === 'gate failed' || row.status === 'refused',\n );\n if (failed.length > 0) {\n if (!ctx.jsonMode) ctx.log(summary('Nothing was published.'));\n return ctx.fail(\n gateFailedError(\n `${failed.length} of ${rows.length} package${rows.length === 1 ? '' : 's'} did not pass: ${failed.map((r) => r.name).join(', ')}.`,\n 'Fix the packages named above (the status column says what), then re-run \"ekanos publish\". Nothing was submitted.',\n ),\n data(),\n );\n }\n\n if (dryRun) {\n return ctx.succeed(\n data({ published: false }),\n summary(\n `Dry run: ${rows.length} package${rows.length === 1 ? '' : 's'} built under ${PUBLISH_ARTIFACT_DIR}. Nothing was published.`,\n ),\n );\n }\n\n // Real publish: integrations in plan order through the existing path.\n let stop: CliError | null = null;\n let host: string | null = null;\n for (const [index, pkg] of plan.order.entries()) {\n const row = rows[index] as PackageRow;\n if (pkg.kind === 'experience') {\n row.status = 'not supported by the host';\n continue;\n }\n if (stop) {\n row.status = 'not attempted';\n continue;\n }\n try {\n const single: PublishArgs = { ...args.publish, dir: pkg.dir };\n const prepared = await prepareIntegrationSubmission(ctx, single);\n host = prepared.env.host;\n const submitted = await submitPrepared(ctx, single, prepared);\n row.status = 'submitted';\n row.receipt = submitted.data;\n if (!ctx.jsonMode) ctx.log(submitted.human);\n } catch (error) {\n if (!(error instanceof CliError)) throw error;\n row.status = 'failed';\n row.detail = error.message;\n stop = error;\n }\n }\n\n const hostNote = host ? ` to ${shortHost(host)}` : '';\n if (stop) {\n if (!ctx.jsonMode) ctx.log(summary('Publish stopped.'));\n return ctx.fail(stop, data());\n }\n if (rows.some((row) => row.status === 'not supported by the host')) {\n if (!ctx.jsonMode) ctx.log(summary(`Submitted${hostNote}.`));\n return ctx.fail(\n gateFailedError(\n EXPERIENCE_NOT_SUPPORTED,\n 'The integrations in this publish were submitted; run \"ekanos publish --dry-run\" to build and pack the experiences. The host has no admission path for experiences until it ships one.',\n ),\n data({ published: true }),\n );\n }\n\n return ctx.succeed(\n data({ published: true }),\n summary(\n `Submitted ${rows.length} package${rows.length === 1 ? '' : 's'}${hostNote}.`,\n ),\n );\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ export declare const REPO_PACKAGE_GLOBS: readonly ['integrations/*', 'experiences/*'];
4
+ /** Where `ekanos new <kind>` puts a package, relative to the repo root. */
5
+ export declare const REPO_PACKAGE_DIR: {
6
+ readonly integration: 'integrations';
7
+ readonly experience: 'experiences';
8
+ };
9
+ export interface InitRepoArgs {
10
+ slug?: string;
11
+ dir?: string;
12
+ cwd: string;
13
+ force: boolean;
14
+ }
15
+ /** `ekanos init --kind repo`: a root that holds many integrations and experiences. */
16
+ export declare function runInitRepo(ctx: CliContext, args: InitRepoArgs): ExitCode;
17
+ export interface NewPackageArgs {
18
+ kind: string | undefined;
19
+ slug: string | undefined;
20
+ cwd: string;
21
+ force: boolean;
22
+ /** Runs the per-kind scaffold; injected by the command to avoid an import cycle with init. */
23
+ scaffold: (args: {
24
+ kind: 'integration' | 'experience';
25
+ slug: string;
26
+ dir: string;
27
+ }) => Promise<ExitCode>;
28
+ }
29
+ /** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */
30
+ export declare function runNewPackage(ctx: CliContext, args: NewPackageArgs): Promise<ExitCode>;
@@ -0,0 +1,165 @@
1
+ import { discoverRepo, findRepoRoot, matchesPackageGlob, } from '@ekanos/integration-schema/repo';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { invalidStateError, preconditionError, usageError, validationError, } from '../errors.js';
5
+ import { classifyPath, writeFileNoFollow } from '../safe-fs.js';
6
+ import { readTemplateSource } from '../templates.js';
7
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
+ export const REPO_PACKAGE_GLOBS = ['integrations/*', 'experiences/*'];
9
+ /** Where `ekanos new <kind>` puts a package, relative to the repo root. */
10
+ export const REPO_PACKAGE_DIR = {
11
+ integration: 'integrations',
12
+ experience: 'experiences',
13
+ };
14
+ function formatJson(value) {
15
+ return `${JSON.stringify(value, null, 2)}\n`;
16
+ }
17
+ /**
18
+ * What the repo-root harness shell resolves upward into the root's
19
+ * node_modules: Next, React and the @ekanos packages. The pins come from the
20
+ * integration template, so there is one source of truth.
21
+ */
22
+ const HARNESS_DEV_DEPS = [
23
+ '@ekanos/cli',
24
+ '@ekanos/harness',
25
+ '@tailwindcss/postcss',
26
+ '@tanstack/react-query',
27
+ '@types/react',
28
+ '@types/react-dom',
29
+ 'next',
30
+ 'next-intl',
31
+ 'react',
32
+ 'react-dom',
33
+ 'tailwindcss',
34
+ 'typescript',
35
+ ];
36
+ const HARNESS_DEPS = ['@ekanos/sdk', '@ekanos/ui'];
37
+ /** Next type-checks its own config and offers to install this when it is missing. */
38
+ const NODE_TYPES_PIN = '^22.10.0';
39
+ function templatePins(section, names) {
40
+ const template = JSON.parse(readTemplateSource('package.json.tmpl'));
41
+ return Object.fromEntries(names.flatMap((name) => {
42
+ const range = template[section][name];
43
+ return range ? [[name, range]] : [];
44
+ }));
45
+ }
46
+ function harnessDevDependencies() {
47
+ return Object.fromEntries(Object.entries(Object.assign(Object.assign({}, templatePins('devDependencies', HARNESS_DEV_DEPS)), { '@types/node': NODE_TYPES_PIN })).sort(([left], [right]) => left.localeCompare(right)));
48
+ }
49
+ const REPO_GITIGNORE = `# Dependencies
50
+ node_modules/
51
+
52
+ # Build output
53
+ dist/
54
+ *.tsbuildinfo
55
+
56
+ # Ekanos dev harness and publish artifacts (generated)
57
+ .ekanos/
58
+
59
+ # Per-developer integration overrides and credentials
60
+ ekanos.local.json
61
+
62
+ # Logs
63
+ *.log
64
+ `;
65
+ function repoPnpmWorkspace() {
66
+ return `${readTemplateSource('pnpm-workspace.yaml.tmpl').trimEnd()}
67
+
68
+ packages:
69
+ ${REPO_PACKAGE_GLOBS.map((glob) => ` - '${glob}'`).join('\n')}
70
+ `;
71
+ }
72
+ /** `ekanos init --kind repo`: a root that holds many integrations and experiences. */
73
+ export function runInitRepo(ctx, args) {
74
+ var _a;
75
+ const slug = args.slug;
76
+ if (slug !== undefined && !SLUG_PATTERN.test(slug)) {
77
+ throw usageError(`Invalid slug "${slug}".`, 'Use kebab-case, e.g. "ekanos init --kind repo --slug acme-platform".');
78
+ }
79
+ if (!slug && args.dir === undefined) {
80
+ throw usageError('init --kind repo needs a name or a directory.', 'Pass "--slug <name>" (creates ./<name>) or "--dir ." to scaffold in place.');
81
+ }
82
+ const targetDir = path.resolve(args.cwd, (_a = args.dir) !== null && _a !== void 0 ? _a : slug);
83
+ if (fs.existsSync(targetDir) && fs.lstatSync(targetDir).isSymbolicLink()) {
84
+ throw invalidStateError(`The target directory ${targetDir} is a symlink.`, 'Scaffold into the real directory.');
85
+ }
86
+ if (findRepoRoot(targetDir) !== null && !args.force) {
87
+ throw invalidStateError(`${targetDir} is already inside an Ekanos repo.`, 'Add packages with "ekanos new integration <slug>" or "ekanos new experience <slug>".');
88
+ }
89
+ fs.mkdirSync(targetDir, { recursive: true });
90
+ const name = slug !== null && slug !== void 0 ? slug : path.basename(targetDir);
91
+ const files = [
92
+ {
93
+ dest: 'ekanos.json',
94
+ content: formatJson({
95
+ kind: 'repo',
96
+ packages: [...REPO_PACKAGE_GLOBS],
97
+ }),
98
+ },
99
+ {
100
+ dest: 'package.json',
101
+ content: formatJson({
102
+ name: SLUG_PATTERN.test(name) ? name : 'ekanos-repo',
103
+ version: '0.0.0',
104
+ private: true,
105
+ type: 'module',
106
+ description: 'Ekanos integrations and experiences in one repo.',
107
+ scripts: { dev: 'ekanos dev --start' },
108
+ dependencies: templatePins('dependencies', HARNESS_DEPS),
109
+ devDependencies: harnessDevDependencies(),
110
+ }),
111
+ },
112
+ { dest: 'pnpm-workspace.yaml', content: repoPnpmWorkspace() },
113
+ { dest: '.gitignore', content: REPO_GITIGNORE },
114
+ { dest: 'integrations/.gitkeep', content: '' },
115
+ { dest: 'experiences/.gitkeep', content: '' },
116
+ ];
117
+ const created = [];
118
+ const skipped = [];
119
+ for (const { dest, content } of files) {
120
+ const destPath = path.join(targetDir, dest);
121
+ const kind = classifyPath(destPath);
122
+ if (kind !== 'missing' && kind !== 'symlink' && !args.force) {
123
+ skipped.push(dest);
124
+ continue;
125
+ }
126
+ writeFileNoFollow(targetDir, destPath, content);
127
+ created.push(dest);
128
+ }
129
+ for (const file of created)
130
+ ctx.log(` created ${file}`);
131
+ for (const file of skipped) {
132
+ ctx.log(` skipped ${file} (already exists; --force overwrites)`);
133
+ }
134
+ const rel = path.relative(args.cwd, targetDir) || '.';
135
+ return ctx.succeed({ kind: 'repo', name, dir: targetDir, created, skipped }, `Scaffolded repo "${name}". Next: ${rel === '.' ? '' : `cd ${rel} && `}ekanos new integration <slug> and ekanos new experience <slug>.`);
136
+ }
137
+ /** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */
138
+ export async function runNewPackage(ctx, args) {
139
+ const kind = args.kind;
140
+ if (kind !== 'integration' && kind !== 'experience') {
141
+ throw usageError(kind ? `Unknown package kind "${kind}".` : 'new needs a package kind.', 'Use "ekanos new integration <slug>" or "ekanos new experience <slug>".');
142
+ }
143
+ const slug = args.slug;
144
+ if (!slug || !SLUG_PATTERN.test(slug)) {
145
+ throw usageError(slug ? `Invalid slug "${slug}".` : 'new needs a slug.', `Pass kebab-case, e.g. "ekanos new ${kind} acme-${kind === 'integration' ? 'crm' : 'queue'}".`);
146
+ }
147
+ const root = findRepoRoot(args.cwd);
148
+ if (!root) {
149
+ throw preconditionError('This directory is not inside an Ekanos repo.', 'Run "ekanos init --kind repo --slug <name>" first, or run this from inside one.');
150
+ }
151
+ const repo = discoverRepo(root);
152
+ const clash = repo.packages.find((pkg) => pkg.name === slug);
153
+ if (clash) {
154
+ throw validationError(`"${slug}" is already a ${clash.kind} in this repo (${clash.relDir}).`, 'Pick a different slug; a package is addressed by its slug.');
155
+ }
156
+ const dir = path.join(root, REPO_PACKAGE_DIR[kind], slug);
157
+ const globs = repo.manifest.packages;
158
+ const rel = path.relative(root, dir).split(path.sep).join('/');
159
+ const covered = globs.some((glob) => matchesPackageGlob(rel, glob));
160
+ if (!covered) {
161
+ throw validationError(`${rel} is not matched by the repo's "packages" globs (${globs.join(', ')}).`, 'Add a glob that covers it to the root ekanos.json, then re-run.');
162
+ }
163
+ return args.scaffold({ kind, slug, dir });
164
+ }
165
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/repo/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,gBAAgB,EAAE,eAAe,CAAU,CAAC;AAE/E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW,EAAE,cAAc;IAC3B,UAAU,EAAE,aAAa;CACjB,CAAC;AAEX,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,gBAAgB,GAAG;IACvB,aAAa;IACb,iBAAiB;IACjB,sBAAsB;IACtB,uBAAuB;IACvB,cAAc;IACd,kBAAkB;IAClB,MAAM;IACN,WAAW;IACX,OAAO;IACP,WAAW;IACX,aAAa;IACb,YAAY;CACJ,CAAC;AAEX,MAAM,YAAY,GAAG,CAAC,aAAa,EAAE,YAAY,CAAU,CAAC;AAE5D,qFAAqF;AACrF,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,SAAS,YAAY,CACnB,OAA2C,EAC3C,KAAwB;IAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,kBAAkB,CAAC,mBAAmB,CAAC,CAC8B,CAAC;IAExE,OAAO,MAAM,CAAC,WAAW,CACvB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB;IAC7B,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,iCACT,YAAY,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,KACpD,aAAa,EAAE,cAAc,IAC7B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CACxD,CAAC;AACJ,CAAC;AAED,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;CAetB,CAAC;AAEF,SAAS,iBAAiB;IACxB,OAAO,GAAG,kBAAkB,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE;;;EAGlE,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CAC7D,CAAC;AACF,CAAC;AASD,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,GAAe,EAAE,IAAkB;;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,UAAU,CACd,iBAAiB,IAAI,IAAI,EACzB,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,UAAU,CACd,+CAA+C,EAC/C,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,QAAE,IAAI,CAAC,GAAG,mCAAK,IAAe,CAAC,CAAC;IACvE,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;QACzE,MAAM,iBAAiB,CACrB,wBAAwB,SAAS,gBAAgB,EACjD,mCAAmC,CACpC,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACpD,MAAM,iBAAiB,CACrB,GAAG,SAAS,oCAAoC,EAChD,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,MAAM,IAAI,GAAG,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,KAAK,GAA6C;QACtD;YACE,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,UAAU,CAAC;gBAClB,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,CAAC,GAAG,kBAAkB,CAAC;aAClC,CAAC;SACH;QACD;YACE,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,UAAU,CAAC;gBAClB,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;gBACpD,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,kDAAkD;gBAC/D,OAAO,EAAE,EAAE,GAAG,EAAE,oBAAoB,EAAE;gBACtC,YAAY,EAAE,YAAY,CAAC,cAAc,EAAE,YAAY,CAAC;gBACxD,eAAe,EAAE,sBAAsB,EAAE;aAC1C,CAAC;SACH;QACD,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,iBAAiB,EAAE,EAAE;QAC7D,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE;QAC/C,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,EAAE,EAAE;QAC9C,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,EAAE,EAAE;KAC9C,CAAC;IAEF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,uCAAuC,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,GAAG,CAAC;IAEtD,OAAO,GAAG,CAAC,OAAO,CAChB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EACxD,oBAAoB,IAAI,YAAY,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,iEAAiE,CACxI,CAAC;AACJ,CAAC;AAeD,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAe,EACf,IAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QACpD,MAAM,UAAU,CACd,IAAI,CAAC,CAAC,CAAC,yBAAyB,IAAI,IAAI,CAAC,CAAC,CAAC,2BAA2B,EACtE,wEAAwE,CACzE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,UAAU,CACd,IAAI,CAAC,CAAC,CAAC,iBAAiB,IAAI,IAAI,CAAC,CAAC,CAAC,mBAAmB,EACtD,qCAAqC,IAAI,SAAS,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAC/F,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,iBAAiB,CACrB,8CAA8C,EAC9C,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC7D,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,eAAe,CACnB,IAAI,IAAI,kBAAkB,KAAK,CAAC,IAAI,kBAAkB,KAAK,CAAC,MAAM,IAAI,EACtE,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,eAAe,CACnB,GAAG,GAAG,mDAAmD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAC7E,iEAAiE,CAClE,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["import {\n discoverRepo,\n findRepoRoot,\n matchesPackageGlob,\n} from '@ekanos/integration-schema/repo';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type { CliContext } from '../context';\nimport {\n invalidStateError,\n preconditionError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { classifyPath, writeFileNoFollow } from '../safe-fs';\nimport { readTemplateSource } from '../templates';\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport const REPO_PACKAGE_GLOBS = ['integrations/*', 'experiences/*'] as const;\n\n/** Where `ekanos new <kind>` puts a package, relative to the repo root. */\nexport const REPO_PACKAGE_DIR = {\n integration: 'integrations',\n experience: 'experiences',\n} as const;\n\nfunction formatJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/**\n * What the repo-root harness shell resolves upward into the root's\n * node_modules: Next, React and the @ekanos packages. The pins come from the\n * integration template, so there is one source of truth.\n */\nconst HARNESS_DEV_DEPS = [\n '@ekanos/cli',\n '@ekanos/harness',\n '@tailwindcss/postcss',\n '@tanstack/react-query',\n '@types/react',\n '@types/react-dom',\n 'next',\n 'next-intl',\n 'react',\n 'react-dom',\n 'tailwindcss',\n 'typescript',\n] as const;\n\nconst HARNESS_DEPS = ['@ekanos/sdk', '@ekanos/ui'] as const;\n\n/** Next type-checks its own config and offers to install this when it is missing. */\nconst NODE_TYPES_PIN = '^22.10.0';\n\nfunction templatePins(\n section: 'dependencies' | 'devDependencies',\n names: readonly string[],\n): Record<string, string> {\n const template = JSON.parse(\n readTemplateSource('package.json.tmpl'),\n ) as Record<'dependencies' | 'devDependencies', Record<string, string>>;\n\n return Object.fromEntries(\n names.flatMap((name) => {\n const range = template[section][name];\n return range ? [[name, range] as const] : [];\n }),\n );\n}\n\nfunction harnessDevDependencies(): Record<string, string> {\n return Object.fromEntries(\n Object.entries({\n ...templatePins('devDependencies', HARNESS_DEV_DEPS),\n '@types/node': NODE_TYPES_PIN,\n }).sort(([left], [right]) => left.localeCompare(right)),\n );\n}\n\nconst REPO_GITIGNORE = `# Dependencies\nnode_modules/\n\n# Build output\ndist/\n*.tsbuildinfo\n\n# Ekanos dev harness and publish artifacts (generated)\n.ekanos/\n\n# Per-developer integration overrides and credentials\nekanos.local.json\n\n# Logs\n*.log\n`;\n\nfunction repoPnpmWorkspace(): string {\n return `${readTemplateSource('pnpm-workspace.yaml.tmpl').trimEnd()}\n\npackages:\n${REPO_PACKAGE_GLOBS.map((glob) => ` - '${glob}'`).join('\\n')}\n`;\n}\n\nexport interface InitRepoArgs {\n slug?: string;\n dir?: string;\n cwd: string;\n force: boolean;\n}\n\n/** `ekanos init --kind repo`: a root that holds many integrations and experiences. */\nexport function runInitRepo(ctx: CliContext, args: InitRepoArgs): ExitCode {\n const slug = args.slug;\n if (slug !== undefined && !SLUG_PATTERN.test(slug)) {\n throw usageError(\n `Invalid slug \"${slug}\".`,\n 'Use kebab-case, e.g. \"ekanos init --kind repo --slug acme-platform\".',\n );\n }\n if (!slug && args.dir === undefined) {\n throw usageError(\n 'init --kind repo needs a name or a directory.',\n 'Pass \"--slug <name>\" (creates ./<name>) or \"--dir .\" to scaffold in place.',\n );\n }\n\n const targetDir = path.resolve(args.cwd, args.dir ?? (slug as string));\n if (fs.existsSync(targetDir) && fs.lstatSync(targetDir).isSymbolicLink()) {\n throw invalidStateError(\n `The target directory ${targetDir} is a symlink.`,\n 'Scaffold into the real directory.',\n );\n }\n if (findRepoRoot(targetDir) !== null && !args.force) {\n throw invalidStateError(\n `${targetDir} is already inside an Ekanos repo.`,\n 'Add packages with \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n fs.mkdirSync(targetDir, { recursive: true });\n\n const name = slug ?? path.basename(targetDir);\n const files: Array<{ dest: string; content: string }> = [\n {\n dest: 'ekanos.json',\n content: formatJson({\n kind: 'repo',\n packages: [...REPO_PACKAGE_GLOBS],\n }),\n },\n {\n dest: 'package.json',\n content: formatJson({\n name: SLUG_PATTERN.test(name) ? name : 'ekanos-repo',\n version: '0.0.0',\n private: true,\n type: 'module',\n description: 'Ekanos integrations and experiences in one repo.',\n scripts: { dev: 'ekanos dev --start' },\n dependencies: templatePins('dependencies', HARNESS_DEPS),\n devDependencies: harnessDevDependencies(),\n }),\n },\n { dest: 'pnpm-workspace.yaml', content: repoPnpmWorkspace() },\n { dest: '.gitignore', content: REPO_GITIGNORE },\n { dest: 'integrations/.gitkeep', content: '' },\n { dest: 'experiences/.gitkeep', content: '' },\n ];\n\n const created: string[] = [];\n const skipped: string[] = [];\n for (const { dest, content } of files) {\n const destPath = path.join(targetDir, dest);\n const kind = classifyPath(destPath);\n if (kind !== 'missing' && kind !== 'symlink' && !args.force) {\n skipped.push(dest);\n continue;\n }\n writeFileNoFollow(targetDir, destPath, content);\n created.push(dest);\n }\n\n for (const file of created) ctx.log(` created ${file}`);\n for (const file of skipped) {\n ctx.log(` skipped ${file} (already exists; --force overwrites)`);\n }\n\n const rel = path.relative(args.cwd, targetDir) || '.';\n\n return ctx.succeed(\n { kind: 'repo', name, dir: targetDir, created, skipped },\n `Scaffolded repo \"${name}\". Next: ${rel === '.' ? '' : `cd ${rel} && `}ekanos new integration <slug> and ekanos new experience <slug>.`,\n );\n}\n\nexport interface NewPackageArgs {\n kind: string | undefined;\n slug: string | undefined;\n cwd: string;\n force: boolean;\n /** Runs the per-kind scaffold; injected by the command to avoid an import cycle with init. */\n scaffold: (args: {\n kind: 'integration' | 'experience';\n slug: string;\n dir: string;\n }) => Promise<ExitCode>;\n}\n\n/** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */\nexport async function runNewPackage(\n ctx: CliContext,\n args: NewPackageArgs,\n): Promise<ExitCode> {\n const kind = args.kind;\n if (kind !== 'integration' && kind !== 'experience') {\n throw usageError(\n kind ? `Unknown package kind \"${kind}\".` : 'new needs a package kind.',\n 'Use \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n const slug = args.slug;\n if (!slug || !SLUG_PATTERN.test(slug)) {\n throw usageError(\n slug ? `Invalid slug \"${slug}\".` : 'new needs a slug.',\n `Pass kebab-case, e.g. \"ekanos new ${kind} acme-${kind === 'integration' ? 'crm' : 'queue'}\".`,\n );\n }\n\n const root = findRepoRoot(args.cwd);\n if (!root) {\n throw preconditionError(\n 'This directory is not inside an Ekanos repo.',\n 'Run \"ekanos init --kind repo --slug <name>\" first, or run this from inside one.',\n );\n }\n\n const repo = discoverRepo(root);\n const clash = repo.packages.find((pkg) => pkg.name === slug);\n if (clash) {\n throw validationError(\n `\"${slug}\" is already a ${clash.kind} in this repo (${clash.relDir}).`,\n 'Pick a different slug; a package is addressed by its slug.',\n );\n }\n\n const dir = path.join(root, REPO_PACKAGE_DIR[kind], slug);\n const globs = repo.manifest.packages;\n const rel = path.relative(root, dir).split(path.sep).join('/');\n const covered = globs.some((glob) => matchesPackageGlob(rel, glob));\n if (!covered) {\n throw validationError(\n `${rel} is not matched by the repo's \"packages\" globs (${globs.join(', ')}).`,\n 'Add a glob that covers it to the root ekanos.json, then re-run.',\n );\n }\n\n return args.scaffold({ kind, slug, dir });\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type ProductScopeList, type ProductScopeProductChange, type TeamScopes, type TeamScopesSetResult, type TeamAppSetResult, type TeamCreateRequest, type TeamCreateResult, type TeamDisconnectResult, type TeamMemberChangeResult, type TeamMemberAddResult, type TeamMemberInviteResult, type TeamMemberList, type TeamRenameResult, type TeamRoles, type TeamIntegrationStatus, type TeamIntegrationEventList, type TeamList, type TeamWebhookMintResult } from '@ekanos/integration-schema';
1
+ import { type AudienceCreateRequest, type AudienceListResponse, type AudienceResponse, type AudienceUpdateRequest, type MemberAudience, type MemberAudienceSetResult, type TeamAudience, type TeamAudienceSetResult, type ProductScopeList, type ProductScopeProductChange, type TeamScopes, type TeamScopesSetResult, type TeamAppSetResult, type TeamCreateRequest, type TeamCreateResult, type TeamDisconnectResult, type TeamMemberChangeResult, type TeamMemberAddResult, type TeamMemberInviteResult, type TeamMemberList, type TeamRenameResult, type TeamRoles, type TeamIntegrationStatus, type TeamIntegrationEventList, type TeamList, type TeamWebhookMintResult } from '@ekanos/integration-schema';
2
2
  import type { z } from 'zod';
3
3
  /**
4
4
  * The CLI's client for a Source's teams and their integration setup — the
@@ -22,6 +22,13 @@ import type { z } from 'zod';
22
22
  * GET /api/partner/scopes?source=<slug> → ProductScopeList
23
23
  * PUT | DELETE /api/partner/scopes/<scope>/products/<product>?source=<slug>
24
24
  * → ProductScopeProductChange
25
+ * GET /api/partner/audiences?source=<slug> → AudienceListResponse
26
+ * GET | PATCH /api/partner/audiences/<audience>?source=<slug> → AudienceResponse
27
+ * POST /api/partner/audiences?source=<slug> { name, … } → AudienceResponse
28
+ * GET | PUT /api/partner/teams/<team>/audience?source=<slug> { audienceId }
29
+ * → TeamAudience | TeamAudienceSetResult
30
+ * GET | PUT …/<team>/members/<email>/audience?source=<slug> { audienceId | null }
31
+ * → MemberAudience | MemberAudienceSetResult
25
32
  * GET /api/partner/teams/<team>/scopes?source=<slug> → TeamScopes
26
33
  * PUT /api/partner/teams/<team>/scopes?source=<slug> { scopes } → TeamScopesSetResult
27
34
  *
@@ -117,6 +124,35 @@ export declare function setTeamScopes(host: string, accessToken: string, target:
117
124
  source: string;
118
125
  team: string;
119
126
  }, scopes: string[]): Promise<TeamsApiResult<TeamScopesSetResult>>;
127
+ export declare function listAudiences(host: string, accessToken: string, source: string): Promise<TeamsApiResult<AudienceListResponse>>;
128
+ export declare function showAudience(host: string, accessToken: string, target: {
129
+ source: string;
130
+ audience: string;
131
+ }): Promise<TeamsApiResult<AudienceResponse>>;
132
+ export declare function createAudience(host: string, accessToken: string, source: string, body: AudienceCreateRequest): Promise<TeamsApiResult<AudienceResponse>>;
133
+ export declare function updateAudience(host: string, accessToken: string, target: {
134
+ source: string;
135
+ audience: string;
136
+ }, body: AudienceUpdateRequest): Promise<TeamsApiResult<AudienceResponse>>;
137
+ export declare function showTeamAudience(host: string, accessToken: string, target: {
138
+ source: string;
139
+ team: string;
140
+ }): Promise<TeamsApiResult<TeamAudience>>;
141
+ export declare function setTeamAudience(host: string, accessToken: string, target: {
142
+ source: string;
143
+ team: string;
144
+ }, audienceId: string): Promise<TeamsApiResult<TeamAudienceSetResult>>;
145
+ export declare function showMemberAudience(host: string, accessToken: string, target: {
146
+ source: string;
147
+ team: string;
148
+ email: string;
149
+ }): Promise<TeamsApiResult<MemberAudience>>;
150
+ /** `audienceId: null` clears the member's own audience. */
151
+ export declare function setMemberAudience(host: string, accessToken: string, target: {
152
+ source: string;
153
+ team: string;
154
+ email: string;
155
+ }, audienceId: string | null): Promise<TeamsApiResult<MemberAudienceSetResult>>;
120
156
  export declare function listTeamRoles(host: string, accessToken: string, target: {
121
157
  source: string;
122
158
  team: string;
package/dist/teams-api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ProductScopeListSchema, ProductScopeProductChangeSchema, TeamScopesSchema, TeamScopesSetResultSchema, TeamAppSetResultSchema, TeamCreateResultSchema, TeamDisconnectResultSchema, TeamMemberChangeResultSchema, TeamMemberAddResultSchema, TeamMemberInviteResultSchema, TeamMemberListSchema, TeamRenameResultSchema, TeamRolesSchema, TeamIntegrationStatusSchema, TeamIntegrationEventListSchema, TeamListSchema, TeamWebhookMintResultSchema, } from '@ekanos/integration-schema';
1
+ import { AudienceListResponseSchema, AudienceResponseSchema, MemberAudienceSchema, MemberAudienceSetResultSchema, TeamAudienceSchema, TeamAudienceSetResultSchema, ProductScopeListSchema, ProductScopeProductChangeSchema, TeamScopesSchema, TeamScopesSetResultSchema, TeamAppSetResultSchema, TeamCreateResultSchema, TeamDisconnectResultSchema, TeamMemberChangeResultSchema, TeamMemberAddResultSchema, TeamMemberInviteResultSchema, TeamMemberListSchema, TeamRenameResultSchema, TeamRolesSchema, TeamIntegrationStatusSchema, TeamIntegrationEventListSchema, TeamListSchema, TeamWebhookMintResultSchema, } from '@ekanos/integration-schema';
2
2
  import { request } from './auth/ekanos-api.js';
3
3
  import { authRequiredError, forbiddenError, invalidStateError, networkError, notFoundError, usageError, validationError, } from './errors.js';
4
4
  export function listTeams(host, accessToken, source) {
@@ -122,6 +122,67 @@ export function setTeamScopes(host, accessToken, target, scopes) {
122
122
  body: { scopes },
123
123
  });
124
124
  }
125
+ export function listAudiences(host, accessToken, source) {
126
+ return call(host, accessToken, AudienceListResponseSchema, {
127
+ method: 'GET',
128
+ path: '/api/partner/audiences',
129
+ source,
130
+ });
131
+ }
132
+ export function showAudience(host, accessToken, target) {
133
+ return call(host, accessToken, AudienceResponseSchema, {
134
+ method: 'GET',
135
+ path: `/api/partner/audiences/${encodeURIComponent(target.audience)}`,
136
+ source: target.source,
137
+ });
138
+ }
139
+ export function createAudience(host, accessToken, source, body) {
140
+ return call(host, accessToken, AudienceResponseSchema, {
141
+ method: 'POST',
142
+ path: '/api/partner/audiences',
143
+ source,
144
+ body,
145
+ });
146
+ }
147
+ export function updateAudience(host, accessToken, target, body) {
148
+ return call(host, accessToken, AudienceResponseSchema, {
149
+ method: 'PATCH',
150
+ path: `/api/partner/audiences/${encodeURIComponent(target.audience)}`,
151
+ source: target.source,
152
+ body,
153
+ });
154
+ }
155
+ export function showTeamAudience(host, accessToken, target) {
156
+ return call(host, accessToken, TeamAudienceSchema, {
157
+ method: 'GET',
158
+ path: `/api/partner/teams/${encodeURIComponent(target.team)}/audience`,
159
+ source: target.source,
160
+ });
161
+ }
162
+ export function setTeamAudience(host, accessToken, target, audienceId) {
163
+ return call(host, accessToken, TeamAudienceSetResultSchema, {
164
+ method: 'PUT',
165
+ path: `/api/partner/teams/${encodeURIComponent(target.team)}/audience`,
166
+ source: target.source,
167
+ body: { audienceId },
168
+ });
169
+ }
170
+ export function showMemberAudience(host, accessToken, target) {
171
+ return call(host, accessToken, MemberAudienceSchema, {
172
+ method: 'GET',
173
+ path: `${membersPath(target.team)}/${encodeURIComponent(target.email)}/audience`,
174
+ source: target.source,
175
+ });
176
+ }
177
+ /** `audienceId: null` clears the member's own audience. */
178
+ export function setMemberAudience(host, accessToken, target, audienceId) {
179
+ return call(host, accessToken, MemberAudienceSetResultSchema, {
180
+ method: 'PUT',
181
+ path: `${membersPath(target.team)}/${encodeURIComponent(target.email)}/audience`,
182
+ source: target.source,
183
+ body: { audienceId },
184
+ });
185
+ }
125
186
  export function listTeamRoles(host, accessToken, target) {
126
187
  return call(host, accessToken, TeamRolesSchema, {
127
188
  method: 'GET',