@gjsify/resolve-npm 0.4.31 → 0.4.33
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/lib/index.d.ts +32 -0
- package/lib/index.mjs +38 -7
- package/lib/runtime-aliases.mjs +419 -0
- package/package.json +2 -1
package/lib/index.d.ts
CHANGED
|
@@ -31,3 +31,35 @@ export declare const ALIASES_GJS_FOR_NODE: {[alias:string]: string};
|
|
|
31
31
|
/** Record of Web modules and his replacement for Node */
|
|
32
32
|
export declare const ALIASES_WEB_FOR_NODE: {[alias:string]: string};
|
|
33
33
|
|
|
34
|
+
/** Runtime-slot type carried by `package.json#gjsify.runtimes.<target>`. */
|
|
35
|
+
export type RuntimeSlot = 'polyfill' | 'native' | 'partial' | 'none';
|
|
36
|
+
|
|
37
|
+
/** Per-package runtime triplet — `{gjs, node, browser}` × {RuntimeSlot}. */
|
|
38
|
+
export interface RuntimeTriplet {
|
|
39
|
+
gjs?: RuntimeSlot;
|
|
40
|
+
node?: RuntimeSlot;
|
|
41
|
+
browser?: RuntimeSlot;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build a derived `@gjsify/<X>` alias map for the given target runtime, driven
|
|
46
|
+
* by each workspace package's declared `gjsify.runtimes` triplet. See
|
|
47
|
+
* `runtime-aliases.mjs` for the routing semantics.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getDerivedAliasesSync(target: 'gjs' | 'node' | 'browser'): {[alias: string]: string};
|
|
50
|
+
|
|
51
|
+
/** Async variant of {@link getDerivedAliasesSync}. */
|
|
52
|
+
export declare function getDerivedAliases(
|
|
53
|
+
target: 'gjs' | 'node' | 'browser',
|
|
54
|
+
): Promise<{[alias: string]: string}>;
|
|
55
|
+
|
|
56
|
+
/** Reset the in-memory cache. Test-only. */
|
|
57
|
+
export declare function resetRuntimeAliasesCache(): void;
|
|
58
|
+
|
|
59
|
+
/** Diagnostic — list every declared runtime triplet keyed by package name. */
|
|
60
|
+
export declare function listDeclaredRuntimes(): Promise<Map<string, {
|
|
61
|
+
name: string;
|
|
62
|
+
dir: string;
|
|
63
|
+
runtimes: RuntimeTriplet;
|
|
64
|
+
hasGlobals: boolean;
|
|
65
|
+
}>>;
|
package/lib/index.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Runtime-aware alias derivation — driven by per-package `gjsify.runtimes`
|
|
2
|
+
// triplet declarations. See `./runtime-aliases.mjs` for the routing rules.
|
|
3
|
+
// These helpers AUGMENT the hardcoded `ALIASES_*` maps below: a package
|
|
4
|
+
// without a declared triplet falls through to the hardcoded map for that
|
|
5
|
+
// specifier, preserving backwards-compatible behavior for the 21 infra/gjs
|
|
6
|
+
// packages that opted out of the triplet model.
|
|
7
|
+
export {
|
|
8
|
+
getDerivedAliases,
|
|
9
|
+
getDerivedAliasesSync,
|
|
10
|
+
listDeclaredRuntimes,
|
|
11
|
+
resetRuntimeAliasesCache,
|
|
12
|
+
} from './runtime-aliases.mjs';
|
|
13
|
+
|
|
1
14
|
/** Array of Node.js build in module names */
|
|
2
15
|
export const EXTERNALS_NODE = [
|
|
3
16
|
'assert',
|
|
@@ -266,15 +279,33 @@ export const ALIASES_WEB_FOR_NODE = {
|
|
|
266
279
|
// Bare specifiers → native re-exports (works for both named and side-effect imports)
|
|
267
280
|
'abort-controller': '@gjsify/abort-controller/globals',
|
|
268
281
|
'compression-streams': '@gjsify/compression-streams/globals',
|
|
269
|
-
|
|
282
|
+
// dom-events deliberately routes to the polyfill on Node — globals.mjs
|
|
283
|
+
// is browser-targeted (re-exports the full lib.dom event class hierarchy
|
|
284
|
+
// from globalThis), but Node 22 only ships Event/EventTarget/CustomEvent
|
|
285
|
+
// natively (no UIEvent / MouseEvent / KeyboardEvent / WheelEvent /
|
|
286
|
+
// PointerEvent / InputEvent / FocusEvent / …). Routing the bare specifier
|
|
287
|
+
// to the polyfill keeps the full class hierarchy available on Node; the
|
|
288
|
+
// polyfill subclasses native Event/EventTarget where present and supplies
|
|
289
|
+
// the rest. The `/globals` path remains reachable for browser builds via
|
|
290
|
+
// the dynamic per-runtimes-triplet resolver in `runtime-aliases.mjs`.
|
|
291
|
+
'dom-events': '@gjsify/dom-events',
|
|
270
292
|
'dom-exception': '@gjsify/dom-exception/globals',
|
|
271
293
|
'message-channel': '@gjsify/message-channel/globals',
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
294
|
+
// eventsource deliberately routes to the polyfill on Node — globals.mjs
|
|
295
|
+
// re-exports `globalThis.EventSource`, but Node 24.x doesn't ship native
|
|
296
|
+
// EventSource at all (added experimentally in 22.3, requires the
|
|
297
|
+
// --experimental-eventsource flag on many distributions). The polyfill
|
|
298
|
+
// itself uses `fetch` + ReadableStream — both native on Node 18+ — and
|
|
299
|
+
// works without any GJS deps; routing to it gives consumers a working
|
|
300
|
+
// EventSource on Node out of the box. Same fix pattern as the `dom-events`
|
|
301
|
+
// entry above.
|
|
302
|
+
'eventsource': '@gjsify/eventsource',
|
|
303
|
+
// 'fetch' routes to @gjsify/fetch/globals on Node — the globals.mjs
|
|
304
|
+
// re-exports the native globalThis.{fetch,Request,Response,Headers,
|
|
305
|
+
// FormData} as named exports. The dynamic resolver routes
|
|
306
|
+
// @gjsify/fetch to the same /globals subpath via its runtimes
|
|
307
|
+
// triplet declaration (`node:"native"`), so both entry forms converge.
|
|
308
|
+
'fetch': '@gjsify/fetch/globals',
|
|
278
309
|
'formdata': '@gjsify/formdata/globals',
|
|
279
310
|
'html-image-element': '@gjsify/html-image-element',
|
|
280
311
|
'webcrypto': '@gjsify/webcrypto/globals',
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// Runtime-aware alias derivation.
|
|
2
|
+
//
|
|
3
|
+
// Each `@gjsify/*` package may declare a `gjsify.runtimes` triplet in its
|
|
4
|
+
// `package.json`:
|
|
5
|
+
//
|
|
6
|
+
// {
|
|
7
|
+
// "gjsify": {
|
|
8
|
+
// "runtimes": {
|
|
9
|
+
// "gjs": "polyfill" | "native" | "partial" | "none",
|
|
10
|
+
// "node": "polyfill" | "native" | "partial" | "none",
|
|
11
|
+
// "browser": "polyfill" | "native" | "partial" | "none"
|
|
12
|
+
// }
|
|
13
|
+
// }
|
|
14
|
+
// }
|
|
15
|
+
//
|
|
16
|
+
// The bundler's alias resolver consults this triplet when routing bare
|
|
17
|
+
// `@gjsify/<X>` specifiers per `--app <target>`:
|
|
18
|
+
//
|
|
19
|
+
// slot=polyfill → keep as-is (`@gjsify/<X>` — our impl ships)
|
|
20
|
+
// slot=native → redirect to `@gjsify/<X>/globals` (re-exports native value)
|
|
21
|
+
// slot=partial → keep as-is (our impl, gracefully degrades at runtime)
|
|
22
|
+
// slot=none → no rewrite (the package's polyfill resolves normally;
|
|
23
|
+
// its GJS-only value-deps are stripped by the bundler's
|
|
24
|
+
// `gjsImportsEmptyPlugin`; runtime calls into GJS-only
|
|
25
|
+
// code paths fail with a structured no-op — same shape
|
|
26
|
+
// as `slot=partial`)
|
|
27
|
+
//
|
|
28
|
+
// Packages without a declared `runtimes` triplet fall through to the hardcoded
|
|
29
|
+
// `ALIASES_*` maps in `./index.mjs` — backwards-compatible behavior for the
|
|
30
|
+
// infra/gjs/framework packages that opted out of the triplet model.
|
|
31
|
+
//
|
|
32
|
+
// When a slot=native package is missing the corresponding `globals.mjs`
|
|
33
|
+
// re-export file, the resolver emits a warn-once log and falls back to
|
|
34
|
+
// `@gjsify/empty` (the current behavior) — surfacing the gap is desirable.
|
|
35
|
+
|
|
36
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
37
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
38
|
+
import { join, dirname, resolve } from 'node:path';
|
|
39
|
+
import { fileURLToPath } from 'node:url';
|
|
40
|
+
|
|
41
|
+
const VALID_SLOTS = new Set(['polyfill', 'native', 'partial', 'none']);
|
|
42
|
+
const VALID_TARGETS = new Set(['gjs', 'node', 'browser']);
|
|
43
|
+
|
|
44
|
+
/** @typedef {'polyfill'|'native'|'partial'|'none'} Slot */
|
|
45
|
+
/** @typedef {{gjs?:Slot, node?:Slot, browser?:Slot}} RuntimeTriplet */
|
|
46
|
+
/** @typedef {{name:string, dir:string, runtimes:RuntimeTriplet, hasGlobals:boolean}} PackageRecord */
|
|
47
|
+
|
|
48
|
+
let _cache = null;
|
|
49
|
+
const _warned = new Set();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Locate every directory that may contain `@gjsify/*` packages — workspace
|
|
53
|
+
* monorepo source dirs AND installed `node_modules/@gjsify/` farms.
|
|
54
|
+
*
|
|
55
|
+
* Two discovery modes (both checked, results merged):
|
|
56
|
+
*
|
|
57
|
+
* 1. **Workspace mode** — when the resolver is being consulted from inside
|
|
58
|
+
* the gjsify monorepo itself, walk up from this module to find a
|
|
59
|
+
* `packages/{node,web,dom,framework,infra}` layout. Returns the
|
|
60
|
+
* `packages/` root.
|
|
61
|
+
*
|
|
62
|
+
* 2. **Installed mode** — when the resolver is being consulted from a
|
|
63
|
+
* consumer project's `node_modules/@gjsify/resolve-npm`, the workspace
|
|
64
|
+
* layout is NOT present. Instead, walk up to find `node_modules/@gjsify/`
|
|
65
|
+
* and scan every `<pkg>/package.json` there for `gjsify.runtimes`.
|
|
66
|
+
*
|
|
67
|
+
* Returns an array of `{kind, dir}` entries; the caller scans each per its
|
|
68
|
+
* own layout convention.
|
|
69
|
+
*/
|
|
70
|
+
function findScanRoots() {
|
|
71
|
+
/** @type {Array<{kind:'workspace'|'installed', dir:string}>} */
|
|
72
|
+
const roots = [];
|
|
73
|
+
|
|
74
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
let cur = here;
|
|
76
|
+
for (let i = 0; i < 8; i++) {
|
|
77
|
+
// Workspace check: <cur>/packages/{node,web,dom,framework,infra}.
|
|
78
|
+
const packagesCandidate = resolve(cur, 'packages');
|
|
79
|
+
if (existsSync(packagesCandidate)) {
|
|
80
|
+
try {
|
|
81
|
+
const stamp = ['node', 'web', 'dom', 'framework', 'infra'].some((s) =>
|
|
82
|
+
existsSync(join(packagesCandidate, s)),
|
|
83
|
+
);
|
|
84
|
+
if (stamp) roots.push({ kind: 'workspace', dir: packagesCandidate });
|
|
85
|
+
} catch {
|
|
86
|
+
// ignore
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Installed check: <cur>/node_modules/@gjsify (single-flat npm layout).
|
|
91
|
+
const installedCandidate = resolve(cur, 'node_modules', '@gjsify');
|
|
92
|
+
if (existsSync(installedCandidate)) {
|
|
93
|
+
roots.push({ kind: 'installed', dir: installedCandidate });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const parent = dirname(cur);
|
|
97
|
+
if (parent === cur) break;
|
|
98
|
+
cur = parent;
|
|
99
|
+
}
|
|
100
|
+
// Also scan the consumer project's cwd-side node_modules — the resolver
|
|
101
|
+
// is invoked from the bundler's process (running in the consumer project),
|
|
102
|
+
// so cwd is a meaningful root even when this module's own dirname is in a
|
|
103
|
+
// hoisted location.
|
|
104
|
+
try {
|
|
105
|
+
const cwdRoot = resolve(process.cwd(), 'node_modules', '@gjsify');
|
|
106
|
+
if (existsSync(cwdRoot)) roots.push({ kind: 'installed', dir: cwdRoot });
|
|
107
|
+
} catch {
|
|
108
|
+
// process.cwd() can throw in unusual environments; ignore.
|
|
109
|
+
}
|
|
110
|
+
return roots;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} dir
|
|
115
|
+
* @param {Map<string,PackageRecord>} out
|
|
116
|
+
*/
|
|
117
|
+
async function ingestPackageDir(dir, out) {
|
|
118
|
+
const pkgJsonPath = join(dir, 'package.json');
|
|
119
|
+
if (!existsSync(pkgJsonPath)) return;
|
|
120
|
+
try {
|
|
121
|
+
const raw = await readFile(pkgJsonPath, 'utf8');
|
|
122
|
+
const json = JSON.parse(raw);
|
|
123
|
+
const name = typeof json.name === 'string' ? json.name : null;
|
|
124
|
+
const runtimes = json?.gjsify?.runtimes;
|
|
125
|
+
if (
|
|
126
|
+
name &&
|
|
127
|
+
name.startsWith('@gjsify/') &&
|
|
128
|
+
!out.has(name) &&
|
|
129
|
+
runtimes &&
|
|
130
|
+
typeof runtimes === 'object'
|
|
131
|
+
) {
|
|
132
|
+
/** @type {RuntimeTriplet} */
|
|
133
|
+
const triplet = {};
|
|
134
|
+
for (const t of VALID_TARGETS) {
|
|
135
|
+
const v = runtimes[t];
|
|
136
|
+
if (typeof v === 'string' && VALID_SLOTS.has(v)) {
|
|
137
|
+
triplet[t] = v;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const hasGlobals = existsSync(join(dir, 'globals.mjs'));
|
|
141
|
+
out.set(name, { name, dir, runtimes: triplet, hasGlobals });
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Ignore unreadable / invalid package.json
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} dir
|
|
150
|
+
* @param {Map<string,PackageRecord>} out
|
|
151
|
+
*/
|
|
152
|
+
function ingestPackageDirSync(dir, out) {
|
|
153
|
+
const pkgJsonPath = join(dir, 'package.json');
|
|
154
|
+
if (!existsSync(pkgJsonPath)) return;
|
|
155
|
+
try {
|
|
156
|
+
const raw = readFileSync(pkgJsonPath, 'utf8');
|
|
157
|
+
const json = JSON.parse(raw);
|
|
158
|
+
const name = typeof json.name === 'string' ? json.name : null;
|
|
159
|
+
const runtimes = json?.gjsify?.runtimes;
|
|
160
|
+
if (
|
|
161
|
+
name &&
|
|
162
|
+
name.startsWith('@gjsify/') &&
|
|
163
|
+
!out.has(name) &&
|
|
164
|
+
runtimes &&
|
|
165
|
+
typeof runtimes === 'object'
|
|
166
|
+
) {
|
|
167
|
+
/** @type {RuntimeTriplet} */
|
|
168
|
+
const triplet = {};
|
|
169
|
+
for (const t of VALID_TARGETS) {
|
|
170
|
+
const v = runtimes[t];
|
|
171
|
+
if (typeof v === 'string' && VALID_SLOTS.has(v)) {
|
|
172
|
+
triplet[t] = v;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const hasGlobals = existsSync(join(dir, 'globals.mjs'));
|
|
176
|
+
out.set(name, { name, dir, runtimes: triplet, hasGlobals });
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Ignore unreadable / invalid package.json
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Walk `packages/**` and collect every `@gjsify/*` package.json + signals. */
|
|
184
|
+
async function collectPackages() {
|
|
185
|
+
const scanRoots = findScanRoots();
|
|
186
|
+
/** @type {Map<string, PackageRecord>} */
|
|
187
|
+
const out = new Map();
|
|
188
|
+
if (scanRoots.length === 0) return out;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Workspace walker — descends past `packages/{node,web,…}/<name>/`.
|
|
192
|
+
* @param {string} dir
|
|
193
|
+
*/
|
|
194
|
+
async function walkWorkspace(dir) {
|
|
195
|
+
let entries;
|
|
196
|
+
try {
|
|
197
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
198
|
+
} catch {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
202
|
+
await ingestPackageDir(dir, out);
|
|
203
|
+
return; // do not descend into package internals
|
|
204
|
+
}
|
|
205
|
+
for (const ent of entries) {
|
|
206
|
+
if (!ent.isDirectory()) continue;
|
|
207
|
+
if (ent.name === 'node_modules' || ent.name === 'lib' || ent.name === 'dist') continue;
|
|
208
|
+
if (ent.name.startsWith('.')) continue;
|
|
209
|
+
await walkWorkspace(join(dir, ent.name));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Installed walker — `node_modules/@gjsify/<name>/` is one level deep.
|
|
215
|
+
* @param {string} gjsifyDir
|
|
216
|
+
*/
|
|
217
|
+
async function walkInstalled(gjsifyDir) {
|
|
218
|
+
let entries;
|
|
219
|
+
try {
|
|
220
|
+
entries = await readdir(gjsifyDir, { withFileTypes: true });
|
|
221
|
+
} catch {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const ent of entries) {
|
|
225
|
+
if (!ent.isDirectory() && !ent.isSymbolicLink()) continue;
|
|
226
|
+
if (ent.name.startsWith('.')) continue;
|
|
227
|
+
await ingestPackageDir(join(gjsifyDir, ent.name), out);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const root of scanRoots) {
|
|
232
|
+
if (root.kind === 'workspace') {
|
|
233
|
+
await walkWorkspace(root.dir);
|
|
234
|
+
} else {
|
|
235
|
+
await walkInstalled(root.dir);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Lazy-loaded cache of all declared runtime triplets. */
|
|
242
|
+
async function getCache() {
|
|
243
|
+
if (_cache) return _cache;
|
|
244
|
+
_cache = await collectPackages();
|
|
245
|
+
return _cache;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Synchronous variant — used by the bundler at config-time (one-shot init). */
|
|
249
|
+
function getCacheSync() {
|
|
250
|
+
if (_cache) return _cache;
|
|
251
|
+
const scanRoots = findScanRoots();
|
|
252
|
+
/** @type {Map<string, PackageRecord>} */
|
|
253
|
+
const out = new Map();
|
|
254
|
+
if (scanRoots.length === 0) {
|
|
255
|
+
_cache = out;
|
|
256
|
+
return _cache;
|
|
257
|
+
}
|
|
258
|
+
/** @param {string} dir */
|
|
259
|
+
function walkWorkspace(dir) {
|
|
260
|
+
let entries;
|
|
261
|
+
try {
|
|
262
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
263
|
+
} catch {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
267
|
+
ingestPackageDirSync(dir, out);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const ent of entries) {
|
|
271
|
+
if (!ent.isDirectory()) continue;
|
|
272
|
+
if (ent.name === 'node_modules' || ent.name === 'lib' || ent.name === 'dist') continue;
|
|
273
|
+
if (ent.name.startsWith('.')) continue;
|
|
274
|
+
walkWorkspace(join(dir, ent.name));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** @param {string} gjsifyDir */
|
|
278
|
+
function walkInstalled(gjsifyDir) {
|
|
279
|
+
let entries;
|
|
280
|
+
try {
|
|
281
|
+
entries = readdirSync(gjsifyDir, { withFileTypes: true });
|
|
282
|
+
} catch {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const ent of entries) {
|
|
286
|
+
if (!ent.isDirectory() && !ent.isSymbolicLink()) continue;
|
|
287
|
+
if (ent.name.startsWith('.')) continue;
|
|
288
|
+
ingestPackageDirSync(join(gjsifyDir, ent.name), out);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (const root of scanRoots) {
|
|
292
|
+
if (root.kind === 'workspace') walkWorkspace(root.dir);
|
|
293
|
+
else walkInstalled(root.dir);
|
|
294
|
+
}
|
|
295
|
+
_cache = out;
|
|
296
|
+
return _cache;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Emit a one-shot warning. Subsequent calls with the same key are silent.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} key
|
|
303
|
+
* @param {string} message
|
|
304
|
+
*/
|
|
305
|
+
function warnOnce(key, message) {
|
|
306
|
+
if (_warned.has(key)) return;
|
|
307
|
+
_warned.add(key);
|
|
308
|
+
// eslint-disable-next-line no-console
|
|
309
|
+
console.warn(`[@gjsify/resolve-npm] ${message}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Given a package record + target runtime, resolve the alias target.
|
|
314
|
+
*
|
|
315
|
+
* @param {PackageRecord} rec
|
|
316
|
+
* @param {'gjs'|'node'|'browser'} target
|
|
317
|
+
* @returns {string|null} The alias target specifier, or null if no rewrite applies.
|
|
318
|
+
*/
|
|
319
|
+
function resolveSlot(rec, target) {
|
|
320
|
+
const slot = rec.runtimes[target];
|
|
321
|
+
if (!slot) return null; // Slot undeclared → no opinion, leave to hardcoded maps.
|
|
322
|
+
switch (slot) {
|
|
323
|
+
case 'polyfill':
|
|
324
|
+
case 'partial':
|
|
325
|
+
// Keep as-is — bundler resolves `@gjsify/<X>` to its `lib/esm/index.js`.
|
|
326
|
+
return null;
|
|
327
|
+
case 'native':
|
|
328
|
+
if (rec.hasGlobals) {
|
|
329
|
+
return `${rec.name}/globals`;
|
|
330
|
+
}
|
|
331
|
+
warnOnce(
|
|
332
|
+
`${rec.name}-${target}-native-missing-globals`,
|
|
333
|
+
`${rec.name} declares runtimes.${target}="native" but ships no globals.mjs — falling back to @gjsify/empty for --app ${target} builds.`,
|
|
334
|
+
);
|
|
335
|
+
return '@gjsify/empty';
|
|
336
|
+
case 'none':
|
|
337
|
+
// Intentionally no rewrite. The original draft routed `none` to
|
|
338
|
+
// `@gjsify/empty` (idea: shrink GJS-only polyfills out of Node
|
|
339
|
+
// bundles), but that breaks bundles whose own `*.gjs.spec.ts`
|
|
340
|
+
// files import their host package by name — e.g. `@gjsify/tls`'s
|
|
341
|
+
// `session-access.gjs.spec.ts` imports `TLSSocket, connect, …`
|
|
342
|
+
// from `@gjsify/tls`. Empty has none of those exports, so the
|
|
343
|
+
// bundler errors with MISSING_EXPORT before the gjs-spec gets a
|
|
344
|
+
// chance to runtime-guard via `on('Gjs', …)`.
|
|
345
|
+
// Letting `none`-slotted packages resolve normally to the
|
|
346
|
+
// polyfill keeps the bundle valid; the `gjsImportsEmptyPlugin`
|
|
347
|
+
// (added to `--app node` in a prior PR) still strips the
|
|
348
|
+
// `gi://`/`@girs/*` value-deps inside the polyfill so the bundle
|
|
349
|
+
// doesn't crash on load. Consumers calling GJS-only code paths
|
|
350
|
+
// on Node will hit a runtime no-op / structured failure inside
|
|
351
|
+
// the polyfill, which is the documented `partial`-shaped
|
|
352
|
+
// behavior — and exactly what the strategy doc allows.
|
|
353
|
+
return null;
|
|
354
|
+
default:
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Build the derived `@gjsify/<X>` alias map for the given target runtime.
|
|
361
|
+
*
|
|
362
|
+
* The returned map only contains entries whose source specifier starts with
|
|
363
|
+
* `@gjsify/` — it is meant to be merged on top of the hardcoded `ALIASES_*`
|
|
364
|
+
* maps in `./index.mjs`. Entries already present in the hardcoded maps win
|
|
365
|
+
* (callers should `{ ...derived, ...hardcoded }` to preserve current behavior
|
|
366
|
+
* for packages opted out of the triplet model).
|
|
367
|
+
*
|
|
368
|
+
* @param {'gjs'|'node'|'browser'} target
|
|
369
|
+
* @returns {Record<string,string>}
|
|
370
|
+
*/
|
|
371
|
+
export function getDerivedAliasesSync(target) {
|
|
372
|
+
if (!VALID_TARGETS.has(target)) return {};
|
|
373
|
+
const cache = getCacheSync();
|
|
374
|
+
/** @type {Record<string,string>} */
|
|
375
|
+
const out = {};
|
|
376
|
+
for (const rec of cache.values()) {
|
|
377
|
+
const alias = resolveSlot(rec, target);
|
|
378
|
+
if (alias !== null && alias !== rec.name) {
|
|
379
|
+
out[rec.name] = alias;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Async variant of {@link getDerivedAliasesSync} — preferred when callable from
|
|
387
|
+
* an async config hook.
|
|
388
|
+
*
|
|
389
|
+
* @param {'gjs'|'node'|'browser'} target
|
|
390
|
+
* @returns {Promise<Record<string,string>>}
|
|
391
|
+
*/
|
|
392
|
+
export async function getDerivedAliases(target) {
|
|
393
|
+
if (!VALID_TARGETS.has(target)) return {};
|
|
394
|
+
const cache = await getCache();
|
|
395
|
+
/** @type {Record<string,string>} */
|
|
396
|
+
const out = {};
|
|
397
|
+
for (const rec of cache.values()) {
|
|
398
|
+
const alias = resolveSlot(rec, target);
|
|
399
|
+
if (alias !== null && alias !== rec.name) {
|
|
400
|
+
out[rec.name] = alias;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Reset the in-memory cache. Test-only — production code should never call
|
|
408
|
+
* this. Exposed so the e2e suite can force a re-scan after writing fixtures.
|
|
409
|
+
*/
|
|
410
|
+
export function resetRuntimeAliasesCache() {
|
|
411
|
+
_cache = null;
|
|
412
|
+
_warned.clear();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Expose the raw declaration map (read-only) for diagnostics. */
|
|
416
|
+
export async function listDeclaredRuntimes() {
|
|
417
|
+
const cache = await getCache();
|
|
418
|
+
return new Map(cache);
|
|
419
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/resolve-npm",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.33",
|
|
4
4
|
"description": "Resolve NPM package aliases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"default": "./lib/index.mjs"
|
|
13
13
|
},
|
|
14
14
|
"./globals-map": "./lib/globals-map.mjs",
|
|
15
|
+
"./runtime-aliases": "./lib/runtime-aliases.mjs",
|
|
15
16
|
"./package.json": "./package.json"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|