@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
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
discoverRegistryReceipts,
|
|
20
20
|
reconcileRegistryCompositions,
|
|
21
21
|
} from './registry.mjs';
|
|
22
|
+
import {upgrade} from '../upgrade.mjs';
|
|
23
|
+
import {logger} from '../../logger.mjs';
|
|
22
24
|
|
|
23
25
|
const ITEM = {
|
|
24
26
|
name: 'example-button-basic',
|
|
@@ -824,3 +826,86 @@ describe('registry composition upgrades', () => {
|
|
|
824
826
|
expect(fs.readFileSync(sourcePath, 'utf8')).toBe('old\n');
|
|
825
827
|
});
|
|
826
828
|
});
|
|
829
|
+
|
|
830
|
+
describe('copied-composition paths name files from the folder the command ran in', () => {
|
|
831
|
+
it('below the project root, and from the scanned source folder at the root', async () => {
|
|
832
|
+
const {sourcePath} = seed({
|
|
833
|
+
base: 'value = 1\n',
|
|
834
|
+
current: 'value = local\n',
|
|
835
|
+
});
|
|
836
|
+
const fetchImpl = remote('value = upstream\n');
|
|
837
|
+
const origin = path.join(project, 'apps', 'web');
|
|
838
|
+
|
|
839
|
+
const [atRoot] = (await reconcile({}, {fetchImpl})).summary.items[0].files;
|
|
840
|
+
const [below] = (await reconcile({}, {fetchImpl, origin})).summary.items[0]
|
|
841
|
+
.files;
|
|
842
|
+
|
|
843
|
+
expect(atRoot).toMatchObject({
|
|
844
|
+
path: SOURCE_TARGET,
|
|
845
|
+
conflictFile: `${SOURCE_TARGET}.astryx-conflict`,
|
|
846
|
+
});
|
|
847
|
+
expect(below).toMatchObject({
|
|
848
|
+
path: `../../src/${SOURCE_TARGET}`,
|
|
849
|
+
conflictFile: `../../src/${SOURCE_TARGET}.astryx-conflict`,
|
|
850
|
+
});
|
|
851
|
+
expect(path.resolve(origin, below.path)).toBe(sourcePath);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('for an invalid receipt too', async () => {
|
|
855
|
+
const value = receipt('base\n');
|
|
856
|
+
value.files[0].sha256 = '0'.repeat(64);
|
|
857
|
+
seed({base: 'base\n', receiptValue: value});
|
|
858
|
+
const fetchImpl = remote('new\n');
|
|
859
|
+
const origin = path.join(project, 'docs');
|
|
860
|
+
|
|
861
|
+
const [atRoot] = (await reconcile({}, {fetchImpl})).summary.items;
|
|
862
|
+
const [below] = (await reconcile({}, {fetchImpl, origin})).summary.items;
|
|
863
|
+
|
|
864
|
+
expect(atRoot.message).toContain(`Invalid receipt ${RECEIPT_TARGET}:`);
|
|
865
|
+
expect(below.message).toContain(
|
|
866
|
+
`Invalid receipt ../src/${RECEIPT_TARGET}:`,
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('through upgrade(), with --registry and in a run, which name the project root', async () => {
|
|
871
|
+
fs.writeFileSync(
|
|
872
|
+
path.join(project, 'package.json'),
|
|
873
|
+
JSON.stringify({
|
|
874
|
+
name: 'app',
|
|
875
|
+
dependencies: {'@astryxdesign/core': '0.6.0'},
|
|
876
|
+
}),
|
|
877
|
+
);
|
|
878
|
+
const core = path.join(project, 'node_modules', '@astryxdesign', 'core');
|
|
879
|
+
fs.mkdirSync(core, {recursive: true});
|
|
880
|
+
fs.writeFileSync(
|
|
881
|
+
path.join(core, 'package.json'),
|
|
882
|
+
JSON.stringify({name: '@astryxdesign/core', version: '0.6.0'}),
|
|
883
|
+
);
|
|
884
|
+
const frontend = path.join(project, 'frontend');
|
|
885
|
+
fs.mkdirSync(frontend);
|
|
886
|
+
seed({base: 'old\n'});
|
|
887
|
+
vi.stubGlobal('fetch', remote('new\n'));
|
|
888
|
+
const log = vi.spyOn(logger, 'log');
|
|
889
|
+
try {
|
|
890
|
+
const registry = await upgrade({registry: true}, {cwd: frontend});
|
|
891
|
+
const run = await upgrade({from: '0.6.0'}, {cwd: frontend});
|
|
892
|
+
const atRoot = await upgrade({registry: true}, {cwd: project});
|
|
893
|
+
|
|
894
|
+
expect(registry.data.items[0].files[0].path).toBe(
|
|
895
|
+
`../src/${SOURCE_TARGET}`,
|
|
896
|
+
);
|
|
897
|
+
expect(run.data.registryCompositions.items[0].files[0].path).toBe(
|
|
898
|
+
`../src/${SOURCE_TARGET}`,
|
|
899
|
+
);
|
|
900
|
+
expect(atRoot.data.items[0].files[0].path).toBe(SOURCE_TARGET);
|
|
901
|
+
expect(
|
|
902
|
+
log.mock.calls.filter(([line]) =>
|
|
903
|
+
String(line).startsWith('Project root:'),
|
|
904
|
+
),
|
|
905
|
+
).toEqual([['Project root: ..'], ['Project root: ..']]);
|
|
906
|
+
} finally {
|
|
907
|
+
vi.unstubAllGlobals();
|
|
908
|
+
log.mockRestore();
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
});
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* on failure. Progress is emitted through the shared `logger` (silent by default).
|
|
8
8
|
*
|
|
9
9
|
* @param {import('../_adapter.mjs').UpgradeOptions} [options]
|
|
10
|
-
* @param {{cwd?: string}} [ctx]
|
|
10
|
+
* @param {{cwd?: string, origin?: string}} [ctx] `origin` is the folder the
|
|
11
|
+
* command ran in, when `cwd` is the project root above it
|
|
11
12
|
* @returns {Promise<import('../upgrade.type.mjs').UpgradeStatusResponse | import('../upgrade.type.mjs').UpgradeRunResponse>}
|
|
12
13
|
*/
|
|
13
|
-
export function run(options?: import("../_adapter.mjs").UpgradeOptions, { cwd }?: {
|
|
14
|
+
export function run(options?: import("../_adapter.mjs").UpgradeOptions, { cwd, origin }?: {
|
|
14
15
|
cwd?: string;
|
|
16
|
+
origin?: string;
|
|
15
17
|
}): Promise<import("../upgrade.type.mjs").UpgradeStatusResponse | import("../upgrade.type.mjs").UpgradeRunResponse>;
|
package/api/upgrade/run/run.mjs
CHANGED
|
@@ -115,10 +115,11 @@ function loadFileProtection(cwd) {
|
|
|
115
115
|
* on failure. Progress is emitted through the shared `logger` (silent by default).
|
|
116
116
|
*
|
|
117
117
|
* @param {import('../_adapter.mjs').UpgradeOptions} [options]
|
|
118
|
-
* @param {{cwd?: string}} [ctx]
|
|
118
|
+
* @param {{cwd?: string, origin?: string}} [ctx] `origin` is the folder the
|
|
119
|
+
* command ran in, when `cwd` is the project root above it
|
|
119
120
|
* @returns {Promise<import('../upgrade.type.mjs').UpgradeStatusResponse | import('../upgrade.type.mjs').UpgradeRunResponse>}
|
|
120
121
|
*/
|
|
121
|
-
export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
122
|
+
export async function run(options = {}, {cwd = process.cwd(), origin} = {}) {
|
|
122
123
|
// Resolve the source dir against the API's cwd (not process.cwd()) so a
|
|
123
124
|
// programmatic caller in another directory scans the right tree. Confine it to
|
|
124
125
|
// cwd: --apply rewrites files in place, so a `..`-escaping or out-of-tree
|
|
@@ -152,6 +153,12 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
152
153
|
const sourcePathFound = fs.existsSync(path_);
|
|
153
154
|
|
|
154
155
|
const currentVersion = /** @type {string} */ (options.from);
|
|
156
|
+
// Named first, so a run that stops on the Core lookup below still says which
|
|
157
|
+
// folder it searched (spec:AST-040 FR12).
|
|
158
|
+
if (origin != null) {
|
|
159
|
+
logger.log(`Project root: ${path.relative(origin, cwd) || '.'}`);
|
|
160
|
+
logger.log(`Source folder: ${path.relative(origin, path_) || '.'}`);
|
|
161
|
+
}
|
|
155
162
|
const installed = detectInstalledTargetVersion(cwd);
|
|
156
163
|
if (!installed) {
|
|
157
164
|
// Outside a project there is no version to install (spec AST-081 FR3).
|
|
@@ -170,6 +177,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
170
177
|
{apply, path: path_},
|
|
171
178
|
{
|
|
172
179
|
cwd,
|
|
180
|
+
origin,
|
|
173
181
|
expectedVersion:
|
|
174
182
|
installed.packageName === '@astryxdesign/core'
|
|
175
183
|
? targetVersion
|
|
@@ -192,6 +200,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
192
200
|
const registryResult = await reconcileCompositions();
|
|
193
201
|
const agentDocsPlan = await prepareAgentDocsRefresh({
|
|
194
202
|
cwd,
|
|
203
|
+
origin,
|
|
195
204
|
installedVersion: targetVersion,
|
|
196
205
|
apply,
|
|
197
206
|
});
|
|
@@ -396,6 +405,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
396
405
|
// No codemods in range — safe to render agent docs from current state.
|
|
397
406
|
const agentDocsPlan = await prepareAgentDocsRefresh({
|
|
398
407
|
cwd,
|
|
408
|
+
origin,
|
|
399
409
|
installedVersion: targetVersion,
|
|
400
410
|
apply,
|
|
401
411
|
});
|
|
@@ -671,6 +681,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
671
681
|
if (protectedFiles.length === 0) {
|
|
672
682
|
const agentDocsPlan = await prepareAgentDocsRefresh({
|
|
673
683
|
cwd,
|
|
684
|
+
origin,
|
|
674
685
|
installedVersion: targetVersion,
|
|
675
686
|
apply,
|
|
676
687
|
fresh: true,
|
|
@@ -3,18 +3,41 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file Colocated tests for the upgrade.run leaf — the --path scan dir must be
|
|
5
5
|
* confined to cwd (upgrade rewrites source in place with --apply, so an escaping
|
|
6
|
-
* or out-of-tree path must be rejected),
|
|
7
|
-
* dir existed at all
|
|
6
|
+
* or out-of-tree path must be rejected), the receipt must say whether that
|
|
7
|
+
* dir existed at all, a run from any folder in a project upgrades that
|
|
8
|
+
* project (the nearest folder whose package.json declares Astryx, never the
|
|
9
|
+
* home folder) and names files relative to the folder it ran in, and the
|
|
10
|
+
* target Core is the project's own.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
|
-
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
13
|
+
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
11
14
|
import * as fs from 'node:fs';
|
|
12
15
|
import * as os from 'node:os';
|
|
13
16
|
import * as path from 'node:path';
|
|
14
17
|
import {upgrade} from '../upgrade.mjs';
|
|
18
|
+
import {relativeToOrigin} from '../_adapter.mjs';
|
|
19
|
+
import {logger} from '../../logger.mjs';
|
|
20
|
+
import {generateCompressedIndex} from '../../../foundation/agent-docs/agent-docs.mjs';
|
|
15
21
|
|
|
16
22
|
const SLOW = 30_000;
|
|
17
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Run `fn` with `home` as the home folder.
|
|
26
|
+
* @template T
|
|
27
|
+
* @param {string} home @param {() => Promise<T>} fn
|
|
28
|
+
* @returns {Promise<T>}
|
|
29
|
+
*/
|
|
30
|
+
async function withHome(home, fn) {
|
|
31
|
+
const previous = process.env.HOME;
|
|
32
|
+
process.env.HOME = home;
|
|
33
|
+
try {
|
|
34
|
+
return await fn();
|
|
35
|
+
} finally {
|
|
36
|
+
if (previous === undefined) delete process.env.HOME;
|
|
37
|
+
else process.env.HOME = previous;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
function seedProject(dir) {
|
|
19
42
|
fs.writeFileSync(
|
|
20
43
|
path.join(dir, 'package.json'),
|
|
@@ -95,3 +118,394 @@ describe('upgrade.run — sourcePathFound', () => {
|
|
|
95
118
|
expect(res.data.sourcePathFound).toBe(true);
|
|
96
119
|
}, SLOW);
|
|
97
120
|
});
|
|
121
|
+
|
|
122
|
+
/** Core 0.7.0 renamed this prop; its codemod renames it on Typeahead. */
|
|
123
|
+
const OLD_PROP_SOURCE =
|
|
124
|
+
"import {Typeahead} from '@astryxdesign/core';\n" +
|
|
125
|
+
'export const App = () => <Typeahead label="Fruit" emptySearchResultsText="None" />;\n';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A project with Core 0.7.0 installed and src/App.tsx using a prop that 0.7.0
|
|
129
|
+
* renamed.
|
|
130
|
+
* @param {string} dir
|
|
131
|
+
*/
|
|
132
|
+
function seedOldPropProject(dir) {
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(dir, 'package.json'),
|
|
135
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
136
|
+
);
|
|
137
|
+
const core = path.join(dir, 'node_modules', '@astryxdesign', 'core');
|
|
138
|
+
fs.mkdirSync(core, {recursive: true});
|
|
139
|
+
fs.writeFileSync(path.join(core, 'package.json'), JSON.stringify({name: '@astryxdesign/core', version: '0.7.0'}));
|
|
140
|
+
fs.mkdirSync(path.join(dir, 'src', 'l1', 'l2'), {recursive: true});
|
|
141
|
+
fs.writeFileSync(path.join(dir, 'src', 'App.tsx'), OLD_PROP_SOURCE);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
describe('upgrade.run — a run from a subfolder upgrades the project', () => {
|
|
145
|
+
let dir;
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-scope-'));
|
|
148
|
+
seedOldPropProject(dir);
|
|
149
|
+
});
|
|
150
|
+
afterEach(() => fs.rmSync(dir, {recursive: true, force: true}));
|
|
151
|
+
|
|
152
|
+
const appSource = () => fs.readFileSync(path.join(dir, 'src', 'App.tsx'), 'utf-8');
|
|
153
|
+
|
|
154
|
+
it.each([['src', 'App.tsx'], ['src/l1/l2', '../../App.tsx']])(
|
|
155
|
+
'scans the project src by default when run from %s',
|
|
156
|
+
async (folder, reported) => {
|
|
157
|
+
const res = await upgrade({from: '0.6.8', apply: true}, {cwd: path.join(dir, folder)});
|
|
158
|
+
|
|
159
|
+
expect(res.type).toBe('upgrade.run');
|
|
160
|
+
expect(res.data.sourcePathFound).toBe(true);
|
|
161
|
+
expect(res.data.filesChanged).toBe(1);
|
|
162
|
+
expect(res.data.modifiedFiles).toEqual([reported]);
|
|
163
|
+
expect(appSource()).toContain('emptySearchText="None"');
|
|
164
|
+
},
|
|
165
|
+
SLOW,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
it('reads a path you name relative to the folder you ran in', async () => {
|
|
169
|
+
const res = await upgrade({from: '0.6.8', path: '../..', apply: true}, {cwd: path.join(dir, 'src', 'l1', 'l2')});
|
|
170
|
+
|
|
171
|
+
expect(res.type).toBe('upgrade.run');
|
|
172
|
+
expect(res.data.filesChanged).toBe(1);
|
|
173
|
+
expect(appSource()).toContain('emptySearchText="None"');
|
|
174
|
+
}, SLOW);
|
|
175
|
+
|
|
176
|
+
it('still rejects a path outside the project', async () => {
|
|
177
|
+
await expect(
|
|
178
|
+
upgrade({from: '0.6.8', path: '../../..'}, {cwd: path.join(dir, 'src', 'l1')}),
|
|
179
|
+
).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
180
|
+
expect(appSource()).toBe(OLD_PROP_SOURCE);
|
|
181
|
+
}, SLOW);
|
|
182
|
+
|
|
183
|
+
it("refreshes the agent docs at the project root, not the folder it ran in", async () => {
|
|
184
|
+
fs.writeFileSync(
|
|
185
|
+
path.join(dir, 'AGENTS.md'),
|
|
186
|
+
`# Agents\n\n${generateCompressedIndex('0.6.8')}\n`,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const res = await upgrade({from: '0.6.8', apply: true}, {cwd: path.join(dir, 'src', 'l1')});
|
|
190
|
+
|
|
191
|
+
expect(res.type).toBe('upgrade.run');
|
|
192
|
+
expect(res.data.agentDocs.action).toBe('refreshed');
|
|
193
|
+
expect(res.data.agentDocs.files).toEqual(['../../AGENTS.md']);
|
|
194
|
+
expect(fs.existsSync(path.join(dir, 'src', 'l1', 'AGENTS.md'))).toBe(false);
|
|
195
|
+
}, SLOW);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('upgrade.run — the result names files relative to the folder it ran in', () => {
|
|
199
|
+
let dir;
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-receipt-'));
|
|
202
|
+
seedOldPropProject(dir);
|
|
203
|
+
fs.writeFileSync(path.join(dir, 'src', 'Generated.tsx'), `// @generated\n${OLD_PROP_SOURCE}`);
|
|
204
|
+
fs.writeFileSync(path.join(dir, 'src', 'Vendored.tsx'), OLD_PROP_SOURCE);
|
|
205
|
+
fs.writeFileSync(path.join(dir, '.gitattributes'), 'src/Vendored.tsx linguist-generated\n');
|
|
206
|
+
fs.writeFileSync(path.join(dir, 'AGENTS.md'), `# Agents\n\n${generateCompressedIndex('0.6.8')}\n`);
|
|
207
|
+
fs.mkdirSync(path.join(dir, 'frontend'), {recursive: true});
|
|
208
|
+
});
|
|
209
|
+
afterEach(() => fs.rmSync(dir, {recursive: true, force: true}));
|
|
210
|
+
|
|
211
|
+
it.each([
|
|
212
|
+
['frontend', '../'],
|
|
213
|
+
['.', ''],
|
|
214
|
+
])('from %s, every file path resolves from that folder', async (folder, up) => {
|
|
215
|
+
const cwd = path.join(dir, folder);
|
|
216
|
+
|
|
217
|
+
const res = await upgrade({from: '0.6.8'}, {cwd});
|
|
218
|
+
|
|
219
|
+
expect(res.type).toBe('upgrade.run');
|
|
220
|
+
expect(res.data.modifiedFiles).toEqual([`${up}src/App.tsx`]);
|
|
221
|
+
expect(res.data.protectedFiles.map(item => item.file)).toEqual([
|
|
222
|
+
`${up}src/Generated.tsx`,
|
|
223
|
+
`${up}src/Vendored.tsx`,
|
|
224
|
+
]);
|
|
225
|
+
for (const file of [...res.data.modifiedFiles, ...res.data.protectedFiles.map(item => item.file)]) {
|
|
226
|
+
expect(fs.existsSync(path.join(cwd, file))).toBe(true);
|
|
227
|
+
}
|
|
228
|
+
// The file a declaration names resolves from that folder too.
|
|
229
|
+
const vendored = res.data.protectedFiles.find(item => item.file.endsWith('Vendored.tsx'));
|
|
230
|
+
expect(vendored?.declarations).toEqual([`linguist-generated in ${up}.gitattributes:1`]);
|
|
231
|
+
expect(fs.existsSync(path.join(cwd, `${up}.gitattributes`))).toBe(true);
|
|
232
|
+
}, SLOW);
|
|
233
|
+
|
|
234
|
+
it('leaves a result from a run at the project root as it is', () => {
|
|
235
|
+
const result = {type: 'upgrade.run', data: {modifiedFiles: ['src/App.tsx']}};
|
|
236
|
+
|
|
237
|
+
expect(relativeToOrigin(result, {cwd: dir})).toBe(result);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('rewrites every project-relative path and passes the copied-composition summary through', () => {
|
|
241
|
+
const registryCompositions = {items: [{item: 'example', path: 'components/Example.tsx', files: []}]};
|
|
242
|
+
const result = relativeToOrigin(
|
|
243
|
+
{
|
|
244
|
+
type: 'upgrade.run',
|
|
245
|
+
data: {
|
|
246
|
+
agentDocs: {status: 'stale', files: ['AGENTS.md']},
|
|
247
|
+
modifiedFiles: ['src/App.tsx'],
|
|
248
|
+
protectedFiles: [
|
|
249
|
+
{
|
|
250
|
+
file: 'src/Generated.tsx',
|
|
251
|
+
codemods: ['a'],
|
|
252
|
+
declarations: [
|
|
253
|
+
'@generated marker',
|
|
254
|
+
'excluded by .gitignore',
|
|
255
|
+
'excluded by src/.hgignore:3',
|
|
256
|
+
'linguist-generated in .gitattributes:1',
|
|
257
|
+
'symbolic link at src/linked',
|
|
258
|
+
'installed dependency metadata node_modules/x',
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
declinedCandidates: [{file: 'src/Loose.tsx', reason: 'unproven'}],
|
|
263
|
+
errors: [{file: '.', codemod: 'post-codemod-hook', error: 'failed'}],
|
|
264
|
+
registryCompositions,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{cwd: dir, origin: path.join(dir, 'apps', 'web')},
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(result.data).toEqual({
|
|
271
|
+
agentDocs: {status: 'stale', files: ['../../AGENTS.md']},
|
|
272
|
+
modifiedFiles: ['../../src/App.tsx'],
|
|
273
|
+
protectedFiles: [
|
|
274
|
+
{
|
|
275
|
+
file: '../../src/Generated.tsx',
|
|
276
|
+
codemods: ['a'],
|
|
277
|
+
declarations: [
|
|
278
|
+
'@generated marker',
|
|
279
|
+
'excluded by ../../.gitignore',
|
|
280
|
+
'excluded by ../../src/.hgignore:3',
|
|
281
|
+
'linguist-generated in ../../.gitattributes:1',
|
|
282
|
+
'symbolic link at ../../src/linked',
|
|
283
|
+
'installed dependency metadata node_modules/x',
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
declinedCandidates: [{file: '../../src/Loose.tsx', reason: 'unproven'}],
|
|
288
|
+
errors: [{file: '../..', codemod: 'post-codemod-hook', error: 'failed'}],
|
|
289
|
+
registryCompositions,
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("upgrade.run — the target Core is the project's own", () => {
|
|
295
|
+
let dir;
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-own-core-'));
|
|
298
|
+
seedOldPropProject(dir);
|
|
299
|
+
});
|
|
300
|
+
afterEach(() => fs.rmSync(dir, {recursive: true, force: true}));
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* A project nested in `dir`, with src/App.tsx using the renamed prop.
|
|
304
|
+
* @param {Record<string, string>} dependencies
|
|
305
|
+
*/
|
|
306
|
+
function nestedProject(dependencies) {
|
|
307
|
+
const nested = path.join(dir, 'apps', 'web');
|
|
308
|
+
fs.mkdirSync(path.join(nested, 'src'), {recursive: true});
|
|
309
|
+
fs.writeFileSync(path.join(nested, 'package.json'), JSON.stringify({name: 'web', dependencies}));
|
|
310
|
+
fs.writeFileSync(path.join(nested, 'src', 'App.tsx'), OLD_PROP_SOURCE);
|
|
311
|
+
return nested;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
it.each([
|
|
315
|
+
['declares no Astryx package', {}],
|
|
316
|
+
['declares a Core it has not installed', {'@astryxdesign/core': '0.6.8'}],
|
|
317
|
+
])(
|
|
318
|
+
'does not upgrade a project that %s to the Core of the home folder above it',
|
|
319
|
+
async (_label, dependencies) => {
|
|
320
|
+
const nested = nestedProject(dependencies);
|
|
321
|
+
|
|
322
|
+
await withHome(dir, () =>
|
|
323
|
+
expect(
|
|
324
|
+
upgrade({from: '0.6.8', apply: true}, {cwd: nested}),
|
|
325
|
+
).rejects.toMatchObject({code: 'ERR_VERSION_DETECT'}),
|
|
326
|
+
);
|
|
327
|
+
expect(fs.readFileSync(path.join(nested, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
328
|
+
expect(fs.readFileSync(path.join(dir, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
329
|
+
},
|
|
330
|
+
SLOW,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
it('names the folder it searched before stopping on a missing Core, from a subfolder (AST-040 FR12)', async () => {
|
|
334
|
+
fs.rmSync(path.join(dir, 'node_modules'), {recursive: true, force: true});
|
|
335
|
+
const sub = path.join(dir, 'src', 'l1');
|
|
336
|
+
const log = vi.spyOn(logger, 'log');
|
|
337
|
+
try {
|
|
338
|
+
await expect(upgrade({from: '0.6.8'}, {cwd: sub})).rejects.toMatchObject({
|
|
339
|
+
code: 'ERR_VERSION_DETECT',
|
|
340
|
+
});
|
|
341
|
+
const lines = log.mock.calls.map(([line]) => String(line));
|
|
342
|
+
expect(lines).toContain('Project root: ../..');
|
|
343
|
+
expect(lines.indexOf('Project root: ../..')).toBeLessThan(
|
|
344
|
+
lines.indexOf('Aborted\n'),
|
|
345
|
+
);
|
|
346
|
+
} finally {
|
|
347
|
+
log.mockRestore();
|
|
348
|
+
}
|
|
349
|
+
}, SLOW);
|
|
350
|
+
|
|
351
|
+
it('does not upgrade a nested project that declares a Core it has not installed to the Core above it', async () => {
|
|
352
|
+
const nested = nestedProject({'@astryxdesign/core': '0.6.8'});
|
|
353
|
+
|
|
354
|
+
await expect(
|
|
355
|
+
upgrade({from: '0.6.8', apply: true}, {cwd: nested}),
|
|
356
|
+
).rejects.toMatchObject({code: 'ERR_VERSION_DETECT'});
|
|
357
|
+
expect(fs.readFileSync(path.join(nested, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
358
|
+
}, SLOW);
|
|
359
|
+
|
|
360
|
+
it.each([
|
|
361
|
+
['an override at the workspace root forces', {overrides: {'@astryxdesign/core': '0.7.0'}}, {'@astryxdesign/core': '~0.6.8'}],
|
|
362
|
+
['a resolution at the workspace root forces', {resolutions: {'@astryxdesign/core': '0.7.0'}}, {'@astryxdesign/core': '0.6.8'}],
|
|
363
|
+
['only a stale peer range names', {}, null],
|
|
364
|
+
['the member declares another version of, not yet installed', {}, {'@astryxdesign/core': '0.9.0'}],
|
|
365
|
+
])(
|
|
366
|
+
'upgrades a workspace member to a hoisted Core that %s',
|
|
367
|
+
async (_label, rootFields, dependencies) => {
|
|
368
|
+
fs.writeFileSync(
|
|
369
|
+
path.join(dir, 'package.json'),
|
|
370
|
+
JSON.stringify({name: 'root', private: true, workspaces: ['apps/*'], ...rootFields}),
|
|
371
|
+
);
|
|
372
|
+
const member = dependencies
|
|
373
|
+
? nestedProject(dependencies)
|
|
374
|
+
: nestedProject({});
|
|
375
|
+
if (!dependencies) {
|
|
376
|
+
fs.writeFileSync(
|
|
377
|
+
path.join(member, 'package.json'),
|
|
378
|
+
JSON.stringify({name: 'web', peerDependencies: {'@astryxdesign/core': '^0.6.8'}}),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const res = await upgrade({from: '0.6.8', apply: true}, {cwd: member});
|
|
383
|
+
|
|
384
|
+
expect(res.type).toBe('upgrade.run');
|
|
385
|
+
expect(res.data.to).toBe('0.7.0');
|
|
386
|
+
expect(res.data.modifiedFiles).toEqual(['src/App.tsx']);
|
|
387
|
+
expect(fs.readFileSync(path.join(member, 'src', 'App.tsx'), 'utf-8')).toContain('emptySearchText="None"');
|
|
388
|
+
},
|
|
389
|
+
SLOW,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
it('does not upgrade a project its workspace does not list to the hoisted Core', async () => {
|
|
393
|
+
fs.writeFileSync(
|
|
394
|
+
path.join(dir, 'package.json'),
|
|
395
|
+
JSON.stringify({name: 'root', private: true, workspaces: ['packages/*']}),
|
|
396
|
+
);
|
|
397
|
+
const project = nestedProject({'@astryxdesign/core': '0.9.0'});
|
|
398
|
+
|
|
399
|
+
await expect(
|
|
400
|
+
upgrade({from: '0.6.8', apply: true}, {cwd: project}),
|
|
401
|
+
).rejects.toMatchObject({code: 'ERR_VERSION_DETECT'});
|
|
402
|
+
expect(fs.readFileSync(path.join(project, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
403
|
+
}, SLOW);
|
|
404
|
+
|
|
405
|
+
it.each([[['apps/*']], [['{apps/*,tools/*}']]])('upgrades a member that declares no Astryx package, in its own src, when the workspace lists %j', async workspaces => {
|
|
406
|
+
fs.writeFileSync(
|
|
407
|
+
path.join(dir, 'package.json'),
|
|
408
|
+
JSON.stringify({name: 'root', private: true, workspaces, dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
409
|
+
);
|
|
410
|
+
const member = nestedProject({});
|
|
411
|
+
|
|
412
|
+
const res = await upgrade({from: '0.6.8', apply: true}, {cwd: path.join(member, 'src')});
|
|
413
|
+
|
|
414
|
+
expect(res.type).toBe('upgrade.run');
|
|
415
|
+
expect(res.data.modifiedFiles).toEqual(['App.tsx']);
|
|
416
|
+
expect(fs.readFileSync(path.join(member, 'src', 'App.tsx'), 'utf-8')).toContain('emptySearchText="None"');
|
|
417
|
+
expect(fs.readFileSync(path.join(dir, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
418
|
+
}, SLOW);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe('upgrade.run — the project is the nearest folder that declares Astryx', () => {
|
|
422
|
+
let home;
|
|
423
|
+
beforeEach(() => {
|
|
424
|
+
home = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-home-'));
|
|
425
|
+
});
|
|
426
|
+
afterEach(() => fs.rmSync(home, {recursive: true, force: true}));
|
|
427
|
+
|
|
428
|
+
// Astryx installed in a home folder whose src holds loose files and another
|
|
429
|
+
// project, run from a folder with no package.json.
|
|
430
|
+
it('does not upgrade the home folder from a folder inside it', async () => {
|
|
431
|
+
seedOldPropProject(home);
|
|
432
|
+
fs.mkdirSync(path.join(home, 'src', 'repoA', 'src'), {recursive: true});
|
|
433
|
+
fs.writeFileSync(
|
|
434
|
+
path.join(home, 'src', 'repoA', 'package.json'),
|
|
435
|
+
JSON.stringify({name: 'repoA', dependencies: {'@astryxdesign/core': '0.6.8'}}),
|
|
436
|
+
);
|
|
437
|
+
fs.writeFileSync(path.join(home, 'src', 'repoA', 'src', 'a.tsx'), OLD_PROP_SOURCE);
|
|
438
|
+
const desktop = path.join(home, 'Desktop');
|
|
439
|
+
fs.mkdirSync(desktop, {recursive: true});
|
|
440
|
+
|
|
441
|
+
await withHome(home, () =>
|
|
442
|
+
expect(
|
|
443
|
+
upgrade({from: '0.6.8', apply: true}, {cwd: desktop}),
|
|
444
|
+
).rejects.toMatchObject({code: 'ERR_VERSION_DETECT'}),
|
|
445
|
+
);
|
|
446
|
+
expect(fs.readFileSync(path.join(home, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
447
|
+
expect(fs.readFileSync(path.join(home, 'src', 'repoA', 'src', 'a.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
448
|
+
}, SLOW);
|
|
449
|
+
|
|
450
|
+
// Installing Core does not make a folder a project.
|
|
451
|
+
it('treats a folder that installs Core without declaring it as part of the project above it', async () => {
|
|
452
|
+
seedOldPropProject(home);
|
|
453
|
+
const tool = path.join(home, 'tools', 'codegen');
|
|
454
|
+
const core = path.join(tool, 'node_modules', '@astryxdesign', 'core');
|
|
455
|
+
fs.mkdirSync(core, {recursive: true});
|
|
456
|
+
fs.writeFileSync(path.join(tool, 'package.json'), JSON.stringify({name: 'codegen'}));
|
|
457
|
+
fs.writeFileSync(path.join(core, 'package.json'), JSON.stringify({name: '@astryxdesign/core', version: '0.9.0'}));
|
|
458
|
+
|
|
459
|
+
const res = await upgrade({from: '0.6.8', apply: true}, {cwd: tool});
|
|
460
|
+
|
|
461
|
+
expect(res.type).toBe('upgrade.run');
|
|
462
|
+
expect(res.data.to).toBe('0.7.0');
|
|
463
|
+
expect(res.data.modifiedFiles).toEqual(['../../src/App.tsx']);
|
|
464
|
+
}, SLOW);
|
|
465
|
+
|
|
466
|
+
it('names the project root and the folder it scans before a dry run in a folder that installs Core without declaring it', async () => {
|
|
467
|
+
seedOldPropProject(home);
|
|
468
|
+
const tool = path.join(home, 'tools', 'codegen');
|
|
469
|
+
const core = path.join(tool, 'node_modules', '@astryxdesign', 'core');
|
|
470
|
+
fs.mkdirSync(core, {recursive: true});
|
|
471
|
+
fs.writeFileSync(path.join(tool, 'package.json'), JSON.stringify({name: 'codegen'}));
|
|
472
|
+
fs.writeFileSync(path.join(core, 'package.json'), JSON.stringify({name: '@astryxdesign/core', version: '0.9.0'}));
|
|
473
|
+
const log = vi.spyOn(logger, 'log');
|
|
474
|
+
try {
|
|
475
|
+
const res = await upgrade({from: '0.6.8'}, {cwd: tool});
|
|
476
|
+
|
|
477
|
+
expect(res.type).toBe('upgrade.run');
|
|
478
|
+
const lines = log.mock.calls.map(([line]) => String(line));
|
|
479
|
+
expect(lines).toContain('Project root: ../..');
|
|
480
|
+
expect(lines).toContain(`Source folder: ${path.join('..', '..', 'src')}`);
|
|
481
|
+
expect(fs.readFileSync(path.join(home, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
482
|
+
} finally {
|
|
483
|
+
log.mockRestore();
|
|
484
|
+
}
|
|
485
|
+
}, SLOW);
|
|
486
|
+
|
|
487
|
+
it('upgrades the home folder as a folder outside any project, when run there', async () => {
|
|
488
|
+
seedOldPropProject(home);
|
|
489
|
+
|
|
490
|
+
const res = await withHome(home, () => upgrade({from: '0.6.8', apply: true}, {cwd: home}));
|
|
491
|
+
|
|
492
|
+
expect(res.type).toBe('upgrade.run');
|
|
493
|
+
expect(res.data.modifiedFiles).toEqual(['src/App.tsx']);
|
|
494
|
+
expect(fs.readFileSync(path.join(home, 'src', 'App.tsx'), 'utf-8')).toContain('emptySearchText="None"');
|
|
495
|
+
}, SLOW);
|
|
496
|
+
|
|
497
|
+
it('does not leave a repository for the project around it', async () => {
|
|
498
|
+
seedOldPropProject(home);
|
|
499
|
+
const repo = path.join(home, 'vendor', 'repo');
|
|
500
|
+
fs.mkdirSync(path.join(repo, '.git'), {recursive: true});
|
|
501
|
+
fs.mkdirSync(path.join(repo, 'src'), {recursive: true});
|
|
502
|
+
fs.writeFileSync(path.join(repo, 'package.json'), JSON.stringify({name: 'repo'}));
|
|
503
|
+
fs.writeFileSync(path.join(repo, 'src', 'App.tsx'), OLD_PROP_SOURCE);
|
|
504
|
+
|
|
505
|
+
await expect(
|
|
506
|
+
upgrade({from: '0.6.8', apply: true}, {cwd: repo}),
|
|
507
|
+
).rejects.toMatchObject({code: 'ERR_VERSION_DETECT'});
|
|
508
|
+
expect(fs.readFileSync(path.join(repo, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
509
|
+
expect(fs.readFileSync(path.join(home, 'src', 'App.tsx'), 'utf-8')).toBe(OLD_PROP_SOURCE);
|
|
510
|
+
}, SLOW);
|
|
511
|
+
});
|
|
@@ -77,8 +77,9 @@ export const doc = {
|
|
|
77
77
|
{
|
|
78
78
|
name: 'options.path',
|
|
79
79
|
type: 'string',
|
|
80
|
-
description:
|
|
81
|
-
|
|
80
|
+
description:
|
|
81
|
+
'Source directory to scan. A path you name, in any spelling, is relative to `ctx.cwd`. When it is omitted, the upgrade scans `./src` at the project root.',
|
|
82
|
+
default: './src at the project root',
|
|
82
83
|
},
|
|
83
84
|
{
|
|
84
85
|
name: 'options.installDeps',
|
|
@@ -103,7 +104,8 @@ export const doc = {
|
|
|
103
104
|
{
|
|
104
105
|
name: 'ctx.cwd',
|
|
105
106
|
type: 'string',
|
|
106
|
-
description:
|
|
107
|
+
description:
|
|
108
|
+
'Any folder in the project to upgrade. The upgrade runs at the project root: the nearest folder at or above it whose package.json declares an Astryx package, or that a workspace whose root declares one lists, found without passing a repository root or counting the home folder. With no such folder, it runs in `ctx.cwd`. File paths in the result are relative to `ctx.cwd`, except that a run at the project root names copied-composition paths relative to the scanned source directory.',
|
|
107
109
|
default: 'process.cwd()',
|
|
108
110
|
},
|
|
109
111
|
],
|
|
@@ -135,10 +137,13 @@ export const doc = {
|
|
|
135
137
|
when: '`from` is missing (and neither `list` nor `registry` is set); `list` and `registry` are both set; `list` is combined with `from`, `force`, `apply`, `path` (non-default), `codemod`, `skipCodemod`, `integration` or `installDeps`; `registry` is combined with `from`, `force`, `codemod`, `skipCodemod`, `integration` or `installDeps`; an `integration` specifier is invalid or not installed; or astryx.config fails to load or validate and no pending config codemod repairs it',
|
|
136
138
|
},
|
|
137
139
|
{code: 'ERR_INVALID_VERSION', when: '`from` is not a valid semver string'},
|
|
138
|
-
{
|
|
140
|
+
{
|
|
141
|
+
code: 'ERR_PATH_TRAVERSAL',
|
|
142
|
+
when: '`path` resolves outside the project root',
|
|
143
|
+
},
|
|
139
144
|
{
|
|
140
145
|
code: 'ERR_VERSION_DETECT',
|
|
141
|
-
when:
|
|
146
|
+
when: "the project has installed neither @astryxdesign/core nor legacy @xds/core in its own node_modules, nor, for a member its workspace lists, in a folder up to the workspace root (with no Astryx project, in `ctx.cwd`'s own node_modules); with `registry`, only when copied-composition receipts exist and @astryxdesign/core is not installed",
|
|
142
147
|
},
|
|
143
148
|
{
|
|
144
149
|
code: 'ERR_DEP_MISSING',
|