@astryxdesign/cli 0.7.2-canary.e26723d → 0.7.2-canary.e4bdd70
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 +9 -8
- package/api/docs/_adapter.d.mts +11 -0
- package/api/docs/_adapter.mjs +37 -0
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/integration/add-contribution.mjs +1 -1
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +19 -4
- package/api/integration/validate-integration.mjs +8 -0
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +12 -1
- package/api/integration/validateIntegration.doc.mjs +17 -3
- package/api/search/search.d.mts +13 -6
- package/api/search/search.domain-priority.test.mjs +93 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +33 -9
- package/api/theme/build/build.family.test.mjs +54 -6
- package/api/theme/build/build.mjs +110 -13
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +1 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
- package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/check-the-props.doc.mjs +51 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
- package/assets/docs/tree/checks.doc.mjs +21 -13
- package/assets/docs/tree/codemods.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +3 -5
- package/assets/docs/tree/component-imports.doc.mjs +2 -2
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/docs.doc.mjs +3 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
- package/assets/docs/tree/integrations.doc.mjs +8 -0
- package/assets/docs/tree/links.doc.mjs +5 -4
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +4 -8
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +37 -21
- package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
- package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
- package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
- package/assets/docs/tree/single-component.doc.mjs +12 -4
- package/assets/docs/tree/start-a-template.doc.mjs +3 -3
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +4 -4
- package/assets/docs/tree/upgrading.doc.mjs +3 -3
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +6 -6
- package/assets/docs/tree/write-the-template-file.doc.mjs +2 -2
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/discover/discover.doc.mjs +1 -1
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +14 -12
- package/authoring/doctypes/component/component.doc.mjs +28 -25
- package/authoring/doctypes/component/type.ts +7 -5
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +11 -10
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/integration-template.doc.mjs +1 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/integration/integration.doc.mjs +4 -3
- package/clients/cli/commands/build-theme.path-safety.test.mjs +155 -21
- package/clients/cli/commands/doctor-integration-components.doc.mjs +8 -3
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
- package/clients/cli/commands/doctor-integration.test.mjs +447 -2
- package/clients/cli/commands/doctor.mjs +116 -0
- package/clients/cli/commands/integration-check.doc.mjs +85 -0
- package/clients/cli/commands/integration-check.test.mjs +267 -0
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +107 -1
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -1
- package/foundation/discovery/doc-types.mjs +2 -2
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/prop-drift.d.mts +61 -3
- package/foundation/discovery/prop-drift.mjs +559 -61
- package/foundation/discovery/prop-drift.test.mjs +1032 -10
- package/foundation/doc-compiler/doc-loads.test.mjs +2 -0
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -476,10 +476,11 @@ Every response has a `type` discriminant. The full set is below (generated from
|
|
|
476
476
|
| `doctor` | The health-check report: `checks` (each with id, label, status: pass \| warn \| fail \| info, a message, and an optional fix, always present on warn and fail) plus a `summary` of counts per status. |
|
|
477
477
|
| `integration.add` | A contribution-writer receipt: kind, name, optional root {path, created}, integration-manifest path, every affected project-relative path, written, and dryRun. |
|
|
478
478
|
| `integration.pack-check` | The packed-package check: name, version, packable, tarball {filename, fileCount, size, unpackedSize} or null, inventory {manifest, roots [{kind, path, expectedFiles, missingFiles, complete}], expectedFiles, packedFiles}, contributions {local, packed}, each null or {themes [{slug, exportName}], components, templates [{id, type, name}], codemods [{version, id}], docs, agentDocsAppend}, and issues [{code, severity, message}]. |
|
|
479
|
-
| `integration.validate` | The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}.
|
|
479
|
+
| `integration.validate` | The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}. When the manifest loads, docTypes checks each doc against its published authoring type: status (checked \| skipped), reason, typescript, docs, findings (all warnings, each naming the field and a fix), unchecked, and fixtures (test fixture docs not checked). |
|
|
480
480
|
| `integration.template-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and non-blocking Core template-id conflicts as {id, severity: warning, integrationPackage, integrationType, integrationName, coreMatches, message, command}. |
|
|
481
|
-
| `integration.component-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and non-blocking conflicts where an integration component name is also owned by Core; each conflict includes the exact package-qualified command. When the manifest loads and declares a components root, propDrift compares documented props with the TypeScript props: status (checked \| skipped), reason, typescript, components, findings (all warnings), unchecked, and propsNotChecked.
|
|
481
|
+
| `integration.component-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and non-blocking conflicts where an integration component name is also owned by Core; each conflict includes the exact package-qualified command. When the manifest loads and declares a components root, propDrift compares documented props with the TypeScript props: status (checked \| skipped), reason, typescript, components, typesCompared, typesSkipped, findings (all warnings), unchecked, typesNotCompared, and propsNotChecked. |
|
|
482
482
|
| `integration.doc-conflicts` | validated (false when no integration manifest was found, so nothing was inspected), the integration identity, structural issues, and Core doc overlaps. Each finding includes `severity` (`info` \| `error`) and `relationship` (`replaces` \| `extends` \| `accidental`). |
|
|
483
|
+
| `integration.check` | Every source check of an integration package in one result: validated (false when no integration manifest was found, so nothing was checked), the package name and version, strict, and checks, one entry per check (validate, components, templates, docs), each with check, errors, warnings, skipped, failed, and response, the envelope that check returns on its own. The totals are errors, warnings, and skipped; unchecked counts what the checks could not compare. With --strict, failures lists each warning and each piece of skipped work again as an error, in the issue format the checks use; it is empty without --strict. failed decides the exit code. |
|
|
483
484
|
|
|
484
485
|
<!-- END GENERATED: response-types -->
|
|
485
486
|
<!-- Generated by scripts/generate-cli-readme.mjs from the response-types EnumDoc. Run `pnpm -F @astryxdesign/cli readme`. -->
|
|
@@ -580,12 +581,12 @@ There is no factory: write a plain object. For editor autocomplete and
|
|
|
580
581
|
type-checking, annotate it with the `AstryxConfig` type exported from
|
|
581
582
|
`@astryxdesign/cli/authoring`.
|
|
582
583
|
|
|
583
|
-
| Field | Type | Purpose
|
|
584
|
-
| ----------------------------- | ------------------------------ |
|
|
585
|
-
| `integrations` | `string[]` | Integration package names to load (see [Integrations](#integrations)).
|
|
586
|
-
| `issuesUrl` | `string` | Where "report an issue" links point for your project. Defaults to the core issue tracker.
|
|
587
|
-
| `hooks.postCodemod` | `PostCodemodHook[]` | Commands to run after `astryx upgrade` applies codemods (e.g. reinstall, rebuild, reformat).
|
|
588
|
-
| `experimental.xle.components` | `Record<string, XleComponent>` |
|
|
584
|
+
| Field | Type | Purpose |
|
|
585
|
+
| ----------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
586
|
+
| `integrations` | `string[]` | Integration package names to load (see [Integrations](#integrations)). |
|
|
587
|
+
| `issuesUrl` | `string` | Where "report an issue" links point for your project. Defaults to the core issue tracker. |
|
|
588
|
+
| `hooks.postCodemod` | `PostCodemodHook[]` | Commands to run after `astryx upgrade` applies codemods (e.g. reinstall, rebuild, reformat). |
|
|
589
|
+
| `experimental.xle.components` | `Record<string, XleComponent>` | Custom components for the removed layout expander, keyed by component name. The CLI still validates the field, so an entry without a string `from` makes `astryx.config` invalid, but nothing reads it. |
|
|
589
590
|
|
|
590
591
|
The config is validated against a strict schema when the CLI loads it, so an
|
|
591
592
|
unknown field is a hard error rather than a silent no-op. `astryx doctor`
|
package/api/docs/_adapter.d.mts
CHANGED
|
@@ -281,6 +281,17 @@ export function resolveTopicDocs(topic: unknown, options?: {
|
|
|
281
281
|
lang: string | null;
|
|
282
282
|
entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
|
|
283
283
|
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Each doc one integration package ships, checked against the published
|
|
286
|
+
* authoring type for its kind with the project's own TypeScript (spec:AST-039
|
|
287
|
+
* FR13). Without TypeScript the report says nothing was checked.
|
|
288
|
+
* @param {any} integration a loaded integration
|
|
289
|
+
* @param {string} cwd
|
|
290
|
+
* @returns {Promise<import('../../foundation/discovery/doc-types.mjs').DocTypesReport & {fixtures?: number}>}
|
|
291
|
+
*/
|
|
292
|
+
export function integrationDocTypes(integration: any, cwd: string): Promise<import("../../foundation/discovery/doc-types.mjs").DocTypesReport & {
|
|
293
|
+
fixtures?: number;
|
|
294
|
+
}>;
|
|
284
295
|
/**
|
|
285
296
|
* A doc a link found: what a read shows of the link, and the typed doc behind
|
|
286
297
|
* it when a reference block can include it.
|
package/api/docs/_adapter.mjs
CHANGED
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
* foundation/discovery/docs-discovery, which the catalog comes from.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import * as path from 'node:path';
|
|
20
21
|
import {Project} from '../../foundation/config/project.mjs';
|
|
21
22
|
import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
|
|
23
|
+
import {checkDocTypes} from '../../foundation/discovery/doc-types.mjs';
|
|
24
|
+
import {integrationDocFiles} from '../../foundation/discovery/integration-doc-files.mjs';
|
|
22
25
|
import {
|
|
23
26
|
linkReferenceTopic,
|
|
24
27
|
lowerReferenceTopic,
|
|
@@ -1331,3 +1334,37 @@ export async function resolveTopicDocs(topic, options = {}) {
|
|
|
1331
1334
|
const node = await lowerTopic(catalog, entry, effectiveLang);
|
|
1332
1335
|
return {catalog, node, lang: effectiveLang, entry};
|
|
1333
1336
|
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Each doc one integration package ships, checked against the published
|
|
1340
|
+
* authoring type for its kind with the project's own TypeScript (spec:AST-039
|
|
1341
|
+
* FR13). Without TypeScript the report says nothing was checked.
|
|
1342
|
+
* @param {any} integration a loaded integration
|
|
1343
|
+
* @param {string} cwd
|
|
1344
|
+
* @returns {Promise<import('../../foundation/discovery/doc-types.mjs').DocTypesReport & {fixtures?: number}>}
|
|
1345
|
+
*/
|
|
1346
|
+
export async function integrationDocTypes(integration, cwd) {
|
|
1347
|
+
const packageDir = /** @type {{__packageDir?: string}} */ (integration)
|
|
1348
|
+
.__packageDir;
|
|
1349
|
+
const {files, unread, fixtures} = await integrationDocFiles(integration);
|
|
1350
|
+
const checked = await checkDocTypes(files, {
|
|
1351
|
+
packageDir: packageDir ?? cwd,
|
|
1352
|
+
resolveFrom: [cwd, packageDir],
|
|
1353
|
+
});
|
|
1354
|
+
// Test fixtures no reader serves don't ship as docs: only counted.
|
|
1355
|
+
const report =
|
|
1356
|
+
checked.status === 'checked' && fixtures > 0
|
|
1357
|
+
? {...checked, fixtures}
|
|
1358
|
+
: checked;
|
|
1359
|
+
if (report.status !== 'checked' || unread.length === 0) return report;
|
|
1360
|
+
// A root its reader cannot read serves no doc; say so rather than skip it.
|
|
1361
|
+
const rel = (/** @type {string} */ file) =>
|
|
1362
|
+
path.relative(packageDir ?? cwd, file).split(path.sep).join('/') || '.';
|
|
1363
|
+
return {
|
|
1364
|
+
...report,
|
|
1365
|
+
unchecked: [
|
|
1366
|
+
...report.unchecked,
|
|
1367
|
+
...unread.map(entry => ({doc: rel(entry.path), reason: entry.reason})),
|
|
1368
|
+
].sort((a, b) => a.doc.localeCompare(b.doc)),
|
|
1369
|
+
};
|
|
1370
|
+
}
|
package/api/index.d.mts
CHANGED
|
@@ -16,6 +16,7 @@ export { init } from "./init/init.mjs";
|
|
|
16
16
|
export { doctor } from "./doctor/doctor.mjs";
|
|
17
17
|
export { integrationAddTheme } from "./integration/add-theme.mjs";
|
|
18
18
|
export { integrationPackCheck } from "./integration/pack-check.mjs";
|
|
19
|
+
export { integrationCheck } from "./integration/check/check.mjs";
|
|
19
20
|
export { AstryxError } from "./error.mjs";
|
|
20
21
|
export { logger } from "./logger.mjs";
|
|
21
22
|
export * from "../foundation/response/batch.type.mjs";
|
|
@@ -37,6 +38,7 @@ export * from "./integration/integration-authoring.type.mjs";
|
|
|
37
38
|
export * from "./integration/pack-check.type.mjs";
|
|
38
39
|
export * from "./integration/validate-integration.type.mjs";
|
|
39
40
|
export * from "./integration/authoring-checks.type.mjs";
|
|
41
|
+
export * from "./integration/integration-check.type.mjs";
|
|
40
42
|
export type Logger = import("./logger.mjs").Logger;
|
|
41
43
|
export { themeBuild, themeAdd, themeRemove, themeUse, themeEject, themeTemplate, themeList, themeListAvailable, themeListCopySources, themeTargets, themePaletteGenerate, generateTonalPalette, listThemes } from "./theme/theme.mjs";
|
|
42
44
|
export { integrationAdd, integrationAddAgentDoc, integrationAddCodemod, integrationAddComponent, integrationAddDoc, integrationAddTemplate } from "./integration/add-contribution.mjs";
|
package/api/index.mjs
CHANGED
|
@@ -67,6 +67,7 @@ export {
|
|
|
67
67
|
integrationComponentConflicts,
|
|
68
68
|
integrationDocConflicts,
|
|
69
69
|
} from './integration/authoring-checks.mjs';
|
|
70
|
+
export {integrationCheck} from './integration/check/check.mjs';
|
|
70
71
|
export {AstryxError} from './error.mjs';
|
|
71
72
|
// The one shared logger: the `logger` instance side-effecting commands write
|
|
72
73
|
// through, plus its generated `Logger` type. Part of the public surface so an
|
|
@@ -100,3 +101,4 @@ export * from './integration/integration-authoring.type.mjs';
|
|
|
100
101
|
export * from './integration/pack-check.type.mjs';
|
|
101
102
|
export * from './integration/validate-integration.type.mjs';
|
|
102
103
|
export * from './integration/authoring-checks.type.mjs';
|
|
104
|
+
export * from './integration/integration-check.type.mjs';
|
|
@@ -695,7 +695,7 @@ async function addTemplate(name, options) {
|
|
|
695
695
|
const sourcePath = projectPath(path.relative(packageDir, sourceFile));
|
|
696
696
|
const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
|
|
697
697
|
const blockFields = templateType === 'block' ? '\n aspectRatio: 1,' : '';
|
|
698
|
-
const specContents = `/** @type {import('@astryxdesign/cli/authoring').
|
|
698
|
+
const specContents = `/** @type {import('@astryxdesign/cli/authoring').IntegrationTemplateDoc} */\nexport default {\n type: '${templateType}',\n name: '${name}',\n displayName: '${kebabToTitle(name)}',\n description: '${kebabToTitle(name)} template.',${blockFields}\n};\n`;
|
|
699
699
|
const sourceContents = `export default function ${pascalName}() {\n return <div>${kebabToTitle(name)}</div>;\n}\n`;
|
|
700
700
|
|
|
701
701
|
/** @type {import('./add-helpers.mjs').WritePlan[]} */
|
|
@@ -536,7 +536,9 @@ describe('integrationAdd template', () => {
|
|
|
536
536
|
path.join(tmpDir, 'templates/my-widget.doc.mjs'),
|
|
537
537
|
'utf-8',
|
|
538
538
|
);
|
|
539
|
-
expect(spec).toContain(
|
|
539
|
+
expect(spec).toContain(
|
|
540
|
+
"@astryxdesign/cli/authoring').IntegrationTemplateDoc",
|
|
541
|
+
);
|
|
540
542
|
expect(spec).toContain("type: 'page'");
|
|
541
543
|
expect(spec).toContain("name: 'my-widget'");
|
|
542
544
|
|
|
@@ -830,7 +832,7 @@ describe('typed descriptor conformance', () => {
|
|
|
830
832
|
for (const [relativePath, type] of [
|
|
831
833
|
['components/MyWidget.doc.mjs', 'ComponentDoc'],
|
|
832
834
|
['docs/my-guide.doc.mjs', 'ReferenceDoc'],
|
|
833
|
-
['templates/my-page.doc.mjs', '
|
|
835
|
+
['templates/my-page.doc.mjs', 'IntegrationTemplateDoc'],
|
|
834
836
|
['themes/ocean/oceanTheme.doc.mjs', 'ThemeDoc'],
|
|
835
837
|
]) {
|
|
836
838
|
const source = fs.readFileSync(path.join(tmpDir, relativePath), 'utf-8');
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The code a skipped comparison carries under `--strict`: its key in the
|
|
6
|
+
* check's result, in snake case, then `_skipped` (`propDrift` is
|
|
7
|
+
* `prop_drift_skipped`).
|
|
8
|
+
* @param {string} key
|
|
9
|
+
*/
|
|
10
|
+
export function skippedCode(key: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a value is a report a check carries beside its issues: a
|
|
13
|
+
* comparison with a `status` of `checked` or `skipped` and its `findings`.
|
|
14
|
+
* @param {unknown} value
|
|
15
|
+
* @returns {value is IntegrationCheckReport}
|
|
16
|
+
*/
|
|
17
|
+
export function isCheckReport(value: unknown): value is IntegrationCheckReport;
|
|
18
|
+
/**
|
|
19
|
+
* Every warning in one check's result, and every piece of work it skipped,
|
|
20
|
+
* as issues: what `--strict` reports as errors. The counts come from the
|
|
21
|
+
* same list, so the default run and the strict run read the same data.
|
|
22
|
+
* @param {IntegrationCheckName} check
|
|
23
|
+
* @param {IntegrationCheckResponse} response
|
|
24
|
+
* @param {{noPackage?: boolean}} [context] `noPackage`: validate found no
|
|
25
|
+
* package.json, which only it reports; then no other check names a
|
|
26
|
+
* missing manifest as skipped work
|
|
27
|
+
* @returns {{warnings: Array<Omit<IntegrationCheckFailure, 'check' | 'severity'>>, skipped: Array<Omit<IntegrationCheckFailure, 'check' | 'severity'>>}}
|
|
28
|
+
*/
|
|
29
|
+
export function strictIssues(check: IntegrationCheckName, response: IntegrationCheckResponse, context?: {
|
|
30
|
+
noPackage?: boolean;
|
|
31
|
+
}): {
|
|
32
|
+
warnings: Array<Omit<IntegrationCheckFailure, "check" | "severity">>;
|
|
33
|
+
skipped: Array<Omit<IntegrationCheckFailure, "check" | "severity">>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Count one check's result the way its own subcommand exits. Errors are what
|
|
37
|
+
* make it exit 1: error-severity issues, and for `docs` error findings.
|
|
38
|
+
* Warnings are warning issues, conflicts and findings, and the findings of
|
|
39
|
+
* any report the check carries. Skipped names work that did not run: no
|
|
40
|
+
* integration manifest, or a report that was skipped.
|
|
41
|
+
* @param {IntegrationCheckResponse} response
|
|
42
|
+
* @param {{noPackage?: boolean}} [context] as {@link strictIssues} takes it
|
|
43
|
+
* @returns {Omit<IntegrationCheckResult, 'check' | 'response'>}
|
|
44
|
+
*/
|
|
45
|
+
export function summarizeCheck(response: IntegrationCheckResponse, context?: {
|
|
46
|
+
noPackage?: boolean;
|
|
47
|
+
}): Omit<IntegrationCheckResult, "check" | "response">;
|
|
48
|
+
/**
|
|
49
|
+
* Run every `doctor integration` check against the integration package in
|
|
50
|
+
* `cwd`, as its own build does, and return the `integration.check` envelope.
|
|
51
|
+
*
|
|
52
|
+
* @param {IntegrationCheckOptions} [options]
|
|
53
|
+
* @returns {Promise<IntegrationCheckCommandResponse>}
|
|
54
|
+
*/
|
|
55
|
+
export function integrationCheck(options?: IntegrationCheckOptions): Promise<IntegrationCheckCommandResponse>;
|
|
56
|
+
/**
|
|
57
|
+
* Count the four checks' results into one summary. Each check's own row
|
|
58
|
+
* counts as its subcommand exits; the totals and `failures` count validate's
|
|
59
|
+
* issues once, though the other checks repeat them, and every other finding
|
|
60
|
+
* on its own, so two findings with one message stay two.
|
|
61
|
+
* @param {IntegrationCheckResponse[]} responses validate first
|
|
62
|
+
* @param {boolean} strict
|
|
63
|
+
*/
|
|
64
|
+
export function combineChecks(responses: IntegrationCheckResponse[], strict: boolean): {
|
|
65
|
+
checks: import("../integration-check.type.mjs").IntegrationCheckResult[];
|
|
66
|
+
errors: number;
|
|
67
|
+
warnings: number;
|
|
68
|
+
skipped: string[];
|
|
69
|
+
unchecked: number;
|
|
70
|
+
failures: import("../integration-check.type.mjs").IntegrationCheckFailure[];
|
|
71
|
+
failed: boolean;
|
|
72
|
+
};
|
|
73
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckName} IntegrationCheckName */
|
|
74
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckReport} IntegrationCheckReport */
|
|
75
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckResponse} IntegrationCheckResponse */
|
|
76
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckResult} IntegrationCheckResult */
|
|
77
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckOptions} IntegrationCheckOptions */
|
|
78
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckCommandResponse} IntegrationCheckCommandResponse */
|
|
79
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckFailure} IntegrationCheckFailure */
|
|
80
|
+
/** The checks `integration check` runs, in the order it runs them. */
|
|
81
|
+
export const INTEGRATION_CHECKS: readonly ["validate", "components", "templates", "docs"];
|
|
82
|
+
/** The skipped-work line for a package with no integration manifest. */
|
|
83
|
+
export const NO_MANIFEST_SKIP: "No astryx.integration.* found next to package.json, so nothing was checked.";
|
|
84
|
+
export namespace CONFLICT_CODES {
|
|
85
|
+
let components: "component_name_conflict";
|
|
86
|
+
let templates: "template_id_conflict";
|
|
87
|
+
}
|
|
88
|
+
/** The code a check with no integration manifest carries under `--strict`. */
|
|
89
|
+
export const NO_MANIFEST_CODE: "missing_manifest";
|
|
90
|
+
export type IntegrationCheckName = import("../integration-check.type.mjs").IntegrationCheckName;
|
|
91
|
+
export type IntegrationCheckReport = import("../integration-check.type.mjs").IntegrationCheckReport;
|
|
92
|
+
export type IntegrationCheckResponse = import("../integration-check.type.mjs").IntegrationCheckResponse;
|
|
93
|
+
export type IntegrationCheckResult = import("../integration-check.type.mjs").IntegrationCheckResult;
|
|
94
|
+
export type IntegrationCheckOptions = import("../integration-check.type.mjs").IntegrationCheckOptions;
|
|
95
|
+
export type IntegrationCheckCommandResponse = import("../integration-check.type.mjs").IntegrationCheckCommandResponse;
|
|
96
|
+
export type IntegrationCheckFailure = import("../integration-check.type.mjs").IntegrationCheckFailure;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `astryx integration check`: every `doctor integration` check in one
|
|
5
|
+
* call, for a package's own build.
|
|
6
|
+
*
|
|
7
|
+
* Runs `validate`, `components`, `templates` and `docs` against the
|
|
8
|
+
* integration package in `cwd` and counts each result the way that check's
|
|
9
|
+
* own subcommand exits, so the
|
|
10
|
+
* run fails exactly when one of them would. With `strict` it also fails on
|
|
11
|
+
* any warning and on any check, or comparison a check carries, that did not
|
|
12
|
+
* run, so a package can gate its build on its docs staying in sync. `--strict`
|
|
13
|
+
* adds no check: it reports the same warnings and skipped work again, as
|
|
14
|
+
* errors in the checks' own issue format, and fails on them.
|
|
15
|
+
*
|
|
16
|
+
* What fails it is a contract for the builds that run it: a new check, or a
|
|
17
|
+
* new failure code, joins only in a minor release that names it.
|
|
18
|
+
* `test/integration-check-failures.test.mjs` lists each one, so adding one
|
|
19
|
+
* shows up in review.
|
|
20
|
+
*
|
|
21
|
+
* @input A local or installed integration package.
|
|
22
|
+
* @output Each check's own envelope with its errors, warnings and skipped
|
|
23
|
+
* work, the totals, and whether the run fails.
|
|
24
|
+
* @position Backs `astryx integration check`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
integrationComponentConflicts,
|
|
29
|
+
integrationDocConflicts,
|
|
30
|
+
integrationTemplateConflicts,
|
|
31
|
+
} from '../authoring-checks.mjs';
|
|
32
|
+
import {
|
|
33
|
+
summarizeIssues,
|
|
34
|
+
validateIntegration,
|
|
35
|
+
} from '../validate-integration.mjs';
|
|
36
|
+
|
|
37
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckName} IntegrationCheckName */
|
|
38
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckReport} IntegrationCheckReport */
|
|
39
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckResponse} IntegrationCheckResponse */
|
|
40
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckResult} IntegrationCheckResult */
|
|
41
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckOptions} IntegrationCheckOptions */
|
|
42
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckCommandResponse} IntegrationCheckCommandResponse */
|
|
43
|
+
/** @typedef {import('../integration-check.type.mjs').IntegrationCheckFailure} IntegrationCheckFailure */
|
|
44
|
+
|
|
45
|
+
/** The checks `integration check` runs, in the order it runs them. */
|
|
46
|
+
export const INTEGRATION_CHECKS = /** @type {const} */ ([
|
|
47
|
+
'validate',
|
|
48
|
+
'components',
|
|
49
|
+
'templates',
|
|
50
|
+
'docs',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/** The skipped-work line for a package with no integration manifest. */
|
|
54
|
+
export const NO_MANIFEST_SKIP =
|
|
55
|
+
'No astryx.integration.* found next to package.json, so nothing was checked.';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The code a conflict carries when `--strict` reports it: conflicts have no
|
|
59
|
+
* code of their own.
|
|
60
|
+
*/
|
|
61
|
+
export const CONFLICT_CODES = /** @type {const} */ ({
|
|
62
|
+
components: 'component_name_conflict',
|
|
63
|
+
templates: 'template_id_conflict',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** The code a check with no integration manifest carries under `--strict`. */
|
|
67
|
+
export const NO_MANIFEST_CODE = 'missing_manifest';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The code a skipped comparison carries under `--strict`: its key in the
|
|
71
|
+
* check's result, in snake case, then `_skipped` (`propDrift` is
|
|
72
|
+
* `prop_drift_skipped`).
|
|
73
|
+
* @param {string} key
|
|
74
|
+
*/
|
|
75
|
+
export function skippedCode(key) {
|
|
76
|
+
return `${key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()}_skipped`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Each check's envelope type, by check. */
|
|
80
|
+
const CHECK_OF_TYPE = /** @type {const} */ ({
|
|
81
|
+
'integration.validate': 'validate',
|
|
82
|
+
'integration.component-conflicts': 'components',
|
|
83
|
+
'integration.template-conflicts': 'templates',
|
|
84
|
+
'integration.doc-conflicts': 'docs',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether a value is a report a check carries beside its issues: a
|
|
89
|
+
* comparison with a `status` of `checked` or `skipped` and its `findings`.
|
|
90
|
+
* @param {unknown} value
|
|
91
|
+
* @returns {value is IntegrationCheckReport}
|
|
92
|
+
*/
|
|
93
|
+
export function isCheckReport(value) {
|
|
94
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const report = /** @type {{status?: unknown, findings?: unknown}} */ (value);
|
|
98
|
+
return (
|
|
99
|
+
(report.status === 'checked' || report.status === 'skipped') &&
|
|
100
|
+
Array.isArray(report.findings)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* One finding as an issue: its own code, message, fix, file, and line where
|
|
106
|
+
* it has them.
|
|
107
|
+
* @param {any} finding
|
|
108
|
+
* @param {string} code
|
|
109
|
+
* @returns {Omit<IntegrationCheckFailure, 'check' | 'severity'>}
|
|
110
|
+
*/
|
|
111
|
+
function asIssue(finding, code) {
|
|
112
|
+
const file = finding.file ?? finding.doc ?? finding.source;
|
|
113
|
+
return {
|
|
114
|
+
code,
|
|
115
|
+
message: String(finding.message ?? ''),
|
|
116
|
+
...(typeof finding.component === 'string'
|
|
117
|
+
? {component: finding.component}
|
|
118
|
+
: {}),
|
|
119
|
+
...(typeof finding.prop === 'string' ? {prop: finding.prop} : {}),
|
|
120
|
+
...(typeof finding.fix === 'string' ? {fix: finding.fix} : {}),
|
|
121
|
+
...(typeof file === 'string' ? {file} : {}),
|
|
122
|
+
...(typeof finding.line === 'number' ? {line: finding.line} : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Every warning in one check's result, and every piece of work it skipped,
|
|
128
|
+
* as issues: what `--strict` reports as errors. The counts come from the
|
|
129
|
+
* same list, so the default run and the strict run read the same data.
|
|
130
|
+
* @param {IntegrationCheckName} check
|
|
131
|
+
* @param {IntegrationCheckResponse} response
|
|
132
|
+
* @param {{noPackage?: boolean}} [context] `noPackage`: validate found no
|
|
133
|
+
* package.json, which only it reports; then no other check names a
|
|
134
|
+
* missing manifest as skipped work
|
|
135
|
+
* @returns {{warnings: Array<Omit<IntegrationCheckFailure, 'check' | 'severity'>>, skipped: Array<Omit<IntegrationCheckFailure, 'check' | 'severity'>>}}
|
|
136
|
+
*/
|
|
137
|
+
export function strictIssues(check, response, context = {}) {
|
|
138
|
+
const data =
|
|
139
|
+
/** @type {Record<string, unknown> & IntegrationCheckResponse['data']} */ (
|
|
140
|
+
response.data
|
|
141
|
+
);
|
|
142
|
+
const warnings = [];
|
|
143
|
+
for (const issue of data.issues) {
|
|
144
|
+
if (issue.severity === 'warning') {
|
|
145
|
+
warnings.push(asIssue(issue, issue.code));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const conflictCode = /** @type {Record<string, string>} */ (CONFLICT_CODES)[
|
|
149
|
+
check
|
|
150
|
+
];
|
|
151
|
+
for (const conflict of /** @type {any[]} */ (
|
|
152
|
+
Array.isArray(data.conflicts) ? data.conflicts : []
|
|
153
|
+
)) {
|
|
154
|
+
if (conflict.severity === 'warning' && conflictCode) {
|
|
155
|
+
warnings.push(asIssue(conflict, conflictCode));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const finding of /** @type {any[]} */ (
|
|
159
|
+
Array.isArray(data.findings) ? data.findings : []
|
|
160
|
+
)) {
|
|
161
|
+
if (finding.severity === 'warning') {
|
|
162
|
+
warnings.push(asIssue(finding, finding.code ?? finding.relationship));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const skipped = [];
|
|
166
|
+
const noPackage =
|
|
167
|
+
context.noPackage === true ||
|
|
168
|
+
data.issues.some(issue => issue.code === 'no_package');
|
|
169
|
+
if (!data.validated && !noPackage) {
|
|
170
|
+
skipped.push({code: NO_MANIFEST_CODE, message: NO_MANIFEST_SKIP});
|
|
171
|
+
}
|
|
172
|
+
for (const [key, value] of Object.entries(data)) {
|
|
173
|
+
if (!isCheckReport(value)) continue;
|
|
174
|
+
if (value.status === 'skipped') {
|
|
175
|
+
skipped.push({
|
|
176
|
+
code: skippedCode(key),
|
|
177
|
+
message: value.reason ?? `${key} did not run`,
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
for (const finding of /** @type {any[]} */ (value.findings)) {
|
|
182
|
+
if ((finding.severity ?? 'warning') !== 'warning') continue;
|
|
183
|
+
warnings.push(
|
|
184
|
+
asIssue(finding, finding.code ?? finding.problem ?? finding.kind),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
// A comparison that ran but judged no prop type (a TypeScript older than
|
|
188
|
+
// the type comparison reads) skipped that work.
|
|
189
|
+
const typesSkipped = /** @type {{typesSkipped?: unknown}} */ (value)
|
|
190
|
+
.typesSkipped;
|
|
191
|
+
if (typeof typesSkipped === 'string') {
|
|
192
|
+
skipped.push({
|
|
193
|
+
code: 'prop_types_skipped',
|
|
194
|
+
message: `prop types not judged: ${typesSkipped}`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {warnings, skipped};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Count one check's result the way its own subcommand exits. Errors are what
|
|
203
|
+
* make it exit 1: error-severity issues, and for `docs` error findings.
|
|
204
|
+
* Warnings are warning issues, conflicts and findings, and the findings of
|
|
205
|
+
* any report the check carries. Skipped names work that did not run: no
|
|
206
|
+
* integration manifest, or a report that was skipped.
|
|
207
|
+
* @param {IntegrationCheckResponse} response
|
|
208
|
+
* @param {{noPackage?: boolean}} [context] as {@link strictIssues} takes it
|
|
209
|
+
* @returns {Omit<IntegrationCheckResult, 'check' | 'response'>}
|
|
210
|
+
*/
|
|
211
|
+
export function summarizeCheck(response, context = {}) {
|
|
212
|
+
const check = /** @type {IntegrationCheckName} */ (
|
|
213
|
+
CHECK_OF_TYPE[response.type]
|
|
214
|
+
);
|
|
215
|
+
let errors = summarizeIssues(response.data.issues).errors;
|
|
216
|
+
// The docs check is the one whose findings fail it.
|
|
217
|
+
if (response.type === 'integration.doc-conflicts') {
|
|
218
|
+
errors += response.data.findings.filter(
|
|
219
|
+
finding => finding.severity === 'error',
|
|
220
|
+
).length;
|
|
221
|
+
}
|
|
222
|
+
const {warnings, skipped} = strictIssues(check, response, context);
|
|
223
|
+
return {
|
|
224
|
+
errors,
|
|
225
|
+
warnings: warnings.length,
|
|
226
|
+
skipped: skipped.map(each => each.message),
|
|
227
|
+
failed: errors > 0,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Run every `doctor integration` check against the integration package in
|
|
233
|
+
* `cwd`, as its own build does, and return the `integration.check` envelope.
|
|
234
|
+
*
|
|
235
|
+
* @param {IntegrationCheckOptions} [options]
|
|
236
|
+
* @returns {Promise<IntegrationCheckCommandResponse>}
|
|
237
|
+
*/
|
|
238
|
+
export async function integrationCheck(options = {}) {
|
|
239
|
+
const {cwd = process.cwd(), strict = false} = options;
|
|
240
|
+
// One after another: each loads the package through the same discovery.
|
|
241
|
+
/** @type {IntegrationCheckResponse[]} */
|
|
242
|
+
const responses = [];
|
|
243
|
+
responses.push(await validateIntegration(undefined, {cwd}));
|
|
244
|
+
responses.push(await integrationComponentConflicts(undefined, {cwd}));
|
|
245
|
+
responses.push(await integrationTemplateConflicts(undefined, {cwd}));
|
|
246
|
+
responses.push(await integrationDocConflicts(undefined, {cwd}));
|
|
247
|
+
|
|
248
|
+
const [validation] = responses;
|
|
249
|
+
return {
|
|
250
|
+
type: 'integration.check',
|
|
251
|
+
data: {
|
|
252
|
+
validated: validation.data.validated,
|
|
253
|
+
name: validation.data.name,
|
|
254
|
+
version: validation.data.version,
|
|
255
|
+
strict,
|
|
256
|
+
...combineChecks(responses, strict),
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Count the four checks' results into one summary. Each check's own row
|
|
263
|
+
* counts as its subcommand exits; the totals and `failures` count validate's
|
|
264
|
+
* issues once, though the other checks repeat them, and every other finding
|
|
265
|
+
* on its own, so two findings with one message stay two.
|
|
266
|
+
* @param {IntegrationCheckResponse[]} responses validate first
|
|
267
|
+
* @param {boolean} strict
|
|
268
|
+
*/
|
|
269
|
+
export function combineChecks(responses, strict) {
|
|
270
|
+
const [validation] = responses;
|
|
271
|
+
// Only validate reports a missing package.json; the other checks read it
|
|
272
|
+
// from there instead of each naming a missing manifest.
|
|
273
|
+
const noPackage = validation.data.issues.some(
|
|
274
|
+
issue => issue.code === 'no_package',
|
|
275
|
+
);
|
|
276
|
+
/** @type {IntegrationCheckResult[]} */
|
|
277
|
+
const checks = responses.map(response => ({
|
|
278
|
+
check: /** @type {IntegrationCheckName} */ (CHECK_OF_TYPE[response.type]),
|
|
279
|
+
...summarizeCheck(response, {noPackage}),
|
|
280
|
+
response,
|
|
281
|
+
}));
|
|
282
|
+
// Later checks repeat validate's issues and its missing manifest: each
|
|
283
|
+
// match is consumed once per check, as from a multiset. Every other issue
|
|
284
|
+
// counts, even a repeat within one check.
|
|
285
|
+
const keyOf = (/** @type {string} */ kind, /** @type {object} */ issue) =>
|
|
286
|
+
`${kind}\0${JSON.stringify(issue)}`;
|
|
287
|
+
const validateFound = strictIssues('validate', validation, {noPackage});
|
|
288
|
+
/** @type {Map<string, number>} */
|
|
289
|
+
const fromValidate = new Map();
|
|
290
|
+
for (const key of [
|
|
291
|
+
...validation.data.issues
|
|
292
|
+
.filter(issue => issue.severity === 'error')
|
|
293
|
+
.map(issue => keyOf('error', asIssue(issue, issue.code))),
|
|
294
|
+
...validateFound.warnings.map(issue => keyOf('warning', issue)),
|
|
295
|
+
...validateFound.skipped.map(issue => keyOf('skipped', issue)),
|
|
296
|
+
]) {
|
|
297
|
+
fromValidate.set(key, (fromValidate.get(key) ?? 0) + 1);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let errors = 0;
|
|
301
|
+
let warnings = 0;
|
|
302
|
+
let unchecked = 0;
|
|
303
|
+
/** @type {string[]} */
|
|
304
|
+
const skipped = [];
|
|
305
|
+
// `--strict` adds no check: it reports the same warnings and skipped work
|
|
306
|
+
// as errors, and they fail the run.
|
|
307
|
+
/** @type {IntegrationCheckFailure[]} */
|
|
308
|
+
const failures = [];
|
|
309
|
+
checks.forEach(({check, response}, index) => {
|
|
310
|
+
/** @type {Map<string, number>} */
|
|
311
|
+
const repeats = index === 0 ? new Map() : new Map(fromValidate);
|
|
312
|
+
const isRepeat = (/** @type {string} */ key) => {
|
|
313
|
+
const left = repeats.get(key) ?? 0;
|
|
314
|
+
if (left === 0) return false;
|
|
315
|
+
repeats.set(key, left - 1);
|
|
316
|
+
return true;
|
|
317
|
+
};
|
|
318
|
+
for (const issue of response.data.issues) {
|
|
319
|
+
if (
|
|
320
|
+
issue.severity === 'error' &&
|
|
321
|
+
!isRepeat(keyOf('error', asIssue(issue, issue.code)))
|
|
322
|
+
) {
|
|
323
|
+
errors++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (response.type === 'integration.doc-conflicts') {
|
|
327
|
+
errors += response.data.findings.filter(
|
|
328
|
+
finding => finding.severity === 'error',
|
|
329
|
+
).length;
|
|
330
|
+
}
|
|
331
|
+
for (const value of Object.values(response.data)) {
|
|
332
|
+
const list = /** @type {{unchecked?: unknown}} */ (value)?.unchecked;
|
|
333
|
+
if (isCheckReport(value) && Array.isArray(list)) unchecked += list.length;
|
|
334
|
+
}
|
|
335
|
+
const found =
|
|
336
|
+
index === 0 ? validateFound : strictIssues(check, response, {noPackage});
|
|
337
|
+
for (const issue of found.warnings) {
|
|
338
|
+
if (isRepeat(keyOf('warning', issue))) continue;
|
|
339
|
+
warnings++;
|
|
340
|
+
if (strict) failures.push({check, severity: 'error', ...issue});
|
|
341
|
+
}
|
|
342
|
+
for (const issue of found.skipped) {
|
|
343
|
+
if (isRepeat(keyOf('skipped', issue))) continue;
|
|
344
|
+
skipped.push(`${check}: ${issue.message}`);
|
|
345
|
+
if (strict) failures.push({check, severity: 'error', ...issue});
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
return {
|
|
349
|
+
checks,
|
|
350
|
+
errors,
|
|
351
|
+
warnings,
|
|
352
|
+
skipped,
|
|
353
|
+
unchecked,
|
|
354
|
+
failures,
|
|
355
|
+
failed: checks.some(check => check.failed) || failures.length > 0,
|
|
356
|
+
};
|
|
357
|
+
}
|