hvigor-dependency-policy-plugin 0.2.0-beta
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 +82 -0
- package/config/dependency-policy.json5 +44 -0
- package/dist/diff.d.ts +6 -0
- package/dist/diff.js +39 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +39 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +47 -0
- package/dist/oh-package-editor.d.ts +10 -0
- package/dist/oh-package-editor.js +296 -0
- package/dist/overrides-applier.d.ts +9 -0
- package/dist/overrides-applier.js +104 -0
- package/dist/path-utils.d.ts +3 -0
- package/dist/path-utils.js +68 -0
- package/dist/policy-loader.d.ts +3 -0
- package/dist/policy-loader.js +78 -0
- package/dist/project-root.d.ts +3 -0
- package/dist/project-root.js +33 -0
- package/dist/report.d.ts +2 -0
- package/dist/report.js +15 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +2 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# hvigor-dependency-policy-plugin v2
|
|
2
|
+
|
|
3
|
+
This version no longer rewrites Hvigor dependency context in memory.
|
|
4
|
+
It materializes the selected dependency policy into the project-level `oh-package.json5` `overrides` field.
|
|
5
|
+
|
|
6
|
+
That means DevEco Sync, OHPM, lock files, and the build process can see the same static dependency declaration.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// root hvigorfile.ts
|
|
12
|
+
import { appTasks } from '@ohos/hvigor-ohos-plugin';
|
|
13
|
+
import { dependencyPolicyPlugin } from '@didi/hvigor-dependency-policy-plugin';
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
system: appTasks,
|
|
17
|
+
plugins: [
|
|
18
|
+
dependencyPolicyPlugin({
|
|
19
|
+
profile: 'stable',
|
|
20
|
+
dryRun: false,
|
|
21
|
+
logLevel: 'debug'
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You can also select the profile with an environment variable:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
DEP_POLICY_PROFILE=gray ./hvigorw --sync
|
|
31
|
+
DEP_POLICY_PROFILE=gray ./hvigorw assembleApp --no-daemon
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
By default the plugin reads:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
<pluginRoot>/config/dependency-policy.json5
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You can override it:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
dependencyPolicyPlugin({
|
|
46
|
+
configPath: './config/dependency-policy.json5'
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Schema v2
|
|
51
|
+
|
|
52
|
+
```json5
|
|
53
|
+
{
|
|
54
|
+
schemaVersion: 2,
|
|
55
|
+
defaultProfile: 'stable',
|
|
56
|
+
conflictPolicy: 'warn',
|
|
57
|
+
pathBase: 'projectRoot',
|
|
58
|
+
report: true,
|
|
59
|
+
profiles: {
|
|
60
|
+
stable: {
|
|
61
|
+
app: {
|
|
62
|
+
strategy: 'merge',
|
|
63
|
+
overrides: {
|
|
64
|
+
'@company/core': '2.4.0',
|
|
65
|
+
'@company/logger': 'file:./libs/logger.har'
|
|
66
|
+
},
|
|
67
|
+
remove: {
|
|
68
|
+
overrides: ['@company/legacy-core']
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Notes
|
|
77
|
+
|
|
78
|
+
- `strategy: 'merge'` keeps existing project overrides and adds/updates the policy items.
|
|
79
|
+
- `strategy: 'replace'` rewrites the whole `overrides` object.
|
|
80
|
+
- `file:` paths are written relative to the project root so they are valid inside project-level `oh-package.json5`.
|
|
81
|
+
- Module-level `profiles.<profile>.modules` rules are intentionally ignored in v2. Move dependency versions that should be forced globally into `profiles.<profile>.app.overrides`.
|
|
82
|
+
- A report is written to `build/dependency-policy/overrides-report.json`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
schemaVersion: 2,
|
|
3
|
+
|
|
4
|
+
// If dependencyPolicyPlugin({ profile }) and DEP_POLICY_PROFILE are both absent,
|
|
5
|
+
// this profile will be used.
|
|
6
|
+
defaultProfile: "stable",
|
|
7
|
+
|
|
8
|
+
// pluginWins | projectWins | warn | error
|
|
9
|
+
conflictPolicy: "warn",
|
|
10
|
+
|
|
11
|
+
// projectRoot | configFile | pluginRoot
|
|
12
|
+
pathBase: "projectRoot",
|
|
13
|
+
|
|
14
|
+
// Write build/dependency-policy/overrides-report.json
|
|
15
|
+
report: true,
|
|
16
|
+
|
|
17
|
+
profiles: {
|
|
18
|
+
stable: {
|
|
19
|
+
app: {
|
|
20
|
+
// merge keeps existing project overrides. replace rewrites the whole overrides object.
|
|
21
|
+
strategy: "merge",
|
|
22
|
+
|
|
23
|
+
overrides: {
|
|
24
|
+
"@didi-wyc/quality": "1.0.20",
|
|
25
|
+
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
remove: {
|
|
29
|
+
overrides: [
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
gray: {
|
|
36
|
+
app: {
|
|
37
|
+
strategy: "merge",
|
|
38
|
+
overrides: {
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DependencyMap, OverridesDiff } from './types';
|
|
2
|
+
export declare function diffOverrides(before: DependencyMap, after: DependencyMap, keptByProjectWins: Record<string, {
|
|
3
|
+
project: string;
|
|
4
|
+
plugin: string;
|
|
5
|
+
}>): OverridesDiff;
|
|
6
|
+
export declare function hasOverridesDiff(diff: OverridesDiff): boolean;
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffOverrides = diffOverrides;
|
|
4
|
+
exports.hasOverridesDiff = hasOverridesDiff;
|
|
5
|
+
function diffOverrides(before, after, keptByProjectWins) {
|
|
6
|
+
const added = {};
|
|
7
|
+
const changed = {};
|
|
8
|
+
const removed = {};
|
|
9
|
+
for (const [name, afterValue] of Object.entries(after)) {
|
|
10
|
+
if (before[name] === undefined) {
|
|
11
|
+
added[name] = afterValue;
|
|
12
|
+
}
|
|
13
|
+
else if (before[name] !== afterValue) {
|
|
14
|
+
changed[name] = {
|
|
15
|
+
from: before[name],
|
|
16
|
+
to: afterValue
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const [name, beforeValue] of Object.entries(before)) {
|
|
21
|
+
if (after[name] === undefined) {
|
|
22
|
+
removed[name] = beforeValue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
before,
|
|
27
|
+
after,
|
|
28
|
+
added,
|
|
29
|
+
changed,
|
|
30
|
+
removed,
|
|
31
|
+
keptByProjectWins
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function hasOverridesDiff(diff) {
|
|
35
|
+
return (Object.keys(diff.added).length > 0 ||
|
|
36
|
+
Object.keys(diff.changed).length > 0 ||
|
|
37
|
+
Object.keys(diff.removed).length > 0 ||
|
|
38
|
+
Object.keys(diff.keptByProjectWins).length > 0);
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DependencyMap, OverridesDiff } from './types';
|
|
2
|
+
export declare function diffOverrides(before: DependencyMap, after: DependencyMap, keptByProjectWins: Record<string, {
|
|
3
|
+
project: string;
|
|
4
|
+
plugin: string;
|
|
5
|
+
}>): OverridesDiff;
|
|
6
|
+
export declare function hasOverridesDiff(diff: OverridesDiff): boolean;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffOverrides = diffOverrides;
|
|
4
|
+
exports.hasOverridesDiff = hasOverridesDiff;
|
|
5
|
+
function diffOverrides(before, after, keptByProjectWins) {
|
|
6
|
+
const added = {};
|
|
7
|
+
const changed = {};
|
|
8
|
+
const removed = {};
|
|
9
|
+
for (const [name, afterValue] of Object.entries(after)) {
|
|
10
|
+
if (before[name] === undefined) {
|
|
11
|
+
added[name] = afterValue;
|
|
12
|
+
}
|
|
13
|
+
else if (before[name] !== afterValue) {
|
|
14
|
+
changed[name] = {
|
|
15
|
+
from: before[name],
|
|
16
|
+
to: afterValue
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const [name, beforeValue] of Object.entries(before)) {
|
|
21
|
+
if (after[name] === undefined) {
|
|
22
|
+
removed[name] = beforeValue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
before,
|
|
27
|
+
after,
|
|
28
|
+
added,
|
|
29
|
+
changed,
|
|
30
|
+
removed,
|
|
31
|
+
keptByProjectWins
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function hasOverridesDiff(diff) {
|
|
35
|
+
return (Object.keys(diff.added).length > 0 ||
|
|
36
|
+
Object.keys(diff.changed).length > 0 ||
|
|
37
|
+
Object.keys(diff.removed).length > 0 ||
|
|
38
|
+
Object.keys(diff.keptByProjectWins).length > 0);
|
|
39
|
+
}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LogLevel } from './types';
|
|
2
|
+
export declare class Logger {
|
|
3
|
+
private readonly level;
|
|
4
|
+
constructor(level?: LogLevel);
|
|
5
|
+
debug(message: string, data?: unknown): void;
|
|
6
|
+
info(message: string, data?: unknown): void;
|
|
7
|
+
warn(message: string, data?: unknown): void;
|
|
8
|
+
error(message: string, data?: unknown): void;
|
|
9
|
+
fatal(message: string, data?: unknown): void;
|
|
10
|
+
private write;
|
|
11
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = void 0;
|
|
4
|
+
const LEVEL_WEIGHT = {
|
|
5
|
+
debug: 10,
|
|
6
|
+
info: 20,
|
|
7
|
+
warn: 30,
|
|
8
|
+
error: 40,
|
|
9
|
+
silent: 100
|
|
10
|
+
};
|
|
11
|
+
class Logger {
|
|
12
|
+
constructor(level = 'info') {
|
|
13
|
+
this.level = level;
|
|
14
|
+
}
|
|
15
|
+
debug(message, data) {
|
|
16
|
+
this.write('debug', message, data);
|
|
17
|
+
}
|
|
18
|
+
info(message, data) {
|
|
19
|
+
this.write('info', message, data);
|
|
20
|
+
}
|
|
21
|
+
warn(message, data) {
|
|
22
|
+
this.write('warn', message, data);
|
|
23
|
+
}
|
|
24
|
+
error(message, data) {
|
|
25
|
+
this.write('error', message, data);
|
|
26
|
+
}
|
|
27
|
+
fatal(message, data) {
|
|
28
|
+
this.write('error', message, data);
|
|
29
|
+
}
|
|
30
|
+
write(level, message, data) {
|
|
31
|
+
if (LEVEL_WEIGHT[level] < LEVEL_WEIGHT[this.level]) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const prefix = `[dependency-policy] ${level.toUpperCase()}: ${message}`;
|
|
35
|
+
if (data === undefined) {
|
|
36
|
+
console.error(prefix);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
console.error(prefix, JSON.stringify(data, null, 2));
|
|
41
|
+
}
|
|
42
|
+
catch (_error) {
|
|
43
|
+
console.error(prefix, data);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.Logger = Logger;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DependencyMap } from './types';
|
|
2
|
+
export interface ProjectOhPackage {
|
|
3
|
+
path: string;
|
|
4
|
+
text: string;
|
|
5
|
+
data: Record<string, unknown>;
|
|
6
|
+
overrides: DependencyMap;
|
|
7
|
+
}
|
|
8
|
+
export declare function readProjectOhPackage(projectOhPackagePath: string): ProjectOhPackage;
|
|
9
|
+
export declare function writeProjectOverrides(projectOhPackagePath: string, originalText: string, overrides: DependencyMap, backup: boolean): boolean;
|
|
10
|
+
export declare function normalizeDependencyMap(value: unknown): DependencyMap;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readProjectOhPackage = readProjectOhPackage;
|
|
7
|
+
exports.writeProjectOverrides = writeProjectOverrides;
|
|
8
|
+
exports.normalizeDependencyMap = normalizeDependencyMap;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const json5_1 = __importDefault(require("json5"));
|
|
12
|
+
function readProjectOhPackage(projectOhPackagePath) {
|
|
13
|
+
if (!fs_1.default.existsSync(projectOhPackagePath)) {
|
|
14
|
+
throw new Error(`[dependency-policy] project oh-package.json5 not found: ${projectOhPackagePath}`);
|
|
15
|
+
}
|
|
16
|
+
const text = fs_1.default.readFileSync(projectOhPackagePath, 'utf-8');
|
|
17
|
+
const data = json5_1.default.parse(text);
|
|
18
|
+
const overrides = normalizeDependencyMap(data.overrides);
|
|
19
|
+
return {
|
|
20
|
+
path: projectOhPackagePath,
|
|
21
|
+
text,
|
|
22
|
+
data,
|
|
23
|
+
overrides
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function writeProjectOverrides(projectOhPackagePath, originalText, overrides, backup) {
|
|
27
|
+
const nextText = updateTopLevelObjectProperty(originalText, 'overrides', overrides);
|
|
28
|
+
if (nextText === originalText) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (backup) {
|
|
32
|
+
const backupPath = `${projectOhPackagePath}.dependency-policy.bak`;
|
|
33
|
+
if (!fs_1.default.existsSync(backupPath)) {
|
|
34
|
+
fs_1.default.writeFileSync(backupPath, originalText, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
fs_1.default.mkdirSync(path_1.default.dirname(projectOhPackagePath), { recursive: true });
|
|
38
|
+
fs_1.default.writeFileSync(projectOhPackagePath, nextText, 'utf-8');
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function normalizeDependencyMap(value) {
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const [key, itemValue] of Object.entries(value)) {
|
|
47
|
+
if (typeof itemValue === 'string') {
|
|
48
|
+
result[key] = itemValue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function updateTopLevelObjectProperty(text, propertyName, value) {
|
|
54
|
+
const rootOpen = findFirstNonCommentChar(text, '{', 0);
|
|
55
|
+
if (rootOpen < 0) {
|
|
56
|
+
throw new Error('[dependency-policy] invalid oh-package.json5: root object not found.');
|
|
57
|
+
}
|
|
58
|
+
const rootClose = findMatchingBracket(text, rootOpen);
|
|
59
|
+
if (rootClose < 0) {
|
|
60
|
+
throw new Error('[dependency-policy] invalid oh-package.json5: root object is not closed.');
|
|
61
|
+
}
|
|
62
|
+
const propertyRange = findTopLevelProperty(text, rootOpen, rootClose, propertyName);
|
|
63
|
+
if (propertyRange) {
|
|
64
|
+
const propertyIndent = getLineIndent(text, propertyRange.propertyStart);
|
|
65
|
+
const renderedValue = renderDependencyMap(value, propertyIndent);
|
|
66
|
+
return text.slice(0, propertyRange.valueStart) + renderedValue + text.slice(propertyRange.valueEnd);
|
|
67
|
+
}
|
|
68
|
+
const rootIndent = getLineIndent(text, rootOpen);
|
|
69
|
+
const propertyIndent = `${rootIndent} `;
|
|
70
|
+
const renderedValue = renderDependencyMap(value, propertyIndent);
|
|
71
|
+
const beforeClose = text.slice(rootOpen + 1, rootClose);
|
|
72
|
+
const hasExistingProperties = beforeClose.trim().length > 0;
|
|
73
|
+
const lastContentIndex = findLastNonWhitespaceIndex(text, rootOpen + 1, rootClose);
|
|
74
|
+
const needsComma = hasExistingProperties && lastContentIndex >= 0 && text[lastContentIndex] !== ',';
|
|
75
|
+
const insertion = hasExistingProperties
|
|
76
|
+
? `${needsComma ? ',' : ''}\n${propertyIndent}${propertyName}: ${renderedValue}\n`
|
|
77
|
+
: `\n${propertyIndent}${propertyName}: ${renderedValue}\n`;
|
|
78
|
+
return text.slice(0, rootClose) + insertion + text.slice(rootClose);
|
|
79
|
+
}
|
|
80
|
+
function findTopLevelProperty(text, rootOpen, rootClose, targetName) {
|
|
81
|
+
let index = rootOpen + 1;
|
|
82
|
+
while (index < rootClose) {
|
|
83
|
+
index = skipWhitespaceAndComments(text, index, rootClose);
|
|
84
|
+
if (index >= rootClose || text[index] === '}') {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const propertyStart = index;
|
|
88
|
+
const nameResult = readPropertyName(text, index, rootClose);
|
|
89
|
+
if (!nameResult) {
|
|
90
|
+
index += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
index = skipWhitespaceAndComments(text, nameResult.end, rootClose);
|
|
94
|
+
if (text[index] !== ':') {
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const valueStart = skipWhitespaceAndComments(text, index + 1, rootClose);
|
|
99
|
+
const valueEnd = readValueEnd(text, valueStart, rootClose);
|
|
100
|
+
if (nameResult.name === targetName) {
|
|
101
|
+
return {
|
|
102
|
+
propertyStart,
|
|
103
|
+
valueStart,
|
|
104
|
+
valueEnd
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
index = valueEnd;
|
|
108
|
+
if (text[index] === ',') {
|
|
109
|
+
index += 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
function readPropertyName(text, start, end) {
|
|
115
|
+
const char = text[start];
|
|
116
|
+
if (char === '"' || char === "'") {
|
|
117
|
+
const stringEnd = readStringEnd(text, start, end);
|
|
118
|
+
const raw = text.slice(start, stringEnd);
|
|
119
|
+
try {
|
|
120
|
+
return {
|
|
121
|
+
name: json5_1.default.parse(raw),
|
|
122
|
+
end: stringEnd
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (_error) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!/[A-Za-z_$]/.test(char)) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
let index = start + 1;
|
|
133
|
+
while (index < end && /[A-Za-z0-9_$-]/.test(text[index])) {
|
|
134
|
+
index += 1;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
name: text.slice(start, index),
|
|
138
|
+
end: index
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function readValueEnd(text, start, hardEnd) {
|
|
142
|
+
if (start >= hardEnd) {
|
|
143
|
+
return start;
|
|
144
|
+
}
|
|
145
|
+
const first = text[start];
|
|
146
|
+
if (first === '{' || first === '[') {
|
|
147
|
+
const bracketEnd = findMatchingBracket(text, start);
|
|
148
|
+
return bracketEnd >= 0 ? bracketEnd + 1 : hardEnd;
|
|
149
|
+
}
|
|
150
|
+
if (first === '"' || first === "'") {
|
|
151
|
+
return readStringEnd(text, start, hardEnd);
|
|
152
|
+
}
|
|
153
|
+
let index = start;
|
|
154
|
+
while (index < hardEnd) {
|
|
155
|
+
const char = text[index];
|
|
156
|
+
if (char === ',' || char === '}') {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
if (char === '/' && text[index + 1] === '/') {
|
|
160
|
+
index = skipLineComment(text, index + 2, hardEnd);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === '/' && text[index + 1] === '*') {
|
|
164
|
+
index = skipBlockComment(text, index + 2, hardEnd);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
index += 1;
|
|
168
|
+
}
|
|
169
|
+
return trimRightIndex(text, start, index);
|
|
170
|
+
}
|
|
171
|
+
function findMatchingBracket(text, openIndex) {
|
|
172
|
+
const openChar = text[openIndex];
|
|
173
|
+
const closeChar = openChar === '{' ? '}' : openChar === '[' ? ']' : undefined;
|
|
174
|
+
if (!closeChar) {
|
|
175
|
+
return -1;
|
|
176
|
+
}
|
|
177
|
+
let depth = 0;
|
|
178
|
+
let index = openIndex;
|
|
179
|
+
while (index < text.length) {
|
|
180
|
+
const char = text[index];
|
|
181
|
+
if (char === '"' || char === "'") {
|
|
182
|
+
index = readStringEnd(text, index, text.length);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (char === '/' && text[index + 1] === '/') {
|
|
186
|
+
index = skipLineComment(text, index + 2, text.length);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (char === '/' && text[index + 1] === '*') {
|
|
190
|
+
index = skipBlockComment(text, index + 2, text.length);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (char === openChar) {
|
|
194
|
+
depth += 1;
|
|
195
|
+
}
|
|
196
|
+
else if (char === closeChar) {
|
|
197
|
+
depth -= 1;
|
|
198
|
+
if (depth === 0) {
|
|
199
|
+
return index;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
index += 1;
|
|
203
|
+
}
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
function findFirstNonCommentChar(text, expected, start) {
|
|
207
|
+
let index = start;
|
|
208
|
+
while (index < text.length) {
|
|
209
|
+
index = skipWhitespaceAndComments(text, index, text.length);
|
|
210
|
+
if (text[index] === expected) {
|
|
211
|
+
return index;
|
|
212
|
+
}
|
|
213
|
+
if (index >= text.length) {
|
|
214
|
+
return -1;
|
|
215
|
+
}
|
|
216
|
+
index += 1;
|
|
217
|
+
}
|
|
218
|
+
return -1;
|
|
219
|
+
}
|
|
220
|
+
function skipWhitespaceAndComments(text, start, end) {
|
|
221
|
+
let index = start;
|
|
222
|
+
while (index < end) {
|
|
223
|
+
while (index < end && /\s/.test(text[index])) {
|
|
224
|
+
index += 1;
|
|
225
|
+
}
|
|
226
|
+
if (text[index] === '/' && text[index + 1] === '/') {
|
|
227
|
+
index = skipLineComment(text, index + 2, end);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (text[index] === '/' && text[index + 1] === '*') {
|
|
231
|
+
index = skipBlockComment(text, index + 2, end);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
return index;
|
|
237
|
+
}
|
|
238
|
+
function skipLineComment(text, start, end) {
|
|
239
|
+
let index = start;
|
|
240
|
+
while (index < end && text[index] !== '\n') {
|
|
241
|
+
index += 1;
|
|
242
|
+
}
|
|
243
|
+
return index;
|
|
244
|
+
}
|
|
245
|
+
function skipBlockComment(text, start, end) {
|
|
246
|
+
const close = text.indexOf('*/', start);
|
|
247
|
+
return close >= 0 && close < end ? close + 2 : end;
|
|
248
|
+
}
|
|
249
|
+
function readStringEnd(text, start, end) {
|
|
250
|
+
const quote = text[start];
|
|
251
|
+
let index = start + 1;
|
|
252
|
+
while (index < end) {
|
|
253
|
+
if (text[index] === '\\') {
|
|
254
|
+
index += 2;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (text[index] === quote) {
|
|
258
|
+
return index + 1;
|
|
259
|
+
}
|
|
260
|
+
index += 1;
|
|
261
|
+
}
|
|
262
|
+
return end;
|
|
263
|
+
}
|
|
264
|
+
function trimRightIndex(text, start, end) {
|
|
265
|
+
let index = end;
|
|
266
|
+
while (index > start && /\s/.test(text[index - 1])) {
|
|
267
|
+
index -= 1;
|
|
268
|
+
}
|
|
269
|
+
return index;
|
|
270
|
+
}
|
|
271
|
+
function findLastNonWhitespaceIndex(text, start, end) {
|
|
272
|
+
let index = end - 1;
|
|
273
|
+
while (index >= start && /\s/.test(text[index])) {
|
|
274
|
+
index -= 1;
|
|
275
|
+
}
|
|
276
|
+
return index;
|
|
277
|
+
}
|
|
278
|
+
function getLineIndent(text, index) {
|
|
279
|
+
const lineStart = text.lastIndexOf('\n', index - 1) + 1;
|
|
280
|
+
const match = /^[ \t]*/.exec(text.slice(lineStart, index));
|
|
281
|
+
return match ? match[0] : '';
|
|
282
|
+
}
|
|
283
|
+
function renderDependencyMap(value, propertyIndent) {
|
|
284
|
+
const entries = Object.entries(value);
|
|
285
|
+
if (entries.length === 0) {
|
|
286
|
+
return '{}';
|
|
287
|
+
}
|
|
288
|
+
const childIndent = `${propertyIndent} `;
|
|
289
|
+
const lines = ['{'];
|
|
290
|
+
entries.forEach(([key, itemValue], index) => {
|
|
291
|
+
const comma = index === entries.length - 1 ? '' : ',';
|
|
292
|
+
lines.push(`${childIndent}${JSON.stringify(key)}: ${JSON.stringify(itemValue)}${comma}`);
|
|
293
|
+
});
|
|
294
|
+
lines.push(`${propertyIndent}}`);
|
|
295
|
+
return lines.join('\n');
|
|
296
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DependencyMap, LoadedPolicyConfig, OverridesDiff } from './types';
|
|
2
|
+
import type { Logger } from './logger';
|
|
3
|
+
export interface BuildOverridesResult {
|
|
4
|
+
after: DependencyMap;
|
|
5
|
+
diff: OverridesDiff;
|
|
6
|
+
warnings: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function buildProjectOverrides(before: DependencyMap, loaded: LoadedPolicyConfig, profileName: string, projectRoot: string, logger: Logger): BuildOverridesResult;
|
|
9
|
+
export declare function resolveProjectOhPackagePath(projectRoot: string, loaded: LoadedPolicyConfig, projectOhPackagePath?: string): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildProjectOverrides = buildProjectOverrides;
|
|
7
|
+
exports.resolveProjectOhPackagePath = resolveProjectOhPackagePath;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const path_utils_1 = require("./path-utils");
|
|
10
|
+
const diff_1 = require("./diff");
|
|
11
|
+
function buildProjectOverrides(before, loaded, profileName, projectRoot, logger) {
|
|
12
|
+
const config = loaded.config;
|
|
13
|
+
const profile = config.profiles[profileName];
|
|
14
|
+
const appRule = profile.app ?? {};
|
|
15
|
+
const warnings = [];
|
|
16
|
+
if (profile.modules && Object.keys(profile.modules).length > 0) {
|
|
17
|
+
const warning = `profile "${profileName}" contains modules rules, but schemaVersion 2 only materializes ` +
|
|
18
|
+
'project-level oh-package.json5 overrides. Move desired dependency versions into profiles.<name>.app.overrides.';
|
|
19
|
+
warnings.push(warning);
|
|
20
|
+
logger.warn(warning);
|
|
21
|
+
}
|
|
22
|
+
const env = {
|
|
23
|
+
projectRoot,
|
|
24
|
+
configDir: loaded.configDir,
|
|
25
|
+
pluginRoot: loaded.pluginRoot,
|
|
26
|
+
pathBase: config.pathBase ?? 'projectRoot',
|
|
27
|
+
conflictPolicy: config.conflictPolicy ?? 'warn'
|
|
28
|
+
};
|
|
29
|
+
const after = applyAppRule(before, appRule, env, logger, warnings);
|
|
30
|
+
const keptByProjectWins = collectKeptByProjectWins(before, appRule, env);
|
|
31
|
+
const diff = (0, diff_1.diffOverrides)(before, after, keptByProjectWins);
|
|
32
|
+
return {
|
|
33
|
+
after,
|
|
34
|
+
diff,
|
|
35
|
+
warnings
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function applyAppRule(before, rule, env, logger, warnings) {
|
|
39
|
+
const strategy = rule.strategy ?? 'merge';
|
|
40
|
+
const next = strategy === 'replace' ? {} : { ...before };
|
|
41
|
+
for (const packageName of rule.remove?.overrides ?? []) {
|
|
42
|
+
delete next[packageName];
|
|
43
|
+
}
|
|
44
|
+
for (const [packageName, rawValue] of Object.entries(rule.overrides ?? {})) {
|
|
45
|
+
const pluginValue = (0, path_utils_1.normalizeDependencyValueForProjectOhPackage)(rawValue, env, rule.pathBase);
|
|
46
|
+
const projectValue = before[packageName];
|
|
47
|
+
if (projectValue !== undefined && projectValue !== pluginValue) {
|
|
48
|
+
const decision = handleConflict(packageName, projectValue, pluginValue, env.conflictPolicy, logger, warnings);
|
|
49
|
+
if (decision === 'keepProject') {
|
|
50
|
+
next[packageName] = projectValue;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
next[packageName] = pluginValue;
|
|
55
|
+
}
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
58
|
+
function collectKeptByProjectWins(before, rule, env) {
|
|
59
|
+
const result = {};
|
|
60
|
+
if (env.conflictPolicy !== 'projectWins') {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
for (const [packageName, rawValue] of Object.entries(rule.overrides ?? {})) {
|
|
64
|
+
const pluginValue = (0, path_utils_1.normalizeDependencyValueForProjectOhPackage)(rawValue, env, rule.pathBase);
|
|
65
|
+
const projectValue = before[packageName];
|
|
66
|
+
if (projectValue !== undefined && projectValue !== pluginValue) {
|
|
67
|
+
result[packageName] = {
|
|
68
|
+
project: projectValue,
|
|
69
|
+
plugin: pluginValue
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function handleConflict(packageName, projectValue, pluginValue, conflictPolicy, logger, warnings) {
|
|
76
|
+
const message = `override conflict for ${packageName}: project oh-package.json5 has ${projectValue}, ` +
|
|
77
|
+
`policy wants ${pluginValue}.`;
|
|
78
|
+
switch (conflictPolicy) {
|
|
79
|
+
case 'pluginWins':
|
|
80
|
+
return 'usePlugin';
|
|
81
|
+
case 'projectWins':
|
|
82
|
+
logger.info(`${message} Keep project value because conflictPolicy=projectWins.`);
|
|
83
|
+
return 'keepProject';
|
|
84
|
+
case 'warn': {
|
|
85
|
+
const warning = `${message} Use policy value because conflictPolicy=warn.`;
|
|
86
|
+
warnings.push(warning);
|
|
87
|
+
logger.warn(warning);
|
|
88
|
+
return 'usePlugin';
|
|
89
|
+
}
|
|
90
|
+
case 'error':
|
|
91
|
+
throw new Error(`[dependency-policy] ${message}`);
|
|
92
|
+
default:
|
|
93
|
+
return 'usePlugin';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function resolveProjectOhPackagePath(projectRoot, loaded, projectOhPackagePath) {
|
|
97
|
+
const configured = projectOhPackagePath ?? loaded.config.projectOhPackagePath;
|
|
98
|
+
if (!configured) {
|
|
99
|
+
return path_1.default.resolve(projectRoot, 'oh-package.json5');
|
|
100
|
+
}
|
|
101
|
+
return path_1.default.isAbsolute(configured)
|
|
102
|
+
? configured
|
|
103
|
+
: path_1.default.resolve(projectRoot, configured);
|
|
104
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeDependencyValueForProjectOhPackage = normalizeDependencyValueForProjectOhPackage;
|
|
7
|
+
exports.toPosix = toPosix;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function normalizeDependencyValueForProjectOhPackage(rawValue, env, rulePathBase) {
|
|
10
|
+
if (!rawValue.startsWith('file:')) {
|
|
11
|
+
return rawValue;
|
|
12
|
+
}
|
|
13
|
+
const rawPath = rawValue.slice('file:'.length);
|
|
14
|
+
if (rawPath.length === 0) {
|
|
15
|
+
return rawValue;
|
|
16
|
+
}
|
|
17
|
+
const absolutePath = resolveFileDependencyPath(rawPath, env, rulePathBase);
|
|
18
|
+
const relativeToProject = toPosix(path_1.default.relative(env.projectRoot, absolutePath));
|
|
19
|
+
if (relativeToProject.length === 0) {
|
|
20
|
+
return 'file:.';
|
|
21
|
+
}
|
|
22
|
+
if (relativeToProject.startsWith('..')) {
|
|
23
|
+
return `file:${relativeToProject}`;
|
|
24
|
+
}
|
|
25
|
+
if (relativeToProject.startsWith('.')) {
|
|
26
|
+
return `file:${relativeToProject}`;
|
|
27
|
+
}
|
|
28
|
+
return `file:./${relativeToProject}`;
|
|
29
|
+
}
|
|
30
|
+
function resolveFileDependencyPath(rawPath, env, rulePathBase) {
|
|
31
|
+
const prefixed = resolveExplicitPrefix(rawPath, env);
|
|
32
|
+
if (prefixed) {
|
|
33
|
+
return prefixed;
|
|
34
|
+
}
|
|
35
|
+
if (path_1.default.isAbsolute(rawPath)) {
|
|
36
|
+
return path_1.default.resolve(rawPath);
|
|
37
|
+
}
|
|
38
|
+
const base = resolvePathBase(rulePathBase ?? env.pathBase, env);
|
|
39
|
+
return path_1.default.resolve(base, rawPath);
|
|
40
|
+
}
|
|
41
|
+
function resolveExplicitPrefix(rawPath, env) {
|
|
42
|
+
const candidates = [
|
|
43
|
+
['$project/', env.projectRoot],
|
|
44
|
+
['$config/', env.configDir],
|
|
45
|
+
['$plugin/', env.pluginRoot]
|
|
46
|
+
];
|
|
47
|
+
for (const [prefix, base] of candidates) {
|
|
48
|
+
if (rawPath.startsWith(prefix)) {
|
|
49
|
+
return path_1.default.resolve(base, rawPath.slice(prefix.length));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function resolvePathBase(base, env) {
|
|
55
|
+
switch (base) {
|
|
56
|
+
case 'projectRoot':
|
|
57
|
+
return env.projectRoot;
|
|
58
|
+
case 'configFile':
|
|
59
|
+
return env.configDir;
|
|
60
|
+
case 'pluginRoot':
|
|
61
|
+
return env.pluginRoot;
|
|
62
|
+
default:
|
|
63
|
+
return env.projectRoot;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function toPosix(value) {
|
|
67
|
+
return value.split(path_1.default.sep).join('/');
|
|
68
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DependencyPolicyConfig, DependencyPolicyPluginOptions, LoadedPolicyConfig } from './types';
|
|
2
|
+
export declare function loadPolicyConfig(projectRoot: string, options: DependencyPolicyPluginOptions): LoadedPolicyConfig;
|
|
3
|
+
export declare function resolveProfileName(config: DependencyPolicyConfig, options: DependencyPolicyPluginOptions): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadPolicyConfig = loadPolicyConfig;
|
|
7
|
+
exports.resolveProfileName = resolveProfileName;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const json5_1 = __importDefault(require("json5"));
|
|
11
|
+
const VALID_CONFLICT_POLICIES = [
|
|
12
|
+
'pluginWins',
|
|
13
|
+
'projectWins',
|
|
14
|
+
'warn',
|
|
15
|
+
'error'
|
|
16
|
+
];
|
|
17
|
+
const VALID_PATH_BASES = [
|
|
18
|
+
'projectRoot',
|
|
19
|
+
'configFile',
|
|
20
|
+
'pluginRoot'
|
|
21
|
+
];
|
|
22
|
+
function loadPolicyConfig(projectRoot, options) {
|
|
23
|
+
const pluginRoot = path_1.default.resolve(__dirname, '..');
|
|
24
|
+
const configPath = options.configPath
|
|
25
|
+
? resolveMaybeRelative(projectRoot, options.configPath)
|
|
26
|
+
: path_1.default.resolve(pluginRoot, 'config', 'dependency-policy.json5');
|
|
27
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
28
|
+
throw new Error(`[dependency-policy] config not found: ${configPath}`);
|
|
29
|
+
}
|
|
30
|
+
const content = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
31
|
+
const config = json5_1.default.parse(content);
|
|
32
|
+
validatePolicyConfig(config, configPath);
|
|
33
|
+
return {
|
|
34
|
+
config,
|
|
35
|
+
configPath,
|
|
36
|
+
configDir: path_1.default.dirname(configPath),
|
|
37
|
+
pluginRoot
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resolveProfileName(config, options) {
|
|
41
|
+
const profileName = options.profile ?? process.env.DEP_POLICY_PROFILE ?? config.defaultProfile;
|
|
42
|
+
if (!profileName) {
|
|
43
|
+
throw new Error('[dependency-policy] profile is not specified. Pass dependencyPolicyPlugin({ profile }) or set DEP_POLICY_PROFILE.');
|
|
44
|
+
}
|
|
45
|
+
if (!config.profiles || !config.profiles[profileName]) {
|
|
46
|
+
throw new Error(`[dependency-policy] profile not found: ${profileName}`);
|
|
47
|
+
}
|
|
48
|
+
return profileName;
|
|
49
|
+
}
|
|
50
|
+
function validatePolicyConfig(config, configPath) {
|
|
51
|
+
if (!config || typeof config !== 'object') {
|
|
52
|
+
throw new Error(`[dependency-policy] invalid config: ${configPath}`);
|
|
53
|
+
}
|
|
54
|
+
if (config.schemaVersion !== 2) {
|
|
55
|
+
throw new Error(`[dependency-policy] unsupported schemaVersion: ${String(config.schemaVersion)}. Expected 2.`);
|
|
56
|
+
}
|
|
57
|
+
if (!config.profiles || typeof config.profiles !== 'object') {
|
|
58
|
+
throw new Error('[dependency-policy] config.profiles is required.');
|
|
59
|
+
}
|
|
60
|
+
if (config.conflictPolicy !== undefined &&
|
|
61
|
+
!VALID_CONFLICT_POLICIES.includes(config.conflictPolicy)) {
|
|
62
|
+
throw new Error(`[dependency-policy] invalid conflictPolicy: ${config.conflictPolicy}`);
|
|
63
|
+
}
|
|
64
|
+
if (config.pathBase !== undefined && !VALID_PATH_BASES.includes(config.pathBase)) {
|
|
65
|
+
throw new Error(`[dependency-policy] invalid pathBase: ${config.pathBase}`);
|
|
66
|
+
}
|
|
67
|
+
for (const [profileName, profile] of Object.entries(config.profiles)) {
|
|
68
|
+
if (!profile || typeof profile !== 'object') {
|
|
69
|
+
throw new Error(`[dependency-policy] invalid profile: ${profileName}`);
|
|
70
|
+
}
|
|
71
|
+
if (profile.app?.strategy !== undefined && !['merge', 'replace'].includes(profile.app.strategy)) {
|
|
72
|
+
throw new Error(`[dependency-policy] invalid app.strategy in profile ${profileName}: ${profile.app.strategy}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function resolveMaybeRelative(baseDir, value) {
|
|
77
|
+
return path_1.default.isAbsolute(value) ? value : path_1.default.resolve(baseDir, value);
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveProjectRoot = resolveProjectRoot;
|
|
7
|
+
exports.safeCallString = safeCallString;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function resolveProjectRoot(currentNode) {
|
|
10
|
+
const fromNodeDir = safeCallString(currentNode, 'getNodeDir');
|
|
11
|
+
if (fromNodeDir) {
|
|
12
|
+
return path_1.default.resolve(fromNodeDir);
|
|
13
|
+
}
|
|
14
|
+
const fromNodePath = safeCallString(currentNode, 'getNodePath');
|
|
15
|
+
if (fromNodePath) {
|
|
16
|
+
return path_1.default.resolve(fromNodePath);
|
|
17
|
+
}
|
|
18
|
+
return process.cwd();
|
|
19
|
+
}
|
|
20
|
+
function safeCallString(target, methodName) {
|
|
21
|
+
const maybeObject = target;
|
|
22
|
+
const method = maybeObject?.[methodName];
|
|
23
|
+
if (typeof method !== 'function') {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const value = method.call(target);
|
|
28
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
catch (_error) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/report.d.ts
ADDED
package/dist/report.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeOverridesReport = writeOverridesReport;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function writeOverridesReport(projectRoot, report) {
|
|
10
|
+
const reportDir = path_1.default.resolve(projectRoot, 'build', 'dependency-policy');
|
|
11
|
+
const reportPath = path_1.default.resolve(reportDir, 'overrides-report.json');
|
|
12
|
+
fs_1.default.mkdirSync(reportDir, { recursive: true });
|
|
13
|
+
fs_1.default.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf-8');
|
|
14
|
+
return reportPath;
|
|
15
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
2
|
+
export type ConflictPolicy = 'pluginWins' | 'projectWins' | 'warn' | 'error';
|
|
3
|
+
export type PathBase = 'projectRoot' | 'configFile' | 'pluginRoot';
|
|
4
|
+
export type ApplyStrategy = 'merge' | 'replace';
|
|
5
|
+
export interface DependencyMap {
|
|
6
|
+
[packageName: string]: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DependencyPolicyPluginOptions {
|
|
9
|
+
/** Path relative to project root, or an absolute path. */
|
|
10
|
+
configPath?: string;
|
|
11
|
+
/** Explicit profile name. If omitted, DEP_POLICY_PROFILE or defaultProfile is used. */
|
|
12
|
+
profile?: string;
|
|
13
|
+
/** If true, compute and report changes but do not write oh-package.json5. */
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
/** Override config.report. */
|
|
16
|
+
report?: boolean;
|
|
17
|
+
/** Console log level. */
|
|
18
|
+
logLevel?: LogLevel;
|
|
19
|
+
/** Project root oh-package file. Defaults to <projectRoot>/oh-package.json5. */
|
|
20
|
+
projectOhPackagePath?: string;
|
|
21
|
+
/** Create a one-time backup next to oh-package.json5 before writing. Defaults to false. */
|
|
22
|
+
backup?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface DependencyPolicyConfig {
|
|
25
|
+
schemaVersion: 2;
|
|
26
|
+
defaultProfile?: string;
|
|
27
|
+
conflictPolicy?: ConflictPolicy;
|
|
28
|
+
pathBase?: PathBase;
|
|
29
|
+
report?: boolean;
|
|
30
|
+
projectOhPackagePath?: string;
|
|
31
|
+
profiles: Record<string, DependencyProfile>;
|
|
32
|
+
}
|
|
33
|
+
export interface DependencyProfile {
|
|
34
|
+
app?: AppRule;
|
|
35
|
+
/**
|
|
36
|
+
* v2 intentionally does not materialize module-level direct dependencies.
|
|
37
|
+
* Keep this field only so old configs produce a clear warning instead of failing parsing.
|
|
38
|
+
*/
|
|
39
|
+
modules?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export interface AppRule {
|
|
42
|
+
/** merge keeps existing overrides; replace rewrites the whole overrides object. */
|
|
43
|
+
strategy?: ApplyStrategy;
|
|
44
|
+
pathBase?: PathBase;
|
|
45
|
+
overrides?: DependencyMap;
|
|
46
|
+
remove?: {
|
|
47
|
+
overrides?: string[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface LoadedPolicyConfig {
|
|
51
|
+
config: DependencyPolicyConfig;
|
|
52
|
+
configPath: string;
|
|
53
|
+
configDir: string;
|
|
54
|
+
pluginRoot: string;
|
|
55
|
+
}
|
|
56
|
+
export interface OverridesDiff {
|
|
57
|
+
before: DependencyMap;
|
|
58
|
+
after: DependencyMap;
|
|
59
|
+
added: DependencyMap;
|
|
60
|
+
changed: Record<string, {
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
}>;
|
|
64
|
+
removed: DependencyMap;
|
|
65
|
+
keptByProjectWins: Record<string, {
|
|
66
|
+
project: string;
|
|
67
|
+
plugin: string;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface OverridesReport {
|
|
71
|
+
pluginId: string;
|
|
72
|
+
schemaVersion: number;
|
|
73
|
+
profile: string;
|
|
74
|
+
dryRun: boolean;
|
|
75
|
+
projectRoot: string;
|
|
76
|
+
projectOhPackagePath: string;
|
|
77
|
+
configPath: string;
|
|
78
|
+
changed: boolean;
|
|
79
|
+
warnings: string[];
|
|
80
|
+
overrides: OverridesDiff;
|
|
81
|
+
}
|
|
82
|
+
export interface ApplyEnvironment {
|
|
83
|
+
projectRoot: string;
|
|
84
|
+
configDir: string;
|
|
85
|
+
pluginRoot: string;
|
|
86
|
+
pathBase: PathBase;
|
|
87
|
+
conflictPolicy: ConflictPolicy;
|
|
88
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hvigor-dependency-policy-plugin",
|
|
3
|
+
"version": "0.2.0-beta",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"registry": "https://repo.harmonyos.com/npm/"
|
|
6
|
+
},
|
|
7
|
+
"description": "Apply dependency policy by materializing project-level oh-package.json5 overrides.",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"config",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"prepack": "npm run clean && npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"json5": "^2.2.3"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@ohos/hvigor": ">=5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@ohos/hvigor": ">=5.0.0",
|
|
28
|
+
"@types/node": "^20.11.30",
|
|
29
|
+
"typescript": "^5.4.5"
|
|
30
|
+
}
|
|
31
|
+
}
|