@gjsify/module 0.3.13 → 0.3.15

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.
Files changed (3) hide show
  1. package/lib/esm/index.js +227 -208
  2. package/lib/esm/pnp.js +161 -116
  3. package/package.json +7 -7
package/lib/esm/index.js CHANGED
@@ -1,238 +1,257 @@
1
+ import { findPnpManifest, loadPnpManifest, resolveBareViaPnp } from "./pnp.js";
1
2
  import "@girs/gjs";
2
3
  import Gio from "@girs/gio-2.0";
3
4
  import GLib from "@girs/glib-2.0";
4
- import { resolve as resolvePath, readJSON } from "@gjsify/utils";
5
- import { findPnpManifest, loadPnpManifest, resolveBareViaPnp } from "./pnp.js";
5
+ import { readJSON, resolve } from "@gjsify/utils";
6
+
7
+ //#region src/index.ts
6
8
  const builtinModules = [
7
- "assert",
8
- "async_hooks",
9
- "buffer",
10
- "child_process",
11
- "cluster",
12
- "console",
13
- "constants",
14
- "crypto",
15
- "dgram",
16
- "diagnostics_channel",
17
- "dns",
18
- "domain",
19
- "events",
20
- "fs",
21
- "http",
22
- "http2",
23
- "https",
24
- "inspector",
25
- "module",
26
- "net",
27
- "os",
28
- "path",
29
- "perf_hooks",
30
- "process",
31
- "punycode",
32
- "querystring",
33
- "readline",
34
- "repl",
35
- "stream",
36
- "string_decoder",
37
- "sys",
38
- "timers",
39
- "tls",
40
- "tty",
41
- "url",
42
- "util",
43
- "v8",
44
- "vm",
45
- "wasi",
46
- "worker_threads",
47
- "zlib"
9
+ "assert",
10
+ "async_hooks",
11
+ "buffer",
12
+ "child_process",
13
+ "cluster",
14
+ "console",
15
+ "constants",
16
+ "crypto",
17
+ "dgram",
18
+ "diagnostics_channel",
19
+ "dns",
20
+ "domain",
21
+ "events",
22
+ "fs",
23
+ "http",
24
+ "http2",
25
+ "https",
26
+ "inspector",
27
+ "module",
28
+ "net",
29
+ "os",
30
+ "path",
31
+ "perf_hooks",
32
+ "process",
33
+ "punycode",
34
+ "querystring",
35
+ "readline",
36
+ "repl",
37
+ "stream",
38
+ "string_decoder",
39
+ "sys",
40
+ "timers",
41
+ "tls",
42
+ "tty",
43
+ "url",
44
+ "util",
45
+ "v8",
46
+ "vm",
47
+ "wasi",
48
+ "worker_threads",
49
+ "zlib"
48
50
  ];
49
51
  function isBuiltin(name) {
50
- const n = name.startsWith("node:") ? name.slice(5) : name;
51
- const base = n.split("/")[0];
52
- return builtinModules.includes(n) || builtinModules.includes(base);
52
+ const n = name.startsWith("node:") ? name.slice(5) : name;
53
+ const base = n.split("/")[0];
54
+ return builtinModules.includes(n) || builtinModules.includes(base);
53
55
  }
56
+ /** Walk up from startDir to find the nearest node_modules directory. */
54
57
  function findNodeModulesDir(startDir) {
55
- let dir = Gio.File.new_for_path(startDir);
56
- while (dir.has_parent(null)) {
57
- const nodeModules = dir.resolve_relative_path("node_modules");
58
- if (nodeModules.query_exists(null)) {
59
- return nodeModules.get_path();
60
- }
61
- dir = dir.get_parent();
62
- }
63
- return null;
58
+ let dir = Gio.File.new_for_path(startDir);
59
+ while (dir.has_parent(null)) {
60
+ const nodeModules = dir.resolve_relative_path("node_modules");
61
+ if (nodeModules.query_exists(null)) {
62
+ return nodeModules.get_path();
63
+ }
64
+ dir = dir.get_parent();
65
+ }
66
+ return null;
64
67
  }
68
+ /** Resolve symlinks for a Gio.File, returning the real path. */
65
69
  function resolveSymlink(file) {
66
- const info = file.query_info("standard::", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
67
- if (info.get_is_symlink()) {
68
- const target = info.get_symlink_target();
69
- const parent = file.get_parent();
70
- if (target && parent) {
71
- return parent.resolve_relative_path(target).get_path();
72
- }
73
- }
74
- return file.get_path();
70
+ const info = file.query_info("standard::", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
71
+ if (info.get_is_symlink()) {
72
+ const target = info.get_symlink_target();
73
+ const parent = file.get_parent();
74
+ if (target && parent) {
75
+ return parent.resolve_relative_path(target).get_path();
76
+ }
77
+ }
78
+ return file.get_path();
75
79
  }
80
+ /** Try appending .js to an extensionless path. Returns the file if found, null otherwise. */
76
81
  function tryJsExtension(filePath) {
77
- const withJs = Gio.File.new_for_path(filePath + ".js");
78
- return withJs.query_exists(null) ? withJs : null;
82
+ const withJs = Gio.File.new_for_path(filePath + ".js");
83
+ return withJs.query_exists(null) ? withJs : null;
79
84
  }
85
+ /** Check if a basename has a file extension. */
80
86
  function hasExtension(basename) {
81
- return basename.includes(".");
87
+ return basename.includes(".");
82
88
  }
89
+ /** Resolve package.json main/module entry for a directory. */
83
90
  function resolvePackageEntry(dirPath) {
84
- const pkgJsonFile = resolvePath(dirPath, "package.json");
85
- if (!pkgJsonFile.query_exists(null)) return null;
86
- const pkg = readJSON(pkgJsonFile.get_path());
87
- const main = pkg.main || pkg.module || "index.js";
88
- const entryFile = resolvePath(dirPath, main);
89
- if (entryFile.query_exists(null)) return entryFile.get_path();
90
- if (!hasExtension(main)) {
91
- const withJs = tryJsExtension(entryFile.get_path());
92
- if (withJs) return withJs.get_path();
93
- }
94
- return null;
91
+ const pkgJsonFile = resolve(dirPath, "package.json");
92
+ if (!pkgJsonFile.query_exists(null)) return null;
93
+ const pkg = readJSON(pkgJsonFile.get_path());
94
+ const main = pkg.main || pkg.module || "index.js";
95
+ const entryFile = resolve(dirPath, main);
96
+ if (entryFile.query_exists(null)) return entryFile.get_path();
97
+ if (!hasExtension(main)) {
98
+ const withJs = tryJsExtension(entryFile.get_path());
99
+ if (withJs) return withJs.get_path();
100
+ }
101
+ return null;
95
102
  }
103
+ /** Convert a file: URL (string or object) to an absolute path. */
96
104
  function fileUrlToPath(filenameOrURL) {
97
- if (typeof filenameOrURL === "object" && filenameOrURL !== null && "href" in filenameOrURL) {
98
- const urlObj = filenameOrURL;
99
- if (urlObj.protocol && urlObj.protocol !== "file:") {
100
- throw new TypeError("The URL must use the file: protocol");
101
- }
102
- return GLib.filename_from_uri(urlObj.href)[0];
103
- }
104
- if (typeof filenameOrURL === "string" && filenameOrURL.startsWith("file:")) {
105
- return GLib.filename_from_uri(filenameOrURL)[0];
106
- }
107
- return String(filenameOrURL);
105
+ if (typeof filenameOrURL === "object" && filenameOrURL !== null && "href" in filenameOrURL) {
106
+ const urlObj = filenameOrURL;
107
+ if (urlObj.protocol && urlObj.protocol !== "file:") {
108
+ throw new TypeError("The URL must use the file: protocol");
109
+ }
110
+ return GLib.filename_from_uri(urlObj.href)[0];
111
+ }
112
+ if (typeof filenameOrURL === "string" && filenameOrURL.startsWith("file:")) {
113
+ return GLib.filename_from_uri(filenameOrURL)[0];
114
+ }
115
+ return String(filenameOrURL);
108
116
  }
117
+ /**
118
+ * Try resolving a bare specifier through a Yarn PnP manifest (`.pnp.cjs`)
119
+ * sitting above `callerDir`. Returns null when no manifest is found, or when
120
+ * PnP can't resolve the request (e.g. the dep isn't listed in the caller
121
+ * package's `packageDependencies`). Callers fall back to the node_modules walk.
122
+ */
109
123
  function resolveBareViaPnpFromCaller(id, callerDir) {
110
- const pnpPath = findPnpManifest(callerDir);
111
- if (!pnpPath) return null;
112
- const manifest = loadPnpManifest(pnpPath);
113
- if (!manifest) return null;
114
- const resolved = resolveBareViaPnp(manifest, id, callerDir);
115
- if (!resolved) return null;
116
- const file = Gio.File.new_for_path(resolved);
117
- return file.query_exists(null) ? file : null;
124
+ const pnpPath = findPnpManifest(callerDir);
125
+ if (!pnpPath) return null;
126
+ const manifest = loadPnpManifest(pnpPath);
127
+ if (!manifest) return null;
128
+ const resolved = resolveBareViaPnp(manifest, id, callerDir);
129
+ if (!resolved) return null;
130
+ const file = Gio.File.new_for_path(resolved);
131
+ return file.query_exists(null) ? file : null;
118
132
  }
133
+ /**
134
+ * Resolve a bare package specifier by walking ALL ancestor node_modules dirs.
135
+ * Mirrors Node.js module resolution: try nearest node_modules first, then each
136
+ * ancestor — so a package in a parent node_modules is found even when a closer
137
+ * node_modules exists but doesn't contain the requested package.
138
+ */
119
139
  function resolveInNodeModules(id, callerDir) {
120
- let dir = Gio.File.new_for_path(callerDir);
121
- while (dir.has_parent(null)) {
122
- const nodeModulesFile = dir.resolve_relative_path("node_modules");
123
- if (nodeModulesFile.query_exists(null)) {
124
- const candidate = nodeModulesFile.resolve_relative_path(id);
125
- if (candidate.query_exists(null)) return candidate;
126
- const bn = candidate.get_basename();
127
- if (bn && !hasExtension(bn)) {
128
- const withJs = tryJsExtension(candidate.get_path());
129
- if (withJs) return withJs;
130
- }
131
- }
132
- dir = dir.get_parent();
133
- }
134
- throw new Error(`Cannot find module "${id}" - not found in any node_modules directory`);
140
+ let dir = Gio.File.new_for_path(callerDir);
141
+ while (dir.has_parent(null)) {
142
+ const nodeModulesFile = dir.resolve_relative_path("node_modules");
143
+ if (nodeModulesFile.query_exists(null)) {
144
+ const candidate = nodeModulesFile.resolve_relative_path(id);
145
+ if (candidate.query_exists(null)) return candidate;
146
+ const bn = candidate.get_basename();
147
+ if (bn && !hasExtension(bn)) {
148
+ const withJs = tryJsExtension(candidate.get_path());
149
+ if (withJs) return withJs;
150
+ }
151
+ }
152
+ dir = dir.get_parent();
153
+ }
154
+ throw new Error(`Cannot find module "${id}" - not found in any node_modules directory`);
135
155
  }
156
+ /** Resolve a module specifier to an absolute file path. */
136
157
  function resolveModulePath(id, callerDir) {
137
- if (isBuiltin(id)) return id;
138
- let file;
139
- if (id.startsWith("/")) {
140
- file = resolvePath(id);
141
- } else if (id.startsWith(".")) {
142
- file = resolvePath(callerDir, id);
143
- } else {
144
- const pnpFile = resolveBareViaPnpFromCaller(id, callerDir);
145
- file = pnpFile ?? resolveInNodeModules(id, callerDir);
146
- }
147
- if (id.startsWith("/") || id.startsWith(".")) {
148
- if (!file.query_exists(null)) {
149
- const basename2 = file.get_basename();
150
- if (basename2 && !hasExtension(basename2)) {
151
- file = tryJsExtension(file.get_path()) ?? file;
152
- }
153
- }
154
- if (!file.query_exists(null)) {
155
- throw new Error(`Cannot find module "${id}"`);
156
- }
157
- }
158
- const resolvedPath = resolveSymlink(file);
159
- const basename = file.get_basename();
160
- if (basename && !hasExtension(basename)) {
161
- const entry = resolvePackageEntry(resolvedPath);
162
- if (entry) return entry;
163
- }
164
- return resolvedPath;
158
+ if (isBuiltin(id)) return id;
159
+ let file;
160
+ if (id.startsWith("/")) {
161
+ file = resolve(id);
162
+ } else if (id.startsWith(".")) {
163
+ file = resolve(callerDir, id);
164
+ } else {
165
+ const pnpFile = resolveBareViaPnpFromCaller(id, callerDir);
166
+ file = pnpFile ?? resolveInNodeModules(id, callerDir);
167
+ }
168
+ if (id.startsWith("/") || id.startsWith(".")) {
169
+ if (!file.query_exists(null)) {
170
+ const basename = file.get_basename();
171
+ if (basename && !hasExtension(basename)) {
172
+ file = tryJsExtension(file.get_path()) ?? file;
173
+ }
174
+ }
175
+ if (!file.query_exists(null)) {
176
+ throw new Error(`Cannot find module "${id}"`);
177
+ }
178
+ }
179
+ const resolvedPath = resolveSymlink(file);
180
+ const basename = file.get_basename();
181
+ if (basename && !hasExtension(basename)) {
182
+ const entry = resolvePackageEntry(resolvedPath);
183
+ if (entry) return entry;
184
+ }
185
+ return resolvedPath;
165
186
  }
187
+ /** Load a CJS .js/.cjs file using GJS's legacy imports system. */
166
188
  function requireJsFile(filePath, cache) {
167
- if (filePath in cache) return cache[filePath];
168
- let file = Gio.File.new_for_path(filePath);
169
- const dir = file.get_parent().get_path();
170
- let basename = file.get_basename();
171
- if (basename.endsWith(".mjs")) {
172
- throw new Error(`Cannot require .mjs files. Use import instead. Path: "${filePath}"`);
173
- }
174
- if (basename.endsWith(".cjs")) {
175
- const dest = resolvePath(dir, "__gjsify__" + basename.replace(/\.cjs$/, ".js"));
176
- if (dest.query_exists(null)) dest.delete(null);
177
- file.copy(dest, Gio.FileCopyFlags.NONE, null, null);
178
- file = dest;
179
- basename = file.get_basename();
180
- }
181
- const savedExports = globalThis.exports;
182
- const savedModule = globalThis.module;
183
- const moduleObj = { exports: {} };
184
- globalThis.exports = moduleObj.exports;
185
- globalThis.module = moduleObj;
186
- try {
187
- const { searchPath } = imports;
188
- searchPath.unshift(dir);
189
- imports[basename.replace(/\.(js|cjs)$/, "")];
190
- searchPath.shift();
191
- const result = moduleObj.exports;
192
- cache[filePath] = result;
193
- return result;
194
- } finally {
195
- globalThis.exports = savedExports;
196
- globalThis.module = savedModule;
197
- }
189
+ if (filePath in cache) return cache[filePath];
190
+ let file = Gio.File.new_for_path(filePath);
191
+ const dir = file.get_parent().get_path();
192
+ let basename = file.get_basename();
193
+ if (basename.endsWith(".mjs")) {
194
+ throw new Error(`Cannot require .mjs files. Use import instead. Path: "${filePath}"`);
195
+ }
196
+ if (basename.endsWith(".cjs")) {
197
+ const dest = resolve(dir, "__gjsify__" + basename.replace(/\.cjs$/, ".js"));
198
+ if (dest.query_exists(null)) dest.delete(null);
199
+ file.copy(dest, Gio.FileCopyFlags.NONE, null, null);
200
+ file = dest;
201
+ basename = file.get_basename();
202
+ }
203
+ const savedExports = globalThis.exports;
204
+ const savedModule = globalThis.module;
205
+ const moduleObj = { exports: {} };
206
+ globalThis.exports = moduleObj.exports;
207
+ globalThis.module = moduleObj;
208
+ try {
209
+ const { searchPath } = imports;
210
+ searchPath.unshift(dir);
211
+ imports[basename.replace(/\.(js|cjs)$/, "")];
212
+ searchPath.shift();
213
+ const result = moduleObj.exports;
214
+ cache[filePath] = result;
215
+ return result;
216
+ } finally {
217
+ globalThis.exports = savedExports;
218
+ globalThis.module = savedModule;
219
+ }
198
220
  }
199
221
  function createRequire(filenameOrURL) {
200
- const filename = fileUrlToPath(filenameOrURL);
201
- if (!filename.startsWith("/")) {
202
- throw new TypeError(
203
- `The argument must be a file URL object, file URL string, or absolute path string. Received "${String(filenameOrURL)}"`
204
- );
205
- }
206
- const callerDir = GLib.path_get_dirname(filename);
207
- const cache = /* @__PURE__ */ Object.create(null);
208
- const req = function require2(id) {
209
- const resolved = resolveModulePath(id, callerDir);
210
- if (resolved in cache) return cache[resolved];
211
- if (resolved.endsWith(".json")) {
212
- const result = readJSON(resolved);
213
- cache[resolved] = result;
214
- return result;
215
- }
216
- if (isBuiltin(id)) {
217
- throw new Error(
218
- `createRequire: Cannot require builtin module "${id}" synchronously in GJS. Use import instead.`
219
- );
220
- }
221
- return requireJsFile(resolved, cache);
222
- };
223
- req.resolve = function resolve(id) {
224
- return resolveModulePath(id, callerDir);
225
- };
226
- req.resolve.paths = (_request) => null;
227
- req.cache = cache;
228
- req.extensions = /* @__PURE__ */ Object.create(null);
229
- req.main = void 0;
230
- return req;
222
+ const filename = fileUrlToPath(filenameOrURL);
223
+ if (!filename.startsWith("/")) {
224
+ throw new TypeError("The argument must be a file URL object, file URL string, or absolute path string. " + `Received "${String(filenameOrURL)}"`);
225
+ }
226
+ const callerDir = GLib.path_get_dirname(filename);
227
+ const cache = Object.create(null);
228
+ const req = function require(id) {
229
+ const resolved = resolveModulePath(id, callerDir);
230
+ if (resolved in cache) return cache[resolved];
231
+ if (resolved.endsWith(".json")) {
232
+ const result = readJSON(resolved);
233
+ cache[resolved] = result;
234
+ return result;
235
+ }
236
+ if (isBuiltin(id)) {
237
+ throw new Error(`createRequire: Cannot require builtin module "${id}" synchronously in GJS. ` + "Use import instead.");
238
+ }
239
+ return requireJsFile(resolved, cache);
240
+ };
241
+ req.resolve = function resolve(id) {
242
+ return resolveModulePath(id, callerDir);
243
+ };
244
+ req.resolve.paths = (_request) => null;
245
+ req.cache = cache;
246
+ req.extensions = Object.create(null);
247
+ req.main = undefined;
248
+ return req;
231
249
  }
232
- var index_default = { builtinModules, isBuiltin, createRequire };
233
- export {
234
- builtinModules,
235
- createRequire,
236
- index_default as default,
237
- isBuiltin
250
+ var src_default = {
251
+ builtinModules,
252
+ isBuiltin,
253
+ createRequire
238
254
  };
255
+
256
+ //#endregion
257
+ export { builtinModules, createRequire, src_default as default, isBuiltin };
package/lib/esm/pnp.js CHANGED
@@ -1,134 +1,179 @@
1
1
  import Gio from "@girs/gio-2.0";
2
2
  import GLib from "@girs/glib-2.0";
3
- const manifestCache = /* @__PURE__ */ new Map();
3
+
4
+ //#region src/pnp.ts
5
+ const manifestCache = new Map();
6
+ /** Walk up from `startDir` looking for the nearest `.pnp.cjs`. */
4
7
  function findPnpManifest(startDir) {
5
- let dir = Gio.File.new_for_path(startDir);
6
- while (dir.has_parent(null)) {
7
- const candidate = dir.resolve_relative_path(".pnp.cjs");
8
- if (candidate.query_exists(null)) return candidate.get_path();
9
- dir = dir.get_parent();
10
- }
11
- return null;
8
+ let dir = Gio.File.new_for_path(startDir);
9
+ while (dir.has_parent(null)) {
10
+ const candidate = dir.resolve_relative_path(".pnp.cjs");
11
+ if (candidate.query_exists(null)) return candidate.get_path();
12
+ dir = dir.get_parent();
13
+ }
14
+ return null;
12
15
  }
16
+ /** Read + parse `.pnp.cjs`'s `RAW_RUNTIME_STATE`. Cached. */
13
17
  function loadPnpManifest(pnpCjsPath) {
14
- const cached = manifestCache.get(pnpCjsPath);
15
- if (cached !== void 0) return cached;
16
- const file = Gio.File.new_for_path(pnpCjsPath);
17
- let text;
18
- try {
19
- const [ok, bytes] = file.load_contents(null);
20
- if (!ok) {
21
- manifestCache.set(pnpCjsPath, null);
22
- return null;
23
- }
24
- text = new TextDecoder().decode(bytes);
25
- } catch {
26
- manifestCache.set(pnpCjsPath, null);
27
- return null;
28
- }
29
- const state = extractRawRuntimeState(text);
30
- if (!state) {
31
- manifestCache.set(pnpCjsPath, null);
32
- return null;
33
- }
34
- const rootDir = GLib.path_get_dirname(pnpCjsPath);
35
- const packages = /* @__PURE__ */ new Map();
36
- const locatorsByLocation = /* @__PURE__ */ new Map();
37
- for (const [name, store] of state.packageRegistryData) {
38
- const inner = /* @__PURE__ */ new Map();
39
- for (const [reference, info] of store) {
40
- inner.set(reference, info);
41
- if (!info.discardFromLookup) {
42
- locatorsByLocation.set(info.packageLocation, { name, reference });
43
- }
44
- }
45
- packages.set(name, inner);
46
- }
47
- const manifest = { rootDir, packages, locatorsByLocation };
48
- manifestCache.set(pnpCjsPath, manifest);
49
- return manifest;
18
+ const cached = manifestCache.get(pnpCjsPath);
19
+ if (cached !== undefined) return cached;
20
+ const file = Gio.File.new_for_path(pnpCjsPath);
21
+ let text;
22
+ try {
23
+ const [ok, bytes] = file.load_contents(null);
24
+ if (!ok) {
25
+ manifestCache.set(pnpCjsPath, null);
26
+ return null;
27
+ }
28
+ text = new TextDecoder().decode(bytes);
29
+ } catch {
30
+ manifestCache.set(pnpCjsPath, null);
31
+ return null;
32
+ }
33
+ const state = extractRawRuntimeState(text);
34
+ if (!state) {
35
+ manifestCache.set(pnpCjsPath, null);
36
+ return null;
37
+ }
38
+ const rootDir = GLib.path_get_dirname(pnpCjsPath);
39
+ const packages = new Map();
40
+ const locatorsByLocation = new Map();
41
+ for (const [name, store] of state.packageRegistryData) {
42
+ const inner = new Map();
43
+ for (const [reference, info] of store) {
44
+ inner.set(reference, info);
45
+ if (!info.discardFromLookup) {
46
+ locatorsByLocation.set(info.packageLocation, {
47
+ name,
48
+ reference
49
+ });
50
+ }
51
+ }
52
+ packages.set(name, inner);
53
+ }
54
+ const manifest = {
55
+ rootDir,
56
+ packages,
57
+ locatorsByLocation
58
+ };
59
+ manifestCache.set(pnpCjsPath, manifest);
60
+ return manifest;
50
61
  }
62
+ /**
63
+ * Strip the line-continuations (`\\\n`) Yarn writes around the JSON literal
64
+ * and JSON.parse the result. Returns null if we can't find the literal.
65
+ */
51
66
  function extractRawRuntimeState(text) {
52
- const start = text.indexOf("const RAW_RUNTIME_STATE =");
53
- if (start < 0) return null;
54
- const openQuote = text.indexOf("'", start);
55
- if (openQuote < 0) return null;
56
- let i = openQuote + 1;
57
- while (i < text.length) {
58
- const ch = text.charCodeAt(i);
59
- if (ch === 92) {
60
- i += 2;
61
- continue;
62
- }
63
- if (ch === 39) {
64
- break;
65
- }
66
- i++;
67
- }
68
- if (i >= text.length) return null;
69
- let raw = text.slice(openQuote + 1, i);
70
- raw = raw.replace(/\\\n/g, "");
71
- try {
72
- return JSON.parse(raw);
73
- } catch {
74
- return null;
75
- }
67
+ const start = text.indexOf("const RAW_RUNTIME_STATE =");
68
+ if (start < 0) return null;
69
+ const openQuote = text.indexOf("'", start);
70
+ if (openQuote < 0) return null;
71
+ let i = openQuote + 1;
72
+ while (i < text.length) {
73
+ const ch = text.charCodeAt(i);
74
+ if (ch === 92) {
75
+ i += 2;
76
+ continue;
77
+ }
78
+ if (ch === 39) {
79
+ break;
80
+ }
81
+ i++;
82
+ }
83
+ if (i >= text.length) return null;
84
+ let raw = text.slice(openQuote + 1, i);
85
+ raw = raw.replace(/\\\n/g, "");
86
+ try {
87
+ return JSON.parse(raw);
88
+ } catch {
89
+ return null;
90
+ }
76
91
  }
92
+ /**
93
+ * Find which package owns `absolutePath`. Returns the locator + its info, or
94
+ * null when the path isn't covered by any package in the manifest.
95
+ *
96
+ * Uses longest-prefix-match against `packageLocation` entries (Yarn does the
97
+ * same in `findPackageLocator`).
98
+ */
77
99
  function findPackageOwning(manifest, absolutePath) {
78
- const relPath = relativeFromRoot(manifest.rootDir, absolutePath);
79
- if (relPath === null) return null;
80
- let bestMatch = null;
81
- for (const candidateLocation of manifest.locatorsByLocation.keys()) {
82
- if (relPath.startsWith(candidateLocation) && (bestMatch === null || candidateLocation.length > bestMatch.length)) {
83
- bestMatch = candidateLocation;
84
- }
85
- }
86
- if (bestMatch === null) return null;
87
- const locator = manifest.locatorsByLocation.get(bestMatch);
88
- const info = manifest.packages.get(locator.name)?.get(locator.reference);
89
- if (!info) return null;
90
- return { locator, info };
100
+ const relPath = relativeFromRoot(manifest.rootDir, absolutePath);
101
+ if (relPath === null) return null;
102
+ let bestMatch = null;
103
+ for (const candidateLocation of manifest.locatorsByLocation.keys()) {
104
+ if (relPath.startsWith(candidateLocation) && (bestMatch === null || candidateLocation.length > bestMatch.length)) {
105
+ bestMatch = candidateLocation;
106
+ }
107
+ }
108
+ if (bestMatch === null) return null;
109
+ const locator = manifest.locatorsByLocation.get(bestMatch);
110
+ const info = manifest.packages.get(locator.name)?.get(locator.reference);
111
+ if (!info) return null;
112
+ return {
113
+ locator,
114
+ info
115
+ };
91
116
  }
117
+ /**
118
+ * Resolve a bare specifier through PnP. Returns the absolute on-disk path,
119
+ * or null when the request can't be resolved this way.
120
+ *
121
+ * `id` is a bare specifier like `@scope/foo` or `@scope/foo/bar/baz.js`.
122
+ * `callerPath` is the absolute path of the file doing the require.
123
+ */
92
124
  function resolveBareViaPnp(manifest, id, callerPath) {
93
- const owner = findPackageOwning(manifest, callerPath);
94
- if (!owner) return null;
95
- const { pkgName, subPath } = splitSpecifier(id);
96
- const dep = owner.info.packageDependencies?.find(([name]) => name === pkgName);
97
- if (!dep) return null;
98
- const [, reference] = dep;
99
- if (reference === null) return null;
100
- const target = manifest.packages.get(pkgName)?.get(reference);
101
- if (!target) return null;
102
- const baseFile = target.packageLocation.startsWith("/") ? Gio.File.new_for_path(target.packageLocation) : Gio.File.new_for_path(manifest.rootDir).resolve_relative_path(
103
- stripLeadingDotSlash(target.packageLocation)
104
- );
105
- const finalFile = subPath ? baseFile.resolve_relative_path(subPath) : baseFile;
106
- return finalFile.get_path();
125
+ const owner = findPackageOwning(manifest, callerPath);
126
+ if (!owner) return null;
127
+ const { pkgName, subPath } = splitSpecifier(id);
128
+ const dep = owner.info.packageDependencies?.find(([name]) => name === pkgName);
129
+ if (!dep) return null;
130
+ const [, reference] = dep;
131
+ if (reference === null) return null;
132
+ const target = manifest.packages.get(pkgName)?.get(reference);
133
+ if (!target) return null;
134
+ const baseFile = target.packageLocation.startsWith("/") ? Gio.File.new_for_path(target.packageLocation) : Gio.File.new_for_path(manifest.rootDir).resolve_relative_path(stripLeadingDotSlash(target.packageLocation));
135
+ const finalFile = subPath ? baseFile.resolve_relative_path(subPath) : baseFile;
136
+ return finalFile.get_path();
107
137
  }
138
+ /** Split `@scope/foo/sub/path` into `pkgName: '@scope/foo'`, `subPath: 'sub/path'`. */
108
139
  function splitSpecifier(id) {
109
- if (id.startsWith("@")) {
110
- const slash1 = id.indexOf("/");
111
- if (slash1 < 0) return { pkgName: id, subPath: "" };
112
- const slash2 = id.indexOf("/", slash1 + 1);
113
- if (slash2 < 0) return { pkgName: id, subPath: "" };
114
- return { pkgName: id.slice(0, slash2), subPath: id.slice(slash2 + 1) };
115
- }
116
- const slash = id.indexOf("/");
117
- if (slash < 0) return { pkgName: id, subPath: "" };
118
- return { pkgName: id.slice(0, slash), subPath: id.slice(slash + 1) };
140
+ if (id.startsWith("@")) {
141
+ const slash1 = id.indexOf("/");
142
+ if (slash1 < 0) return {
143
+ pkgName: id,
144
+ subPath: ""
145
+ };
146
+ const slash2 = id.indexOf("/", slash1 + 1);
147
+ if (slash2 < 0) return {
148
+ pkgName: id,
149
+ subPath: ""
150
+ };
151
+ return {
152
+ pkgName: id.slice(0, slash2),
153
+ subPath: id.slice(slash2 + 1)
154
+ };
155
+ }
156
+ const slash = id.indexOf("/");
157
+ if (slash < 0) return {
158
+ pkgName: id,
159
+ subPath: ""
160
+ };
161
+ return {
162
+ pkgName: id.slice(0, slash),
163
+ subPath: id.slice(slash + 1)
164
+ };
119
165
  }
120
166
  function stripLeadingDotSlash(p) {
121
- return p.startsWith("./") ? p.slice(2) : p;
167
+ return p.startsWith("./") ? p.slice(2) : p;
122
168
  }
169
+ /** Returns `<absolutePath>` minus `<rootDir>/`, with a trailing slash on
170
+ * directory components, matching Yarn's `packageLocation` keys. */
123
171
  function relativeFromRoot(rootDir, absolutePath) {
124
- if (absolutePath === rootDir) return "./";
125
- const prefix = rootDir.endsWith("/") ? rootDir : rootDir + "/";
126
- if (!absolutePath.startsWith(prefix)) return null;
127
- return "./" + absolutePath.slice(prefix.length);
172
+ if (absolutePath === rootDir) return "./";
173
+ const prefix = rootDir.endsWith("/") ? rootDir : rootDir + "/";
174
+ if (!absolutePath.startsWith(prefix)) return null;
175
+ return "./" + absolutePath.slice(prefix.length);
128
176
  }
129
- export {
130
- findPackageOwning,
131
- findPnpManifest,
132
- loadPnpManifest,
133
- resolveBareViaPnp
134
- };
177
+
178
+ //#endregion
179
+ export { findPackageOwning, findPnpManifest, loadPnpManifest, resolveBareViaPnp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/module",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Node.js module module for Gjs",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -30,14 +30,14 @@
30
30
  "module"
31
31
  ],
32
32
  "dependencies": {
33
- "@girs/gio-2.0": "^2.88.0-4.0.0-rc.9",
34
- "@girs/gjs": "^4.0.0-rc.9",
35
- "@girs/glib-2.0": "^2.88.0-4.0.0-rc.9",
36
- "@gjsify/utils": "^0.3.13"
33
+ "@girs/gio-2.0": "2.88.0-4.0.0-rc.9",
34
+ "@girs/gjs": "4.0.0-rc.9",
35
+ "@girs/glib-2.0": "2.88.0-4.0.0-rc.9",
36
+ "@gjsify/utils": "^0.3.15"
37
37
  },
38
38
  "devDependencies": {
39
- "@gjsify/cli": "^0.3.13",
40
- "@gjsify/unit": "^0.3.13",
39
+ "@gjsify/cli": "^0.3.15",
40
+ "@gjsify/unit": "^0.3.15",
41
41
  "@types/node": "^25.6.0",
42
42
  "typescript": "^6.0.3"
43
43
  }