@astryxdesign/cli 0.6.3-canary.f22695a → 0.6.3

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 (197) hide show
  1. package/README.md +1 -2
  2. package/api/build/build.type.d.mts +2 -2
  3. package/api/build/build.type.mjs +2 -2
  4. package/api/component/component.type.d.mts +6 -6
  5. package/api/component/component.type.mjs +19 -19
  6. package/api/discover/discover.type.d.mts +4 -4
  7. package/api/discover/discover.type.mjs +10 -10
  8. package/api/docs/_adapter.d.mts +24 -37
  9. package/api/docs/_adapter.mjs +83 -169
  10. package/api/docs/detail/detail.mjs +63 -14
  11. package/api/docs/detail/section/section.d.mts +1 -1
  12. package/api/docs/detail/section/section.mjs +20 -44
  13. package/api/docs/detail/section/section.test.mjs +0 -41
  14. package/api/docs/docs.d.mts +2 -7
  15. package/api/docs/docs.doc.mjs +10 -27
  16. package/api/docs/docs.mjs +9 -16
  17. package/api/docs/docs.test.mjs +0 -6
  18. package/api/docs/docs.type.d.mts +3 -40
  19. package/api/docs/docs.type.mjs +8 -36
  20. package/api/docs/integrationDocs.test.mjs +0 -106
  21. package/api/doctor/doctor.d.mts +0 -48
  22. package/api/doctor/doctor.mjs +0 -232
  23. package/api/doctor/doctor.test.mjs +0 -196
  24. package/api/hook/hook.type.d.mts +3 -3
  25. package/api/hook/hook.type.mjs +11 -11
  26. package/api/hook/list/list.d.mts +1 -1
  27. package/api/integration/add-contribution.mjs +3 -5
  28. package/api/integration/add-contribution.test.mjs +4 -4
  29. package/api/integration/integration-authoring.type.d.mts +1 -1
  30. package/api/integration/pack-check.mjs +7 -49
  31. package/api/integration/pack-check.test.mjs +0 -249
  32. package/api/search/search.d.mts +1 -1
  33. package/api/search/search.mjs +5 -5
  34. package/api/search/search.type.d.mts +2 -2
  35. package/api/search/search.type.mjs +1 -1
  36. package/api/swizzle/swizzle.type.d.mts +2 -2
  37. package/api/swizzle/swizzle.type.mjs +2 -2
  38. package/api/template/template.d.mts +1 -1
  39. package/api/template/template.type.d.mts +6 -6
  40. package/api/template/template.type.mjs +12 -12
  41. package/api/theme/build/build.mjs +6 -20
  42. package/api/theme/build/build.test.mjs +0 -127
  43. package/api/theme/palette/generate/generate.mjs +1 -1
  44. package/api/theme/palette/generate/generator.d.mts +13 -10
  45. package/api/theme/palette/generate/generator.mjs +3 -7
  46. package/api/theme/theme.type.d.mts +11 -170
  47. package/api/theme/theme.type.mjs +27 -94
  48. package/api/upgrade/_adapter.mjs +5 -71
  49. package/api/upgrade/upgrade.doc.mjs +3 -4
  50. package/api/upgrade/upgrade.type.d.mts +5 -5
  51. package/api/upgrade/upgrade.type.mjs +11 -11
  52. package/assets/codemods/integration-discovery.mjs +2 -40
  53. package/assets/codemods/integration-discovery.test.mjs +0 -58
  54. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +5 -27
  55. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +5 -20
  56. package/assets/docs/README.md +0 -9
  57. package/assets/docs/cli-integrations.doc.mjs +15 -86
  58. package/assets/docs/styling-libraries.doc.mjs +1 -1
  59. package/assets/docs/working-with-ai.doc.mjs +1 -1
  60. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +3 -19
  61. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +65 -383
  62. package/authoring/_shared/contract.ts +0 -22
  63. package/authoring/codemod/codemod.doc.mjs +1 -6
  64. package/authoring/codemod/parse.d.mts +8 -8
  65. package/authoring/codemod/parse.mjs +6 -8
  66. package/authoring/config/parse.d.mts +13 -13
  67. package/authoring/config/parse.mjs +8 -8
  68. package/authoring/config/type.ts +3 -3
  69. package/authoring/debug/parse.d.mts +5 -5
  70. package/authoring/debug/parse.mjs +3 -3
  71. package/authoring/doctypes/_schema.d.mts +23 -788
  72. package/authoring/doctypes/_schema.mjs +39 -492
  73. package/authoring/doctypes/base/type.ts +0 -40
  74. package/authoring/doctypes/command/command.doc.mjs +2 -3
  75. package/authoring/doctypes/command/parse.d.mts +2 -2
  76. package/authoring/doctypes/command/parse.mjs +1 -1
  77. package/authoring/doctypes/command/type.ts +2 -3
  78. package/authoring/doctypes/component/component.doc.mjs +3 -6
  79. package/authoring/doctypes/component/parse.d.mts +2 -2
  80. package/authoring/doctypes/component/parse.mjs +1 -1
  81. package/authoring/doctypes/component/type.ts +3 -4
  82. package/authoring/doctypes/enum/parse.d.mts +2 -2
  83. package/authoring/doctypes/enum/parse.mjs +1 -1
  84. package/authoring/doctypes/enum/type.ts +1 -3
  85. package/authoring/doctypes/function/function.doc.mjs +0 -4
  86. package/authoring/doctypes/function/parse.d.mts +2 -2
  87. package/authoring/doctypes/function/parse.mjs +1 -1
  88. package/authoring/doctypes/function/type.ts +2 -6
  89. package/authoring/doctypes/hook/hook.doc.mjs +0 -4
  90. package/authoring/doctypes/hook/parse.d.mts +2 -2
  91. package/authoring/doctypes/hook/parse.mjs +1 -1
  92. package/authoring/doctypes/hook/type.ts +2 -3
  93. package/authoring/doctypes/legacy.d.mts +6 -8
  94. package/authoring/doctypes/legacy.mjs +4 -5
  95. package/authoring/doctypes/parse.d.mts +18 -20
  96. package/authoring/doctypes/parse.mjs +10 -16
  97. package/authoring/doctypes/parse.test.mjs +3 -77
  98. package/authoring/doctypes/reference/parse.d.mts +2 -2
  99. package/authoring/doctypes/reference/parse.mjs +5 -8
  100. package/authoring/doctypes/reference/reference.doc.mjs +4 -17
  101. package/authoring/doctypes/reference/type.ts +5 -51
  102. package/authoring/doctypes/schema/parse.d.mts +2 -2
  103. package/authoring/doctypes/schema/parse.mjs +1 -1
  104. package/authoring/doctypes/schema/type.ts +2 -3
  105. package/authoring/doctypes/template/parse.d.mts +1 -92
  106. package/authoring/doctypes/template/parse.mjs +2 -36
  107. package/authoring/doctypes/template/parse.test.mjs +2 -8
  108. package/authoring/doctypes/template/template.doc.mjs +0 -4
  109. package/authoring/doctypes/template/type.ts +2 -5
  110. package/authoring/doctypes/types.ts +9 -10
  111. package/authoring/gap-report/parse.d.mts +10 -10
  112. package/authoring/gap-report/parse.mjs +6 -6
  113. package/authoring/gap-report/type.ts +1 -1
  114. package/authoring/index.d.mts +0 -1
  115. package/authoring/index.d.ts +17 -49
  116. package/authoring/index.mjs +0 -1
  117. package/authoring/integration/integration.doc.mjs +6 -13
  118. package/authoring/integration/parse.d.mts +2 -2
  119. package/authoring/integration/parse.mjs +1 -1
  120. package/authoring/integration/parse.test.mjs +1 -10
  121. package/authoring/integration/schema.d.mts +4 -6
  122. package/authoring/integration/schema.mjs +3 -9
  123. package/authoring/integration/type.ts +6 -23
  124. package/authoring/shadcn/receipt.d.mts +6 -6
  125. package/clients/cli/commands/docs.doc.mjs +3 -13
  126. package/clients/cli/commands/docs.mjs +21 -121
  127. package/clients/cli/commands/docs.test.mjs +0 -88
  128. package/clients/cli/commands/integration-authoring.test.mjs +9 -13
  129. package/clients/cli/commands/theme-palette-generate.doc.mjs +4 -8
  130. package/clients/cli/commands/upgrade.doc.mjs +2 -2
  131. package/clients/cli/formatters/index.mjs +1 -162
  132. package/clients/cli/formatters/index.test.mjs +0 -91
  133. package/clients/cli/lib/manifest.mjs +2 -7
  134. package/foundation/config/project.mjs +6 -21
  135. package/foundation/discovery/component-discovery.d.mts +1 -1
  136. package/foundation/discovery/component-discovery.mjs +1 -2
  137. package/foundation/discovery/docs-discovery.d.mts +4 -11
  138. package/foundation/discovery/docs-discovery.mjs +88 -208
  139. package/foundation/discovery/docs-discovery.test.mjs +13 -279
  140. package/foundation/discovery/template-adapter.mjs +1 -2
  141. package/foundation/integrations/autolink.mjs +5 -12
  142. package/foundation/integrations/integration-warnings.mjs +0 -6
  143. package/foundation/integrations/integrations.d.mts +2 -46
  144. package/foundation/integrations/integrations.mjs +8 -167
  145. package/foundation/integrations/integrations.test.mjs +1 -384
  146. package/foundation/integrations/validate-contributions.d.mts +0 -2
  147. package/foundation/integrations/validate-contributions.mjs +0 -10
  148. package/foundation/response/json-contract.test.mjs +17 -46
  149. package/foundation/response/response-types.doc.mjs +1 -6
  150. package/package.json +11 -9
  151. package/api/docs/compiled-topics.test.mjs +0 -78
  152. package/api/docs/index/index.d.mts +0 -18
  153. package/api/docs/index/index.mjs +0 -32
  154. package/api/docs/index/index.test.mjs +0 -62
  155. package/api/upgrade/project-context.test.mjs +0 -272
  156. package/assets/docs/authoring.doc.mjs +0 -14
  157. package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +0 -14
  158. package/assets/templates/blocks/components/Timer/TimerFormats.tsx +0 -34
  159. package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +0 -14
  160. package/assets/templates/blocks/components/Timer/TimerInline.tsx +0 -14
  161. package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +0 -13
  162. package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +0 -47
  163. package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +0 -14
  164. package/assets/templates/blocks/components/Timer/TimerTypography.tsx +0 -31
  165. package/authoring/doctypes/base/graph-fields.doc.d.mts +0 -9
  166. package/authoring/doctypes/base/graph-fields.doc.mjs +0 -62
  167. package/authoring/doctypes/load-contract.test.mjs +0 -207
  168. package/authoring/doctypes/namespace/namespace.doc.d.mts +0 -9
  169. package/authoring/doctypes/namespace/namespace.doc.mjs +0 -132
  170. package/authoring/doctypes/namespace/parse.d.mts +0 -12
  171. package/authoring/doctypes/namespace/parse.mjs +0 -25
  172. package/authoring/doctypes/namespace/parse.test.mjs +0 -165
  173. package/authoring/doctypes/namespace/type.ts +0 -71
  174. package/authoring/identity/identity.doc.d.mts +0 -9
  175. package/authoring/identity/identity.doc.mjs +0 -61
  176. package/authoring/identity/type.ts +0 -132
  177. package/foundation/discovery/authoring-self-docs.d.mts +0 -69
  178. package/foundation/discovery/authoring-self-docs.mjs +0 -214
  179. package/foundation/discovery/authoring-self-docs.test.mjs +0 -154
  180. package/foundation/discovery/docs-output-budget.d.mts +0 -28
  181. package/foundation/discovery/docs-output-budget.mjs +0 -50
  182. package/foundation/discovery/docs-section-key.d.mts +0 -98
  183. package/foundation/discovery/docs-section-key.mjs +0 -221
  184. package/foundation/discovery/docs-section-key.test.mjs +0 -224
  185. package/foundation/doc-compiler/compile.d.mts +0 -162
  186. package/foundation/doc-compiler/compile.mjs +0 -262
  187. package/foundation/doc-compiler/doc-compiler.test.mjs +0 -687
  188. package/foundation/doc-compiler/ir.d.mts +0 -9
  189. package/foundation/doc-compiler/ir.mjs +0 -287
  190. package/foundation/doc-compiler/lenses.d.mts +0 -33
  191. package/foundation/doc-compiler/lenses.mjs +0 -127
  192. package/foundation/identity/provider-identity.d.mts +0 -90
  193. package/foundation/identity/provider-identity.mjs +0 -320
  194. package/foundation/identity/provider-identity.test.mjs +0 -254
  195. package/foundation/identity/providers.d.mts +0 -7
  196. package/foundation/identity/providers.mjs +0 -16
  197. package/foundation/integrations/provider-conflicts.test.mjs +0 -125
@@ -3,29 +3,18 @@
3
3
  /**
4
4
  * @file docs command — Print Astryx reference docs
5
5
  *
6
- * A topic prints its whole doc; `--index` lists its sections instead, so a
7
- * reader can open one section by its key.
6
+ * Auto-discovers .doc.mjs files from the docs/ directory.
8
7
  * Supports --detail (full|compact|brief) and --lang (en|zh|dense).
9
8
  *
10
9
  * Usage:
11
10
  * astryx docs List available topics
12
- * astryx docs <topic> Print the whole topic
13
- * astryx docs <topic> --index List the topic's sections
11
+ * astryx docs <topic> Print full doc
14
12
  * astryx docs <topic> <section> Print one section
15
13
  */
16
14
 
17
15
  import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
18
16
  import {jsonOut} from '../../../foundation/response/json.mjs';
19
- import {
20
- emit,
21
- section,
22
- records,
23
- text,
24
- code,
25
- wrapText,
26
- displayWidth,
27
- WRAP_WIDTH,
28
- } from '../formatters/index.mjs';
17
+ import {emit, section, records, text, code} from '../formatters/index.mjs';
29
18
  import {cliError} from '../lib/cli-error.mjs';
30
19
  import {defineCommand} from '../lib/define-command.mjs';
31
20
  import {resultSet} from '../../../foundation/debug/index.mjs';
@@ -52,28 +41,6 @@ function formatTable(headers, rows) {
52
41
  return `${head}\n${sep}\n${body}`;
53
42
  }
54
43
 
55
- /**
56
- * A table too wide for {@link WRAP_WIDTH}: one `header: cell` line per cell and
57
- * a blank line between rows, so nothing runs off the side of a terminal.
58
- * @param {string[]} headers
59
- * @param {string[][]} rows
60
- * @returns {string}
61
- */
62
- function formatTableVertical(headers, rows) {
63
- const width = Math.max(...headers.map(h => h.length)) + 2;
64
- return rows
65
- .map(row =>
66
- headers
67
- .map((h, i) =>
68
- wrapText(`${`${h}:`.padEnd(width)}${row[i] ?? ''}`, {
69
- indent: ' '.repeat(width),
70
- }),
71
- )
72
- .join('\n'),
73
- )
74
- .join('\n\n');
75
- }
76
-
77
44
  /**
78
45
  * @param {string[]} headers
79
46
  * @param {string[][]} rows
@@ -91,7 +58,7 @@ function formatTableCompact(headers, rows) {
91
58
  function formatBlock(block, detail) {
92
59
  switch (block.type) {
93
60
  case 'prose':
94
- return wrapText(block.text);
61
+ return block.text;
95
62
 
96
63
  case 'heading':
97
64
  return `${'#'.repeat(block.level || 3)} ${block.text}`;
@@ -110,37 +77,16 @@ function formatBlock(block, detail) {
110
77
  if (detail === 'compact') {
111
78
  return formatTableCompact(block.headers, block.rows);
112
79
  }
113
- {
114
- const table = formatTable(block.headers, block.rows);
115
- return table.split('\n').some(line => displayWidth(line) > WRAP_WIDTH)
116
- ? formatTableVertical(block.headers, block.rows)
117
- : table;
118
- }
80
+ return formatTable(block.headers, block.rows);
119
81
 
120
82
  case 'list': {
121
- const prefix =
122
- block.style === 'ordered'
123
- ? (/** @type {number} */ i) => `${i + 1}. `
124
- : block.style === 'dont'
125
- ? () => 'x '
126
- : block.style === 'do'
127
- ? () => '+ '
128
- : () => '- ';
129
- return block.items
130
- .map((item, i) => {
131
- const head = prefix(i);
132
- return wrapText(`${head}${item}`, {indent: ' '.repeat(head.length)});
133
- })
134
- .join('\n');
83
+ const prefix = block.style === 'ordered' ? (/** @type {number} */ i) => `${i + 1}. `
84
+ : block.style === 'dont' ? () => 'x '
85
+ : block.style === 'do' ? () => '+ '
86
+ : () => '- ';
87
+ return block.items.map((item, i) => `${prefix(i)}${item}`).join('\n');
135
88
  }
136
89
 
137
- case 'workflow':
138
- case 'collection':
139
- case 'reference':
140
- throw new Error(
141
- `Documentation block "${block.type}" requires the compiled graph renderer.`,
142
- );
143
-
144
90
  default:
145
91
  return null;
146
92
  }
@@ -161,8 +107,7 @@ function formatSection(section, detail) {
161
107
  return `${section.title}: ${first.split('\n')[0]}`;
162
108
  }
163
109
 
164
- const heading =
165
- detail === 'compact' ? `[${section.title}]` : `## ${section.title}`;
110
+ const heading = detail === 'compact' ? `[${section.title}]` : `## ${section.title}`;
166
111
  return `${heading}\n\n${blocks.join('\n\n')}`;
167
112
  }
168
113
 
@@ -173,51 +118,25 @@ function formatSection(section, detail) {
173
118
  */
174
119
  function formatReferenceFull(docs, detail) {
175
120
  if (detail === 'brief') {
176
- const header = wrapText(`${docs.title}: ${docs.description}`);
121
+ const header = `${docs.title}: ${docs.description}`;
177
122
  const sections = docs.sections.map(s => formatSection(s, detail));
178
123
  return `${header}\n${sections.join('\n')}`;
179
124
  }
180
125
 
181
- const description = wrapText(docs.description);
182
- const header =
183
- detail === 'compact'
184
- ? `# ${docs.title}\n${description}`
185
- : `# ${docs.title}\n\n${description}`;
126
+ const header = detail === 'compact'
127
+ ? `# ${docs.title}\n${docs.description}`
128
+ : `# ${docs.title}\n\n${docs.description}`;
186
129
  const sections = docs.sections.map(s => formatSection(s, detail));
187
130
  const sep = detail === 'compact' ? '\n\n' : '\n\n';
188
131
  return `${header}\n\n${sections.join(sep)}`;
189
132
  }
190
133
 
191
- /**
192
- * A topic's section index: what the topic is, one line per section with the
193
- * key to read it by, and how to read further.
194
- * @param {import('../../../api/docs/docs.type.mjs').DocsIndex} index
195
- * @param {string} run
196
- */
197
- function emitIndex(index, run) {
198
- emit(
199
- section(index.title, index.description ? wrapText(index.description) : undefined),
200
- records(index.sections, {
201
- fields: ['id', 'title', 'summary'],
202
- layout: 'inline',
203
- overflow: 'truncate',
204
- }),
205
- text(
206
- [
207
- `Read one section: ${run} docs ${index.name} <section>`,
208
- `Read everything: ${run} docs ${index.name}`,
209
- ].join('\n'),
210
- ),
211
- );
212
- }
213
-
214
134
  /**
215
135
  * What the run answered with. A named topic (or one of its sections) resolves
216
136
  * or throws, so it is always a direct match of one doc; the bare form lists
217
137
  * every topic there is.
218
138
  *
219
139
  * @param {import('../../../api/docs/docs.type.mjs').DocsListResponse
220
- * | import('../../../api/docs/docs.type.mjs').DocsIndexResponse
221
140
  * | import('../../../api/docs/docs.type.mjs').DocsDetailResponse
222
141
  * | import('../../../api/docs/docs.type.mjs').DocsDetailSectionResponse} result
223
142
  * @returns {import('../../../foundation/debug/command-result.mjs').CommandResult}
@@ -236,11 +155,7 @@ function summarize(result) {
236
155
  export function registerDocs(program) {
237
156
  defineCommand(program, docsCommand, {
238
157
  fn: docsFn,
239
- action: async (
240
- /** @type {string | undefined} */ topic,
241
- /** @type {string | undefined} */ sectionName,
242
- /** @type {{index?: boolean}} */ options = {},
243
- ) => {
158
+ action: async (/** @type {string | undefined} */ topic, /** @type {string | undefined} */ sectionName) => {
244
159
  const run = getCliInvocation();
245
160
  const lang = program.opts().lang || null;
246
161
  const zh = program.opts().zh || false;
@@ -250,17 +165,11 @@ export function registerDocs(program) {
250
165
 
251
166
  let result;
252
167
  try {
253
- result = await docsApi(topic, sectionName, {
254
- lang,
255
- zh,
256
- dense,
257
- index: Boolean(options.index),
258
- });
168
+ result = await docsApi(topic, sectionName, {lang, zh, dense});
259
169
  } catch (e) {
260
170
  // docs API throws structured errors with {name, reason} suggestions —
261
171
  // pass them through untouched so the CLI envelope matches the API.
262
- const err =
263
- /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
172
+ const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
264
173
  return cliError(err.message, {
265
174
  suggestions: err.suggestions || [],
266
175
  code: err.code,
@@ -279,26 +188,17 @@ export function registerDocs(program) {
279
188
  // description), then the usage footer as plain prose.
280
189
  emit(
281
190
  section('Available docs'),
282
- records(result.data, {
283
- fields: ['topic', 'description'],
284
- layout: 'inline',
285
- }),
191
+ records(result.data, {fields: ['topic', 'description']}),
286
192
  text(
287
193
  [
288
- `Usage: ${run} docs <topic> read the whole topic`,
289
- ` ${run} docs <topic> --index list its sections`,
290
- ` ${run} docs <topic> <section> read one section`,
194
+ `Usage: ${run} docs <topic>`,
195
+ ` ${run} docs <topic> <section>`,
291
196
  ].join('\n'),
292
197
  ),
293
198
  );
294
199
  break;
295
200
  }
296
201
 
297
- case 'docs.index': {
298
- emitIndex(result.data, run);
299
- break;
300
- }
301
-
302
202
  case 'docs.detail': {
303
203
  emit(code(formatReferenceFull(result.data, detail)));
304
204
  break;
@@ -6,8 +6,6 @@ import * as path from 'node:path';
6
6
  import * as os from 'node:os';
7
7
  import {Command} from 'commander';
8
8
  import {registerDocs} from './docs.mjs';
9
- import {runCli} from '../../../test-utils/run-cli.mjs';
10
- import {displayWidth} from '../formatters/index.mjs';
11
9
 
12
10
  let tmpDir;
13
11
 
@@ -102,89 +100,3 @@ describe('migration docs', () => {
102
100
  expect(output).toContain('Map shadcn and Radix Primitives');
103
101
  });
104
102
  });
105
-
106
- describe('progressive reads', () => {
107
- const SLOW = 60_000;
108
- /** @param {string} out */
109
- const widest = out => Math.max(...out.split('\n').map(line => line.length));
110
-
111
- it('lists every topic on one line each', async () => {
112
- const {status, stdout} = await runCli(['docs']);
113
- expect(status).toBe(0);
114
- expect(stdout).toMatch(/^principles +\S/m);
115
- expect(widest(stdout)).toBeLessThanOrEqual(120);
116
- }, SLOW);
117
-
118
- it("prints a topic's section index with the keys to read by", async () => {
119
- const {status, stdout} = await runCli(['docs', 'theme', '--index']);
120
- expect(status).toBe(0);
121
- expect(stdout).toMatch(/^quick-start +Quick Start/m);
122
- expect(stdout).toContain('docs theme <section>');
123
- expect(stdout).toMatch(/Read everything: +\S.* docs theme$/m);
124
- expect(widest(stdout)).toBeLessThanOrEqual(120);
125
- }, SLOW);
126
-
127
- it('prints one section by its key', async () => {
128
- const {status, stdout} = await runCli(['docs', 'theme', 'quick-start']);
129
- expect(status).toBe(0);
130
- expect(stdout).toMatch(/^## Quick Start/m);
131
- }, SLOW);
132
-
133
- it('prints the whole topic by default, as before', async () => {
134
- const index = await runCli(['docs', 'theme', '--index']);
135
- const full = await runCli(['docs', 'theme']);
136
- expect(full.status).toBe(0);
137
- expect(full.stdout).toMatch(/^## Quick Start/m);
138
- expect(full.stdout.length).toBeGreaterThan(index.stdout.length * 3);
139
- expect((await runCli(['--detail', 'full', 'docs', 'theme'])).stdout).toBe(
140
- full.stdout,
141
- );
142
- expect(widest(full.stdout.replace(/```[\s\S]*?```/g, ''))).toBeLessThanOrEqual(
143
- 120,
144
- );
145
- }, SLOW);
146
-
147
- it('returns the matching envelopes as JSON', async () => {
148
- const envelope = async args => JSON.parse((await runCli([...args, '--json'])).stdout);
149
- expect((await envelope(['docs', 'theme'])).type).toBe('docs.detail');
150
- expect((await envelope(['docs', 'theme', '--index'])).type).toBe(
151
- 'docs.index',
152
- );
153
- expect((await envelope(['docs', 'theme', 'quick-start'])).type).toBe(
154
- 'docs.detail.section',
155
- );
156
- }, SLOW);
157
- });
158
-
159
- describe('text width in every language', () => {
160
- const SLOW = 60_000;
161
- /** Widest line outside code blocks, in terminal columns. */
162
- const widest = out => {
163
- let inCode = false;
164
- let max = 0;
165
- for (const line of out.split('\n')) {
166
- if (/^\s*```/.test(line)) {
167
- inCode = !inCode;
168
- continue;
169
- }
170
- // A single unbreakable token (a long URL) cannot wrap without breaking it.
171
- const oneToken = !/\s/.test(line.trim());
172
- if (!inCode && !line.startsWith('#') && !oneToken) {
173
- max = Math.max(max, displayWidth(line));
174
- }
175
- }
176
- return max;
177
- };
178
-
179
- it.each([
180
- [['docs', 'theme', '--index', '--lang', 'zh']],
181
- [['docs', 'theme', '--lang', 'zh']],
182
- [['--detail', 'full', 'docs', 'theme', '--lang', 'zh']],
183
- [['--detail', 'full', 'docs', 'internationalization']],
184
- [['--detail', 'full', 'docs', 'styling']],
185
- ])('%j fits in 120 columns', async args => {
186
- const {status, stdout} = await runCli(args);
187
- expect(status).toBe(0);
188
- expect(widest(stdout)).toBeLessThanOrEqual(120);
189
- }, SLOW);
190
- });
@@ -120,7 +120,7 @@ describe('integration authoring CLI', () => {
120
120
  );
121
121
  });
122
122
 
123
- it('pack-check rejects a generated component without an exports map (no-map false green)', async () => {
123
+ it('packs the generated contribution and proves the consumer inventory', async () => {
124
124
  const added = await runCli(
125
125
  ['integration', 'add', 'component', 'AcmeWidget', '--json'],
126
126
  tmpDir,
@@ -131,23 +131,19 @@ describe('integration authoring CLI', () => {
131
131
  ['integration', 'pack', '--check', '--json'],
132
132
  tmpDir,
133
133
  );
134
- // Without an exports map, the extensionless import cannot resolve —
135
- // pack-check must fail, not false-green.
136
- expect(checked.status).not.toBe(0);
137
- const envelope = parseEnvelope(checked.stdout);
138
- expect(envelope).toMatchObject({
134
+ expect(checked.status).toBe(0);
135
+ expect(parseEnvelope(checked.stdout)).toMatchObject({
139
136
  type: 'integration.pack-check',
140
137
  data: {
141
138
  name: '@acme/widgets',
142
- packable: false,
139
+ version: '1.0.0',
140
+ packable: true,
141
+ contributions: {
142
+ local: {components: ['AcmeWidget']},
143
+ packed: {components: ['AcmeWidget']},
144
+ },
143
145
  },
144
146
  });
145
- expect(envelope.data.issues).toContainEqual(
146
- expect.objectContaining({
147
- severity: 'error',
148
- message: expect.stringContaining('AcmeWidget'),
149
- }),
150
- );
151
147
  });
152
148
 
153
149
  it('requires the explicit --check gate on pack', async () => {
@@ -18,11 +18,7 @@ export const doc = {
18
18
  'Without --out it prints a preview. With --out it writes a candidate file and detached ' +
19
19
  'receipt. --preview writes a standardized, self-contained HTML review artifact. ' +
20
20
  'TypeScript output is directly importable and contains no generator dependency. ' +
21
- 'JSON is also supported. Existing author-owned files are left untouched unless --overwrite is explicit. ' +
22
- 'When used in a theme integration, keep the palette request under the theme slug, ' +
23
- 'write the candidate and receipt under that same slug, import the candidate from the theme source, ' +
24
- "and list all three paths in the theme catalog entry's `files` array " +
25
- 'so `astryx theme add` copies them into the consumer project.',
21
+ 'JSON is also supported. Existing author-owned files are left untouched unless --overwrite is explicit.',
26
22
  fn: 'themePaletteGenerate',
27
23
  args: [{name: 'config', param: 'configPath', required: true}],
28
24
  options: [
@@ -46,15 +42,15 @@ export const doc = {
46
42
  examples: [
47
43
  {
48
44
  label: 'Preview candidate JSON',
49
- cli: 'astryx theme palette generate themes/ocean/palette.config.json',
45
+ cli: 'astryx theme palette generate palette.config.json',
50
46
  },
51
47
  {
52
48
  label: 'Write candidate and receipt',
53
- cli: 'astryx theme palette generate themes/ocean/palette.config.json --out themes/ocean/tokens/ocean.palette.ts',
49
+ cli: 'astryx theme palette generate palette.config.json --out ocean.palette.ts',
54
50
  },
55
51
  {
56
52
  label: 'Write candidate, receipt, and review preview',
57
- cli: 'astryx theme palette generate themes/ocean/palette.config.json --out themes/ocean/tokens/ocean.palette.ts --preview themes/ocean/tokens/ocean.palette.html',
53
+ cli: 'astryx theme palette generate palette.config.json --out ocean.palette.ts --preview ocean.palette.html',
58
54
  },
59
55
  ],
60
56
  exitCodes: [
@@ -53,10 +53,10 @@ export const doc = {
53
53
  'Exclude named codemods (repeatable). Re-run past a failed codemod by skipping it.',
54
54
  },
55
55
  {
56
- flag: '--integration <package>',
56
+ flag: '--integration <package-or-file>',
57
57
  param: 'options.integration',
58
58
  description:
59
- 'Explicit integration specifier (repeatable). Resolved beneath node_modules; absolute paths and `.` or `..` segments are rejected.',
59
+ 'Explicit integration package name or integration file path (repeatable)',
60
60
  default: [],
61
61
  },
62
62
  {
@@ -11,8 +11,7 @@
11
11
  * which fields to show and in what order.
12
12
  *
13
13
  * Constraints (deliberately narrow):
14
- * - Plain ASCII only. No color and no TTY detection. Long lines wrap at a
15
- * fixed {@link WRAP_WIDTH}, never at the terminal's width, so output is
14
+ * - Plain ASCII only. No color, no TTY detection, no width wrapping. Output is
16
15
  * byte-for-byte deterministic whether printed or piped to an agent.
17
16
  * - Renderers return an opaque {@link Block}; `emit` accepts ONLY Blocks, so a
18
17
  * stray string can't leak onto stdout (the compiler rejects `emit('x')`).
@@ -34,50 +33,6 @@ export const BULLET = '-';
34
33
  export const ERR = '!!';
35
34
  export const WARN = '!';
36
35
 
37
- /** The column long human-output lines wrap at. */
38
- export const WRAP_WIDTH = 120;
39
-
40
- /** The widest first column an inline record pads to; a longer value overhangs. */
41
- const INLINE_LEAD_MAX = 32;
42
-
43
- /**
44
- * Characters a terminal draws two columns wide: CJK ideographs and
45
- * punctuation, kana, hangul, and fullwidth forms. A line may break between
46
- * any two of them.
47
- */
48
- const WIDE_CHAR =
49
- /[\u1100-\u115f\u2e80-\u303e\u3041-\u33ff\u3400-\u4dbf\u4e00-\u9fff\ua000-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/u;
50
-
51
- /**
52
- * How many terminal columns a string takes.
53
- * @param {string} s
54
- * @returns {number}
55
- */
56
- export function displayWidth(s) {
57
- let width = 0;
58
- for (const ch of String(s)) width += WIDE_CHAR.test(ch) ? 2 : 1;
59
- return width;
60
- }
61
-
62
- /**
63
- * Cut a line to `width` columns, ending it with `...` when anything was cut.
64
- * @param {string} line
65
- * @param {number} width
66
- * @returns {string}
67
- */
68
- function truncateToWidth(line, width) {
69
- if (displayWidth(line) <= width) return line;
70
- let out = '';
71
- let used = 0;
72
- for (const ch of line) {
73
- const w = WIDE_CHAR.test(ch) ? 2 : 1;
74
- if (used + w > width - 3) break;
75
- out += ch;
76
- used += w;
77
- }
78
- return `${out.trimEnd()}...`;
79
- }
80
-
81
36
  /**
82
37
  * An opaque, renderer-produced block of output. Nominal via a private field:
83
38
  * nothing outside this file can construct one, so `emit` can trust that whatever
@@ -112,13 +67,6 @@ export class Block {
112
67
  * @property {Record<string, string>} [labels] - Rename a key for display.
113
68
  * @property {Record<string, (value: any) => string>} [format] - Transform a
114
69
  * value before rendering (e.g. prefix a command with the package manager).
115
- * @property {'stacked' | 'inline'} [layout] - `stacked` (the default): one
116
- * `key: value` line per field, a blank line between records. `inline`: one
117
- * record per line, the first field in a padded column and the rest joined by
118
- * ` - `, for a list a reader scans.
119
- * @property {'wrap' | 'truncate'} [overflow] - What an inline record longer
120
- * than {@link WRAP_WIDTH} does: `wrap` (the default) continues under the
121
- * first column; `truncate` cuts it to one line.
122
70
  */
123
71
 
124
72
  /** @param {unknown} v @returns {boolean} */
@@ -155,72 +103,6 @@ function toAscii(s) {
155
103
  .replace(/\u00a0/g, ' ');
156
104
  }
157
105
 
158
- /**
159
- * Word-wrap text at `width`, keeping its own line breaks. A word longer than
160
- * the width stays whole on a line of its own. Continuation lines start with
161
- * `indent`.
162
- * @param {string} input
163
- * @param {{width?: number, indent?: string}} [options]
164
- * @returns {string}
165
- */
166
- export function wrapText(input, {width = WRAP_WIDTH, indent = ''} = {}) {
167
- return String(input)
168
- .split('\n')
169
- .map(line => wrapLine(line, width, indent))
170
- .join('\n');
171
- }
172
-
173
- /**
174
- * @param {string} line
175
- * @param {number} width
176
- * @param {string} indent
177
- * @returns {string}
178
- */
179
- function wrapLine(line, width, indent) {
180
- if (displayWidth(line) <= width) return line;
181
- const lead = /^\s*/.exec(line)?.[0] ?? '';
182
- // Tokens a line may break between: words, and each wide character on its
183
- // own, since CJK text has no spaces to break at. `gap` is what joined a
184
- // token to the one before it.
185
- /** @type {{text: string, gap: string}[]} */
186
- const tokens = [];
187
- let gap = '';
188
- let word = '';
189
- const flush = () => {
190
- if (word === '') return;
191
- tokens.push({text: word, gap});
192
- gap = '';
193
- word = '';
194
- };
195
- for (const ch of line.slice(lead.length)) {
196
- if (ch === ' ') {
197
- flush();
198
- gap = ' ';
199
- } else if (WIDE_CHAR.test(ch)) {
200
- flush();
201
- tokens.push({text: ch, gap});
202
- gap = '';
203
- } else {
204
- word += ch;
205
- }
206
- }
207
- flush();
208
- /** @type {string[]} */
209
- const out = [];
210
- let current = lead;
211
- tokens.forEach(({text: token, gap: before}, i) => {
212
- const joined = i === 0 ? current + token : current + before + token;
213
- if (i > 0 && displayWidth(joined) > width && current.trim() !== '') {
214
- out.push(current);
215
- current = indent + token;
216
- } else {
217
- current = joined;
218
- }
219
- });
220
- out.push(current);
221
- return out.join('\n');
222
- }
223
-
224
106
  /**
225
107
  * A group label, optionally with an explanatory subtitle rendered on the line(s)
226
108
  * directly beneath the heading (no blank line between). Whatever list/records
@@ -295,53 +177,10 @@ export function record(obj, options = {}) {
295
177
  * @returns {Block}
296
178
  */
297
179
  export function records(items, options = {}) {
298
- if (options.layout === 'inline') return inlineRecords(items, options);
299
180
  const blocks = items.map(o => record(o, options).toString()).filter(Boolean);
300
181
  return new Block(blocks.join('\n\n'));
301
182
  }
302
183
 
303
- /**
304
- * @param {any[]} items
305
- * @param {RecordOptions} options
306
- * @returns {Block}
307
- */
308
- function inlineRecords(items, options) {
309
- const [lead, ...rest] = (options.fields ?? Object.keys(items[0] ?? {})).filter(
310
- k => !options.omit?.includes(k),
311
- );
312
- if (lead == null) return new Block('');
313
- /** @param {any} o @param {string} k */
314
- const value = (o, k) => {
315
- const fmt = options.format?.[k];
316
- return fmt ? fmt(o[k]) : renderValue(o[k]);
317
- };
318
- const leads = items.map(o => (isEmpty(o[lead]) ? '' : value(o, lead)));
319
- const column = Math.min(
320
- Math.max(0, ...leads.map(l => displayWidth(l))),
321
- INLINE_LEAD_MAX,
322
- );
323
- const indent = ' '.repeat(column + 2);
324
- const lines = items.map((o, i) => {
325
- const tail = toAscii(
326
- rest
327
- .filter(k => !isEmpty(o[k]))
328
- .map(k => value(o, k))
329
- .join(' - '),
330
- );
331
- if (tail === '') return toAscii(leads[i]);
332
- const head = toAscii(`${leads[i].padEnd(column)} `);
333
- if (options.overflow === 'truncate') {
334
- return truncateToWidth(head + tail, WRAP_WIDTH);
335
- }
336
- // Wrap only the tail, so the padded first column survives the wrap.
337
- const [first, ...more] = wrapText(tail, {
338
- width: Math.max(20, WRAP_WIDTH - displayWidth(head)),
339
- }).split('\n');
340
- return [head + first, ...more.map(line => indent + line)].join('\n');
341
- });
342
- return new Block(lines.join('\n'));
343
- }
344
-
345
184
  /**
346
185
  * A verbatim block — source dumps, layout skeletons, or a markdown doc. Output
347
186
  * is byte-for-byte (NOT ASCII-normalized), so it survives piping