@gjsify/cli 0.49.0 → 0.50.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.
@@ -450,7 +450,21 @@ export const FORMATS = {
450
450
  // is a download: it lands in a browser's downloads folder beside other
451
451
  // files, so it carries the version and the arch, and it avoids the spaces
452
452
  // a display name may contain.
453
- fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.${archLabel}.zip`,
453
+ //
454
+ // AND THE OPERATING SYSTEM, which the extension does not name. Every other
455
+ // row's suffix names exactly one platform — `.deb`, `.rpm`, `.flatpak`,
456
+ // `.dmg`, `.msi` — and `.zip` names none, so this row and `windows-dir-zip`
457
+ // are the only two artifacts in the table a user cannot tell apart by
458
+ // looking at them. Before this token they were separated by nothing but a
459
+ // coincidence between two unrelated arch tables (`MACOS_ARCH` maps x64 to
460
+ // `x86_64`, `WINDOWS_ARCH` to `x64`), which put a bare `…-1.arm64.zip` on a
461
+ // release page beside a Windows zip with no way to choose — and would have
462
+ // had the two formats silently overwrite each other in `ship/out/` the day
463
+ // a Windows/arm64 row became possible, an upstream blocker (#1117) being
464
+ // all that stood in the way. `macos`, not `darwin`, for the same reason
465
+ // `MACOS_ARCH` writes `x86_64` rather than `x64`: this label is read by
466
+ // whoever downloads the file, never by `process.platform`.
467
+ fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.macos.${archLabel}.zip`,
454
468
  artifactKind: 'file',
455
469
  },
456
470
  // ── macOS (#1354 M4) ─────────────────────────────────────────────────
@@ -661,7 +675,14 @@ export const FORMATS = {
661
675
  // pair splits them: this one is a download that lands in a browser's
662
676
  // folder beside other files, so it carries the version and the arch and
663
677
  // avoids the spaces a display name may contain.
664
- fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.${archLabel}.zip`,
678
+ //
679
+ // …and the OS, for the reason spelled out on the `macos-app-zip` row it was
680
+ // copied from: `.zip` is the one container this table puts on more than one
681
+ // operating system, so it is the one name that has to say which. `windows`
682
+ // rather than `win32` — the spelling is the user's, like the `x64` this row
683
+ // keeps because Node's own archives, gvsbuild's assets and the runner label
684
+ // all use it.
685
+ fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.windows.${archLabel}.zip`,
665
686
  artifactKind: 'file',
666
687
  },
667
688
  // ── The Windows installer (#1354 M5) ─────────────────────────────────
@@ -1,2 +1,31 @@
1
- /** gzip with the header's timestamp zeroed and the OS byte pinned to Unix. */
2
- export declare function gzipDeterministic(input: Uint8Array): Promise<Uint8Array>;
1
+ /**
2
+ * gzip with the header's timestamp zeroed and the OS byte pinned to Unix.
3
+ *
4
+ * @param level 0-9, or omitted for zlib's default. THE ONLY HONEST WAY TO SET XFL:
5
+ * byte 8 of the header records how hard the compressor tried (2 = maximum,
6
+ * 4 = fastest, 0 = neither), `lintian` reads it to raise
7
+ * `changelog-not-compressed-with-max-compression`, and `file` prints it as
8
+ * "max compression". Stamping that byte here beside the mtime and OS bytes would
9
+ * be a different thing entirely and was rejected for it: mtime and OS are facts
10
+ * about the build ENVIRONMENT, which a reproducible build is entitled to
11
+ * normalise, while XFL is a statement about the compression actually performed.
12
+ * Writing a 2 we had not earned would make the artifact lie to the tool reading
13
+ * it. So the level travels down to the compressor instead.
14
+ *
15
+ * AND THE TEMPTATION IS REAL, which is why this is worth writing down: measured
16
+ * on gjsify's own `.deb`, the changelog member compressed at the default level
17
+ * and at 9 differs in EXACTLY ONE BYTE — position 9, XFL, 0 against 2 — and is
18
+ * 1152 bytes either way. For this input, stamping the byte would have produced
19
+ * the identical artifact. It is identical by coincidence of a small input: over
20
+ * this repo's full `CHANGELOG.md` the same two levels differ by thousands of
21
+ * bytes. A rule that happens to hold on today's input is not the rule.
22
+ */
23
+ export declare function gzipDeterministic(input: Uint8Array, level?: number): Promise<Uint8Array>;
24
+ /**
25
+ * `gzip -9`, the level Debian Policy § 4.4 asks of a packaging changelog.
26
+ *
27
+ * Named rather than spelled `9` at the call site so the reason travels with the
28
+ * number: the `-n` half of Policy's `gzip -9 -n` is what {@link gzipDeterministic}
29
+ * already does to bytes 4-7.
30
+ */
31
+ export declare const POLICY_MAX_COMPRESSION = 9;
@@ -5,10 +5,54 @@
5
5
  // input produce different bytes — which no test notices, because every test
6
6
  // compares the DECOMPRESSED content. It is the single most-missed
7
7
  // reproducibility bug in package writers, and it costs nothing to close.
8
+ //
9
+ // WHAT "DETERMINISTIC" DOES NOT COVER, measured 2026-09-11 and named here because
10
+ // the function's own name overstates it: the bytes are reproducible for a given
11
+ // HOST, not for a given artifact. `@gjsify/tar`'s gzip runs on the platform's
12
+ // zlib, and the platforms do not ship the same one — Fedora's libz is
13
+ // `zlib-ng-compat` (2.3.3) while Node bundles its own zlib (1.3.2.1-motley). Over
14
+ // this repo's `CHANGELOG.md` (876 192 bytes) at the DEFAULT level they produce
15
+ // 272 003 and 272 000 bytes respectively, so a `.deb` packed under GJS and one
16
+ // packed under Node already differ, today, before any level argument exists.
17
+ // Levels 1-4 differ too, in the other direction (zlib-ng is smaller). Two
18
+ // consequences worth keeping:
19
+ //
20
+ // * `tests/e2e/ship-from-stage` asserts byte-equality between a direct pack and
21
+ // a `--from-stage` pack, and it holds because both run on ONE host. It is
22
+ // structurally blind to this, and a cross-host pack is exactly what
23
+ // `--from-stage` exists to enable.
24
+ // * zlib-ng's level 9 is WORSE than its level 8 on large inputs — 277 974 vs
25
+ // 270 255 bytes on the file above, ~2.9 % bigger — which is why `level: 9` is
26
+ // asked for only where a reader demands it (see below) and not blanket-applied
27
+ // to the payload tarballs.
28
+ //
29
+ // Ledgered in `status/open-todos.md`; closing it means pinning ONE deflate
30
+ // implementation, which is a larger decision than this file.
8
31
  import { gzip } from '@gjsify/tar';
9
- /** gzip with the header's timestamp zeroed and the OS byte pinned to Unix. */
10
- export async function gzipDeterministic(input) {
11
- const out = await gzip(input);
32
+ /**
33
+ * gzip with the header's timestamp zeroed and the OS byte pinned to Unix.
34
+ *
35
+ * @param level 0-9, or omitted for zlib's default. THE ONLY HONEST WAY TO SET XFL:
36
+ * byte 8 of the header records how hard the compressor tried (2 = maximum,
37
+ * 4 = fastest, 0 = neither), `lintian` reads it to raise
38
+ * `changelog-not-compressed-with-max-compression`, and `file` prints it as
39
+ * "max compression". Stamping that byte here beside the mtime and OS bytes would
40
+ * be a different thing entirely and was rejected for it: mtime and OS are facts
41
+ * about the build ENVIRONMENT, which a reproducible build is entitled to
42
+ * normalise, while XFL is a statement about the compression actually performed.
43
+ * Writing a 2 we had not earned would make the artifact lie to the tool reading
44
+ * it. So the level travels down to the compressor instead.
45
+ *
46
+ * AND THE TEMPTATION IS REAL, which is why this is worth writing down: measured
47
+ * on gjsify's own `.deb`, the changelog member compressed at the default level
48
+ * and at 9 differs in EXACTLY ONE BYTE — position 9, XFL, 0 against 2 — and is
49
+ * 1152 bytes either way. For this input, stamping the byte would have produced
50
+ * the identical artifact. It is identical by coincidence of a small input: over
51
+ * this repo's full `CHANGELOG.md` the same two levels differ by thousands of
52
+ * bytes. A rule that happens to hold on today's input is not the rule.
53
+ */
54
+ export async function gzipDeterministic(input, level) {
55
+ const out = await gzip(input, level === undefined ? undefined : { level });
12
56
  if (out.byteLength < 10 || out[0] !== 0x1f || out[1] !== 0x8b) {
13
57
  throw new Error('gjsify ship: internal error — gzip did not produce a gzip stream.');
14
58
  }
@@ -19,3 +63,11 @@ export async function gzipDeterministic(input) {
19
63
  out[9] = 3;
20
64
  return out;
21
65
  }
66
+ /**
67
+ * `gzip -9`, the level Debian Policy § 4.4 asks of a packaging changelog.
68
+ *
69
+ * Named rather than spelled `9` at the call site so the reason travels with the
70
+ * number: the `-n` half of Policy's `gzip -9 -n` is what {@link gzipDeterministic}
71
+ * already does to bytes 4-7.
72
+ */
73
+ export const POLICY_MAX_COMPRESSION = 9;
@@ -67,11 +67,39 @@ export declare function planOverlay(settings: ShipSettings, format: FormatDescri
67
67
  */
68
68
  export declare function assertOverlayIsLicensed(formatId: FormatId, binaryName: string, overlay: readonly StagedFile[]): void;
69
69
  /**
70
- * The hicolor subdirectory for an icon: `scalable` for SVG, otherwise the
71
- * pixel size read from a `<n>x<n>` path component or a trailing number in the
72
- * filename.
70
+ * Whether an icon belongs in the theme's SYMBOLIC context rather than at a size.
71
+ *
72
+ * `symbolic` is a directory of its own in a hicolor theme and NOT a size value.
73
+ * Measured in `refs/adwaita-icon-theme/index.theme`, which lists `symbolic/apps`
74
+ * in `Directories=` beside `scalable/apps` and `16x16/apps` and gives it its own
75
+ * section — `Context=Applications`, `Size=16`, `MinSize=8`, `MaxSize=512`,
76
+ * `Type=Scalable` — against the scalable row's `Size=128`. The freedesktop icon
77
+ * theme specification has no notion of `symbolic` at all: a theme expresses it by
78
+ * giving the directory a section, which is exactly why reading the EXTENSION
79
+ * cannot see it, and why this question has to be asked before the size one.
80
+ *
81
+ * TWO SIGNALS, the same pair `iconThemeDir` already reads for a size: the
82
+ * directory an author put the file in, and the name they gave it. GTK resolves a
83
+ * symbolic icon by the `-symbolic` name suffix, so an author who wrote the name
84
+ * has said as much as one who wrote the path.
85
+ *
86
+ * SVG ONLY, because the symbolic directory is declared `Type=Scalable`: the
87
+ * raster form GTK's own `gtk-encode-symbolic-svg` produces
88
+ * (`<name>-symbolic.symbolic.png`) is installed at a SIZE instead. So a PNG keeps
89
+ * answering the size question, and one that cannot answer it stays refused rather
90
+ * than quietly becoming a scalable icon that does not scale.
91
+ */
92
+ export declare function isSymbolicIcon(iconPath: string): boolean;
93
+ /**
94
+ * The hicolor subdirectory for an icon: the `symbolic` CONTEXT, else `scalable`
95
+ * for an SVG, else the pixel size read from a `<n>x<n>` path component or a
96
+ * trailing number in the filename.
97
+ *
98
+ * Named for the theme DIRECTORY rather than a size from the moment the first of
99
+ * those answers stopped being one: `iconSizeDir` returning `symbolic` would be a
100
+ * name every caller has to read past.
73
101
  */
74
- export declare function iconSizeDir(iconPath: string): string;
102
+ export declare function iconThemeDir(iconPath: string): string;
75
103
  /**
76
104
  * A native module inside the payload has to keep its executable bit.
77
105
  *
@@ -3,7 +3,7 @@
3
3
  // as `utils/copy-targets.ts`, and for the same reason: a stager that discovers
4
4
  // its own inputs can only be tested by building a real project.
5
5
  import { renderDebianChangelog } from './changelog.js';
6
- import { gzipDeterministic } from './gzip.js';
6
+ import { gzipDeterministic, POLICY_MAX_COMPRESSION } from './gzip.js';
7
7
  import { renderMimePackage } from './mime.js';
8
8
  import { SHARE } from './share-dirs.js';
9
9
  import { basename, extname, posix } from 'node:path';
@@ -174,12 +174,19 @@ export async function planOverlay(settings, format, inputs) {
174
174
  // gzip performed on the packing host would put a second compressor's output in
175
175
  // an artifact whose every other byte came from the first. `gzipDeterministic`
176
176
  // zeroes the header stamp for the usual reason (utils/ship/gzip.ts).
177
+ //
178
+ // It is also the one gzip member in the artifact whose bytes cannot vary with
179
+ // the packing host, for the same reason: the two hosts' zlibs disagree (that
180
+ // file's header measurement), and this member is compressed once, here.
177
181
  if (format.changelogDest !== undefined) {
178
182
  const changelog = renderDebianChangelog(settings, inputs.changelogText, inputs.mtime);
179
183
  files.push({
180
184
  path: format.changelogDest(settings.binaryName),
181
185
  mode: 0o644,
182
- source: { kind: 'bytes', data: await gzipDeterministic(new TextEncoder().encode(changelog)) },
186
+ source: {
187
+ kind: 'bytes',
188
+ data: await gzipDeterministic(new TextEncoder().encode(changelog), POLICY_MAX_COMPRESSION),
189
+ },
183
190
  });
184
191
  }
185
192
  return files;
@@ -233,19 +240,27 @@ function renderDebianCopyright(settings, licenseText) {
233
240
  * Icons land in the hicolor theme, named after the app id — that name is what
234
241
  * the desktop entry's `Icon=` and the MetaInfo `<id>` both point at, so a file
235
242
  * keeping its source basename installs an icon nothing ever looks up.
243
+ *
244
+ * A SYMBOLIC icon keeps the `-symbolic` half of that name, and for the same
245
+ * reason rather than as an exception: the name GTK resolves a symbolic icon by
246
+ * is `<app id>-symbolic`, so renaming the file to the bare id installs an icon
247
+ * nothing ever looks up — one sentence, applied to both lookups.
236
248
  */
237
249
  function planIcons(settings) {
238
250
  const out = [];
239
251
  const seen = new Map();
240
252
  for (const icon of settings.iconFiles) {
241
253
  const ext = extname(icon);
242
- const dir = iconSizeDir(icon);
243
- const path = `${SHARE.icons}/${dir}/apps/${settings.appId}${ext}`;
254
+ const dir = iconThemeDir(icon);
255
+ const name = isSymbolicIcon(icon) ? `${settings.appId}-symbolic` : settings.appId;
256
+ const path = `${SHARE.icons}/${dir}/apps/${name}${ext}`;
244
257
  const previous = seen.get(path);
245
258
  if (previous !== undefined) {
246
259
  throw new Error(`gjsify ship: ${icon} and ${previous} both install as ${path}. ` +
247
- 'Icon size is read from the path (`.../128x128/...`) or the filename (`icon-128.png`); ' +
248
- 'give them distinguishable sizes or point `gjsify.ship.icon` at a single file.');
260
+ 'An icon is keyed on its context and its size: `symbolic` is read from a `symbolic/` ' +
261
+ 'path component or a `-symbolic` filename, the size from the path (`.../128x128/...`) ' +
262
+ 'or the filename (`icon-128.png`). Give them distinguishable sizes, or point ' +
263
+ '`gjsify.ship.icon` at a single file.');
249
264
  }
250
265
  seen.set(path, icon);
251
266
  out.push({ path, mode: 0o644, source: { kind: 'file', path: icon } });
@@ -253,11 +268,47 @@ function planIcons(settings) {
253
268
  return out;
254
269
  }
255
270
  /**
256
- * The hicolor subdirectory for an icon: `scalable` for SVG, otherwise the
257
- * pixel size read from a `<n>x<n>` path component or a trailing number in the
258
- * filename.
271
+ * Whether an icon belongs in the theme's SYMBOLIC context rather than at a size.
272
+ *
273
+ * `symbolic` is a directory of its own in a hicolor theme and NOT a size value.
274
+ * Measured in `refs/adwaita-icon-theme/index.theme`, which lists `symbolic/apps`
275
+ * in `Directories=` beside `scalable/apps` and `16x16/apps` and gives it its own
276
+ * section — `Context=Applications`, `Size=16`, `MinSize=8`, `MaxSize=512`,
277
+ * `Type=Scalable` — against the scalable row's `Size=128`. The freedesktop icon
278
+ * theme specification has no notion of `symbolic` at all: a theme expresses it by
279
+ * giving the directory a section, which is exactly why reading the EXTENSION
280
+ * cannot see it, and why this question has to be asked before the size one.
281
+ *
282
+ * TWO SIGNALS, the same pair `iconThemeDir` already reads for a size: the
283
+ * directory an author put the file in, and the name they gave it. GTK resolves a
284
+ * symbolic icon by the `-symbolic` name suffix, so an author who wrote the name
285
+ * has said as much as one who wrote the path.
286
+ *
287
+ * SVG ONLY, because the symbolic directory is declared `Type=Scalable`: the
288
+ * raster form GTK's own `gtk-encode-symbolic-svg` produces
289
+ * (`<name>-symbolic.symbolic.png`) is installed at a SIZE instead. So a PNG keeps
290
+ * answering the size question, and one that cannot answer it stays refused rather
291
+ * than quietly becoming a scalable icon that does not scale.
292
+ */
293
+ export function isSymbolicIcon(iconPath) {
294
+ if (extname(iconPath).toLowerCase() !== '.svg')
295
+ return false;
296
+ if (/(?:^|[\\/])symbolic[\\/]/.test(iconPath))
297
+ return true;
298
+ return basename(iconPath, extname(iconPath)).endsWith('-symbolic');
299
+ }
300
+ /**
301
+ * The hicolor subdirectory for an icon: the `symbolic` CONTEXT, else `scalable`
302
+ * for an SVG, else the pixel size read from a `<n>x<n>` path component or a
303
+ * trailing number in the filename.
304
+ *
305
+ * Named for the theme DIRECTORY rather than a size from the moment the first of
306
+ * those answers stopped being one: `iconSizeDir` returning `symbolic` would be a
307
+ * name every caller has to read past.
259
308
  */
260
- export function iconSizeDir(iconPath) {
309
+ export function iconThemeDir(iconPath) {
310
+ if (isSymbolicIcon(iconPath))
311
+ return 'symbolic';
261
312
  if (extname(iconPath).toLowerCase() === '.svg')
262
313
  return 'scalable';
263
314
  const square = /(?:^|[\\/])(\d{1,4})x\1(?:[\\/]|$)/.exec(iconPath);
@@ -1,3 +1,18 @@
1
+ export interface TokenizeOptions {
2
+ /**
3
+ * Accept an unquoted `*`/`?` as an ordinary token character.
4
+ *
5
+ * Off by default, and the default is about EXECUTION: a caller that RUNS
6
+ * the tokens must not pass `*.tsbuildinfo` through unexpanded, because the
7
+ * shell it replaces would have expanded it first. A caller that only READS
8
+ * the targets — `utils/package-inputs.ts`, asking which paths a package
9
+ * declares it produces — wants the pattern itself, and `gjsify clear`
10
+ * expands the same two characters in its own last segment anyway
11
+ * (`utils/clear-targets.ts`). Nothing else moves: every operator,
12
+ * substitution, brace and tilde still returns `null`.
13
+ */
14
+ allowGlobs?: boolean;
15
+ }
1
16
  /**
2
17
  * Tokenize `cmd`, or return `null` if the string contains anything the shell
3
18
  * would treat specially — operators (`&& | ; < > &`), substitutions (`$(...)` /
@@ -7,10 +22,10 @@
7
22
  * single literal token, exactly as the shell would hand it to `gjsify` (which
8
23
  * does its own glob expansion).
9
24
  */
10
- export declare function tokenizeSimpleCommand(cmd: string): string[] | null;
25
+ export declare function tokenizeSimpleCommand(cmd: string, options?: TokenizeOptions): string[] | null;
11
26
  /**
12
27
  * The argv of a script that is a single `gjsify <subcommand> …` command — i.e.
13
28
  * the tokens after `gjsify` — or `null` when it is anything else. `null` is the
14
29
  * signal to hand the literal to a shell.
15
30
  */
16
- export declare function gjsifyCommandArgv(literal: string): string[] | null;
31
+ export declare function gjsifyCommandArgv(literal: string, options?: TokenizeOptions): string[] | null;
@@ -6,6 +6,10 @@
6
6
  // deliberately conservative in ONE direction — a `null` costs the caller its
7
7
  // fast path, a wrong token list would run a different command than the script
8
8
  // says.
9
+ /** Characters that make a string something only a shell may execute. */
10
+ const SHELL_SPECIAL = '|&;<>`$()\\\n\r*?{}[]~#!';
11
+ /** The same set minus `*` and `?` — see {@link TokenizeOptions.allowGlobs}. */
12
+ const SHELL_SPECIAL_ALLOWING_GLOBS = SHELL_SPECIAL.replace('*', '').replace('?', '');
9
13
  /**
10
14
  * Tokenize `cmd`, or return `null` if the string contains anything the shell
11
15
  * would treat specially — operators (`&& | ; < > &`), substitutions (`$(...)` /
@@ -15,7 +19,8 @@
15
19
  * single literal token, exactly as the shell would hand it to `gjsify` (which
16
20
  * does its own glob expansion).
17
21
  */
18
- export function tokenizeSimpleCommand(cmd) {
22
+ export function tokenizeSimpleCommand(cmd, options = {}) {
23
+ const special = options.allowGlobs === true ? SHELL_SPECIAL_ALLOWING_GLOBS : SHELL_SPECIAL;
19
24
  const tokens = [];
20
25
  let cur = '';
21
26
  let has = false;
@@ -53,7 +58,7 @@ export function tokenizeSimpleCommand(cmd) {
53
58
  }
54
59
  continue;
55
60
  }
56
- if ('|&;<>`$()\\\n\r*?{}[]~#!'.includes(c))
61
+ if (special.includes(c))
57
62
  return null;
58
63
  cur += c;
59
64
  has = true;
@@ -69,8 +74,8 @@ export function tokenizeSimpleCommand(cmd) {
69
74
  * the tokens after `gjsify` — or `null` when it is anything else. `null` is the
70
75
  * signal to hand the literal to a shell.
71
76
  */
72
- export function gjsifyCommandArgv(literal) {
73
- const tokens = tokenizeSimpleCommand(literal);
77
+ export function gjsifyCommandArgv(literal, options = {}) {
78
+ const tokens = tokenizeSimpleCommand(literal, options);
74
79
  if (!tokens || tokens.length < 2 || tokens[0] !== 'gjsify')
75
80
  return null;
76
81
  return tokens.slice(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,21 +142,21 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/buffer": "^0.49.0",
146
- "@gjsify/create-app": "^0.49.0",
147
- "@gjsify/node-globals": "^0.49.0",
148
- "@gjsify/node-polyfills": "^0.49.0",
149
- "@gjsify/npm-registry": "^0.49.0",
150
- "@gjsify/resolve-npm": "^0.49.0",
151
- "@gjsify/rolldown-plugin-gjsify": "^0.49.0",
152
- "@gjsify/vite-plugin-blueprint": "^0.49.0",
153
- "@gjsify/rolldown-plugin-pnp": "^0.49.0",
154
- "@gjsify/semver": "^0.49.0",
155
- "@gjsify/tar": "^0.49.0",
156
- "@gjsify/tsc": "^0.49.0",
157
- "@gjsify/utils": "^0.49.0",
158
- "@gjsify/web-polyfills": "^0.49.0",
159
- "@gjsify/workspace": "^0.49.0",
145
+ "@gjsify/buffer": "^0.50.0",
146
+ "@gjsify/create-app": "^0.50.0",
147
+ "@gjsify/node-globals": "^0.50.0",
148
+ "@gjsify/node-polyfills": "^0.50.0",
149
+ "@gjsify/npm-registry": "^0.50.0",
150
+ "@gjsify/resolve-npm": "^0.50.0",
151
+ "@gjsify/rolldown-plugin-gjsify": "^0.50.0",
152
+ "@gjsify/vite-plugin-blueprint": "^0.50.0",
153
+ "@gjsify/rolldown-plugin-pnp": "^0.50.0",
154
+ "@gjsify/semver": "^0.50.0",
155
+ "@gjsify/tar": "^0.50.0",
156
+ "@gjsify/tsc": "^0.50.0",
157
+ "@gjsify/utils": "^0.50.0",
158
+ "@gjsify/web-polyfills": "^0.50.0",
159
+ "@gjsify/workspace": "^0.50.0",
160
160
  "acorn": "^8.17.0",
161
161
  "cosmiconfig": "^9.0.2",
162
162
  "get-tsconfig": "^4.14.0",
@@ -165,13 +165,13 @@
165
165
  "yargs": "^18.0.0"
166
166
  },
167
167
  "devDependencies": {
168
- "@gjsify/unit": "^0.49.0",
168
+ "@gjsify/unit": "^0.50.0",
169
169
  "@types/yargs": "^17.0.35",
170
170
  "typescript": "^6.0.3"
171
171
  },
172
172
  "peerDependencies": {
173
- "@gjsify/oxfmt-native": "^0.49.0",
174
- "@gjsify/rolldown-native": "^0.49.0"
173
+ "@gjsify/oxfmt-native": "^0.50.0",
174
+ "@gjsify/rolldown-native": "^0.50.0"
175
175
  },
176
176
  "peerDependenciesMeta": {
177
177
  "@gjsify/oxfmt-native": {