@astryxdesign/cli 0.6.4 → 0.6.5-canary.031021b

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 (288) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +97 -90
  3. package/api/build/build.doc.mjs +6 -1
  4. package/api/build/build.test.mjs +22 -0
  5. package/api/build/kit/kit.mjs +44 -5
  6. package/api/component/_adapter.d.mts +25 -0
  7. package/api/component/_adapter.mjs +59 -5
  8. package/api/component/component.d.mts +6 -3
  9. package/api/component/component.doc.mjs +37 -17
  10. package/api/component/component.mjs +249 -9
  11. package/api/component/component.type.d.mts +25 -0
  12. package/api/component/component.type.mjs +44 -0
  13. package/api/discover/_adapter.d.mts +114 -6
  14. package/api/discover/_adapter.mjs +372 -17
  15. package/api/discover/_adapter.test.mjs +215 -0
  16. package/api/discover/_catalog-view.d.mts +115 -0
  17. package/api/discover/_catalog-view.mjs +203 -0
  18. package/api/discover/_catalog-view.test.mjs +128 -0
  19. package/api/discover/detail/detail.d.mts +18 -6
  20. package/api/discover/detail/detail.mjs +67 -13
  21. package/api/discover/detail/detail.test.mjs +85 -0
  22. package/api/discover/detail/item/item.d.mts +26 -0
  23. package/api/discover/detail/item/item.mjs +78 -0
  24. package/api/discover/detail/item/item.test.mjs +73 -0
  25. package/api/discover/discover.d.mts +3 -9
  26. package/api/discover/discover.doc.mjs +61 -18
  27. package/api/discover/discover.mjs +220 -36
  28. package/api/discover/discover.test.mjs +11 -2
  29. package/api/discover/discover.type.d.mts +147 -8
  30. package/api/discover/discover.type.mjs +102 -12
  31. package/api/discover/list/list.d.mts +20 -6
  32. package/api/discover/list/list.mjs +45 -12
  33. package/api/discover/list/list.test.mjs +46 -0
  34. package/api/discover/search/search.d.mts +18 -16
  35. package/api/discover/search/search.mjs +102 -56
  36. package/api/discover/search/search.test.mjs +144 -10
  37. package/api/docs/_adapter.d.mts +8 -3
  38. package/api/docs/_adapter.mjs +14 -6
  39. package/api/docs/docOverlays.test.mjs +27 -1
  40. package/api/docs/docs.doc.mjs +2 -2
  41. package/api/doctor/doctor.d.mts +8 -3
  42. package/api/doctor/doctor.doc.mjs +17 -8
  43. package/api/doctor/doctor.mjs +90 -9
  44. package/api/doctor/doctor.test.mjs +122 -10
  45. package/api/doctor/doctor.type.d.mts +1 -1
  46. package/api/doctor/doctor.type.mjs +1 -1
  47. package/api/gap-report/gap-report.doc.mjs +19 -10
  48. package/api/hook/hook.doc.mjs +6 -3
  49. package/api/index.d.mts +1 -0
  50. package/api/index.mjs +5 -3
  51. package/api/init/init.doc.mjs +17 -12
  52. package/api/integration/add-helpers.d.mts +5 -2
  53. package/api/integration/add-helpers.mjs +36 -9
  54. package/api/integration/add-theme.mjs +22 -1
  55. package/api/integration/add-theme.test.mjs +34 -0
  56. package/api/integration/authoring-checks.mjs +2 -2
  57. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  58. package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
  59. package/api/integration/pack-check.mjs +82 -9
  60. package/api/integration/pack-check.test.mjs +90 -0
  61. package/api/integration/pack-check.type.mjs +1 -1
  62. package/api/json/assertResponse.doc.mjs +1 -1
  63. package/api/json/isError.doc.mjs +1 -1
  64. package/api/search/search.d.mts +27 -1
  65. package/api/search/search.doc.mjs +2 -2
  66. package/api/search/search.mjs +228 -16
  67. package/api/swizzle/swizzle.doc.mjs +7 -5
  68. package/api/template/copy/copy.mjs +1 -1
  69. package/api/template/copy/copy.test.mjs +9 -0
  70. package/api/template/template.doc.mjs +2 -1
  71. package/api/theme/add/add.mjs +17 -25
  72. package/api/theme/add/add.rollback.test.mjs +158 -0
  73. package/api/theme/add/add.staging.test.mjs +40 -23
  74. package/api/theme/build/build.family.test.mjs +7 -12
  75. package/api/theme/build/build.mjs +8 -18
  76. package/api/theme/build/build.rollback.test.mjs +148 -0
  77. package/api/theme/generateTonalPalette.doc.mjs +1 -2
  78. package/api/theme/listThemes.doc.mjs +1 -1
  79. package/api/theme/themeAdd.doc.mjs +9 -10
  80. package/api/theme/themeBuild.doc.mjs +13 -13
  81. package/api/theme/themeList.doc.mjs +1 -1
  82. package/api/theme/themeListAvailable.doc.mjs +2 -1
  83. package/api/theme/themePaletteGenerate.doc.mjs +15 -8
  84. package/api/theme/themeTargets.doc.mjs +3 -2
  85. package/api/theme/themeTemplate.doc.mjs +2 -1
  86. package/api/upgrade/run/files-changed.test.mjs +111 -0
  87. package/api/upgrade/run/run.mjs +5 -3
  88. package/api/upgrade/upgrade.doc.mjs +24 -22
  89. package/api/upgrade/upgrade.type.mjs +2 -2
  90. package/assets/codemods/__tests__/runner.test.mjs +3 -1
  91. package/assets/codemods/file-count.test.mjs +163 -0
  92. package/assets/codemods/integration-runner.mjs +3 -3
  93. package/assets/codemods/runner.mjs +5 -4
  94. package/assets/docs/README.md +4 -2
  95. package/assets/docs/browser-support.doc.mjs +11 -11
  96. package/assets/docs/color.doc.mjs +8 -2
  97. package/assets/docs/elevation.doc.mjs +6 -4
  98. package/assets/docs/getting-started.doc.mjs +5 -16
  99. package/assets/docs/icons.doc.mjs +2 -21
  100. package/assets/docs/illustrations.doc.mjs +7 -15
  101. package/assets/docs/internationalization.doc.mjs +7 -5
  102. package/assets/docs/layout.doc.dense.mjs +130 -82
  103. package/assets/docs/layout.doc.mjs +133 -77
  104. package/assets/docs/migration.doc.mjs +19 -21
  105. package/assets/docs/motion.doc.mjs +16 -3
  106. package/assets/docs/principles.doc.dense.mjs +5 -5
  107. package/assets/docs/principles.doc.mjs +8 -0
  108. package/assets/docs/principles.doc.zh.mjs +6 -6
  109. package/assets/docs/shape.doc.mjs +8 -3
  110. package/assets/docs/spacing.doc.mjs +7 -2
  111. package/assets/docs/styling-libraries.doc.mjs +6 -2
  112. package/assets/docs/styling.doc.mjs +19 -23
  113. package/assets/docs/theme.doc.dense.mjs +58 -18
  114. package/assets/docs/theme.doc.mjs +57 -47
  115. package/assets/docs/theme.doc.zh.mjs +9 -8
  116. package/assets/docs/tokens.doc.dense.mjs +2 -2
  117. package/assets/docs/tokens.doc.mjs +389 -8
  118. package/assets/docs/tokens.doc.zh.mjs +2 -2
  119. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  120. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  121. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  122. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  123. package/assets/docs/tree/block-template.doc.mjs +130 -0
  124. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  125. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  126. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  127. package/assets/docs/tree/checks.doc.mjs +119 -0
  128. package/assets/docs/tree/codemods.doc.mjs +147 -0
  129. package/assets/docs/tree/component-family.doc.mjs +113 -0
  130. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  131. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  132. package/assets/docs/tree/components.doc.mjs +23 -0
  133. package/assets/docs/tree/configuration.doc.mjs +23 -0
  134. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  135. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  136. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  137. package/assets/docs/tree/docs.doc.mjs +21 -0
  138. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  139. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  140. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  141. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  142. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  143. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  144. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  145. package/assets/docs/tree/help.doc.mjs +16 -0
  146. package/assets/docs/tree/integrations.doc.mjs +25 -451
  147. package/assets/docs/tree/links.doc.mjs +98 -0
  148. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  149. package/assets/docs/tree/page-template.doc.mjs +71 -0
  150. package/assets/docs/tree/publishing.doc.mjs +111 -0
  151. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  152. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  153. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  154. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  155. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  156. package/assets/docs/tree/ship.doc.mjs +16 -0
  157. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  158. package/assets/docs/tree/single-component.doc.mjs +165 -0
  159. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  160. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  161. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  162. package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
  163. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  164. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  165. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  166. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  167. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  168. package/assets/docs/tree/templates.doc.mjs +34 -0
  169. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  170. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  171. package/assets/docs/tree/themes.doc.mjs +39 -0
  172. package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
  173. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  174. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  175. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  176. package/assets/docs/tree/versioning.doc.mjs +161 -0
  177. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  178. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  179. package/assets/docs/typography.doc.mjs +24 -4
  180. package/assets/docs/working-with-ai.doc.mjs +30 -22
  181. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  182. package/authoring/config/config.doc.mjs +9 -1
  183. package/authoring/config/parse.d.mts +2 -0
  184. package/authoring/config/parse.mjs +19 -0
  185. package/authoring/config/parse.test.mjs +8 -0
  186. package/authoring/config/type.ts +11 -0
  187. package/authoring/discover/discover.doc.d.mts +13 -0
  188. package/authoring/discover/discover.doc.mjs +138 -0
  189. package/authoring/discover/parse.d.mts +24 -0
  190. package/authoring/discover/parse.mjs +128 -0
  191. package/authoring/discover/parse.test.mjs +124 -0
  192. package/authoring/discover/type.ts +87 -0
  193. package/authoring/doctypes/_schema.d.mts +3 -2
  194. package/authoring/doctypes/_schema.mjs +6 -0
  195. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  196. package/authoring/doctypes/base/type.ts +4 -2
  197. package/authoring/doctypes/component/component.doc.mjs +6 -0
  198. package/authoring/doctypes/component/type.ts +8 -0
  199. package/authoring/doctypes/reference/reference.doc.mjs +7 -0
  200. package/authoring/doctypes/reference/type.ts +5 -0
  201. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  202. package/authoring/doctypes/template/template.doc.mjs +1 -1
  203. package/authoring/doctypes/template/type.ts +2 -2
  204. package/authoring/index.d.mts +1 -0
  205. package/authoring/index.d.ts +10 -0
  206. package/authoring/index.mjs +1 -0
  207. package/authoring/integration/integration.doc.mjs +12 -10
  208. package/clients/cli/commands/component/index.mjs +152 -55
  209. package/clients/cli/commands/component-batch.test.mjs +341 -0
  210. package/clients/cli/commands/component-ownership.test.mjs +89 -0
  211. package/clients/cli/commands/component.doc.mjs +27 -9
  212. package/clients/cli/commands/discover.doc.mjs +53 -9
  213. package/clients/cli/commands/discover.mjs +393 -118
  214. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  215. package/clients/cli/commands/docs.doc.mjs +1 -1
  216. package/clients/cli/commands/docs.mjs +60 -17
  217. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
  218. package/clients/cli/commands/doctor-integration.test.mjs +53 -0
  219. package/clients/cli/commands/doctor.doc.mjs +3 -1
  220. package/clients/cli/commands/doctor.mjs +49 -5
  221. package/clients/cli/commands/gap-report.doc.mjs +10 -9
  222. package/clients/cli/commands/init.doc.mjs +9 -6
  223. package/clients/cli/commands/integration-add.doc.mjs +9 -9
  224. package/clients/cli/commands/integration-authoring.test.mjs +61 -10
  225. package/clients/cli/commands/integration-pack.doc.mjs +5 -9
  226. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  227. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  228. package/clients/cli/commands/integration.doc.mjs +4 -4
  229. package/clients/cli/commands/integration.mjs +74 -43
  230. package/clients/cli/commands/manifest.doc.mjs +1 -1
  231. package/clients/cli/commands/search.doc.mjs +10 -3
  232. package/clients/cli/commands/search.mjs +21 -2
  233. package/clients/cli/commands/search.test.mjs +21 -4
  234. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  235. package/clients/cli/commands/template.doc.mjs +1 -1
  236. package/clients/cli/commands/text-json-parity.test.mjs +7 -1
  237. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  238. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
  239. package/clients/cli/commands/theme-palette.doc.mjs +1 -2
  240. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  241. package/clients/cli/commands/theme.doc.mjs +2 -1
  242. package/clients/cli/commands/upgrade.doc.mjs +62 -3
  243. package/clients/cli/index.mjs +28 -6
  244. package/clients/cli/lib/define-command.mjs +28 -4
  245. package/clients/cli/lib/define-command.test.mjs +54 -0
  246. package/clients/cli/lib/exit-codes.test.mjs +17 -1
  247. package/clients/cli/lib/json-shim.mjs +24 -14
  248. package/clients/cli/lib/manifest.mjs +18 -5
  249. package/clients/cli/lib/manifest.test.mjs +5 -2
  250. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  251. package/foundation/agent-docs/agent-docs.mjs +1 -1
  252. package/foundation/discovery/authoring-self-docs.mjs +1 -0
  253. package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
  254. package/foundation/discovery/cli-self-docs.mjs +16 -2
  255. package/foundation/discovery/cli-self-docs.test.mjs +20 -0
  256. package/foundation/discovery/docs-discovery.mjs +5 -1
  257. package/foundation/discovery/docs-discovery.test.mjs +21 -0
  258. package/foundation/discovery/docs-section-key.d.mts +1 -1
  259. package/foundation/discovery/docs-section-key.mjs +1 -1
  260. package/foundation/doc-compiler/doc-loads.test.mjs +3 -2
  261. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  262. package/foundation/doc-compiler/tree.d.mts +4 -0
  263. package/foundation/doc-compiler/tree.mjs +6 -1
  264. package/foundation/integrations/cli-requirement.d.mts +26 -6
  265. package/foundation/integrations/cli-requirement.mjs +46 -11
  266. package/foundation/integrations/cli-requirement.test.mjs +7 -2
  267. package/foundation/integrations/contribution-inventory.mjs +1 -1
  268. package/foundation/integrations/integrations.d.mts +14 -1
  269. package/foundation/integrations/integrations.mjs +41 -1
  270. package/foundation/integrations/integrations.test.mjs +31 -0
  271. package/foundation/response/batch.type.d.mts +33 -0
  272. package/foundation/response/batch.type.mjs +34 -0
  273. package/foundation/response/error-codes.doc.mjs +6 -8
  274. package/foundation/response/error-codes.test.mjs +30 -5
  275. package/foundation/response/response-types.doc.d.mts +4 -3
  276. package/foundation/response/response-types.doc.mjs +40 -10
  277. package/foundation/response/response-types.doc.test.mjs +23 -0
  278. package/foundation/response/response.doc.mjs +11 -10
  279. package/package.json +9 -9
  280. package/api/docs/docs.test.mjs +0 -243
  281. package/api/docs/integration-tree.test.mjs +0 -555
  282. package/api/docs/integrationDocs.test.mjs +0 -314
  283. package/api/search/search.test.mjs +0 -512
  284. package/assets/docs/tree/integrations.test.mjs +0 -62
  285. package/assets/docs/tree/writing-docs.doc.mjs +0 -286
  286. package/clients/cli/commands/docs.test.mjs +0 -294
  287. package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
  288. package/foundation/doc-compiler/tree.test.mjs +0 -598
@@ -26,7 +26,11 @@ import * as path from 'node:path';
26
26
  import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/node-version.mjs';
27
27
  import {CLI_ROOT, findCoreDir, findInstalledPackage} from '../../foundation/fs/paths.mjs';
28
28
  import {explainPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs';
29
- import {findConfigPath, Project} from '../../foundation/config/project.mjs';
29
+ import {
30
+ findConfigPath,
31
+ Project,
32
+ providerLedgerOf,
33
+ } from '../../foundation/config/project.mjs';
30
34
  import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
31
35
  import {buildDocsIndexData} from '../../foundation/discovery/docs-section-key.mjs';
32
36
  import {
@@ -76,6 +80,8 @@ import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env
76
80
  * @property {string|null} [docsCatalogError] - Why the project's docs catalog
77
81
  * could not be built, when it could not.
78
82
  * @property {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} [integrationIssues]
83
+ * @property {Array<{spec: string, error: string}>|null} [autolinkFailures]
84
+ * installed dependencies whose integration manifest could not be loaded
79
85
  * Combined project-level integration issues, including cross-package template replacement warnings.
80
86
  * @property {Error|null} [configError] - Error thrown while resolving the config
81
87
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -417,6 +423,9 @@ export function checkImplicitIntegrations(ctx) {
417
423
  const implicit = ctx.integrations.filter(
418
424
  integration => integration.__autolinked,
419
425
  );
426
+ // A dependency whose manifest cannot be loaded leaves no loaded record, so
427
+ // without this line doctor would say no dependency ships a manifest at all.
428
+ const unreadable = describeUnreadableManifests(ctx.autolinkFailures ?? []);
420
429
 
421
430
  if (implicit.length === 0) {
422
431
  return {
@@ -424,9 +433,12 @@ export function checkImplicitIntegrations(ctx) {
424
433
  label,
425
434
  status: 'info',
426
435
  message:
427
- ctx.integrations.length > 0
436
+ (ctx.integrations.length > 0
428
437
  ? 'None — every loaded integration is named in astryx.config.'
429
- : 'None — no installed dependency ships an astryx.integration.* manifest.',
438
+ : unreadable
439
+ ? 'None loaded.'
440
+ : 'None — no installed dependency ships an astryx.integration.* manifest.') +
441
+ unreadable,
430
442
  };
431
443
  }
432
444
 
@@ -439,11 +451,25 @@ export function checkImplicitIntegrations(ctx) {
439
451
  integration.__spec && integration.__spec !== integration.name
440
452
  ? ` (declared as "${integration.__spec}")`
441
453
  : '';
442
- const roots = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
454
+ // A declared root counts only when it exists: the manifest's keys are a
455
+ // claim, and `integration-issues` reports the ones that are not true.
456
+ const declared = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
443
457
  root => integration[/** @type {'components'} */ (root)],
444
458
  );
459
+ const missing = declared.filter(root => {
460
+ const dir = integration[/** @type {'components'} */ (root)];
461
+ if (typeof dir !== 'string') return false;
462
+ const base =
463
+ typeof integration.__packageDir === 'string'
464
+ ? integration.__packageDir
465
+ : (ctx.cwd ?? process.cwd());
466
+ return !fs.existsSync(path.isAbsolute(dir) ? dir : path.resolve(base, dir));
467
+ });
468
+ const roots = declared.filter(root => !missing.includes(root));
445
469
  const contributes = roots.length > 0 ? roots.join(', ') : 'nothing';
446
- return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}`;
470
+ const absent =
471
+ missing.length > 0 ? ` (declared ${missing.join(', ')} missing on disk)` : '';
472
+ return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}${absent}`;
447
473
  });
448
474
 
449
475
  const plural = implicit.length === 1 ? '' : 's';
@@ -453,7 +479,8 @@ export function checkImplicitIntegrations(ctx) {
453
479
  status: 'info',
454
480
  message:
455
481
  `${implicit.length} integration${plural} loaded from installed ` +
456
- `dependencies with no astryx.config entry: ${described.join('; ')}.`,
482
+ `dependencies with no astryx.config entry: ${described.join('; ')}.` +
483
+ unreadable,
457
484
  fix:
458
485
  'Nothing to fix. Keep these dependencies installed. The CLI links them ' +
459
486
  'from package.json, so an unused-dependency check that looks only for ' +
@@ -462,6 +489,30 @@ export function checkImplicitIntegrations(ctx) {
462
489
  };
463
490
  }
464
491
 
492
+ /**
493
+ * The sentence `implicit-integrations` adds for dependencies whose manifest
494
+ * could not be loaded, or '' when there are none. Still informational: the
495
+ * package is a dependency's own bug, which `doctor integration validate`
496
+ * diagnoses, but doctor must not report it as absent.
497
+ * @param {Array<{spec: string, error: string}>} failures
498
+ * @returns {string}
499
+ */
500
+ function describeUnreadableManifests(failures) {
501
+ if (failures.length === 0) return '';
502
+ const one = failures.length === 1;
503
+ const listed = failures
504
+ .map(({spec, error}) => {
505
+ const reason = String(error).split('\n')[0].slice(0, 160);
506
+ return `${spec} (${reason})`;
507
+ })
508
+ .join('; ');
509
+ return (
510
+ ` ${failures.length} installed ${one ? 'dependency ships' : 'dependencies ship'} ` +
511
+ `an astryx.integration.* manifest that could not be loaded, so ${one ? 'it contributes' : 'they contribute'} ` +
512
+ `nothing: ${listed}. Run \`astryx doctor integration validate <package>\` for details.`
513
+ );
514
+ }
515
+
465
516
  /**
466
517
  * Check 7 — agent docs exist and contain the Astryx section markers.
467
518
  * @param {DoctorContext} ctx
@@ -738,12 +789,26 @@ export function checkProviderIdentity(ctx) {
738
789
  integration =>
739
790
  integration.providerId != null && integration.__loadError == null,
740
791
  ).length;
792
+ // An integration that could not be read has no provider ID to check, so the
793
+ // count above is not a complete survey. Say so instead of counting silently.
794
+ const unread = ctx.integrations.filter(
795
+ integration => integration.__loadError != null,
796
+ ).length;
797
+ const unreadNote =
798
+ unread === 0
799
+ ? ''
800
+ : unread === 1
801
+ ? ' 1 loaded integration could not be read, so its provider ID is unknown.'
802
+ : ` ${unread} loaded integrations could not be read, so their provider IDs are unknown.`;
741
803
  if (count === 0) {
742
804
  return {
743
805
  id,
744
806
  label,
745
807
  status: 'info',
746
- message: 'None — no loaded integration has a provider identity.',
808
+ message:
809
+ (unread === 0
810
+ ? 'None — no loaded integration has a provider identity.'
811
+ : 'No readable integration has a provider identity.') + unreadNote,
747
812
  };
748
813
  }
749
814
  return {
@@ -751,9 +816,10 @@ export function checkProviderIdentity(ctx) {
751
816
  label,
752
817
  status: 'pass',
753
818
  message:
754
- count === 1
819
+ (count === 1
755
820
  ? '1 loaded integration has its own provider ID.'
756
- : `${count} loaded integrations each have their own provider ID.`,
821
+ : `${count} loaded integrations each have their own provider ID.`) +
822
+ unreadNote,
757
823
  };
758
824
  }
759
825
 
@@ -1198,11 +1264,25 @@ export async function runChecks(options = {}) {
1198
1264
  let docsCatalogError = null;
1199
1265
  /** @type {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} */
1200
1266
  let integrationIssues = null;
1267
+ /** @type {Array<{spec: string, error: string}>|null} */
1268
+ let autolinkFailures = null;
1201
1269
  try {
1202
1270
  const project = await Project.load(cwd);
1203
1271
  configTheme =
1204
1272
  /** @type {{theme?: string}} */ (project.config ?? {}).theme ?? null;
1205
1273
  integrations = project.loadedIntegrations;
1274
+ // An installed dependency whose manifest cannot be loaded is kept out of
1275
+ // loadedIntegrations on purpose. The provider ledger still records it.
1276
+ autolinkFailures = [...providerLedgerOf(project).values()]
1277
+ .filter(
1278
+ entry =>
1279
+ entry.outcome === 'load-failed' &&
1280
+ entry.candidate.source === 'autolinked',
1281
+ )
1282
+ .map(entry => ({
1283
+ spec: entry.candidate.spec ?? entry.label,
1284
+ error: entry.error ?? 'its manifest could not be loaded',
1285
+ }));
1206
1286
  try {
1207
1287
  docsCatalog = await project.docs();
1208
1288
  docsCatalogIssues = (await project.issues()).filter(
@@ -1229,6 +1309,7 @@ export async function runChecks(options = {}) {
1229
1309
  docsCatalogIssues,
1230
1310
  docsCatalogError,
1231
1311
  integrationIssues,
1312
+ autolinkFailures,
1232
1313
  configError,
1233
1314
  };
1234
1315
 
@@ -354,16 +354,31 @@ describe('checkImplicitIntegrations', () => {
354
354
  });
355
355
 
356
356
  it('names the package, the field, and what it contributes', () => {
357
- const c = checkImplicitIntegrations({
358
- integrations: [
359
- autolinked({templates: '/abs/templates', themes: '/abs/themes'}),
360
- ],
361
- });
362
- expect(c.message).toContain('@acme/widgets@1.0.0');
363
- expect(c.message).toContain('from dependencies');
364
- expect(c.message).toContain(
365
- 'contributing components, templates, themes',
366
- );
357
+ // Roots count only when they exist, so this one gives them real folders.
358
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-implicit-'));
359
+ try {
360
+ const dir = name => {
361
+ fs.mkdirSync(path.join(root, name));
362
+ return path.join(root, name);
363
+ };
364
+ const c = checkImplicitIntegrations({
365
+ integrations: [
366
+ autolinked({
367
+ components: dir('components'),
368
+ templates: dir('templates'),
369
+ themes: dir('themes'),
370
+ }),
371
+ ],
372
+ });
373
+ expect(c.message).toContain('@acme/widgets@1.0.0');
374
+ expect(c.message).toContain('from dependencies');
375
+ expect(c.message).toContain(
376
+ 'contributing components, templates, themes',
377
+ );
378
+ expect(c.message).not.toContain('missing on disk');
379
+ } finally {
380
+ fs.rmSync(root, {recursive: true, force: true});
381
+ }
367
382
  });
368
383
 
369
384
  it('names the declared key too when an npm alias makes them differ', () => {
@@ -906,3 +921,100 @@ describe('checkDocsProgressiveDisclosure languages', () => {
906
921
  expect(c.message).not.toMatch(/deploying overview:/);
907
922
  });
908
923
  });
924
+
925
+ describe('doctor says what it could not check', () => {
926
+ const CORE = {
927
+ 'node_modules/@astryxdesign/core/package.json': JSON.stringify({
928
+ name: '@astryxdesign/core',
929
+ version: '0.6.3',
930
+ }),
931
+ };
932
+
933
+ /** Installed dependency whose manifest declares roots that do not exist. */
934
+ const dangling = {
935
+ 'node_modules/@acme/dangling/package.json': JSON.stringify({
936
+ name: '@acme/dangling',
937
+ version: '2.0.0',
938
+ }),
939
+ 'node_modules/@acme/dangling/astryx.integration.mjs':
940
+ "export default {providerId: 'acme-dangling', components: './components', templates: './templates', docs: './docs'};\n",
941
+ };
942
+
943
+ /** Installed dependency whose manifest cannot be parsed at all. */
944
+ const broken = {
945
+ 'node_modules/@acme/broken/package.json': JSON.stringify({
946
+ name: '@acme/broken',
947
+ version: '1.0.0',
948
+ }),
949
+ 'node_modules/@acme/broken/astryx.integration.mjs':
950
+ 'export default { this is not valid javascript ((\n',
951
+ };
952
+
953
+ /** @param {Record<string, string>} extra @param {string[]} deps */
954
+ const project = (extra, deps) =>
955
+ mkProject({
956
+ 'package.json': JSON.stringify({
957
+ name: 'consumer',
958
+ version: '1.0.0',
959
+ dependencies: Object.fromEntries(
960
+ ['@astryxdesign/core', ...deps].map(d => [d, '1.0.0']),
961
+ ),
962
+ }),
963
+ ...CORE,
964
+ ...extra,
965
+ });
966
+
967
+ // An unparseable manifest is kept out of the loaded set on purpose, and
968
+ // doctor used to report that no installed dependency ships a manifest.
969
+ it('names an installed dependency whose manifest cannot be loaded', async () => {
970
+ const report = (await doctor({cwd: project(broken, ['@acme/broken'])})).data;
971
+ const check = report.checks.find(c => c.id === 'implicit-integrations');
972
+
973
+ expect(check.status).toBe('info');
974
+ expect(check.message).toContain('@acme/broken');
975
+ expect(check.message).toContain('could not be loaded');
976
+ expect(check.message).not.toContain('no installed dependency ships');
977
+ expect(report.summary.fail).toBe(0);
978
+ }, SLOW);
979
+
980
+ it('does not claim contributions from roots that are missing on disk', async () => {
981
+ const report = (await doctor({cwd: project(dangling, ['@acme/dangling'])}))
982
+ .data;
983
+ const implicit = report.checks.find(c => c.id === 'implicit-integrations');
984
+ const issues = report.checks.find(c => c.id === 'integration-issues');
985
+
986
+ expect(implicit.message).toContain('contributing nothing');
987
+ expect(implicit.message).toContain('missing on disk');
988
+ expect(implicit.message).not.toContain('contributing components');
989
+ expect(issues.status).toBe('warn');
990
+ expect(issues.message).toContain('@acme/dangling');
991
+ }, SLOW);
992
+
993
+ it('still says no dependency ships a manifest when none does', async () => {
994
+ const report = (await doctor({cwd: project({}, [])})).data;
995
+ const check = report.checks.find(c => c.id === 'implicit-integrations');
996
+
997
+ expect(check.message).toBe(
998
+ 'None — no installed dependency ships an astryx.integration.* manifest.',
999
+ );
1000
+ }, SLOW);
1001
+
1002
+ it('says how many integrations could not be read, rather than counting silently', () => {
1003
+ /** @type {any} */
1004
+ const ctx = {
1005
+ cwd: '/x',
1006
+ nodeVersion: process.versions.node,
1007
+ coreDir: null,
1008
+ configPath: null,
1009
+ configTheme: null,
1010
+ integrations: [
1011
+ {name: '@acme/ok', __spec: '@acme/ok', providerId: 'ok'},
1012
+ {name: '@acme/bad', __spec: '@acme/bad', __loadError: 'boom'},
1013
+ ],
1014
+ };
1015
+ const check = checkProviderIdentity(ctx);
1016
+
1017
+ expect(check.message).toContain('1 loaded integration has its own provider ID.');
1018
+ expect(check.message).toContain('could not be read');
1019
+ });
1020
+ });
@@ -23,7 +23,7 @@ export type DoctorCheck = {
23
23
  */
24
24
  message: string;
25
25
  /**
26
- * - Actionable remediation, present when status is not 'pass'.
26
+ * - Actionable remediation: always present on 'warn' and 'fail'; some 'info' checks carry one too.
27
27
  */
28
28
  fix?: string | undefined;
29
29
  };
@@ -19,7 +19,7 @@
19
19
  * @property {string} label - Human-readable check name.
20
20
  * @property {DoctorStatus} status
21
21
  * @property {string} message - One-line result summary.
22
- * @property {string} [fix] - Actionable remediation, present when status is not 'pass'.
22
+ * @property {string} [fix] - Actionable remediation: always present on 'warn' and 'fail'; some 'info' checks carry one too.
23
23
  */
24
24
 
25
25
  /**
@@ -12,12 +12,16 @@ export const doc = {
12
12
  name: 'gapReport',
13
13
  namespace: 'cli/api',
14
14
  displayName: 'gapReport()',
15
- summary: 'Route a design-system gap through the fan-out handler composition.',
15
+ summary:
16
+ 'Report a missing or hard-to-use design-system capability to the package that owns it.',
16
17
  description:
17
- 'Creates a normalized gap report and fans it out to every effective handler: the project config handler first, then each loaded integration handler in config order, deduplicated by handle function identity. Each handler receives a structuredClone of the report and an AbortSignal, then runs in its own worker with a 30 s timeout and stdout redirected to stderr. A timed-out worker is terminated before the next handler starts, so process.exit, process.exitCode, and late continuations cannot affect the CLI process. A public handler requires confirmPublic per handler; internal handlers always run. When no handlers exist, a built-in GitHub/routed-only fallback runs. The aggregate response carries ordered deliveries with per-handler outcomes.',
18
+ 'Sends a gap report to every configured handler: the project config handler first, then each integration handler in config order. ' +
19
+ 'Each handler has 30 s to finish, and its output goes to stderr. Public handlers run only with confirmPublic; internal handlers always run. ' +
20
+ "With no handler, it files a GitHub issue for the owning package only with confirmPublic (without it nothing is sent), or returns the package's issues URL when that is not on GitHub. " +
21
+ 'The report records whether an agent or a person ran it, and the response lists each handler outcome in order.',
18
22
  importPath: '@astryxdesign/cli/api',
19
23
  signature:
20
- 'gapReport(component?: string, options?: GapReportOptions): Promise<GapReportCategoriesResponse | GapReportReceiptResponse>',
24
+ 'gapReport(component: string | undefined, options?: GapReportOptions): Promise<GapReportCategoriesResponse | GapReportReceiptResponse>',
21
25
  keywords: [
22
26
  'gap',
23
27
  'report',
@@ -56,13 +60,13 @@ export const doc = {
56
60
  name: 'options.package',
57
61
  type: 'string',
58
62
  description:
59
- 'Explicit owning package when automatic routing is ambiguous.',
63
+ 'Package that owns the gap: @astryxdesign/core, or a loaded integration by package name or config entry. Overrides automatic owner routing; required when more than one package provides the component.',
60
64
  },
61
65
  {
62
66
  name: 'options.confirmPublic',
63
67
  type: 'boolean',
64
68
  description:
65
- 'Explicitly consent to invoking public handlers or creating a GitHub issue.',
69
+ 'Allow public delivery: public handlers run, and with no handler a GitHub issue is filed through the gh CLI.',
66
70
  default: 'false',
67
71
  },
68
72
  {
@@ -77,6 +81,7 @@ export const doc = {
77
81
  type: 'string',
78
82
  description:
79
83
  'Directory used to load project config and component ownership.',
84
+ default: 'process.cwd()',
80
85
  },
81
86
  ],
82
87
  returns: [
@@ -87,17 +92,21 @@ export const doc = {
87
92
  {
88
93
  type: 'gap-report.file',
89
94
  description:
90
- 'An aggregate receipt with per-handler deliveries, filedCount/routedOnlyCount totals, and overall status.',
95
+ 'Receipt: status (filed, partial, failed, routed_only, consent_required, skipped), package, issuesUrl, ordered deliveries (handlerType, handler, audience, status, url, message), filedCount, and routedOnlyCount.',
91
96
  },
92
97
  ],
93
98
  throws: [
99
+ {
100
+ code: 'ERR_MISSING_ARGUMENT',
101
+ when: 'component, category, or reason is missing, blank, or not a string (unless listCategories is true)',
102
+ },
94
103
  {
95
104
  code: 'ERR_UNKNOWN_CATEGORY',
96
105
  when: 'category is not one of the fixed gap-report values',
97
106
  },
98
107
  {
99
108
  code: 'ERR_INVALID_ARGUMENT',
100
- when: 'a field value is invalid',
109
+ when: 'component is over 120 characters, category is over 80, reason is over 2000, or detail is not a string or is over 8000 characters',
101
110
  },
102
111
  {
103
112
  code: 'ERR_AMBIGUOUS_COMPONENT',
@@ -109,7 +118,7 @@ export const doc = {
109
118
  },
110
119
  {
111
120
  code: 'ERR_NOT_FOUND',
112
- when: 'no handler and no issues URL available',
121
+ when: 'no report handler is configured and the owning package has no issues URL',
113
122
  },
114
123
  ],
115
124
  examples: [
@@ -122,8 +131,8 @@ export const doc = {
122
131
  code: "const receipt = await gapReport('Button', {category: 'missing_variant', reason: 'Need a compact size'});",
123
132
  },
124
133
  {
125
- label: 'Confirm public filing',
126
- code: "await gapReport('Button', {category: 'docs_gap', reason: 'Missing keyboard example', confirmPublic: true});",
134
+ label: 'Name the owning package',
135
+ code: "const receipt = await gapReport('Button', {category: 'docs_gap', reason: 'Missing keyboard example', package: '@astryxdesign/core'});",
127
136
  },
128
137
  ],
129
138
  command: 'gap-report',
@@ -34,6 +34,7 @@ export const doc = {
34
34
  name: 'options.cwd',
35
35
  type: 'string',
36
36
  description: 'Directory to resolve @astryxdesign/core from.',
37
+ default: 'process.cwd()',
37
38
  },
38
39
  {
39
40
  name: 'options.list',
@@ -43,7 +44,8 @@ export const doc = {
43
44
  {
44
45
  name: 'options.category',
45
46
  type: 'string',
46
- description: 'List only hooks in this category.',
47
+ description:
48
+ "List only the hooks in this category, a key of the unfiltered list such as 'Media' or 'Focus'.",
47
49
  },
48
50
  {
49
51
  name: 'options.params',
@@ -54,7 +56,8 @@ export const doc = {
54
56
  name: 'options.detail',
55
57
  type: "'full' | 'compact' | 'brief'",
56
58
  description: 'Detail level for list views.',
57
- default: "'full' for a named hook, 'brief' for list views",
59
+ default:
60
+ "'full' for a named hook; 'brief' for lists (returned as data.detail: 'names')",
58
61
  },
59
62
  {
60
63
  name: 'options.lang',
@@ -104,7 +107,7 @@ export const doc = {
104
107
  },
105
108
  {
106
109
  label: 'Browse a category',
107
- code: "await hook(undefined, {category: 'State', detail: 'compact'});",
110
+ code: "await hook(undefined, {category: 'Media', detail: 'compact'});",
108
111
  },
109
112
  ],
110
113
  command: 'hook',
package/api/index.d.mts CHANGED
@@ -18,6 +18,7 @@ export { integrationAddTheme } from "./integration/add-theme.mjs";
18
18
  export { integrationPackCheck } from "./integration/pack-check.mjs";
19
19
  export { AstryxError } from "./error.mjs";
20
20
  export { logger } from "./logger.mjs";
21
+ export * from "../foundation/response/batch.type.mjs";
21
22
  export * from "./component/component.type.mjs";
22
23
  export * from "./docs/docs.type.mjs";
23
24
  export * from "./blog/blog.type.mjs";
package/api/index.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * @file Programmatic API for the Astryx CLI.
5
5
  *
6
- * Every function returns the same { type, data } envelope that `xds --json` outputs.
6
+ * Every function returns the same { type, data } envelope that `astryx --json` outputs.
7
7
  * Errors throw AstryxError (with optional .suggestions).
8
8
  *
9
9
  * @example
@@ -73,10 +73,12 @@ export {logger} from './logger.mjs';
73
73
  * @typedef {import('./logger.mjs').Logger} Logger
74
74
  */
75
75
 
76
- // ── Types (re-exported from each command's colocated `.type.mjs`) ─────
76
+ // ── Types (re-exported from the shared response foundation and each command's
77
+ // colocated `.type.mjs`) ──────────────────────────────────────────────────
77
78
  // Runtime no-ops (the .type.mjs files are `export {}`); tsc carries these
78
79
  // through to the generated api/index.d.mts so the public type surface exposes
79
- // every command's Options + response types by name.
80
+ // the shared receipt vocabulary and every command's Options + response types.
81
+ export * from '../foundation/response/batch.type.mjs';
80
82
  export * from './component/component.type.mjs';
81
83
  export * from './docs/docs.type.mjs';
82
84
  export * from './blog/blog.type.mjs';
@@ -19,10 +19,9 @@ export const doc = {
19
19
  'Sets a project up with NO prompts, so it behaves identically for humans, ' +
20
20
  'agents, CI, and piped I/O. By default it installs the AGENTS.md/CLAUDE.md ' +
21
21
  'agent-docs cheat sheet, including guidance from configured integrations, and ' +
22
- 'prints getting-started guidance; `features` / ' +
23
- '`all` add theme and page-building guidance and can scaffold a starter ' +
24
- 'template. With `removeAgents` it removes the managed agent-docs block ' +
25
- 'instead of installing.',
22
+ "prints getting-started guidance; `features` / `all` print only the chosen features' " +
23
+ 'guidance instead (the theme feature also writes the annotated theme template). ' +
24
+ 'With `removeAgents` it removes the managed agent-docs block instead of installing.',
26
25
  importPath: '@astryxdesign/cli/api',
27
26
  signature:
28
27
  'init(options?: InitOptions, ctx?: {cwd?: string}): Promise<InitRunResponse | InitRemoveResponse>',
@@ -32,7 +31,7 @@ export const doc = {
32
31
  name: 'options.features',
33
32
  type: 'string',
34
33
  description:
35
- 'Comma-separated features to install: agents (agent docs), theme (writes the annotated theme.template.ts), template (page-template guidance).',
34
+ 'Comma-separated features to install: agents (agent docs), theme (writes the annotated theme.template.ts), template (the page-building commands; writes a page only with templateName).',
36
35
  },
37
36
  {
38
37
  name: 'options.all',
@@ -47,7 +46,8 @@ export const doc = {
47
46
  {
48
47
  name: 'options.agent',
49
48
  type: 'string',
50
- description: 'Agent preset: claude, cursor, codex, hermes, muse, all.',
49
+ description:
50
+ 'Agent preset: claude, cursor, codex, hermes, muse, all. Used only when the agents feature runs.',
51
51
  },
52
52
  {
53
53
  name: 'options.agentDocsPath',
@@ -58,9 +58,14 @@ export const doc = {
58
58
  name: 'options.templateName',
59
59
  type: 'string',
60
60
  description:
61
- 'Scaffold a named page template (programmatic only; the CLI never sets it).',
61
+ 'Scaffold this page template to src/pages/<name>/page.tsx. Used only when the template feature runs (features includes template, or all); otherwise ignored. Programmatic only; the CLI never sets it.',
62
+ },
63
+ {
64
+ name: 'ctx.cwd',
65
+ type: 'string',
66
+ description: 'Directory to set up.',
67
+ default: 'process.cwd()',
62
68
  },
63
- {name: 'ctx.cwd', type: 'string', description: 'Directory to set up.'},
64
69
  ],
65
70
  returns: [
66
71
  {
@@ -71,13 +76,13 @@ export const doc = {
71
76
  {
72
77
  type: 'init.remove',
73
78
  description:
74
- 'Confirmation that the managed agent-docs block was removed (`data.removed: true`), returned when `removeAgents` is set.',
79
+ 'Returned when `removeAgents` is set. `data.removed` is always true, even when no managed block was found.',
75
80
  },
76
81
  ],
77
82
  throws: [
78
83
  {
79
84
  code: 'ERR_UNKNOWN_AGENT',
80
- when: '`agent` is not one of claude, cursor, codex, hermes, muse, all',
85
+ when: 'the agents feature runs (default mode, features includes agents, or all) and `agent` is not one of claude, cursor, codex, hermes, muse, all',
81
86
  },
82
87
  {
83
88
  code: 'ERR_UNKNOWN_FEATURE',
@@ -85,7 +90,7 @@ export const doc = {
85
90
  },
86
91
  {
87
92
  code: 'ERR_UNKNOWN_TEMPLATE',
88
- when: '`templateName` matches no bundled template',
93
+ when: 'the template feature runs and `templateName` matches no bundled template',
89
94
  },
90
95
  {
91
96
  code: 'ERR_FILE_EXISTS',
@@ -93,7 +98,7 @@ export const doc = {
93
98
  },
94
99
  {
95
100
  code: 'ERR_PATH_TRAVERSAL',
96
- when: 'the template output path resolves outside cwd, for example through a symlinked src directory',
101
+ when: 'the template output path resolves outside cwd (for example through a symlinked src directory), or, with removeAgents, a managed agent-docs file resolves outside cwd through a symlink',
97
102
  },
98
103
  ],
99
104
  examples: [
@@ -8,7 +8,7 @@ export function findPackageDir(startDir: string): string;
8
8
  /**
9
9
  * @typedef {object} WritePlan
10
10
  * @property {string} path
11
- * @property {string} contents
11
+ * @property {string | Buffer} contents a Buffer is written byte for byte
12
12
  * @property {boolean} createOnly
13
13
  * @property {Buffer} [expectedOriginal] bytes captured before validation;
14
14
  * a different current file is a concurrent edit and must never be overwritten
@@ -56,7 +56,10 @@ export function packageJsonUpdate(packageFile: string, rootPath: string, manifes
56
56
  export function applyWrites(plans: WritePlan[]): () => void;
57
57
  export type WritePlan = {
58
58
  path: string;
59
- contents: string;
59
+ /**
60
+ * a Buffer is written byte for byte
61
+ */
62
+ contents: string | Buffer;
60
63
  createOnly: boolean;
61
64
  /**
62
65
  * bytes captured before validation;