@astryxdesign/cli 0.7.2 → 0.7.3-canary.0bf23bd
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/README.md +104 -71
- package/api/build/_adapter.d.mts +58 -0
- package/api/build/_adapter.mjs +115 -3
- package/api/build/blocks/blocks.d.mts +42 -0
- package/api/build/blocks/blocks.mjs +169 -0
- package/api/build/blocks/blocks.test.mjs +108 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/kit/answer.d.mts +14 -0
- package/api/build/kit/answer.mjs +85 -0
- package/api/build/kit/answer.test.mjs +38 -0
- package/api/build/kit/kind.d.mts +25 -0
- package/api/build/kit/kind.mjs +170 -0
- package/api/build/kit/kit.d.mts +2 -4
- package/api/build/kit/kit.mjs +8 -398
- package/api/build/kit/rank.d.mts +3 -32
- package/api/build/kit/rank.mjs +25 -342
- package/api/build/kit/rank.test.mjs +2 -1
- package/api/build/kit/signals.d.mts +60 -0
- package/api/build/kit/signals.mjs +284 -0
- package/api/build/kit/start.d.mts +32 -0
- package/api/build/kit/start.mjs +163 -0
- package/api/build/kit/words.d.mts +72 -0
- package/api/build/kit/words.mjs +202 -0
- package/api/build/tree.d.mts +20 -0
- package/api/build/tree.mjs +52 -0
- package/api/build/tree.test.mjs +177 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/component.mjs +17 -3
- package/api/component/component.test.mjs +15 -0
- package/api/component/doc-links.test.mjs +2 -2
- package/api/discover/_adapter.mjs +36 -9
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/docs/_adapter.d.mts +2 -2
- package/api/docs/_adapter.mjs +78 -23
- package/api/docs/docs-homes.test.mjs +127 -0
- package/api/docs/docs.doc.mjs +3 -3
- package/api/docs/docs.test.mjs +35 -19
- package/api/docs/integration-tree.test.mjs +20 -20
- package/api/docs/integrationDocs.test.mjs +1 -1
- package/api/docs/namespace-section.test.mjs +6 -6
- package/api/docs/reference-blocks.test.mjs +2 -2
- package/api/doctor/doctor.mjs +102 -24
- package/api/doctor/doctor.test.mjs +37 -12
- package/api/hook/hook.mjs +1 -1
- package/api/init/init.doc.mjs +20 -6
- package/api/init/init.test.mjs +18 -2
- package/api/init/init.type.d.mts +3 -3
- package/api/init/init.type.mjs +3 -3
- package/api/init/remove/remove.mjs +17 -4
- package/api/init/remove/remove.test.mjs +114 -2
- package/api/init/run/run.mjs +69 -6
- package/api/integration/_adapter.d.mts +8 -0
- package/api/integration/_adapter.mjs +21 -1
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/authoring-checks.test.mjs +220 -0
- package/api/integration/pack-check.mjs +123 -4
- package/api/integration/pack-check.test.mjs +87 -0
- package/api/integration/validate-integration-case-disk.test.mjs +111 -0
- package/api/integration/validate-integration-gaps.test.mjs +1964 -0
- package/api/integration/validate-integration.d.mts +11 -2
- package/api/integration/validate-integration.mjs +250 -32
- package/api/integration/validate-integration.type.d.mts +59 -1
- package/api/integration/validate-integration.type.mjs +46 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +82 -2
- package/api/search/_adapter.mjs +157 -10
- package/api/search/_adapter.test.mjs +173 -0
- package/api/search/_candidate-score.d.mts +40 -0
- package/api/search/_candidate-score.mjs +238 -0
- package/api/search/_cards.json +6098 -0
- package/api/search/_compound.d.mts +50 -0
- package/api/search/_compound.mjs +170 -0
- package/api/search/_doc-searcher.d.mts +4 -0
- package/api/search/_doc-searcher.mjs +23 -0
- package/api/search/_doc-searcher.test.mjs +36 -0
- package/api/search/_doc-searcher.weights.json +1 -0
- package/api/search/_doc-text.d.mts +30 -0
- package/api/search/_doc-text.mjs +87 -0
- package/api/search/_entries.d.mts +52 -0
- package/api/search/_entries.mjs +214 -0
- package/api/search/_features.d.mts +35 -0
- package/api/search/_features.mjs +155 -0
- package/api/search/_gather-components.d.mts +26 -0
- package/api/search/_gather-components.mjs +284 -0
- package/api/search/_gather-docs.d.mts +20 -0
- package/api/search/_gather-docs.mjs +254 -0
- package/api/search/_gather-hooks.d.mts +12 -0
- package/api/search/_gather-hooks.mjs +52 -0
- package/api/search/_gather-templates.d.mts +25 -0
- package/api/search/_gather-templates.mjs +107 -0
- package/api/search/_lexical.d.mts +135 -0
- package/api/search/_lexical.mjs +351 -0
- package/api/search/_main-searcher.d.mts +19 -0
- package/api/search/_main-searcher.mjs +115 -0
- package/api/search/_main-searcher.test.mjs +106 -0
- package/api/search/_main-searcher.weights.json +1 -0
- package/api/search/_module-doc.d.mts +32 -0
- package/api/search/_module-doc.mjs +78 -0
- package/api/search/_order.d.mts +32 -0
- package/api/search/_order.mjs +162 -0
- package/api/search/_order.test.mjs +72 -0
- package/api/search/_priority.d.mts +50 -0
- package/api/search/_priority.mjs +139 -0
- package/api/search/_query-words.d.mts +25 -0
- package/api/search/_query-words.mjs +92 -0
- package/api/search/_query.d.mts +65 -0
- package/api/search/_query.mjs +176 -0
- package/api/search/_result.d.mts +97 -0
- package/api/search/_result.mjs +150 -0
- package/api/search/_score.d.mts +60 -0
- package/api/search/_score.mjs +283 -0
- package/api/search/_synonyms.d.mts +21 -0
- package/api/search/_synonyms.mjs +67 -0
- package/api/search/_template-searcher.d.mts +4 -0
- package/api/search/_template-searcher.mjs +23 -0
- package/api/search/_template-searcher.test.mjs +40 -0
- package/api/search/_template-searcher.weights.json +1 -0
- package/api/search/_titles.d.mts +33 -0
- package/api/search/_titles.mjs +65 -0
- package/api/search/_words.d.mts +64 -0
- package/api/search/_words.mjs +157 -0
- package/api/search/search.d.mts +22 -305
- package/api/search/search.doc.mjs +8 -6
- package/api/search/search.mjs +56 -2482
- package/api/search/search.test.mjs +14 -14
- package/api/search/search.type.d.mts +6 -1
- package/api/search/search.type.mjs +6 -1
- package/api/search/tree.d.mts +15 -0
- package/api/search/tree.mjs +40 -0
- package/api/search/tree.test.mjs +66 -0
- package/api/search/tree.weights.json +1 -0
- package/api/theme/build/build.packed-old-core.test.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +57 -9
- package/api/upgrade/_adapter.mjs +198 -28
- package/api/upgrade/registry/registry.d.mts +10 -4
- package/api/upgrade/registry/registry.mjs +33 -16
- package/api/upgrade/registry/registry.test.mjs +85 -0
- package/api/upgrade/run/run.d.mts +4 -2
- package/api/upgrade/run/run.mjs +13 -2
- package/api/upgrade/run/run.test.mjs +417 -3
- package/api/upgrade/upgrade.doc.mjs +10 -5
- package/api/upgrade/upgrade.mjs +13 -2
- package/api/upgrade/upgrade.test.mjs +194 -0
- package/api/upgrade/upgrade.type.d.mts +5 -3
- package/api/upgrade/upgrade.type.mjs +8 -6
- package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
- package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
- package/assets/docs/author-a-theme.doc.mjs +2 -2
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/add-a-component.doc.mjs +1 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
- package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
- package/assets/docs/tree/block-template.doc.mjs +1 -1
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +1 -1
- package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
- package/assets/docs/tree/checks.doc.mjs +2 -2
- package/assets/docs/tree/cli-command.doc.mjs +1 -1
- package/assets/docs/tree/cli.doc.mjs +1 -1
- package/assets/docs/tree/codemods.doc.mjs +1 -1
- package/assets/docs/tree/component-family.doc.mjs +1 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/configuration.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/docs.doc.mjs +1 -1
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
- package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
- package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
- package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
- package/assets/docs/tree/global-integrations.doc.mjs +2 -2
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
- package/assets/docs/tree/help.doc.mjs +2 -2
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/links.doc.mjs +1 -1
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/ship.doc.mjs +2 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +1 -1
- package/assets/docs/tree/start-a-template.doc.mjs +1 -1
- package/assets/docs/tree/subcomponent.doc.mjs +1 -1
- package/assets/docs/tree/template-assets.doc.mjs +1 -1
- package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +1 -1
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/themes.doc.mjs +1 -1
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
- package/assets/docs/tree/upgrading.doc.mjs +1 -1
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +1 -1
- package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
- package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
- package/assets/docs/use-integrations.doc.mjs +120 -0
- package/assets/docs/working-with-ai.doc.mjs +6 -2
- package/assets/templates/themes/butter/butterTheme.ts +4 -2
- package/assets/templates/themes/stone/stoneTheme.ts +4 -2
- package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
- package/authoring/debug/parse.d.mts +1 -1
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/integration/integration.doc.mjs +1 -1
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/command-result-coverage.test.mjs +6 -5
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
- package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
- package/clients/cli/commands/component.test.mjs +6 -6
- package/clients/cli/commands/docs.doc.mjs +2 -2
- package/clients/cli/commands/docs.mjs +1 -1
- package/clients/cli/commands/docs.test.mjs +14 -14
- package/clients/cli/commands/doctor.mjs +30 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
- package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
- package/clients/cli/commands/init.behavior.test.mjs +230 -1
- package/clients/cli/commands/init.doc.mjs +28 -9
- package/clients/cli/commands/init.flag-help.test.mjs +1 -0
- package/clients/cli/commands/init.mjs +2 -2
- package/clients/cli/commands/integration-check.test.mjs +48 -0
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/manifest.mjs +67 -0
- package/clients/cli/commands/outside-project.test.mjs +2 -1
- package/clients/cli/commands/postinstall.doc.mjs +23 -0
- package/clients/cli/commands/postinstall.mjs +31 -0
- package/clients/cli/commands/setup-nudge.test.mjs +61 -0
- package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +4 -2
- package/clients/cli/commands/upgrade.mjs +9 -2
- package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
- package/clients/cli/index.mjs +29 -86
- package/clients/cli/lib/component-format.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +12 -7
- package/clients/cli/lib/exit-codes.test.mjs +5 -0
- package/clients/cli/lib/manifest.test.mjs +12 -0
- package/clients/cli/lib/welcome-box.mjs +50 -0
- package/clients/cli/run.mjs +108 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
- package/foundation/agent-docs/agent-doc-state.mjs +35 -8
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1012 -100
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +683 -39
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/authoring-self-docs.d.mts +7 -0
- package/foundation/discovery/authoring-self-docs.mjs +16 -4
- package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
- package/foundation/discovery/authoring-surface.d.mts +1 -1
- package/foundation/discovery/authoring-surface.mjs +2 -2
- package/foundation/discovery/authoring-surface.test.mjs +1 -1
- package/foundation/discovery/cli-self-docs.d.mts +2 -1
- package/foundation/discovery/cli-self-docs.mjs +4 -3
- package/foundation/discovery/cli-self-docs.test.mjs +2 -2
- package/foundation/discovery/component-discovery.d.mts +37 -0
- package/foundation/discovery/component-discovery.mjs +80 -5
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/docs-discovery.mjs +14 -1
- package/foundation/discovery/module-exports.d.mts +41 -0
- package/foundation/discovery/module-exports.mjs +253 -0
- package/foundation/discovery/runner-allowlist.d.mts +2 -4
- package/foundation/discovery/runner-allowlist.mjs +2 -4
- package/foundation/discovery/runner-commands.d.mts +77 -0
- package/foundation/discovery/runner-commands.mjs +294 -0
- package/foundation/discovery/runner-commands.test.mjs +128 -0
- package/foundation/discovery/stray-pointers.test.mjs +1 -1
- package/foundation/discovery/template-adapter.mjs +18 -1
- package/foundation/discovery/template-needs.mjs +1 -44
- package/foundation/discovery/theme-discovery.mjs +147 -3
- package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
- package/foundation/doc-compiler/tree.d.mts +32 -6
- package/foundation/doc-compiler/tree.mjs +77 -43
- package/foundation/doc-compiler/tree.test.mjs +7 -7
- package/foundation/fs/paths.d.mts +70 -8
- package/foundation/fs/paths.mjs +556 -38
- package/foundation/fs/paths.test.mjs +430 -54
- package/foundation/fs/read-record.d.mts +18 -0
- package/foundation/fs/read-record.mjs +131 -0
- package/foundation/fs/read-record.test.mjs +77 -0
- package/foundation/integrations/autolink.d.mts +2 -1
- package/foundation/integrations/autolink.mjs +4 -13
- package/foundation/integrations/computer-integrations.d.mts +6 -3
- package/foundation/integrations/computer-integrations.mjs +38 -9
- package/foundation/integrations/contribution-inventory.d.mts +15 -20
- package/foundation/integrations/contribution-inventory.mjs +41 -5
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/run-context.mjs +14 -0
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/route/route.d.mts +285 -0
- package/foundation/route/route.mjs +324 -0
- package/foundation/route/route.test.mjs +153 -0
- package/foundation/runtime/hosted-runtime.test.mjs +403 -0
- package/foundation/runtime/locked-versions.d.mts +32 -0
- package/foundation/runtime/locked-versions.mjs +65 -0
- package/foundation/runtime/package-root.d.mts +91 -0
- package/foundation/runtime/package-root.mjs +174 -0
- package/foundation/runtime/package-set.d.mts +62 -0
- package/foundation/runtime/package-set.mjs +130 -0
- package/foundation/runtime/package-set.test.mjs +432 -0
- package/host/door-conformance.test.mjs +274 -0
- package/host/index.d.ts +116 -0
- package/host/index.mjs +295 -0
- package/host/index.test.mjs +302 -0
- package/package.json +14 -9
package/api/init/init.test.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
|
+
import * as os from 'node:os';
|
|
13
14
|
import * as path from 'node:path';
|
|
14
15
|
import {init, getNextSteps} from './init.mjs';
|
|
15
16
|
import {logger} from '../logger.mjs';
|
|
@@ -21,7 +22,8 @@ const MARKER_START = '<!-- ASTRYX:START -->';
|
|
|
21
22
|
let tmpDir;
|
|
22
23
|
|
|
23
24
|
beforeEach(() => {
|
|
24
|
-
|
|
25
|
+
// Outside any package: init writes agent docs at the nearest package.json.
|
|
26
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-init-api-'));
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
afterEach(() => {
|
|
@@ -30,6 +32,18 @@ afterEach(() => {
|
|
|
30
32
|
|
|
31
33
|
/** @param {string} rel */
|
|
32
34
|
const read = rel => fs.readFileSync(path.join(tmpDir, rel), 'utf8');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Make tmpDir a project that declares Astryx: a folder that declares nothing
|
|
38
|
+
* is outside a project, where init prints no getting-started steps (spec
|
|
39
|
+
* AST-081 FR3).
|
|
40
|
+
*/
|
|
41
|
+
function asProject() {
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
path.join(tmpDir, 'package.json'),
|
|
44
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '*'}}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
33
47
|
/** @param {string} rel */
|
|
34
48
|
const exists = rel => fs.existsSync(path.join(tmpDir, rel));
|
|
35
49
|
|
|
@@ -58,6 +72,7 @@ function writeIntegration() {
|
|
|
58
72
|
|
|
59
73
|
describe('init() — receipts + side effects', () => {
|
|
60
74
|
it('default mode installs AGENTS.md and returns an init.run receipt', async () => {
|
|
75
|
+
asProject();
|
|
61
76
|
const res = await init({}, {cwd: tmpDir});
|
|
62
77
|
expect(res.type).toBe('init.run');
|
|
63
78
|
if (res.type !== 'init.run') return;
|
|
@@ -217,6 +232,7 @@ describe('init() — logger', () => {
|
|
|
217
232
|
});
|
|
218
233
|
|
|
219
234
|
it('emits the install line + full next-steps through the shared logger', async () => {
|
|
235
|
+
asProject();
|
|
220
236
|
/** @type {string[]} */
|
|
221
237
|
const lines = [];
|
|
222
238
|
const logSpy = vi.spyOn(console, 'log').mockImplementation((...a) => lines.push(a.join(' ')));
|
|
@@ -243,7 +259,7 @@ describe('init() — ASCII output', () => {
|
|
|
243
259
|
const lines = [];
|
|
244
260
|
const logSpy = vi.spyOn(console, 'log').mockImplementation((...a) => lines.push(a.join(' ')));
|
|
245
261
|
const errSpy = vi.spyOn(console, 'error').mockImplementation((...a) => lines.push(a.join(' ')));
|
|
246
|
-
const empty = fs.mkdtempSync(path.join(
|
|
262
|
+
const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-init-remove-'));
|
|
247
263
|
logger.setSilent(false);
|
|
248
264
|
try {
|
|
249
265
|
await init({}, {cwd: tmpDir});
|
package/api/init/init.type.d.mts
CHANGED
|
@@ -11,11 +11,11 @@ export type InitRunData = {
|
|
|
11
11
|
*/
|
|
12
12
|
features: string[];
|
|
13
13
|
/**
|
|
14
|
-
* Agent-doc files written (empty if agents weren't run or install failed).
|
|
14
|
+
* Agent-doc files written, relative to the directory init ran in (empty if agents weren't run or install failed). Without --agent-docs-path they sit at the project root: the nearest folder at or above it whose package.json declares an Astryx package, else the nearest with any package.json, within its repository and never the home folder; with none, the directory init ran in.
|
|
15
15
|
*/
|
|
16
16
|
docsWritten: string[];
|
|
17
17
|
/**
|
|
18
|
-
* Soft agent-docs failure, if any. `path-safety` also implies a non-zero exit.
|
|
18
|
+
* Soft agent-docs failure, if any. `path-safety` also implies a non-zero exit. `install-failed` carries a message when init wrote no agent docs (the home folder, or a settings folder in it outside a project) or skipped a file that resolves outside the project; docsWritten then lists what was written.
|
|
19
19
|
*/
|
|
20
20
|
docsError: {
|
|
21
21
|
kind: "path-safety" | "install-failed";
|
|
@@ -113,7 +113,7 @@ export type InitOptions = {
|
|
|
113
113
|
*/
|
|
114
114
|
all?: boolean | undefined;
|
|
115
115
|
/**
|
|
116
|
-
* Remove the managed agent-docs block instead of installing; with `user`, remove the computer-level agent text.
|
|
116
|
+
* Remove the managed agent-docs block, at the project root and in the folder init runs in, instead of installing; with `user`, remove the computer-level agent text.
|
|
117
117
|
*/
|
|
118
118
|
removeAgents?: boolean | undefined;
|
|
119
119
|
/**
|
package/api/init/init.type.mjs
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* @typedef {object} InitRunData
|
|
11
11
|
* @property {'default' | 'features'} mode `default` (no flags) or `features` (--features/--all).
|
|
12
12
|
* @property {string[]} features Features that were run, in order.
|
|
13
|
-
* @property {string[]} docsWritten Agent-doc files written (empty if agents weren't run or install failed).
|
|
14
|
-
* @property {{kind: 'path-safety' | 'install-failed', message?: string} | null} docsError Soft agent-docs failure, if any. `path-safety` also implies a non-zero exit.
|
|
13
|
+
* @property {string[]} docsWritten Agent-doc files written, relative to the directory init ran in (empty if agents weren't run or install failed). Without --agent-docs-path they sit at the project root: the nearest folder at or above it whose package.json declares an Astryx package, else the nearest with any package.json, within its repository and never the home folder; with none, the directory init ran in.
|
|
14
|
+
* @property {{kind: 'path-safety' | 'install-failed', message?: string} | null} docsError Soft agent-docs failure, if any. `path-safety` also implies a non-zero exit. `install-failed` carries a message when init wrote no agent docs (the home folder, or a settings folder in it outside a project) or skipped a file that resolves outside the project; docsWritten then lists what was written.
|
|
15
15
|
* @property {boolean} theme Whether the theme feature ran.
|
|
16
16
|
* @property {'created' | 'skipped' | 'failed' | null} themeTemplate Outcome of writing the annotated theme template (`skipped` = a file was already there).
|
|
17
17
|
* @property {string | null} themeTemplatePath Relative output path when `themeTemplate === 'created'`.
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
* @typedef {object} InitOptions
|
|
71
71
|
* @property {string} [features] Comma-separated features to install (agents, theme, template).
|
|
72
72
|
* @property {boolean} [all] Install all features.
|
|
73
|
-
* @property {boolean} [removeAgents] Remove the managed agent-docs block instead of installing; with `user`, remove the computer-level agent text.
|
|
73
|
+
* @property {boolean} [removeAgents] Remove the managed agent-docs block, at the project root and in the folder init runs in, instead of installing; with `user`, remove the computer-level agent text.
|
|
74
74
|
* @property {boolean} [user] Write the computer-level agent text (`~/.agents/skills/astryx/SKILL.md`, linked into `~/.claude/skills` and the skill folders of other installed tools, with a copy for Continue) instead of setting up the folder.
|
|
75
75
|
* @property {string} [agent] Agent preset: claude, cursor, codex, hermes, muse, all. Refused with `user`.
|
|
76
76
|
* @property {string | string[]} [agentDocsPath] Explicit agent-docs file path(s).
|
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
* @file init.remove leaf — the `--remove-agents` path behind `astryx init`.
|
|
5
5
|
*
|
|
6
6
|
* `remove(ctx)` deletes the managed agent-docs block from every agent-doc file
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* at the project root, where init writes it, and in the working folder, and
|
|
8
|
+
* returns an `init.remove` receipt. The removal engine lives in
|
|
9
|
+
* foundation/agent-docs; this leaf only wires the cwd.
|
|
9
10
|
*
|
|
10
11
|
* Note: removeAgentDocs() still logs its own per-file lines via humanLog
|
|
11
12
|
* (shared lib behavior) — so a programmatic remove is not perfectly silent yet.
|
|
12
13
|
* Threading a logger through agent-docs is a separate cleanup.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import {removeProjectAgentDocs} from '../../../foundation/agent-docs/agent-docs.mjs';
|
|
16
17
|
import {logger} from '../../logger.mjs';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -23,7 +24,19 @@ import {logger} from '../../logger.mjs';
|
|
|
23
24
|
* @returns {Promise<import('../init.type.mjs').InitRemoveResponse>}
|
|
24
25
|
*/
|
|
25
26
|
export async function remove({cwd = process.cwd()} = {}) {
|
|
26
|
-
|
|
27
|
+
/** @type {string[]} */
|
|
28
|
+
const skipped = [];
|
|
29
|
+
const removedFrom = removeProjectAgentDocs(cwd, {
|
|
30
|
+
onSkipped: (rel, reason) => skipped.push(`${rel} (${reason})`),
|
|
31
|
+
});
|
|
32
|
+
if (skipped.length > 0) {
|
|
33
|
+
// Below the project root, as install does there (0.7.x touched only the
|
|
34
|
+
// working folder): a root agent file outside the project is left alone.
|
|
35
|
+
logger.warn(
|
|
36
|
+
`Note: Left as ${skipped.length === 1 ? 'it is' : 'they are'}: ${skipped.join('; ')}. ` +
|
|
37
|
+
'To remove the Astryx block there, delete it by hand.',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
27
40
|
const removed = removedFrom.length > 0;
|
|
28
41
|
logger.log(
|
|
29
42
|
removed
|
|
@@ -1,12 +1,124 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
3
|
-
import * as fs from 'node:fs'; import * as path from 'node:path';
|
|
3
|
+
import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os';
|
|
4
4
|
import {init} from '../init.mjs'; import {remove} from './remove.mjs'; import {logger} from '../../logger.mjs';
|
|
5
5
|
let dir;
|
|
6
|
-
beforeEach(() => { dir = fs.mkdtempSync(path.join(
|
|
6
|
+
beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-rm-')); fs.writeFileSync(path.join(dir,'package.json'),JSON.stringify({name:'c',version:'1.0.0'})); });
|
|
7
7
|
afterEach(() => { fs.rmSync(dir,{recursive:true,force:true}); logger.setSilent(true); });
|
|
8
8
|
describe('init.remove says whether anything was removed', () => {
|
|
9
9
|
it('false on clean', async () => { const r=await remove({cwd:dir}); expect(r.type).toBe('init.remove'); expect(r.data.removed).toBe(false); });
|
|
10
10
|
it('true then false', async () => { await init({features:['agents']},{cwd:dir}); expect(fs.readFileSync(path.join(dir,'AGENTS.md'),'utf8')).toContain('<!-- ASTRYX:START -->'); const a=await remove({cwd:dir}); expect(a.data.removed).toBe(true); const b=await remove({cwd:dir}); expect(b.data.removed).toBe(false); });
|
|
11
11
|
it('human line', async () => { const l=[]; const s=vi.spyOn(console,'log').mockImplementation((...a)=>l.push(a.join(' '))); logger.setSilent(false); try{await remove({cwd:dir});}finally{logger.setSilent(true);s.mockRestore();} expect(l.join('\n')).toContain('Nothing to remove'); });
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
describe('init.remove acts where init writes, the project root, and in the folder it runs in', () => {
|
|
15
|
+
/** @type {string} */
|
|
16
|
+
let project;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
project = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-rm-root-'));
|
|
19
|
+
fs.writeFileSync(
|
|
20
|
+
path.join(project, 'package.json'),
|
|
21
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '1.0.0'}}),
|
|
22
|
+
);
|
|
23
|
+
fs.mkdirSync(path.join(project, 'src', 'app'), {recursive: true});
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => { fs.rmSync(project, {recursive: true, force: true}); });
|
|
26
|
+
const block = '<!-- ASTRYX:START -->\nAstryx v0.7.0 · old\n<!-- ASTRYX:END -->\n';
|
|
27
|
+
|
|
28
|
+
it('removes from a subfolder what init wrote at the project root', async () => {
|
|
29
|
+
const sub = path.join(project, 'src', 'app');
|
|
30
|
+
const written = await init({features: ['agents']}, {cwd: sub});
|
|
31
|
+
expect(written.data.docsWritten).toEqual(['../../AGENTS.md', '../../GEMINI.md']);
|
|
32
|
+
|
|
33
|
+
const r = await remove({cwd: sub});
|
|
34
|
+
expect(r).toEqual({type: 'init.remove', data: {removed: true}});
|
|
35
|
+
// Both files held only what init wrote, so they are deleted.
|
|
36
|
+
expect(fs.existsSync(path.join(project, 'AGENTS.md'))).toBe(false);
|
|
37
|
+
expect(fs.existsSync(path.join(project, 'GEMINI.md'))).toBe(false);
|
|
38
|
+
expect((await remove({cwd: sub})).data.removed).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('removes a block an earlier version left in the subfolder, and keeps the files\' own text', async () => {
|
|
42
|
+
const sub = path.join(project, 'src', 'app');
|
|
43
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\nRoot rules.\n\n${block}`);
|
|
44
|
+
fs.writeFileSync(path.join(sub, 'AGENTS.md'), `# App\n\nApp rules.\n\n${block}`);
|
|
45
|
+
|
|
46
|
+
expect((await remove({cwd: sub})).data.removed).toBe(true);
|
|
47
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf8').trimEnd()).toBe('# Agents\n\nRoot rules.');
|
|
48
|
+
expect(fs.readFileSync(path.join(sub, 'AGENTS.md'), 'utf8').trimEnd()).toBe('# App\n\nApp rules.');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('run at the project root, leaves a subfolder alone', async () => {
|
|
52
|
+
const sub = path.join(project, 'src', 'app');
|
|
53
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\n${block}`);
|
|
54
|
+
fs.writeFileSync(path.join(sub, 'AGENTS.md'), `# App\n\n${block}`);
|
|
55
|
+
|
|
56
|
+
expect((await remove({cwd: project})).data.removed).toBe(true);
|
|
57
|
+
expect(fs.readFileSync(path.join(sub, 'AGENTS.md'), 'utf8')).toContain('<!-- ASTRYX:START -->');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('below the root, leaves a root agent file linked outside the project with a note, and removes the rest (0.7.x exited 0)', async () => {
|
|
61
|
+
// A workspace member whose CLAUDE.md links to the monorepo's.
|
|
62
|
+
const monorepo = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-rm-mono-'));
|
|
63
|
+
try {
|
|
64
|
+
const web = path.join(monorepo, 'apps', 'web');
|
|
65
|
+
const sub = path.join(web, 'src');
|
|
66
|
+
fs.mkdirSync(sub, {recursive: true});
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
path.join(web, 'package.json'),
|
|
69
|
+
JSON.stringify({name: 'web', dependencies: {'@astryxdesign/core': '1.0.0'}}),
|
|
70
|
+
);
|
|
71
|
+
fs.writeFileSync(path.join(monorepo, 'CLAUDE.md'), `# Shared\n\n${block}`);
|
|
72
|
+
fs.symlinkSync('../../CLAUDE.md', path.join(web, 'CLAUDE.md'));
|
|
73
|
+
fs.writeFileSync(path.join(web, 'AGENTS.md'), `# Web\n\n${block}`);
|
|
74
|
+
fs.writeFileSync(path.join(sub, 'AGENTS.md'), `# Src\n\n${block}`);
|
|
75
|
+
const warned = [];
|
|
76
|
+
const warn = vi.spyOn(console, 'error').mockImplementation((...a) => warned.push(a.join(' ')));
|
|
77
|
+
logger.setSilent(false);
|
|
78
|
+
let r;
|
|
79
|
+
try {
|
|
80
|
+
r = await remove({cwd: sub});
|
|
81
|
+
} finally {
|
|
82
|
+
logger.setSilent(true);
|
|
83
|
+
warn.mockRestore();
|
|
84
|
+
}
|
|
85
|
+
expect(r).toEqual({type: 'init.remove', data: {removed: true}});
|
|
86
|
+
expect(fs.readFileSync(path.join(web, 'AGENTS.md'), 'utf8').trimEnd()).toBe('# Web');
|
|
87
|
+
expect(fs.readFileSync(path.join(sub, 'AGENTS.md'), 'utf8').trimEnd()).toBe('# Src');
|
|
88
|
+
// The file outside the project keeps its block.
|
|
89
|
+
expect(fs.readFileSync(path.join(monorepo, 'CLAUDE.md'), 'utf8')).toContain('<!-- ASTRYX:START -->');
|
|
90
|
+
expect(warned.join('\n')).toMatch(/Note: Left as it is: \.\.\/CLAUDE\.md \(/u);
|
|
91
|
+
} finally {
|
|
92
|
+
fs.rmSync(monorepo, {recursive: true, force: true});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('at the root, a root agent file linked outside the project still refuses the run', async () => {
|
|
97
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-rm-outside-'));
|
|
98
|
+
try {
|
|
99
|
+
fs.writeFileSync(path.join(outside, 'CLAUDE.md'), `# Shared\n\n${block}`);
|
|
100
|
+
fs.symlinkSync(path.join(outside, 'CLAUDE.md'), path.join(project, 'CLAUDE.md'));
|
|
101
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\n${block}`);
|
|
102
|
+
await expect(remove({cwd: project})).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
103
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf8')).toContain('<!-- ASTRYX:START -->');
|
|
104
|
+
} finally {
|
|
105
|
+
fs.rmSync(outside, {recursive: true, force: true});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('a link in the subfolder that escapes it refuses the run before anything is written', async () => {
|
|
110
|
+
const sub = path.join(project, 'src', 'app');
|
|
111
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-rm-outside-'));
|
|
112
|
+
try {
|
|
113
|
+
fs.writeFileSync(path.join(project, 'AGENTS.md'), `# Agents\n\n${block}`);
|
|
114
|
+
fs.writeFileSync(path.join(outside, 'CLAUDE.md'), `# Shared\n\n${block}`);
|
|
115
|
+
fs.symlinkSync(path.join(outside, 'CLAUDE.md'), path.join(sub, 'CLAUDE.md'));
|
|
116
|
+
|
|
117
|
+
await expect(remove({cwd: sub})).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
118
|
+
expect(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf8')).toContain('<!-- ASTRYX:START -->');
|
|
119
|
+
expect(fs.readFileSync(path.join(outside, 'CLAUDE.md'), 'utf8')).toContain('<!-- ASTRYX:START -->');
|
|
120
|
+
} finally {
|
|
121
|
+
fs.rmSync(outside, {recursive: true, force: true});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
package/api/init/run/run.mjs
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* @file init.run leaf — non-interactive project setup + feature installer.
|
|
5
5
|
*
|
|
6
6
|
* `run(options, ctx)` is the default / `--features` / `--all` path behind
|
|
7
|
-
* `astryx init`. It installs the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* `astryx init`. It installs the agent-docs cheat sheet at the project root so
|
|
8
|
+
* every agent reads it (AGENTS.md, a copy in the project's Claude Code file, and
|
|
9
|
+
* a GEMINI.md import), points at the theme + page-building workflows, and
|
|
10
|
+
* (optionally) scaffolds a starter template — with NO prompts, so it behaves
|
|
11
|
+
* identically for humans, agents, CI, and piped I/O. It performs the side
|
|
12
|
+
* effects and returns an
|
|
11
13
|
* `init.run` receipt. Hard errors (unknown feature/template) throw AstryxError
|
|
12
14
|
* with a stable code. Human output is emitted through the shared `logger`
|
|
13
15
|
* (silent by default) so the CLI keeps its exact plain output and a
|
|
@@ -21,8 +23,11 @@ import {PathSafetyError, assertWithin} from '../../../foundation/fs/path-safety.
|
|
|
21
23
|
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
22
24
|
import {isOutsideProject} from '../../../foundation/env/project-scope.mjs';
|
|
23
25
|
import {
|
|
26
|
+
AgentDocsHomeError,
|
|
27
|
+
assertAgentDocsTarget,
|
|
24
28
|
installAgentDocs,
|
|
25
29
|
renderAgentDocsBlock,
|
|
30
|
+
agentDocsTargetDir,
|
|
26
31
|
} from '../../../foundation/agent-docs/agent-docs.mjs';
|
|
27
32
|
import {themeTemplate} from '../../theme/template/template.mjs';
|
|
28
33
|
import {listTemplates} from '../../template/template.mjs';
|
|
@@ -64,6 +69,24 @@ export function getNextSteps(invocation) {
|
|
|
64
69
|
];
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Whether two paths name one folder, links resolved.
|
|
74
|
+
* @param {string} a
|
|
75
|
+
* @param {string} b
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
function sameFolder(a, b) {
|
|
79
|
+
/** @param {string} p */
|
|
80
|
+
const real = p => {
|
|
81
|
+
try {
|
|
82
|
+
return fs.realpathSync(p);
|
|
83
|
+
} catch {
|
|
84
|
+
return path.resolve(p);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return real(a) === real(b);
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
/**
|
|
68
91
|
* Install the agent-docs block; records the outcome (or soft error) on `data`.
|
|
69
92
|
* Mirrors the CLI's historical soft-error policy: a PathSafetyError surfaces its
|
|
@@ -92,14 +115,46 @@ async function applyAgents(cwd, options, invocation, data) {
|
|
|
92
115
|
? options.agentDocsPath
|
|
93
116
|
: [options.agentDocsPath]
|
|
94
117
|
: undefined;
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
// The docs belong at the project root, not in the subfolder init ran in;
|
|
119
|
+
// with no project above, they go where init ran, as before. Explicit
|
|
120
|
+
// --agent-docs-path entries stay relative to where init ran.
|
|
121
|
+
const root = agentDocsTargetDir(cwd);
|
|
122
|
+
const targetDir = paths ? cwd : root;
|
|
123
|
+
// With explicit paths, installAgentDocs checks each file's own folder.
|
|
124
|
+
if (!paths) assertAgentDocsTarget(targetDir);
|
|
125
|
+
const renderedBlock = await renderAgentDocsBlock(root);
|
|
126
|
+
/** @type {string[]} */
|
|
127
|
+
const skipped = [];
|
|
128
|
+
const files = installAgentDocs(targetDir, {
|
|
97
129
|
agent: options.agent,
|
|
98
130
|
paths,
|
|
99
131
|
renderedBlock,
|
|
132
|
+
onSkipped: (rel, reason) => skipped.push(`${rel} (${reason})`),
|
|
133
|
+
// Below the project root, 0.7.1 wrote in the folder init ran in, so a
|
|
134
|
+
// project-root file that escapes the project is skipped and reported,
|
|
135
|
+
// not refused.
|
|
136
|
+
refuseEscapes: sameFolder(targetDir, cwd),
|
|
100
137
|
});
|
|
138
|
+
// Each file is reported from where init ran, as before.
|
|
139
|
+
const written =
|
|
140
|
+
path.resolve(targetDir) === path.resolve(cwd)
|
|
141
|
+
? files
|
|
142
|
+
: files.map(rel =>
|
|
143
|
+
path.relative(cwd, path.join(targetDir, rel)).split(path.sep).join('/'),
|
|
144
|
+
);
|
|
101
145
|
data.docsWritten = written;
|
|
102
146
|
logger.log(`[ok] AI agent docs installed -> ${written.join(', ')}`);
|
|
147
|
+
// A file 0.7.1 did not write (GEMINI.md, or a project-root file when init
|
|
148
|
+
// runs below the root) that resolves outside the project is left as it
|
|
149
|
+
// is, reported in docsError with one note; the exit code stays 0. A file
|
|
150
|
+
// 0.7.1 wrote whose path escapes the project still fails the run (below).
|
|
151
|
+
if (skipped.length > 0) {
|
|
152
|
+
const message =
|
|
153
|
+
`Left as ${skipped.length === 1 ? 'it is' : 'they are'}: ${skipped.join('; ')}. ` +
|
|
154
|
+
'To have init write the Astryx block there, make it a regular file inside the project, or copy the block from AGENTS.md by hand.';
|
|
155
|
+
logger.warn(`Note: ${message}`);
|
|
156
|
+
data.docsError = {kind: 'install-failed', message};
|
|
157
|
+
}
|
|
103
158
|
} catch (err) {
|
|
104
159
|
// PathSafetyError carries a precise, user-actionable message — surface it
|
|
105
160
|
// (and flag exit 1) instead of the generic "could not install" warning so
|
|
@@ -109,6 +164,14 @@ async function applyAgents(cwd, options, invocation, data) {
|
|
|
109
164
|
data.docsError = {kind: 'path-safety', message: err.message};
|
|
110
165
|
return;
|
|
111
166
|
}
|
|
167
|
+
// The home folder, and a settings folder in it outside a project, are
|
|
168
|
+
// refused: nothing is written, the reason is printed as a note, and the
|
|
169
|
+
// exit code stays 0.
|
|
170
|
+
if (err instanceof AgentDocsHomeError) {
|
|
171
|
+
logger.warn(`Note: ${err.message}`);
|
|
172
|
+
data.docsError = {kind: 'install-failed', message: err.message};
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
112
175
|
logger.error(
|
|
113
176
|
`Could not install agent docs. Try again with \`${invocation} init --features agents\`.`,
|
|
114
177
|
);
|
|
@@ -40,6 +40,14 @@ export function integrationFluff(discovered: Parameters<typeof findFluff>[0]): P
|
|
|
40
40
|
* read too
|
|
41
41
|
*/
|
|
42
42
|
export function integrationStrayPointers(discovered: Parameters<typeof findStrayPointers>[0], integration?: any): Promise<import("./authoring-checks.type.mjs").StrayPointer[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Each Astryx command a package's topics write with a package runner. A
|
|
45
|
+
* package whose topics cannot be discovered has none here: the other checks
|
|
46
|
+
* report topics that do not load.
|
|
47
|
+
* @param {any} integration a loaded integration
|
|
48
|
+
* @returns {Promise<import('../../foundation/discovery/runner-commands.mjs').RunnerCommand[]>}
|
|
49
|
+
*/
|
|
50
|
+
export function integrationRunnerCommands(integration: any): Promise<import("../../foundation/discovery/runner-commands.mjs").RunnerCommand[]>;
|
|
43
51
|
import { findCopiedPassages } from '../../foundation/discovery/copied-passages.mjs';
|
|
44
52
|
import { findFluff } from '../../foundation/discovery/doc-fluff.mjs';
|
|
45
53
|
import { findStrayPointers } from '../../foundation/discovery/stray-pointers.mjs';
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file The integration subject's access to the environment (INV21): reading
|
|
5
|
-
* one package's component docs and sources with the project's own TypeScript
|
|
5
|
+
* one package's component docs and sources with the project's own TypeScript,
|
|
6
|
+
* and its topics.
|
|
6
7
|
*
|
|
7
8
|
* @input a loaded integration and the directory the command runs in
|
|
8
9
|
* @output the reports the integration checks attach to their results
|
|
@@ -14,9 +15,11 @@ import {
|
|
|
14
15
|
forEachIntegrationDoc,
|
|
15
16
|
integrationSourceForDoc,
|
|
16
17
|
} from '../../foundation/discovery/component-discovery.mjs';
|
|
18
|
+
import {discoverIntegrationDocs} from '../../foundation/discovery/docs-discovery.mjs';
|
|
17
19
|
import {comparePropDocs} from '../../foundation/discovery/prop-drift.mjs';
|
|
18
20
|
import {findCopiedPassages} from '../../foundation/discovery/copied-passages.mjs';
|
|
19
21
|
import {findFluff} from '../../foundation/discovery/doc-fluff.mjs';
|
|
22
|
+
import {findRunnerCommands} from '../../foundation/discovery/runner-commands.mjs';
|
|
20
23
|
import {findStrayPointers} from '../../foundation/discovery/stray-pointers.mjs';
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -110,3 +113,20 @@ export async function integrationStrayPointers(discovered, integration) {
|
|
|
110
113
|
}
|
|
111
114
|
return findStrayPointers({...discovered, components});
|
|
112
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Each Astryx command a package's topics write with a package runner. A
|
|
119
|
+
* package whose topics cannot be discovered has none here: the other checks
|
|
120
|
+
* report topics that do not load.
|
|
121
|
+
* @param {any} integration a loaded integration
|
|
122
|
+
* @returns {Promise<import('../../foundation/discovery/runner-commands.mjs').RunnerCommand[]>}
|
|
123
|
+
*/
|
|
124
|
+
export async function integrationRunnerCommands(integration) {
|
|
125
|
+
let discovered;
|
|
126
|
+
try {
|
|
127
|
+
discovered = await discoverIntegrationDocs(integration);
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return findRunnerCommands(discovered);
|
|
132
|
+
}
|
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
validateInstalledIntegration,
|
|
52
52
|
validateLocalIntegration,
|
|
53
53
|
} from './validate-integration.mjs';
|
|
54
|
+
import {rootProblem} from '../../foundation/integrations/validate-contributions.mjs';
|
|
55
|
+
import {normalizeProviderId} from '../../foundation/identity/provider-identity.mjs';
|
|
54
56
|
|
|
55
57
|
/** @param {string} value */
|
|
56
58
|
export function shellArg(value) {
|
|
@@ -58,15 +60,61 @@ export function shellArg(value) {
|
|
|
58
60
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The provider ID a package without a name is checked under: a valid bare
|
|
65
|
+
* package name no published package can hold (npm names are lowercase).
|
|
66
|
+
*/
|
|
67
|
+
const UNNAMED_PROVIDER = 'astryx-unnamed-local-package';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A message as the package sees it: the stand-in provider ID gone, its docs
|
|
71
|
+
* named by their path in the package.
|
|
72
|
+
* @param {string} message
|
|
73
|
+
*/
|
|
74
|
+
function unnamedShown(message) {
|
|
75
|
+
return message
|
|
76
|
+
.split(`${UNNAMED_PROVIDER}/`)
|
|
77
|
+
.join('')
|
|
78
|
+
.split(UNNAMED_PROVIDER)
|
|
79
|
+
.join('this package');
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
/**
|
|
62
83
|
* Resolve one local or installed integration for an authoring check.
|
|
63
84
|
* @param {string | undefined} pkg
|
|
64
85
|
* @param {string} cwd
|
|
65
86
|
*/
|
|
66
87
|
async function resolveIntegration(pkg, cwd) {
|
|
67
|
-
|
|
68
|
-
? validateInstalledIntegration(pkg, cwd)
|
|
69
|
-
: validateLocalIntegration(cwd);
|
|
88
|
+
const resolved = pkg
|
|
89
|
+
? await validateInstalledIntegration(pkg, cwd)
|
|
90
|
+
: await validateLocalIntegration(cwd);
|
|
91
|
+
const integration = resolved.found ? resolved.integration : undefined;
|
|
92
|
+
if (!integration) return resolved;
|
|
93
|
+
// A root that is not a readable folder is reported in the issues; the
|
|
94
|
+
// checks read nothing from it rather than fail on it.
|
|
95
|
+
/** @type {typeof integration & {__unnamed?: boolean}} */
|
|
96
|
+
const usable = {...integration};
|
|
97
|
+
// The provider ID readers key the package on: the manifest's providerId,
|
|
98
|
+
// else its name. A package with neither (validate warned about it) is
|
|
99
|
+
// still checked; the docs check names its docs under a stand-in ID.
|
|
100
|
+
try {
|
|
101
|
+
usable.providerId = normalizeProviderId(
|
|
102
|
+
integration.__declaredProviderId ?? integration.name,
|
|
103
|
+
);
|
|
104
|
+
} catch {
|
|
105
|
+
usable.__unnamed = true;
|
|
106
|
+
}
|
|
107
|
+
for (const kind of /** @type {const} */ ([
|
|
108
|
+
'components',
|
|
109
|
+
'templates',
|
|
110
|
+
'codemods',
|
|
111
|
+
'docs',
|
|
112
|
+
'themes',
|
|
113
|
+
])) {
|
|
114
|
+
const root = usable[kind];
|
|
115
|
+
if (root != null && rootProblem(root)) usable[kind] = undefined;
|
|
116
|
+
}
|
|
117
|
+
return {...resolved, integration: usable};
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
/**
|
|
@@ -387,8 +435,20 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
387
435
|
};
|
|
388
436
|
}
|
|
389
437
|
|
|
438
|
+
// The docs tree names a package's docs by its provider ID, which a
|
|
439
|
+
// package.json without a name does not give: its docs are checked under a
|
|
440
|
+
// stand-in ID, which no message names.
|
|
441
|
+
const unnamed = /** @type {{__unnamed?: boolean}} */ (resolved.integration)
|
|
442
|
+
.__unnamed;
|
|
443
|
+
const docsIntegration = unnamed
|
|
444
|
+
? {
|
|
445
|
+
...resolved.integration,
|
|
446
|
+
name: UNNAMED_PROVIDER,
|
|
447
|
+
providerId: UNNAMED_PROVIDER,
|
|
448
|
+
}
|
|
449
|
+
: resolved.integration;
|
|
390
450
|
const [discovered, builtinTopics] = await Promise.all([
|
|
391
|
-
discoverIntegrationDocs(
|
|
451
|
+
discoverIntegrationDocs(docsIntegration),
|
|
392
452
|
Promise.resolve(discoverBuiltinTopics()),
|
|
393
453
|
]);
|
|
394
454
|
const {records, errors} = discovered;
|
|
@@ -458,27 +518,40 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
458
518
|
// its docs (spec:AST-046, spec:AST-047).
|
|
459
519
|
if (errors.length === 0) {
|
|
460
520
|
for (const {severity, message} of await packageDocsProblems(
|
|
461
|
-
/** @type {{name: string}} */ (
|
|
521
|
+
/** @type {{name: string}} */ (docsIntegration),
|
|
462
522
|
discovered,
|
|
463
523
|
)) {
|
|
464
|
-
issues.push({
|
|
524
|
+
issues.push({
|
|
525
|
+
code: 'invalid_doc_graph',
|
|
526
|
+
severity,
|
|
527
|
+
message: unnamedShown(message),
|
|
528
|
+
});
|
|
465
529
|
}
|
|
466
530
|
|
|
467
531
|
// A reference block includes content rather than linking to it, so one
|
|
468
532
|
// that cannot include what it names loses that content for every reader:
|
|
469
533
|
// an error, where a link that names no doc still prints as written.
|
|
470
534
|
for (const message of await packageReferenceProblems(
|
|
471
|
-
/** @type {{name: string}} */ (
|
|
535
|
+
/** @type {{name: string}} */ (docsIntegration),
|
|
472
536
|
discovered,
|
|
473
537
|
)) {
|
|
474
|
-
issues.push({
|
|
538
|
+
issues.push({
|
|
539
|
+
code: 'invalid_doc_reference',
|
|
540
|
+
severity: 'error',
|
|
541
|
+
message: unnamedShown(message),
|
|
542
|
+
});
|
|
475
543
|
}
|
|
476
544
|
|
|
477
545
|
// One fact has one owner: a passage two topics repeat goes stale in one
|
|
478
546
|
// of them. Each is listed and is a warning (architecture:cli-surface
|
|
479
547
|
// DEC-8), so `integration check --strict` fails until it is fixed;
|
|
480
548
|
// `doctor integration docs` keeps its exit code, as it fails on errors.
|
|
481
|
-
copiedPassages.push(
|
|
549
|
+
copiedPassages.push(
|
|
550
|
+
...(await integrationCopiedPassages(discovered)).map(passage => ({
|
|
551
|
+
...passage,
|
|
552
|
+
message: unnamedShown(passage.message),
|
|
553
|
+
})),
|
|
554
|
+
);
|
|
482
555
|
for (const {message} of copiedPassages) {
|
|
483
556
|
issues.push({code: 'copied_passage', severity: 'warning', message});
|
|
484
557
|
}
|
|
@@ -488,6 +561,7 @@ export async function integrationDocConflicts(pkg, options = {}) {
|
|
|
488
561
|
...(await integrationFluff(discovered)).map(each => ({
|
|
489
562
|
...each,
|
|
490
563
|
file: fromPackage(each.file),
|
|
564
|
+
message: unnamedShown(each.message),
|
|
491
565
|
})),
|
|
492
566
|
);
|
|
493
567
|
for (const {message} of fluff) {
|