@ekanos/cli 0.22.0 → 0.23.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 (77) hide show
  1. package/README.md +18 -4
  2. package/dist/commands/add.js +21 -10
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/contract.js +12 -2
  5. package/dist/commands/contract.js.map +1 -1
  6. package/dist/commands/dev.js +33 -2
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/publish.d.ts +6 -0
  9. package/dist/commands/publish.js +80 -4
  10. package/dist/commands/publish.js.map +1 -1
  11. package/dist/commands/status.d.ts +44 -0
  12. package/dist/commands/status.js +79 -18
  13. package/dist/commands/status.js.map +1 -1
  14. package/dist/commands/upgrade.js +287 -144
  15. package/dist/commands/upgrade.js.map +1 -1
  16. package/dist/docs-links.js +5 -1
  17. package/dist/docs-links.js.map +1 -1
  18. package/dist/errors.d.ts +2 -0
  19. package/dist/errors.js +9 -0
  20. package/dist/errors.js.map +1 -1
  21. package/dist/exit-codes.d.ts +3 -0
  22. package/dist/exit-codes.js +3 -0
  23. package/dist/exit-codes.js.map +1 -1
  24. package/dist/experience/checks.js +2 -1
  25. package/dist/experience/checks.js.map +1 -1
  26. package/dist/experience/definition-checks.d.ts +7 -0
  27. package/dist/experience/definition-checks.js +36 -4
  28. package/dist/experience/definition-checks.js.map +1 -1
  29. package/dist/experience/init.js +10 -0
  30. package/dist/experience/init.js.map +1 -1
  31. package/dist/experience/load-experience.js +33 -2
  32. package/dist/experience/load-experience.js.map +1 -1
  33. package/dist/experience/pins.d.ts +10 -0
  34. package/dist/experience/pins.js +19 -1
  35. package/dist/experience/pins.js.map +1 -1
  36. package/dist/generated/phosphor-names.d.ts +3 -0
  37. package/dist/generated/phosphor-names.js +1541 -0
  38. package/dist/generated/phosphor-names.js.map +1 -0
  39. package/dist/harness-scaffold.d.ts +13 -0
  40. package/dist/harness-scaffold.js +19 -3
  41. package/dist/harness-scaffold.js.map +1 -1
  42. package/dist/icon-findings.d.ts +12 -17
  43. package/dist/icon-findings.js +49 -74
  44. package/dist/icon-findings.js.map +1 -1
  45. package/dist/index.js +28 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/lockfile-instances.d.ts +60 -0
  48. package/dist/lockfile-instances.js +177 -0
  49. package/dist/lockfile-instances.js.map +1 -0
  50. package/dist/pack-hygiene.d.ts +8 -0
  51. package/dist/pack-hygiene.js +57 -0
  52. package/dist/pack-hygiene.js.map +1 -0
  53. package/dist/package-manager.d.ts +34 -6
  54. package/dist/package-manager.js +130 -20
  55. package/dist/package-manager.js.map +1 -1
  56. package/dist/project.js +11 -0
  57. package/dist/project.js.map +1 -1
  58. package/dist/repo/scaffold.js +4 -0
  59. package/dist/repo/scaffold.js.map +1 -1
  60. package/dist/schema-skew.js.map +1 -1
  61. package/dist/validate-findings.js +10 -1
  62. package/dist/validate-findings.js.map +1 -1
  63. package/dist/workspace-root.d.ts +16 -0
  64. package/dist/workspace-root.js +50 -0
  65. package/dist/workspace-root.js.map +1 -0
  66. package/package.json +4 -4
  67. package/templates/AGENTS.md.tmpl +11 -15
  68. package/templates/experience/package.json.tmpl +8 -6
  69. package/templates/experience/src/widgets/hello-widget.test.tsx.tmpl +49 -0
  70. package/templates/experience/vitest.config.ts.tmpl +21 -0
  71. package/templates/harness/next.config.mjs.tmpl +7 -4
  72. package/templates/package.json.tmpl +5 -5
  73. package/templates/skills/build-integration/references/widget-design.md +2 -3
  74. package/templates/skills/polish-widgets/references/patterns.md +5 -5
  75. package/dist/generated/fa-free-names.d.ts +0 -9
  76. package/dist/generated/fa-free-names.js +0 -2599
  77. package/dist/generated/fa-free-names.js.map +0 -1
@@ -1,26 +1,21 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { FA_FREE_ICON_NAMES, FA_PRO_TO_FREE_HINTS, } from './generated/fa-free-names.js';
3
+ import { PHOSPHOR_ICON_NAMES } from './generated/phosphor-names.js';
4
4
  /**
5
- * Flags Font Awesome **Pro**-only `fa-*` icon names in a project's widget
6
- * source — today this is a silent, runtime-only failure: `@ekanos/ui`'s
7
- * `Icon` warns once via `console.warn` in dev, but ships nothing to
8
- * `validate`, so a partner only discovers a Pro-only name when the generated
9
- * harness (Font Awesome **Free**-only, see `@ekanos/harness`) renders the
10
- * circle-question fallback in place of the intended glyph — or never
11
- * discovers it, if nobody was watching the console.
5
+ * Flags icon names in a project's widget source that `@ekanos/ui`'s `Icon`
6
+ * cannot draw. Icons are Phosphor (phosphoricons.com): a name that is not
7
+ * one draws the `shapes` fallback, which nobody notices until a screenshot
8
+ * review. A Font Awesome value from before the move (`fa-house`) still
9
+ * renders through a translation table, but new code should write the
10
+ * Phosphor name.
12
11
  *
13
- * `validate` has no live component tree to inspect the way the runtime
14
- * warning does, and the definition's `component`/`icon` fields are live
15
- * React values by the time `loadDefinition` resolves them (function
16
- * references, not paths) — there is no file path to walk back to from an
17
- * already-imported component. So this scans the project's own `src/**\/*.{ts,tsx}`
18
- * on disk instead, the same fallback `collectProjectFindings` would use if it
19
- * needed source text rather than a loaded value.
12
+ * `validate` has no live component tree, and by the time `loadDefinition`
13
+ * resolves a definition its `component`/`icon` fields are live values with
14
+ * no path back to source, so this scans the project's own
15
+ * `src/**\/*.{ts,tsx}` on disk.
20
16
  *
21
- * WARN, not error: a Pro-only name is broken UX in a Free-only host, not an
22
- * unbuildable submission — plenty of Ekanos-hosted partners run under Pro,
23
- * where the exact same name renders correctly.
17
+ * WARN, not error: an unknown name is broken UX, not an unbuildable
18
+ * submission.
24
19
  */
25
20
  export function collectIconFindings(projectDir) {
26
21
  const srcDir = path.join(projectDir, 'src');
@@ -44,75 +39,55 @@ function* walkTsFiles(dir) {
44
39
  }
45
40
  }
46
41
  /**
47
- * One Font Awesome style-family token, e.g. the `fa-solid` in
48
- * `"fa-solid fa-house"` (the host's icon helper produces exactly this
49
- * two-token shape). Kept in sync by hand with the host UI package's
50
- * `FA_ICON_STYLES` — a style name is never itself an icon name, so it must
51
- * not be treated as one, but that list lives in a browser package this one
52
- * may not import.
42
+ * A quoted literal whose entire contents could be an icon value: a Phosphor
43
+ * name (`house`, `star:fill`) or a Font Awesome value (`fa-solid fa-house`).
44
+ * Parameterized on its backreference because it is spliced in twice.
53
45
  */
54
- const FA_STYLE_TOKEN = '(?:solid|regular|light|thin|duotone|sharp|brands|classic)';
55
- /**
56
- * A quoted string literal whose ENTIRE contents are a bare `fa-<name>` value
57
- * or a two-token `fa-<style> fa-<name>` value — anchored to the quote
58
- * boundaries (the backreference to the opening quote must follow the name
59
- * pattern immediately) so this cannot match a `fa-`-prefixed SUBSTRING inside
60
- * a longer, unrelated string.
61
- *
62
- * Parameterized on its own backreference number because it is spliced into
63
- * `FA_ICON_CONTEXT_RE` twice — each copy opens its own capture group, so a
64
- * hardcoded `\1` in the second copy would wrongly point at the FIRST copy's
65
- * quote group instead of its own.
66
- */
67
- function faIconLiteral(quoteGroup) {
68
- return `(['"\`])(?:fa-${FA_STYLE_TOKEN}\\s+)?fa-([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\\${quoteGroup}`;
46
+ function iconLiteral(quoteGroup) {
47
+ return `(['"\`])((?:fa-[a-z]+\\s+)?[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?::fill)?)\\${quoteGroup}`;
69
48
  }
70
49
  /**
71
- * Only literals in a position that plausibly HOLDS an icon name: the `name`/
72
- * `icon` prop or object key an `<Icon>`, a widget's `category.icon`, or a
73
- * config object typically uses, or a direct argument to `resolveIcon`/
74
- * `renderIcon` (`@ekanos/ui`'s runtime helpers).
75
- *
76
- * This is a heuristic, not a parser — it will miss an icon name built up any
77
- * other way (a variable, a template with interpolation, an indirect prop
78
- * name), and in principle a `name`/`icon` key on some unrelated object could
79
- * coincidentally hold an `fa-`-shaped string and get flagged. Both failure
80
- * modes are accepted: a scan that required full TS type information to be
81
- * exact is not worth the cost here, and this project's own convention names
82
- * icon props/keys `name` or `icon` specifically to keep that collision rare.
83
- * Group numbers: 1 = quote, 2 = name for the `name`/`icon` branch; 3 = quote,
84
- * 4 = name for the `resolveIcon`/`renderIcon` branch.
50
+ * Only literals in a position that plausibly holds an icon name: the `icon`
51
+ * prop or key, an `<Icon name=…>`, or a direct argument to `resolveIcon` /
52
+ * `renderIcon`. A heuristic, not a parser: it misses names built any other
53
+ * way. Groups: 1 = quote, 2 = value for the `icon` key; 3/4 for `<Icon
54
+ * name>`; 5/6 for the helper calls.
85
55
  */
86
- const FA_ICON_CONTEXT_RE = new RegExp(`\\b(?:name|icon)\\s*[:=]\\s*\\{?\\s*${faIconLiteral(1)}` +
87
- `|\\b(?:resolveIcon|renderIcon)\\(\\s*${faIconLiteral(3)}`, 'g');
56
+ const ICON_CONTEXT_RE = new RegExp(`\\bicon\\s*[:=]\\s*\\{?\\s*${iconLiteral(1)}` +
57
+ `|<Icon\\b[^>]*?\\bname=\\{?\\s*${iconLiteral(3)}` +
58
+ `|\\b(?:resolveIcon|renderIcon)\\(\\s*${iconLiteral(5)}`, 'g');
88
59
  function collectIconFindingsInFile(file) {
89
- var _a;
60
+ var _a, _b;
90
61
  const source = fs.readFileSync(file, 'utf8');
91
62
  const findings = [];
92
- const seen = new Set();
93
- for (const match of source.matchAll(FA_ICON_CONTEXT_RE)) {
94
- const iconName = (_a = match[2]) !== null && _a !== void 0 ? _a : match[4];
95
- if (!iconName || FA_FREE_ICON_NAMES.has(iconName))
63
+ for (const match of source.matchAll(ICON_CONTEXT_RE)) {
64
+ const value = (_b = (_a = match[2]) !== null && _a !== void 0 ? _a : match[4]) !== null && _b !== void 0 ? _b : match[6];
65
+ if (!value)
96
66
  continue;
97
- const dedupeKey = `${iconName}@${match.index}`;
98
- if (seen.has(dedupeKey))
99
- continue;
100
- seen.add(dedupeKey);
101
67
  const line = source.slice(0, match.index).split('\n').length;
102
- const alternative = FA_PRO_TO_FREE_HINTS.get(iconName);
68
+ if (value.startsWith('fa-')) {
69
+ findings.push({
70
+ check: 'icons/font-awesome',
71
+ severity: 'warn',
72
+ file,
73
+ line,
74
+ message: `"${value}" is a Font Awesome value. Ekanos draws Phosphor icons; ` +
75
+ 'this one still renders through a translation table, but may not ' +
76
+ 'be the glyph you meant.',
77
+ hint: 'Write the Phosphor name from phosphoricons.com (e.g. "house").',
78
+ });
79
+ continue;
80
+ }
81
+ if (PHOSPHOR_ICON_NAMES.has(value.replace(/:fill$/, '')))
82
+ continue;
103
83
  findings.push({
104
- check: 'icons/pro-only',
84
+ check: 'icons/unknown',
105
85
  severity: 'warn',
106
86
  file,
107
87
  line,
108
- message: `"fa-${iconName}" is not in the Font Awesome Free set. Ekanos itself ` +
109
- 'loads Font Awesome Pro, so this renders fine there, but the ' +
110
- 'generated harness (and any other Free-only host) shows the ' +
111
- 'circle-question placeholder instead of the intended glyph.',
112
- hint: alternative
113
- ? `Use "fa-${alternative}" instead — the nearest Free equivalent.`
114
- : 'Pick a Free icon name — Pro names render as placeholder discs ' +
115
- 'outside Pro-licensed hosts.',
88
+ message: `"${value}" is not a Phosphor icon, so it draws the "shapes" ` +
89
+ 'fallback instead of the glyph you meant.',
90
+ hint: 'Pick a name from phosphoricons.com (e.g. "house", "caret-down").',
116
91
  });
117
92
  }
118
93
  return findings;
@@ -1 +1 @@
1
- {"version":3,"file":"icon-findings.js","sourceRoot":"","sources":["../src/icon-findings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n FA_FREE_ICON_NAMES,\n FA_PRO_TO_FREE_HINTS,\n} from './generated/fa-free-names';\n\n/**\n * Flags Font Awesome **Pro**-only `fa-*` icon names in a project's widget\n * source — today this is a silent, runtime-only failure: `@ekanos/ui`'s\n * `Icon` warns once via `console.warn` in dev, but ships nothing to\n * `validate`, so a partner only discovers a Pro-only name when the generated\n * harness (Font Awesome **Free**-only, see `@ekanos/harness`) renders the\n * circle-question fallback in place of the intended glyph — or never\n * discovers it, if nobody was watching the console.\n *\n * `validate` has no live component tree to inspect the way the runtime\n * warning does, and the definition's `component`/`icon` fields are live\n * React values by the time `loadDefinition` resolves them (function\n * references, not paths) — there is no file path to walk back to from an\n * already-imported component. So this scans the project's own `src/**\\/*.{ts,tsx}`\n * on disk instead, the same fallback `collectProjectFindings` would use if it\n * needed source text rather than a loaded value.\n *\n * WARN, not error: a Pro-only name is broken UX in a Free-only host, not an\n * unbuildable submission — plenty of Ekanos-hosted partners run under Pro,\n * where the exact same name renders correctly.\n */\nexport function collectIconFindings(projectDir: string): Finding[] {\n const srcDir = path.join(projectDir, 'src');\n if (!fs.existsSync(srcDir)) return [];\n\n const findings: Finding[] = [];\n for (const file of walkTsFiles(srcDir)) {\n findings.push(...collectIconFindingsInFile(file));\n }\n return findings;\n}\n\nfunction* walkTsFiles(dir: string): Generator<string> {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkTsFiles(full);\n } else if (/\\.(ts|tsx)$/.test(entry.name)) {\n yield full;\n }\n }\n}\n\n/**\n * One Font Awesome style-family token, e.g. the `fa-solid` in\n * `\"fa-solid fa-house\"` (the host's icon helper produces exactly this\n * two-token shape). Kept in sync by hand with the host UI package's\n * `FA_ICON_STYLES` — a style name is never itself an icon name, so it must\n * not be treated as one, but that list lives in a browser package this one\n * may not import.\n */\nconst FA_STYLE_TOKEN =\n '(?:solid|regular|light|thin|duotone|sharp|brands|classic)';\n\n/**\n * A quoted string literal whose ENTIRE contents are a bare `fa-<name>` value\n * or a two-token `fa-<style> fa-<name>` value — anchored to the quote\n * boundaries (the backreference to the opening quote must follow the name\n * pattern immediately) so this cannot match a `fa-`-prefixed SUBSTRING inside\n * a longer, unrelated string.\n *\n * Parameterized on its own backreference number because it is spliced into\n * `FA_ICON_CONTEXT_RE` twice — each copy opens its own capture group, so a\n * hardcoded `\\1` in the second copy would wrongly point at the FIRST copy's\n * quote group instead of its own.\n */\nfunction faIconLiteral(quoteGroup: number): string {\n return `(['\"\\`])(?:fa-${FA_STYLE_TOKEN}\\\\s+)?fa-([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\\\\${quoteGroup}`;\n}\n\n/**\n * Only literals in a position that plausibly HOLDS an icon name: the `name`/\n * `icon` prop or object key an `<Icon>`, a widget's `category.icon`, or a\n * config object typically uses, or a direct argument to `resolveIcon`/\n * `renderIcon` (`@ekanos/ui`'s runtime helpers).\n *\n * This is a heuristic, not a parser — it will miss an icon name built up any\n * other way (a variable, a template with interpolation, an indirect prop\n * name), and in principle a `name`/`icon` key on some unrelated object could\n * coincidentally hold an `fa-`-shaped string and get flagged. Both failure\n * modes are accepted: a scan that required full TS type information to be\n * exact is not worth the cost here, and this project's own convention names\n * icon props/keys `name` or `icon` specifically to keep that collision rare.\n * Group numbers: 1 = quote, 2 = name for the `name`/`icon` branch; 3 = quote,\n * 4 = name for the `resolveIcon`/`renderIcon` branch.\n */\nconst FA_ICON_CONTEXT_RE = new RegExp(\n `\\\\b(?:name|icon)\\\\s*[:=]\\\\s*\\\\{?\\\\s*${faIconLiteral(1)}` +\n `|\\\\b(?:resolveIcon|renderIcon)\\\\(\\\\s*${faIconLiteral(3)}`,\n 'g',\n);\n\nfunction collectIconFindingsInFile(file: string): Finding[] {\n const source = fs.readFileSync(file, 'utf8');\n const findings: Finding[] = [];\n const seen = new Set<string>();\n\n for (const match of source.matchAll(FA_ICON_CONTEXT_RE)) {\n const iconName = match[2] ?? match[4];\n if (!iconName || FA_FREE_ICON_NAMES.has(iconName)) continue;\n\n const dedupeKey = `${iconName}@${match.index}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n const line = source.slice(0, match.index).split('\\n').length;\n const alternative = FA_PRO_TO_FREE_HINTS.get(iconName);\n\n findings.push({\n check: 'icons/pro-only',\n severity: 'warn',\n file,\n line,\n message:\n `\"fa-${iconName}\" is not in the Font Awesome Free set. Ekanos itself ` +\n 'loads Font Awesome Pro, so this renders fine there, but the ' +\n 'generated harness (and any other Free-only host) shows the ' +\n 'circle-question placeholder instead of the intended glyph.',\n hint: alternative\n ? `Use \"fa-${alternative}\" instead — the nearest Free equivalent.`\n : 'Pick a Free icon name — Pro names render as placeholder discs ' +\n 'outside Pro-licensed hosts.',\n });\n }\n\n return findings;\n}\n"]}
1
+ {"version":3,"file":"icon-findings.js","sourceRoot":"","sources":["../src/icon-findings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { PHOSPHOR_ICON_NAMES } from './generated/phosphor-names';\n\n/**\n * Flags icon names in a project's widget source that `@ekanos/ui`'s `Icon`\n * cannot draw. Icons are Phosphor (phosphoricons.com): a name that is not\n * one draws the `shapes` fallback, which nobody notices until a screenshot\n * review. A Font Awesome value from before the move (`fa-house`) still\n * renders through a translation table, but new code should write the\n * Phosphor name.\n *\n * `validate` has no live component tree, and by the time `loadDefinition`\n * resolves a definition its `component`/`icon` fields are live values with\n * no path back to source, so this scans the project's own\n * `src/**\\/*.{ts,tsx}` on disk.\n *\n * WARN, not error: an unknown name is broken UX, not an unbuildable\n * submission.\n */\nexport function collectIconFindings(projectDir: string): Finding[] {\n const srcDir = path.join(projectDir, 'src');\n if (!fs.existsSync(srcDir)) return [];\n\n const findings: Finding[] = [];\n for (const file of walkTsFiles(srcDir)) {\n findings.push(...collectIconFindingsInFile(file));\n }\n return findings;\n}\n\nfunction* walkTsFiles(dir: string): Generator<string> {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkTsFiles(full);\n } else if (/\\.(ts|tsx)$/.test(entry.name)) {\n yield full;\n }\n }\n}\n\n/**\n * A quoted literal whose entire contents could be an icon value: a Phosphor\n * name (`house`, `star:fill`) or a Font Awesome value (`fa-solid fa-house`).\n * Parameterized on its backreference because it is spliced in twice.\n */\nfunction iconLiteral(quoteGroup: number): string {\n return `(['\"\\`])((?:fa-[a-z]+\\\\s+)?[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?::fill)?)\\\\${quoteGroup}`;\n}\n\n/**\n * Only literals in a position that plausibly holds an icon name: the `icon`\n * prop or key, an `<Icon name=…>`, or a direct argument to `resolveIcon` /\n * `renderIcon`. A heuristic, not a parser: it misses names built any other\n * way. Groups: 1 = quote, 2 = value for the `icon` key; 3/4 for `<Icon\n * name>`; 5/6 for the helper calls.\n */\nconst ICON_CONTEXT_RE = new RegExp(\n `\\\\bicon\\\\s*[:=]\\\\s*\\\\{?\\\\s*${iconLiteral(1)}` +\n `|<Icon\\\\b[^>]*?\\\\bname=\\\\{?\\\\s*${iconLiteral(3)}` +\n `|\\\\b(?:resolveIcon|renderIcon)\\\\(\\\\s*${iconLiteral(5)}`,\n 'g',\n);\n\nfunction collectIconFindingsInFile(file: string): Finding[] {\n const source = fs.readFileSync(file, 'utf8');\n const findings: Finding[] = [];\n\n for (const match of source.matchAll(ICON_CONTEXT_RE)) {\n const value = match[2] ?? match[4] ?? match[6];\n if (!value) continue;\n const line = source.slice(0, match.index).split('\\n').length;\n\n if (value.startsWith('fa-')) {\n findings.push({\n check: 'icons/font-awesome',\n severity: 'warn',\n file,\n line,\n message:\n `\"${value}\" is a Font Awesome value. Ekanos draws Phosphor icons; ` +\n 'this one still renders through a translation table, but may not ' +\n 'be the glyph you meant.',\n hint: 'Write the Phosphor name from phosphoricons.com (e.g. \"house\").',\n });\n continue;\n }\n\n if (PHOSPHOR_ICON_NAMES.has(value.replace(/:fill$/, ''))) continue;\n\n findings.push({\n check: 'icons/unknown',\n severity: 'warn',\n file,\n line,\n message:\n `\"${value}\" is not a Phosphor icon, so it draws the \"shapes\" ` +\n 'fallback instead of the glyph you meant.',\n hint: 'Pick a name from phosphoricons.com (e.g. \"house\", \"caret-down\").',\n });\n }\n\n return findings;\n}\n"]}
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import { runRoutes } from './commands/routes.js';
30
30
  import { runApprovals, runRuns } from './commands/runs.js';
31
31
  import { runSkills } from './commands/skills.js';
32
32
  import { runSources } from './commands/sources.js';
33
- import { runStatus } from './commands/status.js';
33
+ import { parseTimeoutMinutes, runStatus } from './commands/status.js';
34
34
  import { TEAM_AGENT_AREAS, usageFor } from './commands/team-agents.js';
35
35
  import { runTeams } from './commands/teams.js';
36
36
  import { runTest } from './commands/test.js';
@@ -200,6 +200,14 @@ const COMMAND_SPECS = {
200
200
  type: 'boolean',
201
201
  description: "Wait until this project's current version reaches a gate verdict",
202
202
  },
203
+ wait: {
204
+ type: 'boolean',
205
+ description: 'Same as --watch',
206
+ },
207
+ timeout: {
208
+ type: 'string',
209
+ description: 'With --watch/--wait: give up after this many minutes (default 30; exit 11)',
210
+ },
203
211
  },
204
212
  sources: {
205
213
  host: HOST,
@@ -751,6 +759,14 @@ const COMMAND_SPECS = {
751
759
  type: 'boolean',
752
760
  description: 'In a repo: publish every package',
753
761
  },
762
+ wait: {
763
+ type: 'boolean',
764
+ description: 'After submitting, wait for the gate verdict and print its findings (exit 10 if rejected)',
765
+ },
766
+ timeout: {
767
+ type: 'string',
768
+ description: 'With --wait: give up after this many minutes (default 30; exit 11)',
769
+ },
754
770
  },
755
771
  upgrade: {
756
772
  dir: DIR,
@@ -843,8 +859,9 @@ const USAGE = [
843
859
  ' a compact list of the sources you hold a seat on',
844
860
  " status Show login state and this project's submissions on the host",
845
861
  ' (--findings for full gate findings, --all to expand other',
846
- ' integrations in the source, --watch to block until this',
847
- " project's current version reaches a gate verdict)",
862
+ ' integrations in the source, --watch (or --wait) to block until',
863
+ " this project's current version reaches a gate verdict, up to",
864
+ ' --timeout <minutes>, default 30)',
848
865
  ' sources List every source you hold a developer/source-admin seat on |',
849
866
  ' products [--source <slug>] (each product, Experience or',
850
867
  ' Integration, and whether it is enabled for the Source) |',
@@ -1105,7 +1122,9 @@ const USAGE = [
1105
1122
  ' <run-id> | runs cancel <run-id> [--yes] | approvals list |',
1106
1123
  ' approvals approve|reject <id> [--reason "…"]',
1107
1124
  ' use Set this project\'s publish target ("ekanos use <source-slug>")',
1108
- ' publish Validate, pack and submit the integration to an Ekanos host.',
1125
+ ' publish Validate, pack and submit the integration to an Ekanos host',
1126
+ ' (--dry-run gates and packs, sends nothing; --wait [--timeout',
1127
+ ' <minutes>] then waits for the gate verdict).',
1109
1128
  ' In a repo: publish [<package>...] | --all [--dry-run] (integrations',
1110
1129
  ' first, each package gated, artifacts under .ekanos/publish/)',
1111
1130
  ' upgrade Move this toolchain to a coherent target version, with',
@@ -1268,7 +1287,8 @@ export async function run(argv, options = {}) {
1268
1287
  env,
1269
1288
  findings: parsed.flags.findings === true,
1270
1289
  all: parsed.flags.all === true,
1271
- watch: parsed.flags.watch === true,
1290
+ watch: parsed.flags.watch === true || parsed.flags.wait === true,
1291
+ timeoutMinutes: parseTimeoutMinutes(parsed.flags.timeout),
1272
1292
  sleep: options.sleep,
1273
1293
  now: options.now,
1274
1294
  });
@@ -1506,6 +1526,9 @@ export async function run(argv, options = {}) {
1506
1526
  dryRun: parsed.flags['dry-run'] === true,
1507
1527
  packages: parsed.positionals,
1508
1528
  all: parsed.flags.all === true,
1529
+ wait: parsed.flags.wait === true,
1530
+ timeoutMinutes: parseTimeoutMinutes(parsed.flags.timeout),
1531
+ sleep: options.sleep,
1509
1532
  env,
1510
1533
  stdinIsTTY: options.stdinIsTTY,
1511
1534
  confirm: options.confirm,