@esmx/core 3.0.0-rc.116 → 3.0.0-rc.118
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/LICENSE +21 -0
- package/README.md +17 -3
- package/README.zh-CN.md +20 -6
- package/dist/app.mjs +4 -2
- package/dist/cli/cli.d.ts +1 -0
- package/dist/cli/cli.mjs +28 -1
- package/dist/cli/validate.d.ts +15 -0
- package/dist/cli/validate.mjs +139 -0
- package/dist/cli/validate.test.d.ts +1 -0
- package/dist/cli/validate.test.mjs +216 -0
- package/dist/core.d.ts +31 -0
- package/dist/core.mjs +71 -5
- package/dist/declaration/index.d.ts +47 -0
- package/dist/declaration/index.mjs +63 -0
- package/dist/declaration/index.test.d.ts +1 -0
- package/dist/declaration/index.test.mjs +202 -0
- package/dist/declaration/lower.d.ts +11 -0
- package/dist/declaration/lower.mjs +78 -0
- package/dist/declaration/lower.test.d.ts +1 -0
- package/dist/declaration/lower.test.mjs +333 -0
- package/dist/declaration/reader.d.ts +28 -0
- package/dist/declaration/reader.mjs +55 -0
- package/dist/declaration/reinit.e2e.test.d.ts +1 -0
- package/dist/declaration/reinit.e2e.test.mjs +117 -0
- package/dist/declaration/resolver.d.ts +59 -0
- package/dist/declaration/resolver.mjs +430 -0
- package/dist/declaration/resolver.test.d.ts +1 -0
- package/dist/declaration/resolver.test.mjs +1005 -0
- package/dist/declaration/schema.d.ts +89 -0
- package/dist/declaration/schema.mjs +282 -0
- package/dist/declaration/schema.test.d.ts +1 -0
- package/dist/declaration/schema.test.mjs +101 -0
- package/dist/declaration/semver.d.ts +22 -0
- package/dist/declaration/semver.mjs +229 -0
- package/dist/declaration/semver.test.d.ts +1 -0
- package/dist/declaration/semver.test.mjs +87 -0
- package/dist/declaration/test-fixtures.d.ts +18 -0
- package/dist/declaration/test-fixtures.mjs +69 -0
- package/dist/declaration/types.d.ts +58 -0
- package/dist/declaration/types.mjs +21 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +10 -0
- package/dist/manifest-json.d.ts +61 -0
- package/dist/manifest-json.mjs +68 -5
- package/dist/manifest-json.test.d.ts +1 -0
- package/dist/manifest-json.test.mjs +196 -0
- package/dist/module-config.d.ts +45 -5
- package/dist/module-config.mjs +60 -49
- package/dist/module-config.test.mjs +227 -91
- package/dist/pack-config.d.ts +2 -9
- package/dist/render-context.mjs +22 -5
- package/dist/utils/import-map.d.ts +23 -3
- package/dist/utils/import-map.mjs +38 -1
- package/dist/utils/import-map.test.mjs +228 -0
- package/package.json +9 -6
- package/src/app.ts +5 -2
- package/src/cli/cli.ts +44 -1
- package/src/cli/validate.test.ts +251 -0
- package/src/cli/validate.ts +196 -0
- package/src/core.ts +84 -5
- package/src/declaration/index.test.ts +223 -0
- package/src/declaration/index.ts +135 -0
- package/src/declaration/lower.test.ts +372 -0
- package/src/declaration/lower.ts +135 -0
- package/src/declaration/reader.ts +93 -0
- package/src/declaration/reinit.e2e.test.ts +148 -0
- package/src/declaration/resolver.test.ts +1111 -0
- package/src/declaration/resolver.ts +638 -0
- package/src/declaration/schema.test.ts +118 -0
- package/src/declaration/schema.ts +339 -0
- package/src/declaration/semver.test.ts +101 -0
- package/src/declaration/semver.ts +278 -0
- package/src/declaration/test-fixtures.ts +96 -0
- package/src/declaration/types.ts +71 -0
- package/src/index.ts +28 -1
- package/src/manifest-json.test.ts +236 -0
- package/src/manifest-json.ts +166 -5
- package/src/module-config.test.ts +266 -105
- package/src/module-config.ts +130 -58
- package/src/pack-config.ts +2 -9
- package/src/render-context.ts +34 -6
- package/src/utils/import-map.test.ts +261 -0
- package/src/utils/import-map.ts +92 -6
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2
|
+
export function parseSemver(input) {
|
|
3
|
+
const match = VERSION_RE.exec(input.trim());
|
|
4
|
+
if (!match) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
major: Number(match[1]),
|
|
9
|
+
minor: Number(match[2]),
|
|
10
|
+
patch: Number(match[3]),
|
|
11
|
+
prerelease: match[4] ? match[4].split(".") : []
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function comparePrerelease(a, b) {
|
|
15
|
+
if (a.length === 0 && b.length === 0) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (a.length === 0) {
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (b.length === 0) {
|
|
22
|
+
return -1;
|
|
23
|
+
}
|
|
24
|
+
const length = Math.max(a.length, b.length);
|
|
25
|
+
for (let i = 0; i < length; i++) {
|
|
26
|
+
const idA = a[i];
|
|
27
|
+
const idB = b[i];
|
|
28
|
+
if (idA === void 0) {
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
if (idB === void 0) {
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const numA = /^\d+$/.test(idA) ? Number(idA) : null;
|
|
35
|
+
const numB = /^\d+$/.test(idB) ? Number(idB) : null;
|
|
36
|
+
if (numA !== null && numB !== null) {
|
|
37
|
+
if (numA !== numB) {
|
|
38
|
+
return numA < numB ? -1 : 1;
|
|
39
|
+
}
|
|
40
|
+
} else if (numA !== null) {
|
|
41
|
+
return -1;
|
|
42
|
+
} else if (numB !== null) {
|
|
43
|
+
return 1;
|
|
44
|
+
} else if (idA !== idB) {
|
|
45
|
+
return idA < idB ? -1 : 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
export function compareSemver(a, b) {
|
|
51
|
+
if (a.major !== b.major) {
|
|
52
|
+
return a.major < b.major ? -1 : 1;
|
|
53
|
+
}
|
|
54
|
+
if (a.minor !== b.minor) {
|
|
55
|
+
return a.minor < b.minor ? -1 : 1;
|
|
56
|
+
}
|
|
57
|
+
if (a.patch !== b.patch) {
|
|
58
|
+
return a.patch < b.patch ? -1 : 1;
|
|
59
|
+
}
|
|
60
|
+
return comparePrerelease(a.prerelease, b.prerelease);
|
|
61
|
+
}
|
|
62
|
+
function parsePartial(input) {
|
|
63
|
+
const trimmed = input.trim().replace(/^v/, "");
|
|
64
|
+
if (trimmed === "" || trimmed === "*" || /^x$/i.test(trimmed)) {
|
|
65
|
+
return { major: null, minor: null, patch: null, prerelease: [] };
|
|
66
|
+
}
|
|
67
|
+
const exact = parseSemver(trimmed);
|
|
68
|
+
if (exact) {
|
|
69
|
+
return exact;
|
|
70
|
+
}
|
|
71
|
+
const segments = trimmed.split(".");
|
|
72
|
+
if (segments.length > 3) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const parsed = [];
|
|
76
|
+
for (const segment of segments) {
|
|
77
|
+
if (segment === "*" || /^x$/i.test(segment)) {
|
|
78
|
+
parsed.push(null);
|
|
79
|
+
} else if (/^\d+$/.test(segment)) {
|
|
80
|
+
parsed.push(Number(segment));
|
|
81
|
+
} else {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
major: parsed[0] ?? null,
|
|
87
|
+
minor: parsed[1] ?? null,
|
|
88
|
+
patch: parsed[2] ?? null,
|
|
89
|
+
prerelease: []
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function lowerBound(partial) {
|
|
93
|
+
return {
|
|
94
|
+
major: partial.major ?? 0,
|
|
95
|
+
minor: partial.minor ?? 0,
|
|
96
|
+
patch: partial.patch ?? 0,
|
|
97
|
+
prerelease: partial.prerelease
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function xRangeUpperBound(partial) {
|
|
101
|
+
if (partial.major === null) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (partial.minor === null) {
|
|
105
|
+
return { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] };
|
|
106
|
+
}
|
|
107
|
+
if (partial.patch === null) {
|
|
108
|
+
return {
|
|
109
|
+
major: partial.major,
|
|
110
|
+
minor: partial.minor + 1,
|
|
111
|
+
patch: 0,
|
|
112
|
+
prerelease: []
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function caretUpperBound(partial) {
|
|
118
|
+
if (partial.major === null) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (partial.major > 0) {
|
|
122
|
+
return { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] };
|
|
123
|
+
}
|
|
124
|
+
if (partial.minor === null) {
|
|
125
|
+
return { major: 1, minor: 0, patch: 0, prerelease: [] };
|
|
126
|
+
}
|
|
127
|
+
if (partial.minor > 0) {
|
|
128
|
+
return { major: 0, minor: partial.minor + 1, patch: 0, prerelease: [] };
|
|
129
|
+
}
|
|
130
|
+
if (partial.patch === null) {
|
|
131
|
+
return { major: 0, minor: partial.minor + 1, patch: 0, prerelease: [] };
|
|
132
|
+
}
|
|
133
|
+
return { major: 0, minor: 0, patch: partial.patch + 1, prerelease: [] };
|
|
134
|
+
}
|
|
135
|
+
function tildeUpperBound(partial) {
|
|
136
|
+
if (partial.major === null) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (partial.minor === null) {
|
|
140
|
+
return { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
major: partial.major,
|
|
144
|
+
minor: partial.minor + 1,
|
|
145
|
+
patch: 0,
|
|
146
|
+
prerelease: []
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function satisfiesComparator(version, comparator) {
|
|
150
|
+
const match = /^(\^|~|>=|<=|>|<|=)?\s*(.*)$/.exec(comparator.trim());
|
|
151
|
+
if (!match) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const operator = match[1] ?? "";
|
|
155
|
+
const partial = parsePartial(match[2]);
|
|
156
|
+
if (!partial) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const lower = lowerBound(partial);
|
|
160
|
+
switch (operator) {
|
|
161
|
+
case ">=":
|
|
162
|
+
return compareSemver(version, lower) >= 0;
|
|
163
|
+
case ">":
|
|
164
|
+
return compareSemver(version, lower) > 0;
|
|
165
|
+
case "<=":
|
|
166
|
+
return compareSemver(version, lower) <= 0;
|
|
167
|
+
case "<":
|
|
168
|
+
return compareSemver(version, lower) < 0;
|
|
169
|
+
case "^": {
|
|
170
|
+
if (compareSemver(version, lower) < 0) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const upper = caretUpperBound(partial);
|
|
174
|
+
return upper === null || compareSemver(version, upper) < 0;
|
|
175
|
+
}
|
|
176
|
+
case "~": {
|
|
177
|
+
if (compareSemver(version, lower) < 0) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const upper = tildeUpperBound(partial);
|
|
181
|
+
return upper === null || compareSemver(version, upper) < 0;
|
|
182
|
+
}
|
|
183
|
+
default: {
|
|
184
|
+
if (partial.major !== null && partial.minor !== null && partial.patch !== null) {
|
|
185
|
+
return compareSemver(version, lower) === 0;
|
|
186
|
+
}
|
|
187
|
+
if (compareSemver(version, lower) < 0) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const upper = xRangeUpperBound(partial);
|
|
191
|
+
return upper === null || compareSemver(version, upper) < 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export function satisfiesRange(version, range) {
|
|
196
|
+
const parsedVersion = parseSemver(version);
|
|
197
|
+
if (!parsedVersion) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const trimmed = range.trim();
|
|
201
|
+
if (trimmed === "" || trimmed === "*" || /^x$/i.test(trimmed)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
for (const clause of trimmed.split("||")) {
|
|
205
|
+
const comparators = clause.trim().split(/\s+/).filter(Boolean);
|
|
206
|
+
if (comparators.length === 0) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
let clauseResult = true;
|
|
210
|
+
for (const comparator of comparators) {
|
|
211
|
+
const result = satisfiesComparator(parsedVersion, comparator);
|
|
212
|
+
if (result === null) {
|
|
213
|
+
clauseResult = null;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
if (!result) {
|
|
217
|
+
clauseResult = false;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (clauseResult === null) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
if (clauseResult) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { compareSemver, parseSemver, satisfiesRange } from "./semver.mjs";
|
|
3
|
+
describe("parseSemver", () => {
|
|
4
|
+
it("should parse a plain version", () => {
|
|
5
|
+
const result = parseSemver("3.4.21");
|
|
6
|
+
expect(result).toEqual({
|
|
7
|
+
major: 3,
|
|
8
|
+
minor: 4,
|
|
9
|
+
patch: 21,
|
|
10
|
+
prerelease: []
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it("should parse a prerelease version with build metadata", () => {
|
|
14
|
+
const result = parseSemver("1.2.3-beta.1+build.5");
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
major: 1,
|
|
17
|
+
minor: 2,
|
|
18
|
+
patch: 3,
|
|
19
|
+
prerelease: ["beta", "1"]
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it("should return null for non-version input", () => {
|
|
23
|
+
expect(parseSemver("workspace:*")).toBeNull();
|
|
24
|
+
expect(parseSemver("not-a-version")).toBeNull();
|
|
25
|
+
expect(parseSemver("1.2")).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("compareSemver", () => {
|
|
29
|
+
it("should order versions by triple then prerelease", () => {
|
|
30
|
+
const v340 = parseSemver("3.4.0");
|
|
31
|
+
const v352 = parseSemver("3.5.2");
|
|
32
|
+
const v352beta = parseSemver("3.5.2-beta.1");
|
|
33
|
+
if (!v340 || !v352 || !v352beta) {
|
|
34
|
+
throw new Error("fixture versions must parse");
|
|
35
|
+
}
|
|
36
|
+
expect(compareSemver(v340, v352)).toBe(-1);
|
|
37
|
+
expect(compareSemver(v352, v340)).toBe(1);
|
|
38
|
+
expect(compareSemver(v352, v352)).toBe(0);
|
|
39
|
+
expect(compareSemver(v352beta, v352)).toBe(-1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("satisfiesRange", () => {
|
|
43
|
+
it("should handle caret ranges", () => {
|
|
44
|
+
expect(satisfiesRange("3.5.2", "^3.4.0")).toBe(true);
|
|
45
|
+
expect(satisfiesRange("4.0.0", "^3.4.0")).toBe(false);
|
|
46
|
+
expect(satisfiesRange("3.3.9", "^3.4.0")).toBe(false);
|
|
47
|
+
expect(satisfiesRange("0.2.5", "^0.2.3")).toBe(true);
|
|
48
|
+
expect(satisfiesRange("0.3.0", "^0.2.3")).toBe(false);
|
|
49
|
+
expect(satisfiesRange("0.0.3", "^0.0.3")).toBe(true);
|
|
50
|
+
expect(satisfiesRange("0.0.4", "^0.0.3")).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it("should handle tilde ranges", () => {
|
|
53
|
+
expect(satisfiesRange("1.2.9", "~1.2.3")).toBe(true);
|
|
54
|
+
expect(satisfiesRange("1.3.0", "~1.2.3")).toBe(false);
|
|
55
|
+
expect(satisfiesRange("1.5.0", "~1")).toBe(true);
|
|
56
|
+
expect(satisfiesRange("2.0.0", "~1")).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it("should handle comparator ranges", () => {
|
|
59
|
+
expect(satisfiesRange("3.4.0", ">=3.4.0")).toBe(true);
|
|
60
|
+
expect(satisfiesRange("3.3.9", ">=3.4.0")).toBe(false);
|
|
61
|
+
expect(satisfiesRange("3.4.0", ">3.4.0")).toBe(false);
|
|
62
|
+
expect(satisfiesRange("3.4.0", "<=3.4.0")).toBe(true);
|
|
63
|
+
expect(satisfiesRange("3.4.0", "<3.4.0")).toBe(false);
|
|
64
|
+
expect(satisfiesRange("3.4.0", "=3.4.0")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it("should handle exact versions, star and x-ranges", () => {
|
|
67
|
+
expect(satisfiesRange("3.4.21", "3.4.21")).toBe(true);
|
|
68
|
+
expect(satisfiesRange("3.4.22", "3.4.21")).toBe(false);
|
|
69
|
+
expect(satisfiesRange("9.9.9", "*")).toBe(true);
|
|
70
|
+
expect(satisfiesRange("1.7.0", "1.x")).toBe(true);
|
|
71
|
+
expect(satisfiesRange("2.0.0", "1.x")).toBe(false);
|
|
72
|
+
expect(satisfiesRange("1.2.9", "1.2.x")).toBe(true);
|
|
73
|
+
expect(satisfiesRange("1.3.0", "1.2.x")).toBe(false);
|
|
74
|
+
expect(satisfiesRange("1.5.0", "1")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it("should handle compound AND and OR clauses", () => {
|
|
77
|
+
expect(satisfiesRange("1.5.0", ">=1.2.0 <2.0.0")).toBe(true);
|
|
78
|
+
expect(satisfiesRange("2.1.0", ">=1.2.0 <2.0.0")).toBe(false);
|
|
79
|
+
expect(satisfiesRange("2.1.0", "^1.0.0 || ^2.0.0")).toBe(true);
|
|
80
|
+
expect(satisfiesRange("3.0.0", "^1.0.0 || ^2.0.0")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it("should return null for unparsable ranges or versions", () => {
|
|
83
|
+
expect(satisfiesRange("1.0.0", "workspace:*")).toBeNull();
|
|
84
|
+
expect(satisfiesRange("1.0.0", "npm:vue@^3.0.0")).toBeNull();
|
|
85
|
+
expect(satisfiesRange("not-a-version", "^1.0.0")).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Test-only helpers building fake package trees in temp directories. */
|
|
2
|
+
export interface FixturePackage {
|
|
3
|
+
/** Directory relative to the fixture root. */
|
|
4
|
+
dir: string;
|
|
5
|
+
packageJson: Record<string, unknown>;
|
|
6
|
+
/** Write a dist/client/manifest.json stub (marks the module as built). */
|
|
7
|
+
built?: boolean;
|
|
8
|
+
/** Manifest content for the stub; implies `built`. */
|
|
9
|
+
manifest?: Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Skip auto-creating the empty source files for declared
|
|
12
|
+
* `esmx.entry`/`esmx.exports` targets — use to trigger E_TARGET_MISSING.
|
|
13
|
+
*/
|
|
14
|
+
noSources?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function createFixtureRoot(): Promise<string>;
|
|
17
|
+
export declare function writeFixturePackage(rootDir: string, pkg: FixturePackage): string;
|
|
18
|
+
export declare function removeFixtureRoot(rootDir: string): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function declaredTargets(esmx) {
|
|
6
|
+
if (typeof esmx !== "object" || esmx === null) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const out = [];
|
|
10
|
+
const decl = esmx;
|
|
11
|
+
const entry = decl.entry;
|
|
12
|
+
if (typeof entry === "object" && entry !== null) {
|
|
13
|
+
for (const side of Object.values(entry)) {
|
|
14
|
+
if (typeof side === "string") {
|
|
15
|
+
out.push(side);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const exports = decl.exports;
|
|
20
|
+
if (typeof exports === "object" && exports !== null) {
|
|
21
|
+
for (const value of Object.values(exports)) {
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
out.push(value);
|
|
24
|
+
} else if (typeof value === "object" && value !== null) {
|
|
25
|
+
for (const side of Object.values(
|
|
26
|
+
value
|
|
27
|
+
)) {
|
|
28
|
+
if (typeof side === "string") {
|
|
29
|
+
out.push(side);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
export async function createFixtureRoot() {
|
|
38
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "esmx-declaration-"));
|
|
39
|
+
return fs.realpathSync(dir);
|
|
40
|
+
}
|
|
41
|
+
export function writeFixturePackage(rootDir, pkg) {
|
|
42
|
+
const dir = path.join(rootDir, pkg.dir);
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
fs.writeFileSync(
|
|
45
|
+
path.join(dir, "package.json"),
|
|
46
|
+
JSON.stringify(pkg.packageJson, null, 4)
|
|
47
|
+
);
|
|
48
|
+
if (pkg.built || pkg.manifest) {
|
|
49
|
+
const clientDir = path.join(dir, "dist/client");
|
|
50
|
+
fs.mkdirSync(clientDir, { recursive: true });
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(clientDir, "manifest.json"),
|
|
53
|
+
JSON.stringify(pkg.manifest ?? {})
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (!pkg.noSources) {
|
|
57
|
+
for (const target of declaredTargets(pkg.packageJson.esmx)) {
|
|
58
|
+
const file = path.resolve(dir, target);
|
|
59
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
60
|
+
if (!fs.existsSync(file)) {
|
|
61
|
+
fs.writeFileSync(file, "");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
export async function removeFixtureRoot(rootDir) {
|
|
68
|
+
await fsp.rm(rootDir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0001 declaration layer types.
|
|
3
|
+
*
|
|
4
|
+
* The `esmx` field of package.json carries strictly local protocol facts:
|
|
5
|
+
* a module declares only facts about itself (RFC P2).
|
|
6
|
+
*/
|
|
7
|
+
export interface EsmxDeclarationEntry {
|
|
8
|
+
client?: string;
|
|
9
|
+
server?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EsmxDeclarationExportFork {
|
|
12
|
+
client?: string | false;
|
|
13
|
+
server?: string | false;
|
|
14
|
+
}
|
|
15
|
+
export type EsmxDeclarationExportValue = string | EsmxDeclarationExportFork;
|
|
16
|
+
export interface EsmxDeclaration {
|
|
17
|
+
entry?: EsmxDeclarationEntry;
|
|
18
|
+
exports?: Record<string, EsmxDeclarationExportValue>;
|
|
19
|
+
provides?: string[];
|
|
20
|
+
uses?: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Complete diagnostic taxonomy from RFC 0001 §7, plus E_SCHEMA for
|
|
24
|
+
* declaration shape violations (the RFC ships a JSON Schema but assigns
|
|
25
|
+
* no code to schema failures; one code is needed so agents can detect them).
|
|
26
|
+
*/
|
|
27
|
+
export declare const DiagnosticCode: {
|
|
28
|
+
readonly E_NOT_LINKED: "E_NOT_LINKED";
|
|
29
|
+
readonly E_NOT_BUILT: "E_NOT_BUILT";
|
|
30
|
+
readonly E_CYCLE: "E_CYCLE";
|
|
31
|
+
readonly E_VERSION: "E_VERSION";
|
|
32
|
+
readonly E_DUP_PROVIDER: "E_DUP_PROVIDER";
|
|
33
|
+
/** A declared entry/exports target file does not exist on disk. */
|
|
34
|
+
readonly E_TARGET_MISSING: "E_TARGET_MISSING";
|
|
35
|
+
readonly E_NOT_USED: "E_NOT_USED";
|
|
36
|
+
readonly E_NO_EXPORT: "E_NO_EXPORT";
|
|
37
|
+
readonly E_PROTOCOL: "E_PROTOCOL";
|
|
38
|
+
readonly E_PROTOCOL_IN_BEHAVIOR: "E_PROTOCOL_IN_BEHAVIOR";
|
|
39
|
+
readonly E_SCHEMA: "E_SCHEMA";
|
|
40
|
+
readonly W_MULTI_MAJOR: "W_MULTI_MAJOR";
|
|
41
|
+
readonly W_NO_RANGE: "W_NO_RANGE";
|
|
42
|
+
readonly W_TYPE_DRIFT: "W_TYPE_DRIFT";
|
|
43
|
+
};
|
|
44
|
+
export type DiagnosticCodeValue = (typeof DiagnosticCode)[keyof typeof DiagnosticCode];
|
|
45
|
+
export type DiagnosticSeverity = 'error' | 'warning';
|
|
46
|
+
export type DiagnosticCheck = 'intent';
|
|
47
|
+
/** Structured diagnostic matching the RFC `esmx validate --json` envelope. */
|
|
48
|
+
export interface Diagnostic {
|
|
49
|
+
code: string;
|
|
50
|
+
severity: DiagnosticSeverity;
|
|
51
|
+
module: string;
|
|
52
|
+
package?: string;
|
|
53
|
+
check?: DiagnosticCheck;
|
|
54
|
+
found?: string;
|
|
55
|
+
required?: string;
|
|
56
|
+
message: string;
|
|
57
|
+
fix: string;
|
|
58
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const DiagnosticCode = {
|
|
2
|
+
E_NOT_LINKED: "E_NOT_LINKED",
|
|
3
|
+
E_NOT_BUILT: "E_NOT_BUILT",
|
|
4
|
+
E_CYCLE: "E_CYCLE",
|
|
5
|
+
E_VERSION: "E_VERSION",
|
|
6
|
+
E_DUP_PROVIDER: "E_DUP_PROVIDER",
|
|
7
|
+
/** A declared entry/exports target file does not exist on disk. */
|
|
8
|
+
E_TARGET_MISSING: "E_TARGET_MISSING",
|
|
9
|
+
// Build-time (phase 3) codes: emitted by the bundler during its
|
|
10
|
+
// per-specifier traversal, NOT by the build-free `esmx validate` (which
|
|
11
|
+
// never lexes source). Reserved here so the bundler emits them with the
|
|
12
|
+
// same envelope; do not wire emit paths into the resolver.
|
|
13
|
+
E_NOT_USED: "E_NOT_USED",
|
|
14
|
+
E_NO_EXPORT: "E_NO_EXPORT",
|
|
15
|
+
E_PROTOCOL: "E_PROTOCOL",
|
|
16
|
+
E_PROTOCOL_IN_BEHAVIOR: "E_PROTOCOL_IN_BEHAVIOR",
|
|
17
|
+
E_SCHEMA: "E_SCHEMA",
|
|
18
|
+
W_MULTI_MAJOR: "W_MULTI_MAJOR",
|
|
19
|
+
W_NO_RANGE: "W_NO_RANGE",
|
|
20
|
+
W_TYPE_DRIFT: "W_TYPE_DRIFT"
|
|
21
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { type App, createApp } from './app';
|
|
2
2
|
export { type BuildEnvironment, type COMMAND, Esmx, type EsmxOptions, type ImportMap, type ScopesMap, type SpecifierMap } from './core';
|
|
3
|
-
export type {
|
|
3
|
+
export type { Diagnostic, DiagnosticCheck, DiagnosticCodeValue, DiagnosticSeverity, EsmxDeclaration, EsmxDeclarationEntry, EsmxDeclarationExportFork, EsmxDeclarationExportValue, ReadDeclarationResult, ResolveDeclarationOptions, ResolveDeclarationResult, SupplyEntry, SupplyGroup } from './declaration';
|
|
4
|
+
export { DiagnosticCode, esmxDeclarationSchema, readDeclaration, resolveDeclaration } from './declaration';
|
|
5
|
+
export type { ManifestJson, ManifestJsonChunk, ManifestJsonChunks, ManifestJsonExport, ManifestJsonExports, ManifestJsonProvide, ManifestProtocolFields } from './manifest-json';
|
|
6
|
+
export { buildManifestProtocolFields, MANIFEST_PROTOCOL_VERSION } from './manifest-json';
|
|
4
7
|
export type { ModuleConfig, ModuleConfigExportExport, ModuleConfigExportExports, ModuleConfigExportObject, ModuleConfigImportMapping, ParsedModuleConfig, ParsedModuleConfigEnvironment, ParsedModuleConfigExport, ParsedModuleConfigExports, ParsedModuleConfigLink } from './module-config';
|
|
5
8
|
export type { PackConfig, ParsedPackConfig } from './pack-config';
|
|
6
9
|
export { RenderContext, type RenderContextOptions, type RenderFiles, type ServerRenderHandle } from './render-context';
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,16 @@ export { createApp } from "./app.mjs";
|
|
|
2
2
|
export {
|
|
3
3
|
Esmx
|
|
4
4
|
} from "./core.mjs";
|
|
5
|
+
export {
|
|
6
|
+
DiagnosticCode,
|
|
7
|
+
esmxDeclarationSchema,
|
|
8
|
+
readDeclaration,
|
|
9
|
+
resolveDeclaration
|
|
10
|
+
} from "./declaration/index.mjs";
|
|
11
|
+
export {
|
|
12
|
+
buildManifestProtocolFields,
|
|
13
|
+
MANIFEST_PROTOCOL_VERSION
|
|
14
|
+
} from "./manifest-json.mjs";
|
|
5
15
|
export {
|
|
6
16
|
RenderContext
|
|
7
17
|
} from "./render-context.mjs";
|
package/dist/manifest-json.d.ts
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import type { BuildEnvironment } from './core';
|
|
2
2
|
import type { ParsedModuleConfig } from './module-config';
|
|
3
|
+
/**
|
|
4
|
+
* Manifest protocol version emitted by the bundler plugins (RFC 0001 §5).
|
|
5
|
+
* The linker rejects manifests whose `protocol` is HIGHER than this value.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MANIFEST_PROTOCOL_VERSION = 2;
|
|
3
8
|
export interface ManifestJson {
|
|
9
|
+
/**
|
|
10
|
+
* Manifest protocol version (RFC 0001 §5). Absent in pre-v2 manifests;
|
|
11
|
+
* readers treat absence as protocol 1 and skip v2-only checks.
|
|
12
|
+
*/
|
|
13
|
+
protocol: number;
|
|
4
14
|
/**
|
|
5
15
|
* Module name
|
|
6
16
|
*/
|
|
7
17
|
name: string;
|
|
18
|
+
/**
|
|
19
|
+
* Module version, transcribed from the module's package.json at build
|
|
20
|
+
* time (RFC 0001 §5). Pre-v2 manifests default to '0.0.0' at read time.
|
|
21
|
+
*/
|
|
22
|
+
version: string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolved versions for every pkg-export (`pkg: true`) package, captured
|
|
25
|
+
* at build time (RFC 0001 §5).
|
|
26
|
+
* Type: Record<package specifier, provide record>
|
|
27
|
+
*/
|
|
28
|
+
provides: Record<string, ManifestJsonProvide>;
|
|
29
|
+
/**
|
|
30
|
+
* Consumption edges transcribed from the module's package.json `esmx.uses`
|
|
31
|
+
* declaration (RFC 0001 §5). Pre-v2 manifests default to [] at read time.
|
|
32
|
+
*/
|
|
33
|
+
uses: string[];
|
|
8
34
|
/**
|
|
9
35
|
* Scope-specific import mappings
|
|
10
36
|
* Type: Record<scope name, import mappings within that scope>
|
|
@@ -24,6 +50,24 @@ export interface ManifestJson {
|
|
|
24
50
|
* Type: Record<source file, compilation information>
|
|
25
51
|
*/
|
|
26
52
|
chunks: ManifestJsonChunks;
|
|
53
|
+
/**
|
|
54
|
+
* Subresource Integrity (SRI) hashes for build output files.
|
|
55
|
+
* Only generated in production builds to avoid development overhead.
|
|
56
|
+
* Type: Record<file path, integrity hash>
|
|
57
|
+
*/
|
|
58
|
+
integrity?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build-time facts about one provided (pkg-export) package (RFC 0001 §5).
|
|
62
|
+
*/
|
|
63
|
+
export interface ManifestJsonProvide {
|
|
64
|
+
/**
|
|
65
|
+
* The RESOLVED installed version of the provided package at build time,
|
|
66
|
+
* read from node_modules/<pkg>/package.json relative to the module root.
|
|
67
|
+
* Subpath specifiers (e.g. `vue/jsx-runtime`) resolve via their parent
|
|
68
|
+
* package. '0.0.0' when the package could not be resolved.
|
|
69
|
+
*/
|
|
70
|
+
version: string;
|
|
27
71
|
}
|
|
28
72
|
/**
|
|
29
73
|
* Export item configuration mapping
|
|
@@ -69,6 +113,23 @@ export interface ManifestJsonChunk {
|
|
|
69
113
|
*/
|
|
70
114
|
resources: string[];
|
|
71
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* The RFC 0001 §5 protocol fields shared by every bundler manifest plugin.
|
|
118
|
+
*/
|
|
119
|
+
export interface ManifestProtocolFields {
|
|
120
|
+
protocol: number;
|
|
121
|
+
version: string;
|
|
122
|
+
provides: Record<string, ManifestJsonProvide>;
|
|
123
|
+
uses: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Builds the RFC 0001 §5 manifest protocol fields (`protocol`, `version`,
|
|
127
|
+
* `provides`, `uses`) from the module root's package.json plus the list of
|
|
128
|
+
* pkg-export package specifiers the bundler plugin computed. Shared by the
|
|
129
|
+
* @esmx/rspack, @esmx/rsbuild and @esmx/vite manifest plugins so their output
|
|
130
|
+
* cannot drift.
|
|
131
|
+
*/
|
|
132
|
+
export declare function buildManifestProtocolFields(moduleRoot: string, provides: string[]): ManifestProtocolFields;
|
|
72
133
|
/**
|
|
73
134
|
* Get service manifest files
|
|
74
135
|
*/
|
package/dist/manifest-json.mjs
CHANGED
|
@@ -1,20 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import fsp from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
export const MANIFEST_PROTOCOL_VERSION = 2;
|
|
5
|
+
function readJsonObjectSync(filePath) {
|
|
6
|
+
let json;
|
|
7
|
+
try {
|
|
8
|
+
json = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return json;
|
|
16
|
+
}
|
|
17
|
+
function parentPackageName(specifier) {
|
|
18
|
+
const segments = specifier.split("/");
|
|
19
|
+
return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
20
|
+
}
|
|
21
|
+
function resolveInstalledVersionSync(fromDir, packageName) {
|
|
22
|
+
let dir = path.resolve(fromDir);
|
|
23
|
+
for (; ; ) {
|
|
24
|
+
const candidate = path.join(dir, "node_modules", packageName);
|
|
25
|
+
const pkg = readJsonObjectSync(path.join(candidate, "package.json"));
|
|
26
|
+
if (pkg) {
|
|
27
|
+
return typeof pkg.version === "string" && pkg.version !== "" ? pkg.version : null;
|
|
28
|
+
}
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
dir = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function buildManifestProtocolFields(moduleRoot, provides) {
|
|
37
|
+
const pkg = readJsonObjectSync(path.resolve(moduleRoot, "package.json"));
|
|
38
|
+
const version = pkg && typeof pkg.version === "string" && pkg.version !== "" ? pkg.version : "0.0.0";
|
|
39
|
+
const esmx = pkg && typeof pkg.esmx === "object" && pkg.esmx !== null ? pkg.esmx : null;
|
|
40
|
+
const uses = Array.isArray(esmx?.uses) ? esmx.uses.filter((item) => typeof item === "string") : [];
|
|
41
|
+
const providesField = {};
|
|
42
|
+
for (const name of provides) {
|
|
43
|
+
const resolvedVersion = resolveInstalledVersionSync(
|
|
44
|
+
moduleRoot,
|
|
45
|
+
parentPackageName(name)
|
|
46
|
+
);
|
|
47
|
+
providesField[name] = {
|
|
48
|
+
version: resolvedVersion ?? "0.0.0"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
protocol: MANIFEST_PROTOCOL_VERSION,
|
|
53
|
+
version,
|
|
54
|
+
provides: providesField,
|
|
55
|
+
uses
|
|
56
|
+
};
|
|
57
|
+
}
|
|
3
58
|
export async function getManifestList(env, moduleConfig) {
|
|
4
59
|
return Promise.all(
|
|
5
60
|
Object.values(moduleConfig.links).map(async (item) => {
|
|
6
61
|
const filename = path.resolve(item[env], "manifest.json");
|
|
62
|
+
let data;
|
|
7
63
|
try {
|
|
8
|
-
|
|
9
|
-
await fsp.readFile(filename, "utf-8")
|
|
10
|
-
);
|
|
11
|
-
data.name = item.name;
|
|
12
|
-
return data;
|
|
64
|
+
data = await JSON.parse(await fsp.readFile(filename, "utf-8"));
|
|
13
65
|
} catch (e) {
|
|
14
66
|
throw new Error(
|
|
15
67
|
`'${item.name}' service '${filename}' file read error on environment '${env}': ${e instanceof Error ? e.message : String(e)}`
|
|
16
68
|
);
|
|
17
69
|
}
|
|
70
|
+
data.name = item.name;
|
|
71
|
+
data.protocol = typeof data.protocol === "number" ? data.protocol : 1;
|
|
72
|
+
if (data.protocol > MANIFEST_PROTOCOL_VERSION) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[E_PROTOCOL] '${item.name}' service '${filename}' declares manifest protocol ${data.protocol}, but this linker supports up to ${MANIFEST_PROTOCOL_VERSION}. Upgrade esmx, or rebuild '${item.name}' with a matching toolchain.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
data.version = typeof data.version === "string" ? data.version : "0.0.0";
|
|
78
|
+
data.provides = typeof data.provides === "object" && data.provides !== null ? data.provides : {};
|
|
79
|
+
data.uses = Array.isArray(data.uses) ? data.uses : [];
|
|
80
|
+
return data;
|
|
18
81
|
})
|
|
19
82
|
);
|
|
20
83
|
}
|