@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4
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 +99 -66
- 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 +16 -2
- package/api/component/component.test.mjs +15 -0
- package/api/discover/_adapter.mjs +36 -9
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/docs/_adapter.mjs +17 -5
- package/api/docs/docs.test.mjs +7 -1
- package/api/doctor/doctor.mjs +86 -12
- package/api/doctor/doctor.test.mjs +28 -3
- 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/authoring-checks.mjs +83 -9
- 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 +226 -32
- package/api/integration/validate-integration.type.d.mts +58 -1
- package/api/integration/validate-integration.type.mjs +45 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +66 -2
- package/api/search/_adapter.mjs +118 -10
- 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 +158 -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 +7 -5
- package/api/search/search.mjs +53 -2483
- 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/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- 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/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/commands/doctor.mjs +30 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- 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/outside-project.test.mjs +2 -1
- package/clients/cli/commands/setup-nudge.test.mjs +61 -0
- 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 +19 -1
- 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 +1010 -99
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +678 -35
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- 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/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 +8 -0
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- 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/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
|
@@ -4,7 +4,7 @@ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import * as os from 'node:os';
|
|
7
|
-
import {findCoreDir, findProjectCoreDir, findProjectRoot, listComponents, discoverExternalPackages, existsCaseExact, findInstalledPackage} from './paths.mjs';
|
|
7
|
+
import {findAstryxProject, findCoreDir, findProjectCoreDir, findProjectRoot, listComponents, discoverExternalPackages, existsCaseExact, findInstalledPackage, findNodeModulesPackage, findPackageRoot, findProjectPackageOf, findWorkspaceRoot} from './paths.mjs';
|
|
8
8
|
import {CLI_ROOT} from './paths.mjs';
|
|
9
9
|
import {linkThisCliAsProjectCli} from '../../test-utils/release-fixtures.mjs';
|
|
10
10
|
|
|
@@ -19,13 +19,93 @@ beforeEach(() => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
afterEach(() => {
|
|
22
|
+
delete process.versions.pnp;
|
|
22
23
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
23
24
|
});
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Write a package.json naming `name` into `dir`, creating the folder.
|
|
28
|
+
* @param {string} dir @param {string} name @param {string} [version]
|
|
29
|
+
*/
|
|
30
|
+
function writePackage(dir, name, version = '0.7.0') {
|
|
31
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
32
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name, version}));
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Write a project package.json into `dir`, creating the folder.
|
|
38
|
+
* @param {string} dir @param {Record<string, unknown>} [fields]
|
|
39
|
+
*/
|
|
40
|
+
function writeProject(dir, fields = {}) {
|
|
41
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
42
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name: path.basename(dir), ...fields}));
|
|
43
|
+
return dir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run `fn` with `home` as the home folder.
|
|
48
|
+
* @template T
|
|
49
|
+
* @param {string} home @param {() => T} fn
|
|
50
|
+
* @returns {T}
|
|
51
|
+
*/
|
|
52
|
+
function withHome(home, fn) {
|
|
53
|
+
const previous = process.env.HOME;
|
|
54
|
+
process.env.HOME = home;
|
|
55
|
+
try {
|
|
56
|
+
return fn();
|
|
57
|
+
} finally {
|
|
58
|
+
if (previous === undefined) delete process.env.HOME;
|
|
59
|
+
else process.env.HOME = previous;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Declares Core, which makes a package.json an Astryx project. */
|
|
64
|
+
const DECLARES_CORE = {dependencies: {'@astryxdesign/core': '^0.7.0'}};
|
|
65
|
+
|
|
66
|
+
/** @param {string} root @param {string} name @param {string} version */
|
|
67
|
+
function install(root, name, version) {
|
|
68
|
+
return writePackage(path.join(root, 'node_modules', ...name.split('/')), name, version);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stand in for the runtime a Yarn Plug'n'Play process exposes:
|
|
73
|
+
* `process.versions.pnp` plus a requirable `pnpapi`. The packages themselves
|
|
74
|
+
* live outside any node_modules, the way Yarn stores them. The top-level
|
|
75
|
+
* afterEach removes `process.versions.pnp` again.
|
|
76
|
+
* @param {Record<string, string>} resolved name -> package directory
|
|
77
|
+
* @returns {string} a project folder inside the PnP runtime
|
|
78
|
+
*/
|
|
79
|
+
function withPnp(resolved) {
|
|
80
|
+
const runtime = path.join(tmpDir, 'runtime');
|
|
81
|
+
const api = path.join(runtime, 'node_modules', 'pnpapi');
|
|
82
|
+
fs.mkdirSync(api, {recursive: true});
|
|
83
|
+
fs.writeFileSync(
|
|
84
|
+
path.join(api, 'package.json'),
|
|
85
|
+
JSON.stringify({name: 'pnpapi', version: '1.0.0', main: 'index.js'}),
|
|
86
|
+
);
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
path.join(api, 'index.js'),
|
|
89
|
+
`const resolved = ${JSON.stringify(resolved)};\n` +
|
|
90
|
+
'module.exports = {\n' +
|
|
91
|
+
' resolveToUnqualified(name) {\n' +
|
|
92
|
+
' if (!(name in resolved)) {\n' +
|
|
93
|
+
" const e = new Error(name); e.code = 'MODULE_NOT_FOUND'; throw e;\n" +
|
|
94
|
+
' }\n' +
|
|
95
|
+
' return resolved[name];\n' +
|
|
96
|
+
' },\n' +
|
|
97
|
+
'};\n',
|
|
98
|
+
);
|
|
99
|
+
Object.defineProperty(process.versions, 'pnp', {
|
|
100
|
+
value: '3',
|
|
101
|
+
configurable: true,
|
|
102
|
+
});
|
|
103
|
+
return runtime;
|
|
104
|
+
}
|
|
105
|
+
|
|
25
106
|
describe('findCoreDir', () => {
|
|
26
107
|
it('finds packages/core walking up directories', () => {
|
|
27
|
-
const coreDir = path.join(tmpDir, 'packages', 'core');
|
|
28
|
-
fs.mkdirSync(coreDir, {recursive: true});
|
|
108
|
+
const coreDir = writePackage(path.join(tmpDir, 'packages', 'core'), '@astryxdesign/core');
|
|
29
109
|
|
|
30
110
|
const nested = path.join(tmpDir, 'sub', 'deep');
|
|
31
111
|
fs.mkdirSync(nested, {recursive: true});
|
|
@@ -34,8 +114,7 @@ describe('findCoreDir', () => {
|
|
|
34
114
|
});
|
|
35
115
|
|
|
36
116
|
it('finds node_modules/@astryxdesign/core fallback', () => {
|
|
37
|
-
const nmCore =
|
|
38
|
-
fs.mkdirSync(nmCore, {recursive: true});
|
|
117
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
39
118
|
|
|
40
119
|
expect(findCoreDir(tmpDir)).toBe(nmCore);
|
|
41
120
|
});
|
|
@@ -48,22 +127,111 @@ describe('findCoreDir', () => {
|
|
|
48
127
|
// This copy's carried Core is the workspace Core beside it.
|
|
49
128
|
expect(findCoreDir(tmpDir)).toBe(path.resolve(CLI_ROOT, '..', 'core'));
|
|
50
129
|
linkThisCliAsProjectCli(tmpDir);
|
|
51
|
-
|
|
130
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
131
|
+
expect(findProjectCoreDir(tmpDir)).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// A route folder such as src/app/(shop)/orders/[id]/edit sits six folders
|
|
135
|
+
// below the project root, where Core is installed.
|
|
136
|
+
it('finds Core from any depth inside the project that installs it', () => {
|
|
137
|
+
writeProject(tmpDir, {dependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
138
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
139
|
+
const route = path.join(tmpDir, 'src', 'app', '(shop)', 'orders', '[id]', 'edit');
|
|
140
|
+
fs.mkdirSync(route, {recursive: true});
|
|
141
|
+
|
|
142
|
+
expect(findCoreDir(route)).toBe(nmCore);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('searches a workspace up to its root from any depth in a member', () => {
|
|
146
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
147
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
148
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), {dependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
149
|
+
const route = path.join(member, 'src', 'app', '(shop)', 'orders', '[id]');
|
|
150
|
+
fs.mkdirSync(route, {recursive: true});
|
|
151
|
+
|
|
152
|
+
expect(findCoreDir(route)).toBe(nmCore);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('searches only the folder and the four above it outside any Astryx project', () => {
|
|
156
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
157
|
+
const project = writeProject(path.join(tmpDir, 'a', 'b', 'c', 'd', 'e', 'app'));
|
|
158
|
+
|
|
159
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
160
|
+
expect(findProjectCoreDir(project)).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// A package.json that only sets the module type is not the project, so a
|
|
164
|
+
// folder deep below it still reaches the Core the project installs.
|
|
165
|
+
it('looks past a nested package.json that declares no Astryx package', () => {
|
|
166
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
167
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
168
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
|
|
169
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'package.json'), JSON.stringify({type: 'module'}));
|
|
170
|
+
const deep = path.join(tmpDir, 'src', 'l1', 'l2', 'l3', 'l4', 'l5', 'l6');
|
|
171
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
172
|
+
|
|
173
|
+
expect(findCoreDir(deep)).toBe(nmCore);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Past the reach the search starts at the project root: a tool folder
|
|
177
|
+
// between them that installs its own Core but declares nothing never
|
|
178
|
+
// answers for the project, as `upgrade` targets the project's Core.
|
|
179
|
+
it('answers the project\'s Core, not a tool folder\'s, from deep inside the tool', () => {
|
|
180
|
+
const app = path.join(tmpDir, 'app');
|
|
181
|
+
writeProject(app, DECLARES_CORE);
|
|
182
|
+
const projectCore = install(app, '@astryxdesign/core', '0.7.0');
|
|
183
|
+
const tool = path.join(app, 'tools', 'codegen');
|
|
184
|
+
writePackage(tool, 'codegen');
|
|
185
|
+
install(tool, '@astryxdesign/core', '0.9.0');
|
|
186
|
+
const deep = path.join(tool, 'src', 'a', 'b', 'c', 'd', 'e');
|
|
187
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
188
|
+
|
|
189
|
+
expect(findProjectCoreDir(deep)).toBe(projectCore);
|
|
190
|
+
expect(findInstalledPackage(path.join(deep, 'f'), '@astryxdesign/core')).toBe(projectCore);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Node loads the hoisted copy whatever the member declares: an override or
|
|
194
|
+
// resolution can force it, and a peer range can be stale. Reads answer from
|
|
195
|
+
// the copy that loads.
|
|
196
|
+
it.each([
|
|
197
|
+
['a range the copy is outside of', {devDependencies: {'@astryxdesign/core': '~0.6.8'}}],
|
|
198
|
+
['only a stale peer range', {peerDependencies: {'@astryxdesign/core': '^0.6.8'}}],
|
|
199
|
+
])('takes a workspace Core when the member declares %s', (_label, fields) => {
|
|
200
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*'], overrides: {'@astryxdesign/core': '0.7.1'}});
|
|
201
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
202
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), fields);
|
|
203
|
+
|
|
204
|
+
expect(findCoreDir(member)).toBe(nmCore);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('skips a packages/core folder that holds another package', () => {
|
|
208
|
+
writePackage(path.join(tmpDir, 'packages', 'core'), '@acme/core', '1.0.0');
|
|
209
|
+
const app = path.join(tmpDir, 'apps', 'web');
|
|
210
|
+
fs.mkdirSync(app, {recursive: true});
|
|
211
|
+
|
|
212
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
213
|
+
expect(findProjectCoreDir(app)).toBeNull();
|
|
214
|
+
|
|
215
|
+
const nmCore = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
216
|
+
expect(findCoreDir(app)).toBe(nmCore);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('skips a node_modules/@astryxdesign/core folder with no package.json', () => {
|
|
220
|
+
fs.mkdirSync(path.join(tmpDir, 'node_modules', '@astryxdesign', 'core'), {recursive: true});
|
|
221
|
+
|
|
222
|
+
// The project's own Core: findCoreDir falls back to the carried one.
|
|
223
|
+
expect(findProjectCoreDir(tmpDir)).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("asks Yarn Plug'n'Play when no folder holds Core", () => {
|
|
227
|
+
const store = writePackage(path.join(tmpDir, 'store', '@astryxdesign-core'), '@astryxdesign/core');
|
|
228
|
+
const project = withPnp({'@astryxdesign/core': store});
|
|
229
|
+
|
|
230
|
+
expect(findCoreDir(project)).toBe(store);
|
|
52
231
|
});
|
|
53
232
|
});
|
|
54
233
|
|
|
55
234
|
describe('findInstalledPackage', () => {
|
|
56
|
-
/** @param {string} root @param {string} name @param {string} version */
|
|
57
|
-
function install(root, name, version) {
|
|
58
|
-
const dir = path.join(root, 'node_modules', ...name.split('/'));
|
|
59
|
-
fs.mkdirSync(dir, {recursive: true});
|
|
60
|
-
fs.writeFileSync(
|
|
61
|
-
path.join(dir, 'package.json'),
|
|
62
|
-
JSON.stringify({name, version}),
|
|
63
|
-
);
|
|
64
|
-
return dir;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
235
|
it('finds a package in the node_modules chain above startDir', () => {
|
|
68
236
|
const installed = install(tmpDir, '@astryxdesign/core', '0.6.0');
|
|
69
237
|
const nested = path.join(tmpDir, 'src', 'deep');
|
|
@@ -72,6 +240,22 @@ describe('findInstalledPackage', () => {
|
|
|
72
240
|
expect(findInstalledPackage(nested, '@astryxdesign/core')).toBe(installed);
|
|
73
241
|
});
|
|
74
242
|
|
|
243
|
+
it('finds a package from any depth inside the project', () => {
|
|
244
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
245
|
+
const installed = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
246
|
+
const deep = path.join(tmpDir, 'apps', 'web', 'src', 'app', '(shop)', 'orders', '[id]');
|
|
247
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
248
|
+
|
|
249
|
+
expect(findInstalledPackage(deep, '@astryxdesign/core')).toBe(installed);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('does not search past six folders outside the project', () => {
|
|
253
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
254
|
+
const project = writeProject(path.join(tmpDir, 'a', 'b', 'c', 'd', 'e', 'f', 'app'));
|
|
255
|
+
|
|
256
|
+
expect(findInstalledPackage(project, '@astryxdesign/core')).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
75
259
|
it('reports a package the project never installed as missing', () => {
|
|
76
260
|
expect(findInstalledPackage(tmpDir, '@astryxdesign/core')).toBeNull();
|
|
77
261
|
});
|
|
@@ -96,43 +280,6 @@ describe('findInstalledPackage', () => {
|
|
|
96
280
|
});
|
|
97
281
|
|
|
98
282
|
describe('under Yarn Plug\'n\'Play, which has no node_modules', () => {
|
|
99
|
-
/**
|
|
100
|
-
* Stand in for the runtime a PnP process exposes: `process.versions.pnp`
|
|
101
|
-
* plus a requirable `pnpapi`. The package itself lives outside any
|
|
102
|
-
* node_modules, the way Yarn stores it.
|
|
103
|
-
* @param {Record<string, string>} resolved name -> package directory
|
|
104
|
-
*/
|
|
105
|
-
function withPnp(resolved) {
|
|
106
|
-
const runtime = path.join(tmpDir, 'runtime');
|
|
107
|
-
const api = path.join(runtime, 'node_modules', 'pnpapi');
|
|
108
|
-
fs.mkdirSync(api, {recursive: true});
|
|
109
|
-
fs.writeFileSync(
|
|
110
|
-
path.join(api, 'package.json'),
|
|
111
|
-
JSON.stringify({name: 'pnpapi', version: '1.0.0', main: 'index.js'}),
|
|
112
|
-
);
|
|
113
|
-
fs.writeFileSync(
|
|
114
|
-
path.join(api, 'index.js'),
|
|
115
|
-
`const resolved = ${JSON.stringify(resolved)};\n` +
|
|
116
|
-
'module.exports = {\n' +
|
|
117
|
-
' resolveToUnqualified(name) {\n' +
|
|
118
|
-
' if (!(name in resolved)) {\n' +
|
|
119
|
-
" const e = new Error(name); e.code = 'MODULE_NOT_FOUND'; throw e;\n" +
|
|
120
|
-
' }\n' +
|
|
121
|
-
' return resolved[name];\n' +
|
|
122
|
-
' },\n' +
|
|
123
|
-
'};\n',
|
|
124
|
-
);
|
|
125
|
-
Object.defineProperty(process.versions, 'pnp', {
|
|
126
|
-
value: '3',
|
|
127
|
-
configurable: true,
|
|
128
|
-
});
|
|
129
|
-
return runtime;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
afterEach(() => {
|
|
133
|
-
delete process.versions.pnp;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
283
|
it('falls back to the PnP runtime when there is no chain to walk', () => {
|
|
137
284
|
const store = path.join(tmpDir, 'store', '@astryxdesign-core');
|
|
138
285
|
fs.mkdirSync(store, {recursive: true});
|
|
@@ -153,6 +300,235 @@ describe('findInstalledPackage', () => {
|
|
|
153
300
|
});
|
|
154
301
|
});
|
|
155
302
|
|
|
303
|
+
describe('findProjectPackageOf', () => {
|
|
304
|
+
const NAMES = ['@astryxdesign/core', '@xds/core'];
|
|
305
|
+
|
|
306
|
+
it('takes the earlier name when one folder installs both', () => {
|
|
307
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
308
|
+
const current = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
309
|
+
install(tmpDir, '@xds/core', '0.3.0');
|
|
310
|
+
|
|
311
|
+
expect(findProjectPackageOf(tmpDir, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: current});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('takes the nearest folder over an earlier name further up', () => {
|
|
315
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
316
|
+
install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
317
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'legacy'), {dependencies: {'@xds/core': '0.3.0'}});
|
|
318
|
+
const legacy = install(member, '@xds/core', '0.3.0');
|
|
319
|
+
|
|
320
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@xds/core', packageDir: legacy});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('returns null when none is installed', () => {
|
|
324
|
+
expect(findProjectPackageOf(tmpDir, NAMES)).toBeNull();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('finds the package from a subfolder of the project', () => {
|
|
328
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
329
|
+
const current = install(tmpDir, '@astryxdesign/core', '0.7.0');
|
|
330
|
+
const deep = path.join(tmpDir, 'src', 'l1', 'l2');
|
|
331
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
332
|
+
|
|
333
|
+
expect(findProjectPackageOf(deep, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: current});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// A member uses what its workspace installs, whatever its own package.json
|
|
337
|
+
// says: an override or resolution can force the copy, and a range or peer
|
|
338
|
+
// range can be stale. Membership decides, not the declared version.
|
|
339
|
+
it.each([
|
|
340
|
+
['~0.6.8', 'dependencies'],
|
|
341
|
+
['0.7.0 - 0.8.0', 'dependencies'],
|
|
342
|
+
['>= 0.7.0', 'dependencies'],
|
|
343
|
+
['^0.6.8', 'peerDependencies'],
|
|
344
|
+
])('takes the hoisted package for a member that declares %s in %s', (range, field) => {
|
|
345
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
346
|
+
const hoisted = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
347
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), {[field]: {'@astryxdesign/core': range}});
|
|
348
|
+
|
|
349
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: hoisted});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it.each([
|
|
353
|
+
['Yarn object-form workspaces', {workspaces: {packages: ['apps/*']}}, null],
|
|
354
|
+
['a ** pattern', {workspaces: ['apps/**']}, null],
|
|
355
|
+
['a brace pattern', {workspaces: ['{apps,tools}/*']}, null],
|
|
356
|
+
['a brace list of globs', {workspaces: ['{apps/*,tools/*}']}, null],
|
|
357
|
+
['a nested brace list', {workspaces: ['{apps/{web,admin},tools/*}']}, null],
|
|
358
|
+
['a pnpm-workspace.yaml block list', {}, "packages:\n - 'apps/*' # apps\n - \"tools/*\"\nonlyBuiltDependencies: []\n"],
|
|
359
|
+
['a pnpm-workspace.yaml flow list', {}, "packages: ['tools/*', apps/*]\n"],
|
|
360
|
+
])('takes the hoisted package for a member that %s lists', (_label, rootFields, yaml) => {
|
|
361
|
+
writeProject(tmpDir, {private: true, ...rootFields});
|
|
362
|
+
if (yaml) fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), yaml);
|
|
363
|
+
const hoisted = install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
364
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'), DECLARES_CORE);
|
|
365
|
+
|
|
366
|
+
expect(findProjectPackageOf(member, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: hoisted});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it.each([
|
|
370
|
+
['lists other folders', {workspaces: ['packages/*']}],
|
|
371
|
+
['excludes it with a ! pattern', {workspaces: ['apps/*', '!apps/web']}],
|
|
372
|
+
['lists other folders in a brace list of globs', {workspaces: ['{packages/*,tools/*}']}],
|
|
373
|
+
])('stops at the project root when the workspace %s', (_label, rootFields) => {
|
|
374
|
+
writeProject(tmpDir, {private: true, ...rootFields});
|
|
375
|
+
install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
376
|
+
const project = writeProject(path.join(tmpDir, 'apps', 'web'), DECLARES_CORE);
|
|
377
|
+
|
|
378
|
+
expect(findProjectPackageOf(project, NAMES)).toBeNull();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// AST-040 FR11: a Plug'n'Play project that declares Core keeps no
|
|
382
|
+
// node_modules copy; the runtime answers for it.
|
|
383
|
+
it("asks Yarn Plug'n'Play for a project that declares Core and has no node_modules copy", () => {
|
|
384
|
+
const store = writePackage(path.join(tmpDir, 'store', '@astryxdesign-core'), '@astryxdesign/core', '0.7.1');
|
|
385
|
+
const project = withPnp({'@astryxdesign/core': store});
|
|
386
|
+
writeProject(project, DECLARES_CORE);
|
|
387
|
+
|
|
388
|
+
expect(fs.existsSync(path.join(project, 'node_modules', '@astryxdesign'))).toBe(false);
|
|
389
|
+
expect(findProjectPackageOf(project, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: store});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// A project nested in another, outside any workspace, has its own install.
|
|
393
|
+
it('stops at the root of a nested project outside any workspace', () => {
|
|
394
|
+
writeProject(tmpDir, {dependencies: {'@astryxdesign/core': '0.8.0'}});
|
|
395
|
+
install(tmpDir, '@astryxdesign/core', '0.8.0');
|
|
396
|
+
const nested = writeProject(path.join(tmpDir, 'apps', 'web'), {dependencies: {'@astryxdesign/core': '0.9.0'}});
|
|
397
|
+
|
|
398
|
+
expect(findProjectPackageOf(nested, NAMES)).toBeNull();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// With no Astryx project, released versions read only this folder's own
|
|
402
|
+
// node_modules.
|
|
403
|
+
it('reads only the folder itself when no Astryx project contains it', () => {
|
|
404
|
+
writeProject(tmpDir);
|
|
405
|
+
install(tmpDir, '@astryxdesign/core', '0.7.1');
|
|
406
|
+
const plain = writeProject(path.join(tmpDir, 'plain'));
|
|
407
|
+
|
|
408
|
+
expect(findProjectPackageOf(plain, NAMES)).toBeNull();
|
|
409
|
+
const own = install(plain, '@astryxdesign/core', '0.7.1');
|
|
410
|
+
expect(findProjectPackageOf(plain, NAMES)).toEqual({name: '@astryxdesign/core', packageDir: own});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('findAstryxProject', () => {
|
|
415
|
+
it('is the nearest folder whose package.json declares an Astryx package', () => {
|
|
416
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
417
|
+
const tool = writeProject(path.join(tmpDir, 'tools', 'lint'));
|
|
418
|
+
const deep = path.join(tool, 'src');
|
|
419
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
420
|
+
|
|
421
|
+
expect(findAstryxProject(deep)).toBe(tmpDir);
|
|
422
|
+
const app = writeProject(path.join(tmpDir, 'apps', 'web'), {devDependencies: {'@astryxdesign/cli': '0.7.1'}});
|
|
423
|
+
expect(findAstryxProject(app)).toBe(app);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('counts a peer declaration and the legacy scope', () => {
|
|
427
|
+
const integration = writeProject(path.join(tmpDir, 'integration'), {peerDependencies: {'@astryxdesign/core': '^0.7.0'}});
|
|
428
|
+
const legacy = writeProject(path.join(tmpDir, 'legacy'), {dependencies: {'@xds/core': '0.3.0'}});
|
|
429
|
+
|
|
430
|
+
expect(findAstryxProject(integration)).toBe(integration);
|
|
431
|
+
expect(findAstryxProject(legacy)).toBe(legacy);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('stops at a repository root', () => {
|
|
435
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
436
|
+
const repo = writeProject(path.join(tmpDir, 'repo'));
|
|
437
|
+
fs.mkdirSync(path.join(repo, '.git'));
|
|
438
|
+
const tools = path.join(repo, 'tools');
|
|
439
|
+
fs.mkdirSync(tools, {recursive: true});
|
|
440
|
+
|
|
441
|
+
expect(findAstryxProject(tools)).toBeNull();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('never counts the home folder', () => {
|
|
445
|
+
writeProject(tmpDir, DECLARES_CORE);
|
|
446
|
+
const desktop = path.join(tmpDir, 'Desktop');
|
|
447
|
+
fs.mkdirSync(desktop, {recursive: true});
|
|
448
|
+
|
|
449
|
+
withHome(tmpDir, () => {
|
|
450
|
+
expect(findAstryxProject(desktop)).toBeNull();
|
|
451
|
+
expect(findAstryxProject(tmpDir)).toBeNull();
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('returns null when no folder declares Astryx', () => {
|
|
456
|
+
writeProject(tmpDir);
|
|
457
|
+
|
|
458
|
+
expect(findAstryxProject(tmpDir)).toBeNull();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('counts a member its workspace lists when the workspace root declares Astryx', () => {
|
|
462
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*'], ...DECLARES_CORE});
|
|
463
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
464
|
+
const tests = writeProject(path.join(member, 'e2e'));
|
|
465
|
+
|
|
466
|
+
expect(findAstryxProject(path.join(member, 'src'))).toBe(member);
|
|
467
|
+
expect(findAstryxProject(tests)).toBe(member);
|
|
468
|
+
const other = writeProject(path.join(tmpDir, 'tools', 'lint'));
|
|
469
|
+
expect(findAstryxProject(other)).toBe(tmpDir);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('findPackageRoot', () => {
|
|
474
|
+
it('finds the nearest package.json at or above the folder', () => {
|
|
475
|
+
writeProject(tmpDir);
|
|
476
|
+
const app = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
477
|
+
const deep = path.join(app, 'src', 'app');
|
|
478
|
+
fs.mkdirSync(deep, {recursive: true});
|
|
479
|
+
|
|
480
|
+
expect(findPackageRoot(deep)).toBe(app);
|
|
481
|
+
expect(findPackageRoot(tmpDir)).toBe(tmpDir);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('returns null with no package.json', () => {
|
|
485
|
+
expect(findPackageRoot(tmpDir)).toBeNull();
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
describe('findWorkspaceRoot', () => {
|
|
490
|
+
it('finds the nearest folder that declares workspaces in package.json', () => {
|
|
491
|
+
writeProject(tmpDir, {private: true, workspaces: ['apps/*']});
|
|
492
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
493
|
+
|
|
494
|
+
expect(findWorkspaceRoot(path.join(member, 'src'))).toBe(tmpDir);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('finds the nearest folder with pnpm-workspace.yaml', () => {
|
|
498
|
+
writeProject(tmpDir);
|
|
499
|
+
fs.writeFileSync(path.join(tmpDir, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
|
500
|
+
const member = writeProject(path.join(tmpDir, 'apps', 'web'));
|
|
501
|
+
|
|
502
|
+
expect(findWorkspaceRoot(member)).toBe(tmpDir);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('is the nearest package.json outside a workspace, and the folder itself with none', () => {
|
|
506
|
+
const app = writeProject(path.join(tmpDir, 'app'));
|
|
507
|
+
const src = path.join(app, 'src');
|
|
508
|
+
fs.mkdirSync(src, {recursive: true});
|
|
509
|
+
|
|
510
|
+
expect(findWorkspaceRoot(src)).toBe(app);
|
|
511
|
+
expect(findWorkspaceRoot(tmpDir)).toBe(tmpDir);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('findNodeModulesPackage', () => {
|
|
516
|
+
it('finds a package hoisted to a workspace root and names the folder that holds it', () => {
|
|
517
|
+
const hoisted = install(tmpDir, '@acme/widgets', '1.0.0');
|
|
518
|
+
const member = path.join(tmpDir, 'apps', 'web');
|
|
519
|
+
fs.mkdirSync(member, {recursive: true});
|
|
520
|
+
|
|
521
|
+
expect(findNodeModulesPackage(member, '@acme/widgets')).toEqual({packageDir: hoisted, hostDir: tmpDir});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('does not ask Plug\'n\'Play', () => {
|
|
525
|
+
const store = writePackage(path.join(tmpDir, 'store', 'acme-widgets'), '@acme/widgets');
|
|
526
|
+
const project = withPnp({'@acme/widgets': store});
|
|
527
|
+
|
|
528
|
+
expect(findNodeModulesPackage(project, '@acme/widgets')).toBeNull();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
156
532
|
describe('findProjectRoot', () => {
|
|
157
533
|
it('finds root with workspaces in package.json', () => {
|
|
158
534
|
fs.writeFileSync(
|
|
@@ -21,7 +21,8 @@ export function readDeclaredDependencies(projectDir: string): Array<{
|
|
|
21
21
|
*
|
|
22
22
|
* Walking up matters because hoisting is normal — yarn and npm workspaces lift
|
|
23
23
|
* a workspace member's dependency to the repo root, so the app's own
|
|
24
|
-
* node_modules can be empty of a package it genuinely declares.
|
|
24
|
+
* node_modules can be empty of a package it genuinely declares. Integrations
|
|
25
|
+
* named in astryx.config resolve through the same walk.
|
|
25
26
|
*
|
|
26
27
|
* Presence is decided by the package's own package.json, not the directory, so
|
|
27
28
|
* a stale empty directory is not mistaken for an install.
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
import * as fs from 'node:fs';
|
|
41
41
|
import * as path from 'node:path';
|
|
42
|
+
import {findNodeModulesPackage} from '../fs/paths.mjs';
|
|
42
43
|
import {findManifestPaths, loadIntegrations} from './integrations.mjs';
|
|
43
44
|
import {resolveProviders} from './provider-resolution.mjs';
|
|
44
45
|
|
|
@@ -123,7 +124,8 @@ export function readDeclaredDependencies(projectDir) {
|
|
|
123
124
|
*
|
|
124
125
|
* Walking up matters because hoisting is normal — yarn and npm workspaces lift
|
|
125
126
|
* a workspace member's dependency to the repo root, so the app's own
|
|
126
|
-
* node_modules can be empty of a package it genuinely declares.
|
|
127
|
+
* node_modules can be empty of a package it genuinely declares. Integrations
|
|
128
|
+
* named in astryx.config resolve through the same walk.
|
|
127
129
|
*
|
|
128
130
|
* Presence is decided by the package's own package.json, not the directory, so
|
|
129
131
|
* a stale empty directory is not mistaken for an install.
|
|
@@ -135,18 +137,7 @@ export function readDeclaredDependencies(projectDir) {
|
|
|
135
137
|
*/
|
|
136
138
|
export function resolveInstalledPackageDir(name, fromDir) {
|
|
137
139
|
if (!isBarePackageName(name)) return null;
|
|
138
|
-
|
|
139
|
-
let dir = path.resolve(fromDir);
|
|
140
|
-
for (let i = 0; i < 50; i++) {
|
|
141
|
-
const packageDir = path.join(dir, 'node_modules', ...segments);
|
|
142
|
-
if (fs.existsSync(path.join(packageDir, 'package.json'))) {
|
|
143
|
-
return {packageDir, hostDir: dir};
|
|
144
|
-
}
|
|
145
|
-
const parent = path.dirname(dir);
|
|
146
|
-
if (parent === dir) break;
|
|
147
|
-
dir = parent;
|
|
148
|
-
}
|
|
149
|
-
return null;
|
|
140
|
+
return findNodeModulesPackage(fromDir, name);
|
|
150
141
|
}
|
|
151
142
|
|
|
152
143
|
/**
|
|
@@ -34,15 +34,18 @@ export function computerInstallRoots(cliRoot: string, options?: {
|
|
|
34
34
|
export function peerProblem(pkg: Record<string, any>, release: string): string | null;
|
|
35
35
|
/**
|
|
36
36
|
* The computer-level integrations installed beside the CLI at `cliRoot`, in
|
|
37
|
-
* name order (declaration order when the root declares them)
|
|
37
|
+
* name order (declaration order when the root declares them), then each
|
|
38
|
+
* integration a host approves (`approved`, architecture:cli-surface INV30),
|
|
39
|
+
* resolved by name from the package set that holds that CLI alone. Reads
|
|
38
40
|
* package.json files only.
|
|
39
|
-
* @param {{cliRoot: string, release: string, cacheDir?: string}} options
|
|
41
|
+
* @param {{cliRoot: string, release: string, cacheDir?: string, approved?: readonly string[]}} options
|
|
40
42
|
* @returns {ComputerIntegrationCandidate[]}
|
|
41
43
|
*/
|
|
42
|
-
export function scanComputerIntegrations({ cliRoot, release, cacheDir }: {
|
|
44
|
+
export function scanComputerIntegrations({ cliRoot, release, cacheDir, approved, }: {
|
|
43
45
|
cliRoot: string;
|
|
44
46
|
release: string;
|
|
45
47
|
cacheDir?: string;
|
|
48
|
+
approved?: readonly string[];
|
|
46
49
|
}): ComputerIntegrationCandidate[];
|
|
47
50
|
/**
|
|
48
51
|
* Load the compatible computer-level integrations as provider candidates,
|