@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.
- package/README.md +62 -4
- 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/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/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/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/scope-notice.d.ts +6 -0
- package/dist/commands/scope-notice.js +15 -0
- package/dist/commands/scope-notice.js.map +1 -1
- package/dist/commands/sources.js +2 -0
- 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/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-scope.js +4 -0
- package/dist/commands/teams-scope.js.map +1 -1
- package/dist/commands/teams.d.ts +1 -1
- package/dist/commands/teams.js +6 -1
- package/dist/commands/teams.js.map +1 -1
- package/dist/compatibility.d.ts +10 -1
- package/dist/compatibility.js +10 -1
- package/dist/compatibility.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.js +139 -7
- package/dist/index.js.map +1 -1
- package/dist/pack.js +5 -0
- package/dist/pack.js.map +1 -1
- package/dist/project-checks.d.ts +19 -0
- package/dist/project-checks.js +93 -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/teams-api.d.ts +37 -1
- package/dist/teams-api.js +62 -1
- package/dist/teams-api.js.map +1 -1
- package/dist/validate-findings.js +3 -1
- package/dist/validate-findings.js.map +1 -1
- package/package.json +2 -2
- 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 +5 -4
package/README.md
CHANGED
|
@@ -178,7 +178,7 @@ imply an isolation that does not exist.
|
|
|
178
178
|
app/harness-shell.tsx 'use client' — injects your registry
|
|
179
179
|
app/page.tsx server component — the index
|
|
180
180
|
app/[slug]/layout.tsx ─┐
|
|
181
|
-
app/[slug]/page.tsx │ (
|
|
181
|
+
app/[slug]/page.tsx │ (redirect the old /<slug> addresses to Preview)
|
|
182
182
|
app/[slug]/widgets/page.tsx │
|
|
183
183
|
app/[slug]/widgets/[widgetId]/page.tsx
|
|
184
184
|
app/[slug]/tile/page.tsx ├─ one-line re-exports of @ekanos/harness/routes
|
|
@@ -187,6 +187,24 @@ imply an isolation that does not exist.
|
|
|
187
187
|
app/api/integrations/[slug]/[...path]/route.ts
|
|
188
188
|
server — injects your registry into @ekanos/harness/api
|
|
189
189
|
(live-mode OAuth loop, storage bridge, trigger route)
|
|
190
|
+
app/%5F%5Fekanos/api/[...path]/route.ts
|
|
191
|
+
server — /__ekanos/api/*: integrations, operations, approvals, run log
|
|
192
|
+
app/integrations/layout.tsx the Integration console (mounted from @ekanos/harness/routes)
|
|
193
|
+
app/integrations/page.tsx
|
|
194
|
+
app/integrations/[slug]/page.tsx
|
|
195
|
+
app/integrations/[slug]/connect/page.tsx
|
|
196
|
+
app/integrations/[slug]/operations/page.tsx
|
|
197
|
+
app/integrations/[slug]/operations/[kind]/[name]/page.tsx
|
|
198
|
+
app/integrations/[slug]/events/page.tsx
|
|
199
|
+
app/integrations/[slug]/health/page.tsx
|
|
200
|
+
app/integrations/[slug]/runs/page.tsx
|
|
201
|
+
app/integrations/[slug]/preview/layout.tsx
|
|
202
|
+
app/integrations/[slug]/preview/page.tsx
|
|
203
|
+
app/integrations/[slug]/preview/widgets/page.tsx
|
|
204
|
+
app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx
|
|
205
|
+
app/integrations/[slug]/preview/tile/page.tsx
|
|
206
|
+
app/integrations/[slug]/preview/activation/page.tsx
|
|
207
|
+
app/integrations/[slug]/preview/triggers/page.tsx
|
|
190
208
|
next.config.mjs transpiles @ekanos/harness, aliases the workspace mock
|
|
191
209
|
postcss.config.mjs @tailwindcss/postcss
|
|
192
210
|
tsconfig.json no baseUrl, no paths
|
|
@@ -195,6 +213,27 @@ imply an isolation that does not exist.
|
|
|
195
213
|
package.json scripts only — no deps, everything resolves upward
|
|
196
214
|
```
|
|
197
215
|
|
|
216
|
+
A `kind: "experience"` project also gets `app/experience/**` (the Experience view,
|
|
217
|
+
whose layout imports your entry) and a home page that redirects there.
|
|
218
|
+
|
|
219
|
+
An experience declares the integrations it depends on in `ekanos.json`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{ "kind": "experience", "slug": "my-exp", "entry": "src/experience.ts",
|
|
223
|
+
"integrations": [{ "name": "sanity", "version": "0.2",
|
|
224
|
+
"operations": ["tool/list_posts", "action/create-draft"] }] }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`name` is the integration's slug and `version` its compat line. `ekanos add
|
|
228
|
+
sanity@0.2` adds an entry (reads by default; writes via `--ops`), pins its
|
|
229
|
+
contract in `contracts/` and `ekanos.lock.json`, and regenerates
|
|
230
|
+
`src/.ekanos/integrations.d.ts`, after which `useOperation('sanity',
|
|
231
|
+
'tool/list_posts')` is typed. A per-developer `ekanos.local.json`
|
|
232
|
+
(`{ "integrations": { "sanity": { "path": "../sanity" } } }`) points an
|
|
233
|
+
integration at a local checkout; `ekanos publish` refuses while one is active.
|
|
234
|
+
The earlier `uses` / `slots` shapes are still read for one version with a
|
|
235
|
+
warning; `ekanos sync` rewrites `ekanos.json` and the lock.
|
|
236
|
+
|
|
198
237
|
`next dev`/`next build` add `next-env.d.ts`, `.next/`, and (Next 16)
|
|
199
238
|
`AGENTS.md`/`CLAUDE.md` alongside them. None of that is ours and none of it is
|
|
200
239
|
tracked — `.ekanos/` is gitignored in full.
|
|
@@ -360,9 +399,28 @@ edit.
|
|
|
360
399
|
### For agents
|
|
361
400
|
|
|
362
401
|
`dev` in JSON mode (`--json`, a pipe, or `CLAUDECODE=1`) **scaffolds and
|
|
363
|
-
returns** rather than blocking on a dev server that never exits —
|
|
364
|
-
|
|
365
|
-
|
|
402
|
+
returns** rather than blocking on a dev server that never exits — it prints
|
|
403
|
+
"Scaffolded only: the server was NOT started", and the envelope's `data.server`
|
|
404
|
+
carries `{ started: false, reason, note, command, url, port }` so the caller can
|
|
405
|
+
start it itself. **Pass `--start` to actually run the server** under an agent
|
|
406
|
+
or a pipe (`ekanos dev --start --json`).
|
|
407
|
+
|
|
408
|
+
## ESLint and the publish gate
|
|
409
|
+
|
|
410
|
+
The hosted gate's `checks/capability-lint` runs the **project's own** ESLint (its
|
|
411
|
+
pinned `node_modules/.bin/eslint` and an `eslint.config.*` spreading
|
|
412
|
+
`@ekanos/sdk/eslint`) and refuses a submission without them. It does not bring an
|
|
413
|
+
ESLint of its own: a control the submission can leave out is not a control.
|
|
414
|
+
`ekanos init` scaffolds `eslint`, `@typescript-eslint/parser` and
|
|
415
|
+
`eslint.config.mjs`. A repo created before that (or by hand) adds them once:
|
|
416
|
+
|
|
417
|
+
```sh
|
|
418
|
+
pnpm add -D eslint @typescript-eslint/parser # or npm install -D / yarn add -D
|
|
419
|
+
# then add eslint.config.mjs spreading @ekanos/sdk/eslint
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`ekanos validate` warns (`project.capability-lint-tooling`) when either is missing
|
|
423
|
+
and prints the config to paste.
|
|
366
424
|
|
|
367
425
|
## Keeping the toolchain current
|
|
368
426
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { harnessContractSources } from '../dev-experience.js';
|
|
2
|
+
import { invalidStateError, usageError } from '../errors.js';
|
|
3
|
+
import { defaultSources, resolveContract, } from '../experience/contract-sources.js';
|
|
4
|
+
import { pinIntegration } from '../experience/pin-integration.js';
|
|
5
|
+
import { inContractOrder, readOperationIds, resolveOpTokens, } from '../experience/pin-ops.js';
|
|
6
|
+
import { DEPRECATED_USES } from '../experience/pins.js';
|
|
7
|
+
import { readExperienceProject, writeIntegrations, } from '../experience/project.js';
|
|
8
|
+
import { loadPins, regenerateIntegrations } from '../experience/regenerate.js';
|
|
9
|
+
const SPEC = /^([a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
|
|
10
|
+
export const addCommand = {
|
|
11
|
+
name: 'add',
|
|
12
|
+
usage: [
|
|
13
|
+
' add Add an integration to an experience: pin its contract,',
|
|
14
|
+
' record it in ekanos.json + ekanos.lock.json, regenerate types',
|
|
15
|
+
' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',
|
|
16
|
+
],
|
|
17
|
+
positionals: ['spec'],
|
|
18
|
+
specs: {
|
|
19
|
+
dir: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Project directory (default: the current one)',
|
|
22
|
+
},
|
|
23
|
+
ops: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',
|
|
26
|
+
},
|
|
27
|
+
as: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Removed: an experience addresses an integration by its own name',
|
|
30
|
+
},
|
|
31
|
+
'from-dir': {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Directory of <integration>.contract.json files (the contract source)',
|
|
34
|
+
},
|
|
35
|
+
'from-path': {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'An integration project directory whose contract.json to pin',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
async run(ctx, args) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
const spec = args.positionals[0];
|
|
43
|
+
const match = spec ? SPEC.exec(spec) : null;
|
|
44
|
+
if (!match) {
|
|
45
|
+
throw usageError(spec
|
|
46
|
+
? `"${spec}" is not <integration>@<version>.`
|
|
47
|
+
: 'add needs <integration>@<version>.', 'e.g. "ekanos add sanity@0.2 --from-dir ../contracts".');
|
|
48
|
+
}
|
|
49
|
+
const [, integration, version] = match;
|
|
50
|
+
if (args.flags.as !== undefined) {
|
|
51
|
+
throw usageError('--as is gone: an experience addresses an integration by its own name.', `Use "${integration}" in useOperation('${integration}', ...).`);
|
|
52
|
+
}
|
|
53
|
+
const project = readExperienceProject(args.dir);
|
|
54
|
+
if (project.deprecatedUses)
|
|
55
|
+
ctx.warn(DEPRECATED_USES);
|
|
56
|
+
const sources = defaultSources(project, {
|
|
57
|
+
fromDir: args.flags['from-dir'],
|
|
58
|
+
fromPath: args.flags['from-path'],
|
|
59
|
+
env: args.env,
|
|
60
|
+
}, await harnessContractSources(project.dir, args.env));
|
|
61
|
+
const { contract, origin } = await resolveContract(sources, integration, version);
|
|
62
|
+
const existing = project.integrations.find((u) => u.name === integration);
|
|
63
|
+
if (existing && existing.version !== version) {
|
|
64
|
+
throw invalidStateError(`Integration "${integration}" is already pinned at ${existing.version}.`, `Move it to ${version} by editing its "version" in ekanos.json, then run "ekanos sync".`);
|
|
65
|
+
}
|
|
66
|
+
const requested = ((_a = args.flags.ops) !== null && _a !== void 0 ? _a : '')
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((t) => t.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
const explicit = resolveOpTokens(contract, requested);
|
|
71
|
+
const operations = inContractOrder(contract, [
|
|
72
|
+
...(existing ? existing.operations : readOperationIds(contract)),
|
|
73
|
+
...explicit,
|
|
74
|
+
]);
|
|
75
|
+
if (operations.length === 0) {
|
|
76
|
+
throw usageError(`${integration}@${version} has no read operations to allow by default.`, 'Pass --ops <tool/name,...> to choose operations.');
|
|
77
|
+
}
|
|
78
|
+
const entry = {
|
|
79
|
+
name: integration,
|
|
80
|
+
version,
|
|
81
|
+
operations,
|
|
82
|
+
required: (_b = existing === null || existing === void 0 ? void 0 : existing.required) !== null && _b !== void 0 ? _b : 'required',
|
|
83
|
+
modes: (_c = existing === null || existing === void 0 ? void 0 : existing.modes) !== null && _c !== void 0 ? _c : ['team'],
|
|
84
|
+
};
|
|
85
|
+
const integrations = existing
|
|
86
|
+
? project.integrations.map((u) => (u.name === integration ? entry : u))
|
|
87
|
+
: [...project.integrations, entry];
|
|
88
|
+
const integrationsChanged = writeIntegrations(project, integrations);
|
|
89
|
+
const updated = Object.assign(Object.assign({}, project), { integrations });
|
|
90
|
+
const pins = pinIntegration(updated, entry, contract);
|
|
91
|
+
const { pinned } = loadPins(updated);
|
|
92
|
+
const typesChanged = regenerateIntegrations(updated, pinned);
|
|
93
|
+
const changed = integrationsChanged ||
|
|
94
|
+
pins.snapshotChanged ||
|
|
95
|
+
pins.lockChanged ||
|
|
96
|
+
typesChanged;
|
|
97
|
+
const writes = operations.filter((id) => contract.operations.find((op) => op.id === id && op.effect === 'write'));
|
|
98
|
+
return ctx.succeed({
|
|
99
|
+
integration,
|
|
100
|
+
version,
|
|
101
|
+
contractVersion: contract.contractVersion,
|
|
102
|
+
digest: contract.digest,
|
|
103
|
+
operations,
|
|
104
|
+
writes,
|
|
105
|
+
origin,
|
|
106
|
+
changed,
|
|
107
|
+
files: {
|
|
108
|
+
integrationsChanged,
|
|
109
|
+
snapshotChanged: pins.snapshotChanged,
|
|
110
|
+
lockChanged: pins.lockChanged,
|
|
111
|
+
typesChanged,
|
|
112
|
+
},
|
|
113
|
+
}, changed
|
|
114
|
+
? `Added integration "${integration}" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`
|
|
115
|
+
: `Integration "${integration}" is already up to date (no files changed).`);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IntegrationEntry } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { invalidStateError, usageError } from '../errors';\nimport {\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport {\n inContractOrder,\n readOperationIds,\n resolveOpTokens,\n} from '../experience/pin-ops';\nimport { DEPRECATED_USES } from '../experience/pins';\nimport {\n readExperienceProject,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport type { ExperienceCommand } from './index-experience';\n\nconst SPEC = /^([a-z][a-z0-9-]*)@(\\d+(?:\\.\\d+)?)$/;\n\nexport const addCommand: ExperienceCommand = {\n name: 'add',\n usage: [\n ' add Add an integration to an experience: pin its contract,',\n ' record it in ekanos.json + ekanos.lock.json, regenerate types',\n ' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',\n ],\n positionals: ['spec'],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n ops: {\n type: 'string',\n description:\n 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',\n },\n as: {\n type: 'string',\n description:\n 'Removed: an experience addresses an integration by its own name',\n },\n 'from-dir': {\n type: 'string',\n description:\n 'Directory of <integration>.contract.json files (the contract source)',\n },\n 'from-path': {\n type: 'string',\n description:\n 'An integration project directory whose contract.json to pin',\n },\n },\n async run(ctx, args) {\n const spec = args.positionals[0];\n const match = spec ? SPEC.exec(spec) : null;\n if (!match) {\n throw usageError(\n spec\n ? `\"${spec}\" is not <integration>@<version>.`\n : 'add needs <integration>@<version>.',\n 'e.g. \"ekanos add sanity@0.2 --from-dir ../contracts\".',\n );\n }\n const [, integration, version] = match as unknown as [\n string,\n string,\n string,\n ];\n if (args.flags.as !== undefined) {\n throw usageError(\n '--as is gone: an experience addresses an integration by its own name.',\n `Use \"${integration}\" in useOperation('${integration}', ...).`,\n );\n }\n\n const project = readExperienceProject(args.dir);\n if (project.deprecatedUses) ctx.warn(DEPRECATED_USES);\n const sources = defaultSources(\n project,\n {\n fromDir: args.flags['from-dir'] as string | undefined,\n fromPath: args.flags['from-path'] as string | undefined,\n env: args.env,\n },\n await harnessContractSources(project.dir, args.env),\n );\n const { contract, origin } = await resolveContract(\n sources,\n integration,\n version,\n );\n\n const existing = project.integrations.find((u) => u.name === integration);\n if (existing && existing.version !== version) {\n throw invalidStateError(\n `Integration \"${integration}\" is already pinned at ${existing.version}.`,\n `Move it to ${version} by editing its \"version\" in ekanos.json, then run \"ekanos sync\".`,\n );\n }\n\n const requested = ((args.flags.ops as string | undefined) ?? '')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const explicit = resolveOpTokens(contract, requested);\n const operations = inContractOrder(contract, [\n ...(existing ? existing.operations : readOperationIds(contract)),\n ...explicit,\n ]);\n if (operations.length === 0) {\n throw usageError(\n `${integration}@${version} has no read operations to allow by default.`,\n 'Pass --ops <tool/name,...> to choose operations.',\n );\n }\n\n const entry: IntegrationEntry = {\n name: integration,\n version,\n operations,\n required: existing?.required ?? 'required',\n modes: existing?.modes ?? ['team'],\n };\n const integrations = existing\n ? project.integrations.map((u) => (u.name === integration ? entry : u))\n : [...project.integrations, entry];\n const integrationsChanged = writeIntegrations(project, integrations);\n\n const updated = { ...project, integrations };\n const pins = pinIntegration(updated, entry, contract);\n const { pinned } = loadPins(updated);\n const typesChanged = regenerateIntegrations(updated, pinned);\n\n const changed =\n integrationsChanged ||\n pins.snapshotChanged ||\n pins.lockChanged ||\n typesChanged;\n const writes = operations.filter((id) =>\n contract.operations.find((op) => op.id === id && op.effect === 'write'),\n );\n\n return ctx.succeed(\n {\n integration,\n version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n operations,\n writes,\n origin,\n changed,\n files: {\n integrationsChanged,\n snapshotChanged: pins.snapshotChanged,\n lockChanged: pins.lockChanged,\n typesChanged,\n },\n },\n changed\n ? `Added integration \"${integration}\" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`\n : `Integration \"${integration}\" is already up to date (no files changed).`,\n );\n },\n};\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CliContext } from '../context.js';
|
|
2
|
+
import type { ExitCode } from '../exit-codes.js';
|
|
3
|
+
export interface AudiencesArgs {
|
|
4
|
+
host?: string;
|
|
5
|
+
source?: string;
|
|
6
|
+
/** `list`, `show <audience>`, `create <name>`, `set <audience>`. */
|
|
7
|
+
positionals: readonly string[];
|
|
8
|
+
yes: boolean;
|
|
9
|
+
/** `--products`, `--widgets`, `--tools`, `--name`, `--description` … */
|
|
10
|
+
flags?: Record<string, string | boolean | string[]>;
|
|
11
|
+
dir: string;
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `audiences` — a Source's audiences, for its Source admin. An audience is a
|
|
16
|
+
* named set of the Source's products (with optional per-product widget and
|
|
17
|
+
* tool rules) that people see in the app: each team member holds one, their
|
|
18
|
+
* own else their team's default (`ekanos teams audience`). It replaces a
|
|
19
|
+
* product scope; `sources scopes` and `teams scope` keep working until 0.20.
|
|
20
|
+
*
|
|
21
|
+
* list every audience, its products and rules
|
|
22
|
+
* show <audience> one, in full
|
|
23
|
+
* create <name> … --products a,b | --all-entitled, --description,
|
|
24
|
+
* --default, --widgets <product>=<id>,…,
|
|
25
|
+
* --tools <product>=<name>,…
|
|
26
|
+
* set <audience> … the same flags, plus --name; --products
|
|
27
|
+
* replaces the product set (rules of kept
|
|
28
|
+
* products stay unless re-stated). Dropping a
|
|
29
|
+
* product needs --yes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runAudiences(ctx: CliContext, args: AudiencesArgs): Promise<ExitCode>;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { requireSession, resolveAuthEnvironment } from '../auth/session.js';
|
|
2
|
+
import { invalidStateError, usageError } from '../errors.js';
|
|
3
|
+
import { resolveSourceTarget } from '../source-target.js';
|
|
4
|
+
import { createAudience, listAudiences, showAudience, updateAudience, } from '../teams-api.js';
|
|
5
|
+
import { pluralize } from '../ui.js';
|
|
6
|
+
import { TeamsClient } from './teams.js';
|
|
7
|
+
const USAGE = 'Run "ekanos audiences list", "ekanos audiences show <audience>", "ekanos audiences create <name> [--products a,b | --all-entitled]" or "ekanos audiences set <audience> [--products a,b] [--widgets <product>=<id>,…] [--tools <product>=<name>,…]".';
|
|
8
|
+
/**
|
|
9
|
+
* `audiences` — a Source's audiences, for its Source admin. An audience is a
|
|
10
|
+
* named set of the Source's products (with optional per-product widget and
|
|
11
|
+
* tool rules) that people see in the app: each team member holds one, their
|
|
12
|
+
* own else their team's default (`ekanos teams audience`). It replaces a
|
|
13
|
+
* product scope; `sources scopes` and `teams scope` keep working until 0.20.
|
|
14
|
+
*
|
|
15
|
+
* list every audience, its products and rules
|
|
16
|
+
* show <audience> one, in full
|
|
17
|
+
* create <name> … --products a,b | --all-entitled, --description,
|
|
18
|
+
* --default, --widgets <product>=<id>,…,
|
|
19
|
+
* --tools <product>=<name>,…
|
|
20
|
+
* set <audience> … the same flags, plus --name; --products
|
|
21
|
+
* replaces the product set (rules of kept
|
|
22
|
+
* products stay unless re-stated). Dropping a
|
|
23
|
+
* product needs --yes.
|
|
24
|
+
*/
|
|
25
|
+
export async function runAudiences(ctx, args) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const [verb = 'list', rawName, ...extra] = args.positionals;
|
|
28
|
+
if (!['list', 'show', 'create', 'set'].includes(verb)) {
|
|
29
|
+
throw usageError(`Unknown audiences subcommand "${verb}".`, USAGE);
|
|
30
|
+
}
|
|
31
|
+
if (extra.length > 0) {
|
|
32
|
+
throw usageError(`Unexpected argument "${extra[0]}".`, USAGE);
|
|
33
|
+
}
|
|
34
|
+
if (verb === 'list' && rawName) {
|
|
35
|
+
throw usageError(`Unexpected argument "${rawName}".`, USAGE);
|
|
36
|
+
}
|
|
37
|
+
const name = rawName === null || rawName === void 0 ? void 0 : rawName.trim();
|
|
38
|
+
if (verb !== 'list' && !name) {
|
|
39
|
+
throw usageError(verb === 'create'
|
|
40
|
+
? 'audiences create needs a name.'
|
|
41
|
+
: `audiences ${verb} needs an audience (its name or id).`, USAGE);
|
|
42
|
+
}
|
|
43
|
+
const flags = readFlags((_a = args.flags) !== null && _a !== void 0 ? _a : {});
|
|
44
|
+
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
45
|
+
projectDir: args.dir,
|
|
46
|
+
});
|
|
47
|
+
const resolved = await resolveSourceTarget(env, requireSession(env), args);
|
|
48
|
+
const client = new TeamsClient(env, resolved.session);
|
|
49
|
+
const source = resolved.source;
|
|
50
|
+
if (verb === 'list') {
|
|
51
|
+
const list = await client.call((token) => listAudiences(env.host, token, source));
|
|
52
|
+
if (list.audiences.length === 0) {
|
|
53
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
|
|
54
|
+
title: `No audiences on ${list.source.slug}`,
|
|
55
|
+
notes: [`"ekanos audiences create <name> --products a,b" makes one.`],
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
|
|
59
|
+
title: `Audiences on ${list.source.slug}`,
|
|
60
|
+
rows: list.audiences.map((audience) => [
|
|
61
|
+
audience.name,
|
|
62
|
+
describeLines(audience),
|
|
63
|
+
]),
|
|
64
|
+
notes: [
|
|
65
|
+
'A person sees the products of their own audience, else their team’s ("ekanos teams audience <team>").',
|
|
66
|
+
],
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
if (verb === 'show') {
|
|
70
|
+
const shown = await client.call((token) => showAudience(env.host, token, { source, audience: name }));
|
|
71
|
+
return ctx.succeed(Object.assign({ host: env.host }, shown), renderAudience(ctx.ui, shown.audience, `${shown.audience.name}`));
|
|
72
|
+
}
|
|
73
|
+
if (verb === 'create') {
|
|
74
|
+
const body = Object.assign(Object.assign(Object.assign(Object.assign({ name: name }, (flags.description !== undefined
|
|
75
|
+
? { description: flags.description }
|
|
76
|
+
: {})), (flags.isDefault ? { isDefault: true } : {})), (flags.allEntitled !== undefined
|
|
77
|
+
? { includesAllEntitled: flags.allEntitled }
|
|
78
|
+
: {})), (flags.products || flags.widgets.size > 0 || flags.tools.size > 0
|
|
79
|
+
? { products: applyRules([], flags) }
|
|
80
|
+
: {}));
|
|
81
|
+
const created = await client.call((token) => createAudience(env.host, token, source, body));
|
|
82
|
+
return ctx.succeed(Object.assign({ host: env.host }, created), renderAudience(ctx.ui, created.audience, `Created ${created.audience.name}`));
|
|
83
|
+
}
|
|
84
|
+
const current = await client.call((token) => showAudience(env.host, token, { source, audience: name }));
|
|
85
|
+
const before = current.audience;
|
|
86
|
+
const body = Object.assign(Object.assign(Object.assign(Object.assign({}, (flags.name !== undefined ? { name: flags.name } : {})), (flags.description !== undefined
|
|
87
|
+
? { description: flags.description }
|
|
88
|
+
: {})), (flags.isDefault ? { isDefault: true } : {})), (flags.allEntitled !== undefined
|
|
89
|
+
? { includesAllEntitled: flags.allEntitled }
|
|
90
|
+
: {}));
|
|
91
|
+
if (flags.products || flags.widgets.size > 0 || flags.tools.size > 0) {
|
|
92
|
+
body.products = applyRules(before.products.map((product) => (Object.assign(Object.assign({ slug: product.slug }, (product.widgetIds ? { widgetIds: product.widgetIds } : {})), (product.toolNames ? { toolNames: product.toolNames } : {})))), flags);
|
|
93
|
+
}
|
|
94
|
+
if (Object.keys(body).length === 0) {
|
|
95
|
+
throw usageError('Nothing to change.', 'Pass --products, --widgets, --tools, --name, --description, --default or --all-entitled.');
|
|
96
|
+
}
|
|
97
|
+
const kept = new Set(((_b = body.products) !== null && _b !== void 0 ? _b : []).map((product) => product.slug));
|
|
98
|
+
const dropped = body.products
|
|
99
|
+
? before.products.filter((product) => !kept.has(product.slug))
|
|
100
|
+
: [];
|
|
101
|
+
if (dropped.length > 0 && !args.yes) {
|
|
102
|
+
throw invalidStateError(`${before.name} would no longer show ${dropped.map((product) => product.slug).join(', ')}: everyone on it stops seeing ${dropped.length === 1 ? 'it' : 'them'} in the app.`, `Re-run with --yes: ekanos audiences set ${quote(before.name)} … --source ${source} --yes`);
|
|
103
|
+
}
|
|
104
|
+
const updated = await client.call((token) => updateAudience(env.host, token, { source, audience: name }, body));
|
|
105
|
+
return ctx.succeed(Object.assign({ host: env.host }, updated), renderAudience(ctx.ui, updated.audience, `Updated ${updated.audience.name}`, {
|
|
106
|
+
tone: dropped.length > 0 ? 'warn' : 'ok',
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
function readFlags(flags) {
|
|
110
|
+
const text = (key) => typeof flags[key] === 'string' ? flags[key] : undefined;
|
|
111
|
+
const list = (key) => {
|
|
112
|
+
const value = flags[key];
|
|
113
|
+
return Array.isArray(value)
|
|
114
|
+
? value
|
|
115
|
+
: typeof value === 'string'
|
|
116
|
+
? [value]
|
|
117
|
+
: [];
|
|
118
|
+
};
|
|
119
|
+
const products = text('products');
|
|
120
|
+
return {
|
|
121
|
+
name: text('name'),
|
|
122
|
+
description: text('description'),
|
|
123
|
+
isDefault: flags.default === true,
|
|
124
|
+
allEntitled: typeof flags['all-entitled'] === 'boolean'
|
|
125
|
+
? flags['all-entitled']
|
|
126
|
+
: undefined,
|
|
127
|
+
products: products === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: products === 'none'
|
|
130
|
+
? []
|
|
131
|
+
: products
|
|
132
|
+
.split(',')
|
|
133
|
+
.map((slug) => slug.trim().toLowerCase())
|
|
134
|
+
.filter(Boolean),
|
|
135
|
+
widgets: ruleMap('widgets', list('widgets')),
|
|
136
|
+
tools: ruleMap('tools', list('tools')),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** `linear=w1,w2` per product; `linear=` clears the rule (all of them). */
|
|
140
|
+
function ruleMap(flag, values) {
|
|
141
|
+
const rules = new Map();
|
|
142
|
+
for (const value of values) {
|
|
143
|
+
const at = value.indexOf('=');
|
|
144
|
+
const slug = (at < 0 ? value : value.slice(0, at)).trim().toLowerCase();
|
|
145
|
+
if (!slug || at < 0) {
|
|
146
|
+
throw usageError(`--${flag} takes <product>=<name>,<name>… (got "${value}").`, `e.g. --${flag} linear=${flag === 'widgets' ? 'linear-issues' : 'create_issue'}; "<product>=" lifts the rule.`);
|
|
147
|
+
}
|
|
148
|
+
rules.set(slug, value
|
|
149
|
+
.slice(at + 1)
|
|
150
|
+
.split(',')
|
|
151
|
+
.map((item) => item.trim())
|
|
152
|
+
.filter(Boolean));
|
|
153
|
+
}
|
|
154
|
+
return rules;
|
|
155
|
+
}
|
|
156
|
+
/** The product set after `--products` and the widget and tool rules. */
|
|
157
|
+
function applyRules(base, flags) {
|
|
158
|
+
var _a;
|
|
159
|
+
const known = new Map(base.map((product) => [product.slug, product]));
|
|
160
|
+
const slugs = (_a = flags.products) !== null && _a !== void 0 ? _a : base.map((product) => product.slug);
|
|
161
|
+
const products = slugs.map((slug) => {
|
|
162
|
+
const existing = known.get(slug);
|
|
163
|
+
return Object.assign(Object.assign({ slug }, ((existing === null || existing === void 0 ? void 0 : existing.widgetIds) ? { widgetIds: existing.widgetIds } : {})), ((existing === null || existing === void 0 ? void 0 : existing.toolNames) ? { toolNames: existing.toolNames } : {}));
|
|
164
|
+
});
|
|
165
|
+
for (const [kind, rules] of [
|
|
166
|
+
['widgetIds', flags.widgets],
|
|
167
|
+
['toolNames', flags.tools],
|
|
168
|
+
]) {
|
|
169
|
+
for (const [slug, names] of rules) {
|
|
170
|
+
const product = products.find((entry) => entry.slug === slug);
|
|
171
|
+
if (!product) {
|
|
172
|
+
throw usageError(`--${kind === 'widgetIds' ? 'widgets' : 'tools'} names ${slug}, which the audience would not show.`, 'Add it with --products, or name a product that is in the audience.');
|
|
173
|
+
}
|
|
174
|
+
if (names.length > 0)
|
|
175
|
+
product[kind] = names;
|
|
176
|
+
else
|
|
177
|
+
delete product[kind];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return products;
|
|
181
|
+
}
|
|
182
|
+
function describeLines(audience) {
|
|
183
|
+
const lines = [
|
|
184
|
+
[
|
|
185
|
+
audience.includesAllEntitled
|
|
186
|
+
? 'every product the Source has'
|
|
187
|
+
: pluralize(audience.products.length, 'product'),
|
|
188
|
+
audience.isDefault ? 'given to every new team' : null,
|
|
189
|
+
]
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join(' · '),
|
|
192
|
+
];
|
|
193
|
+
if (!audience.includesAllEntitled) {
|
|
194
|
+
lines.push(audience.products.length > 0
|
|
195
|
+
? audience.products.map(productWords).join(', ')
|
|
196
|
+
: 'no products');
|
|
197
|
+
}
|
|
198
|
+
return lines;
|
|
199
|
+
}
|
|
200
|
+
function productWords(product) {
|
|
201
|
+
const rules = [
|
|
202
|
+
product.widgetIds
|
|
203
|
+
? `${pluralize(product.widgetIds.length, 'widget')}`
|
|
204
|
+
: null,
|
|
205
|
+
product.toolNames ? `${pluralize(product.toolNames.length, 'tool')}` : null,
|
|
206
|
+
].filter(Boolean);
|
|
207
|
+
return rules.length > 0
|
|
208
|
+
? `${product.slug} (only ${rules.join(', ')})`
|
|
209
|
+
: product.slug;
|
|
210
|
+
}
|
|
211
|
+
function renderAudience(ui, audience, title, options = {}) {
|
|
212
|
+
var _a;
|
|
213
|
+
return ui.result({
|
|
214
|
+
title,
|
|
215
|
+
tone: (_a = options.tone) !== null && _a !== void 0 ? _a : 'ok',
|
|
216
|
+
rows: [
|
|
217
|
+
['Audience', audience.name],
|
|
218
|
+
...(audience.description
|
|
219
|
+
? [['About', audience.description]]
|
|
220
|
+
: []),
|
|
221
|
+
['Default', audience.isDefault ? 'yes, new teams start on it' : 'no'],
|
|
222
|
+
[
|
|
223
|
+
'Products',
|
|
224
|
+
audience.includesAllEntitled
|
|
225
|
+
? 'every product the Source has, now and later'
|
|
226
|
+
: audience.products.length > 0
|
|
227
|
+
? audience.products.map(productWords)
|
|
228
|
+
: 'none',
|
|
229
|
+
],
|
|
230
|
+
],
|
|
231
|
+
notes: [
|
|
232
|
+
'Teams and members are put on an audience with "ekanos teams audience <team> [<audience>]".',
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function quote(name) {
|
|
237
|
+
return /^[A-Za-z0-9._-]+$/.test(name) ? name : `"${name}"`;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=audiences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audiences.js","sourceRoot":"","sources":["../../src/commands/audiences.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Audience,\n AudienceCreateRequest,\n AudienceUpdateRequest,\n} from '@ekanos/integration-schema';\n\nimport { requireSession, resolveAuthEnvironment } from '../auth/session';\nimport type { CliContext } from '../context';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport {\n createAudience,\n listAudiences,\n showAudience,\n updateAudience,\n} from '../teams-api';\nimport { type Ui, pluralize } from '../ui';\nimport { TeamsClient } from './teams';\n\nexport interface AudiencesArgs {\n host?: string;\n source?: string;\n /** `list`, `show <audience>`, `create <name>`, `set <audience>`. */\n positionals: readonly string[];\n yes: boolean;\n /** `--products`, `--widgets`, `--tools`, `--name`, `--description` … */\n flags?: Record<string, string | boolean | string[]>;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\nconst USAGE =\n 'Run \"ekanos audiences list\", \"ekanos audiences show <audience>\", \"ekanos audiences create <name> [--products a,b | --all-entitled]\" or \"ekanos audiences set <audience> [--products a,b] [--widgets <product>=<id>,…] [--tools <product>=<name>,…]\".';\n\ntype ProductRule = { slug: string; widgetIds?: string[]; toolNames?: string[] };\n\n/**\n * `audiences` — a Source's audiences, for its Source admin. An audience is a\n * named set of the Source's products (with optional per-product widget and\n * tool rules) that people see in the app: each team member holds one, their\n * own else their team's default (`ekanos teams audience`). It replaces a\n * product scope; `sources scopes` and `teams scope` keep working until 0.20.\n *\n * list every audience, its products and rules\n * show <audience> one, in full\n * create <name> … --products a,b | --all-entitled, --description,\n * --default, --widgets <product>=<id>,…,\n * --tools <product>=<name>,…\n * set <audience> … the same flags, plus --name; --products\n * replaces the product set (rules of kept\n * products stay unless re-stated). Dropping a\n * product needs --yes.\n */\nexport async function runAudiences(\n ctx: CliContext,\n args: AudiencesArgs,\n): Promise<ExitCode> {\n const [verb = 'list', rawName, ...extra] = args.positionals;\n if (!['list', 'show', 'create', 'set'].includes(verb)) {\n throw usageError(`Unknown audiences subcommand \"${verb}\".`, USAGE);\n }\n if (extra.length > 0) {\n throw usageError(`Unexpected argument \"${extra[0]}\".`, USAGE);\n }\n if (verb === 'list' && rawName) {\n throw usageError(`Unexpected argument \"${rawName}\".`, USAGE);\n }\n const name = rawName?.trim();\n if (verb !== 'list' && !name) {\n throw usageError(\n verb === 'create'\n ? 'audiences create needs a name.'\n : `audiences ${verb} needs an audience (its name or id).`,\n USAGE,\n );\n }\n const flags = readFlags(args.flags ?? {});\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(env, requireSession(env), args);\n const client = new TeamsClient(env, resolved.session);\n const source = resolved.source;\n\n if (verb === 'list') {\n const list = await client.call((token) =>\n listAudiences(env.host, token, source),\n );\n if (list.audiences.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No audiences on ${list.source.slug}`,\n notes: [`\"ekanos audiences create <name> --products a,b\" makes one.`],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `Audiences on ${list.source.slug}`,\n rows: list.audiences.map((audience): [string, string[]] => [\n audience.name,\n describeLines(audience),\n ]),\n notes: [\n 'A person sees the products of their own audience, else their team’s (\"ekanos teams audience <team>\").',\n ],\n }),\n );\n }\n\n if (verb === 'show') {\n const shown = await client.call((token) =>\n showAudience(env.host, token, { source, audience: name! }),\n );\n return ctx.succeed(\n { host: env.host, ...shown },\n renderAudience(ctx.ui, shown.audience, `${shown.audience.name}`),\n );\n }\n\n if (verb === 'create') {\n const body: AudienceCreateRequest = {\n name: name!,\n ...(flags.description !== undefined\n ? { description: flags.description }\n : {}),\n ...(flags.isDefault ? { isDefault: true } : {}),\n ...(flags.allEntitled !== undefined\n ? { includesAllEntitled: flags.allEntitled }\n : {}),\n ...(flags.products || flags.widgets.size > 0 || flags.tools.size > 0\n ? { products: applyRules([], flags) }\n : {}),\n };\n const created = await client.call((token) =>\n createAudience(env.host, token, source, body),\n );\n return ctx.succeed(\n { host: env.host, ...created },\n renderAudience(\n ctx.ui,\n created.audience,\n `Created ${created.audience.name}`,\n ),\n );\n }\n\n const current = await client.call((token) =>\n showAudience(env.host, token, { source, audience: name! }),\n );\n const before = current.audience;\n const body: AudienceUpdateRequest = {\n ...(flags.name !== undefined ? { name: flags.name } : {}),\n ...(flags.description !== undefined\n ? { description: flags.description }\n : {}),\n ...(flags.isDefault ? { isDefault: true } : {}),\n ...(flags.allEntitled !== undefined\n ? { includesAllEntitled: flags.allEntitled }\n : {}),\n };\n if (flags.products || flags.widgets.size > 0 || flags.tools.size > 0) {\n body.products = applyRules(\n before.products.map((product) => ({\n slug: product.slug,\n ...(product.widgetIds ? { widgetIds: product.widgetIds } : {}),\n ...(product.toolNames ? { toolNames: product.toolNames } : {}),\n })),\n flags,\n );\n }\n if (Object.keys(body).length === 0) {\n throw usageError(\n 'Nothing to change.',\n 'Pass --products, --widgets, --tools, --name, --description, --default or --all-entitled.',\n );\n }\n\n const kept = new Set((body.products ?? []).map((product) => product.slug));\n const dropped = body.products\n ? before.products.filter((product) => !kept.has(product.slug))\n : [];\n if (dropped.length > 0 && !args.yes) {\n throw invalidStateError(\n `${before.name} would no longer show ${dropped.map((product) => product.slug).join(', ')}: everyone on it stops seeing ${dropped.length === 1 ? 'it' : 'them'} in the app.`,\n `Re-run with --yes: ekanos audiences set ${quote(before.name)} … --source ${source} --yes`,\n );\n }\n\n const updated = await client.call((token) =>\n updateAudience(env.host, token, { source, audience: name! }, body),\n );\n return ctx.succeed(\n { host: env.host, ...updated },\n renderAudience(\n ctx.ui,\n updated.audience,\n `Updated ${updated.audience.name}`,\n {\n tone: dropped.length > 0 ? 'warn' : 'ok',\n },\n ),\n );\n}\n\ninterface AudienceFlags {\n name?: string;\n description?: string;\n isDefault: boolean;\n allEntitled?: boolean;\n /** Slugs, in the order given; absent when the flag was not passed. */\n products?: string[];\n widgets: Map<string, string[]>;\n tools: Map<string, string[]>;\n}\n\nfunction readFlags(\n flags: Record<string, string | boolean | string[]>,\n): AudienceFlags {\n const text = (key: string) =>\n typeof flags[key] === 'string' ? (flags[key] as string) : undefined;\n const list = (key: string) => {\n const value = flags[key];\n return Array.isArray(value)\n ? value\n : typeof value === 'string'\n ? [value]\n : [];\n };\n\n const products = text('products');\n return {\n name: text('name'),\n description: text('description'),\n isDefault: flags.default === true,\n allEntitled:\n typeof flags['all-entitled'] === 'boolean'\n ? (flags['all-entitled'] as boolean)\n : undefined,\n products:\n products === undefined\n ? undefined\n : products === 'none'\n ? []\n : products\n .split(',')\n .map((slug) => slug.trim().toLowerCase())\n .filter(Boolean),\n widgets: ruleMap('widgets', list('widgets')),\n tools: ruleMap('tools', list('tools')),\n };\n}\n\n/** `linear=w1,w2` per product; `linear=` clears the rule (all of them). */\nfunction ruleMap(flag: string, values: string[]): Map<string, string[]> {\n const rules = new Map<string, string[]>();\n for (const value of values) {\n const at = value.indexOf('=');\n const slug = (at < 0 ? value : value.slice(0, at)).trim().toLowerCase();\n if (!slug || at < 0) {\n throw usageError(\n `--${flag} takes <product>=<name>,<name>… (got \"${value}\").`,\n `e.g. --${flag} linear=${flag === 'widgets' ? 'linear-issues' : 'create_issue'}; \"<product>=\" lifts the rule.`,\n );\n }\n rules.set(\n slug,\n value\n .slice(at + 1)\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean),\n );\n }\n return rules;\n}\n\n/** The product set after `--products` and the widget and tool rules. */\nfunction applyRules(base: ProductRule[], flags: AudienceFlags): ProductRule[] {\n const known = new Map(base.map((product) => [product.slug, product]));\n const slugs = flags.products ?? base.map((product) => product.slug);\n\n const products = slugs.map((slug): ProductRule => {\n const existing = known.get(slug);\n return {\n slug,\n ...(existing?.widgetIds ? { widgetIds: existing.widgetIds } : {}),\n ...(existing?.toolNames ? { toolNames: existing.toolNames } : {}),\n };\n });\n for (const [kind, rules] of [\n ['widgetIds', flags.widgets],\n ['toolNames', flags.tools],\n ] as const) {\n for (const [slug, names] of rules) {\n const product = products.find((entry) => entry.slug === slug);\n if (!product) {\n throw usageError(\n `--${kind === 'widgetIds' ? 'widgets' : 'tools'} names ${slug}, which the audience would not show.`,\n 'Add it with --products, or name a product that is in the audience.',\n );\n }\n if (names.length > 0) product[kind] = names;\n else delete product[kind];\n }\n }\n return products;\n}\n\nfunction describeLines(audience: Audience): string[] {\n const lines = [\n [\n audience.includesAllEntitled\n ? 'every product the Source has'\n : pluralize(audience.products.length, 'product'),\n audience.isDefault ? 'given to every new team' : null,\n ]\n .filter(Boolean)\n .join(' · '),\n ];\n if (!audience.includesAllEntitled) {\n lines.push(\n audience.products.length > 0\n ? audience.products.map(productWords).join(', ')\n : 'no products',\n );\n }\n return lines;\n}\n\nfunction productWords(product: Audience['products'][number]): string {\n const rules = [\n product.widgetIds\n ? `${pluralize(product.widgetIds.length, 'widget')}`\n : null,\n product.toolNames ? `${pluralize(product.toolNames.length, 'tool')}` : null,\n ].filter(Boolean);\n return rules.length > 0\n ? `${product.slug} (only ${rules.join(', ')})`\n : product.slug;\n}\n\nfunction renderAudience(\n ui: Ui,\n audience: Audience,\n title: string,\n options: { tone?: 'ok' | 'warn' } = {},\n): string {\n return ui.result({\n title,\n tone: options.tone ?? 'ok',\n rows: [\n ['Audience', audience.name],\n ...(audience.description\n ? ([['About', audience.description]] as Array<[string, string]>)\n : []),\n ['Default', audience.isDefault ? 'yes, new teams start on it' : 'no'],\n [\n 'Products',\n audience.includesAllEntitled\n ? 'every product the Source has, now and later'\n : audience.products.length > 0\n ? audience.products.map(productWords)\n : 'none',\n ],\n ],\n notes: [\n 'Teams and members are put on an audience with \"ekanos teams audience <team> [<audience>]\".',\n ],\n });\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { emitExperience } from '../experience/build.js';
|
|
2
|
+
import { runExperienceChecks } from '../experience/checks.js';
|
|
3
|
+
import { readExperienceProject } from '../experience/project.js';
|
|
4
|
+
import { failOnErrors } from '../experience/report.js';
|
|
5
|
+
export const buildCommand = {
|
|
6
|
+
name: 'build',
|
|
7
|
+
usage: [
|
|
8
|
+
' build Build an experience: gate (pins, boundary, definition), then emit',
|
|
9
|
+
' dist/ and manifest.json',
|
|
10
|
+
],
|
|
11
|
+
specs: {
|
|
12
|
+
dir: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'Project directory (default: the current one)',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
async run(ctx, args) {
|
|
18
|
+
const project = readExperienceProject(args.dir);
|
|
19
|
+
const result = await runExperienceChecks(project, {
|
|
20
|
+
typecheck: false,
|
|
21
|
+
loadDefinition: true,
|
|
22
|
+
inlineSdk: args.env.EKANOS_INLINE_SDK,
|
|
23
|
+
});
|
|
24
|
+
const failed = failOnErrors(ctx, 'build', result.findings, { slug: project.slug }, project.dir);
|
|
25
|
+
if (failed !== null)
|
|
26
|
+
return failed;
|
|
27
|
+
const output = await emitExperience(project, result);
|
|
28
|
+
return ctx.succeed({
|
|
29
|
+
slug: project.slug,
|
|
30
|
+
version: output.manifest.version,
|
|
31
|
+
digest: output.manifest.digest,
|
|
32
|
+
files: output.files,
|
|
33
|
+
widgets: output.manifest.widgets.length,
|
|
34
|
+
pages: output.manifest.pages.length,
|
|
35
|
+
workflowTemplates: output.manifest.workflowTemplates.length,
|
|
36
|
+
findings: result.findings,
|
|
37
|
+
changed: output.changed,
|
|
38
|
+
}, `Built ${project.slug}@${output.manifest.version} (${output.manifest.digest.slice(0, 18)}…): ${output.files.join(', ')}.`);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC7C,IAAI,EAAE,OAAO;IACb,KAAK,EAAE;QACL,gFAAgF;QAChF,sCAAsC;KACvC;IACD,KAAK,EAAE;QACL,GAAG,EAAE;YACH,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,8CAA8C;SAC5D;KACF;IACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI;QACjB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE;YAChD,SAAS,EAAE,KAAK;YAChB,cAAc,EAAE,IAAI;YACpB,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB;SACtC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,YAAY,CACzB,GAAG,EACH,OAAO,EACP,MAAM,CAAC,QAAQ,EACf,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EACtB,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;QAEnC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAErD,OAAO,GAAG,CAAC,OAAO,CAChB;YACE,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO;YAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM;YACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM;YACnC,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,MAAM;YAC3D,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,EACD,SAAS,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1H,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { emitExperience } from '../experience/build';\nimport { runExperienceChecks } from '../experience/checks';\nimport { readExperienceProject } from '../experience/project';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const buildCommand: ExperienceCommand = {\n name: 'build',\n usage: [\n ' build Build an experience: gate (pins, boundary, definition), then emit',\n ' dist/ and manifest.json',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const result = await runExperienceChecks(project, {\n typecheck: false,\n loadDefinition: true,\n inlineSdk: args.env.EKANOS_INLINE_SDK,\n });\n\n const failed = failOnErrors(\n ctx,\n 'build',\n result.findings,\n { slug: project.slug },\n project.dir,\n );\n if (failed !== null) return failed;\n\n const output = await emitExperience(project, result);\n\n return ctx.succeed(\n {\n slug: project.slug,\n version: output.manifest.version,\n digest: output.manifest.digest,\n files: output.files,\n widgets: output.manifest.widgets.length,\n pages: output.manifest.pages.length,\n workflowTemplates: output.manifest.workflowTemplates.length,\n findings: result.findings,\n changed: output.changed,\n },\n `Built ${project.slug}@${output.manifest.version} (${output.manifest.digest.slice(0, 18)}…): ${output.files.join(', ')}.`,\n );\n },\n};\n"]}
|