@esmx/core 3.0.0-rc.117 → 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/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,236 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import type { BuildEnvironment } from './core';
|
|
6
|
+
import {
|
|
7
|
+
createFixtureRoot,
|
|
8
|
+
removeFixtureRoot,
|
|
9
|
+
writeFixturePackage
|
|
10
|
+
} from './declaration/test-fixtures';
|
|
11
|
+
import type { ManifestJson } from './manifest-json';
|
|
12
|
+
import {
|
|
13
|
+
buildManifestProtocolFields,
|
|
14
|
+
getManifestList,
|
|
15
|
+
MANIFEST_PROTOCOL_VERSION
|
|
16
|
+
} from './manifest-json';
|
|
17
|
+
import type { ParsedModuleConfig } from './module-config';
|
|
18
|
+
|
|
19
|
+
const fixtureRoots: string[] = [];
|
|
20
|
+
|
|
21
|
+
async function fixtureRoot(): Promise<string> {
|
|
22
|
+
const root = await createFixtureRoot();
|
|
23
|
+
fixtureRoots.push(root);
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(async () => {
|
|
28
|
+
await Promise.all(fixtureRoots.splice(0).map(removeFixtureRoot));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('buildManifestProtocolFields', () => {
|
|
32
|
+
it('should transcribe protocol, version and uses from the module package.json', async () => {
|
|
33
|
+
const root = await fixtureRoot();
|
|
34
|
+
const moduleRoot = writeFixturePackage(root, {
|
|
35
|
+
dir: 'shared',
|
|
36
|
+
packageJson: {
|
|
37
|
+
name: 'shared',
|
|
38
|
+
version: '1.8.0',
|
|
39
|
+
esmx: { provides: ['vue'], uses: ['base', 'theme'] }
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const fields = buildManifestProtocolFields(moduleRoot, []);
|
|
44
|
+
|
|
45
|
+
expect(fields.protocol).toBe(MANIFEST_PROTOCOL_VERSION);
|
|
46
|
+
expect(fields.protocol).toBe(2);
|
|
47
|
+
expect(fields.version).toBe('1.8.0');
|
|
48
|
+
expect(fields.uses).toEqual(['base', 'theme']);
|
|
49
|
+
expect(fields.provides).toEqual({});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should resolve provided package versions from node_modules of the module root', async () => {
|
|
53
|
+
const root = await fixtureRoot();
|
|
54
|
+
const moduleRoot = writeFixturePackage(root, {
|
|
55
|
+
dir: 'shared',
|
|
56
|
+
packageJson: { name: 'shared', version: '1.0.0' }
|
|
57
|
+
});
|
|
58
|
+
writeFixturePackage(root, {
|
|
59
|
+
dir: 'shared/node_modules/vue',
|
|
60
|
+
packageJson: { name: 'vue', version: '3.4.21' }
|
|
61
|
+
});
|
|
62
|
+
writeFixturePackage(root, {
|
|
63
|
+
dir: 'shared/node_modules/@esmx/router',
|
|
64
|
+
packageJson: { name: '@esmx/router', version: '0.6.1' }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const fields = buildManifestProtocolFields(moduleRoot, [
|
|
68
|
+
'vue',
|
|
69
|
+
'@esmx/router'
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
expect(fields.provides).toEqual({
|
|
73
|
+
vue: { version: '3.4.21' },
|
|
74
|
+
'@esmx/router': { version: '0.6.1' }
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should resolve subpath specifiers via their parent package', async () => {
|
|
79
|
+
const root = await fixtureRoot();
|
|
80
|
+
const moduleRoot = writeFixturePackage(root, {
|
|
81
|
+
dir: 'shared',
|
|
82
|
+
packageJson: { name: 'shared', version: '1.0.0' }
|
|
83
|
+
});
|
|
84
|
+
writeFixturePackage(root, {
|
|
85
|
+
dir: 'shared/node_modules/vue',
|
|
86
|
+
packageJson: { name: 'vue', version: '3.4.21' }
|
|
87
|
+
});
|
|
88
|
+
writeFixturePackage(root, {
|
|
89
|
+
dir: 'shared/node_modules/@scope/pkg',
|
|
90
|
+
packageJson: { name: '@scope/pkg', version: '2.1.0' }
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const fields = buildManifestProtocolFields(moduleRoot, [
|
|
94
|
+
'vue/jsx-runtime',
|
|
95
|
+
'@scope/pkg/client'
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
expect(fields.provides['vue/jsx-runtime'].version).toBe('3.4.21');
|
|
99
|
+
expect(fields.provides['@scope/pkg/client'].version).toBe('2.1.0');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should walk up node_modules for hoisted installs', async () => {
|
|
103
|
+
const root = await fixtureRoot();
|
|
104
|
+
const moduleRoot = writeFixturePackage(root, {
|
|
105
|
+
dir: 'packages/shared',
|
|
106
|
+
packageJson: { name: 'shared', version: '1.0.0' }
|
|
107
|
+
});
|
|
108
|
+
writeFixturePackage(root, {
|
|
109
|
+
dir: 'node_modules/vue',
|
|
110
|
+
packageJson: { name: 'vue', version: '3.5.2' }
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const fields = buildManifestProtocolFields(moduleRoot, ['vue']);
|
|
114
|
+
|
|
115
|
+
expect(fields.provides.vue.version).toBe('3.5.2');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should fall back to 0.0.0 for unresolvable packages', async () => {
|
|
119
|
+
const root = await fixtureRoot();
|
|
120
|
+
const moduleRoot = writeFixturePackage(root, {
|
|
121
|
+
dir: 'shared',
|
|
122
|
+
packageJson: { name: 'shared', version: '1.0.0' }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const fields = buildManifestProtocolFields(moduleRoot, ['ghost']);
|
|
126
|
+
|
|
127
|
+
expect(fields.provides.ghost).toEqual({ version: '0.0.0' });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should default version and uses when package.json is missing or has no esmx field', async () => {
|
|
131
|
+
const root = await fixtureRoot();
|
|
132
|
+
|
|
133
|
+
const fields = buildManifestProtocolFields(
|
|
134
|
+
path.join(root, 'does-not-exist'),
|
|
135
|
+
[]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(fields).toEqual({
|
|
139
|
+
protocol: 2,
|
|
140
|
+
version: '0.0.0',
|
|
141
|
+
provides: {},
|
|
142
|
+
uses: []
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function writeManifest(
|
|
148
|
+
root: string,
|
|
149
|
+
dir: string,
|
|
150
|
+
manifest: Record<string, unknown>
|
|
151
|
+
): string {
|
|
152
|
+
const artifactDir = path.join(root, dir);
|
|
153
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(artifactDir, 'manifest.json'),
|
|
156
|
+
JSON.stringify(manifest)
|
|
157
|
+
);
|
|
158
|
+
return artifactDir;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function makeModuleConfig(name: string, dir: string): ParsedModuleConfig {
|
|
162
|
+
return {
|
|
163
|
+
links: {
|
|
164
|
+
[name]: { name, client: dir, server: dir }
|
|
165
|
+
}
|
|
166
|
+
} as unknown as ParsedModuleConfig;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const ENV: BuildEnvironment = 'client';
|
|
170
|
+
|
|
171
|
+
describe('getManifestList — protocol fields at read time', () => {
|
|
172
|
+
it('should default pre-v2 manifests to protocol 1 with empty v2 fields', async () => {
|
|
173
|
+
const root = await fixtureRoot();
|
|
174
|
+
const dir = writeManifest(root, 'legacy/dist/client', {
|
|
175
|
+
name: 'legacy',
|
|
176
|
+
exports: {},
|
|
177
|
+
scopes: {},
|
|
178
|
+
files: [],
|
|
179
|
+
chunks: {}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const [manifest] = await getManifestList(
|
|
183
|
+
ENV,
|
|
184
|
+
makeModuleConfig('legacy', dir)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(manifest.protocol).toBe(1);
|
|
188
|
+
expect(manifest.version).toBe('0.0.0');
|
|
189
|
+
expect(manifest.provides).toEqual({});
|
|
190
|
+
expect(manifest.uses).toEqual([]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should keep v2 fields as emitted', async () => {
|
|
194
|
+
const root = await fixtureRoot();
|
|
195
|
+
const emitted: Partial<ManifestJson> = {
|
|
196
|
+
protocol: 2,
|
|
197
|
+
name: 'shared',
|
|
198
|
+
version: '1.8.0',
|
|
199
|
+
provides: {
|
|
200
|
+
vue: { version: '3.4.21' }
|
|
201
|
+
},
|
|
202
|
+
uses: ['base'],
|
|
203
|
+
exports: {},
|
|
204
|
+
scopes: {},
|
|
205
|
+
files: [],
|
|
206
|
+
chunks: {}
|
|
207
|
+
};
|
|
208
|
+
const dir = writeManifest(root, 'shared/dist/client', emitted);
|
|
209
|
+
|
|
210
|
+
const [manifest] = await getManifestList(
|
|
211
|
+
ENV,
|
|
212
|
+
makeModuleConfig('shared', dir)
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(manifest.protocol).toBe(2);
|
|
216
|
+
expect(manifest.version).toBe('1.8.0');
|
|
217
|
+
expect(manifest.provides).toEqual(emitted.provides);
|
|
218
|
+
expect(manifest.uses).toEqual(['base']);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should reject manifests with a protocol higher than the linker supports', async () => {
|
|
222
|
+
const root = await fixtureRoot();
|
|
223
|
+
const dir = writeManifest(root, 'future/dist/client', {
|
|
224
|
+
protocol: MANIFEST_PROTOCOL_VERSION + 1,
|
|
225
|
+
name: 'future',
|
|
226
|
+
exports: {},
|
|
227
|
+
scopes: {},
|
|
228
|
+
files: [],
|
|
229
|
+
chunks: {}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await expect(
|
|
233
|
+
getManifestList(ENV, makeModuleConfig('future', dir))
|
|
234
|
+
).rejects.toThrow(/E_PROTOCOL/);
|
|
235
|
+
});
|
|
236
|
+
});
|
package/src/manifest-json.ts
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import fsp from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import type { BuildEnvironment } from './core';
|
|
5
6
|
import type { ParsedModuleConfig } from './module-config';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Manifest protocol version emitted by the bundler plugins (RFC 0001 §5).
|
|
10
|
+
* The linker rejects manifests whose `protocol` is HIGHER than this value.
|
|
11
|
+
*/
|
|
12
|
+
export const MANIFEST_PROTOCOL_VERSION = 2;
|
|
13
|
+
|
|
7
14
|
export interface ManifestJson {
|
|
15
|
+
/**
|
|
16
|
+
* Manifest protocol version (RFC 0001 §5). Absent in pre-v2 manifests;
|
|
17
|
+
* readers treat absence as protocol 1 and skip v2-only checks.
|
|
18
|
+
*/
|
|
19
|
+
protocol: number;
|
|
8
20
|
/**
|
|
9
21
|
* Module name
|
|
10
22
|
*/
|
|
11
23
|
name: string;
|
|
24
|
+
/**
|
|
25
|
+
* Module version, transcribed from the module's package.json at build
|
|
26
|
+
* time (RFC 0001 §5). Pre-v2 manifests default to '0.0.0' at read time.
|
|
27
|
+
*/
|
|
28
|
+
version: string;
|
|
29
|
+
/**
|
|
30
|
+
* Resolved versions for every pkg-export (`pkg: true`) package, captured
|
|
31
|
+
* at build time (RFC 0001 §5).
|
|
32
|
+
* Type: Record<package specifier, provide record>
|
|
33
|
+
*/
|
|
34
|
+
provides: Record<string, ManifestJsonProvide>;
|
|
35
|
+
/**
|
|
36
|
+
* Consumption edges transcribed from the module's package.json `esmx.uses`
|
|
37
|
+
* declaration (RFC 0001 §5). Pre-v2 manifests default to [] at read time.
|
|
38
|
+
*/
|
|
39
|
+
uses: string[];
|
|
12
40
|
/**
|
|
13
41
|
* Scope-specific import mappings
|
|
14
42
|
* Type: Record<scope name, import mappings within that scope>
|
|
@@ -28,6 +56,25 @@ export interface ManifestJson {
|
|
|
28
56
|
* Type: Record<source file, compilation information>
|
|
29
57
|
*/
|
|
30
58
|
chunks: ManifestJsonChunks;
|
|
59
|
+
/**
|
|
60
|
+
* Subresource Integrity (SRI) hashes for build output files.
|
|
61
|
+
* Only generated in production builds to avoid development overhead.
|
|
62
|
+
* Type: Record<file path, integrity hash>
|
|
63
|
+
*/
|
|
64
|
+
integrity?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build-time facts about one provided (pkg-export) package (RFC 0001 §5).
|
|
69
|
+
*/
|
|
70
|
+
export interface ManifestJsonProvide {
|
|
71
|
+
/**
|
|
72
|
+
* The RESOLVED installed version of the provided package at build time,
|
|
73
|
+
* read from node_modules/<pkg>/package.json relative to the module root.
|
|
74
|
+
* Subpath specifiers (e.g. `vue/jsx-runtime`) resolve via their parent
|
|
75
|
+
* package. '0.0.0' when the package could not be resolved.
|
|
76
|
+
*/
|
|
77
|
+
version: string;
|
|
31
78
|
}
|
|
32
79
|
|
|
33
80
|
/**
|
|
@@ -78,6 +125,105 @@ export interface ManifestJsonChunk {
|
|
|
78
125
|
resources: string[];
|
|
79
126
|
}
|
|
80
127
|
|
|
128
|
+
/**
|
|
129
|
+
* The RFC 0001 §5 protocol fields shared by every bundler manifest plugin.
|
|
130
|
+
*/
|
|
131
|
+
export interface ManifestProtocolFields {
|
|
132
|
+
protocol: number;
|
|
133
|
+
version: string;
|
|
134
|
+
provides: Record<string, ManifestJsonProvide>;
|
|
135
|
+
uses: string[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readJsonObjectSync(filePath: string): Record<string, unknown> | null {
|
|
139
|
+
// Boundary adapter: absence or malformed JSON means "no readable
|
|
140
|
+
// package.json"; callers fall back to protocol defaults.
|
|
141
|
+
let json: unknown;
|
|
142
|
+
try {
|
|
143
|
+
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return json as Record<string, unknown>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parent package name of a bare specifier: `vue/jsx-runtime` → `vue`,
|
|
155
|
+
* `@esmx/router` → `@esmx/router`.
|
|
156
|
+
*/
|
|
157
|
+
function parentPackageName(specifier: string): string {
|
|
158
|
+
const segments = specifier.split('/');
|
|
159
|
+
return specifier.startsWith('@')
|
|
160
|
+
? segments.slice(0, 2).join('/')
|
|
161
|
+
: segments[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Walks up from `fromDir` through node_modules, Node-resolution style. */
|
|
165
|
+
function resolveInstalledVersionSync(
|
|
166
|
+
fromDir: string,
|
|
167
|
+
packageName: string
|
|
168
|
+
): string | null {
|
|
169
|
+
let dir = path.resolve(fromDir);
|
|
170
|
+
for (;;) {
|
|
171
|
+
const candidate = path.join(dir, 'node_modules', packageName);
|
|
172
|
+
const pkg = readJsonObjectSync(path.join(candidate, 'package.json'));
|
|
173
|
+
if (pkg) {
|
|
174
|
+
return typeof pkg.version === 'string' && pkg.version !== ''
|
|
175
|
+
? pkg.version
|
|
176
|
+
: null;
|
|
177
|
+
}
|
|
178
|
+
const parent = path.dirname(dir);
|
|
179
|
+
if (parent === dir) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
dir = parent;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Builds the RFC 0001 §5 manifest protocol fields (`protocol`, `version`,
|
|
188
|
+
* `provides`, `uses`) from the module root's package.json plus the list of
|
|
189
|
+
* pkg-export package specifiers the bundler plugin computed. Shared by the
|
|
190
|
+
* @esmx/rspack, @esmx/rsbuild and @esmx/vite manifest plugins so their output
|
|
191
|
+
* cannot drift.
|
|
192
|
+
*/
|
|
193
|
+
export function buildManifestProtocolFields(
|
|
194
|
+
moduleRoot: string,
|
|
195
|
+
provides: string[]
|
|
196
|
+
): ManifestProtocolFields {
|
|
197
|
+
const pkg = readJsonObjectSync(path.resolve(moduleRoot, 'package.json'));
|
|
198
|
+
const version =
|
|
199
|
+
pkg && typeof pkg.version === 'string' && pkg.version !== ''
|
|
200
|
+
? pkg.version
|
|
201
|
+
: '0.0.0';
|
|
202
|
+
const esmx =
|
|
203
|
+
pkg && typeof pkg.esmx === 'object' && pkg.esmx !== null
|
|
204
|
+
? (pkg.esmx as Record<string, unknown>)
|
|
205
|
+
: null;
|
|
206
|
+
const uses = Array.isArray(esmx?.uses)
|
|
207
|
+
? esmx.uses.filter((item): item is string => typeof item === 'string')
|
|
208
|
+
: [];
|
|
209
|
+
const providesField: Record<string, ManifestJsonProvide> = {};
|
|
210
|
+
for (const name of provides) {
|
|
211
|
+
const resolvedVersion = resolveInstalledVersionSync(
|
|
212
|
+
moduleRoot,
|
|
213
|
+
parentPackageName(name)
|
|
214
|
+
);
|
|
215
|
+
providesField[name] = {
|
|
216
|
+
version: resolvedVersion ?? '0.0.0'
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
protocol: MANIFEST_PROTOCOL_VERSION,
|
|
221
|
+
version,
|
|
222
|
+
provides: providesField,
|
|
223
|
+
uses
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
81
227
|
/**
|
|
82
228
|
* Get service manifest files
|
|
83
229
|
*/
|
|
@@ -88,17 +234,32 @@ export async function getManifestList(
|
|
|
88
234
|
return Promise.all(
|
|
89
235
|
Object.values(moduleConfig.links).map(async (item) => {
|
|
90
236
|
const filename = path.resolve(item[env], 'manifest.json');
|
|
237
|
+
let data: ManifestJson;
|
|
91
238
|
try {
|
|
92
|
-
|
|
93
|
-
await fsp.readFile(filename, 'utf-8')
|
|
94
|
-
);
|
|
95
|
-
data.name = item.name;
|
|
96
|
-
return data;
|
|
239
|
+
data = await JSON.parse(await fsp.readFile(filename, 'utf-8'));
|
|
97
240
|
} catch (e) {
|
|
98
241
|
throw new Error(
|
|
99
242
|
`'${item.name}' service '${filename}' file read error on environment '${env}': ${e instanceof Error ? e.message : String(e)}`
|
|
100
243
|
);
|
|
101
244
|
}
|
|
245
|
+
data.name = item.name;
|
|
246
|
+
// Pre-v2 manifests carry none of the protocol fields: treat them
|
|
247
|
+
// as protocol 1 and default the v2 fields so v2-only checks skip.
|
|
248
|
+
data.protocol =
|
|
249
|
+
typeof data.protocol === 'number' ? data.protocol : 1;
|
|
250
|
+
if (data.protocol > MANIFEST_PROTOCOL_VERSION) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`[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.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
data.version =
|
|
256
|
+
typeof data.version === 'string' ? data.version : '0.0.0';
|
|
257
|
+
data.provides =
|
|
258
|
+
typeof data.provides === 'object' && data.provides !== null
|
|
259
|
+
? data.provides
|
|
260
|
+
: {};
|
|
261
|
+
data.uses = Array.isArray(data.uses) ? data.uses : [];
|
|
262
|
+
return data;
|
|
102
263
|
})
|
|
103
264
|
);
|
|
104
265
|
}
|