@astryxdesign/cli 0.6.3-canary.c82ebb8 → 0.6.3-canary.ca632c6

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 (63) hide show
  1. package/README.md +2 -0
  2. package/api/docs/detail/section/section.mjs +9 -15
  3. package/api/docs/detail/section/section.test.mjs +15 -6
  4. package/api/doctor/doctor.mjs +1 -1
  5. package/api/doctor/doctor.test.mjs +6 -6
  6. package/api/upgrade/_adapter.d.mts +22 -4
  7. package/api/upgrade/_adapter.mjs +19 -6
  8. package/api/upgrade/run/run.mjs +355 -59
  9. package/api/upgrade/upgrade.doc.mjs +5 -1
  10. package/api/upgrade/upgrade.type.d.mts +34 -0
  11. package/api/upgrade/upgrade.type.mjs +15 -0
  12. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  13. package/assets/codemods/integration-runner.mjs +56 -4
  14. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  15. package/assets/codemods/run-codemod.mjs +177 -34
  16. package/assets/codemods/runner.mjs +248 -110
  17. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  18. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +4 -0
  19. package/assets/codemods/transforms/next/index.mjs +8 -0
  20. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  21. package/assets/docs/cli-integrations.doc.mjs +13 -0
  22. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  23. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  24. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  25. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  26. package/authoring/codemod/type.ts +12 -0
  27. package/authoring/doctypes/_schema.d.mts +71 -141
  28. package/authoring/doctypes/_schema.mjs +29 -2
  29. package/authoring/doctypes/load-contract.test.mjs +1 -1
  30. package/authoring/doctypes/namespace/namespace.doc.mjs +2 -2
  31. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  32. package/authoring/doctypes/namespace/type.ts +5 -2
  33. package/authoring/doctypes/parse.mjs +3 -2
  34. package/authoring/doctypes/reference/reference.doc.mjs +5 -3
  35. package/authoring/doctypes/reference/type.ts +7 -8
  36. package/authoring/index.d.ts +1 -0
  37. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  38. package/clients/cli/commands/build-theme.mjs +10 -44
  39. package/clients/cli/commands/docs.mjs +15 -58
  40. package/clients/cli/commands/docs.test.mjs +11 -14
  41. package/clients/cli/commands/text-json-parity.test.mjs +718 -0
  42. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  43. package/clients/cli/commands/upgrade.doc.mjs +2 -1
  44. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  45. package/clients/cli/commands/upgrade.mjs +29 -7
  46. package/clients/cli/formatters/index.mjs +2 -0
  47. package/clients/cli/formatters/index.test.mjs +6 -0
  48. package/foundation/discovery/authoring-self-docs.test.mjs +3 -2
  49. package/foundation/discovery/docs-discovery.mjs +6 -5
  50. package/foundation/discovery/docs-discovery.test.mjs +7 -10
  51. package/foundation/discovery/docs-section-key.d.mts +19 -8
  52. package/foundation/discovery/docs-section-key.mjs +119 -32
  53. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  54. package/foundation/doc-compiler/compile.mjs +5 -4
  55. package/foundation/fs/file-protection.d.mts +33 -0
  56. package/foundation/fs/file-protection.mjs +825 -0
  57. package/foundation/fs/file-protection.test.mjs +250 -0
  58. package/foundation/response/error-codes.d.mts +3 -1
  59. package/foundation/response/error-codes.d.ts +2 -0
  60. package/foundation/response/error-codes.doc.mjs +10 -0
  61. package/foundation/response/error-codes.mjs +6 -0
  62. package/foundation/response/error-codes.test.mjs +55 -11
  63. package/package.json +10 -9
package/README.md CHANGED
@@ -189,6 +189,8 @@ if (isError(result)) {
189
189
  | `ERR_UNKNOWN_FEATURE` | An unrecognized `--features` value was passed to init. |
190
190
  | `ERR_UNKNOWN_CODEMOD` | A `--codemod` value did not match any registered codemod (upgrade). |
191
191
  | `ERR_CODEMOD_FAILED` | One or more codemods failed during an upgrade run. |
192
+ | `ERR_CODEMOD_PROTECTED` | A required codemod change remains blocked by a protected consumer file. |
193
+ | `ERR_CODEMOD_PROTECTION_SOURCE` | A working-tree protection declaration could not be read or parsed. |
192
194
  | `ERR_NOT_FOUND` | A generic discover/lookup query matched nothing in any package. |
193
195
  | `ERR_NO_DOC` | A component exists but has no typed `.doc.mjs` file. |
194
196
  | `ERR_NO_SHOWCASE` | No showcase exists for the requested component. |
@@ -4,9 +4,9 @@
4
4
  * @file docs.detail.section leaf — load a single section of a topic.
5
5
  *
6
6
  * @input A topic name, a section query, and optional {lang, zh, dense}. Resolves
7
- * the topic via the shared adapter, finds the section in its lowered compiled
8
- * node by its stable key, then by exact title, then by a title that contains
9
- * the query, and links only that section.
7
+ * the topic via the shared adapter, keeps the 0.6.x first title-substring
8
+ * match, then falls back to a stable key or normalized title key, and links
9
+ * only that section.
10
10
  * @output { type: 'docs.detail.section', data: ReferenceSection } with any
11
11
  * token-ref blocks inlined — matching `astryx --json docs <topic> <section>`.
12
12
  * Throws ERR_UNKNOWN_SECTION when nothing matches, or when the query matches
@@ -17,10 +17,7 @@
17
17
 
18
18
  import {AstryxError} from '../../../error.mjs';
19
19
  import {ERROR_CODES} from '../../../../foundation/response/error-codes.mjs';
20
- import {
21
- findDocSection,
22
- sectionKey,
23
- } from '../../../../foundation/discovery/docs-section-key.mjs';
20
+ import {findDocSection} from '../../../../foundation/discovery/docs-section-key.mjs';
24
21
  import {linkReferenceSection} from '../../../../foundation/doc-compiler/compile.mjs';
25
22
  import {
26
23
  readerSections,
@@ -52,16 +49,13 @@ export async function section(topic, sectionName, options = {}) {
52
49
 
53
50
  const {catalog, node, lang} = await resolveTopicDocs(topic, options);
54
51
  const sections = readerSections(node);
55
- const {section: match, candidates} = findDocSection(sections, sectionName);
52
+ const {section: match} = findDocSection(sections, sectionName);
56
53
  if (!match) {
57
- const ambiguous = candidates.length > 1;
58
54
  throw new AstryxError(
59
- ambiguous
60
- ? `Section "${sectionName}" matches ${candidates.length} sections in "${topic}". Read one by its key.`
61
- : `Section "${sectionName}" not found in "${topic}"`,
62
- (ambiguous ? candidates : sections).map(s => ({
63
- name: sectionKey(s),
64
- reason: s.title,
55
+ `Section "${sectionName}" not found in "${topic}"`,
56
+ sections.map(s => ({
57
+ name: s.title,
58
+ reason: 'available section',
65
59
  })),
66
60
  ERROR_CODES.ERR_UNKNOWN_SECTION,
67
61
  );
@@ -30,6 +30,17 @@ describe('docs.detail.section leaf', () => {
30
30
  }
31
31
  expect(err).toBeInstanceOf(AstryxError);
32
32
  expect(err.code).toBe('ERR_UNKNOWN_SECTION');
33
+ expect(err.suggestions).toEqual(
34
+ expect.arrayContaining([
35
+ expect.objectContaining({
36
+ name: expect.any(String),
37
+ reason: 'available section',
38
+ }),
39
+ ]),
40
+ );
41
+ expect(err.suggestions.some(suggestion => /\s/u.test(suggestion.name))).toBe(
42
+ true,
43
+ );
33
44
  }, SLOW);
34
45
 
35
46
  it('does not return the first section for an empty section name', async () => {
@@ -50,12 +61,10 @@ describe('docs.detail.section leaf', () => {
50
61
  expect(res.data.id).toBe(target.id);
51
62
  }, SLOW);
52
63
 
53
- it('refuses a query that matches more than one section', async () => {
54
- const err = await section('theme', 'e').catch(e => e);
55
- expect(err).toBeInstanceOf(AstryxError);
56
- expect(err.code).toBe('ERR_UNKNOWN_SECTION');
57
- expect(err.message).toMatch(/matches \d+ sections/);
58
- expect(err.suggestions.length).toBeGreaterThan(1);
64
+ it('keeps a previously accepted ambiguous query on its first match', async () => {
65
+ const res = await section('theme', 'e');
66
+ expect(res.type).toBe('docs.detail.section');
67
+ expect(res.data).toBeDefined();
59
68
  }, SLOW);
60
69
 
61
70
  it.each([null, 'zh', 'dense'])(
@@ -994,7 +994,7 @@ export async function checkDocsProgressiveDisclosure(ctx) {
994
994
  return {
995
995
  id,
996
996
  label,
997
- status: 'fail',
997
+ status: 'warn',
998
998
  message: joinProblems(problems),
999
999
  fix: 'Fix the doc each problem names; split a section that is too large into smaller ones, each with its own key.',
1000
1000
  };
@@ -391,7 +391,7 @@ describe('checkDocsProgressiveDisclosure', () => {
391
391
  expect(c.message).toMatch(/^\d+ topics: /);
392
392
  }, SLOW);
393
393
 
394
- it('fails on an invalid doc an integration contributed', async () => {
394
+ it('warns on an invalid doc an integration contributed', async () => {
395
395
  const c = await checkDocsProgressiveDisclosure({
396
396
  docsCatalogIssues: [
397
397
  {
@@ -402,13 +402,13 @@ describe('checkDocsProgressiveDisclosure', () => {
402
402
  },
403
403
  ],
404
404
  });
405
- expect(c.status).toBe('fail');
405
+ expect(c.status).toBe('warn');
406
406
  expect(c.message).toBe('@acme/widgets: bad.doc.mjs exports no doc');
407
407
  });
408
408
 
409
- it('fails when the docs catalog cannot be built', async () => {
409
+ it('warns when the docs catalog cannot be built', async () => {
410
410
  const c = await checkDocsProgressiveDisclosure({docsCatalogError: 'boom'});
411
- expect(c.status).toBe('fail');
411
+ expect(c.status).toBe('warn');
412
412
  expect(c.message).toContain('boom');
413
413
  });
414
414
 
@@ -436,7 +436,7 @@ describe('checkDocsProgressiveDisclosure', () => {
436
436
  }),
437
437
  docsCatalogIssues: [],
438
438
  });
439
- expect(c.status).toBe('fail');
439
+ expect(c.status).toBe('warn');
440
440
  expect(c.message).toMatch(/^2 problems: /);
441
441
  expect(c.message).toContain('huge everything: 41 KB, over the 32 KB one read may return');
442
442
  expect(c.message).toContain('broken: ');
@@ -812,7 +812,7 @@ describe('checkDocsProgressiveDisclosure languages', () => {
812
812
  docsCatalog: DocsCatalog.fromBuiltins({deploying: path.join(dir, 'deploying.doc.mjs')}),
813
813
  docsCatalogIssues: [],
814
814
  });
815
- expect(c.status).toBe('fail');
815
+ expect(c.status).toBe('warn');
816
816
  expect(c.message).toContain('deploying [zh]: zh overlay broken');
817
817
  expect(c.message).toContain('deploying [dense] overview: 41 KB');
818
818
  expect(c.message).not.toMatch(/deploying overview:/);
@@ -104,19 +104,27 @@ export function ensureCodemodDeps({ installDeps }?: {
104
104
  * Run the CORE registry codemods. Runs BEFORE the config is loaded so a core
105
105
  * CONFIG codemod can repair a config the strict loader would otherwise reject.
106
106
  * @param {CoreVersionManifest[]} versionManifests
107
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string}} options
107
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean}} options
108
108
  */
109
- export function runCoreCodemods(versionManifests: CoreVersionManifest[], { apply, path: srcPath, codemod, skipCodemods, root }: {
109
+ export function runCoreCodemods(versionManifests: CoreVersionManifest[], { apply, path: srcPath, codemod, skipCodemods, root, protection, silent }: {
110
110
  apply: boolean;
111
111
  path: string;
112
112
  codemod?: string;
113
113
  skipCodemods: Set<string>;
114
114
  root?: string;
115
+ protection?: {
116
+ root: string;
117
+ classify: (file: string) => import("../../foundation/fs/file-protection.mjs").FileProtection[];
118
+ };
119
+ silent?: boolean;
115
120
  }): Promise<{
116
121
  totalFilesChanged: number;
117
122
  totalTransformsApplied: number;
118
123
  totalValidationBlocked: number;
124
+ changedFiles: string[];
119
125
  writtenFiles: string[];
126
+ stagedContents: Map<string, string>;
127
+ protectedFiles: import("../../authoring/codemod/type").CodemodRunResult["protectedFiles"];
120
128
  errors: Array<{
121
129
  file: string;
122
130
  codemod: string;
@@ -180,20 +188,30 @@ export function selectIntegrationCodemodsFor(integrations: Array<import("../../f
180
188
  /**
181
189
  * Run the file-based INTEGRATION codemods (config codemods first, then code).
182
190
  * @param {Array<{version: string, codemods: import('../../authoring/codemod/type').CodemodEntry[]}>} versionGroups
183
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>}} options
191
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, contents?: Map<string, string>, silent?: boolean}} options
184
192
  */
185
193
  export function runIntegrationCodemodsStep(versionGroups: Array<{
186
194
  version: string;
187
195
  codemods: import("../../authoring/codemod/type").CodemodEntry[];
188
- }>, { apply, path: srcPath, codemod, skipCodemods }: {
196
+ }>, { apply, path: srcPath, codemod, skipCodemods, root, protection, contents, silent, }: {
189
197
  apply: boolean;
190
198
  path: string;
191
199
  codemod?: string;
192
200
  skipCodemods: Set<string>;
201
+ root?: string;
202
+ protection?: {
203
+ root: string;
204
+ classify: (file: string) => import("../../foundation/fs/file-protection.mjs").FileProtection[];
205
+ };
206
+ contents?: Map<string, string>;
207
+ silent?: boolean;
193
208
  }): Promise<{
194
209
  totalFilesChanged: number;
195
210
  totalTransformsApplied: number;
211
+ changedFiles: string[];
196
212
  writtenFiles: string[];
213
+ stagedContents: Map<string, string>;
214
+ protectedFiles: import("../../authoring/codemod/type").CodemodRunResult["protectedFiles"];
197
215
  errors: Array<{
198
216
  file: string;
199
217
  codemod: string;
@@ -329,19 +329,20 @@ export async function ensureCodemodDeps({installDeps} = {}) {
329
329
  * Run the CORE registry codemods. Runs BEFORE the config is loaded so a core
330
330
  * CONFIG codemod can repair a config the strict loader would otherwise reject.
331
331
  * @param {CoreVersionManifest[]} versionManifests
332
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string}} options
332
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean}} options
333
333
  */
334
334
  export async function runCoreCodemods(
335
335
  versionManifests,
336
- {apply, path: srcPath, codemod, skipCodemods, root},
336
+ {apply, path: srcPath, codemod, skipCodemods, root, protection, silent},
337
337
  ) {
338
338
  return runCodemods(versionManifests, {
339
339
  apply,
340
340
  path: srcPath,
341
341
  codemod,
342
342
  skipCodemods,
343
- silent: logger.silent,
343
+ silent: silent ?? logger.silent,
344
344
  root,
345
+ protection,
345
346
  });
346
347
  }
347
348
 
@@ -479,11 +480,20 @@ export async function selectIntegrationCodemodsFor(integrations, from, to) {
479
480
  /**
480
481
  * Run the file-based INTEGRATION codemods (config codemods first, then code).
481
482
  * @param {Array<{version: string, codemods: import('../../authoring/codemod/type').CodemodEntry[]}>} versionGroups
482
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>}} options
483
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, contents?: Map<string, string>, silent?: boolean}} options
483
484
  */
484
485
  export async function runIntegrationCodemodsStep(
485
486
  versionGroups,
486
- {apply, path: srcPath, codemod, skipCodemods},
487
+ {
488
+ apply,
489
+ path: srcPath,
490
+ codemod,
491
+ skipCodemods,
492
+ root,
493
+ protection,
494
+ contents,
495
+ silent,
496
+ },
487
497
  ) {
488
498
  const jscodeshift = (await import('jscodeshift')).default;
489
499
  return runIntegrationCodemods(versionGroups, {
@@ -492,6 +502,9 @@ export async function runIntegrationCodemodsStep(
492
502
  codemod,
493
503
  skipCodemods,
494
504
  jscodeshift,
495
- silent: logger.silent,
505
+ silent: silent ?? logger.silent,
506
+ root,
507
+ protection,
508
+ contents,
496
509
  });
497
510
  }