@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.ddb63c3
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 +20 -1
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/build.doc.mjs +1 -0
- package/api/component/component.doc.mjs +1 -0
- package/api/discover/discover.doc.mjs +1 -0
- package/api/docs/_adapter.d.mts +3 -8
- package/api/docs/_adapter.mjs +13 -105
- package/api/docs/docs.doc.mjs +1 -0
- package/api/docs/list/list.mjs +3 -7
- package/api/doctor/doctor.d.mts +25 -3
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +185 -13
- package/api/doctor/doctor.test.mjs +326 -1
- package/api/gap-report/gap-report.doc.mjs +1 -0
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +1 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/hook/list/list.mjs +69 -17
- package/api/init/init.doc.mjs +1 -0
- package/api/integration/add-contribution.mjs +7 -7
- package/api/integration/add-contribution.test.mjs +37 -3
- package/api/integration/add-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.test.mjs +8 -4
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +1 -0
- package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
- package/api/integration/integrationAddCodemod.doc.mjs +1 -0
- package/api/integration/integrationAddComponent.doc.mjs +1 -0
- package/api/integration/integrationAddDoc.doc.mjs +1 -0
- package/api/integration/integrationAddTemplate.doc.mjs +1 -0
- package/api/integration/integrationAddTheme.doc.mjs +6 -5
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
- package/api/integration/integrationDocConflicts.doc.mjs +1 -0
- package/api/integration/integrationPackCheck.doc.mjs +1 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
- package/api/integration/pack-check.test.mjs +17 -47
- package/api/integration/summarizeIssues.doc.mjs +1 -0
- package/api/integration/validate-integration-fixes.test.mjs +1389 -0
- package/api/integration/validate-integration.mjs +50 -102
- package/api/integration/validate-integration.test.mjs +85 -23
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +1 -0
- package/api/json/assertResponse.doc.mjs +1 -0
- package/api/json/isError.doc.mjs +1 -0
- package/api/json/parseResponse.doc.mjs +1 -0
- package/api/layout/layoutCheck.doc.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -0
- package/api/layout/layoutGrammar.doc.mjs +1 -0
- package/api/search/search.doc.mjs +1 -0
- package/api/search/search.mjs +16 -6
- package/api/swizzle/swizzle.doc.mjs +1 -0
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.doc.mjs +1 -0
- package/api/theme/_adapter.d.mts +2 -3
- package/api/theme/_adapter.mjs +4 -5
- package/api/theme/add/add.binary.test.mjs +10 -17
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -0
- package/api/theme/integration-themes.test.mjs +39 -28
- package/api/theme/list/list.test.mjs +19 -20
- package/api/theme/listThemes.doc.mjs +6 -5
- package/api/theme/themeAdd.doc.mjs +4 -3
- package/api/theme/themeBuild.doc.mjs +1 -0
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +5 -3
- package/api/theme/themePaletteGenerate.doc.mjs +1 -0
- package/api/theme/themeTargets.doc.mjs +1 -0
- package/api/theme/themeTemplate.doc.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +12 -3
- package/api/upgrade/_adapter.mjs +81 -66
- package/api/upgrade/provider-agreement.test.mjs +152 -0
- package/api/upgrade/run/run.mjs +1 -0
- package/api/upgrade/upgrade.doc.mjs +1 -0
- package/assets/codemods/integration-discovery.mjs +8 -2
- package/assets/codemods/integration-discovery.test.mjs +15 -0
- package/assets/codemods/runner.mjs +115 -5
- package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
- package/assets/docs/cli-integrations.doc.mjs +17 -30
- package/assets/docs/cli.doc.mjs +15 -0
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
- package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
- package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
- package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
- package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
- package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
- package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
- package/authoring/codemod/codemod.doc.mjs +1 -1
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/type.ts +2 -2
- package/authoring/doctypes/doctypes-new.test.mjs +48 -6
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +1 -1
- package/authoring/doctypes/function/type.ts +1 -1
- package/authoring/doctypes/hook/type.ts +2 -2
- package/authoring/doctypes/load-contract.test.mjs +2 -1
- package/authoring/doctypes/parse.d.mts +4 -2
- package/authoring/doctypes/parse.mjs +7 -3
- package/authoring/doctypes/reference/reference.doc.mjs +1 -1
- package/authoring/doctypes/reference/type.ts +2 -2
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -2
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/theme/parse.d.mts +35 -0
- package/authoring/doctypes/theme/parse.mjs +76 -0
- package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
- package/authoring/doctypes/theme/theme.doc.mjs +79 -0
- package/authoring/doctypes/theme/type.ts +42 -0
- package/authoring/doctypes/types.ts +2 -1
- package/authoring/gap-report/gap-report.doc.d.mts +12 -0
- package/authoring/gap-report/gap-report.doc.mjs +183 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +6 -4
- package/authoring/index.mjs +2 -1
- package/authoring/integration/integration.doc.mjs +2 -2
- package/authoring/integration/type.ts +3 -9
- package/clients/cli/__tests__/cliManifest.test.ts +27 -29
- package/clients/cli/commands/blog.doc.mjs +1 -1
- package/clients/cli/commands/build.doc.mjs +1 -1
- package/clients/cli/commands/component.doc.mjs +1 -1
- package/clients/cli/commands/discover.doc.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.test.mjs +15 -7
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/gap-report.doc.mjs +1 -1
- package/clients/cli/commands/hook.doc.mjs +1 -1
- package/clients/cli/commands/init.doc.mjs +1 -1
- package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
- package/clients/cli/commands/integration-add.doc.mjs +1 -1
- package/clients/cli/commands/integration-pack.doc.mjs +1 -1
- package/clients/cli/commands/integration-real-world.test.mjs +3 -9
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/layout-check.doc.mjs +1 -1
- package/clients/cli/commands/layout-expand.doc.mjs +1 -1
- package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +1 -1
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/theme-add.doc.mjs +2 -2
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/clients/cli/commands/theme-list.doc.mjs +2 -2
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
- package/clients/cli/commands/theme-palette.doc.mjs +1 -1
- package/clients/cli/commands/theme-targets.doc.mjs +1 -1
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/theme.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +1 -1
- package/clients/cli/lib/hook-format.mjs +14 -5
- package/foundation/config/project-themes.test.mjs +11 -19
- package/foundation/config/project.d.mts +8 -0
- package/foundation/config/project.mjs +50 -21
- package/foundation/config/project.test.mjs +3 -16
- package/foundation/discovery/authoring-self-docs.d.mts +6 -0
- package/foundation/discovery/authoring-self-docs.mjs +17 -7
- package/foundation/discovery/authoring-surface.d.mts +74 -0
- package/foundation/discovery/authoring-surface.mjs +525 -0
- package/foundation/discovery/authoring-surface.test.mjs +392 -0
- package/foundation/discovery/cli-self-docs.d.mts +113 -0
- package/foundation/discovery/cli-self-docs.mjs +514 -0
- package/foundation/discovery/cli-self-docs.test.mjs +437 -0
- package/foundation/discovery/component-loader.d.mts +35 -38
- package/foundation/discovery/component-loader.mjs +53 -222
- package/foundation/discovery/docs-discovery.d.mts +3 -2
- package/foundation/discovery/docs-discovery.mjs +8 -12
- package/foundation/discovery/docs-discovery.test.mjs +8 -5
- package/foundation/discovery/template-adapter.d.mts +7 -0
- package/foundation/discovery/template-adapter.mjs +27 -40
- package/foundation/discovery/template-adapter.test.mjs +15 -0
- package/foundation/discovery/theme-discovery.d.mts +67 -7
- package/foundation/discovery/theme-discovery.mjs +916 -186
- package/foundation/discovery/theme-discovery.test.mjs +613 -219
- package/foundation/doc-compiler/bundle.d.mts +47 -0
- package/foundation/doc-compiler/bundle.mjs +218 -0
- package/foundation/doc-compiler/bundle.test.mjs +255 -0
- package/foundation/doc-compiler/compile.d.mts +200 -0
- package/foundation/doc-compiler/compile.mjs +254 -5
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +277 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
- package/foundation/doc-compiler/import.d.mts +24 -0
- package/foundation/doc-compiler/import.mjs +59 -0
- package/foundation/doc-compiler/inputs.d.mts +102 -0
- package/foundation/doc-compiler/inputs.mjs +286 -0
- package/foundation/doc-compiler/inputs.test.mjs +299 -0
- package/foundation/doc-compiler/ir.d.mts +13 -0
- package/foundation/doc-compiler/ir.mjs +189 -12
- package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
- package/foundation/doc-compiler/overlays.d.mts +37 -0
- package/foundation/doc-compiler/overlays.mjs +206 -0
- package/foundation/doc-compiler/parse-readable.d.mts +9 -0
- package/foundation/doc-compiler/parse-readable.mjs +29 -0
- package/foundation/doc-compiler/read.d.mts +126 -0
- package/foundation/doc-compiler/read.mjs +320 -0
- package/foundation/doc-compiler/read.test.mjs +313 -0
- package/foundation/doc-compiler/source.d.mts +33 -0
- package/foundation/doc-compiler/source.mjs +128 -0
- package/foundation/integrations/autolink.d.mts +58 -1
- package/foundation/integrations/autolink.mjs +143 -57
- package/foundation/integrations/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +1 -1
- package/foundation/integrations/contribution-inventory.mjs +17 -20
- package/foundation/integrations/contribution-inventory.test.mjs +67 -27
- package/foundation/integrations/integrations.d.mts +3 -0
- package/foundation/integrations/integrations.mjs +11 -97
- package/foundation/integrations/provider-ledger.test.mjs +275 -0
- package/foundation/integrations/provider-resolution.d.mts +152 -0
- package/foundation/integrations/provider-resolution.mjs +576 -0
- package/foundation/integrations/provider-resolution.test.mjs +369 -0
- package/foundation/integrations/theme-descriptor.d.mts +8 -0
- package/foundation/integrations/theme-descriptor.mjs +44 -0
- package/foundation/integrations/validate-contributions.mjs +104 -10
- package/foundation/response/error-codes.doc.mjs +2 -2
- package/foundation/response/error-codes.mjs +1 -1
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/response/response.doc.mjs +1 -1
- package/foundation/text/string-utils.mjs +22 -10
- package/package.json +9 -9
- package/assets/templates/themes/manifest.json +0 -95
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/**
|
|
17
17
|
* Compute the files the tarball must contain for an integration's
|
|
18
|
-
* contributions to be visible.
|
|
18
|
+
* contributions to be visible. Walks descriptor-owned directories;
|
|
19
19
|
* never imports or executes authored modules.
|
|
20
20
|
*
|
|
21
21
|
* @param {import('./integrations.mjs').LoadedIntegration} loaded
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as path from 'node:path';
|
|
16
16
|
import {isDeepStrictEqual} from 'node:util';
|
|
17
|
-
import {
|
|
18
|
-
|
|
17
|
+
import {
|
|
18
|
+
discoverIntegrationThemes,
|
|
19
|
+
isThemeFolder,
|
|
20
|
+
listThemeFiles,
|
|
21
|
+
} from '../discovery/theme-discovery.mjs';
|
|
19
22
|
import {discoverIntegrationComponents} from '../discovery/component-discovery.mjs';
|
|
20
23
|
import {loadComponentDoc} from '../discovery/component-loader.mjs';
|
|
21
24
|
import {discoverIntegrationTemplatesForOne} from '../discovery/template-adapter.mjs';
|
|
@@ -65,27 +68,21 @@ function rel(packageDir, file) {
|
|
|
65
68
|
|
|
66
69
|
/** @param {string} root @param {string} pkgDir */
|
|
67
70
|
function enumerateThemeFiles(root, pkgDir) {
|
|
71
|
+
if (!root || !fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
// Exactly what `theme add` copies: each theme folder's files, including
|
|
75
|
+
// tests and fixtures, but no dot entry or file npm never publishes. Files
|
|
76
|
+
// beside the theme folders belong to no theme.
|
|
68
77
|
/** @type {string[]} */
|
|
69
78
|
const files = [];
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const data = JSON.parse(fs.readFileSync(catalog, 'utf-8'));
|
|
75
|
-
if (data?.version !== 1 || !Array.isArray(data.themes)) return files;
|
|
76
|
-
for (const theme of data.themes) {
|
|
77
|
-
if (typeof theme.slug !== 'string' || !Array.isArray(theme.files))
|
|
78
|
-
continue;
|
|
79
|
-
for (const f of theme.files) {
|
|
80
|
-
if (typeof f === 'string') {
|
|
81
|
-
files.push(rel(pkgDir, path.join(root, theme.slug, f)));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
79
|
+
for (const entry of fs.readdirSync(root, {withFileTypes: true})) {
|
|
80
|
+
const full = path.join(root, entry.name);
|
|
81
|
+
if (entry.isDirectory() && isThemeFolder(full)) {
|
|
82
|
+
files.push(...listThemeFiles(full).map(file => path.join(full, file)));
|
|
84
83
|
}
|
|
85
|
-
} catch {
|
|
86
|
-
/* Phase 1 catches structural issues */
|
|
87
84
|
}
|
|
88
|
-
return files;
|
|
85
|
+
return files.sort().map(file => rel(pkgDir, file));
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
/** @param {string} root @param {string} pkgDir */
|
|
@@ -158,7 +155,7 @@ const ROOT_KINDS = /** @type {const} */ ([
|
|
|
158
155
|
|
|
159
156
|
/**
|
|
160
157
|
* Compute the files the tarball must contain for an integration's
|
|
161
|
-
* contributions to be visible.
|
|
158
|
+
* contributions to be visible. Walks descriptor-owned directories;
|
|
162
159
|
* never imports or executes authored modules.
|
|
163
160
|
*
|
|
164
161
|
* @param {import('./integrations.mjs').LoadedIntegration} loaded
|
|
@@ -37,30 +37,37 @@ function loaded(overrides = {}) {
|
|
|
37
37
|
|
|
38
38
|
function writeTheme(slug, files = [`${slug}Theme.ts`]) {
|
|
39
39
|
const root = path.join(tmpDir, 'themes');
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const themeDir = path.join(root, slug);
|
|
41
|
+
fs.mkdirSync(themeDir, {recursive: true});
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const target = path.join(themeDir, file);
|
|
44
|
+
fs.mkdirSync(path.dirname(target), {recursive: true});
|
|
45
|
+
fs.writeFileSync(target, `// ${file}\n`);
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
catalog.themes.push({
|
|
50
|
-
slug,
|
|
51
|
-
displayName: slug,
|
|
52
|
-
description: `${slug} theme.`,
|
|
53
|
-
maintained: true,
|
|
54
|
-
entry: files[0],
|
|
55
|
-
exportName: `${slug}Theme`,
|
|
56
|
-
files,
|
|
57
|
-
});
|
|
58
|
-
fs.writeFileSync(catalogPath, JSON.stringify(catalog, null, 2) + '\n');
|
|
47
|
+
fs.writeFileSync(
|
|
48
|
+
path.join(themeDir, `${slug}Theme.doc.mjs`),
|
|
49
|
+
`/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */\nexport default {type: 'theme', name: '${slug}', displayName: '${slug}', description: '${slug} theme.', maintained: true};\n`,
|
|
50
|
+
);
|
|
59
51
|
return root;
|
|
60
52
|
}
|
|
61
53
|
|
|
54
|
+
const UNPUBLISHED = [
|
|
55
|
+
'.DS_Store',
|
|
56
|
+
'._oceanTheme.ts',
|
|
57
|
+
'.gitignore',
|
|
58
|
+
'.npmignore',
|
|
59
|
+
'.npmrc',
|
|
60
|
+
'oceanTheme.ts.orig',
|
|
61
|
+
'npm-debug.log',
|
|
62
|
+
'palette/.DS_Store',
|
|
63
|
+
'palette/npm-debug.log',
|
|
64
|
+
'.git/HEAD',
|
|
65
|
+
'CVS/Entries',
|
|
66
|
+
'node_modules/dep/index.js',
|
|
67
|
+
];
|
|
68
|
+
|
|
62
69
|
describe('computeRequiredFiles', () => {
|
|
63
|
-
it('enumerates
|
|
70
|
+
it('enumerates each complete descriptor-owned theme directory', () => {
|
|
64
71
|
const root = writeTheme('ocean', ['oceanTheme.ts', 'tokens.ts']);
|
|
65
72
|
const inv = computeRequiredFiles(loaded({themes: root}));
|
|
66
73
|
|
|
@@ -69,13 +76,46 @@ describe('computeRequiredFiles', () => {
|
|
|
69
76
|
expect(inv.roots[0].kind).toBe('themes');
|
|
70
77
|
expect(inv.roots[0].files).toEqual(
|
|
71
78
|
expect.arrayContaining([
|
|
72
|
-
'themes/
|
|
79
|
+
'themes/ocean/oceanTheme.doc.mjs',
|
|
73
80
|
'themes/ocean/oceanTheme.ts',
|
|
74
81
|
'themes/ocean/tokens.ts',
|
|
75
82
|
]),
|
|
76
83
|
);
|
|
77
84
|
expect(inv.allFiles).toContain('astryx.integration.mjs');
|
|
78
|
-
expect(inv.allFiles).toContain('themes/
|
|
85
|
+
expect(inv.allFiles).toContain('themes/ocean/oceanTheme.doc.mjs');
|
|
86
|
+
expect(inv.allFiles).not.toContain('themes/manifest.json');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('requires no file npm leaves out, at any depth, and none beside the themes', () => {
|
|
90
|
+
const root = writeTheme('ocean', ['oceanTheme.ts', 'palette/tokens.ts']);
|
|
91
|
+
for (const file of [
|
|
92
|
+
...UNPUBLISHED.map(name => `ocean/${name}`),
|
|
93
|
+
'README.md',
|
|
94
|
+
'npm-debug.log',
|
|
95
|
+
]) {
|
|
96
|
+
fs.mkdirSync(path.dirname(path.join(root, file)), {recursive: true});
|
|
97
|
+
fs.writeFileSync(path.join(root, file), 'x\n');
|
|
98
|
+
}
|
|
99
|
+
expect(computeRequiredFiles(loaded({themes: root})).roots[0].files).toEqual(
|
|
100
|
+
[
|
|
101
|
+
'themes/ocean/oceanTheme.doc.mjs',
|
|
102
|
+
'themes/ocean/oceanTheme.ts',
|
|
103
|
+
'themes/ocean/palette/tokens.ts',
|
|
104
|
+
],
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('requires only the folders theme discovery reads', () => {
|
|
109
|
+
const root = writeTheme('ocean');
|
|
110
|
+
for (const file of ['shared/palette.ts', '.cache/state.json']) {
|
|
111
|
+
fs.mkdirSync(path.dirname(path.join(root, file)), {recursive: true});
|
|
112
|
+
fs.writeFileSync(path.join(root, file), '{}\n');
|
|
113
|
+
}
|
|
114
|
+
const inv = computeRequiredFiles(loaded({themes: root}));
|
|
115
|
+
expect(inv.roots[0].files).toEqual([
|
|
116
|
+
'themes/ocean/oceanTheme.doc.mjs',
|
|
117
|
+
'themes/ocean/oceanTheme.ts',
|
|
118
|
+
]);
|
|
79
119
|
});
|
|
80
120
|
|
|
81
121
|
it('enumerates paired component metadata and source files', () => {
|
|
@@ -139,31 +179,31 @@ describe('computeRequiredFiles', () => {
|
|
|
139
179
|
const root = path.join(tmpDir, 'templates');
|
|
140
180
|
fs.mkdirSync(path.join(root, 'dashboard'), {recursive: true});
|
|
141
181
|
fs.writeFileSync(
|
|
142
|
-
path.join(root, 'dashboard', 'dashboard.
|
|
182
|
+
path.join(root, 'dashboard', 'dashboard.doc.mjs'),
|
|
143
183
|
'// tmpl\n',
|
|
144
184
|
);
|
|
145
185
|
fs.writeFileSync(path.join(root, 'dashboard', 'dashboard.tsx'), '// src\n');
|
|
146
186
|
const inv = computeRequiredFiles(loaded({templates: root}));
|
|
147
187
|
|
|
148
188
|
expect(inv.roots[0].files).toContain(
|
|
149
|
-
'templates/dashboard/dashboard.
|
|
189
|
+
'templates/dashboard/dashboard.doc.mjs',
|
|
150
190
|
);
|
|
151
191
|
expect(inv.roots[0].files).toContain('templates/dashboard/dashboard.tsx');
|
|
152
192
|
});
|
|
153
193
|
|
|
154
|
-
it('enumerates
|
|
194
|
+
it('enumerates released .template compatibility metadata with paired sources', () => {
|
|
155
195
|
const root = path.join(tmpDir, 'templates');
|
|
156
196
|
fs.mkdirSync(path.join(root, 'dashboard'), {recursive: true});
|
|
157
197
|
fs.writeFileSync(
|
|
158
|
-
path.join(root, 'dashboard', 'dashboard.
|
|
159
|
-
'//
|
|
198
|
+
path.join(root, 'dashboard', 'dashboard.template.mjs'),
|
|
199
|
+
'// released compatibility template\n',
|
|
160
200
|
);
|
|
161
201
|
fs.writeFileSync(path.join(root, 'dashboard', 'dashboard.tsx'), '// src\n');
|
|
162
202
|
|
|
163
203
|
const inv = computeRequiredFiles(loaded({templates: root}));
|
|
164
204
|
|
|
165
205
|
expect(inv.roots[0].files).toEqual([
|
|
166
|
-
'templates/dashboard/dashboard.
|
|
206
|
+
'templates/dashboard/dashboard.template.mjs',
|
|
167
207
|
'templates/dashboard/dashboard.tsx',
|
|
168
208
|
]);
|
|
169
209
|
});
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* issues, Doctor, and the per-command warning name the one set aside instead of
|
|
15
15
|
* dropping it without a word.
|
|
16
16
|
*
|
|
17
|
+
* The conflict pass of {@link resolveProviders}, for a list that is already
|
|
18
|
+
* assembled.
|
|
19
|
+
*
|
|
17
20
|
* @param {LoadedIntegration[]} integrations in precedence order
|
|
18
21
|
* @returns {LoadedIntegration[]}
|
|
19
22
|
*/
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '../../authoring/integration/schema.mjs';
|
|
30
30
|
import {importUserModule, findPresentFiles} from '../fs/module-loader.mjs';
|
|
31
31
|
import {parseGapReportHandler} from '../../authoring/gap-report/parse.mjs';
|
|
32
|
+
import {resolveProviders} from './provider-resolution.mjs';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* A fully-resolved, loaded integration. Identity (`name`, `version`) comes from
|
|
@@ -116,107 +117,20 @@ function providerIdForPackage(name) {
|
|
|
116
117
|
* issues, Doctor, and the per-command warning name the one set aside instead of
|
|
117
118
|
* dropping it without a word.
|
|
118
119
|
*
|
|
120
|
+
* The conflict pass of {@link resolveProviders}, for a list that is already
|
|
121
|
+
* assembled.
|
|
122
|
+
*
|
|
119
123
|
* @param {LoadedIntegration[]} integrations in precedence order
|
|
120
124
|
* @returns {LoadedIntegration[]}
|
|
121
125
|
*/
|
|
122
126
|
export function markProviderConflicts(integrations) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
/** @type {LoadedIntegration[]} */
|
|
132
|
-
const resolved = [];
|
|
133
|
-
for (const integration of integrations) {
|
|
134
|
-
if (!claimsProvider(integration)) {
|
|
135
|
-
resolved.push(integration);
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
const providerId = /** @type {string} */ (integration.providerId);
|
|
139
|
-
const claimant = claims.get(providerId);
|
|
140
|
-
if (claimant == null) {
|
|
141
|
-
claims.set(providerId, integration);
|
|
142
|
-
resolved.push(integration);
|
|
143
|
-
} else if (claimant === integration) {
|
|
144
|
-
resolved.push(integration);
|
|
145
|
-
} else if (
|
|
146
|
-
claimant.name !== integration.name ||
|
|
147
|
-
claimant.version !== integration.version
|
|
148
|
-
) {
|
|
149
|
-
resolved.push(providerConflict(integration, claimant));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return resolved;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Whether an entry contributes under its provider ID: it has one, its manifest
|
|
157
|
-
* loaded, and it has not already been set aside.
|
|
158
|
-
* @param {LoadedIntegration | undefined} integration
|
|
159
|
-
* @returns {boolean}
|
|
160
|
-
*/
|
|
161
|
-
function claimsProvider(integration) {
|
|
162
|
-
return (
|
|
163
|
-
integration?.providerId != null &&
|
|
164
|
-
integration.__loadError == null &&
|
|
165
|
-
integration.__providerConflict == null
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* @param {LoadedIntegration} integration
|
|
171
|
-
* @returns {string}
|
|
172
|
-
*/
|
|
173
|
-
function describeIntegration(integration) {
|
|
174
|
-
const id = integration.version
|
|
175
|
-
? `${integration.name}@${integration.version}`
|
|
176
|
-
: integration.name;
|
|
177
|
-
return integration.__spec && integration.__spec !== integration.name
|
|
178
|
-
? `${id} (from "${integration.__spec}")`
|
|
179
|
-
: id;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* An inert record for a package whose provider ID is already claimed. It keeps
|
|
184
|
-
* the package's identity and location for reporting, and drops every
|
|
185
|
-
* contribution root and handler.
|
|
186
|
-
* @param {LoadedIntegration} integration
|
|
187
|
-
* @param {LoadedIntegration} claimant
|
|
188
|
-
* @returns {LoadedIntegration}
|
|
189
|
-
*/
|
|
190
|
-
function providerConflict(integration, claimant) {
|
|
191
|
-
const providerId =
|
|
192
|
-
/** @type {import('../../authoring/identity/type').ProviderId} */ (
|
|
193
|
-
integration.providerId
|
|
194
|
-
);
|
|
195
|
-
const winner = describeIntegration(claimant);
|
|
196
|
-
const setAside = describeIntegration(integration);
|
|
197
|
-
const reason = claimant.__local
|
|
198
|
-
? 'is the package being authored, so it is used'
|
|
199
|
-
: 'loads first and is used';
|
|
200
|
-
return {
|
|
201
|
-
name: integration.name,
|
|
202
|
-
providerId,
|
|
203
|
-
version: integration.version,
|
|
204
|
-
__spec: integration.__spec,
|
|
205
|
-
__packageDir: integration.__packageDir,
|
|
206
|
-
__manifestFile: integration.__manifestFile,
|
|
207
|
-
...(integration.__local ? {__local: true} : {}),
|
|
208
|
-
...(integration.__autolinked
|
|
209
|
-
? {__autolinked: true, __dependencyField: integration.__dependencyField}
|
|
210
|
-
: {}),
|
|
211
|
-
__providerConflict: {
|
|
212
|
-
providerId,
|
|
213
|
-
claimedBy: claimant.name,
|
|
214
|
-
message:
|
|
215
|
-
`${setAside} and ${winner} both claim provider ID "${providerId}". ` +
|
|
216
|
-
`${winner} ${reason}; ${setAside} contributes nothing ` +
|
|
217
|
-
'until one package changes its providerId.',
|
|
218
|
-
},
|
|
219
|
-
};
|
|
127
|
+
return resolveProviders(
|
|
128
|
+
integrations.map(integration => ({
|
|
129
|
+
source: /** @type {const} */ ('configured'),
|
|
130
|
+
integration,
|
|
131
|
+
spec: integration?.__spec,
|
|
132
|
+
})),
|
|
133
|
+
).integrations;
|
|
220
134
|
}
|
|
221
135
|
|
|
222
136
|
/**
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The provider ledger property: over seeded, generated projects, the
|
|
5
|
+
* real Project.load and the real `upgrade` context account for every candidate
|
|
6
|
+
* exactly once, and agree on the winner of every provider ID except where the
|
|
7
|
+
* codemod policy says otherwise.
|
|
8
|
+
*
|
|
9
|
+
* The expected candidates come from the scenario description, never from the
|
|
10
|
+
* resolver. Fixtures live under a repo-local temp dir, not /tmp, because Vite
|
|
11
|
+
* refuses to dynamically import a module from outside the project root.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import {Project, providerLedgerOf} from '../config/project.mjs';
|
|
18
|
+
import {loadProjectContext} from '../../api/upgrade/_adapter.mjs';
|
|
19
|
+
import {
|
|
20
|
+
expectedProjectInputs,
|
|
21
|
+
generateScenario,
|
|
22
|
+
isRunnable,
|
|
23
|
+
writeScenario,
|
|
24
|
+
} from '../../test-utils/provider-scenarios.mjs';
|
|
25
|
+
|
|
26
|
+
// 200 seeds keep this near 4 s; any seed range must hold.
|
|
27
|
+
const SEEDS = Array.from({length: 200}, (_, index) => index + 1);
|
|
28
|
+
const OUTCOMES = ['contributes', 'set-aside', 'load-failed', 'alias-of'];
|
|
29
|
+
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
let root;
|
|
32
|
+
/** @type {string} */
|
|
33
|
+
let originalCwd;
|
|
34
|
+
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
originalCwd = process.cwd();
|
|
37
|
+
root = fs.mkdtempSync(path.join(process.cwd(), '.astryx-provider-ledger-'));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(() => {
|
|
41
|
+
process.chdir(originalCwd);
|
|
42
|
+
fs.rmSync(root, {recursive: true, force: true});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The contributing package for each provider ID. Fails when two contribute
|
|
47
|
+
* under one ID.
|
|
48
|
+
* @param {Map<string, any>} ledger
|
|
49
|
+
* @returns {Map<string, any>}
|
|
50
|
+
*/
|
|
51
|
+
function winners(ledger) {
|
|
52
|
+
/** @type {Map<string, any>} */
|
|
53
|
+
const byProvider = new Map();
|
|
54
|
+
for (const entry of ledger.values()) {
|
|
55
|
+
if (entry.outcome !== 'contributes' || entry.providerId == null) continue;
|
|
56
|
+
expect(byProvider.has(entry.providerId)).toBe(false);
|
|
57
|
+
byProvider.set(entry.providerId, entry);
|
|
58
|
+
}
|
|
59
|
+
return byProvider;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Assert the ledger records one outcome for exactly the given inputs.
|
|
64
|
+
* @param {Map<string, any>} ledger
|
|
65
|
+
* @param {Array<{source: string, spec: string, key: string}>} inputs
|
|
66
|
+
*/
|
|
67
|
+
function expectAccounted(ledger, inputs) {
|
|
68
|
+
const refs = [...ledger.values()].flatMap(entry =>
|
|
69
|
+
entry.refs.map(ref => ({entry, ref})),
|
|
70
|
+
);
|
|
71
|
+
for (const input of inputs) {
|
|
72
|
+
const holders = refs.filter(
|
|
73
|
+
({ref}) => ref.source === input.source && ref.spec === input.spec,
|
|
74
|
+
);
|
|
75
|
+
expect(holders.map(({entry}) => entry.key)).toEqual([input.key]);
|
|
76
|
+
}
|
|
77
|
+
expect(refs).toHaveLength(inputs.length);
|
|
78
|
+
expect(new Set(ledger.keys())).toEqual(
|
|
79
|
+
new Set(inputs.map(input => input.key)),
|
|
80
|
+
);
|
|
81
|
+
for (const [key, entry] of ledger) {
|
|
82
|
+
expect(entry.key).toBe(key);
|
|
83
|
+
expect(OUTCOMES).toContain(entry.outcome);
|
|
84
|
+
if (entry.outcome === 'set-aside') {
|
|
85
|
+
expect(ledger.has(entry.winner)).toBe(true);
|
|
86
|
+
expect(entry.reason).toEqual(expect.any(String));
|
|
87
|
+
}
|
|
88
|
+
if (entry.outcome === 'alias-of') {
|
|
89
|
+
expect(ledger.has(entry.aliasOf)).toBe(true);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('provider ledger over generated projects', () => {
|
|
95
|
+
it('accounts for every candidate once and agrees across Project and upgrade', async () => {
|
|
96
|
+
const coverage = {
|
|
97
|
+
scenarios: 0,
|
|
98
|
+
setAside: 0,
|
|
99
|
+
aliasOf: 0,
|
|
100
|
+
loadFailed: 0,
|
|
101
|
+
replaced: 0,
|
|
102
|
+
agreed: 0,
|
|
103
|
+
standIn: 0,
|
|
104
|
+
extraReordered: 0,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
for (const seed of SEEDS) {
|
|
108
|
+
const scenario = generateScenario(seed);
|
|
109
|
+
if (!isRunnable(scenario)) continue;
|
|
110
|
+
coverage.scenarios++;
|
|
111
|
+
const dir = path.join(root, `seed-${seed}`);
|
|
112
|
+
fs.mkdirSync(dir);
|
|
113
|
+
writeScenario(dir, scenario);
|
|
114
|
+
const context = `seed ${seed}: ${JSON.stringify(scenario)}`;
|
|
115
|
+
|
|
116
|
+
const project = await Project.load(dir, {fresh: true});
|
|
117
|
+
const ledger = providerLedgerOf(project);
|
|
118
|
+
const inputs = expectedProjectInputs(dir, scenario);
|
|
119
|
+
try {
|
|
120
|
+
expectAccounted(ledger, inputs);
|
|
121
|
+
|
|
122
|
+
// Nothing leaves the list without a recorded reason.
|
|
123
|
+
const listed = new Set(project.loadedIntegrations);
|
|
124
|
+
for (const integration of project.loadedIntegrations) {
|
|
125
|
+
expect(ledger.has(fs.realpathSync(integration.__packageDir))).toBe(
|
|
126
|
+
true,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
for (const entry of ledger.values()) {
|
|
130
|
+
if (entry.integration != null) {
|
|
131
|
+
expect(listed.has(entry.integration)).toBe(true);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
expect(entry.outcome).not.toBe('contributes');
|
|
135
|
+
if (entry.outcome === 'set-aside') {
|
|
136
|
+
expect(entry.reason).toBe('replaced-by-authored');
|
|
137
|
+
}
|
|
138
|
+
if (entry.outcome === 'load-failed') {
|
|
139
|
+
expect(entry.error).toEqual(expect.any(String));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(`${context}\n${error}`, {cause: error});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const entry of ledger.values()) {
|
|
147
|
+
if (entry.outcome === 'set-aside') coverage.setAside++;
|
|
148
|
+
if (entry.outcome === 'alias-of') coverage.aliasOf++;
|
|
149
|
+
if (entry.outcome === 'load-failed') coverage.loadFailed++;
|
|
150
|
+
if (entry.reason === 'replaced-by-authored') coverage.replaced++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// `--integration` specs resolve from the process cwd.
|
|
154
|
+
process.chdir(dir);
|
|
155
|
+
const upgrade = await loadProjectContext(dir, scenario.extras);
|
|
156
|
+
process.chdir(originalCwd);
|
|
157
|
+
|
|
158
|
+
const modules = path.join(dir, 'node_modules');
|
|
159
|
+
/** @param {string} spec */
|
|
160
|
+
const installedKey = spec =>
|
|
161
|
+
fs.realpathSync(path.join(modules, ...spec.split('/')));
|
|
162
|
+
/** @param {string} spec */
|
|
163
|
+
const installedName = spec =>
|
|
164
|
+
JSON.parse(
|
|
165
|
+
fs.readFileSync(
|
|
166
|
+
path.join(modules, ...spec.split('/'), 'package.json'),
|
|
167
|
+
'utf8',
|
|
168
|
+
),
|
|
169
|
+
).name;
|
|
170
|
+
const configured = new Set(scenario.config ?? []);
|
|
171
|
+
const extras = [...new Set(scenario.extras)].filter(
|
|
172
|
+
spec => !configured.has(spec),
|
|
173
|
+
);
|
|
174
|
+
const {local} = scenario;
|
|
175
|
+
const localKey = fs.realpathSync(dir);
|
|
176
|
+
const selfListed = local != null && configured.has(local.name);
|
|
177
|
+
// The codemod policy: the installed copy stands in for the package being
|
|
178
|
+
// authored when it lists itself or is named with --integration.
|
|
179
|
+
const localStandsIn =
|
|
180
|
+
local != null &&
|
|
181
|
+
(selfListed || extras.some(spec => installedName(spec) === local.name));
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Upgrade accounts for everything Project did, plus its own inputs.
|
|
185
|
+
expectAccounted(upgrade.ledger, [
|
|
186
|
+
...inputs,
|
|
187
|
+
...(selfListed
|
|
188
|
+
? [
|
|
189
|
+
{
|
|
190
|
+
source: 'installed',
|
|
191
|
+
spec: local.name,
|
|
192
|
+
key: installedKey(local.name),
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
: []),
|
|
196
|
+
...extras.map(spec => ({
|
|
197
|
+
source: 'extra',
|
|
198
|
+
spec,
|
|
199
|
+
key: installedKey(spec),
|
|
200
|
+
})),
|
|
201
|
+
]);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(`${context}\n${error}`, {cause: error});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const projectWinners = winners(ledger);
|
|
207
|
+
const upgradeWinners = winners(upgrade.ledger);
|
|
208
|
+
for (const providerId of new Set([
|
|
209
|
+
...projectWinners.keys(),
|
|
210
|
+
...upgradeWinners.keys(),
|
|
211
|
+
])) {
|
|
212
|
+
const inProject = projectWinners.get(providerId);
|
|
213
|
+
const inUpgrade = upgradeWinners.get(providerId);
|
|
214
|
+
const same =
|
|
215
|
+
inProject != null &&
|
|
216
|
+
inUpgrade != null &&
|
|
217
|
+
(inProject.key === inUpgrade.key ||
|
|
218
|
+
upgrade.ledger.get(inProject.key)?.aliasOf === inUpgrade.key);
|
|
219
|
+
if (same) {
|
|
220
|
+
coverage.agreed++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// The one documented exception, asserted explicitly: an installed
|
|
224
|
+
// copy took the authored package's place, so either upgrade's winner
|
|
225
|
+
// is an installed copy of that package, or Project's winner is the
|
|
226
|
+
// authored package it replaced.
|
|
227
|
+
const upgradeName =
|
|
228
|
+
inUpgrade?.integration?.name ??
|
|
229
|
+
inUpgrade?.candidate.integration?.name;
|
|
230
|
+
const standInWins =
|
|
231
|
+
localStandsIn && inUpgrade != null && upgradeName === local?.name;
|
|
232
|
+
const localReplaced =
|
|
233
|
+
localStandsIn && inProject != null && inProject.key === localKey;
|
|
234
|
+
// Project never saw an extra, so an ID only an extra claims has no
|
|
235
|
+
// Project winner to agree with.
|
|
236
|
+
const extraOnly =
|
|
237
|
+
inProject == null && inUpgrade?.candidate.source === 'extra';
|
|
238
|
+
// Known disagreement, pinned by the `it.fails` repros in
|
|
239
|
+
// api/upgrade/provider-agreement.test.mjs: upgrade replaces an
|
|
240
|
+
// autolinked winner with any extra of the same name@version, from
|
|
241
|
+
// any directory, and orders it with the extras. Another extra can
|
|
242
|
+
// then win, or nothing does, in upgrade only.
|
|
243
|
+
const projectWinnerInUpgrade =
|
|
244
|
+
inProject == null ? undefined : upgrade.ledger.get(inProject.key);
|
|
245
|
+
const repeatedByExtra =
|
|
246
|
+
projectWinnerInUpgrade != null &&
|
|
247
|
+
(projectWinnerInUpgrade.refs.some(ref => ref.source === 'extra') ||
|
|
248
|
+
(projectWinnerInUpgrade.outcome === 'alias-of' &&
|
|
249
|
+
upgrade.ledger
|
|
250
|
+
.get(projectWinnerInUpgrade.aliasOf)
|
|
251
|
+
?.refs.some(ref => ref.source === 'extra')));
|
|
252
|
+
const extraReordered =
|
|
253
|
+
inProject?.candidate.source === 'autolinked' && repeatedByExtra;
|
|
254
|
+
if (extraReordered) {
|
|
255
|
+
coverage.extraReordered++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
expect(
|
|
259
|
+
standInWins || localReplaced || extraOnly,
|
|
260
|
+
`${context}\nprovider ${providerId}: Project ${inProject?.label ?? 'none'} vs upgrade ${inUpgrade?.label ?? 'none'}`,
|
|
261
|
+
).toBe(true);
|
|
262
|
+
if (standInWins || localReplaced) coverage.standIn++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// The generator must keep reaching every shape, or the property is vacuous.
|
|
267
|
+
expect(coverage.scenarios).toBeGreaterThanOrEqual(190);
|
|
268
|
+
expect(coverage.setAside).toBeGreaterThan(0);
|
|
269
|
+
expect(coverage.aliasOf).toBeGreaterThan(0);
|
|
270
|
+
expect(coverage.loadFailed).toBeGreaterThan(0);
|
|
271
|
+
expect(coverage.replaced).toBeGreaterThan(0);
|
|
272
|
+
expect(coverage.agreed).toBeGreaterThan(0);
|
|
273
|
+
expect(coverage.standIn).toBeGreaterThan(0);
|
|
274
|
+
}, 120_000);
|
|
275
|
+
});
|