@gjsify/fetch 0.38.1 → 0.39.0

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.
@@ -1 +1 @@
1
- import"../_virtual/_rolldown/runtime.js";function rewriteRootRelativeUrl(e,t){if(!e.startsWith(`/`)||e.startsWith(`//`))return e;let n=t.lastIndexOf(`/`);return n<=0?e:`file://${t.slice(0,n)}${e}`}export{rewriteRootRelativeUrl};
1
+ import"../_virtual/_rolldown/runtime.js";import{lastPathSeparatorIndex as e,pathToFileUrlHref as t}from"@gjsify/utils/core";function rewriteRootRelativeUrl(n,r){if(!n.startsWith(`/`)||n.startsWith(`//`))return n;let i=e(r);return i<=0?n:`${t(r.slice(0,i))}${n}`}export{rewriteRootRelativeUrl};
@@ -34,6 +34,32 @@ export default async () => {
34
34
  expect(rewriteRootRelativeUrl('/res/x.png', '')).toBe('/res/x.png');
35
35
  expect(rewriteRootRelativeUrl('/res/x.png', 'main.js')).toBe('/res/x.png');
36
36
  });
37
+ // #1143, measured on Windows 11: the program path is `C:\…\dist\main.js`, the old
38
+ // `lastIndexOf('/')` returned -1, the "no program dir" guard swallowed it, and the
39
+ // URL reached the `Request` constructor unrewritten — `Invalid URL` on bun and
40
+ // deno, then a SIGSEGV / 0xC0000005. These run on the LINUX runner: the win32
41
+ // answer is decided from the path's shape, so it needs no win32 host to check.
42
+ await it('maps a root-relative URL under a win32 drive program path', async () => {
43
+ expect(rewriteRootRelativeUrl('/res/tilemaps/level1.tmx', 'C:\\app\\dist\\main.js')).toBe('file:///C:/app/dist/res/tilemaps/level1.tmx');
44
+ });
45
+ await it('maps a root-relative URL under a UNC program path', async () => {
46
+ expect(rewriteRootRelativeUrl('/res/x.png', '\\\\build\\share\\app\\dist\\main.js')).toBe('file://build/share/app/dist/res/x.png');
47
+ });
48
+ await it('keeps the query string a cache-busted asset request carries', async () => {
49
+ // Only the DIRECTORY is percent-encoded. Encoding the URL half would turn
50
+ // `?v=2` into `%3Fv=2` and ask for a file whose name contains a question mark.
51
+ expect(rewriteRootRelativeUrl('/res/x.png?v=2', 'C:\\app\\dist\\main.js')).toBe('file:///C:/app/dist/res/x.png?v=2');
52
+ expect(rewriteRootRelativeUrl('/res/x.png?v=2', '/opt/app/dist/main.js')).toBe('file:///opt/app/dist/res/x.png?v=2');
53
+ });
54
+ await it('percent-encodes a program dir that needs it', async () => {
55
+ // A space in the program directory is ordinary on Windows (`C:\Program Files`)
56
+ // and was previously emitted raw.
57
+ expect(rewriteRootRelativeUrl('/res/x.png', 'C:\\Program Files\\app\\main.js')).toBe('file:///C:/Program%20Files/app/res/x.png');
58
+ });
59
+ await it('treats a backslash in a POSIX program path as part of the name', async () => {
60
+ // `\` is legal in a POSIX filename, so this must NOT be read as a separator.
61
+ expect(rewriteRootRelativeUrl('/res/x.png', '/opt/od\\d/main.js')).toBe('file:///opt/od%5Cd/res/x.png');
62
+ });
37
63
  });
38
64
  // The `system`-reading wrapper, end to end on gjs — the runtime where the
39
65
  // program path is real. Reached through the aliased package root so the
@@ -8,8 +8,18 @@
8
8
  * cases — revisit if these packages ever handle untrusted input.
9
9
  *
10
10
  * Deliberately no `GLib.path_get_dirname`: `@girs/glib-2.0` is emptied to `{}` in a
11
- * plain-Node bundle, so a GLib call here only ever "worked" behind a swallowing catch. The
12
- * program path is `/`-separated on every runtime this serves, making dirname a slice.
11
+ * plain-Node bundle, so a GLib call here only ever "worked" behind a swallowing catch.
12
+ * The separator question goes to `@gjsify/utils/core` instead, which answers it from the
13
+ * SHAPE of the path — this function's own `lastIndexOf('/')` returned -1 for
14
+ * `C:\…\dist\main.js`, the `slash <= 0` guard read that as "no program dir", and every
15
+ * root-relative `fetch()` on win32 died as `Invalid URL` in the `Request` constructor
16
+ * (#1143).
17
+ *
18
+ * The base and the URL are CONCATENATED rather than resolved through `new URL(url, base)`:
19
+ * resolving a root-relative path against `file:///C:/app/dist` drops the program directory
20
+ * and keeps only the root — and the runtimes do not agree on what the root is. Measured:
21
+ * Node keeps the drive (`file:///C:/res/…`), GJS/SpiderMonkey does not (`file:///res/…`).
22
+ * So resolution is both wrong for this purpose and host-dependent.
13
23
  *
14
24
  * @param programPath `System.programPath ?? System.programInvocationName` on gjs;
15
25
  * `process.argv[1]` — the bundle itself — on the node-gi reverse bridge.
@@ -9,6 +9,9 @@
9
9
  // bare `system` specifier is aliased to `@gjsify/node-gi/system` and kept EXTERNAL, so any
10
10
  // module importing it makes the bundle hard-require the bridge at load, and this package's
11
11
  // node test bundle must load on plain Node.
12
+ // `/core` and not the `@gjsify/utils` root: the root reaches Gio/GLib, which would undo the
13
+ // plain-Node loadability this module's split exists to protect.
14
+ import { lastPathSeparatorIndex, pathToFileUrlHref } from '@gjsify/utils/core';
12
15
  /**
13
16
  * Rewrite a root-relative URL (starts with `/` but not `//`) to a `file://` URL under the
14
17
  * program path's directory, so a GJS/GTK app loads bundled assets with the same paths a
@@ -19,8 +22,18 @@
19
22
  * cases — revisit if these packages ever handle untrusted input.
20
23
  *
21
24
  * Deliberately no `GLib.path_get_dirname`: `@girs/glib-2.0` is emptied to `{}` in a
22
- * plain-Node bundle, so a GLib call here only ever "worked" behind a swallowing catch. The
23
- * program path is `/`-separated on every runtime this serves, making dirname a slice.
25
+ * plain-Node bundle, so a GLib call here only ever "worked" behind a swallowing catch.
26
+ * The separator question goes to `@gjsify/utils/core` instead, which answers it from the
27
+ * SHAPE of the path — this function's own `lastIndexOf('/')` returned -1 for
28
+ * `C:\…\dist\main.js`, the `slash <= 0` guard read that as "no program dir", and every
29
+ * root-relative `fetch()` on win32 died as `Invalid URL` in the `Request` constructor
30
+ * (#1143).
31
+ *
32
+ * The base and the URL are CONCATENATED rather than resolved through `new URL(url, base)`:
33
+ * resolving a root-relative path against `file:///C:/app/dist` drops the program directory
34
+ * and keeps only the root — and the runtimes do not agree on what the root is. Measured:
35
+ * Node keeps the drive (`file:///C:/res/…`), GJS/SpiderMonkey does not (`file:///res/…`).
36
+ * So resolution is both wrong for this purpose and host-dependent.
24
37
  *
25
38
  * @param programPath `System.programPath ?? System.programInvocationName` on gjs;
26
39
  * `process.argv[1]` — the bundle itself — on the node-gi reverse bridge.
@@ -28,8 +41,11 @@
28
41
  export function rewriteRootRelativeUrl(url, programPath) {
29
42
  if (!url.startsWith('/') || url.startsWith('//'))
30
43
  return url;
31
- const slash = programPath.lastIndexOf('/');
32
- if (slash <= 0)
44
+ const separator = lastPathSeparatorIndex(programPath);
45
+ if (separator <= 0)
33
46
  return url; // no usable program dir ('' or a bare name)
34
- return `file://${programPath.slice(0, slash)}${url}`;
47
+ // Only the DIRECTORY is percent-encoded. `url` is already a URL and carries its own
48
+ // `?query` and `#fragment` semantics — encoding it would turn a cache-busting
49
+ // `/res/x.png?v=2` into a request for a file literally called `x.png?v=2`.
50
+ return `${pathToFileUrlHref(programPath.slice(0, separator))}${url}`;
35
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/fetch",
3
- "version": "0.38.1",
3
+ "version": "0.39.0",
4
4
  "description": "Web and Node.js fetch module for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -51,8 +51,8 @@
51
51
  "fetch"
52
52
  ],
53
53
  "devDependencies": {
54
- "@gjsify/cli": "^0.38.1",
55
- "@gjsify/unit": "^0.38.1",
54
+ "@gjsify/cli": "^0.39.0",
55
+ "@gjsify/unit": "^0.39.0",
56
56
  "@types/node": "^25.9.2",
57
57
  "typescript": "^6.0.3"
58
58
  },
@@ -61,11 +61,11 @@
61
61
  "@girs/gjs": "^4.1.0",
62
62
  "@girs/glib-2.0": "^4.1.0",
63
63
  "@girs/soup-3.0": "^4.1.0",
64
- "@gjsify/dom-events": "^0.38.1",
65
- "@gjsify/formdata": "^0.38.1",
66
- "@gjsify/http": "^0.38.1",
67
- "@gjsify/url": "^0.38.1",
68
- "@gjsify/utils": "^0.38.1"
64
+ "@gjsify/dom-events": "^0.39.0",
65
+ "@gjsify/formdata": "^0.39.0",
66
+ "@gjsify/http": "^0.39.0",
67
+ "@gjsify/url": "^0.39.0",
68
+ "@gjsify/utils": "^0.39.0"
69
69
  },
70
70
  "gjsify": {
71
71
  "runtimes": {