@gjsify/cli 0.7.3 → 0.7.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.
@@ -96,6 +96,13 @@ const GLOBAL_TRIGGERS = [
96
96
  /^packages\/infra\/cli\//,
97
97
  /^packages\/infra\/rolldown-plugin-gjsify\//,
98
98
  /^packages\/infra\/resolve-npm\//,
99
+ // The test framework every spec imports. It is a *devDependency* of
100
+ // virtually every workspace, so with the prod-deps-only closure below
101
+ // (`includeDev: false`) a change to it would otherwise yield a near-empty
102
+ // closure and silently skip every downstream test — yet a bug in a matcher
103
+ // can break assertions anywhere. Treat it as a global trigger so a
104
+ // `@gjsify/unit` change re-runs the full suite.
105
+ /^packages\/gjs\/unit\//,
99
106
  // Cross-cutting dep + lockfile + root config.
100
107
  /^gjsify-lock\.json$/,
101
108
  /^package\.json$/,
@@ -210,12 +217,29 @@ function classifyAndExpand(workspaces, rootDir, changedFiles) {
210
217
  skipAll: false,
211
218
  };
212
219
  }
213
- // Default: closure walk.
214
- const reverse = buildReverseDependencyGraph(workspaces, { includeDev: true });
220
+ // Default: closure walk over PRODUCTION dependencies only (`includeDev:
221
+ // false`). The closure answers "whose tests must re-run because a package
222
+ // they depend on changed" — and that is a RUNTIME relationship. Walking
223
+ // devDependency edges instead conflated it with the build/test toolchain:
224
+ // every workspace devDepends `@gjsify/cli`/`@gjsify/tsc`/`@gjsify/unit`,
225
+ // and the umbrella `@gjsify/node-polyfills` prod-depends every polyfill, so
226
+ // ANY single-package change fanned out to ~210 of 221 workspaces — the
227
+ // closure was the whole monorepo and selective CI bought almost nothing.
228
+ // Prod-only collapses a typical seed to a handful (e.g. sqlite 210→4,
229
+ // fetch 210→34) while keeping every real runtime dependent. Safe against
230
+ // under-selection: no `*.spec.ts` imports a *cross-package* sibling via a
231
+ // devDependency (the only such spec imports are self-imports, which are
232
+ // covered by the package being its own seed); the universal test framework
233
+ // `@gjsify/unit` is handled by GLOBAL_TRIGGERS above; and every push-to-
234
+ // main + the nightly cron still run the FULL suite as the backstop.
235
+ const reverse = buildReverseDependencyGraph(workspaces, { includeDev: false });
215
236
  const closure = affectedClosure(reverse, [...matched]);
216
237
  // Integration suites whose forward deps overlap with the closure also
217
- // need to run. We walk the forward graph and pull any integration ws
218
- // that depends on something inside the closure.
238
+ // need to run. We walk the forward graph with `includeDev: true` here
239
+ // (deliberately broader than the reverse closure) so an integration suite
240
+ // is never missed: integration packages declare the pillars they exercise
241
+ // as `dependencies`, but the wider walk also tolerates a devDep-declared
242
+ // edge. Over-running an integration suite is cheap; missing one is not.
219
243
  const forward = buildDependencyGraph(workspaces, { includeDev: true });
220
244
  let runIntegration = false;
221
245
  for (const [from, deps] of forward.edges) {
@@ -6,6 +6,7 @@ interface InstallOptions {
6
6
  'save-peer'?: boolean;
7
7
  'save-optional'?: boolean;
8
8
  immutable?: boolean;
9
+ 'refresh-lockfile'?: boolean;
9
10
  verbose: boolean;
10
11
  quiet?: boolean;
11
12
  progress?: boolean;
@@ -57,6 +57,11 @@ export const installCommand = {
57
57
  description: 'CI mode: install strictly from gjsify-lock.json, fail if the lockfile is missing or stale. Equivalent to yarn --immutable / npm ci --frozen-lockfile.',
58
58
  type: 'boolean',
59
59
  default: false,
60
+ })
61
+ .option('refresh-lockfile', {
62
+ description: 'Re-resolve every dependency to the newest version satisfying its range and rewrite the lockfile (bumps in-range transitive deps). Without it, a resolve preserves versions already pinned in the lockfile and only resolves new/changed deps — the npm/yarn/pnpm default. Mirrors yarn install --mode=update-lockfile.',
63
+ type: 'boolean',
64
+ default: false,
60
65
  })
61
66
  .option('verbose', {
62
67
  description: 'Verbose install logging.',
@@ -97,6 +102,11 @@ export const installCommand = {
97
102
  console.error('gjsify install --immutable is incompatible with --global.');
98
103
  process.exit(1);
99
104
  }
105
+ if (args['refresh-lockfile']) {
106
+ console.error('gjsify install --immutable is incompatible with --refresh-lockfile ' +
107
+ '(--immutable forbids rewriting the lockfile). Drop one.');
108
+ process.exit(1);
109
+ }
100
110
  }
101
111
  if (args.global) {
102
112
  if (!args.packages || args.packages.length === 0) {
@@ -266,6 +276,7 @@ async function projectInstallNative(args, signal) {
266
276
  // it (the whole point is byte-stability under CI).
267
277
  lockfile: !args.immutable,
268
278
  frozen: args.immutable,
279
+ refreshLockfile: args['refresh-lockfile'],
269
280
  signal,
270
281
  progress,
271
282
  });
@@ -604,6 +615,7 @@ async function workspaceInstall(cwd, args, signal) {
604
615
  verbose: args.verbose,
605
616
  lockfile: !args.immutable,
606
617
  frozen: args.immutable,
618
+ refreshLockfile: args['refresh-lockfile'],
607
619
  overrides,
608
620
  signal,
609
621
  progress,
@@ -298,7 +298,22 @@ export const publishCommand = {
298
298
  process.exit(1);
299
299
  }
300
300
  // Auto-detect: fall back to the npmrc token auth that
301
- // buildPublishHeaders already resolved into `headers`.
301
+ // buildPublishHeaders already resolved into `headers` — BUT only
302
+ // if one actually exists. In OIDC-only CI there is no npmrc
303
+ // token, so a "fallback" would PUT with no credentials, get a
304
+ // 404, and risk being mistaken for a tolerable outcome — exactly
305
+ // the v0.7.3 incident where a transient OIDC 503 on
306
+ // `@gjsify/process` left it unpublished while the release stayed
307
+ // green. Refuse loudly so a transient OIDC failure can never
308
+ // silently drop a package from a release.
309
+ if (!headers['authorization']) {
310
+ const msg = err instanceof Error ? err.message : String(err);
311
+ console.error(`gjsify publish: OIDC token exchange failed for ${packed.name} (${msg}) and no fallback npm token is configured — refusing to publish without credentials.`);
312
+ if (args.json) {
313
+ process.stdout.write(`${JSON.stringify({ ok: false, name: packed.name, version: packed.version, error: 'oidc-failed-no-token' }, null, 2)}\n`);
314
+ }
315
+ process.exit(1);
316
+ }
302
317
  if (verbose) {
303
318
  const msg = err instanceof Error ? err.message : String(err);
304
319
  console.error(`gjsify publish: OIDC auto-detect failed (${msg}) — falling back to token auth`);
@@ -41,6 +41,14 @@ export const runCommand = {
41
41
  // Without this, anything after `--` lands in args._ and is
42
42
  // silently dropped by the handler.
43
43
  'populate--': true,
44
+ // Forward UNKNOWN flags to the target without requiring `--`:
45
+ // gjsify run dist/app.gjs.mjs serve --port 8080 --year 2025
46
+ // Without this, the top-level `.strict()` (cli-app.ts) rejects
47
+ // --port/--year as "unknown arguments" before they reach the
48
+ // target. Treating unknown options as positional args routes
49
+ // them into the `[args..]` positional (then to the child).
50
+ // Known options (--help / --version) are still handled by gjsify.
51
+ 'unknown-options-as-args': true,
44
52
  }),
45
53
  handler: async (args) => {
46
54
  const target = args.target;
@@ -10,5 +10,5 @@ export interface InstalledTopLevel {
10
10
  }
11
11
  export declare function installPackagesNative(opts: InstallOptions): Promise<InstalledTopLevel[]>;
12
12
  export declare function parseSpec(raw: string): ParsedSpec;
13
- export declare function pickVersion(packument: Packument, range: string): string | null;
13
+ export declare function pickVersion(packument: Packument, range: string, preferred?: string): string | null;
14
14
  export {};
@@ -58,13 +58,21 @@ export async function installPackagesNative(opts) {
58
58
  log('install: --immutable, using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
59
59
  nodes = lockfileToNodes(existingLock);
60
60
  }
61
- else if (existingLock && lockfileMatchesRequest(existingLock, opts.specs)) {
61
+ else if (!opts.refreshLockfile && existingLock && lockfileMatchesRequest(existingLock, opts.specs)) {
62
62
  log('install: using lockfile (%d package(s))', Object.keys(existingLock.packages).length);
63
63
  nodes = lockfileToNodes(existingLock);
64
64
  }
65
65
  else {
66
- log('install: resolving %d top-level spec(s) → %s', opts.specs.length, opts.prefix);
67
- nodes = await resolveDeps(opts.specs, npmrc, log, opts.overrides, opts.skipDeps, opts.signal, progress);
66
+ // A resolve has to run (new/changed/removed dep, or no lockfile yet).
67
+ // Unless --refresh-lockfile was passed, seed it with the versions
68
+ // already pinned in the existing lockfile so unchanged deps keep their
69
+ // resolved version and only the genuinely new/changed deps move — the
70
+ // npm/yarn/pnpm `install` default. Without this, every `^`-range would
71
+ // re-resolve to the newest registry version, churning the whole tree
72
+ // (and silently bumping transitive deps) on a one-package add.
73
+ const preferred = !opts.refreshLockfile && existingLock ? buildPreferredVersions(existingLock) : undefined;
74
+ log('install: resolving %d top-level spec(s) → %s%s', opts.specs.length, opts.prefix, preferred ? ` (preserving ${preferred.size} locked name(s))` : '');
75
+ nodes = await resolveDeps(opts.specs, npmrc, log, opts.overrides, opts.skipDeps, opts.signal, progress, preferred);
68
76
  if (opts.lockfile) {
69
77
  writeLockfile(lockfilePath, opts.specs, nodes);
70
78
  log('install: wrote %s (%d entries)', LOCKFILE_NAME, nodes.length);
@@ -127,7 +135,15 @@ function parseSpecName(spec) {
127
135
  * the root. Each placement returns a `ResolvedNode` whose `installPath`
128
136
  * captures where it lives in the tree.
129
137
  */
130
- async function resolveDeps(specs, npmrc, log, overrides, skipDeps, signal, progress) {
138
+ async function resolveDeps(specs, npmrc, log, overrides, skipDeps, signal, progress,
139
+ /**
140
+ * Lockfile-preservation oracle: `name → versions already pinned in the
141
+ * existing lockfile`. When an edge's range is satisfiable by a pinned
142
+ * version, that version is reused instead of the newest registry match.
143
+ * Undefined ⇒ a fresh resolve that always picks the newest in-range version
144
+ * (first install, or an explicit `--refresh-lockfile`).
145
+ */
146
+ preferredVersions) {
131
147
  progress?.beginPhase('resolve', specs.length);
132
148
  const applyOverride = (name, range) => {
133
149
  if (!overrides)
@@ -201,11 +217,14 @@ async function resolveDeps(specs, npmrc, log, overrides, skipDeps, signal, progr
201
217
  // Compatible placement reachable; reuse, no new install.
202
218
  continue;
203
219
  }
204
- // No compatible existing placement. Resolve a fresh version.
220
+ // No compatible existing placement. Resolve a version — preferring a
221
+ // version already pinned in the lockfile when it satisfies the range
222
+ // (so an add doesn't gratuitously bump unchanged deps).
205
223
  let version = null;
206
224
  try {
207
225
  const packument = await fetchPkg(edge.name);
208
- version = pickVersion(packument, edge.range);
226
+ const preferred = preferredVersionFor(preferredVersions?.get(edge.name), edge.range);
227
+ version = pickVersion(packument, edge.range, preferred);
209
228
  if (!version) {
210
229
  if (!edge.required)
211
230
  continue;
@@ -452,6 +471,44 @@ function writeLockfile(lockfilePath, specs, nodes) {
452
471
  };
453
472
  fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2) + '\n');
454
473
  }
474
+ /**
475
+ * Build the lockfile-preservation oracle: `name → every version currently
476
+ * pinned in the lockfile` (a name can appear at more than one install path /
477
+ * version when the tree nested a conflicting copy). Consulted during a resolve
478
+ * so unchanged deps keep their pinned version instead of bumping to the newest
479
+ * in-range match.
480
+ */
481
+ function buildPreferredVersions(lockfile) {
482
+ const byName = new Map();
483
+ for (const [installPath, entry] of Object.entries(lockfile.packages)) {
484
+ const name = nameFromInstallPath(installPath);
485
+ let set = byName.get(name);
486
+ if (!set) {
487
+ set = new Set();
488
+ byName.set(name, set);
489
+ }
490
+ set.add(entry.version);
491
+ }
492
+ return byName;
493
+ }
494
+ /**
495
+ * Pick the pinned version to reuse for an edge: the highest lockfile version of
496
+ * the name that still satisfies the edge's range, or undefined when none do
497
+ * (range was tightened/changed, or the name is brand new) — in which case the
498
+ * resolver falls back to the newest registry match.
499
+ */
500
+ function preferredVersionFor(locked, range) {
501
+ if (!locked || locked.size === 0)
502
+ return undefined;
503
+ let best;
504
+ for (const v of locked) {
505
+ if (!satisfiesRange(v, range))
506
+ continue;
507
+ if (best === undefined || new SemVer(v).compare(new SemVer(best)) > 0)
508
+ best = v;
509
+ }
510
+ return best;
511
+ }
455
512
  function lockfileToNodes(lockfile) {
456
513
  return Object.entries(lockfile.packages).map(([installPath, entry]) => ({
457
514
  // Recover the package name from the path: the last segment is
@@ -535,7 +592,14 @@ export function parseSpec(raw) {
535
592
  return { name: raw.slice(0, at), range: raw.slice(at + 1) || 'latest' };
536
593
  }
537
594
  // Exported for unit-testing. Internal API.
538
- export function pickVersion(packument, range) {
595
+ export function pickVersion(packument, range, preferred) {
596
+ // Lockfile preservation: if a version already pinned in the lockfile still
597
+ // satisfies the range and is still published, reuse it instead of bumping to
598
+ // the newest match. `preferred` is only ever a concrete semver (never a
599
+ // dist-tag), so this never hijacks a `latest`/`next` range.
600
+ if (preferred && packument.versions[preferred] && satisfiesRange(preferred, range)) {
601
+ return preferred;
602
+ }
539
603
  // dist-tag fast path: `latest`, `next`, ...
540
604
  if (packument['dist-tags'][range])
541
605
  return packument['dist-tags'][range];
@@ -18,6 +18,17 @@ export interface InstallOptions {
18
18
  lockfile?: boolean;
19
19
  /** Use `<prefix>/gjsify-lock.json` as the source of truth — fail if missing. */
20
20
  frozen?: boolean;
21
+ /**
22
+ * Native backend only: force a fresh re-resolution that bumps every spec to
23
+ * the newest version satisfying its range, rewriting the lockfile — the
24
+ * pre-0.7.x behaviour. By default (this flag off) a resolve that has to run
25
+ * (a new/changed/removed dep) PRESERVES the versions already pinned in the
26
+ * existing lockfile and only resolves the genuinely new/changed deps, the
27
+ * way `npm install` / `yarn install` / `pnpm install` do. Set this to
28
+ * intentionally pick up in-range updates (≈ `yarn install
29
+ * --mode=update-lockfile` / `npm update` / `pnpm update`).
30
+ */
31
+ refreshLockfile?: boolean;
21
32
  /**
22
33
  * Per-package version overrides — `<name> → <range>`. Applied to every
23
34
  * edge during dependency resolution, irrespective of the requester.
@@ -5,6 +5,19 @@ interface OidcExchangeOptions {
5
5
  registry: string;
6
6
  /** Optional verbose logger — receives single-line strings. */
7
7
  log?: (msg: string) => void;
8
+ /**
9
+ * Max retries on a TRANSIENT exchange failure (5xx / 429 / network error).
10
+ * Default 3. A transient npm-side blip (e.g. a 503 Service Unavailable on a
11
+ * single package mid-release) used to fall straight through to token auth,
12
+ * which in OIDC-only CI has no token → a 404 that broke the release while
13
+ * the workflow stayed green (the `@gjsify/process@0.7.3` v0.7.3 incident).
14
+ * Retrying the exchange keeps the publish on the OIDC path. Non-transient
15
+ * statuses (404 "package not found", 401/403 misconfig) are NOT retried —
16
+ * they're surfaced immediately so `--tolerate-untrusted-new` etc. still work.
17
+ */
18
+ maxRetries?: number;
19
+ /** Base backoff in ms for transient retries; doubles each attempt (capped 8s). Default 1000. Tests pass 0. */
20
+ retryBaseMs?: number;
8
21
  }
9
22
  export interface OidcExchangeResult {
10
23
  /** Short-lived npm token (`Authorization: Bearer <token>`-compatible). */
@@ -19,6 +19,9 @@
19
19
  //
20
20
  // Reference: refs/npm-cli/lib/utils/oidc.js
21
21
  // Original: Copyright (c) npm contributors. Artistic-2.0.
22
+ /** HTTP statuses worth retrying — npm-side transient/overload conditions. */
23
+ const TRANSIENT_EXCHANGE_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);
24
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
22
25
  export class OidcUnavailableError extends Error {
23
26
  reason;
24
27
  constructor(message, reason) {
@@ -139,32 +142,59 @@ export async function exchangeOidcForNpmToken(args) {
139
142
  : encodeURIComponent(packageName);
140
143
  const exchangeUrl = `${registryClean}/-/npm/v1/oidc/token/exchange/package/${escapedName}`;
141
144
  log?.(`gjsify oidc: POST ${exchangeUrl}`);
142
- const res = await fetch(exchangeUrl, {
143
- method: 'POST',
144
- headers: {
145
- Authorization: `Bearer ${idToken}`,
146
- 'Content-Type': 'application/json',
147
- Accept: 'application/json',
148
- },
149
- // npm's exchange endpoint accepts an empty JSON body — the JWT is
150
- // the proof, no additional claims needed from us.
151
- body: '{}',
152
- });
153
- const text = await res.text().catch(() => '');
154
- if (!res.ok) {
155
- throw new OidcExchangeError(`npm OIDC token exchange failed for ${packageName}: ${res.status} ${res.statusText} — ${text.slice(0, 300)}`, res.status, text, packageName, decodeJwtPayload(idToken) ?? undefined);
156
- }
157
- let json;
158
- try {
159
- json = JSON.parse(text);
160
- }
161
- catch {
162
- throw new OidcExchangeError(`npm OIDC token exchange returned non-JSON body for ${packageName}: ${text.slice(0, 200)}`, res.status, text, packageName);
163
- }
164
- if (!json.token) {
165
- throw new OidcExchangeError(`npm OIDC token exchange returned no \`token\` field for ${packageName}`, res.status, text, packageName);
145
+ const maxRetries = args.maxRetries ?? 3;
146
+ const retryBaseMs = args.retryBaseMs ?? 1000;
147
+ for (let attempt = 0;; attempt++) {
148
+ let res;
149
+ try {
150
+ res = await fetch(exchangeUrl, {
151
+ method: 'POST',
152
+ headers: {
153
+ Authorization: `Bearer ${idToken}`,
154
+ 'Content-Type': 'application/json',
155
+ Accept: 'application/json',
156
+ },
157
+ // npm's exchange endpoint accepts an empty JSON body — the JWT
158
+ // is the proof, no additional claims needed from us.
159
+ body: '{}',
160
+ });
161
+ }
162
+ catch (netErr) {
163
+ // Network-level failure (DNS, connection reset, TLS): transient.
164
+ if (attempt < maxRetries) {
165
+ const delay = Math.min(retryBaseMs * 2 ** attempt, 8000);
166
+ log?.(`gjsify oidc: network error for ${packageName} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms`);
167
+ await sleep(delay);
168
+ continue;
169
+ }
170
+ throw new OidcExchangeError(`npm OIDC token exchange network error for ${packageName} after ${attempt + 1} attempt(s): ${netErr instanceof Error ? netErr.message : String(netErr)}`, 0, '', packageName, decodeJwtPayload(idToken) ?? undefined);
171
+ }
172
+ const text = await res.text().catch(() => '');
173
+ if (!res.ok) {
174
+ // Retry only TRANSIENT npm-side failures; surface everything else
175
+ // (404/401/403 etc.) immediately so the caller's diagnostics —
176
+ // `--tolerate-untrusted-new`, Trusted-Publisher misconfig hints —
177
+ // still fire on the first response.
178
+ if (TRANSIENT_EXCHANGE_STATUSES.has(res.status) && attempt < maxRetries) {
179
+ const delay = Math.min(retryBaseMs * 2 ** attempt, 8000);
180
+ log?.(`gjsify oidc: transient ${res.status} ${res.statusText} for ${packageName} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms`);
181
+ await sleep(delay);
182
+ continue;
183
+ }
184
+ throw new OidcExchangeError(`npm OIDC token exchange failed for ${packageName}: ${res.status} ${res.statusText} — ${text.slice(0, 300)}`, res.status, text, packageName, decodeJwtPayload(idToken) ?? undefined);
185
+ }
186
+ let json;
187
+ try {
188
+ json = JSON.parse(text);
189
+ }
190
+ catch {
191
+ throw new OidcExchangeError(`npm OIDC token exchange returned non-JSON body for ${packageName}: ${text.slice(0, 200)}`, res.status, text, packageName);
192
+ }
193
+ if (!json.token) {
194
+ throw new OidcExchangeError(`npm OIDC token exchange returned no \`token\` field for ${packageName}`, res.status, text, packageName);
195
+ }
196
+ return json.token;
166
197
  }
167
- return json.token;
168
198
  }
169
199
  /**
170
200
  * End-to-end: probe env → fetch id-token → exchange for npm token.
@@ -30,7 +30,15 @@ export async function promptHidden(question) {
30
30
  stdin.setRawMode(true);
31
31
  stdin.resume();
32
32
  stdin.setEncoding('utf-8');
33
- const onData = (chunk) => {
33
+ // Do NOT assume `chunk` is a string: under GJS `@gjsify/process` does
34
+ // not honour `setEncoding('utf-8')` in raw mode, so each chunk arrives
35
+ // as bytes (a Buffer). Iterating bytes makes `ch === '\r'` always false
36
+ // and Enter (byte 13) coerce past `ch >= ' '` (→ `13 >= 0`) so it was
37
+ // masked as another `*` and the prompt never submitted. Normalise to a
38
+ // string first (same shape as readLine's handler), so this works on
39
+ // both Node (string) and GJS (bytes) — aligning with `npm login`.
40
+ const onData = (raw) => {
41
+ const chunk = typeof raw === 'string' ? raw : raw.toString('utf-8');
34
42
  for (const ch of chunk) {
35
43
  if (ch === '\r' || ch === '\n') {
36
44
  cleanup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -120,19 +120,19 @@
120
120
  "cli"
121
121
  ],
122
122
  "dependencies": {
123
- "@gjsify/buffer": "^0.7.3",
124
- "@gjsify/create-app": "^0.7.3",
125
- "@gjsify/node-globals": "^0.7.3",
126
- "@gjsify/node-polyfills": "^0.7.3",
127
- "@gjsify/npm-registry": "^0.7.3",
128
- "@gjsify/resolve-npm": "^0.7.3",
129
- "@gjsify/rolldown-plugin-gjsify": "^0.7.3",
130
- "@gjsify/rolldown-plugin-pnp": "^0.7.3",
131
- "@gjsify/semver": "^0.7.3",
132
- "@gjsify/tar": "^0.7.3",
133
- "@gjsify/tsc": "^0.7.3",
134
- "@gjsify/web-polyfills": "^0.7.3",
135
- "@gjsify/workspace": "^0.7.3",
123
+ "@gjsify/buffer": "^0.7.4",
124
+ "@gjsify/create-app": "^0.7.4",
125
+ "@gjsify/node-globals": "^0.7.4",
126
+ "@gjsify/node-polyfills": "^0.7.4",
127
+ "@gjsify/npm-registry": "^0.7.4",
128
+ "@gjsify/resolve-npm": "^0.7.4",
129
+ "@gjsify/rolldown-plugin-gjsify": "^0.7.4",
130
+ "@gjsify/rolldown-plugin-pnp": "^0.7.4",
131
+ "@gjsify/semver": "^0.7.4",
132
+ "@gjsify/tar": "^0.7.4",
133
+ "@gjsify/tsc": "^0.7.4",
134
+ "@gjsify/web-polyfills": "^0.7.4",
135
+ "@gjsify/workspace": "^0.7.4",
136
136
  "cosmiconfig": "^9.0.2",
137
137
  "get-tsconfig": "^4.14.0",
138
138
  "pkg-types": "^2.3.1",
@@ -140,13 +140,13 @@
140
140
  "yargs": "^18.0.0"
141
141
  },
142
142
  "devDependencies": {
143
- "@gjsify/unit": "^0.7.3",
143
+ "@gjsify/unit": "^0.7.4",
144
144
  "@types/yargs": "^17.0.35",
145
145
  "typescript": "^6.0.3"
146
146
  },
147
147
  "peerDependencies": {
148
- "@gjsify/oxfmt-native": "^0.7.3",
149
- "@gjsify/rolldown-native": "^0.7.3"
148
+ "@gjsify/oxfmt-native": "^0.7.4",
149
+ "@gjsify/rolldown-native": "^0.7.4"
150
150
  },
151
151
  "peerDependenciesMeta": {
152
152
  "@gjsify/oxfmt-native": {