@astryxdesign/cli 0.7.12-canary.f36bd72 → 0.7.12
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.
- package/CHANGELOG.md +107 -0
- package/api/build/blocks/blocks.d.mts +41 -0
- package/api/build/blocks/blocks.mjs +118 -68
- package/api/doctor/doctor.d.mts +4 -4
- package/api/doctor/doctor.mjs +248 -78
- package/api/icon/_authoring.mjs +8 -1
- package/api/icon/add/add.mjs +45 -5
- package/api/icon/icon.type.d.mts +24 -0
- package/api/icon/icon.type.mjs +6 -0
- package/api/icon/iconAdd.doc.mjs +1 -1
- package/api/icon/map/map.mjs +11 -4
- package/api/integration/add-helpers.d.mts +5 -1
- package/api/integration/add-helpers.mjs +1 -1
- package/api/render/render.doc.mjs +1 -1
- package/api/render/render.type.d.mts +1 -1
- package/api/render/render.type.mjs +1 -1
- package/api/search/ORDERING.md +3 -0
- package/api/search/_adapter.d.mts +10 -2
- package/api/search/_adapter.mjs +13 -2
- package/api/search/_fit-order.d.mts +47 -0
- package/api/search/_fit-order.mjs +257 -0
- package/api/search/_main-searcher.mjs +15 -5
- package/api/template/skeleton/skeleton.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
- package/assets/docs/tree/icon-sets.doc.mjs +4 -4
- package/assets/docs/tree/render-pages.doc.mjs +1 -1
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +13 -7
- package/assets/templates/pages/table-grouped/page.tsx +30 -30
- package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
- package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
- package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/experimental/icon.mjs +25 -4
- package/clients/cli/commands/experimental-render.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +204 -181
- package/foundation/discovery/component-discovery.mjs +1 -1
- package/foundation/discovery/icon-set-writer.d.mts +21 -5
- package/foundation/discovery/icon-set-writer.mjs +114 -13
- package/foundation/discovery/template-adapter.mjs +2 -2
- package/foundation/render/page-host.d.mts +4 -0
- package/foundation/render/page-host.mjs +45 -0
- package/foundation/render/page-worker.mjs +11 -5
- package/foundation/render/render-page.d.mts +11 -6
- package/foundation/render/render-page.mjs +138 -44
- package/foundation/response/error-codes.doc.mjs +1 -1
- package/foundation/xle/registry-core.mjs +1 -1
- package/package.json +6 -6
package/api/doctor/doctor.mjs
CHANGED
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
import * as fs from 'node:fs';
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
MIN_NODE_VERSION,
|
|
32
|
+
isNodeVersionSupported,
|
|
33
|
+
} from '../../foundation/env/node-version.mjs';
|
|
31
34
|
import {
|
|
32
35
|
AGENT_DOC_PATHS,
|
|
33
36
|
holdsAstryxBlock,
|
|
@@ -37,9 +40,21 @@ import {
|
|
|
37
40
|
assertAgentDocsTarget,
|
|
38
41
|
resolveAgentDocsRoot,
|
|
39
42
|
} from '../../foundation/agent-docs/agent-docs.mjs';
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
import {
|
|
44
|
+
CLI_ROOT,
|
|
45
|
+
findProjectCoreDir,
|
|
46
|
+
findInstalledPackage,
|
|
47
|
+
} from '../../foundation/fs/paths.mjs';
|
|
48
|
+
import {
|
|
49
|
+
explainPackageManager,
|
|
50
|
+
formatCliCommand,
|
|
51
|
+
getCliInvocation,
|
|
52
|
+
guessCliInvocation,
|
|
53
|
+
} from '../../foundation/env/package-manager.mjs';
|
|
54
|
+
import {
|
|
55
|
+
chooseDeclaredCliCommand,
|
|
56
|
+
declaredCliCommandsOf,
|
|
57
|
+
} from '../../foundation/env/cli-command.mjs';
|
|
43
58
|
import {
|
|
44
59
|
findConfigPath,
|
|
45
60
|
Project,
|
|
@@ -64,15 +79,40 @@ import {
|
|
|
64
79
|
import {typedEdges} from '../docs/node/node.mjs';
|
|
65
80
|
import {detailView, indexView} from '../../foundation/doc-compiler/lenses.mjs';
|
|
66
81
|
import {isExperimentalTreeNode} from '../../foundation/doc-compiler/tree.mjs';
|
|
67
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
semverCompare,
|
|
84
|
+
isValidSemver,
|
|
85
|
+
satisfiesRange,
|
|
86
|
+
} from '../../foundation/env/semver.mjs';
|
|
68
87
|
import {
|
|
69
88
|
checkAppThemes,
|
|
70
89
|
checkThemes,
|
|
71
90
|
themeImportWiring,
|
|
72
91
|
} from './theme-checks.mjs';
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
import {
|
|
93
|
+
availableLine,
|
|
94
|
+
RUN_FROM_PROJECT,
|
|
95
|
+
upgradeLine,
|
|
96
|
+
} from '../../foundation/release/notices.mjs';
|
|
97
|
+
import {
|
|
98
|
+
COMPUTER_COMMAND,
|
|
99
|
+
COMPUTER_INSTALL_STEP,
|
|
100
|
+
} from '../../foundation/agent-docs/computer-agent-text.mjs';
|
|
101
|
+
import {
|
|
102
|
+
alignCommand,
|
|
103
|
+
addCliCommandFor,
|
|
104
|
+
computerAgentTextState,
|
|
105
|
+
installCommandFor,
|
|
106
|
+
insideProjectElsewhere,
|
|
107
|
+
projectCoreVersion,
|
|
108
|
+
projectHasOwnCli,
|
|
109
|
+
computerIntegrationsOutside,
|
|
110
|
+
computerIntegrationsToAdd,
|
|
111
|
+
displayHomePath,
|
|
112
|
+
outsideProject,
|
|
113
|
+
releaseFacts,
|
|
114
|
+
builtIntegrationPackages,
|
|
115
|
+
} from './_adapter.mjs';
|
|
76
116
|
import {checkBuilt} from './built-checks.mjs';
|
|
77
117
|
|
|
78
118
|
/**
|
|
@@ -248,7 +288,9 @@ export function checkCoreInstalled(ctx) {
|
|
|
248
288
|
: '@astryxdesign/core could not be resolved from this project.',
|
|
249
289
|
...(found
|
|
250
290
|
? {}
|
|
251
|
-
: {
|
|
291
|
+
: {
|
|
292
|
+
fix: 'Install the design system: `npm install @astryxdesign/core` (or yarn/pnpm/bun).',
|
|
293
|
+
}),
|
|
252
294
|
};
|
|
253
295
|
}
|
|
254
296
|
|
|
@@ -272,7 +314,8 @@ export function checkVersionAlignment(ctx) {
|
|
|
272
314
|
id: 'version-alignment',
|
|
273
315
|
label: '@astryxdesign/core <-> @astryxdesign/cli alignment',
|
|
274
316
|
status: 'info',
|
|
275
|
-
message:
|
|
317
|
+
message:
|
|
318
|
+
'Skipped — could not read both @astryxdesign/core and @astryxdesign/cli versions.',
|
|
276
319
|
};
|
|
277
320
|
}
|
|
278
321
|
|
|
@@ -386,7 +429,10 @@ export async function checkConfig(ctx) {
|
|
|
386
429
|
const {pathToFileURL} = await import('node:url');
|
|
387
430
|
const mod = await import(pathToFileURL(ctx.configPath).href);
|
|
388
431
|
const config = mod.default;
|
|
389
|
-
if (
|
|
432
|
+
if (
|
|
433
|
+
config !== undefined &&
|
|
434
|
+
(typeof config !== 'object' || config === null)
|
|
435
|
+
) {
|
|
390
436
|
return {
|
|
391
437
|
id: 'config',
|
|
392
438
|
label: 'astryx.config.mjs',
|
|
@@ -438,9 +484,13 @@ export async function checkConfig(ctx) {
|
|
|
438
484
|
function describeNoImplicit(integrations) {
|
|
439
485
|
const own = integrations.filter(integration => integration.__local);
|
|
440
486
|
const configured = integrations.length - own.length;
|
|
441
|
-
const ownNames = own
|
|
442
|
-
|
|
443
|
-
|
|
487
|
+
const ownNames = own
|
|
488
|
+
.map(integration => integration.name ?? 'this package')
|
|
489
|
+
.join(', ');
|
|
490
|
+
if (own.length === 0)
|
|
491
|
+
return 'None — every loaded integration is named in astryx.config.';
|
|
492
|
+
if (configured === 0)
|
|
493
|
+
return `None — the only loaded integration is this package's own manifest (${ownNames}).`;
|
|
444
494
|
return `None — every loaded integration is named in astryx.config or is this package's own manifest (${ownNames}).`;
|
|
445
495
|
}
|
|
446
496
|
|
|
@@ -476,7 +526,10 @@ export function checkImplicitIntegrations(ctx) {
|
|
|
476
526
|
id,
|
|
477
527
|
label,
|
|
478
528
|
status: 'info',
|
|
479
|
-
message: skippedBecause(
|
|
529
|
+
message: skippedBecause(
|
|
530
|
+
ctx,
|
|
531
|
+
'the project configuration could not be read',
|
|
532
|
+
),
|
|
480
533
|
};
|
|
481
534
|
}
|
|
482
535
|
|
|
@@ -491,7 +544,9 @@ export function checkImplicitIntegrations(ctx) {
|
|
|
491
544
|
// A record that carries `__loadError` (the package's own manifest, or a
|
|
492
545
|
// configured one, that failed to load) loaded nothing, so it is not named
|
|
493
546
|
// as loaded beside the line reporting that it could not be loaded.
|
|
494
|
-
const loaded = ctx.integrations.filter(
|
|
547
|
+
const loaded = ctx.integrations.filter(
|
|
548
|
+
integration => !integration.__loadError,
|
|
549
|
+
);
|
|
495
550
|
return {
|
|
496
551
|
id,
|
|
497
552
|
label,
|
|
@@ -533,12 +588,16 @@ export function checkImplicitIntegrations(ctx) {
|
|
|
533
588
|
typeof integration.__packageDir === 'string'
|
|
534
589
|
? integration.__packageDir
|
|
535
590
|
: (ctx.cwd ?? process.cwd());
|
|
536
|
-
return !fs.existsSync(
|
|
591
|
+
return !fs.existsSync(
|
|
592
|
+
path.isAbsolute(dir) ? dir : path.resolve(base, dir),
|
|
593
|
+
);
|
|
537
594
|
});
|
|
538
595
|
const roots = declared.filter(root => !missing.includes(root));
|
|
539
596
|
const contributes = roots.length > 0 ? roots.join(', ') : 'nothing';
|
|
540
597
|
const absent =
|
|
541
|
-
missing.length > 0
|
|
598
|
+
missing.length > 0
|
|
599
|
+
? ` (declared ${missing.join(', ')} missing on disk)`
|
|
600
|
+
: '';
|
|
542
601
|
return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}${absent}`;
|
|
543
602
|
});
|
|
544
603
|
|
|
@@ -610,7 +669,10 @@ export function checkConfiguredIntegrations(ctx) {
|
|
|
610
669
|
id,
|
|
611
670
|
label,
|
|
612
671
|
status: 'info',
|
|
613
|
-
message: skippedBecause(
|
|
672
|
+
message: skippedBecause(
|
|
673
|
+
ctx,
|
|
674
|
+
'the project configuration could not be read',
|
|
675
|
+
),
|
|
614
676
|
};
|
|
615
677
|
}
|
|
616
678
|
// What counts as configured is what astryx.config names, read from the
|
|
@@ -676,7 +738,9 @@ function agentDocsRefusal(cwd) {
|
|
|
676
738
|
assertAgentDocsTarget(agentDocsTargetDir(cwd));
|
|
677
739
|
return null;
|
|
678
740
|
} catch (err) {
|
|
679
|
-
return err instanceof Error && err.name === 'AgentDocsHomeError'
|
|
741
|
+
return err instanceof Error && err.name === 'AgentDocsHomeError'
|
|
742
|
+
? err.message
|
|
743
|
+
: null;
|
|
680
744
|
}
|
|
681
745
|
}
|
|
682
746
|
|
|
@@ -691,9 +755,11 @@ export function checkAgentDocs(ctx) {
|
|
|
691
755
|
// src/ or a nested package, the docs sit at the root.
|
|
692
756
|
const dirs = [
|
|
693
757
|
...new Set(
|
|
694
|
-
[
|
|
695
|
-
|
|
696
|
-
|
|
758
|
+
[
|
|
759
|
+
ctx.cwd,
|
|
760
|
+
projectRootOf(ctx),
|
|
761
|
+
resolveAgentDocsRoot(ctx.cwd) ?? ctx.cwd,
|
|
762
|
+
].map(dir => path.resolve(dir)),
|
|
697
763
|
),
|
|
698
764
|
];
|
|
699
765
|
const present = dirs.flatMap(dir =>
|
|
@@ -736,8 +802,10 @@ export function checkAgentDocs(ctx) {
|
|
|
736
802
|
const withMarkers = present.filter(abs => {
|
|
737
803
|
const content = read(abs);
|
|
738
804
|
return (
|
|
739
|
-
(content.includes('<!-- ASTRYX:START -->') ||
|
|
740
|
-
|
|
805
|
+
(content.includes('<!-- ASTRYX:START -->') ||
|
|
806
|
+
content.includes('<!-- XDS:START -->')) &&
|
|
807
|
+
(content.includes('<!-- ASTRYX:END -->') ||
|
|
808
|
+
content.includes('<!-- XDS:END -->')) &&
|
|
741
809
|
holdsAstryxBlock(content)
|
|
742
810
|
);
|
|
743
811
|
});
|
|
@@ -746,7 +814,9 @@ export function checkAgentDocs(ctx) {
|
|
|
746
814
|
// of AGENTS.md) is broken when that file holds no block: the agents that
|
|
747
815
|
// read either file get no Astryx docs.
|
|
748
816
|
const dangling = present.flatMap(abs => {
|
|
749
|
-
const match = /<!-- ASTRYX:START -->\s*@(\S+)\s*<!-- ASTRYX:END -->/.exec(
|
|
817
|
+
const match = /<!-- ASTRYX:START -->\s*@(\S+)\s*<!-- ASTRYX:END -->/.exec(
|
|
818
|
+
read(abs),
|
|
819
|
+
);
|
|
750
820
|
if (match == null) return [];
|
|
751
821
|
const target = path.resolve(path.dirname(abs), match[1]);
|
|
752
822
|
return holdsAstryxBlock(read(target)) ? [] : [{abs, target}];
|
|
@@ -848,8 +918,12 @@ export function checkPeerDeps(ctx) {
|
|
|
848
918
|
// Pin the required range for anything wrong so the hint fixes it even when a
|
|
849
919
|
// stale consumer range would otherwise resolve an incompatible version.
|
|
850
920
|
// Quote targets containing shell metacharacters (e.g. `react@>=19.0.0`).
|
|
851
|
-
const quote = (/** @type {string} */ s) =>
|
|
852
|
-
|
|
921
|
+
const quote = (/** @type {string} */ s) =>
|
|
922
|
+
/[<>|() ]/.test(s) ? `'${s}'` : s;
|
|
923
|
+
const targets = [
|
|
924
|
+
...missing,
|
|
925
|
+
...mismatched.map(m => `${m.name}@${m.want}`),
|
|
926
|
+
].map(quote);
|
|
853
927
|
return {
|
|
854
928
|
id: 'peer-deps',
|
|
855
929
|
label: '@astryxdesign/core peer dependencies',
|
|
@@ -1060,7 +1134,10 @@ export function checkProviderIdentity(ctx) {
|
|
|
1060
1134
|
id,
|
|
1061
1135
|
label,
|
|
1062
1136
|
status: 'info',
|
|
1063
|
-
message: skippedBecause(
|
|
1137
|
+
message: skippedBecause(
|
|
1138
|
+
ctx,
|
|
1139
|
+
'the project configuration could not be read',
|
|
1140
|
+
),
|
|
1064
1141
|
};
|
|
1065
1142
|
}
|
|
1066
1143
|
|
|
@@ -1241,7 +1318,8 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
|
|
|
1241
1318
|
const audit = await auditAuthoringSelfDocs({root, sources});
|
|
1242
1319
|
const problems = [
|
|
1243
1320
|
...audit.unreachable.map(
|
|
1244
|
-
source =>
|
|
1321
|
+
source =>
|
|
1322
|
+
`${source} is not in \`${formatCliCommand('docs authoring/field-reference')}\``,
|
|
1245
1323
|
),
|
|
1246
1324
|
...audit.failed.map(
|
|
1247
1325
|
({source, error}) => `${source} failed to load: ${error}`,
|
|
@@ -1257,7 +1335,9 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
|
|
|
1257
1335
|
: {keys: options.topicKeys};
|
|
1258
1336
|
const surface = [
|
|
1259
1337
|
...('error' in topic
|
|
1260
|
-
? [
|
|
1338
|
+
? [
|
|
1339
|
+
`\`${formatCliCommand('docs authoring/field-reference')}\` could not be read: ${topic.error}`,
|
|
1340
|
+
]
|
|
1261
1341
|
: []),
|
|
1262
1342
|
...surfaceProblems(
|
|
1263
1343
|
await auditAuthoringSurface({root, sources, topicKeys: topic.keys}),
|
|
@@ -1299,7 +1379,7 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
|
|
|
1299
1379
|
|
|
1300
1380
|
/** How the CLI-docs audit's problems are fixed. */
|
|
1301
1381
|
const CLI_DOCS_FIX =
|
|
1302
|
-
|
|
1382
|
+
'Set `namespace` on each CLI doc to the one that reads it: cli/commands for a command, cli/api for an API function or the output schema, error codes, and response types, and authoring for a file an author writes (and list it in AUTHORING_SELF_DOCS).';
|
|
1303
1383
|
|
|
1304
1384
|
/** How the docs-tree check's problems are fixed. */
|
|
1305
1385
|
const DOCS_TREE_FIX =
|
|
@@ -1381,8 +1461,7 @@ export async function checkDocsTree(_ctx, options = {}) {
|
|
|
1381
1461
|
const id = 'docs-tree';
|
|
1382
1462
|
const label = 'Docs tree';
|
|
1383
1463
|
try {
|
|
1384
|
-
const catalog =
|
|
1385
|
-
/** @type {any} */ (_ctx)?.docsCatalog ?? builtinCatalog();
|
|
1464
|
+
const catalog = /** @type {any} */ (_ctx)?.docsCatalog ?? builtinCatalog();
|
|
1386
1465
|
const tree = options.tree ?? (await projectTree(catalog, {fresh: true}));
|
|
1387
1466
|
const problems = tree.diagnostics
|
|
1388
1467
|
.filter(d => d.severity === 'error')
|
|
@@ -1397,7 +1476,9 @@ export async function checkDocsTree(_ctx, options = {}) {
|
|
|
1397
1476
|
);
|
|
1398
1477
|
for (const {source, doc} of (await loadCliSelfDocs()).loaded) {
|
|
1399
1478
|
if (CLI_DOC_NAMESPACES[doc.namespace]?.reader !== 'tree') continue;
|
|
1400
|
-
if (
|
|
1479
|
+
if (
|
|
1480
|
+
!placed.has(`@astryxdesign/cli\u0000${doc.type}\u0000${doc.name}`)
|
|
1481
|
+
) {
|
|
1401
1482
|
problems.push(
|
|
1402
1483
|
`${source} has namespace "${doc.namespace}", but no docs-tree namespace adopts it`,
|
|
1403
1484
|
);
|
|
@@ -1450,7 +1531,6 @@ export async function checkDocsTree(_ctx, options = {}) {
|
|
|
1450
1531
|
}
|
|
1451
1532
|
}
|
|
1452
1533
|
|
|
1453
|
-
|
|
1454
1534
|
/**
|
|
1455
1535
|
* Every topic reads progressively, in every language it ships: it loads, its
|
|
1456
1536
|
* section index and each of its sections fit in one read, and no contributed
|
|
@@ -1465,7 +1545,9 @@ export async function checkDocsProgressiveDisclosure(ctx) {
|
|
|
1465
1545
|
/** @type {string[]} */
|
|
1466
1546
|
const problems = [];
|
|
1467
1547
|
if (ctx.docsCatalogError) {
|
|
1468
|
-
problems.push(
|
|
1548
|
+
problems.push(
|
|
1549
|
+
`The docs catalog could not be built: ${ctx.docsCatalogError}`,
|
|
1550
|
+
);
|
|
1469
1551
|
}
|
|
1470
1552
|
for (const issue of ctx.docsCatalogIssues ?? []) {
|
|
1471
1553
|
problems.push(`${issue.package ?? 'a contributed doc'}: ${issue.message}`);
|
|
@@ -1581,14 +1663,14 @@ export function checkCliCommand(ctx) {
|
|
|
1581
1663
|
* @property {{cli: string, core: string | null, copy: string}} answeredBy
|
|
1582
1664
|
* @property {string | null} latest the newest release known, or null
|
|
1583
1665
|
* @property {Array<{path: string, state: import('../../foundation/agent-docs/computer-agent-text.mjs').ComputerAgentTextState}>} [agentText]
|
|
1584
|
-
|
|
1666
|
+
* each file of the computer-level agent text against this release's rendering
|
|
1585
1667
|
* @property {import('../../foundation/env/user-settings.mjs').ComputerAgentTextSettings} [agentTextSettings]
|
|
1586
1668
|
* the person's choices about the computer-level agent text
|
|
1587
1669
|
* @property {string | null} [agentTextCommand] the command the skill file
|
|
1588
1670
|
* tells agents to run, or null
|
|
1589
1671
|
* @property {string | null} [agentTextInstall] the install step the skill
|
|
1590
1672
|
* file records for when that command is not found, or null
|
|
1591
|
-
|
|
1673
|
+
* @property {string} [cwd] the folder doctor runs in, for the printed fix
|
|
1592
1674
|
* @property {import('../../foundation/release/notices.mjs').GlobalBehind | null} [globalBehind]
|
|
1593
1675
|
* the CLI installed for the computer, when it is older than the newest
|
|
1594
1676
|
* release (outside a project)
|
|
@@ -1600,7 +1682,7 @@ export function checkCliCommand(ctx) {
|
|
|
1600
1682
|
* the newest update notice that did not print, and why (diagnostic only)
|
|
1601
1683
|
* @property {{pid: number | null, host: string | null, since: number} | null} [stateLock]
|
|
1602
1684
|
* who holds the state file's lock now (diagnostic only)
|
|
1603
|
-
|
|
1685
|
+
* @property {Array<import('../../foundation/env/search-path.mjs').SearchPathCommand & {upgrade?: string | null, runner?: boolean}>} [searchPathCommands]
|
|
1604
1686
|
* every `astryx` on the search path, with the command that upgrades it
|
|
1605
1687
|
* the `astryx` commands on the search path, in order
|
|
1606
1688
|
* @property {import('../../foundation/integrations/computer-integrations.mjs').ComputerIntegrationCandidate[]} [computerIntegrations]
|
|
@@ -1649,7 +1731,9 @@ export function checkNewestRelease(ctx) {
|
|
|
1649
1731
|
skipNote(ctx.lastNoticeSkip),
|
|
1650
1732
|
lockNote(ctx.stateLock),
|
|
1651
1733
|
].filter(Boolean);
|
|
1652
|
-
return notes.length > 0
|
|
1734
|
+
return notes.length > 0
|
|
1735
|
+
? {...check, message: [check.message, ...notes].join(' ')}
|
|
1736
|
+
: check;
|
|
1653
1737
|
}
|
|
1654
1738
|
|
|
1655
1739
|
/**
|
|
@@ -1660,7 +1744,10 @@ export function checkNewestRelease(ctx) {
|
|
|
1660
1744
|
*/
|
|
1661
1745
|
function lockNote(lock) {
|
|
1662
1746
|
if (!lock) return null;
|
|
1663
|
-
const by =
|
|
1747
|
+
const by =
|
|
1748
|
+
lock.pid == null
|
|
1749
|
+
? ''
|
|
1750
|
+
: ` by process ${lock.pid}${lock.host ? ` on ${lock.host}` : ''}`;
|
|
1664
1751
|
return `The state file's lock is held${by} since ${new Date(lock.since).toISOString()}.`;
|
|
1665
1752
|
}
|
|
1666
1753
|
|
|
@@ -1687,7 +1774,8 @@ function checkFailureNote(failed) {
|
|
|
1687
1774
|
const SKIP_REASONS = {
|
|
1688
1775
|
'already-shown': 'it had already printed that day',
|
|
1689
1776
|
'no-newer': 'no newer release was known',
|
|
1690
|
-
'no-project':
|
|
1777
|
+
'no-project':
|
|
1778
|
+
'the folder belongs to no Astryx project, so no install command fits',
|
|
1691
1779
|
};
|
|
1692
1780
|
|
|
1693
1781
|
/**
|
|
@@ -1698,7 +1786,9 @@ const SKIP_REASONS = {
|
|
|
1698
1786
|
*/
|
|
1699
1787
|
function skipNote(skip) {
|
|
1700
1788
|
if (!skip) return null;
|
|
1701
|
-
const why =
|
|
1789
|
+
const why =
|
|
1790
|
+
/** @type {Record<string, string>} */ (SKIP_REASONS)[skip.reason] ??
|
|
1791
|
+
skip.reason;
|
|
1702
1792
|
return `Last update notice skipped ${new Date(skip.at).toISOString()} (${skip.key}): ${why}.`;
|
|
1703
1793
|
}
|
|
1704
1794
|
|
|
@@ -1710,7 +1800,11 @@ function skipNote(skip) {
|
|
|
1710
1800
|
function newestReleaseCheck(ctx) {
|
|
1711
1801
|
const {latest, globalBehind: behind, projectLag: lag} = ctx;
|
|
1712
1802
|
const {cli} = ctx.answeredBy;
|
|
1713
|
-
const base = {
|
|
1803
|
+
const base = {
|
|
1804
|
+
id: 'newest-release',
|
|
1805
|
+
label: 'Newest release',
|
|
1806
|
+
status: /** @type {const} */ ('info'),
|
|
1807
|
+
};
|
|
1714
1808
|
if (behind) {
|
|
1715
1809
|
return {
|
|
1716
1810
|
...base,
|
|
@@ -1728,16 +1822,24 @@ function newestReleaseCheck(ctx) {
|
|
|
1728
1822
|
if (!latest) {
|
|
1729
1823
|
return {
|
|
1730
1824
|
...base,
|
|
1731
|
-
message:
|
|
1825
|
+
message:
|
|
1826
|
+
'Update check: not run yet, so the newest release is not known here; it runs in the background once a day, after a command.',
|
|
1732
1827
|
};
|
|
1733
1828
|
}
|
|
1734
|
-
if (
|
|
1829
|
+
if (
|
|
1830
|
+
isValidSemver(latest) &&
|
|
1831
|
+
isValidSemver(cli) &&
|
|
1832
|
+
semverCompare(latest, cli) > 0
|
|
1833
|
+
) {
|
|
1735
1834
|
return {
|
|
1736
1835
|
...base,
|
|
1737
1836
|
message: `Release ${latest} is newer than ${cli}, the release that answered.`,
|
|
1738
1837
|
};
|
|
1739
1838
|
}
|
|
1740
|
-
return {
|
|
1839
|
+
return {
|
|
1840
|
+
...base,
|
|
1841
|
+
message: `No newer release is known: the newest is ${latest}.`,
|
|
1842
|
+
};
|
|
1741
1843
|
}
|
|
1742
1844
|
|
|
1743
1845
|
/**
|
|
@@ -1750,16 +1852,24 @@ function newestReleaseCheck(ctx) {
|
|
|
1750
1852
|
export function checkCliOnPath(ctx) {
|
|
1751
1853
|
const rows = ctx.searchPathCommands ?? [];
|
|
1752
1854
|
const base = {id: 'cli-on-path', label: '`astryx` on the search path'};
|
|
1753
|
-
const describe = (
|
|
1754
|
-
|
|
1855
|
+
const describe = (
|
|
1856
|
+
/** @type {import('../../foundation/env/search-path.mjs').SearchPathCommand} */ row,
|
|
1857
|
+
) =>
|
|
1858
|
+
row.astryx
|
|
1859
|
+
? `${row.file} (CLI ${row.version ?? 'of unknown version'})`
|
|
1860
|
+
: `${row.file} (not the Astryx CLI)`;
|
|
1755
1861
|
const others = rows.slice(1);
|
|
1756
|
-
const othersNote =
|
|
1862
|
+
const othersNote =
|
|
1863
|
+
others.length > 0
|
|
1864
|
+
? ` Later on the search path: ${others.map(describe).join(', ')}.`
|
|
1865
|
+
: '';
|
|
1757
1866
|
const [first] = rows;
|
|
1758
1867
|
if (!first) {
|
|
1759
1868
|
return {
|
|
1760
1869
|
...base,
|
|
1761
1870
|
status: 'info',
|
|
1762
|
-
message:
|
|
1871
|
+
message:
|
|
1872
|
+
'No `astryx` command on the search path, so `astryx` runs only through a package manager.',
|
|
1763
1873
|
fix: 'To run the CLI in any folder, install it for the computer: `npm i -g @astryxdesign/cli`.',
|
|
1764
1874
|
};
|
|
1765
1875
|
}
|
|
@@ -1796,8 +1906,11 @@ export function checkCliOnPath(ctx) {
|
|
|
1796
1906
|
* @returns {DoctorCheck}
|
|
1797
1907
|
*/
|
|
1798
1908
|
export function checkComputerAgentText(ctx) {
|
|
1799
|
-
const state =
|
|
1800
|
-
|
|
1909
|
+
const state =
|
|
1910
|
+
ctx.agentTextSettings && ctx.agentText ? null : computerAgentTextState();
|
|
1911
|
+
const settings =
|
|
1912
|
+
ctx.agentTextSettings ??
|
|
1913
|
+
/** @type {NonNullable<typeof state>} */ (state).settings;
|
|
1801
1914
|
const base = {
|
|
1802
1915
|
id: 'computer-agent-text',
|
|
1803
1916
|
label: 'Computer-level agent text',
|
|
@@ -1806,26 +1919,41 @@ export function checkComputerAgentText(ctx) {
|
|
|
1806
1919
|
if (!settings.install) {
|
|
1807
1920
|
return {
|
|
1808
1921
|
...base,
|
|
1809
|
-
message:
|
|
1922
|
+
message:
|
|
1923
|
+
'The computer-level agent text is turned off (`astryx init --user --remove-agents`), so the CLI installed for the computer does not write it.',
|
|
1810
1924
|
fix: `To turn it back on, run \`${formatCliCommand('astryx init --user', ctx.cwd)}\`.`,
|
|
1811
1925
|
};
|
|
1812
1926
|
}
|
|
1813
|
-
const entries =
|
|
1814
|
-
|
|
1815
|
-
const
|
|
1927
|
+
const entries =
|
|
1928
|
+
ctx.agentText ?? /** @type {NonNullable<typeof state>} */ (state).entries;
|
|
1929
|
+
const command = ctx.agentText
|
|
1930
|
+
? (ctx.agentTextCommand ?? null)
|
|
1931
|
+
: /** @type {NonNullable<typeof state>} */ (state).command;
|
|
1932
|
+
const recorded =
|
|
1933
|
+
(ctx.agentText
|
|
1934
|
+
? ctx.agentTextInstall
|
|
1935
|
+
: /** @type {NonNullable<typeof state>} */ (state).install) ?? null;
|
|
1816
1936
|
const fix = `Run \`${formatCliCommand('astryx init --user', ctx.cwd)}\`.`;
|
|
1817
1937
|
const [file, ...links] = entries;
|
|
1818
1938
|
const home = (/** @type {{path: string}} */ e) => displayHomePath(e.path);
|
|
1819
1939
|
const linked = links.filter(l => l.state === 'linked').map(home);
|
|
1820
1940
|
const copied = links.filter(l => l.state === 'current').map(home);
|
|
1821
|
-
const skipped = links
|
|
1941
|
+
const skipped = links
|
|
1942
|
+
.filter(l => l.state === 'foreign' || l.state === 'edited')
|
|
1943
|
+
.map(home);
|
|
1822
1944
|
const dangling = links.filter(l => l.state === 'dangling').map(home);
|
|
1823
|
-
const missing = links
|
|
1945
|
+
const missing = links
|
|
1946
|
+
.filter(l => l.state === 'absent' || l.state === 'differs')
|
|
1947
|
+
.map(home);
|
|
1824
1948
|
const linkNote =
|
|
1825
1949
|
(linked.length > 0 ? ` Linked into: ${linked.join(', ')}.` : '') +
|
|
1826
1950
|
(copied.length > 0 ? ` Copied into: ${copied.join(', ')}.` : '') +
|
|
1827
|
-
(skipped.length > 0
|
|
1828
|
-
|
|
1951
|
+
(skipped.length > 0
|
|
1952
|
+
? ` Not linked, because something Astryx did not create is there: ${skipped.join(', ')}.`
|
|
1953
|
+
: '') +
|
|
1954
|
+
(dangling.length > 0
|
|
1955
|
+
? ` Linked, but the skill folder the link points to is not there, so agents reading it see nothing (as in a container that mounts only that folder): ${dangling.join(', ')}.`
|
|
1956
|
+
: '') +
|
|
1829
1957
|
(missing.length > 0 ? ` Not linked yet: ${missing.join(', ')}.` : '');
|
|
1830
1958
|
if (file.state === 'foreign' || file.state === 'edited') {
|
|
1831
1959
|
return {
|
|
@@ -1878,15 +2006,20 @@ export function checkComputerAgentText(ctx) {
|
|
|
1878
2006
|
export function checkComputerIntegrations(ctx) {
|
|
1879
2007
|
const found = ctx.computerIntegrations ?? [];
|
|
1880
2008
|
const loaded = new Set(ctx.loadedComputerIntegrations ?? []);
|
|
1881
|
-
const base = {
|
|
2009
|
+
const base = {
|
|
2010
|
+
id: 'computer-integrations',
|
|
2011
|
+
label: 'Integrations installed for this computer',
|
|
2012
|
+
};
|
|
1882
2013
|
if (found.length === 0) {
|
|
1883
2014
|
return {
|
|
1884
2015
|
...base,
|
|
1885
2016
|
status: 'info',
|
|
1886
|
-
message:
|
|
2017
|
+
message:
|
|
2018
|
+
'No integration is installed beside the CLI installed for this computer.',
|
|
1887
2019
|
};
|
|
1888
2020
|
}
|
|
1889
|
-
const label = (/** @type {{name: string, version?: string}} */ c) =>
|
|
2021
|
+
const label = (/** @type {{name: string, version?: string}} */ c) =>
|
|
2022
|
+
c.version ? `${c.name}@${c.version}` : c.name;
|
|
1890
2023
|
const own = new Set(ctx.folderIntegrations ?? []);
|
|
1891
2024
|
const shadowed = found.filter(c => own.has(c.name));
|
|
1892
2025
|
const rest = found.filter(c => !own.has(c.name));
|
|
@@ -1894,14 +2027,24 @@ export function checkComputerIntegrations(ctx) {
|
|
|
1894
2027
|
const skipped = rest.filter(c => c.incompatible);
|
|
1895
2028
|
const failed = rest.filter(c => !c.incompatible && !loaded.has(c.name));
|
|
1896
2029
|
const notes = [
|
|
1897
|
-
used.length > 0
|
|
1898
|
-
|
|
2030
|
+
used.length > 0
|
|
2031
|
+
? `Used outside a project: ${used.map(label).join(', ')}.`
|
|
2032
|
+
: '',
|
|
2033
|
+
...shadowed.map(
|
|
2034
|
+
c =>
|
|
2035
|
+
`${label(c)} is not used here: this folder loads its own copy of ${c.name}.`,
|
|
2036
|
+
),
|
|
1899
2037
|
...skipped.map(c => `Skipped ${label(c)}: ${c.incompatible}.`),
|
|
1900
2038
|
...failed.map(c => `${label(c)} could not be loaded.`),
|
|
1901
2039
|
].filter(Boolean);
|
|
1902
2040
|
const fixes = [
|
|
1903
|
-
...skipped.map(
|
|
1904
|
-
|
|
2041
|
+
...skipped.map(
|
|
2042
|
+
c => `install a release of ${c.name} that supports this CLI`,
|
|
2043
|
+
),
|
|
2044
|
+
...failed.map(
|
|
2045
|
+
c =>
|
|
2046
|
+
`run \`${formatCliCommand(`astryx doctor integration validate ${c.name}`, ctx.cwd)}\``,
|
|
2047
|
+
),
|
|
1905
2048
|
];
|
|
1906
2049
|
return {
|
|
1907
2050
|
...base,
|
|
@@ -1922,18 +2065,33 @@ export function checkComputerIntegrations(ctx) {
|
|
|
1922
2065
|
* fix points at the project's folder
|
|
1923
2066
|
* @returns {DoctorCheck}
|
|
1924
2067
|
*/
|
|
1925
|
-
export function checkComputerIntegrationsAvailable({
|
|
2068
|
+
export function checkComputerIntegrationsAvailable({
|
|
2069
|
+
installed,
|
|
2070
|
+
candidates,
|
|
2071
|
+
install,
|
|
2072
|
+
elsewhere = false,
|
|
2073
|
+
}) {
|
|
1926
2074
|
const available = candidates.filter(c => !installed.has(c.name));
|
|
1927
|
-
const base = {
|
|
2075
|
+
const base = {
|
|
2076
|
+
id: 'computer-integrations',
|
|
2077
|
+
label: 'Integrations installed for this computer',
|
|
2078
|
+
status: /** @type {const} */ ('info'),
|
|
2079
|
+
};
|
|
1928
2080
|
if (available.length === 0) {
|
|
1929
|
-
return {
|
|
2081
|
+
return {
|
|
2082
|
+
...base,
|
|
2083
|
+
message:
|
|
2084
|
+
'No integration installed for this computer is missing from this project.',
|
|
2085
|
+
};
|
|
1930
2086
|
}
|
|
1931
2087
|
const add = install;
|
|
1932
2088
|
return {
|
|
1933
2089
|
...base,
|
|
1934
2090
|
message: `Installed for this computer but not in this project, so not used here: ${available.map(c => c.name).join(', ')}.`,
|
|
1935
2091
|
...(add
|
|
1936
|
-
? {
|
|
2092
|
+
? {
|
|
2093
|
+
fix: `To use one in this project, run ${available.map(c => `\`${add(c.name)}\``).join(' or ')}.`,
|
|
2094
|
+
}
|
|
1937
2095
|
: elsewhere
|
|
1938
2096
|
? {fix: NO_PROJECT_FIX}
|
|
1939
2097
|
: {}),
|
|
@@ -1952,9 +2110,9 @@ export const COMPUTER_CHECKS = [
|
|
|
1952
2110
|
coreDir: null,
|
|
1953
2111
|
configPath: null,
|
|
1954
2112
|
}),
|
|
1955
|
-
|
|
2113
|
+
checkAnsweringRelease,
|
|
1956
2114
|
checkNewestRelease,
|
|
1957
|
-
|
|
2115
|
+
checkCliOnPath,
|
|
1958
2116
|
checkComputerIntegrations,
|
|
1959
2117
|
checkComputerAgentText,
|
|
1960
2118
|
];
|
|
@@ -2016,7 +2174,10 @@ export async function runChecks(options = {}) {
|
|
|
2016
2174
|
const cwd = rest.cwd ?? process.cwd();
|
|
2017
2175
|
const checks = [
|
|
2018
2176
|
...report.checks,
|
|
2019
|
-
...checkBuilt(built, {
|
|
2177
|
+
...checkBuilt(built, {
|
|
2178
|
+
cwd,
|
|
2179
|
+
packages: builtIntegrationPackages(built, cwd),
|
|
2180
|
+
}),
|
|
2020
2181
|
];
|
|
2021
2182
|
const summary = {pass: 0, warn: 0, fail: 0, info: 0};
|
|
2022
2183
|
for (const c of checks) summary[c.status] += 1;
|
|
@@ -2222,9 +2383,18 @@ export async function runChecks(options = {}) {
|
|
|
2222
2383
|
}),
|
|
2223
2384
|
);
|
|
2224
2385
|
{
|
|
2225
|
-
const loadedNames = (project?.loadedIntegrations ?? []).map(
|
|
2386
|
+
const loadedNames = (project?.loadedIntegrations ?? []).map(
|
|
2387
|
+
(/** @type {{name: string}} */ i) => i.name,
|
|
2388
|
+
);
|
|
2226
2389
|
const candidates = await computerIntegrationsToAdd(cwd, loadedNames);
|
|
2227
|
-
checks.push(
|
|
2390
|
+
checks.push(
|
|
2391
|
+
checkComputerIntegrationsAvailable({
|
|
2392
|
+
installed: new Set(loadedNames),
|
|
2393
|
+
candidates,
|
|
2394
|
+
install: installCommandFor(cwd),
|
|
2395
|
+
elsewhere: insideProjectElsewhere(cwd),
|
|
2396
|
+
}),
|
|
2397
|
+
);
|
|
2228
2398
|
}
|
|
2229
2399
|
checks.push(await checkAuthoringDocs(ctx));
|
|
2230
2400
|
checks.push(await checkCliDocs(ctx));
|