@ekanos/cli 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/commands/add.js +1 -1
  2. package/dist/commands/add.js.map +1 -1
  3. package/dist/commands/audiences.d.ts +5 -3
  4. package/dist/commands/audiences.js +6 -4
  5. package/dist/commands/audiences.js.map +1 -1
  6. package/dist/commands/scope-notice.d.ts +5 -4
  7. package/dist/commands/scope-notice.js +22 -11
  8. package/dist/commands/scope-notice.js.map +1 -1
  9. package/dist/commands/sync.js +2 -2
  10. package/dist/commands/sync.js.map +1 -1
  11. package/dist/commands/teams-audience.d.ts +11 -2
  12. package/dist/commands/teams-audience.js +22 -7
  13. package/dist/commands/teams-audience.js.map +1 -1
  14. package/dist/commands/teams-lifecycle.js +8 -2
  15. package/dist/commands/teams-lifecycle.js.map +1 -1
  16. package/dist/experience/contract-sources.d.ts +7 -2
  17. package/dist/experience/contract-sources.js +15 -8
  18. package/dist/experience/contract-sources.js.map +1 -1
  19. package/dist/experience/definition-checks.js +4 -1
  20. package/dist/experience/definition-checks.js.map +1 -1
  21. package/dist/experience/definition-shape.d.ts +2 -0
  22. package/dist/experience/definition-shape.js.map +1 -1
  23. package/dist/experience/project.js +8 -2
  24. package/dist/experience/project.js.map +1 -1
  25. package/dist/index.js +13 -9
  26. package/dist/index.js.map +1 -1
  27. package/dist/team-lifecycle-api.d.ts +30 -0
  28. package/dist/team-lifecycle-api.js +2 -0
  29. package/dist/team-lifecycle-api.js.map +1 -1
  30. package/dist/teams-api.js +29 -24
  31. package/dist/teams-api.js.map +1 -1
  32. package/package.json +2 -2
  33. package/templates/experience/package.json.tmpl +4 -4
  34. package/templates/package.json.tmpl +4 -4
@@ -6,7 +6,7 @@ import { inContractOrder, readOperationIds, resolveOpTokens, } from '../experien
6
6
  import { DEPRECATED_USES } from '../experience/pins.js';
7
7
  import { readExperienceProject, writeIntegrations, } from '../experience/project.js';
8
8
  import { loadPins, regenerateIntegrations } from '../experience/regenerate.js';
9
- const SPEC = /^([a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
9
+ const SPEC = /^((?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/)?[a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
10
10
  export const addCommand = {
11
11
  name: 'add',
12
12
  usage: [
@@ -1 +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"]}
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 =\n /^((?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\\/)?[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"]}
@@ -14,9 +14,11 @@ export interface AudiencesArgs {
14
14
  /**
15
15
  * `audiences` — a Source's audiences, for its Source admin. An audience is a
16
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.
17
+ * tool rules) that people see in the app. Each team is on one, its business
18
+ * type (`ekanos teams audience`), and every member is on their team's; the
19
+ * team's groups narrow it per person (`ekanos teams groups`). It replaces a
20
+ * product scope; `sources scopes` and `teams scope` keep working until the
21
+ * 2027-03-01 sunset.
20
22
  *
21
23
  * list every audience, its products and rules
22
24
  * show <audience> one, in full
@@ -8,9 +8,11 @@ const USAGE = 'Run "ekanos audiences list", "ekanos audiences show <audience>",
8
8
  /**
9
9
  * `audiences` — a Source's audiences, for its Source admin. An audience is a
10
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.
11
+ * tool rules) that people see in the app. Each team is on one, its business
12
+ * type (`ekanos teams audience`), and every member is on their team's; the
13
+ * team's groups narrow it per person (`ekanos teams groups`). It replaces a
14
+ * product scope; `sources scopes` and `teams scope` keep working until the
15
+ * 2027-03-01 sunset.
14
16
  *
15
17
  * list every audience, its products and rules
16
18
  * show <audience> one, in full
@@ -62,7 +64,7 @@ export async function runAudiences(ctx, args) {
62
64
  describeLines(audience),
63
65
  ]),
64
66
  notes: [
65
- 'A person sees the products of their own audience, else their team’s ("ekanos teams audience <team>").',
67
+ 'A person sees their team’s audience ("ekanos teams audience <team>"), narrowed by their groups ("ekanos teams groups list <team>").',
66
68
  ],
67
69
  }));
68
70
  }
@@ -1 +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"]}
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 is on one, its business\n * type (`ekanos teams audience`), and every member is on their team's; the\n * team's groups narrow it per person (`ekanos teams groups`). It replaces a\n * product scope; `sources scopes` and `teams scope` keep working until the\n * 2027-03-01 sunset.\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 their team’s audience (\"ekanos teams audience <team>\"), narrowed by their groups (\"ekanos teams groups list <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"]}
@@ -2,14 +2,15 @@ import type { TeamScopeState } from '@ekanos/integration-schema';
2
2
  /**
3
3
  * A product the CLI reports as ready for a team can still be hidden from the
4
4
  * team in the app by the team's product scopes — the "Bronze" scope that
5
- * hid Linear and Zendesk on a team whose setup read as done. Wherever the
6
- * CLI says a product is ready, it says this too, with the command that
7
- * changes it.
5
+ * hid Linear and Zendesk on a team whose setup read as done — or, once a
6
+ * Source's scopes are retired, by the team's audience (its business type).
7
+ * Wherever the CLI says a product is ready, it says this too, with the
8
+ * command that changes it.
8
9
  */
9
10
  /**
10
11
  * The one line `sources scopes` and `teams scope` print (to stderr, so JSON
11
12
  * on stdout is unchanged): a scope is an audience now. Both commands keep
12
- * their published behaviour until 0.20.
13
+ * their published behaviour until the scope routes' 2027-03-01 sunset.
13
14
  */
14
15
  export declare function scopeDeprecationNotice(replacement: string): string;
15
16
  export interface ScopeFacts {
@@ -2,30 +2,41 @@ import { cli } from '../invocation.js';
2
2
  /**
3
3
  * A product the CLI reports as ready for a team can still be hidden from the
4
4
  * team in the app by the team's product scopes — the "Bronze" scope that
5
- * hid Linear and Zendesk on a team whose setup read as done. Wherever the
6
- * CLI says a product is ready, it says this too, with the command that
7
- * changes it.
5
+ * hid Linear and Zendesk on a team whose setup read as done — or, once a
6
+ * Source's scopes are retired, by the team's audience (its business type).
7
+ * Wherever the CLI says a product is ready, it says this too, with the
8
+ * command that changes it.
8
9
  */
9
10
  /**
10
11
  * The one line `sources scopes` and `teams scope` print (to stderr, so JSON
11
12
  * on stdout is unchanged): a scope is an audience now. Both commands keep
12
- * their published behaviour until 0.20.
13
+ * their published behaviour until the scope routes' 2027-03-01 sunset.
13
14
  */
14
15
  export function scopeDeprecationNotice(replacement) {
15
- return `Deprecated: product scopes are audiences now. Use "${cli()} ${replacement}"; this command keeps working until 0.20.`;
16
+ return `Deprecated: product scopes are audiences now. Use "${cli()} ${replacement}"; this command keeps working until 2027-03-01.`;
16
17
  }
17
18
  /** "hidden by its product scope Bronze" — the problem, in words. */
18
19
  export function scopeHiddenWords(scope) {
19
- return scope.state === 'locked_out' || scope.scopes.length === 0
20
- ? 'hidden from the team in the app: it holds none of the Source’s product scopes'
21
- : `hidden from the team in the app by its product scope${scope.scopes.length === 1 ? '' : 's'} ${scope.scopes.join(', ')}`;
20
+ // No scope held and still scoped: the team's audience (its business type)
21
+ // decides, which only a host whose scopes are retired reports.
22
+ if (scope.state === 'locked_out') {
23
+ return 'hidden from the team in the app: it holds none of the Source’s product scopes, and no audience';
24
+ }
25
+ if (scope.scopes.length === 0) {
26
+ return 'hidden from the team in the app by its audience';
27
+ }
28
+ return `hidden from the team in the app by its product scope${scope.scopes.length === 1 ? '' : 's'} ${scope.scopes.join(', ')}`;
22
29
  }
23
30
  /** The command that shows the product to the team. */
24
31
  export function scopeFix(scope, target) {
25
- const set = `${cli()} teams scope set ${target.team} <scope> --source ${target.source}`;
26
- if (scope.state === 'locked_out' || scope.scopes.length === 0) {
27
- return `${set} (scopes and their products: ${cli()} sources scopes --source ${target.source})`;
32
+ const audience = `${cli()} teams audience ${target.team} <audience> --source ${target.source}`;
33
+ if (scope.state === 'locked_out') {
34
+ return `${audience} (audiences and their products: ${cli()} audiences list --source ${target.source}; a host still on scopes: ${cli()} teams scope set ${target.team} <scope>)`;
28
35
  }
36
+ if (scope.scopes.length === 0) {
37
+ return `"${cli()} teams audience ${target.team} --source ${target.source}" names its audience; add ${target.product} to it with "${cli()} audiences set <audience> --products …", or move the team: ${audience}`;
38
+ }
39
+ const set = `${cli()} teams scope set ${target.team} <scope> --source ${target.source}`;
29
40
  return `${cli()} sources scopes add ${quote(scope.scopes[0])} ${target.product} --source ${target.source} (every team on ${scope.scopes[0]} then sees it; or move the team to a scope that has it: ${set})`;
30
41
  }
31
42
  /** The two lines `teams show` / `teams connect` print for a hidden product. */
@@ -1 +1 @@
1
- {"version":3,"file":"scope-notice.js","sourceRoot":"","sources":["../../src/commands/scope-notice.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,OAAO,sDAAsD,GAAG,EAAE,IAAI,WAAW,2CAA2C,CAAC;AAC/H,CAAC;AAOD,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO,KAAK,CAAC,KAAK,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAC9D,CAAC,CAAC,+EAA+E;QACjF,CAAC,CAAC,uDAAuD,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,QAAQ,CACtB,KAAiB,EACjB,MAAyD;IAEzD,MAAM,GAAG,GAAG,GAAG,GAAG,EAAE,oBAAoB,MAAM,CAAC,IAAI,qBAAqB,MAAM,CAAC,MAAM,EAAE,CAAC;IACxF,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAG,GAAG,iCAAiC,GAAG,EAAE,4BAA4B,MAAM,CAAC,MAAM,GAAG,CAAC;IAClG,CAAC;IACD,OAAO,GAAG,GAAG,EAAE,uBAAuB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,MAAM,oBAAoB,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,2DAA2D,GAAG,GAAG,CAAC;AAChN,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAC9B,KAAqD,EACrD,MAAyD;IAEzD,IAAI,EAAC,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAAM,CAAA;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO;QACL,KAAK,MAAM,CAAC,OAAO,OAAO,gBAAgB,CAAC,KAAK,CAAC,GAAG;QACpD,UAAU,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;AAC7D,CAAC","sourcesContent":["import type { TeamScopeState } from '@ekanos/integration-schema';\n\nimport { cli } from '../invocation';\n\n/**\n * A product the CLI reports as ready for a team can still be hidden from the\n * team in the app by the team's product scopes — the \"Bronze\" scope that\n * hid Linear and Zendesk on a team whose setup read as done. Wherever the\n * CLI says a product is ready, it says this too, with the command that\n * changes it.\n */\n\n/**\n * The one line `sources scopes` and `teams scope` print (to stderr, so JSON\n * on stdout is unchanged): a scope is an audience now. Both commands keep\n * their published behaviour until 0.20.\n */\nexport function scopeDeprecationNotice(replacement: string): string {\n return `Deprecated: product scopes are audiences now. Use \"${cli()} ${replacement}\"; this command keeps working until 0.20.`;\n}\n\nexport interface ScopeFacts {\n state: TeamScopeState;\n scopes: string[];\n}\n\n/** \"hidden by its product scope Bronze\" — the problem, in words. */\nexport function scopeHiddenWords(scope: ScopeFacts): string {\n return scope.state === 'locked_out' || scope.scopes.length === 0\n ? 'hidden from the team in the app: it holds none of the Source’s product scopes'\n : `hidden from the team in the app by its product scope${scope.scopes.length === 1 ? '' : 's'} ${scope.scopes.join(', ')}`;\n}\n\n/** The command that shows the product to the team. */\nexport function scopeFix(\n scope: ScopeFacts,\n target: { team: string; product: string; source: string },\n): string {\n const set = `${cli()} teams scope set ${target.team} <scope> --source ${target.source}`;\n if (scope.state === 'locked_out' || scope.scopes.length === 0) {\n return `${set} (scopes and their products: ${cli()} sources scopes --source ${target.source})`;\n }\n return `${cli()} sources scopes add ${quote(scope.scopes[0]!)} ${target.product} --source ${target.source} (every team on ${scope.scopes[0]} then sees it; or move the team to a scope that has it: ${set})`;\n}\n\n/** The two lines `teams show` / `teams connect` print for a hidden product. */\nexport function scopeHiddenLines(\n scope: (ScopeFacts & { hidden: boolean }) | undefined,\n target: { team: string; product: string; source: string },\n): string[] {\n if (!scope?.hidden) return [];\n return [\n `! ${target.product} is ${scopeHiddenWords(scope)}.`,\n ` Fix: ${scopeFix(scope, target)}`,\n ];\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
1
+ {"version":3,"file":"scope-notice.js","sourceRoot":"","sources":["../../src/commands/scope-notice.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,OAAO,sDAAsD,GAAG,EAAE,IAAI,WAAW,iDAAiD,CAAC;AACrI,CAAC;AAOD,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,0EAA0E;IAC1E,+DAA+D;IAC/D,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QACjC,OAAO,gGAAgG,CAAC;IAC1G,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,iDAAiD,CAAC;IAC3D,CAAC;IACD,OAAO,uDAAuD,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAClI,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,QAAQ,CACtB,KAAiB,EACjB,MAAyD;IAEzD,MAAM,QAAQ,GAAG,GAAG,GAAG,EAAE,mBAAmB,MAAM,CAAC,IAAI,wBAAwB,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/F,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QACjC,OAAO,GAAG,QAAQ,oCAAoC,GAAG,EAAE,4BAA4B,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,oBAAoB,MAAM,CAAC,IAAI,WAAW,CAAC;IACnL,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,GAAG,EAAE,mBAAmB,MAAM,CAAC,IAAI,aAAa,MAAM,CAAC,MAAM,6BAA6B,MAAM,CAAC,OAAO,gBAAgB,GAAG,EAAE,8DAA8D,QAAQ,EAAE,CAAC;IACnN,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,GAAG,EAAE,oBAAoB,MAAM,CAAC,IAAI,qBAAqB,MAAM,CAAC,MAAM,EAAE,CAAC;IACxF,OAAO,GAAG,GAAG,EAAE,uBAAuB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,MAAM,oBAAoB,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,2DAA2D,GAAG,GAAG,CAAC;AAChN,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAC9B,KAAqD,EACrD,MAAyD;IAEzD,IAAI,EAAC,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAAM,CAAA;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO;QACL,KAAK,MAAM,CAAC,OAAO,OAAO,gBAAgB,CAAC,KAAK,CAAC,GAAG;QACpD,UAAU,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;AAC7D,CAAC","sourcesContent":["import type { TeamScopeState } from '@ekanos/integration-schema';\n\nimport { cli } from '../invocation';\n\n/**\n * A product the CLI reports as ready for a team can still be hidden from the\n * team in the app by the team's product scopes — the \"Bronze\" scope that\n * hid Linear and Zendesk on a team whose setup read as done — or, once a\n * Source's scopes are retired, by the team's audience (its business type).\n * Wherever the CLI says a product is ready, it says this too, with the\n * command that changes it.\n */\n\n/**\n * The one line `sources scopes` and `teams scope` print (to stderr, so JSON\n * on stdout is unchanged): a scope is an audience now. Both commands keep\n * their published behaviour until the scope routes' 2027-03-01 sunset.\n */\nexport function scopeDeprecationNotice(replacement: string): string {\n return `Deprecated: product scopes are audiences now. Use \"${cli()} ${replacement}\"; this command keeps working until 2027-03-01.`;\n}\n\nexport interface ScopeFacts {\n state: TeamScopeState;\n scopes: string[];\n}\n\n/** \"hidden by its product scope Bronze\" — the problem, in words. */\nexport function scopeHiddenWords(scope: ScopeFacts): string {\n // No scope held and still scoped: the team's audience (its business type)\n // decides, which only a host whose scopes are retired reports.\n if (scope.state === 'locked_out') {\n return 'hidden from the team in the app: it holds none of the Source’s product scopes, and no audience';\n }\n if (scope.scopes.length === 0) {\n return 'hidden from the team in the app by its audience';\n }\n return `hidden from the team in the app by its product scope${scope.scopes.length === 1 ? '' : 's'} ${scope.scopes.join(', ')}`;\n}\n\n/** The command that shows the product to the team. */\nexport function scopeFix(\n scope: ScopeFacts,\n target: { team: string; product: string; source: string },\n): string {\n const audience = `${cli()} teams audience ${target.team} <audience> --source ${target.source}`;\n if (scope.state === 'locked_out') {\n return `${audience} (audiences and their products: ${cli()} audiences list --source ${target.source}; a host still on scopes: ${cli()} teams scope set ${target.team} <scope>)`;\n }\n if (scope.scopes.length === 0) {\n return `\"${cli()} teams audience ${target.team} --source ${target.source}\" names its audience; add ${target.product} to it with \"${cli()} audiences set <audience> --products …\", or move the team: ${audience}`;\n }\n const set = `${cli()} teams scope set ${target.team} <scope> --source ${target.source}`;\n return `${cli()} sources scopes add ${quote(scope.scopes[0]!)} ${target.product} --source ${target.source} (every team on ${scope.scopes[0]} then sees it; or move the team to a scope that has it: ${set})`;\n}\n\n/** The two lines `teams show` / `teams connect` print for a hidden product. */\nexport function scopeHiddenLines(\n scope: (ScopeFacts & { hidden: boolean }) | undefined,\n target: { team: string; product: string; source: string },\n): string[] {\n if (!scope?.hidden) return [];\n return [\n `! ${target.product} is ${scopeHiddenWords(scope)}.`,\n ` Fix: ${scopeFix(scope, target)}`,\n ];\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
@@ -87,9 +87,9 @@ export const syncCommand = {
87
87
  const contract = candidate.get(use.name);
88
88
  if (!contract)
89
89
  return [];
90
- const have = new Set(contract.operations.map((op) => op.id));
90
+ const have = new Set(contract.operations.map((op) => shortOperation(op.id)));
91
91
  return use.operations
92
- .filter((id) => !have.has(id))
92
+ .filter((id) => !have.has(shortOperation(id)))
93
93
  .map((id) => ({
94
94
  check: 'checks/integration-pins',
95
95
  severity: 'error',
@@ -1 +1 @@
1
- {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { shortOperation } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { validationError } from '../errors';\nimport {\n type ResolvedContract,\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport { DEPRECATED_USES, renamesNotice } from '../experience/pins';\nimport {\n readExperienceProject,\n readLockfile,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const syncCommand: ExperienceCommand = {\n name: 'sync',\n usage: [\n \" sync Re-resolve every integration's contract, accept a new digest within the\",\n ' pinned line, and regenerate the lock + types (--check: report only)',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n check: {\n type: 'boolean',\n description:\n 'Report drift without writing anything; exit 3 when there is some',\n },\n 'from-dir': {\n type: 'string',\n description: 'Directory of contract files to resolve from',\n },\n 'contracts-dir': {\n type: 'string',\n description:\n 'Contracts directory the harness resolves from (same as \"ekanos dev --contracts-dir\"; default: EKANOS_CONTRACTS_DIR)',\n },\n 'from-path': {\n type: 'string',\n description: 'An integration project directory to resolve from',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const checkOnly = args.flags.check === true;\n const fromDir = args.flags['from-dir'] as string | undefined;\n const fromPath = args.flags['from-path'] as string | undefined;\n const contractsDir = args.flags['contracts-dir'] as string | undefined;\n const explicit = Boolean(\n fromDir ?? fromPath ?? contractsDir ?? args.env.EKANOS_CONTRACTS_DIR,\n );\n // The harness's own registry chain (session catalog, published-package\n // cache, contracts directory, then the committed snapshot) is what\n // `ekanos dev` resolves with, so it is what decides drift here too.\n const sources = defaultSources(\n project,\n { fromDir, fromPath, env: args.env },\n await harnessContractSources(project.dir, args.env, {\n ...((contractsDir ?? fromDir)\n ? { contractsDir: (contractsDir ?? fromDir) as string }\n : {}),\n onUnavailable: (reason) =>\n ctx.warn(\n `Could not ask the project's @ekanos/harness for contracts (${reason}); resolving from the committed snapshots only.`,\n ),\n }),\n );\n\n if (project.deprecatedUses) {\n ctx.warn(DEPRECATED_USES);\n const notice = renamesNotice(project.renames);\n if (notice) ctx.warn(notice);\n }\n\n const lock = readLockfile(project);\n const resolved = new Map<string, ResolvedContract>();\n const drift: string[] = [];\n const consulted = new Set<ResolvedContract['kind'] | 'harness'>();\n for (const use of project.integrations) {\n const hit = await resolveContract(sources, use.name, use.version);\n resolved.set(use.name, hit);\n consulted.add(isSnapshotOnly(hit) ? 'snapshot' : 'harness');\n const locked = lock.integrations[use.name];\n if (!locked || locked.digest !== hit.contract.digest) {\n drift.push(\n `${use.name}: ${locked ? `${locked.contractVersion} -> ${hit.contract.contractVersion}` : `new pin ${hit.contract.contractVersion}`}`,\n );\n }\n }\n\n const snapshotOnly = !explicit && !consulted.has('harness');\n const snapshotNotice =\n 'Only the committed snapshots were consulted (no contracts directory and nothing newer from the harness registry), so a running \"ekanos dev\" that resolves elsewhere can still see a drifted digest. Pass --contracts-dir <dir> or set EKANOS_CONTRACTS_DIR, as you do for \"ekanos dev\".';\n\n if (checkOnly) {\n if (drift.length > 0) {\n throw validationError(\n `${drift.length} integration${drift.length === 1 ? '' : 's'} drifted: ${drift.join('; ')}.`,\n 'Run \"ekanos sync\" to accept the new contracts.',\n );\n }\n\n if (snapshotOnly) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n { drift: [], changed: false, snapshotOnly },\n snapshotOnly\n ? 'All integrations match their committed snapshots (no other contract source was consulted).'\n : 'All integrations match their pinned contracts.',\n );\n }\n\n // Judge the incoming contracts before writing any of it: a new contract\n // that drops a declared operation must not silently replace the old pin.\n const candidate = new Map(\n [...resolved].map(([name, hit]) => [name, hit.contract]),\n );\n const incoming = project.integrations.flatMap((use) => {\n const contract = candidate.get(use.name);\n if (!contract) return [];\n const have = new Set(contract.operations.map((op) => op.id));\n\n return use.operations\n .filter((id) => !have.has(id))\n .map((id) => ({\n check: 'checks/integration-pins',\n severity: 'error' as const,\n message: `integration \"${use.name}\": ${use.name}@${use.version} (contract ${contract.contractVersion}) no longer provides ${shortOperation(id)}.`,\n hint: 'Remove the operation from \"integrations\", or keep the older pinned contract.',\n file: 'ekanos.json',\n }));\n });\n const failed = failOnErrors(ctx, 'sync', incoming, { drift }, project.dir);\n if (failed !== null) return failed;\n\n let changed = false;\n if (project.deprecatedUses) {\n changed = writeIntegrations(project, project.integrations) || changed;\n }\n for (const use of project.integrations) {\n const hit = resolved.get(use.name);\n if (!hit) continue;\n const result = pinIntegration(project, use, hit.contract);\n changed = changed || result.snapshotChanged || result.lockChanged;\n }\n const { pinned } = loadPins(project);\n const typesChanged = regenerateIntegrations(project, pinned);\n changed = changed || typesChanged;\n\n if (snapshotOnly && drift.length === 0) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n {\n drift,\n changed,\n snapshotOnly,\n explicitSource: explicit,\n migratedUses: project.deprecatedUses,\n renames: project.renames,\n integrations: project.integrations.map((u) => ({\n integration: u.name,\n contractVersion: resolved.get(u.name)?.contract.contractVersion,\n digest: resolved.get(u.name)?.contract.digest,\n origin: resolved.get(u.name)?.origin,\n })),\n },\n drift.length > 0\n ? `Synced ${project.integrations.length} integration${project.integrations.length === 1 ? '' : 's'}; accepted: ${drift.join('; ')}.`\n : changed\n ? 'Synced; lock or types were regenerated.'\n : snapshotOnly\n ? 'Nothing to sync against the committed snapshots; no other contract source was consulted.'\n : 'Nothing to sync: every integration matches its pinned contract.',\n );\n },\n};\n\n/**\n * The harness's registry source falls back to the committed snapshot when it\n * finds nothing newer, and says so in its origin (\"snapshot <dir>\"). A hit that\n * is only that fallback proves nothing about the registry.\n */\nfunction isSnapshotOnly(hit: ResolvedContract): boolean {\n return hit.kind === 'snapshot' || hit.origin.startsWith('snapshot ');\n}\n"]}
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { shortOperation } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { validationError } from '../errors';\nimport {\n type ResolvedContract,\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport { DEPRECATED_USES, renamesNotice } from '../experience/pins';\nimport {\n readExperienceProject,\n readLockfile,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const syncCommand: ExperienceCommand = {\n name: 'sync',\n usage: [\n \" sync Re-resolve every integration's contract, accept a new digest within the\",\n ' pinned line, and regenerate the lock + types (--check: report only)',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n check: {\n type: 'boolean',\n description:\n 'Report drift without writing anything; exit 3 when there is some',\n },\n 'from-dir': {\n type: 'string',\n description: 'Directory of contract files to resolve from',\n },\n 'contracts-dir': {\n type: 'string',\n description:\n 'Contracts directory the harness resolves from (same as \"ekanos dev --contracts-dir\"; default: EKANOS_CONTRACTS_DIR)',\n },\n 'from-path': {\n type: 'string',\n description: 'An integration project directory to resolve from',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const checkOnly = args.flags.check === true;\n const fromDir = args.flags['from-dir'] as string | undefined;\n const fromPath = args.flags['from-path'] as string | undefined;\n const contractsDir = args.flags['contracts-dir'] as string | undefined;\n const explicit = Boolean(\n fromDir ?? fromPath ?? contractsDir ?? args.env.EKANOS_CONTRACTS_DIR,\n );\n // The harness's own registry chain (session catalog, published-package\n // cache, contracts directory, then the committed snapshot) is what\n // `ekanos dev` resolves with, so it is what decides drift here too.\n const sources = defaultSources(\n project,\n { fromDir, fromPath, env: args.env },\n await harnessContractSources(project.dir, args.env, {\n ...((contractsDir ?? fromDir)\n ? { contractsDir: (contractsDir ?? fromDir) as string }\n : {}),\n onUnavailable: (reason) =>\n ctx.warn(\n `Could not ask the project's @ekanos/harness for contracts (${reason}); resolving from the committed snapshots only.`,\n ),\n }),\n );\n\n if (project.deprecatedUses) {\n ctx.warn(DEPRECATED_USES);\n const notice = renamesNotice(project.renames);\n if (notice) ctx.warn(notice);\n }\n\n const lock = readLockfile(project);\n const resolved = new Map<string, ResolvedContract>();\n const drift: string[] = [];\n const consulted = new Set<ResolvedContract['kind'] | 'harness'>();\n for (const use of project.integrations) {\n const hit = await resolveContract(sources, use.name, use.version);\n resolved.set(use.name, hit);\n consulted.add(isSnapshotOnly(hit) ? 'snapshot' : 'harness');\n const locked = lock.integrations[use.name];\n if (!locked || locked.digest !== hit.contract.digest) {\n drift.push(\n `${use.name}: ${locked ? `${locked.contractVersion} -> ${hit.contract.contractVersion}` : `new pin ${hit.contract.contractVersion}`}`,\n );\n }\n }\n\n const snapshotOnly = !explicit && !consulted.has('harness');\n const snapshotNotice =\n 'Only the committed snapshots were consulted (no contracts directory and nothing newer from the harness registry), so a running \"ekanos dev\" that resolves elsewhere can still see a drifted digest. Pass --contracts-dir <dir> or set EKANOS_CONTRACTS_DIR, as you do for \"ekanos dev\".';\n\n if (checkOnly) {\n if (drift.length > 0) {\n throw validationError(\n `${drift.length} integration${drift.length === 1 ? '' : 's'} drifted: ${drift.join('; ')}.`,\n 'Run \"ekanos sync\" to accept the new contracts.',\n );\n }\n\n if (snapshotOnly) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n { drift: [], changed: false, snapshotOnly },\n snapshotOnly\n ? 'All integrations match their committed snapshots (no other contract source was consulted).'\n : 'All integrations match their pinned contracts.',\n );\n }\n\n // Judge the incoming contracts before writing any of it: a new contract\n // that drops a declared operation must not silently replace the old pin.\n const candidate = new Map(\n [...resolved].map(([name, hit]) => [name, hit.contract]),\n );\n const incoming = project.integrations.flatMap((use) => {\n const contract = candidate.get(use.name);\n if (!contract) return [];\n const have = new Set(\n contract.operations.map((op) => shortOperation(op.id)),\n );\n\n return use.operations\n .filter((id) => !have.has(shortOperation(id)))\n .map((id) => ({\n check: 'checks/integration-pins',\n severity: 'error' as const,\n message: `integration \"${use.name}\": ${use.name}@${use.version} (contract ${contract.contractVersion}) no longer provides ${shortOperation(id)}.`,\n hint: 'Remove the operation from \"integrations\", or keep the older pinned contract.',\n file: 'ekanos.json',\n }));\n });\n const failed = failOnErrors(ctx, 'sync', incoming, { drift }, project.dir);\n if (failed !== null) return failed;\n\n let changed = false;\n if (project.deprecatedUses) {\n changed = writeIntegrations(project, project.integrations) || changed;\n }\n for (const use of project.integrations) {\n const hit = resolved.get(use.name);\n if (!hit) continue;\n const result = pinIntegration(project, use, hit.contract);\n changed = changed || result.snapshotChanged || result.lockChanged;\n }\n const { pinned } = loadPins(project);\n const typesChanged = regenerateIntegrations(project, pinned);\n changed = changed || typesChanged;\n\n if (snapshotOnly && drift.length === 0) ctx.warn(snapshotNotice);\n\n return ctx.succeed(\n {\n drift,\n changed,\n snapshotOnly,\n explicitSource: explicit,\n migratedUses: project.deprecatedUses,\n renames: project.renames,\n integrations: project.integrations.map((u) => ({\n integration: u.name,\n contractVersion: resolved.get(u.name)?.contract.contractVersion,\n digest: resolved.get(u.name)?.contract.digest,\n origin: resolved.get(u.name)?.origin,\n })),\n },\n drift.length > 0\n ? `Synced ${project.integrations.length} integration${project.integrations.length === 1 ? '' : 's'}; accepted: ${drift.join('; ')}.`\n : changed\n ? 'Synced; lock or types were regenerated.'\n : snapshotOnly\n ? 'Nothing to sync against the committed snapshots; no other contract source was consulted.'\n : 'Nothing to sync: every integration matches its pinned contract.',\n );\n },\n};\n\n/**\n * The harness's registry source falls back to the committed snapshot when it\n * finds nothing newer, and says so in its origin (\"snapshot <dir>\"). A hit that\n * is only that fallback proves nothing about the registry.\n */\nfunction isSnapshotOnly(hit: ResolvedContract): boolean {\n return hit.kind === 'snapshot' || hit.origin.startsWith('snapshot ');\n}\n"]}
@@ -3,10 +3,19 @@ import type { ExitCode } from '../exit-codes.js';
3
3
  import { type TeamsArgs } from './teams.js';
4
4
  /**
5
5
  * `teams audience <team> [<audience>] [--member <email> [--inherit]]` — the
6
- * audience a team starts its members on, and a member's own override of it
7
- * (a Source admin's only; audited). Without an audience it reads. Moving
6
+ * team's audience (its business type), which every member is on; their
7
+ * groups narrow it (`teams groups`). Without an audience it reads. Moving
8
8
  * people to an audience that shows fewer products hides the difference from
9
9
  * them in the app, so that needs `--yes`. `teams scope` is the deprecated
10
10
  * form of this.
11
+ *
12
+ * `--member <email>` reads one person's. Setting it (`<audience>` or
13
+ * `--inherit`) is deprecated: a host on plan 258 refuses a per-person
14
+ * audience (`AUDIENCE_SET_ON_TEAM`), and an older host still takes it.
11
15
  */
12
16
  export declare function runTeamAudience(ctx: CliContext, args: TeamsArgs, rest: readonly string[]): Promise<ExitCode>;
17
+ /**
18
+ * The one line a per-person audience write prints (to stderr, so JSON on
19
+ * stdout is unchanged) before it is sent.
20
+ */
21
+ export declare function memberAudienceDeprecationNotice(team: string): string;
@@ -3,14 +3,18 @@ import { invalidStateError, usageError } from '../errors.js';
3
3
  import { resolveSourceTarget } from '../source-target.js';
4
4
  import { listAudiences, setMemberAudience, setTeamAudience, showMemberAudience, showTeamAudience, } from '../teams-api.js';
5
5
  import { TeamsClient } from './teams.js';
6
- const USAGE = 'Run "ekanos teams audience <team>" to read a team’s audience, "ekanos teams audience <team> <audience> [--yes]" to set it, or add --member <email> (with <audience>, or --inherit to clear it) for one person’s own.';
6
+ const USAGE = 'Run "ekanos teams audience <team>" to read a team’s audience (its business type), "ekanos teams audience <team> <audience> [--yes]" to set it, or add --member <email> to read one person’s.';
7
7
  /**
8
8
  * `teams audience <team> [<audience>] [--member <email> [--inherit]]` — the
9
- * audience a team starts its members on, and a member's own override of it
10
- * (a Source admin's only; audited). Without an audience it reads. Moving
9
+ * team's audience (its business type), which every member is on; their
10
+ * groups narrow it (`teams groups`). Without an audience it reads. Moving
11
11
  * people to an audience that shows fewer products hides the difference from
12
12
  * them in the app, so that needs `--yes`. `teams scope` is the deprecated
13
13
  * form of this.
14
+ *
15
+ * `--member <email>` reads one person's. Setting it (`<audience>` or
16
+ * `--inherit`) is deprecated: a host on plan 258 refuses a per-person
17
+ * audience (`AUDIENCE_SET_ON_TEAM`), and an older host still takes it.
14
18
  */
15
19
  export async function runTeamAudience(ctx, args, rest) {
16
20
  var _a, _b;
@@ -50,6 +54,10 @@ export async function runTeamAudience(ctx, args, rest) {
50
54
  if (!audience && !inherit) {
51
55
  return ctx.succeed(Object.assign({ host: env.host }, current), describeMember(ctx, current));
52
56
  }
57
+ if (inherit && !current.isOverride) {
58
+ return ctx.succeed(Object.assign(Object.assign({ host: env.host }, current), { before: current.audience }), describeMember(ctx, current, current.audience));
59
+ }
60
+ ctx.warn(memberAudienceDeprecationNotice(team));
53
61
  const next = inherit
54
62
  ? (await client.call((token) => showTeamAudience(env.host, token, target))).audience.id
55
63
  : audience;
@@ -72,7 +80,7 @@ export async function runTeamAudience(ctx, args, rest) {
72
80
  source,
73
81
  from: current.audience,
74
82
  to: audience,
75
- who: `members of ${team} on the team default`,
83
+ who: `members of ${team}`,
76
84
  yes: args.yes,
77
85
  retry: `ekanos teams audience ${team} ${quote(audience)} --source ${source} --yes`,
78
86
  });
@@ -109,7 +117,7 @@ function findAudience(audiences, ref) {
109
117
  function describeTeam(ctx, result, before) {
110
118
  var _a;
111
119
  return ctx.ui.result({
112
- title: `${result.team.slug} starts its members on ${result.audience.name}`,
120
+ title: `${result.team.slug} is on ${result.audience.name}`,
113
121
  rows: [
114
122
  ['Audience', result.audience.name],
115
123
  ...(before !== undefined
@@ -117,7 +125,7 @@ function describeTeam(ctx, result, before) {
117
125
  : []),
118
126
  ],
119
127
  notes: [
120
- 'A member’s own audience overrides it: "ekanos teams audience <team> <audience> --member <email>".',
128
+ `Every member is on it; their groups narrow it: "ekanos teams groups list ${result.team.slug}".`,
121
129
  '"ekanos audiences show <audience>" lists what it shows.',
122
130
  ],
123
131
  });
@@ -130,7 +138,7 @@ function describeMember(ctx, result, before) {
130
138
  ['Audience', result.audience.name],
131
139
  [
132
140
  'From',
133
- result.isOverride ? 'their own' : `the ${result.team.slug} default`,
141
+ result.isOverride ? 'their own' : `the ${result.team.slug} audience`,
134
142
  ],
135
143
  ...(before !== undefined
136
144
  ? [['Before', (_a = before === null || before === void 0 ? void 0 : before.name) !== null && _a !== void 0 ? _a : 'none']]
@@ -138,6 +146,13 @@ function describeMember(ctx, result, before) {
138
146
  ],
139
147
  });
140
148
  }
149
+ /**
150
+ * The one line a per-person audience write prints (to stderr, so JSON on
151
+ * stdout is unchanged) before it is sent.
152
+ */
153
+ export function memberAudienceDeprecationNotice(team) {
154
+ return `Deprecated: a person’s access is set by the team’s audience and their groups. Hosts on the audiences model refuse this (AUDIENCE_SET_ON_TEAM); set the team’s with "ekanos teams audience ${team} <audience>", or add them to a group with "ekanos teams groups members add ${team} <group> <email>".`;
155
+ }
141
156
  function quote(name) {
142
157
  return /^[A-Za-z0-9._-]+$/.test(name) ? name : `"${name}"`;
143
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"teams-audience.js","sourceRoot":"","sources":["../../src/commands/teams-audience.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Audience,\n AudienceRef,\n MemberAudience,\n TeamAudience,\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 listAudiences,\n setMemberAudience,\n setTeamAudience,\n showMemberAudience,\n showTeamAudience,\n} from '../teams-api';\nimport { TeamsClient, type TeamsArgs } from './teams';\n\nconst USAGE =\n 'Run \"ekanos teams audience <team>\" to read a team’s audience, \"ekanos teams audience <team> <audience> [--yes]\" to set it, or add --member <email> (with <audience>, or --inherit to clear it) for one person’s own.';\n\n/**\n * `teams audience <team> [<audience>] [--member <email> [--inherit]]` — the\n * audience a team starts its members on, and a member's own override of it\n * (a Source admin's only; audited). Without an audience it reads. Moving\n * people to an audience that shows fewer products hides the difference from\n * them in the app, so that needs `--yes`. `teams scope` is the deprecated\n * form of this.\n */\nexport async function runTeamAudience(\n ctx: CliContext,\n args: TeamsArgs,\n rest: readonly string[],\n): Promise<ExitCode> {\n const [rawTeam, audience, ...extra] = rest;\n const team = rawTeam?.trim().toLowerCase();\n if (!team || !/^[a-z0-9][a-z0-9-]*$/.test(team)) {\n throw usageError(\n rawTeam\n ? `\"${rawTeam}\" is not a team slug.`\n : 'teams audience needs a team.',\n USAGE,\n );\n }\n if (extra.length > 0) {\n throw usageError(`Unexpected argument \"${extra[0]}\".`, USAGE);\n }\n const member =\n typeof args.flags?.member === 'string'\n ? args.flags.member.trim().toLowerCase()\n : undefined;\n const inherit = args.flags?.inherit === true;\n if (inherit && !member) {\n throw usageError('--inherit clears one member’s audience.', USAGE);\n }\n if (inherit && audience) {\n throw usageError(\n 'Name an audience or pass --inherit, not both.',\n 'ekanos teams audience <team> --member <email> --inherit puts them back on the team’s.',\n );\n }\n if (member && !/^[^@\\s]+@[^@\\s]+$/.test(member)) {\n throw usageError(`\"${member}\" is not an email address.`, USAGE);\n }\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 const target = { source, team };\n\n if (member) {\n const person = { ...target, email: member };\n const current = await client.call((token) =>\n showMemberAudience(env.host, token, person),\n );\n if (!audience && !inherit) {\n return ctx.succeed(\n { host: env.host, ...current },\n describeMember(ctx, current),\n );\n }\n\n const next = inherit\n ? (\n await client.call((token) =>\n showTeamAudience(env.host, token, target),\n )\n ).audience.id\n : audience!;\n await requireYesIfNarrowing(client, env.host, {\n source,\n from: current.audience,\n to: next,\n who: `${member}`,\n yes: args.yes,\n retry: `ekanos teams audience ${team} ${inherit ? '' : `${quote(audience!)} `}--member ${member}${inherit ? ' --inherit' : ''} --source ${source} --yes`,\n });\n const result = await client.call((token) =>\n setMemberAudience(env.host, token, person, inherit ? null : audience!),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n describeMember(ctx, result, result.before),\n );\n }\n\n const current = await client.call((token) =>\n showTeamAudience(env.host, token, target),\n );\n if (!audience) {\n return ctx.succeed(\n { host: env.host, ...current },\n describeTeam(ctx, current),\n );\n }\n await requireYesIfNarrowing(client, env.host, {\n source,\n from: current.audience,\n to: audience,\n who: `members of ${team} on the team default`,\n yes: args.yes,\n retry: `ekanos teams audience ${team} ${quote(audience)} --source ${source} --yes`,\n });\n const result = await client.call((token) =>\n setTeamAudience(env.host, token, target, audience),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n describeTeam(ctx, result, result.before),\n );\n}\n\n/**\n * Refuses, before any write, a move to an audience that shows less than the\n * one people are on, unless `--yes`. An audience that shows every entitled\n * product can only be left for a narrower one, which is also refused.\n */\nasync function requireYesIfNarrowing(\n client: TeamsClient,\n host: string,\n input: {\n source: string;\n from: AudienceRef;\n to: string;\n who: string;\n yes: boolean;\n retry: string;\n },\n): Promise<void> {\n if (input.yes) return;\n const { audiences } = await client.call((token) =>\n listAudiences(host, token, input.source),\n );\n const from = audiences.find((entry) => entry.id === input.from.id);\n const to = findAudience(audiences, input.to);\n if (!from || !to || to.includesAllEntitled) return;\n\n const kept = new Set(to.products.map((product) => product.slug));\n const lost = from.includesAllEntitled\n ? null\n : from.products.filter((product) => !kept.has(product.slug));\n if (lost !== null && lost.length === 0) return;\n\n throw invalidStateError(\n lost === null\n ? `${to.name} does not show every product the Source has, as ${from.name} does: ${input.who} may stop seeing some in the app.`\n : `${input.who} would stop seeing ${lost.map((product) => product.slug).join(', ')} in the app: ${from.name} shows ${lost.length === 1 ? 'it' : 'them'}, ${to.name} does not.`,\n `Re-run with --yes: ${input.retry}`,\n );\n}\n\nfunction findAudience(\n audiences: Audience[],\n ref: string,\n): Audience | undefined {\n const wanted = ref.trim().toLowerCase();\n return audiences.find(\n (entry) => entry.id === wanted || entry.name.toLowerCase() === wanted,\n );\n}\n\nfunction describeTeam(\n ctx: CliContext,\n result: TeamAudience,\n before?: AudienceRef | null,\n): string {\n return ctx.ui.result({\n title: `${result.team.slug} starts its members on ${result.audience.name}`,\n rows: [\n ['Audience', result.audience.name],\n ...(before !== undefined\n ? ([['Before', before?.name ?? 'none']] as Array<[string, string]>)\n : []),\n ],\n notes: [\n 'A member’s own audience overrides it: \"ekanos teams audience <team> <audience> --member <email>\".',\n '\"ekanos audiences show <audience>\" lists what it shows.',\n ],\n });\n}\n\nfunction describeMember(\n ctx: CliContext,\n result: MemberAudience,\n before?: AudienceRef | null,\n): string {\n return ctx.ui.result({\n title: `${result.member.email} is on ${result.audience.name}`,\n rows: [\n ['Audience', result.audience.name],\n [\n 'From',\n result.isOverride ? 'their own' : `the ${result.team.slug} default`,\n ],\n ...(before !== undefined\n ? ([['Before', before?.name ?? 'none']] as Array<[string, string]>)\n : []),\n ],\n });\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
1
+ {"version":3,"file":"teams-audience.js","sourceRoot":"","sources":["../../src/commands/teams-audience.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Audience,\n AudienceRef,\n MemberAudience,\n TeamAudience,\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 listAudiences,\n setMemberAudience,\n setTeamAudience,\n showMemberAudience,\n showTeamAudience,\n} from '../teams-api';\nimport { TeamsClient, type TeamsArgs } from './teams';\n\nconst USAGE =\n 'Run \"ekanos teams audience <team>\" to read a team’s audience (its business type), \"ekanos teams audience <team> <audience> [--yes]\" to set it, or add --member <email> to read one person’s.';\n\n/**\n * `teams audience <team> [<audience>] [--member <email> [--inherit]]` — the\n * team's audience (its business type), which every member is on; their\n * groups narrow it (`teams groups`). Without an audience it reads. Moving\n * people to an audience that shows fewer products hides the difference from\n * them in the app, so that needs `--yes`. `teams scope` is the deprecated\n * form of this.\n *\n * `--member <email>` reads one person's. Setting it (`<audience>` or\n * `--inherit`) is deprecated: a host on plan 258 refuses a per-person\n * audience (`AUDIENCE_SET_ON_TEAM`), and an older host still takes it.\n */\nexport async function runTeamAudience(\n ctx: CliContext,\n args: TeamsArgs,\n rest: readonly string[],\n): Promise<ExitCode> {\n const [rawTeam, audience, ...extra] = rest;\n const team = rawTeam?.trim().toLowerCase();\n if (!team || !/^[a-z0-9][a-z0-9-]*$/.test(team)) {\n throw usageError(\n rawTeam\n ? `\"${rawTeam}\" is not a team slug.`\n : 'teams audience needs a team.',\n USAGE,\n );\n }\n if (extra.length > 0) {\n throw usageError(`Unexpected argument \"${extra[0]}\".`, USAGE);\n }\n const member =\n typeof args.flags?.member === 'string'\n ? args.flags.member.trim().toLowerCase()\n : undefined;\n const inherit = args.flags?.inherit === true;\n if (inherit && !member) {\n throw usageError('--inherit clears one member’s audience.', USAGE);\n }\n if (inherit && audience) {\n throw usageError(\n 'Name an audience or pass --inherit, not both.',\n 'ekanos teams audience <team> --member <email> --inherit puts them back on the team’s.',\n );\n }\n if (member && !/^[^@\\s]+@[^@\\s]+$/.test(member)) {\n throw usageError(`\"${member}\" is not an email address.`, USAGE);\n }\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 const target = { source, team };\n\n if (member) {\n const person = { ...target, email: member };\n const current = await client.call((token) =>\n showMemberAudience(env.host, token, person),\n );\n if (!audience && !inherit) {\n return ctx.succeed(\n { host: env.host, ...current },\n describeMember(ctx, current),\n );\n }\n if (inherit && !current.isOverride) {\n return ctx.succeed(\n { host: env.host, ...current, before: current.audience },\n describeMember(ctx, current, current.audience),\n );\n }\n ctx.warn(memberAudienceDeprecationNotice(team));\n\n const next = inherit\n ? (\n await client.call((token) =>\n showTeamAudience(env.host, token, target),\n )\n ).audience.id\n : audience!;\n await requireYesIfNarrowing(client, env.host, {\n source,\n from: current.audience,\n to: next,\n who: `${member}`,\n yes: args.yes,\n retry: `ekanos teams audience ${team} ${inherit ? '' : `${quote(audience!)} `}--member ${member}${inherit ? ' --inherit' : ''} --source ${source} --yes`,\n });\n const result = await client.call((token) =>\n setMemberAudience(env.host, token, person, inherit ? null : audience!),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n describeMember(ctx, result, result.before),\n );\n }\n\n const current = await client.call((token) =>\n showTeamAudience(env.host, token, target),\n );\n if (!audience) {\n return ctx.succeed(\n { host: env.host, ...current },\n describeTeam(ctx, current),\n );\n }\n await requireYesIfNarrowing(client, env.host, {\n source,\n from: current.audience,\n to: audience,\n who: `members of ${team}`,\n yes: args.yes,\n retry: `ekanos teams audience ${team} ${quote(audience)} --source ${source} --yes`,\n });\n const result = await client.call((token) =>\n setTeamAudience(env.host, token, target, audience),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n describeTeam(ctx, result, result.before),\n );\n}\n\n/**\n * Refuses, before any write, a move to an audience that shows less than the\n * one people are on, unless `--yes`. An audience that shows every entitled\n * product can only be left for a narrower one, which is also refused.\n */\nasync function requireYesIfNarrowing(\n client: TeamsClient,\n host: string,\n input: {\n source: string;\n from: AudienceRef;\n to: string;\n who: string;\n yes: boolean;\n retry: string;\n },\n): Promise<void> {\n if (input.yes) return;\n const { audiences } = await client.call((token) =>\n listAudiences(host, token, input.source),\n );\n const from = audiences.find((entry) => entry.id === input.from.id);\n const to = findAudience(audiences, input.to);\n if (!from || !to || to.includesAllEntitled) return;\n\n const kept = new Set(to.products.map((product) => product.slug));\n const lost = from.includesAllEntitled\n ? null\n : from.products.filter((product) => !kept.has(product.slug));\n if (lost !== null && lost.length === 0) return;\n\n throw invalidStateError(\n lost === null\n ? `${to.name} does not show every product the Source has, as ${from.name} does: ${input.who} may stop seeing some in the app.`\n : `${input.who} would stop seeing ${lost.map((product) => product.slug).join(', ')} in the app: ${from.name} shows ${lost.length === 1 ? 'it' : 'them'}, ${to.name} does not.`,\n `Re-run with --yes: ${input.retry}`,\n );\n}\n\nfunction findAudience(\n audiences: Audience[],\n ref: string,\n): Audience | undefined {\n const wanted = ref.trim().toLowerCase();\n return audiences.find(\n (entry) => entry.id === wanted || entry.name.toLowerCase() === wanted,\n );\n}\n\nfunction describeTeam(\n ctx: CliContext,\n result: TeamAudience,\n before?: AudienceRef | null,\n): string {\n return ctx.ui.result({\n title: `${result.team.slug} is on ${result.audience.name}`,\n rows: [\n ['Audience', result.audience.name],\n ...(before !== undefined\n ? ([['Before', before?.name ?? 'none']] as Array<[string, string]>)\n : []),\n ],\n notes: [\n `Every member is on it; their groups narrow it: \"ekanos teams groups list ${result.team.slug}\".`,\n '\"ekanos audiences show <audience>\" lists what it shows.',\n ],\n });\n}\n\nfunction describeMember(\n ctx: CliContext,\n result: MemberAudience,\n before?: AudienceRef | null,\n): string {\n return ctx.ui.result({\n title: `${result.member.email} is on ${result.audience.name}`,\n rows: [\n ['Audience', result.audience.name],\n [\n 'From',\n result.isOverride ? 'their own' : `the ${result.team.slug} audience`,\n ],\n ...(before !== undefined\n ? ([['Before', before?.name ?? 'none']] as Array<[string, string]>)\n : []),\n ],\n });\n}\n\n/**\n * The one line a per-person audience write prints (to stderr, so JSON on\n * stdout is unchanged) before it is sent.\n */\nexport function memberAudienceDeprecationNotice(team: string): string {\n return `Deprecated: a person’s access is set by the team’s audience and their groups. Hosts on the audiences model refuse this (AUDIENCE_SET_ON_TEAM); set the team’s with \"ekanos teams audience ${team} <audience>\", or add them to a group with \"ekanos teams groups members add ${team} <group> <email>\".`;\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}