@gjsify/cli 0.35.0 → 0.37.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.
Files changed (124) hide show
  1. package/dist/cli.gjs.mjs +136 -126
  2. package/lib/actions/build.d.ts +19 -0
  3. package/lib/actions/build.js +2 -2
  4. package/lib/bundler-pick.js +72 -98
  5. package/lib/cli-fail.d.ts +4 -7
  6. package/lib/cli-fail.js +29 -55
  7. package/lib/commands/affected.js +161 -286
  8. package/lib/commands/install.d.ts +63 -66
  9. package/lib/commands/install.js +372 -536
  10. package/lib/commands/run.d.ts +1 -0
  11. package/lib/commands/run.js +37 -0
  12. package/lib/templates/install.mjs.tmpl +38 -68
  13. package/lib/types/cli-build-options.d.ts +33 -67
  14. package/lib/types/command.d.ts +9 -12
  15. package/lib/types/config-data.d.ts +125 -221
  16. package/lib/utils/auth-npmrc.d.ts +5 -10
  17. package/lib/utils/auth-npmrc.js +11 -21
  18. package/lib/utils/check-system-deps.d.ts +16 -0
  19. package/lib/utils/check-system-deps.js +49 -0
  20. package/lib/utils/clear-targets.d.ts +8 -11
  21. package/lib/utils/clear-targets.js +22 -29
  22. package/lib/utils/copy-targets.d.ts +13 -19
  23. package/lib/utils/copy-targets.js +31 -44
  24. package/lib/utils/dep-aggregation.d.ts +7 -15
  25. package/lib/utils/dep-aggregation.js +9 -22
  26. package/lib/utils/detect-native-packages.d.ts +83 -116
  27. package/lib/utils/detect-native-packages.js +111 -149
  28. package/lib/utils/dir-link.d.ts +29 -48
  29. package/lib/utils/dir-link.js +33 -56
  30. package/lib/utils/discover-showcases.d.ts +6 -7
  31. package/lib/utils/discover-showcases.js +11 -15
  32. package/lib/utils/dlx-cache.d.ts +29 -38
  33. package/lib/utils/dlx-cache.js +53 -63
  34. package/lib/utils/force-exit.d.ts +0 -3
  35. package/lib/utils/force-exit.js +13 -24
  36. package/lib/utils/gi-search-path.d.ts +16 -24
  37. package/lib/utils/gi-search-path.js +54 -82
  38. package/lib/utils/gjs-bundle-guard.d.ts +5 -13
  39. package/lib/utils/gjs-bundle-guard.js +35 -57
  40. package/lib/utils/gjs-source-escape.d.ts +2 -2
  41. package/lib/utils/gjs-source-escape.js +21 -39
  42. package/lib/utils/gjsify-shim.d.ts +26 -8
  43. package/lib/utils/gjsify-shim.js +128 -80
  44. package/lib/utils/html-entry.d.ts +8 -12
  45. package/lib/utils/html-entry.js +19 -28
  46. package/lib/utils/install-backend-native.d.ts +123 -161
  47. package/lib/utils/install-backend-native.js +536 -737
  48. package/lib/utils/install-backend.d.ts +42 -57
  49. package/lib/utils/install-backend.js +16 -21
  50. package/lib/utils/install-cache-fs.d.ts +10 -15
  51. package/lib/utils/install-cache-fs.js +18 -26
  52. package/lib/utils/install-lock.d.ts +5 -6
  53. package/lib/utils/install-lock.js +46 -65
  54. package/lib/utils/install-packument-cache.d.ts +27 -33
  55. package/lib/utils/install-packument-cache.js +44 -57
  56. package/lib/utils/install-progress.d.ts +2 -9
  57. package/lib/utils/install-progress.js +14 -39
  58. package/lib/utils/install-tarball-cache.d.ts +14 -20
  59. package/lib/utils/install-tarball-cache.js +41 -59
  60. package/lib/utils/library-output.d.ts +9 -22
  61. package/lib/utils/library-output.js +19 -37
  62. package/lib/utils/load-npmrc.d.ts +5 -6
  63. package/lib/utils/load-npmrc.js +18 -27
  64. package/lib/utils/node-bundle-guard.d.ts +4 -8
  65. package/lib/utils/node-bundle-guard.js +21 -35
  66. package/lib/utils/node-script.d.ts +12 -0
  67. package/lib/utils/node-script.js +95 -0
  68. package/lib/utils/node-version.d.ts +5 -14
  69. package/lib/utils/node-version.js +15 -24
  70. package/lib/utils/normalize-bundler-options.d.ts +7 -11
  71. package/lib/utils/normalize-bundler-options.js +24 -42
  72. package/lib/utils/npm-oidc.d.ts +23 -37
  73. package/lib/utils/npm-oidc.js +28 -53
  74. package/lib/utils/npm-otp-cache.d.ts +4 -4
  75. package/lib/utils/npm-otp-cache.js +20 -25
  76. package/lib/utils/npm-otp.d.ts +20 -30
  77. package/lib/utils/npm-otp.js +32 -51
  78. package/lib/utils/onboard-probe.d.ts +7 -13
  79. package/lib/utils/onboard-probe.js +16 -27
  80. package/lib/utils/parse-spec.d.ts +3 -3
  81. package/lib/utils/parse-spec.js +4 -5
  82. package/lib/utils/pkg-json-edit.d.ts +7 -12
  83. package/lib/utils/pkg-json-edit.js +13 -26
  84. package/lib/utils/pkg-json.d.ts +11 -18
  85. package/lib/utils/pkg-json.js +19 -28
  86. package/lib/utils/prefixed-output.d.ts +3 -4
  87. package/lib/utils/prefixed-output.js +13 -19
  88. package/lib/utils/prompt.d.ts +3 -4
  89. package/lib/utils/prompt.js +14 -18
  90. package/lib/utils/publish-diagnose.d.ts +11 -16
  91. package/lib/utils/publish-diagnose.js +15 -28
  92. package/lib/utils/publish-headers.d.ts +7 -11
  93. package/lib/utils/publish-headers.js +36 -65
  94. package/lib/utils/publishable-packages.d.ts +4 -6
  95. package/lib/utils/publishable-packages.js +14 -18
  96. package/lib/utils/resolve-gjs-entry.d.ts +5 -8
  97. package/lib/utils/resolve-gjs-entry.js +10 -27
  98. package/lib/utils/resolve-npm-package.d.ts +6 -11
  99. package/lib/utils/resolve-npm-package.js +29 -63
  100. package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
  101. package/lib/utils/resolve-plugin-by-name.js +9 -14
  102. package/lib/utils/run-gjs.d.ts +19 -17
  103. package/lib/utils/run-gjs.js +37 -51
  104. package/lib/utils/run-lifecycle-script.d.ts +5 -9
  105. package/lib/utils/run-lifecycle-script.js +23 -42
  106. package/lib/utils/run-node.d.ts +23 -33
  107. package/lib/utils/run-node.js +56 -60
  108. package/lib/utils/runtimes.d.ts +25 -35
  109. package/lib/utils/runtimes.js +34 -46
  110. package/lib/utils/spawn.d.ts +32 -48
  111. package/lib/utils/spawn.js +51 -84
  112. package/lib/utils/stdin.d.ts +2 -2
  113. package/lib/utils/stdin.js +10 -25
  114. package/lib/utils/suggest.d.ts +6 -7
  115. package/lib/utils/suggest.js +8 -10
  116. package/lib/utils/system-gi.d.ts +18 -27
  117. package/lib/utils/system-gi.js +60 -114
  118. package/lib/utils/trust-registry.d.ts +4 -8
  119. package/lib/utils/trust-registry.js +7 -15
  120. package/lib/utils/win32-command.d.ts +8 -14
  121. package/lib/utils/win32-command.js +29 -43
  122. package/lib/utils/workspace-root.js +7 -12
  123. package/package.json +18 -17
  124. package/showcases.json +6 -0
@@ -1,15 +1,13 @@
1
1
  import { type NpmrcConfig } from '@gjsify/npm-registry';
2
2
  /**
3
3
  * Percent-encode a package name for the publish PUT path, matching
4
- * npm-package-arg's `escapedName` convention (npa.js:188):
5
- * `this.escapedName = name.replace('/', '%2f')`.
4
+ * npm-package-arg's `escapedName` convention.
6
5
  *
7
6
  * Scoped: `@scope/name` → `@scope%2fname` (literal `@`, lowercase `%2f`, the
8
7
  * scope/name segments NOT otherwise re-encoded — npm only replaces the slash).
9
- * Unscoped: returned unchanged (npa stores the bare name; the only characters
10
- * npm forbids are those `encodeURIComponent` would touch, which npa rejects up
11
- * front). The npm registry is picky about this exact shape on the publish PUT
12
- * URL — `%2F` (uppercase) or a re-encoded `@` would miss the create route.
8
+ * Unscoped: returned unchanged. The registry is picky about this exact shape on
9
+ * the publish PUT URL — uppercase `%2F` or a re-encoded `@` misses the create
10
+ * route.
13
11
  */
14
12
  export declare function escapePackageName(name: string): string;
15
13
  export interface PublishHeaderOptions {
@@ -19,8 +17,7 @@ export interface PublishHeaderOptions {
19
17
  otp?: string;
20
18
  /**
21
19
  * Override the `authorization` header (e.g. an OIDC Bearer token resolved
22
- * after `buildHeaders` ran). When set it replaces whatever the npmrc auth
23
- * resolution produced.
20
+ * after `buildHeaders` ran), replacing whatever npmrc auth resolution produced.
24
21
  */
25
22
  authorization?: string;
26
23
  /**
@@ -37,8 +34,7 @@ export interface PublishHeaderOptions {
37
34
  * `npm-command: publish`, `npm-auth-type` (`web`, or `legacy` when `--otp`),
38
35
  * `user-agent`, and `npm-otp` when an OTP is supplied.
39
36
  *
40
- * Notably it does NOT set an `accept: *` wildcard — `npm-registry-fetch` sends
41
- * no `accept` header on the publish PUT; the stray wildcard gjsify used to send
42
- * is dropped to match.
37
+ * Notably NO `accept` header: `npm-registry-fetch` sends none on the publish PUT,
38
+ * and a wildcard here misses the create route.
43
39
  */
44
40
  export declare function buildPublishHeaders(url: string, opts: PublishHeaderOptions): Record<string, string>;
@@ -3,48 +3,30 @@
3
3
  // package correctly.
4
4
  //
5
5
  // Root cause this fixes: a first-publish of a never-before-published
6
- // `@scope/<name>` via token auth was returning a bare `404 Not Found` (body
7
- // `Not Found`, NOT a JSON npm error). That is a routing miss, not a
8
- // payload-validation failure — npm's write frontdoor keys the create path on
9
- // the `npm-command` header, which gjsify was not sending. `npm publish` with
10
- // the same ~/.npmrc token succeeds because libnpmpublish goes through
6
+ // `@scope/<name>` via token auth returned a bare `404 Not Found` (body
7
+ // `Not Found`, NOT a JSON npm error). That is a ROUTING miss, not a
8
+ // payload-validation failure — npm's write frontdoor keys the create path on the
9
+ // `npm-command` header, which gjsify was not sending. `npm publish` with the same
10
+ // ~/.npmrc token succeeds because libnpmpublish goes through
11
11
  // `npm-registry-fetch`'s `getHeaders`, which always sets `npm-command`
12
- // (+ `npm-auth-type`, + a real `user-agent`).
13
- //
14
- // Evidence (refs/npm-cli):
15
- // - `lib/npm.js:404` `flat.npmCommand = this.command` → set for EVERY npm
16
- // command; equals `"publish"` for `npm publish`.
17
- // - `node_modules/npm-registry-fetch/lib/index.js:231-233`
18
- // `if (opts.npmCommand) headers['npm-command'] = opts.npmCommand`
19
- // and `:219-221` `if (opts.authType) headers['npm-auth-type'] = …`
20
- // and `:215-217` `'user-agent': opts.userAgent`.
21
- // - `workspaces/config/lib/definitions/definitions.js:282-290` — `auth-type`
22
- // default `'web'`, flattened to `flatOptions.authType`.
23
- // - `:1581-1587` — when `--otp` is set, npm forces `auth-type` to `'legacy'`
24
- // before flattening, so an OTP publish sends `npm-auth-type: legacy`.
25
- // - `node_modules/npm-package-arg/lib/npa.js:188`
26
- // `this.escapedName = name.replace('/', '%2f')` — lowercase `%2f`,
27
- // literal `@`. (gjsify's URL encoding already matches this — it was never
28
- // the bug.)
12
+ // (+ `npm-auth-type`, + a real `user-agent`). Reference: `refs/npm-cli`.
29
13
  //
30
14
  // This module pulls in no `gi://` and no runtime fetch/Soup stack, so it is
31
- // unit-testable on both Node and GJS. Its one filesystem touch is the
32
- // manifest walk backing the user-agent version — see `cliVersion()`.
15
+ // unit-testable on both Node and GJS. Its one filesystem touch is the manifest
16
+ // walk backing the user-agent version — see `cliVersion()`.
33
17
  import { readFileSync } from 'node:fs';
34
18
  import { dirname } from 'node:path';
35
19
  import { fileURLToPath } from 'node:url';
36
20
  import { buildHeaders } from '@gjsify/npm-registry';
37
21
  /**
38
22
  * Percent-encode a package name for the publish PUT path, matching
39
- * npm-package-arg's `escapedName` convention (npa.js:188):
40
- * `this.escapedName = name.replace('/', '%2f')`.
23
+ * npm-package-arg's `escapedName` convention.
41
24
  *
42
25
  * Scoped: `@scope/name` → `@scope%2fname` (literal `@`, lowercase `%2f`, the
43
26
  * scope/name segments NOT otherwise re-encoded — npm only replaces the slash).
44
- * Unscoped: returned unchanged (npa stores the bare name; the only characters
45
- * npm forbids are those `encodeURIComponent` would touch, which npa rejects up
46
- * front). The npm registry is picky about this exact shape on the publish PUT
47
- * URL — `%2F` (uppercase) or a re-encoded `@` would miss the create route.
27
+ * Unscoped: returned unchanged. The registry is picky about this exact shape on
28
+ * the publish PUT URL — uppercase `%2F` or a re-encoded `@` misses the create
29
+ * route.
48
30
  */
49
31
  export function escapePackageName(name) {
50
32
  return name.replace('/', '%2f');
@@ -57,34 +39,26 @@ export function escapePackageName(name) {
57
39
  * `npm-command: publish`, `npm-auth-type` (`web`, or `legacy` when `--otp`),
58
40
  * `user-agent`, and `npm-otp` when an OTP is supplied.
59
41
  *
60
- * Notably it does NOT set an `accept: *` wildcard — `npm-registry-fetch` sends
61
- * no `accept` header on the publish PUT; the stray wildcard gjsify used to send
62
- * is dropped to match.
42
+ * Notably NO `accept` header: `npm-registry-fetch` sends none on the publish PUT,
43
+ * and a wildcard here misses the create route.
63
44
  */
64
45
  export function buildPublishHeaders(url, opts) {
65
46
  const headers = buildHeaders(url, { npmrc: opts.npmrc });
66
- // npm-registry-fetch never sets `accept` on the publish PUT — drop the
67
- // placeholder `accept-encoding` we don't want to influence the create route
68
- // either. (buildHeaders defaults accept-encoding to `identity`; a publish
69
- // PUT body is JSON we generated, so the encoding header is irrelevant — but
70
- // we keep it harmless and explicit by leaving it as buildHeaders set it.)
71
- // content-type: application/json — npm-registry-fetch sets this for any
72
- // non-string/non-stream/non-buffer body (index.js:63-67); our body is a
73
- // JSON string, so we set it explicitly.
47
+ // `buildHeaders`' `accept-encoding: identity` default is left as it set it —
48
+ // the PUT body is JSON we generated, so the encoding header is irrelevant here.
49
+ // npm-registry-fetch sets this for any non-string/non-stream/non-buffer body;
50
+ // ours is a JSON string, so it is set explicitly.
74
51
  headers['content-type'] = 'application/json';
75
- // npm-command: publish — THE routing header. npm sets it for every command
76
- // (npm.js:404); the registry's write frontdoor uses it to route the
77
- // create-package PUT. Missing it = bare `404 Not Found` on a new package.
52
+ // THE routing header: npm sets it for every command, and the registry's write
53
+ // frontdoor uses it to route the create-package PUT. Missing it = bare
54
+ // `404 Not Found` on a new package.
78
55
  headers['npm-command'] = 'publish';
79
- // npm-auth-type — `web` by default, `legacy` when an OTP is supplied
80
- // (definitions.js:1583-1586). Sent by getHeaders (index.js:219-221).
56
+ // npm forces auth-type `legacy` whenever `--otp` is set, `web` otherwise.
81
57
  headers['npm-auth-type'] = opts.otp ? 'legacy' : 'web';
82
- // user-agent — a real npm-shaped UA (getHeaders index.js:215-217).
83
58
  headers['user-agent'] = opts.userAgent ?? defaultUserAgent();
84
- // npm-otp — only when an OTP is supplied (index.js:243-245).
85
59
  if (opts.otp)
86
60
  headers['npm-otp'] = opts.otp;
87
- // authorization override (OIDC token resolved after npmrc auth).
61
+ // OIDC token resolved after npmrc auth.
88
62
  if (opts.authorization)
89
63
  headers['authorization'] = opts.authorization;
90
64
  return headers;
@@ -94,21 +68,18 @@ export function buildPublishHeaders(url, opts) {
94
68
  * `@gjsify/cli` is a DUAL-ENTRY package and neither layer covers both entries:
95
69
  *
96
70
  * 1. **`__PACKAGE_VERSION__`** — the compile-time define
97
- * (`package.json#gjsify.defineFromPackageJson`), the mechanism
98
- * `@gjsify/npm-registry` uses for the same stale-UA bug. Zero I/O, but the
99
- * bundler only ever writes `dist/cli.gjs.mjs`: the npm-wired Node bin under
100
- * `lib/` is emitted by plain `tsc`, which knows nothing about defines. A
101
- * define alone would therefore have left the Node entry — the one `npm
102
- * install @gjsify/cli` actually runs — on a permanent placeholder.
71
+ * (`package.json#gjsify.defineFromPackageJson`). Zero I/O, but the bundler only
72
+ * writes `dist/cli.gjs.mjs`; the npm-wired Node bin under `lib/` comes from
73
+ * plain `tsc`, which knows nothing about defines. A define alone would leave the
74
+ * Node entry — the one `npm install @gjsify/cli` runs — on a permanent placeholder.
103
75
  *
104
76
  * 2. **an upward manifest walk** for the nearest `package.json` naming
105
77
  * `@gjsify/cli`, covering `lib/`. Deliberately NOT a fixed
106
- * `new URL('../../package.json', import.meta.url)` read: that resolves
107
- * correctly from `lib/utils/` but points one directory too high from
108
- * `dist/`, and the build-time `inlineStaticReads` pass that would have
109
- * frozen it is scoped to `node_modules` paths, so first-party source never
110
- * gets it. The walk is depth-independent and so works from `src/`, `lib/`,
111
- * `dist/`, and from an installed `node_modules/@gjsify/cli/**` alike.
78
+ * `new URL('../../package.json', import.meta.url)` read: that resolves correctly
79
+ * from `lib/utils/` but points one directory too high from `dist/`, and the
80
+ * build-time `inlineStaticReads` pass that would have frozen it is scoped to
81
+ * `node_modules` paths. The walk is depth-independent, so it works from `src/`,
82
+ * `lib/`, `dist/` and an installed `node_modules/@gjsify/cli/**` alike.
112
83
  *
113
84
  * `GJSIFY_CLI_PACKAGE_JSON` overrides both, matching `self-update.ts`.
114
85
  */
@@ -147,10 +118,10 @@ function walkForCliVersion() {
147
118
  return '';
148
119
  }
149
120
  /**
150
- * npm-CLI-shaped user-agent. Format mirrors npm-registry-fetch's
151
- * `default-opts.js` (`npm@<v>/node@<v>+<arch> (<platform>)`); we substitute the
152
- * gjsify CLI identity. The registry does not validate the exact value — it just
153
- * must be a non-placeholder UA, which some WAF / CDN frontdoors require.
121
+ * npm-CLI-shaped user-agent, mirroring npm-registry-fetch's
122
+ * `npm@<v>/node@<v>+<arch> (<platform>)` with the gjsify CLI identity substituted.
123
+ * The registry does not validate the value; it must merely be a non-placeholder UA,
124
+ * which some WAF / CDN frontdoors require.
154
125
  */
155
126
  function defaultUserAgent() {
156
127
  const proc = globalThis.process;
@@ -1,18 +1,16 @@
1
1
  import type { Workspace } from '@gjsify/workspace';
2
2
  /**
3
3
  * Package-group directories holding publishable packages that are NOT root
4
- * workspaces. Add one here the moment a new non-workspace publishable group
5
- * appears — this list is the only thing standing between such a package and a
6
- * silently broken first release.
4
+ * workspaces. Extend this the moment a new such group appears — the list is the
5
+ * only thing standing between one and a silently broken first release.
7
6
  */
8
7
  export declare const NON_WORKSPACE_PUBLISHABLE_DIRS: readonly [readonly ["packages", "node-gi"], readonly ["packages", "napi"]];
9
8
  /**
10
9
  * Publishable packages in {@link NON_WORKSPACE_PUBLISHABLE_DIRS}, shaped as
11
10
  * `Workspace` entries so callers can treat them like any other package.
12
11
  *
13
- * Best-effort by design: a missing directory or an unreadable/malformed
14
- * manifest is skipped rather than thrown, because this runs inside sweeps whose
15
- * value is covering everything they can reach.
12
+ * Best-effort by design — a missing directory or malformed manifest is skipped,
13
+ * not thrown: this runs inside sweeps whose value is covering everything reachable.
16
14
  */
17
15
  export declare function discoverNonWorkspacePublishables(cwd: string): Workspace[];
18
16
  /**
@@ -1,23 +1,20 @@
1
1
  // Publishable-package discovery shared by `gjsify trust` and `gjsify onboard`.
2
2
  //
3
- // `discoverWorkspaces` only sees ROOT WORKSPACES. Some publishable packages
3
+ // `discoverWorkspaces` only sees ROOT WORKSPACES, and some publishable packages
4
4
  // deliberately are not: each is an independent native engine with its own CI
5
- // workflow, kept out of the workspace graph so a full `gjsify foreach` never
6
- // tries to build it. They still publish through `release.yml`, so every sweep
7
- // that reasons about "what do we publish" has to include them — otherwise the
8
- // sweep silently under-reports and the package's first release fails the OIDC
9
- // exchange with `404 — package not found`.
10
- //
11
- // That is not hypothetical: `gjsify onboard` reported 127 packages "already
12
- // done" on 2026-07-26 while `@gjsify/napi` was neither in the list nor
13
- // published at all, because only `packages/node-gi/*` was carved out.
5
+ // workflow, kept out of the graph so a full `gjsify foreach` never tries to build
6
+ // it. They still publish through `release.yml`, so a sweep that reasons about
7
+ // "what do we publish" must include them — otherwise it silently under-reports and
8
+ // the package's first release fails the OIDC exchange with `404 — package not
9
+ // found`. Measured: `gjsify onboard` called 127 packages "already done" while
10
+ // `@gjsify/napi` was neither listed nor published, because only
11
+ // `packages/node-gi/*` had been carved out.
14
12
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
15
13
  import { dirname, join } from 'node:path';
16
14
  /**
17
15
  * Package-group directories holding publishable packages that are NOT root
18
- * workspaces. Add one here the moment a new non-workspace publishable group
19
- * appears — this list is the only thing standing between such a package and a
20
- * silently broken first release.
16
+ * workspaces. Extend this the moment a new such group appears — the list is the
17
+ * only thing standing between one and a silently broken first release.
21
18
  */
22
19
  export const NON_WORKSPACE_PUBLISHABLE_DIRS = [
23
20
  ['packages', 'node-gi'], // @gjsify/node-gi + the @gjsify/gtk-runtime-* bundles
@@ -27,13 +24,12 @@ export const NON_WORKSPACE_PUBLISHABLE_DIRS = [
27
24
  * Publishable packages in {@link NON_WORKSPACE_PUBLISHABLE_DIRS}, shaped as
28
25
  * `Workspace` entries so callers can treat them like any other package.
29
26
  *
30
- * Best-effort by design: a missing directory or an unreadable/malformed
31
- * manifest is skipped rather than thrown, because this runs inside sweeps whose
32
- * value is covering everything they can reach.
27
+ * Best-effort by design — a missing directory or malformed manifest is skipped,
28
+ * not thrown: this runs inside sweeps whose value is covering everything reachable.
33
29
  */
34
30
  export function discoverNonWorkspacePublishables(cwd) {
35
- // Walk up to the repo root. Anchored on the FIRST configured group rather
36
- // than on `packages/` itself, which also exists inside individual packages.
31
+ // Walk up to the repo root, anchored on the FIRST configured group rather than
32
+ // on `packages/` itself, which also exists inside individual packages.
37
33
  let root = cwd;
38
34
  const anchor = NON_WORKSPACE_PUBLISHABLE_DIRS[0];
39
35
  for (let i = 0; i < 8; i++) {
@@ -5,14 +5,11 @@ interface ResolvedEntry {
5
5
  }
6
6
  export declare function resolveGjsEntry(pkgDir: string, binName: string | null): ResolvedEntry;
7
7
  /**
8
- * Resolve the `--app node` bundle of a package for the non-gjs runtimes
9
- * (node/bun/deno all consume it). Resolution order:
10
- * 1. explicit `gjsify.example.node` (a package can name its node bundle)
11
- * 2. convention: the node twin of the GJS entry — swap the `<name>.gjs.<ext>`
12
- * infix to `<name>.node.<ext>` and probe `.node.{mjs,js,cjs}` (gjsify's
13
- * `build:node → dist/<name>.node.mjs` convention; the GJS entry is
14
- * `dist/<name>.gjs.js`, so the extension can differ).
15
- * Hard-fails with an actionable hint when neither is found.
8
+ * Resolve the `--app node` bundle, which node/bun/deno all consume. An explicit
9
+ * `gjsify.example.node` wins; otherwise the node twin of the GJS entry is derived by
10
+ * swapping the `.gjs.<ext>` infix for `.node.<ext>` and probing `mjs`/`js`/`cjs` —
11
+ * the extension can differ, since `build:node` emits `dist/<name>.node.mjs` while the
12
+ * GJS entry is `dist/<name>.gjs.js`.
16
13
  */
17
14
  export declare function resolveNodeEntry(pkgDir: string): string;
18
15
  export {};
@@ -1,20 +1,8 @@
1
- // Resolve the GJS entry point of an installed package.
2
- //
3
- // Per the `gjsify` field convention (see CLI reference):
4
- //
5
- // {
6
- // "gjsify": {
7
- // "main": "dist/gjs.js",
8
- // "bin": { "name-a": "dist/a.js", "name-b": "dist/b.js" }
9
- // }
10
- // }
11
- //
12
- // Resolution order:
13
- // 1. user-supplied bin name + `gjsify.bin[name]` → that path
14
- // 2. single-entry `gjsify.bin` → the only path
15
- // 3. `gjsify.main` → that path
16
- // 4. fallback: `package.json#main` → that path (advisory warning)
17
- // 5. otherwise: hard-fail with a fix hint
1
+ // Resolve the GJS entry point of an installed package, per the `gjsify` field
2
+ // convention. In order: a requested `gjsify.bin[name]`, a single-entry
3
+ // `gjsify.bin`, `gjsify.main`, then `package.json#main` — the last flagged
4
+ // `fromFallback` so the caller can warn (`commands/dlx.ts` does). No entry at all,
5
+ // or several bins with none named, hard-fails with a fix hint.
18
6
  import { readFileSync } from 'node:fs';
19
7
  import { existsSync } from 'node:fs';
20
8
  import { join, resolve } from 'node:path';
@@ -64,14 +52,11 @@ export function resolveGjsEntry(pkgDir, binName) {
64
52
  return { bundlePath, binName: resolvedBin, fromFallback };
65
53
  }
66
54
  /**
67
- * Resolve the `--app node` bundle of a package for the non-gjs runtimes
68
- * (node/bun/deno all consume it). Resolution order:
69
- * 1. explicit `gjsify.example.node` (a package can name its node bundle)
70
- * 2. convention: the node twin of the GJS entry — swap the `<name>.gjs.<ext>`
71
- * infix to `<name>.node.<ext>` and probe `.node.{mjs,js,cjs}` (gjsify's
72
- * `build:node → dist/<name>.node.mjs` convention; the GJS entry is
73
- * `dist/<name>.gjs.js`, so the extension can differ).
74
- * Hard-fails with an actionable hint when neither is found.
55
+ * Resolve the `--app node` bundle, which node/bun/deno all consume. An explicit
56
+ * `gjsify.example.node` wins; otherwise the node twin of the GJS entry is derived by
57
+ * swapping the `.gjs.<ext>` infix for `.node.<ext>` and probing `mjs`/`js`/`cjs` —
58
+ * the extension can differ, since `build:node` emits `dist/<name>.node.mjs` while the
59
+ * GJS entry is `dist/<name>.gjs.js`.
75
60
  */
76
61
  export function resolveNodeEntry(pkgDir) {
77
62
  const pkgJsonPath = join(pkgDir, 'package.json');
@@ -79,7 +64,6 @@ export function resolveNodeEntry(pkgDir) {
79
64
  throw new Error(`no package.json found at ${pkgDir}`);
80
65
  }
81
66
  const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
82
- // 1. Explicit declaration wins.
83
67
  const explicit = pkg.gjsify?.example?.node;
84
68
  if (explicit) {
85
69
  const p = resolve(pkgDir, explicit);
@@ -88,7 +72,6 @@ export function resolveNodeEntry(pkgDir) {
88
72
  }
89
73
  return p;
90
74
  }
91
- // 2. Derive from the GJS entry.
92
75
  const gjsEntry = pkg.gjsify?.main ?? (pkg.gjsify?.bin ? Object.values(pkg.gjsify.bin)[0] : undefined) ?? pkg.main;
93
76
  if (!gjsEntry) {
94
77
  throw new Error(`package "${pkg.name ?? pkgDir}" has no entry to derive a node bundle from — ` +
@@ -2,20 +2,15 @@ export interface ResolveNpmPackageOptions {
2
2
  /** Anchor directory to search from (typically the caller's cwd). */
3
3
  cwd?: string;
4
4
  /**
5
- * Bundle path or module URL to anchor the bundle-side createRequire
6
- * at. Pass `import.meta.url` from the call site so the bundle's
7
- * own node_modules chain stays reachable even when the CLI is
8
- * invoked from an unrelated cwd.
5
+ * Bundle path or module URL to anchor the bundle-side createRequire at. Pass
6
+ * `import.meta.url` from the call site so the bundle's own node_modules chain stays
7
+ * reachable even when the CLI is invoked from an unrelated cwd.
9
8
  */
10
9
  bundleUrl?: string;
11
10
  }
12
11
  /**
13
- * Resolve a bare npm specifier through multiple createRequire anchors.
14
- * Returns the absolute filesystem path of the resolved module, or null
15
- * if no anchor succeeded.
16
- *
17
- * Each anchor is tried in priority order — `GJSIFY_NODE_PATH` (highest)
18
- * → caller cwd → workspace root → bundle URL → parent-dir walk from
19
- * cwd. The first anchor whose `require.resolve()` succeeds wins.
12
+ * Resolve a bare npm specifier through the anchors listed above, returning the resolved
13
+ * module's absolute path or null when every anchor failed. Callers usually wrap the result in
14
+ * `pathToFileURL().href` before a dynamic `import(...)`.
20
15
  */
21
16
  export declare function resolveNpmPackage(specifier: string, opts?: ResolveNpmPackageOptions): string | null;
@@ -1,88 +1,59 @@
1
1
  // Multi-anchor npm-package resolver for the GJS-bundled CLI.
2
2
  //
3
- // GJS's native ESM loader has no node_modules walker — a bare
4
- // `await import('rolldown')` from inside the bundle throws
5
- // `ImportError: Module not found: rolldown` even when the package is
6
- // physically present in a node_modules above the caller's cwd.
3
+ // GJS's native ESM loader has no node_modules walker — a bare `await import('rolldown')`
4
+ // from inside the bundle throws `ImportError: Module not found: rolldown` even when the
5
+ // package is physically present in a node_modules above the caller's cwd.
7
6
  //
8
- // `createRequire(import.meta.url)` works when invoked under Node, and
9
- // when invoked under GJS *with* `@gjsify/module`'s polyfill it walks the
10
- // node_modules chain from the URL it was anchored at. But it walks
11
- // only ONE chain — the one rooted at the anchor's parent. When the
12
- // bundle lives at `<install>/dist/cli.gjs.mjs` and the user runs
13
- // `gjs -m <install>/dist/cli.gjs.mjs build …` from a completely
14
- // unrelated cwd (`/tmp/sandbox/`) whose `node_modules` is the only
15
- // place rolldown is present, anchoring on the bundle URL misses it.
7
+ // `createRequire(import.meta.url)` works under Node, and under GJS with `@gjsify/module`'s
8
+ // polyfill it walks the node_modules chain from the URL it was anchored at — but only ONE
9
+ // chain, the one rooted at the anchor's parent. When the bundle lives at
10
+ // `<install>/dist/cli.gjs.mjs` and the user runs it from an unrelated cwd whose
11
+ // `node_modules` is the only place rolldown is present, anchoring on the bundle URL misses
12
+ // it. So a bare specifier is tried against several anchors, first hit wins:
16
13
  //
17
- // This helper resolves a bare specifier against multiple anchors in
18
- // order — the first one that resolves wins:
19
- //
20
- // 1. `GJSIFY_NODE_PATH` env override (colon-separated dir list,
21
- // matches Node's NODE_PATH semantics — each entry is treated as a
22
- // synthetic `node_modules` parent).
14
+ // 1. `GJSIFY_NODE_PATH` env override (NODE_PATH semantics — each entry is a synthetic
15
+ // `node_modules` parent).
23
16
  // 2. Caller-supplied anchor dir (typically process.cwd()).
24
- // 3. Workspace root from `findWorkspaceRoot(anchorDir)` — finds the
25
- // monorepo top when invoked from a sub-package directory.
17
+ // 3. Workspace root from `findWorkspaceRoot(anchorDir)` — the monorepo top when invoked
18
+ // from a sub-package directory.
26
19
  // 4. Bundle path's parent chain (`import.meta.url` of THIS module).
27
- // 5. Parent dirs of the anchor (fallback for nested-without-workspace
28
- // setups — matches `oxc-resolve.ts`'s walker).
29
- //
30
- // Returns the absolute path on success (the caller usually wraps it in
31
- // `pathToFileURL().href` before passing to dynamic `import(...)`).
32
- // Returns null when every anchor failed.
20
+ // 5. Parent dirs of the anchor (nested-without-workspace layouts).
33
21
  //
34
- // Reference: `packages/infra/cli/src/commands/tsc.ts` (workspace-root +
35
- // cwd anchoring precedent) and `packages/infra/cli/src/utils/oxc-resolve.ts`
36
- // (parent-dir walker precedent).
22
+ // Precedents: `commands/tsc.ts` (workspace-root + cwd anchoring), `utils/oxc-resolve.ts`
23
+ // (parent-dir walker).
37
24
  import { createRequire } from 'node:module';
38
25
  import { pathToFileURL } from 'node:url';
39
26
  import { delimiter, join, resolve } from 'node:path';
40
27
  import { findWorkspaceRoot } from './workspace-root.js';
41
28
  /**
42
- * Resolve a bare npm specifier through multiple createRequire anchors.
43
- * Returns the absolute filesystem path of the resolved module, or null
44
- * if no anchor succeeded.
45
- *
46
- * Each anchor is tried in priority order — `GJSIFY_NODE_PATH` (highest)
47
- * → caller cwd → workspace root → bundle URL → parent-dir walk from
48
- * cwd. The first anchor whose `require.resolve()` succeeds wins.
29
+ * Resolve a bare npm specifier through the anchors listed above, returning the resolved
30
+ * module's absolute path or null when every anchor failed. Callers usually wrap the result in
31
+ * `pathToFileURL().href` before a dynamic `import(...)`.
49
32
  */
50
33
  export function resolveNpmPackage(specifier, opts = {}) {
51
34
  const cwd = opts.cwd ?? process.cwd();
52
- // 1. GJSIFY_NODE_PATH env override (NODE_PATH-like) — each entry is
53
- // treated as a directory whose own `node_modules` participates
54
- // in the lookup. Mirrors Node's NODE_PATH semantics.
55
35
  const envPath = process.env['GJSIFY_NODE_PATH'];
56
36
  if (envPath) {
57
- // `path.delimiter`, not `':'` — Node's own NODE_PATH uses `;` on
58
- // Windows, and this override claims to mirror NODE_PATH semantics. Split
59
- // on a colon there and even a SINGLE entry is destroyed: `C:\tools`
60
- // becomes `['C', '\tools']`, neither of which resolves, so the documented
61
- // override silently did nothing on Windows and fell through to anchor 2.
62
- // `commands/run.ts` and `utils/run-lifecycle-script.ts` already get this
63
- // right; this was the site that was missed.
37
+ // `path.delimiter`, not `':'` — NODE_PATH uses `;` on Windows, where splitting on a
38
+ // colon destroys even a SINGLE entry (`C:\tools` → `['C', '\tools']`), so the
39
+ // documented override silently did nothing there and fell through to anchor 2.
64
40
  for (const dir of envPath.split(delimiter).filter(Boolean)) {
65
41
  const hit = tryResolveFromDir(specifier, resolve(dir));
66
42
  if (hit)
67
43
  return hit;
68
44
  }
69
45
  }
70
- // 2. Caller cwd → walk up for nearest node_modules/<pkg>.
71
46
  const fromCwd = tryResolveFromDir(specifier, cwd);
72
47
  if (fromCwd)
73
48
  return fromCwd;
74
- // 3. Workspace root (monorepo top, if any).
75
49
  const wsRoot = findWorkspaceRoot(cwd);
76
50
  if (wsRoot && wsRoot !== cwd) {
77
51
  const fromWsRoot = tryResolveFromDir(specifier, wsRoot);
78
52
  if (fromWsRoot)
79
53
  return fromWsRoot;
80
54
  }
81
- // 4. Bundle path's own parent chain — anchored at the bundle URL
82
- // so the install-time node_modules layout (e.g.
83
- // `<install>/node_modules/rolldown` next to the CLI bundle)
84
- // is reachable even when the user runs the bundle directly from
85
- // an unrelated cwd.
55
+ // Anchored at the bundle URL so the install-time layout (`<install>/node_modules/rolldown`
56
+ // next to the CLI bundle) stays reachable when the bundle is run from an unrelated cwd.
86
57
  if (opts.bundleUrl) {
87
58
  try {
88
59
  const req = createRequire(opts.bundleUrl);
@@ -92,11 +63,8 @@ export function resolveNpmPackage(specifier, opts = {}) {
92
63
  // Fall through.
93
64
  }
94
65
  }
95
- // 5. Parent-dir walk from cwd as a last resort (nested
96
- // package layouts without a workspace marker). Capped at 8
97
- // levels to avoid runaway filesystem walks on pathological
98
- // layouts. Matches the depth `oxc-resolve.ts` uses for its
99
- // equivalent walker.
66
+ // Last resort for nested layouts without a workspace marker. Depth-capped so a
67
+ // pathological layout cannot turn a failed resolve into a filesystem walk to `/`.
100
68
  let dir = resolve(cwd, '..');
101
69
  for (let i = 0; i < 8; i++) {
102
70
  const hit = tryResolveFromDir(specifier, dir);
@@ -110,11 +78,9 @@ export function resolveNpmPackage(specifier, opts = {}) {
110
78
  return null;
111
79
  }
112
80
  /**
113
- * Attempt to resolve `specifier` as if a fictional source file at
114
- * `<dir>/__gjsify_resolve__.js` were doing the lookup. createRequire's
115
- * algorithm walks up from the anchor's parent for `node_modules/<pkg>`
116
- * so seeding it inside `<dir>` makes `<dir>/node_modules/<pkg>` the
117
- * first hit.
81
+ * Resolve `specifier` as if a fictional source file at `<dir>/__gjsify_resolve__.js` were
82
+ * doing the lookup: createRequire walks up from the anchor's PARENT for `node_modules/<pkg>`,
83
+ * so seeding it inside `<dir>` makes `<dir>/node_modules/<pkg>` the first hit.
118
84
  */
119
85
  function tryResolveFromDir(specifier, dir) {
120
86
  try {
@@ -10,32 +10,24 @@ export interface PluginByName {
10
10
  /**
11
11
  * Strategy for turning a resolved plugin file path into its module namespace.
12
12
  *
13
- * On Node this is a plain dynamic `import()`. Under GJS it cannot be — GJS's
14
- * native ESM loader does not follow npm `package.json#exports` subpath maps
15
- * for bare specifiers, so importing a plugin whose source does
16
- * `import … from '@scope/pkg/internal-subpath'` throws
17
- * `Module not found: @scope/pkg/internal-subpath` even when the file is on
18
- * disk. The CLI injects a GJS strategy that first bundles the plugin to a
19
- * single self-contained ESM file (Rolldown resolves the exports map at
20
- * bundle time), then imports that. See `BuildAction.loadPluginViaGjsBundle`.
13
+ * A plain dynamic `import()` on Node, but under GJS it cannot be: GJS's native ESM loader does not
14
+ * follow npm `package.json#exports` subpath maps for bare specifiers, so a plugin whose source
15
+ * does `import … from '@scope/pkg/internal-subpath'` throws `Module not found` even with the file
16
+ * on disk. The CLI injects a GJS strategy that first bundles the plugin to one self-contained ESM
17
+ * file (Rolldown resolves the exports map at bundle time), then imports that — see
18
+ * `BuildAction.loadPluginViaGjsBundle`.
21
19
  */
22
20
  export type LoadPluginModule = (resolvedPath: string, pluginName: string) => Promise<Record<string, unknown>>;
23
21
  export interface ResolveUserPluginsOptions {
24
- /**
25
- * Override how a resolved plugin path is turned into its module
26
- * namespace. Defaults to a direct dynamic `import()` (correct on Node).
27
- */
22
+ /** Defaults to a direct dynamic `import()`, which is correct on Node. */
28
23
  loadModule?: LoadPluginModule;
29
24
  }
30
25
  /** Type-guard: a `PluginByName` shape rather than a Rolldown plugin object. */
31
26
  export declare function isPluginByName(value: unknown): value is PluginByName;
32
27
  /**
33
- * Resolve a list of mixed user plugins. Entries that are already plugin
34
- * objects pass through unchanged; entries shaped like `PluginByName` get
35
- * dynamically imported, instantiated with their `options`, and returned in
36
- * the same position. Resolution is anchored at `projectDir`.
37
- *
38
- * Throws when a name fails to resolve, when the chosen export is not a
39
- * function, or when the factory returns nothing.
28
+ * Resolve a list of mixed user plugins, anchored at `projectDir`. Plugin objects pass through
29
+ * unchanged; `PluginByName` entries are imported, instantiated with their `options`, and returned
30
+ * in the same position. Throws when a name fails to resolve, the chosen export is not a function,
31
+ * or the factory returns nothing.
40
32
  */
41
33
  export declare function resolveUserPlugins(plugins: ReadonlyArray<RolldownPluginOption | PluginByName>, projectDir: string, options?: ResolveUserPluginsOptions): Promise<RolldownPluginOption[]>;