@hmos-library-adapter/fast 0.0.17-beta.0 → 0.0.17-beta.1
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/node_modules/@hmos-library-adapter/executer/README.md +2 -2
- package/node_modules/@hmos-library-adapter/executer/shared/skill-map.json +5 -1
- package/node_modules/@hmos-library-adapter/executer/shared/skills/arkts-rules/SKILL.md +400 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/CHANGELOG.md +1391 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/COMPATIBILITY.md +53 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/README.md +123 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/SKILL.md +48 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/agents/upgrade-assess-change.md +37 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/agents/upgrade-fix-file.md +23 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/package.json +19 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/26.0.0/beta1-changes-for-all-apps.md +1793 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/26.0.0/beta1-ux-style-and-effect-changes.md +288 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/26.0.0/beta2-changes-for-all-apps.md +148 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/26.0.0/release-changes-for-all-apps.md +662 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.1(13)/beta3-os-api-behavior-changes.md +652 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.1(13)/beta3-ux-style-and-effect-changes.md +222 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.1(13)/release-os-api-behavior-changes.md +444 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.1(13)/release-ux-style-and-effect-changes.md +221 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.2(14)/os-api-behavior-changes.md +1767 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.2(14)/ux-style-and-effect-changes.md +134 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.3(15)/beta1-os-api-behavior-changes.md +430 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.3(15)/beta1-ux-style-and-effect-changes.md +49 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.3(15)/beta2-os-api-behavior-changes.md +240 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.3(15)/beta2-ux-style-and-effect-changes.md +90 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.0.3(15)/release-os-api-behavior-changes.md +69 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.1.0(18)/os-api-behavior-changes.md +1294 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/5.1.0(18)/ux-style-and-effect-changes.md +147 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta1-os-api-behavior-changes.md +1019 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta1-ux-style-and-effect-changes.md +153 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta2-os-api-behavior-changes.md +662 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta2-ux-style-and-effect-changes.md +78 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta3-os-api-behavior-changes.md +167 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.0.0(20)/beta5-os-api-behavior-changes.md +52 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs/6.1.0(23)/changes-for-all-apps.md +397 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/changelogs-index.json +883 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/arkts-coding-standards.md +53 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/compatibility.md +207 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/native-adaptation.md +21 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/runtime-functional-tests.md +62 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/sdk-versions.md +77 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/troubleshooting.md +54 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/verification.md +79 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/common/workflow-scan.md +41 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/deprecated/context-migration.md +274 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/deprecated/deprecated-api-adaptation.md +157 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/deprecated/deprecated-api-examples.md +219 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/toolchain/hvigor.md +50 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/upgrade/har-upgrade.md +60 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/upgrade/scanner-missed-cases.md +171 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/upgrade/upgrade-compatibility.md +112 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/upgrade/upgrade-examples.md +289 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/references/upgrade/upgrade-workflow.md +122 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/references/generate-behavior-policy.mjs +96 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/references/references.mjs +9 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/regression/compare-deprecated.mjs +57 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/regression/compare-golden.mjs +130 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/regression/regress-deprecated.mjs +120 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/behavior-catalog-generation.test.mjs +59 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/behavior-local-sources.test.mjs +44 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/changelog-index.test.mjs +66 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/compare-golden.test.mjs +103 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/fixtures/behavior/local-sources.csv +5 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/references.test.mjs +16 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/skill-content.test.mjs +139 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/skill-frontmatter.test.mjs +11 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/test/test-suite.test.mjs +65 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/validation/skill-content.mjs +125 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/validation/skill-frontmatter.mjs +6 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/maintenance/validation/validate-skill.mjs +57 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/CHANGELOG.md +506 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/GOLDEN_VALIDATION.md +42 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/README.md +137 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/behavior/behavior-policy.mjs +117 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/behavior/upgrade-coverage.mjs +29 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/bin/deveco-worker-compat.cjs +21 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/bin/harmony-api-scan.mjs +17 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/cli.mjs +416 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/deveco.mjs +348 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/project.mjs +81 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/rules.mjs +425 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/runtime-identity.mjs +23 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/scan-runner.mjs +521 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/core/sdk-deprecations.mjs +204 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/data/behavior-change-policy.json +4378 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/deprecated/arkts-deprecations.mjs +363 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/deprecated/build-log.mjs +72 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/native/native.mjs +95 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/package.json +16 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/batch-layout.test.mjs +94 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/behavior-policy.test.mjs +75 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/build-log.test.mjs +69 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/cli.test.mjs +102 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/csv.test.mjs +35 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/deprecations.test.mjs +283 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/deveco.test.mjs +262 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/helpers/scan-batch.mjs +10 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/native.test.mjs +108 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/project.test.mjs +42 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/rules.test.mjs +190 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/runtime-identity.test.mjs +53 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/scan-mode.test.mjs +38 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/scan-runner.test.mjs +481 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/scan-scope.test.mjs +110 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/source-inputs.test.mjs +64 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/scanner/test/upgrade.test.mjs +164 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/format/args.mjs +66 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/format/csv.mjs +85 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/format/json5.mjs +92 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/format/upgrade-results.mjs +45 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/format/zip.mjs +87 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/io/fs-utils.mjs +60 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/artifact-layout.mjs +74 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/changelog-index.mjs +29 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/deveco-version.mjs +40 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/document-links.mjs +30 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/errors.mjs +41 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/fs.mjs +72 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/paths.mjs +34 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/process.mjs +154 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/scan-mode.mjs +7 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/scan-statistics.mjs +7 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/scan-version-catalog.json +43 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/scan-versions.mjs +51 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/sdk-layout.mjs +27 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/source-scope.mjs +108 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/source-types.mjs +7 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/platform/version.mjs +76 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/deveco-version.test.mjs +50 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/document-links.test.mjs +13 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/errors.test.mjs +40 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/fs-utils.test.mjs +31 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/platform-paths.test.mjs +19 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/process-utils.test.mjs +68 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/scan-versions.test.mjs +26 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/sdk-version.test.mjs +17 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/shared/test/upgrade-results.test.mjs +140 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/scripts/test-suite.mjs +29 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/deprecated-report.md +86 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/progress.md +150 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/results.md +110 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/upgrade-report.md +105 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/upgrade-spec.md +58 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-api-upgrade-adapter/templates/upgrade-test-report.md +47 -0
- package/node_modules/@hmos-library-adapter/executer/shared/skills/harmonyos-knowledge/SKILL.md +70 -0
- package/node_modules/@hmos-library-adapter/executer/src/config.js +3 -4
- package/node_modules/@hmos-library-adapter/executer/src/doctor.js +1 -1
- package/node_modules/@hmos-library-adapter/executer/src/install.js +1 -1
- package/node_modules/@hmos-library-adapter/executer/src/paths.js +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseJson5 } from '../../shared/format/json5.mjs';
|
|
4
|
+
import { sha256, portableRelative } from '../../shared/io/fs-utils.mjs';
|
|
5
|
+
import { catalogEntry, declarationEvidence, deprecatedDocUrl } from '../core/sdk-deprecations.mjs';
|
|
6
|
+
import { hyperlink } from '../../shared/format/csv.mjs';
|
|
7
|
+
import { canonicalHarmonyVersion as canonicalVersion } from '../../shared/platform/version.mjs';
|
|
8
|
+
import { normalizeComparablePath } from '../../shared/platform/paths.mjs';
|
|
9
|
+
import { isArkTsSource } from '../../shared/platform/source-types.mjs';
|
|
10
|
+
|
|
11
|
+
function realFile(file) {
|
|
12
|
+
return existsSync(file) ? realpathSync.native(file) : file;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function compilerPathIdentity(file, platform = process.platform) {
|
|
16
|
+
return normalizeComparablePath(file, platform);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function projectModuleForFile(project, file, platform = process.platform) {
|
|
20
|
+
const api = platform === 'win32' ? path.win32 : path.posix;
|
|
21
|
+
return project.modules.find((module) => {
|
|
22
|
+
const relation = api.relative(module.root, file);
|
|
23
|
+
return relation !== '' && relation !== '..'
|
|
24
|
+
&& !relation.startsWith(`..${api.sep}`) && !api.isAbsolute(relation);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createArkTsProgram(project, catalog, { overlays = new Map(), compilerOptions = {}, builder = false } = {}) {
|
|
29
|
+
const { ts, toolchain } = catalog;
|
|
30
|
+
const options = { noEmit: true, skipLibCheck: true, target: ts.ScriptTarget.ES2021,
|
|
31
|
+
module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.NodeJs, allowNonTsExtensions: true, etsAnnotationsEnable: true, ets: catalog.etsOptions, ...compilerOptions };
|
|
32
|
+
const host = ts.createCompilerHost(options);
|
|
33
|
+
host.getCanonicalFileName = file => compilerPathIdentity(file);
|
|
34
|
+
const overlayFiles = new Map([...overlays].map(([file, text]) => [host.getCanonicalFileName(file), { file, text }]));
|
|
35
|
+
const originalExists = host.fileExists.bind(host);
|
|
36
|
+
host.fileExists = file => overlayFiles.has(host.getCanonicalFileName(file)) || originalExists(file);
|
|
37
|
+
const canonicalFiles = new Map([...project.sourceFiles, ...catalog.sourceFiles.keys()]
|
|
38
|
+
.map(file => [host.getCanonicalFileName(file), file]));
|
|
39
|
+
const sdkSourceFiles = new Map([...catalog.sourceFiles]
|
|
40
|
+
.map(([file, sourceFile]) => [host.getCanonicalFileName(file), sourceFile]));
|
|
41
|
+
const packageCache = new Map();
|
|
42
|
+
const manifestHashes = new Map();
|
|
43
|
+
const unresolvedImports = [];
|
|
44
|
+
function packageJson(directory) {
|
|
45
|
+
if (!packageCache.has(directory)) {
|
|
46
|
+
const file = path.join(directory, 'oh-package.json5');
|
|
47
|
+
const text = existsSync(file) ? readFileSync(file, 'utf8') : null;
|
|
48
|
+
if (text !== null) manifestHashes.set(file, sha256(text));
|
|
49
|
+
packageCache.set(directory, text !== null ? parseJson5(text) : null);
|
|
50
|
+
}
|
|
51
|
+
return packageCache.get(directory);
|
|
52
|
+
}
|
|
53
|
+
function resolveFile(candidate) {
|
|
54
|
+
for (const suffix of ['', '.ets', '.ts', '.d.ets', '.d.ts']) {
|
|
55
|
+
const file = candidate + suffix;
|
|
56
|
+
if (isArkTsSource(file) && host.fileExists(file) && !requireDirectory(file)) {
|
|
57
|
+
const actual = realFile(file);
|
|
58
|
+
if (!isArkTsSource(actual)) continue;
|
|
59
|
+
return canonicalFiles.get(host.getCanonicalFileName(actual)) ?? actual;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const pkg = packageJson(candidate);
|
|
63
|
+
for (const main of [pkg?.types, pkg?.main, 'Index.ets', 'index.ets', 'index.d.ts', 'index.ts'].filter(Boolean)) {
|
|
64
|
+
const file = path.join(candidate, main);
|
|
65
|
+
if (isArkTsSource(file) && host.fileExists(file) && !requireDirectory(file)) {
|
|
66
|
+
const actual = realFile(file);
|
|
67
|
+
if (!isArkTsSource(actual)) continue;
|
|
68
|
+
return canonicalFiles.get(host.getCanonicalFileName(actual)) ?? actual;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
// statSync is avoided in the hot path except for existing candidates.
|
|
74
|
+
function requireDirectory(file) {
|
|
75
|
+
try { return ts.sys.directoryExists(file); } catch { return false; }
|
|
76
|
+
}
|
|
77
|
+
function resolveModule(name, containing) {
|
|
78
|
+
if (/^@(kit|ohos|hms|arkts|system)\./.test(name)) {
|
|
79
|
+
for (const family of ['openharmony', 'hms']) {
|
|
80
|
+
const category = name.startsWith('@kit.') ? 'kits' : name.startsWith('@arkts.') ? 'arkts' : 'api';
|
|
81
|
+
const modulePath = name.startsWith('@ohos.inner.') ? name.slice('@ohos.inner.'.length).replaceAll('.', '/') : name;
|
|
82
|
+
const etsRoot = toolchain.sdkFamilies?.[family]
|
|
83
|
+
?? path.join(toolchain.sdkRoot, 'default', family, 'ets');
|
|
84
|
+
const file = resolveFile(path.join(etsRoot, category, modulePath));
|
|
85
|
+
if (file) return file;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (name.startsWith('.')) return resolveFile(path.resolve(path.dirname(containing), name));
|
|
90
|
+
let directory = path.dirname(containing);
|
|
91
|
+
while (directory !== path.dirname(directory)) {
|
|
92
|
+
const pkg = packageJson(directory);
|
|
93
|
+
const dependency = pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name];
|
|
94
|
+
if (typeof dependency === 'string' && dependency.startsWith('file:')) {
|
|
95
|
+
const local = resolveFile(path.resolve(directory, dependency.slice(5)));
|
|
96
|
+
if (local) return local;
|
|
97
|
+
}
|
|
98
|
+
const installed = resolveFile(path.join(directory, 'oh_modules', name));
|
|
99
|
+
if (installed) return installed;
|
|
100
|
+
directory = path.dirname(directory);
|
|
101
|
+
}
|
|
102
|
+
const module = project.modules.find(item => item.name === name || packageJson(item.root)?.name === name);
|
|
103
|
+
const resolved = module ? resolveFile(module.root) : ts.resolveModuleName(name, containing, options, host).resolvedModule?.resolvedFileName;
|
|
104
|
+
if (!resolved || !isArkTsSource(resolved)) return null;
|
|
105
|
+
const actual = realFile(resolved);
|
|
106
|
+
return isArkTsSource(actual) ? canonicalFiles.get(host.getCanonicalFileName(actual)) ?? actual : null;
|
|
107
|
+
}
|
|
108
|
+
const getSourceFile = host.getSourceFile.bind(host);
|
|
109
|
+
host.getSourceFile = (file, languageVersion, onError) => {
|
|
110
|
+
if (!isArkTsSource(file)) return undefined;
|
|
111
|
+
const identity = host.getCanonicalFileName(file);
|
|
112
|
+
const overlay = overlayFiles.get(identity);
|
|
113
|
+
if (overlay) return ts.createSourceFile(overlay.file, overlay.text, languageVersion, true,
|
|
114
|
+
overlay.file.endsWith('.ets') ? ts.ScriptKind.ETS : ts.ScriptKind.TS, options);
|
|
115
|
+
file = canonicalFiles.get(identity) ?? file;
|
|
116
|
+
const sdkFile = sdkSourceFiles.get(identity);
|
|
117
|
+
if (sdkFile) return sdkFile;
|
|
118
|
+
if (existsSync(file)) return ts.createSourceFile(file, readFileSync(file, 'utf8'), languageVersion, true, path.extname(file).toLowerCase() === '.ets' ? ts.ScriptKind.ETS : ts.ScriptKind.TS, options);
|
|
119
|
+
return getSourceFile(file, languageVersion, onError);
|
|
120
|
+
};
|
|
121
|
+
host.resolveModuleNames = (names, containing) => names.map(name => {
|
|
122
|
+
const file = resolveModule(name, containing);
|
|
123
|
+
if (!file) {
|
|
124
|
+
unresolvedImports.push({ file: containing, module: name, reason: 'MODULE_NOT_RESOLVED' });
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return { resolvedFileName: file, extension: file.endsWith('.d.ets') ? ts.Extension.Dets : file.endsWith('.ets') ? ts.Extension.Ets : file.endsWith('.d.ts') ? ts.Extension.Dts : ts.Extension.Ts };
|
|
128
|
+
});
|
|
129
|
+
const roots = [...new Set([...project.sourceFiles, ...overlays.keys()])].filter(isArkTsSource);
|
|
130
|
+
const globals = [...catalog.sourceFiles].filter(([file, sf]) => isArkTsSource(file) && file.includes(`${path.sep}component${path.sep}`) && !ts.isExternalModule(sf)).map(([file]) => file);
|
|
131
|
+
if (builder) {
|
|
132
|
+
const originalSource = host.getSourceFile;
|
|
133
|
+
host.getSourceFile = (...args) => {
|
|
134
|
+
const source = originalSource(...args);
|
|
135
|
+
if (source) source.version = sha256(source.text);
|
|
136
|
+
return source;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const builderProgram = builder ? ts.createSemanticDiagnosticsBuilderProgram([...roots, ...globals], options, host) : null;
|
|
140
|
+
const program = builderProgram?.getProgram() ?? ts.createProgram([...roots, ...globals], options, host);
|
|
141
|
+
return { program, builderProgram, checker: program.getTypeChecker(), unresolvedImports, roots, manifestHashes,
|
|
142
|
+
sdkSourceFileIdentities: new Set(sdkSourceFiles.keys()), resolveModule };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Follow only declarations and exported names actually used by project code.
|
|
146
|
+
// Merely loading an SDK barrel (or a global component library) does not make all
|
|
147
|
+
// of its exports relevant to the project's type evidence.
|
|
148
|
+
export function classifySdkImportImpact({ program, checker, roots, unresolvedImports,
|
|
149
|
+
sdkSourceFileIdentities, resolveModule }, catalog) {
|
|
150
|
+
const { ts } = catalog;
|
|
151
|
+
const needed = new Set();
|
|
152
|
+
const visitedNodes = new Set();
|
|
153
|
+
const visitedExports = new Set();
|
|
154
|
+
const importKey = (file, module) => `${compilerPathIdentity(file)}\0${module}`;
|
|
155
|
+
const unresolvedKeys = new Set(unresolvedImports.map(item => importKey(item.file, item.module)));
|
|
156
|
+
const sourceFor = file => program.getSourceFile(file)
|
|
157
|
+
?? program.getSourceFiles().find(source => compilerPathIdentity(source.fileName) === compilerPathIdentity(file));
|
|
158
|
+
function followExport(file, name) {
|
|
159
|
+
const key = `${compilerPathIdentity(file)}\0${name}`;
|
|
160
|
+
if (visitedExports.has(key)) return;
|
|
161
|
+
visitedExports.add(key);
|
|
162
|
+
const source = sourceFor(file);
|
|
163
|
+
if (!source) return;
|
|
164
|
+
for (const statement of source.statements) {
|
|
165
|
+
if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier) continue;
|
|
166
|
+
const module = statement.moduleSpecifier.text;
|
|
167
|
+
const elements = statement.exportClause?.elements;
|
|
168
|
+
const selected = elements?.filter(item => name === '*' || item.name.text === name);
|
|
169
|
+
if (elements && !selected.length) continue;
|
|
170
|
+
const resolved = resolveModule(module, source.fileName);
|
|
171
|
+
if (!resolved) {
|
|
172
|
+
needed.add(importKey(source.fileName, module));
|
|
173
|
+
} else if (!elements) followExport(resolved, name);
|
|
174
|
+
else for (const item of selected) followExport(resolved, item.propertyName?.text ?? item.name.text);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function declaration(node) {
|
|
178
|
+
if (!node || visitedNodes.has(node)) return;
|
|
179
|
+
visitedNodes.add(node);
|
|
180
|
+
const source = node.getSourceFile?.();
|
|
181
|
+
if (!source) return;
|
|
182
|
+
if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || ts.isImportClause(node)) {
|
|
183
|
+
let owner = node;
|
|
184
|
+
while (owner && !ts.isImportDeclaration(owner)) owner = owner.parent;
|
|
185
|
+
if (owner?.moduleSpecifier) {
|
|
186
|
+
const module = owner.moduleSpecifier.text;
|
|
187
|
+
const resolved = resolveModule(module, source.fileName);
|
|
188
|
+
if (!resolved) needed.add(importKey(source.fileName, module));
|
|
189
|
+
else followExport(resolved, ts.isNamespaceImport(node) ? '*' : node.propertyName?.text ?? node.name?.text ?? 'default');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (ts.isExportSpecifier(node)) {
|
|
193
|
+
const owner = node.parent?.parent;
|
|
194
|
+
if (owner?.moduleSpecifier) {
|
|
195
|
+
const module = owner.moduleSpecifier.text;
|
|
196
|
+
const resolved = resolveModule(module, source.fileName);
|
|
197
|
+
if (!resolved) needed.add(importKey(source.fileName, module));
|
|
198
|
+
else followExport(resolved, node.propertyName?.text ?? node.name.text);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (sdkSourceFileIdentities.has(compilerPathIdentity(source.fileName))) {
|
|
202
|
+
function visit(child) {
|
|
203
|
+
if (ts.isIdentifier(child) || ts.isStringLiteral(child)) symbol(checker.getSymbolAtLocation(child));
|
|
204
|
+
ts.forEachChild(child, visit);
|
|
205
|
+
}
|
|
206
|
+
visit(node);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function symbol(value) {
|
|
210
|
+
for (const node of value?.declarations ?? []) declaration(node);
|
|
211
|
+
if (value?.flags & ts.SymbolFlags.Alias) {
|
|
212
|
+
const target = checker.getAliasedSymbol(value);
|
|
213
|
+
if (target !== value) for (const node of target?.declarations ?? []) declaration(node);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const root of roots) {
|
|
217
|
+
const source = sourceFor(root);
|
|
218
|
+
if (!source) continue;
|
|
219
|
+
function visit(node) {
|
|
220
|
+
if (ts.isImportDeclaration(node)) return;
|
|
221
|
+
if (ts.isExportDeclaration(node)) {
|
|
222
|
+
if (node.moduleSpecifier) {
|
|
223
|
+
const resolved = resolveModule(node.moduleSpecifier.text, source.fileName);
|
|
224
|
+
if (resolved) {
|
|
225
|
+
if (node.exportClause?.elements) for (const exported of node.exportClause.elements) followExport(resolved, exported.propertyName?.text ?? exported.name.text);
|
|
226
|
+
else followExport(resolved, '*');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (ts.isIdentifier(node) || ts.isStringLiteral(node)) symbol(checker.getSymbolAtLocation(node));
|
|
232
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) declaration(checker.getResolvedSignature(node)?.declaration);
|
|
233
|
+
ts.forEachChild(node, visit);
|
|
234
|
+
}
|
|
235
|
+
visit(source);
|
|
236
|
+
}
|
|
237
|
+
return unresolvedImports.map(item => {
|
|
238
|
+
const sdk = sdkSourceFileIdentities.has(compilerPathIdentity(item.file));
|
|
239
|
+
const system = /^@(kit|ohos|hms|arkts|system)\./.test(item.module);
|
|
240
|
+
const affected = !sdk ? system : needed.has(importKey(item.file, item.module));
|
|
241
|
+
return { ...item, blocking: affected,
|
|
242
|
+
reason: sdk && !affected ? 'UNUSED_SDK_IMPORT_NOT_RESOLVED' : item.reason,
|
|
243
|
+
impactEvidence: sdk ? { usedDeclarationAnalysis: true, requiredImport: affected,
|
|
244
|
+
inspectedMissingImport: unresolvedKeys.has(importKey(item.file, item.module)) } : undefined };
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function actualSymbol(ts, checker, node) {
|
|
249
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
250
|
+
if (symbol?.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
251
|
+
return symbol;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function symbolEntries(catalog, symbol) {
|
|
255
|
+
return symbol?.declarations?.map(node => catalogEntry(catalog, node)).filter(Boolean) ?? [];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const PLATFORM_BY_LEVEL = new Map([[12, '5.0.0'], [13, '5.0.1'], [14, '5.0.2'], [15, '5.0.3'], [16, '5.0.4'], [17, '5.0.5'], [18, '5.1.0'], [19, '5.1.1'], [20, '6.0.0'], [21, '6.0.1'], [22, '6.0.2'], [23, '6.1.0'], [24, '6.1.1'], [26, '26.0.0']]);
|
|
259
|
+
|
|
260
|
+
export async function scanArkTsDeprecations({ project, catalog, documentRules = [] }) {
|
|
261
|
+
const { ts } = catalog;
|
|
262
|
+
const context = createArkTsProgram(project, catalog);
|
|
263
|
+
const { program, checker, roots, manifestHashes } = context;
|
|
264
|
+
const active = new Set(catalog.deprecatedEntries.map(entry => entry.symbolId));
|
|
265
|
+
const unresolved = [];
|
|
266
|
+
const findings = [];
|
|
267
|
+
const seen = new Set();
|
|
268
|
+
const isCallLike = node => ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isEtsComponentExpression(node);
|
|
269
|
+
function emit(node, entry, related = [], call = null) {
|
|
270
|
+
if (!active.has(entry.symbolId)) return;
|
|
271
|
+
const sf = node.getSourceFile();
|
|
272
|
+
const start = node.getStart(sf);
|
|
273
|
+
const position = sf.getLineAndCharacterOfPosition(start);
|
|
274
|
+
const module = projectModuleForFile(project, sf.fileName);
|
|
275
|
+
const relative = module ? `${module.name}/${portableRelative(module.root, sf.fileName)}` : portableRelative(project.root, sf.fileName);
|
|
276
|
+
const occurrenceId = `occurrence-${sha256([relative, start, node.end, entry.symbolId].join('\0')).slice(0, 24)}`;
|
|
277
|
+
if (seen.has(occurrenceId)) return;
|
|
278
|
+
seen.add(occurrenceId);
|
|
279
|
+
const references = documentRules.filter(rule => rule.sdkSymbolIds?.includes(entry.symbolId)).map(rule => ({ ruleId: rule.ruleId, sourceReference: rule.sourceReference, url: rule.url || null }));
|
|
280
|
+
const platform = PLATFORM_BY_LEVEL.get(entry.deprecatedSince);
|
|
281
|
+
findings.push({
|
|
282
|
+
'Api Definition': entry.declaration, Language: 'ArkTS',
|
|
283
|
+
'Changed in SDK': platform ? canonicalVersion(`${platform}(${entry.deprecatedSince})`) : '',
|
|
284
|
+
'Affected Versions': 'N/A (compile-time)', Title: hyperlink(deprecatedDocUrl(entry, references), `${entry.qualifiedName}:API 废弃`),
|
|
285
|
+
'Code Location': `${relative}:${position.line + 1}`, Fixed: 'Unfixed',
|
|
286
|
+
'Change Type': 'Deprecated API', 'Rule ID': `sdk-deprecated-${entry.symbolId.slice(4)}`,
|
|
287
|
+
'Change Operation': 'API_DEPRECATED', 'Source Reference': `${entry.file}:${entry.line}`,
|
|
288
|
+
'Replacement API': entry.replacementApi ?? '', 'Replacement Source': `${entry.file}:${entry.line}`,
|
|
289
|
+
'API Symbol ID': entry.symbolId, Column: position.character + 1, 'Deprecated Since': entry.deprecatedSince,
|
|
290
|
+
'Detection Source': 'SDK_STATE', 'Source Scope': 'PROJECT',
|
|
291
|
+
includedInCsv: true, disposition: 'REQUIRES_REVIEW', filterReason: null, applicabilityProof: null,
|
|
292
|
+
occurrenceId, sourceSymbol: node.text, sourceFileHash: sha256(sf.text), sourceRange: { start, end: node.end },
|
|
293
|
+
callRange: call ? { start: call.getStart(sf), end: call.end } : null,
|
|
294
|
+
sdkEvidence: declarationEvidence(entry, catalog),
|
|
295
|
+
relatedDeclarations: related.map(item => declarationEvidence(item, catalog)), documentReferences: references,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
for (const file of roots) {
|
|
299
|
+
const sf = program.getSourceFile(file);
|
|
300
|
+
if (!sf) { unresolved.push({ file, reason: 'SOURCE_NOT_PARSED' }); continue; }
|
|
301
|
+
unresolved.push(...sf.parseDiagnostics.map(d => ({ file, reason: 'SOURCE_PARSE_ERROR', start: d.start, message: ts.flattenDiagnosticMessageText(d.messageText, ' ') })));
|
|
302
|
+
function visit(node) {
|
|
303
|
+
if (ts.isIdentifier(node) || ts.isStringLiteral(node)) {
|
|
304
|
+
const parent = node.parent;
|
|
305
|
+
const declarationName = parent.name === node && !ts.isPropertyAccessExpression(parent) && !ts.isPropertyAssignment(parent) && !ts.isShorthandPropertyAssignment(parent);
|
|
306
|
+
const importName = ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent) || ts.isExportSpecifier(parent);
|
|
307
|
+
if (!declarationName && !importName) {
|
|
308
|
+
let entries = symbolEntries(catalog, actualSymbol(ts, checker, node));
|
|
309
|
+
let callee = node;
|
|
310
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node) callee = parent;
|
|
311
|
+
if (ts.isElementAccessExpression(parent) && parent.argumentExpression === node) callee = parent;
|
|
312
|
+
const call = callee.parent && isCallLike(callee.parent) && callee.parent.expression === callee ? callee.parent : null;
|
|
313
|
+
const receiverCall = (ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && parent.expression === node && parent.parent && isCallLike(parent.parent) && parent.parent.expression === parent ? parent.parent : null;
|
|
314
|
+
if (call) {
|
|
315
|
+
const signatureEntry = catalogEntry(catalog, checker.getResolvedSignature(call)?.declaration);
|
|
316
|
+
if (ts.isElementAccessExpression(callee) && !signatureEntry && !ts.isStringLiteral(node)) {
|
|
317
|
+
const receiver = checker.getTypeAtLocation(callee.expression);
|
|
318
|
+
if (symbolEntries(catalog, receiver.symbol ?? receiver.aliasSymbol).length && !checker.getIndexInfoOfType(receiver, ts.IndexKind.String)) unresolved.push({ file, start: node.getStart(sf), api: call.getText(sf), reason: 'SDK_DYNAMIC_MEMBER_NOT_RESOLVED' });
|
|
319
|
+
}
|
|
320
|
+
if (!signatureEntry && entries.some(entry => entry.kind === 'method')) unresolved.push({ file, start: node.getStart(sf), api: call.getText(sf), reason: 'SDK_CALL_SIGNATURE_NOT_RESOLVED' });
|
|
321
|
+
if (ts.isNewExpression(call)) {
|
|
322
|
+
const typeEntry = entries.find(entry => ['class', 'struct', 'interface'].includes(entry.kind));
|
|
323
|
+
entries = typeEntry && active.has(typeEntry.symbolId) ? [typeEntry] : signatureEntry ? [signatureEntry] : [];
|
|
324
|
+
if (entries[0]) emit(node, entries[0], signatureEntry ? [signatureEntry] : [], call);
|
|
325
|
+
entries = [];
|
|
326
|
+
} else entries = signatureEntry ? [signatureEntry] : entries.filter(entry => entry.kind !== 'method');
|
|
327
|
+
}
|
|
328
|
+
if (ts.isPropertyAssignment(parent) && parent.name === node) {
|
|
329
|
+
const object = parent.parent;
|
|
330
|
+
const contextual = checker.getContextualType(object)?.getProperty(node.text);
|
|
331
|
+
entries = symbolEntries(catalog, contextual);
|
|
332
|
+
// ArkUI struct initializers are not regular TS constructor parameter objects.
|
|
333
|
+
if (!entries.length && isCallLike(object.parent)) {
|
|
334
|
+
const component = actualSymbol(ts, checker, object.parent.expression);
|
|
335
|
+
const componentEntry = symbolEntries(catalog, component).find(entry => entry.kind === 'struct');
|
|
336
|
+
if (componentEntry) entries = catalog.declarations.filter(entry => entry.file === componentEntry.file && entry.owner === componentEntry.qualifiedName && entry.name === node.text);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const entry of entries) emit(node, entry, [], call ?? receiverCall);
|
|
340
|
+
if (!entries.length && ts.isPropertyAccessExpression(parent) && parent.name === node && !checker.getSymbolAtLocation(node)) {
|
|
341
|
+
let base = parent.expression;
|
|
342
|
+
while (ts.isPropertyAccessExpression(base)) base = base.expression;
|
|
343
|
+
const receiverType = checker.getTypeAtLocation(parent.expression);
|
|
344
|
+
const sdkType = symbolEntries(catalog, receiverType.symbol ?? receiverType.aliasSymbol).length > 0;
|
|
345
|
+
const indexed = checker.getIndexInfoOfType(receiverType, ts.IndexKind.String);
|
|
346
|
+
if (!indexed && (sdkType || symbolEntries(catalog, actualSymbol(ts, checker, base)).length)) unresolved.push({ file, start: node.getStart(sf), api: parent.getText(sf), reason: 'SDK_MEMBER_NOT_RESOLVED' });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
ts.forEachChild(node, visit);
|
|
351
|
+
}
|
|
352
|
+
visit(sf);
|
|
353
|
+
}
|
|
354
|
+
// Triggered module resolution includes SDK and dependency declarations, not just roots.
|
|
355
|
+
const importImpact = classifySdkImportImpact(context, catalog);
|
|
356
|
+
unresolved.push(...importImpact.filter(item => item.blocking));
|
|
357
|
+
const resolutionWarnings = importImpact.filter(item => !item.blocking);
|
|
358
|
+
const uniqueUnresolved = [...new Map(unresolved.map(item => [JSON.stringify(item), item])).values()];
|
|
359
|
+
findings.sort((a, b) => a['Code Location'].localeCompare(b['Code Location'], undefined, { numeric: true }) || a.Column - b.Column || a['API Symbol ID'].localeCompare(b['API Symbol ID']));
|
|
360
|
+
const analyzedFiles = [...program.getSourceFiles().map(sf => ({ file: sf.fileName, hash: sha256(sf.text) })), ...[...manifestHashes].map(([file, hash]) => ({ file, hash }))];
|
|
361
|
+
return { findings, unresolved: uniqueUnresolved, resolutionWarnings, analyzedFiles, coverage: { status: uniqueUnresolved.length ? 'INCOMPLETE' : 'COMPLETE', scannedFileCount: roots.length, findingCount: findings.length, unresolvedCount: uniqueUnresolved.length, projectImportWarningCount: resolutionWarnings.length, sdkSnapshot: catalog.snapshot,
|
|
362
|
+
analysisSnapshot: sha256(analyzedFiles.map(item => `${item.file}\0${item.hash}`).sort().join('\n')) } };
|
|
363
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { sha256 } from '../../shared/io/fs-utils.mjs';
|
|
4
|
+
import { deprecatedDocUrl } from '../core/sdk-deprecations.mjs';
|
|
5
|
+
import { hyperlink } from '../../shared/format/csv.mjs';
|
|
6
|
+
import { relativePathInside, comparableCodeLocation } from '../../shared/platform/paths.mjs';
|
|
7
|
+
|
|
8
|
+
export function parseDeprecatedBuildLog(text) {
|
|
9
|
+
const diagnostics = [];
|
|
10
|
+
let location = null;
|
|
11
|
+
const lines = text.replace(/\x1b\[[0-9;]*m/g, '').split(/\r?\n/);
|
|
12
|
+
for (const [index, line] of lines.entries()) {
|
|
13
|
+
const match = line.match(/File:\s*(.+?):(\d+):(\d+)/);
|
|
14
|
+
if (match) location = { file: match[1].replaceAll('\\', '/'), line: Number(match[2]), column: Number(match[3]) };
|
|
15
|
+
const deprecated = line.match(/'([^']+)' has been deprecated\./);
|
|
16
|
+
if (deprecated) diagnostics.push({ ...location, symbol: deprecated[1], logLine: index + 1, located: location !== null });
|
|
17
|
+
if (/^(?:>|WARN:|ERROR:)/.test(line) && !match && !deprecated) location = null;
|
|
18
|
+
}
|
|
19
|
+
return [...new Map(diagnostics.map(item => [[item.file, item.line, item.column, item.symbol].join('\0'), item])).values()];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function importDeprecatedBuildLog({ project, catalog, logPath = null, evidencePath = null }) {
|
|
23
|
+
if (!logPath && evidencePath) throw new Error('--build-evidence requires --build-log');
|
|
24
|
+
if (!logPath) return { findings: [], warnings: [], audit: null };
|
|
25
|
+
const text = await readFile(logPath, 'utf8');
|
|
26
|
+
const diagnostics = parseDeprecatedBuildLog(text);
|
|
27
|
+
if (!evidencePath) return { findings: [], warnings: ['编译日志缺少配套快照证据,仅用于对比,不导入主CSV'], audit: {
|
|
28
|
+
status: 'UNVERIFIED_LOG', logHash: sha256(text), diagnostics, importedCount: 0,
|
|
29
|
+
} };
|
|
30
|
+
const evidence = JSON.parse(await readFile(evidencePath, 'utf8'));
|
|
31
|
+
const expected = { schemaVersion: 1, logHash: sha256(text), sourceSnapshot: project.sourceSnapshot,
|
|
32
|
+
buildProfileHash: project.buildProfileHash, sdkSnapshot: catalog.snapshot, targetSdk: catalog.coverage.targetSdk, product: project.product.name, nonIncremental: true, status: 'PASSED' };
|
|
33
|
+
const errors = Object.entries(expected).filter(([key, value]) => evidence[key] !== value).map(([key]) => key);
|
|
34
|
+
const platform = catalog.platform ?? process.platform;
|
|
35
|
+
const api = platform === 'win32' ? path.win32 : path;
|
|
36
|
+
if (typeof evidence.projectRoot !== 'string' || !api.isAbsolute(evidence.projectRoot)) errors.push('projectRoot');
|
|
37
|
+
if (errors.length) {
|
|
38
|
+
const error = new Error(`Build evidence does not match this scan: ${errors.join(', ')}`);
|
|
39
|
+
error.code = 'BUILD_EVIDENCE_MISMATCH'; error.details = errors; throw error;
|
|
40
|
+
}
|
|
41
|
+
const findings = [];
|
|
42
|
+
const records = [];
|
|
43
|
+
for (const diagnostic of diagnostics) {
|
|
44
|
+
const relative = diagnostic.file ? relativePathInside(evidence.projectRoot, diagnostic.file, platform) : null;
|
|
45
|
+
if (!relative || relative.split('/').includes('..')) { records.push({ ...diagnostic, reason: 'UNMAPPED_LOCATION' }); continue; }
|
|
46
|
+
if (!relative.split('/').includes('oh_modules')) { records.push({ ...diagnostic, reason: 'PROJECT_DIAGNOSTIC_RECONCILIATION_ONLY' }); continue; }
|
|
47
|
+
const binding = evidence.apiBindings?.find(item => comparableCodeLocation(item.file, null, platform) === comparableCodeLocation(relative, null, platform)
|
|
48
|
+
&& item.line === diagnostic.line && item.column === diagnostic.column && item.symbol === diagnostic.symbol);
|
|
49
|
+
const entry = binding ? catalog.deprecatedEntries.find(item => item.symbolId === binding.symbolId) : null;
|
|
50
|
+
if (binding && (!entry || entry.name !== diagnostic.symbol)) {
|
|
51
|
+
const error = new Error(`Invalid SDK symbol binding in build evidence: ${relative}:${diagnostic.line}`);
|
|
52
|
+
error.code = 'BUILD_API_BINDING_MISMATCH'; throw error;
|
|
53
|
+
}
|
|
54
|
+
const occurrenceId = `compiler-${sha256([relative, diagnostic.line, diagnostic.column, diagnostic.symbol].join('\0')).slice(0, 24)}`;
|
|
55
|
+
findings.push({
|
|
56
|
+
'Api Definition': entry?.declaration ?? `${diagnostic.symbol} (compiler deprecation warning)`, Language: 'ArkTS',
|
|
57
|
+
'Changed in SDK': '', 'Affected Versions': 'N/A (compile-time)', Title: hyperlink(deprecatedDocUrl(entry), `${diagnostic.symbol}:第三方依赖废弃告警`),
|
|
58
|
+
'Code Location': `${relative}:${diagnostic.line}`, Fixed: 'Unfixed', 'Change Type': 'Deprecated API',
|
|
59
|
+
'Rule ID': entry ? `sdk-deprecated-${entry.symbolId.slice(4)}` : occurrenceId, 'Change Operation': 'API_DEPRECATED',
|
|
60
|
+
'Source Reference': `${path.resolve(logPath)}:${diagnostic.logLine}`,
|
|
61
|
+
'Replacement API': entry?.replacementApi ?? '', 'Replacement Source': entry ? `${entry.file}:${entry.line}` : '',
|
|
62
|
+
'API Symbol ID': entry?.symbolId ?? '', Column: diagnostic.column, 'Deprecated Since': entry?.deprecatedSince ?? '',
|
|
63
|
+
'Detection Source': 'BUILD_LOG', 'Source Scope': 'DEPENDENCY',
|
|
64
|
+
includedInCsv: true, disposition: 'REQUIRES_REVIEW', filterReason: null, applicabilityProof: null,
|
|
65
|
+
occurrenceId, evidenceStatus: entry ? 'SDK_BOUND' : 'COMPILER_ONLY', autoEditAllowed: false,
|
|
66
|
+
sdkEvidence: entry ? { ...entry, sdkSnapshot: catalog.snapshot, targetSdk: catalog.coverage.targetSdk } : null,
|
|
67
|
+
compilerEvidence: { ...diagnostic, logHash: evidence.logHash },
|
|
68
|
+
});
|
|
69
|
+
records.push({ ...diagnostic, reason: entry ? 'IMPORTED_SDK_BOUND_DEPENDENCY' : 'IMPORTED_DEPENDENCY_REQUIRES_CONFIRMATION' });
|
|
70
|
+
}
|
|
71
|
+
return { findings, warnings: [], audit: { status: 'VERIFIED_LOG', importedCount: findings.length, logHash: evidence.logHash, diagnostics: records } };
|
|
72
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, listFiles } from '../../shared/io/fs-utils.mjs';
|
|
4
|
+
import { runProcess } from '../../shared/platform/process.mjs';
|
|
5
|
+
|
|
6
|
+
async function findNinja(directory) {
|
|
7
|
+
if (!(await exists(directory))) return [];
|
|
8
|
+
const results = [];
|
|
9
|
+
async function visit(current) {
|
|
10
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
11
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const target = path.join(current, entry.name);
|
|
14
|
+
if (entry.isDirectory()) await visit(target);
|
|
15
|
+
else if (entry.isFile() && entry.name === 'build.ninja') results.push(target);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
await visit(directory);
|
|
19
|
+
return results;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function run(command, args, cwd, environment = process.env, timeoutMs = null) {
|
|
23
|
+
const result = await runProcess(command, args, { cwd, env: environment, timeoutMs });
|
|
24
|
+
if (result.error) throw new Error(result.error);
|
|
25
|
+
return { code: result.exitCode, stdout: result.stdout, stderr: result.stderr, error: result.error };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function nativeBuildIsStale(moduleRoot, ninjaFiles) {
|
|
29
|
+
if (ninjaFiles.length === 0) return true;
|
|
30
|
+
const inputs = await listFiles(moduleRoot, (file) => {
|
|
31
|
+
const normalized = file.split(path.sep).join('/');
|
|
32
|
+
if (normalized.includes('/.cxx/') || normalized.includes('/build/') || normalized.includes('/oh_modules/')) return false;
|
|
33
|
+
return /\.(?:c|cc|cpp|cxx|h|hpp)$/i.test(file)
|
|
34
|
+
|| ['CMakeLists.txt', 'build-profile.json5', 'module.json5'].includes(path.basename(file));
|
|
35
|
+
});
|
|
36
|
+
const inputTimes = await Promise.all(inputs.map(async (file) => (await stat(file)).mtimeMs));
|
|
37
|
+
const ninjaTimes = await Promise.all(ninjaFiles.map(async (file) => (await stat(file)).mtimeMs));
|
|
38
|
+
const newestInput = Math.max(0, ...inputTimes);
|
|
39
|
+
return ninjaTimes.some((time) => time < newestInput);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function ensureNativeBuild({ project, toolchain, mode = 'auto', runCommand = run }) {
|
|
43
|
+
const nativeModules = (project.selectedModules ?? project.modules).filter((module) => module.hasNativeConfig);
|
|
44
|
+
const records = [];
|
|
45
|
+
for (const module of nativeModules) {
|
|
46
|
+
let ninjaFiles = await findNinja(path.join(module.root, '.cxx'));
|
|
47
|
+
let stale = await nativeBuildIsStale(module.root, ninjaFiles);
|
|
48
|
+
let rebuilt = false;
|
|
49
|
+
if (stale && mode === 'auto') {
|
|
50
|
+
if (!toolchain.hvigorw) {
|
|
51
|
+
const error = new Error(`Hvigor is unavailable for native module ${module.name}`);
|
|
52
|
+
error.code = 'NATIVE_BUILD_TOOL_UNAVAILABLE';
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
if (!(await exists(path.join(project.root, 'oh_modules')))) {
|
|
56
|
+
if (!toolchain.ohpm) {
|
|
57
|
+
const error = new Error(`oh_modules is missing and ohpm is unavailable for native module ${module.name}; run ohpm install manually`);
|
|
58
|
+
error.code = 'OHPM_UNAVAILABLE';
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
const install = await runCommand(toolchain.ohpm, ['install'], project.root,
|
|
62
|
+
{ ...process.env, DEVECO_SDK_HOME: toolchain.sdkRoot }, 10 * 60_000);
|
|
63
|
+
if (install.code !== 0) {
|
|
64
|
+
const error = new Error(`ohpm install failed for native module ${module.name}`);
|
|
65
|
+
error.code = 'OHPM_INSTALL_FAILED';
|
|
66
|
+
error.details = {
|
|
67
|
+
stdout: String(install.stdout ?? '').slice(-8000),
|
|
68
|
+
stderr: String(install.stderr ?? '').slice(-8000),
|
|
69
|
+
};
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const result = await runCommand(toolchain.hvigorw, [
|
|
74
|
+
'--mode', 'module',
|
|
75
|
+
'-p', `module=${module.name}@${module.target}`,
|
|
76
|
+
'-p', `product=${project.product.name}`,
|
|
77
|
+
'compileNative', '--analyze=normal', '--no-daemon',
|
|
78
|
+
], project.root, { ...process.env, DEVECO_SDK_HOME: toolchain.sdkRoot });
|
|
79
|
+
if (result.code !== 0) {
|
|
80
|
+
const error = new Error(`Hvigor native build failed for ${module.name}`);
|
|
81
|
+
error.code = 'NATIVE_BUILD_FAILED';
|
|
82
|
+
error.details = { stdout: result.stdout.slice(-8000), stderr: result.stderr.slice(-8000) };
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
rebuilt = true;
|
|
86
|
+
ninjaFiles = await findNinja(path.join(module.root, '.cxx'));
|
|
87
|
+
stale = await nativeBuildIsStale(module.root, ninjaFiles);
|
|
88
|
+
}
|
|
89
|
+
if (ninjaFiles.length === 0 || stale) {
|
|
90
|
+
throw new Error(`build.ninja is missing or stale for native module ${module.name}`);
|
|
91
|
+
}
|
|
92
|
+
records.push({ module: module.name, ninjaFiles, rebuilt });
|
|
93
|
+
}
|
|
94
|
+
return records;
|
|
95
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@local/harmonyos-api-scanner",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"harmony-api-scan": "./bin/harmony-api-scan.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test",
|
|
11
|
+
"inspect-rules": "node ./bin/harmony-api-scan.mjs inspect-rules"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, readFile, writeFile, readdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { batchArtifactPaths, createScanBatch, ensureBatchDirectory } from '../../shared/platform/artifact-layout.mjs';
|
|
7
|
+
|
|
8
|
+
async function fixture(t) {
|
|
9
|
+
const root = await mkdtemp(path.join(os.tmpdir(), '批次 中文 space-'));
|
|
10
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
11
|
+
const directory = path.join(root, '20261027172821-2');
|
|
12
|
+
await mkdir(directory);
|
|
13
|
+
return { root, directory };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const mode of ['upgrade', 'deprecated']) {
|
|
17
|
+
test(`${mode} creates only layout4 and binds original/adapted CSVs to one random ID`, async t => {
|
|
18
|
+
const { root, directory } = await fixture(t);
|
|
19
|
+
const batch = await createScanBatch(root, directory, { mode });
|
|
20
|
+
assert.equal(batch.schemaVersion, 4);
|
|
21
|
+
assert.equal(batch.deliveryDirectory, path.join(directory, mode));
|
|
22
|
+
const stem = mode === 'upgrade' ? `scan-results-${batch.batchId}` : `apiChange_${path.basename(directory)}`;
|
|
23
|
+
assert.equal(batch.rawCsv, path.join(directory, mode, `${stem}.csv`));
|
|
24
|
+
assert.equal(batch.adaptedCsv, path.join(directory, mode, `adapted-results-${batch.batchId}.csv`));
|
|
25
|
+
assert.equal(batch.scanJson, path.join(directory, '.internal', 'scanner', `${stem}.scan.json`));
|
|
26
|
+
const metadata = JSON.parse(await readFile(batch.batchMetadataPath, 'utf8'));
|
|
27
|
+
assert.equal(metadata.schemaVersion, 4);
|
|
28
|
+
assert.equal(metadata.batchId, batch.batchId);
|
|
29
|
+
assert.equal(metadata.mode, mode);
|
|
30
|
+
assert.deepEqual((await readdir(directory)).sort(), ['.internal', mode].sort());
|
|
31
|
+
const original = Buffer.from('\ufeffTitle,Fixed\r\n"重复,调用",Unfixed\r\n"重复,调用",Unfixed\r\n');
|
|
32
|
+
await writeFile(batch.rawCsv, original);
|
|
33
|
+
await writeFile(batch.adaptedCsv, '独立评估\r\n');
|
|
34
|
+
assert.deepEqual(await readFile(batch.rawCsv), original);
|
|
35
|
+
const variant = path.join(directory, mode.toUpperCase());
|
|
36
|
+
let sameDirectory = false;
|
|
37
|
+
try { sameDirectory = await realpath(variant) === await realpath(batch.deliveryDirectory); }
|
|
38
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
39
|
+
if (sameDirectory) await ensureBatchDirectory(batch, variant);
|
|
40
|
+
assert.equal(batchArtifactPaths(directory, batch.batchId, mode).adaptedCsv, batch.adaptedCsv);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test(`${mode} supports drive and UNC paths and rejects invalid batch identities`, () => {
|
|
44
|
+
for (const directory of ['D:\\中文 工程\\20261027172821-2', '\\\\server\\共享\\20261027172821-2']) {
|
|
45
|
+
const batch = batchArtifactPaths(directory, 'abcdef012345', mode);
|
|
46
|
+
assert.equal(batch.deliveryDirectory, path.win32.join(directory, mode));
|
|
47
|
+
assert.equal(batch.adaptedCsv, path.win32.join(directory, mode, 'adapted-results-abcdef012345.csv'));
|
|
48
|
+
assert.equal(batch.batchMetadataPath, path.win32.join(directory, '.internal', 'scan-batch.json'));
|
|
49
|
+
}
|
|
50
|
+
assert.throws(() => batchArtifactPaths('20261027172821', '../outside', mode), { code: 'SCAN_BATCH_INVALID' });
|
|
51
|
+
assert.throws(() => batchArtifactPaths('20261027172821', 'abcdef012345', 'other'), { code: 'SCAN_BATCH_INVALID' });
|
|
52
|
+
if (mode === 'deprecated') assert.throws(() => batchArtifactPaths('invalid-stamp', 'abcdef012345', mode), { code: 'SCAN_BATCH_INVALID' });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test(`${mode} resolves concurrent ID collisions and stops after the collision budget`, async t => {
|
|
56
|
+
const { root, directory } = await fixture(t);
|
|
57
|
+
const second = path.join(root, '20261027172822');
|
|
58
|
+
await mkdir(second);
|
|
59
|
+
let a = 0, b = 0;
|
|
60
|
+
const batches = await Promise.all([
|
|
61
|
+
createScanBatch(root, directory, { mode, idFactory: () => a++ ? 'bbbbbbbbbbbb' : 'aaaaaaaaaaaa' }),
|
|
62
|
+
createScanBatch(root, second, { mode, idFactory: () => b++ ? 'cccccccccccc' : 'aaaaaaaaaaaa' }),
|
|
63
|
+
]);
|
|
64
|
+
assert.notEqual(batches[0].batchId, batches[1].batchId);
|
|
65
|
+
const third = path.join(root, '20261027172823');
|
|
66
|
+
await mkdir(third);
|
|
67
|
+
let calls = 0;
|
|
68
|
+
await assert.rejects(createScanBatch(root, third, { mode, idFactory: () => { calls++; return 'aaaaaaaaaaaa'; } }), { code: 'SCAN_BATCH_COLLISION' });
|
|
69
|
+
assert.equal(calls, 32);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test(`${mode} creation rejects linked feature and internal directories outside the batch`, async t => {
|
|
73
|
+
const { root, directory } = await fixture(t);
|
|
74
|
+
const outside = await mkdtemp(path.join(os.tmpdir(), 'outside-batch-'));
|
|
75
|
+
t.after(() => rm(outside, { recursive: true, force: true }));
|
|
76
|
+
for (const name of [mode, '.internal']) {
|
|
77
|
+
await rm(path.join(directory, '.internal'), { recursive: true, force: true });
|
|
78
|
+
const link = path.join(directory, name);
|
|
79
|
+
await symlink(outside, link, process.platform === 'win32' ? 'junction' : 'dir');
|
|
80
|
+
await assert.rejects(createScanBatch(root, directory, { mode }), { code: 'SCAN_BATCH_INVALID' });
|
|
81
|
+
await rm(link, { recursive: true });
|
|
82
|
+
assert.deepEqual(await readdir(outside), []);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
test('a linked batch registry cannot publish outside the chosen output root', async t => {
|
|
88
|
+
const { root, directory } = await fixture(t);
|
|
89
|
+
const outside = await mkdtemp(path.join(os.tmpdir(), 'outside-registry-'));
|
|
90
|
+
t.after(() => rm(outside, { recursive: true, force: true }));
|
|
91
|
+
await symlink(outside, path.join(root, '.batch-ids'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
92
|
+
await assert.rejects(createScanBatch(root, directory), { code: 'SCAN_BATCH_INVALID' });
|
|
93
|
+
assert.deepEqual(await readdir(outside), []);
|
|
94
|
+
});
|