@gjsify/cli 0.3.3 → 0.3.4

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.
@@ -2,8 +2,10 @@ import { build } from "esbuild";
2
2
  import { gjsifyPlugin } from "@gjsify/esbuild-plugin-gjsify";
3
3
  import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals, } from "@gjsify/esbuild-plugin-gjsify/globals";
4
4
  import { dirname, extname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
5
6
  import { chmod, readFile, writeFile } from "node:fs/promises";
6
7
  import { existsSync } from "node:fs";
8
+ import { createRequire } from "node:module";
7
9
  const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
8
10
  /** Walk up from dir until .pnp.cjs is found; return its directory or null. */
9
11
  function findPnpRoot(dir) {
@@ -22,26 +24,77 @@ function findPnpRoot(dir) {
22
24
  * @yarnpkg/esbuild-plugin-pnp plugin so esbuild can resolve
23
25
  * modules from zip archives without manual extraction.
24
26
  *
25
- * Custom onResolve: fall through on UNDECLARED_DEPENDENCY errors so the
26
- * gjsify alias plugin can handle bare specifiers (e.g. `abort-controller`)
27
- * that PnP can't resolve from the inject file's issuer context but that
28
- * gjsify maps to `@gjsify/*` packages the project DOES have available.
27
+ * Custom onResolve: when the project's PnP context throws
28
+ * UNDECLARED_DEPENDENCY, retry via a two-hop relay:
29
+ * 1. @gjsify/cli context (direct dep of the project using gjsify build)
30
+ * 2. @gjsify/node-polyfills context (direct dep of @gjsify/cli, has all
31
+ * node polyfills as direct deps including @gjsify/node-globals)
32
+ * 3. @gjsify/web-polyfills context (direct dep of @gjsify/cli, has all
33
+ * web polyfills as direct deps including @gjsify/fetch, @gjsify/abort-controller)
34
+ * For bare specifiers that the gjsify alias plugin maps (e.g. `abort-controller`),
35
+ * fall through so that plugin can handle the transformation first.
29
36
  */
30
37
  async function getPnpPlugin() {
31
38
  if (!findPnpRoot(process.cwd()))
32
39
  return null;
33
40
  try {
34
41
  const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
42
+ // gjsify's own file path — @gjsify/cli has node-polyfills + web-polyfills
43
+ // as direct deps, so we can resolve them as relay issuers from here.
44
+ const gjsifyIssuer = fileURLToPath(import.meta.url);
45
+ // Two-hop relay: node-polyfills and web-polyfills have all individual
46
+ // @gjsify/* packages as direct deps. Resolving from their paths allows
47
+ // PnP to reach e.g. @gjsify/node-globals (dep of node-polyfills).
48
+ const requireFromGjsify = createRequire(gjsifyIssuer);
49
+ const relayIssuers = [];
50
+ for (const pkg of ["@gjsify/node-polyfills", "@gjsify/web-polyfills"]) {
51
+ try {
52
+ relayIssuers.push(requireFromGjsify.resolve(pkg));
53
+ }
54
+ catch {
55
+ // polyfills package not in dep tree — relay won't cover it
56
+ }
57
+ }
58
+ let pnpApi = null;
59
+ try {
60
+ // pnpapi has no npm package — it is a virtual module injected by Yarn PnP
61
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
62
+ // @ts-expect-error
63
+ pnpApi = (await import("pnpapi"));
64
+ }
65
+ catch {
66
+ // Not in a PnP runtime (shouldn't happen since findPnpRoot passed)
67
+ }
35
68
  return pnpPlugin({
36
- onResolve: async (_args, { resolvedPath, error, watchFiles }) => {
69
+ onResolve: async (args, { resolvedPath, error, watchFiles }) => {
37
70
  if (resolvedPath !== null) {
38
71
  return { namespace: "pnp", path: resolvedPath, watchFiles };
39
72
  }
40
- // UNDECLARED_DEPENDENCY: package exists transitively but isn't
41
- // in the issuer's direct deps. Fall through so the gjsify alias
42
- // plugin can resolve it (e.g. bare → @gjsify/* mappings).
43
73
  if (error?.pnpCode ===
44
74
  "UNDECLARED_DEPENDENCY") {
75
+ if (pnpApi !== null) {
76
+ // Try @gjsify/cli context first (covers @gjsify/* that are
77
+ // direct deps of cli's own deps — unlikely but fast check).
78
+ try {
79
+ const rp = pnpApi.resolveRequest(args.path, gjsifyIssuer);
80
+ if (rp !== null)
81
+ return { namespace: "pnp", path: rp, watchFiles };
82
+ }
83
+ catch { }
84
+ // Two-hop relay: resolve from node-polyfills / web-polyfills context
85
+ // which have the individual @gjsify/* packages as direct deps.
86
+ for (const relayIssuer of relayIssuers) {
87
+ try {
88
+ const rp = pnpApi.resolveRequest(args.path, relayIssuer);
89
+ if (rp !== null)
90
+ return { namespace: "pnp", path: rp, watchFiles };
91
+ }
92
+ catch { }
93
+ }
94
+ }
95
+ // Fall through — bare aliases (abort-controller, fetch/register/*)
96
+ // are handled by the gjsify alias plugin after this returns null,
97
+ // then the re-resolved @gjsify/* path goes through this hook again.
45
98
  return null;
46
99
  }
47
100
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,15 +23,15 @@
23
23
  "cli"
24
24
  ],
25
25
  "dependencies": {
26
- "@gjsify/create-app": "^0.3.3",
27
- "@gjsify/esbuild-plugin-gjsify": "^0.3.3",
28
- "@gjsify/example-dom-canvas2d-fireworks": "^0.3.3",
29
- "@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.3",
30
- "@gjsify/example-dom-three-geometry-teapot": "^0.3.3",
31
- "@gjsify/example-dom-three-postprocessing-pixel": "^0.3.3",
32
- "@gjsify/example-node-express-webserver": "^0.3.3",
33
- "@gjsify/node-polyfills": "^0.3.3",
34
- "@gjsify/web-polyfills": "^0.3.3",
26
+ "@gjsify/create-app": "^0.3.4",
27
+ "@gjsify/esbuild-plugin-gjsify": "^0.3.4",
28
+ "@gjsify/example-dom-canvas2d-fireworks": "^0.3.4",
29
+ "@gjsify/example-dom-excalibur-jelly-jumper": "^0.3.4",
30
+ "@gjsify/example-dom-three-geometry-teapot": "^0.3.4",
31
+ "@gjsify/example-dom-three-postprocessing-pixel": "^0.3.4",
32
+ "@gjsify/example-node-express-webserver": "^0.3.4",
33
+ "@gjsify/node-polyfills": "^0.3.4",
34
+ "@gjsify/web-polyfills": "^0.3.4",
35
35
  "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
36
36
  "cosmiconfig": "^9.0.1",
37
37
  "esbuild": "^0.28.0",
@@ -8,8 +8,10 @@ import {
8
8
  detectAutoGlobals,
9
9
  } from "@gjsify/esbuild-plugin-gjsify/globals";
10
10
  import { dirname, extname, join } from "node:path";
11
+ import { fileURLToPath } from "node:url";
11
12
  import { chmod, readFile, writeFile } from "node:fs/promises";
12
13
  import { existsSync } from "node:fs";
14
+ import { createRequire } from "node:module";
13
15
 
14
16
  const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
15
17
 
@@ -29,27 +31,82 @@ function findPnpRoot(dir: string): string | null {
29
31
  * @yarnpkg/esbuild-plugin-pnp plugin so esbuild can resolve
30
32
  * modules from zip archives without manual extraction.
31
33
  *
32
- * Custom onResolve: fall through on UNDECLARED_DEPENDENCY errors so the
33
- * gjsify alias plugin can handle bare specifiers (e.g. `abort-controller`)
34
- * that PnP can't resolve from the inject file's issuer context but that
35
- * gjsify maps to `@gjsify/*` packages the project DOES have available.
34
+ * Custom onResolve: when the project's PnP context throws
35
+ * UNDECLARED_DEPENDENCY, retry via a two-hop relay:
36
+ * 1. @gjsify/cli context (direct dep of the project using gjsify build)
37
+ * 2. @gjsify/node-polyfills context (direct dep of @gjsify/cli, has all
38
+ * node polyfills as direct deps including @gjsify/node-globals)
39
+ * 3. @gjsify/web-polyfills context (direct dep of @gjsify/cli, has all
40
+ * web polyfills as direct deps including @gjsify/fetch, @gjsify/abort-controller)
41
+ * For bare specifiers that the gjsify alias plugin maps (e.g. `abort-controller`),
42
+ * fall through so that plugin can handle the transformation first.
36
43
  */
37
44
  async function getPnpPlugin(): Promise<Plugin | null> {
38
45
  if (!findPnpRoot(process.cwd())) return null;
39
46
  try {
40
47
  const { pnpPlugin } = await import("@yarnpkg/esbuild-plugin-pnp");
48
+
49
+ // gjsify's own file path — @gjsify/cli has node-polyfills + web-polyfills
50
+ // as direct deps, so we can resolve them as relay issuers from here.
51
+ const gjsifyIssuer = fileURLToPath(import.meta.url);
52
+
53
+ // Two-hop relay: node-polyfills and web-polyfills have all individual
54
+ // @gjsify/* packages as direct deps. Resolving from their paths allows
55
+ // PnP to reach e.g. @gjsify/node-globals (dep of node-polyfills).
56
+ const requireFromGjsify = createRequire(gjsifyIssuer);
57
+ const relayIssuers: string[] = [];
58
+ for (const pkg of ["@gjsify/node-polyfills", "@gjsify/web-polyfills"]) {
59
+ try {
60
+ relayIssuers.push(requireFromGjsify.resolve(pkg));
61
+ } catch {
62
+ // polyfills package not in dep tree — relay won't cover it
63
+ }
64
+ }
65
+
66
+ // pnpapi is a virtual module injected by Yarn PnP at runtime.
67
+ type PnpApi = {
68
+ resolveRequest: (req: string, issuer: string) => string | null;
69
+ };
70
+ let pnpApi: PnpApi | null = null;
71
+ try {
72
+ // pnpapi has no npm package — it is a virtual module injected by Yarn PnP
73
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
74
+ // @ts-expect-error
75
+ pnpApi = (await import("pnpapi")) as PnpApi;
76
+ } catch {
77
+ // Not in a PnP runtime (shouldn't happen since findPnpRoot passed)
78
+ }
79
+
41
80
  return pnpPlugin({
42
- onResolve: async (_args, { resolvedPath, error, watchFiles }) => {
81
+ onResolve: async (args, { resolvedPath, error, watchFiles }) => {
43
82
  if (resolvedPath !== null) {
44
83
  return { namespace: "pnp", path: resolvedPath, watchFiles };
45
84
  }
46
- // UNDECLARED_DEPENDENCY: package exists transitively but isn't
47
- // in the issuer's direct deps. Fall through so the gjsify alias
48
- // plugin can resolve it (e.g. bare → @gjsify/* mappings).
49
85
  if (
50
86
  (error as { pnpCode?: string } | null)?.pnpCode ===
51
87
  "UNDECLARED_DEPENDENCY"
52
88
  ) {
89
+ if (pnpApi !== null) {
90
+ // Try @gjsify/cli context first (covers @gjsify/* that are
91
+ // direct deps of cli's own deps — unlikely but fast check).
92
+ try {
93
+ const rp = pnpApi.resolveRequest(args.path, gjsifyIssuer);
94
+ if (rp !== null)
95
+ return { namespace: "pnp", path: rp, watchFiles };
96
+ } catch {}
97
+ // Two-hop relay: resolve from node-polyfills / web-polyfills context
98
+ // which have the individual @gjsify/* packages as direct deps.
99
+ for (const relayIssuer of relayIssuers) {
100
+ try {
101
+ const rp = pnpApi.resolveRequest(args.path, relayIssuer);
102
+ if (rp !== null)
103
+ return { namespace: "pnp", path: rp, watchFiles };
104
+ } catch {}
105
+ }
106
+ }
107
+ // Fall through — bare aliases (abort-controller, fetch/register/*)
108
+ // are handled by the gjsify alias plugin after this returns null,
109
+ // then the re-resolved @gjsify/* path goes through this hook again.
53
110
  return null;
54
111
  }
55
112
  return {