@gjsify/cli 0.53.0 → 0.54.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 (37) hide show
  1. package/dist/cli.gjs.mjs +263 -248
  2. package/lib/bundler-pick.d.ts +35 -0
  3. package/lib/bundler-pick.js +74 -7
  4. package/lib/cli-app.js +3 -1
  5. package/lib/commands/blueprint/index.d.ts +4 -0
  6. package/lib/commands/blueprint/index.js +15 -0
  7. package/lib/commands/blueprint/types.d.ts +8 -0
  8. package/lib/commands/blueprint/types.js +124 -0
  9. package/lib/commands/devtools.d.ts +21 -0
  10. package/lib/commands/devtools.js +146 -0
  11. package/lib/commands/index.d.ts +2 -0
  12. package/lib/commands/index.js +2 -0
  13. package/lib/commands/ship.js +51 -8
  14. package/lib/commands/webext/index.js +1 -1
  15. package/lib/utils/oxc-resolve.js +9 -7
  16. package/lib/utils/ship/app-runtime.d.ts +7 -5
  17. package/lib/utils/ship/app-runtime.js +11 -8
  18. package/lib/utils/ship/appimage.d.ts +9 -0
  19. package/lib/utils/ship/appimage.js +2 -1
  20. package/lib/utils/ship/deb.d.ts +10 -0
  21. package/lib/utils/ship/deb.js +7 -0
  22. package/lib/utils/ship/depends.d.ts +37 -3
  23. package/lib/utils/ship/depends.js +21 -9
  24. package/lib/utils/ship/gi-namespaces.d.ts +16 -0
  25. package/lib/utils/ship/gi-namespaces.js +64 -1
  26. package/lib/utils/ship/launcher.js +2 -2
  27. package/lib/utils/ship/layout.js +25 -2
  28. package/lib/utils/ship/rpm.d.ts +5 -0
  29. package/lib/utils/ship/rpm.js +42 -0
  30. package/lib/utils/ship/settings.js +6 -0
  31. package/lib/utils/ship/signing.d.ts +7 -2
  32. package/lib/utils/ship/signing.js +59 -32
  33. package/lib/utils/ship/stage-manifest.d.ts +14 -0
  34. package/lib/utils/ship/stage-manifest.js +7 -0
  35. package/lib/utils/webext/targets.d.ts +1 -1
  36. package/lib/utils/webext/targets.js +20 -8
  37. package/package.json +20 -19
@@ -5,6 +5,11 @@ export interface RpmInputs {
5
5
  payload: readonly PayloadEntry[];
6
6
  prefix: string;
7
7
  depends: readonly string[];
8
+ /**
9
+ * Soft dependencies, written as the `RECOMMEND*` tags — an `&optional` typelib
10
+ * (ADR 0087), which the package installs with and runs without.
11
+ */
12
+ recommends?: readonly string[];
8
13
  /** RPM architecture (`x86_64`, `noarch`, …). */
9
14
  archLabel: string;
10
15
  mtime: number;
@@ -54,6 +54,17 @@ const TAG = {
54
54
  REQUIREFLAGS: 1048,
55
55
  REQUIRENAME: 1049,
56
56
  REQUIREVERSION: 1050,
57
+ // The recommend trio. NOT the 1099/1046/1047 of rpm ≤ 4.18, which shared ONE
58
+ // index space with `REQUIRENAME`; since 4.19 each dependency tag carries its
59
+ // own arrays. MEASURED against a package `rpmbuild` produced and read back with
60
+ // `rpm` 6.0.2: RECOMMENDNAME is a STRING_ARRAY of its own right after
61
+ // PROVIDEVERSION, not an extension of the four REQUIRE entries. The numbers are
62
+ // in the 5000s for that reason and a wrong one is invisible here — rpm either
63
+ // ignores the tag or refuses the header, and `rpm -qp --recommends` is what
64
+ // says which (e2e `ship`).
65
+ RECOMMENDNAME: 5046,
66
+ RECOMMENDVERSION: 5047,
67
+ RECOMMENDFLAGS: 5048,
57
68
  RPMVERSION: 1064,
58
69
  POSTINPROG: 1086,
59
70
  POSTUNPROG: 1088,
@@ -225,6 +236,10 @@ function mainHeaderEntries(inputs, files, cpio, compressed) {
225
236
  // From the PAYLOAD, not the settings — see the same call in `deb.ts`.
226
237
  const scripts = renderRpmScriptlets(readPayloadFacts(inputs.payload), inputs.prefix);
227
238
  const requires = buildRequires(inputs.depends, scripts, readShebangInterpreters(inputs.payload));
239
+ // OMITTED, not written empty: rpm rejects a zero-count array (the same rule
240
+ // `encodeValue` enforces), so a package with no `&optional` namespace carries
241
+ // no RECOMMEND tag at all.
242
+ const recommends = buildRecommends(inputs.recommends ?? []);
228
243
  const entries = [
229
244
  { tag: TAG.HEADERI18NTABLE, type: RpmType.STRING_ARRAY, value: ['C'] },
230
245
  { tag: TAG.NAME, type: RpmType.STRING, value: settings.binaryName },
@@ -277,6 +292,13 @@ function mainHeaderEntries(inputs, files, cpio, compressed) {
277
292
  { tag: TAG.REQUIRENAME, type: RpmType.STRING_ARRAY, value: requires.names },
278
293
  { tag: TAG.REQUIREFLAGS, type: RpmType.INT32, value: requires.flags },
279
294
  { tag: TAG.REQUIREVERSION, type: RpmType.STRING_ARRAY, value: requires.versions },
295
+ ...(recommends.names.length > 0
296
+ ? [
297
+ { tag: TAG.RECOMMENDNAME, type: RpmType.STRING_ARRAY, value: recommends.names },
298
+ { tag: TAG.RECOMMENDFLAGS, type: RpmType.INT32, value: recommends.flags },
299
+ { tag: TAG.RECOMMENDVERSION, type: RpmType.STRING_ARRAY, value: recommends.versions },
300
+ ]
301
+ : []),
280
302
  { tag: TAG.PAYLOADFORMAT, type: RpmType.STRING, value: 'cpio' },
281
303
  { tag: TAG.PAYLOADCOMPRESSOR, type: RpmType.STRING, value: 'gzip' },
282
304
  // Write-side metadata only — no reader acts on it (rpm-format.md § 6.5).
@@ -383,6 +405,26 @@ function buildRequires(depends, scripts, interpreters) {
383
405
  }
384
406
  return { names, flags, versions };
385
407
  }
408
+ /**
409
+ * The three parallel `Recommends` arrays — the soft half of
410
+ * {@link buildRequires}, same relation spelling, no `RPMLIB` and no sense bits.
411
+ *
412
+ * `0` is the right flags value and not a missing one: rpm reads an unversioned
413
+ * recommendation as "any version", which is what an `&optional` typelib package
414
+ * means (the app asks for the NAMESPACE, never for a version of the package).
415
+ */
416
+ function buildRecommends(recommends) {
417
+ const names = [];
418
+ const flags = [];
419
+ const versions = [];
420
+ for (const depend of recommends) {
421
+ const parsed = parseDepend(depend);
422
+ names.push(parsed.name);
423
+ flags.push(senseFor(parsed.relation));
424
+ versions.push(parsed.version ?? '');
425
+ }
426
+ return { names, flags, versions };
427
+ }
386
428
  function senseFor(relation) {
387
429
  switch (relation) {
388
430
  case '>=':
@@ -166,6 +166,12 @@ export function resolveShipSettings(input) {
166
166
  throw new Error('gjsify ship: no licence — set `license` in package.json (an SPDX id, e.g. "MIT") ' +
167
167
  'or `gjsify.ship.license.project`. Both deb and rpm carry it as a required field.');
168
168
  }
169
+ // Written BACK, not read twice: the packers read `settings.license` and the AppStream
170
+ // renderers read `metadata.license.project`, so resolving the `package.json#license`
171
+ // fallback into the first alone left `<project_license>` empty in the staged metainfo
172
+ // and warned about a field the project had answered. The returned metadata is what every
173
+ // renderer reads, so this is where the one answer has to land.
174
+ metadata.license = { ...metadata.license, project: license };
169
175
  const maintainer = ship.maintainer ?? formatAuthor(pkg.author) ?? formatDeveloper(metadata);
170
176
  if (!maintainer) {
171
177
  throw new Error('gjsify ship: no maintainer — set `author` in package.json ("Name <you@example.com>"), ' +
@@ -30,14 +30,19 @@ export interface SignerDescriptor {
30
30
  /** argv after {@link tool}, for ONE file. */
31
31
  args: (input: SignArgs) => readonly string[];
32
32
  /**
33
- * The entitlements this signer asks for, or `[]` where the OS has none.
33
+ * The entitlements this signer asks for, given the identity in hand.
34
+ *
35
+ * A FUNCTION, not a list, and the darwin row's second argument is what makes
36
+ * that necessary: the set depends on whether the identity is a real one, and
37
+ * one entitlement is exactly wrong in one of the two directions. It is a
38
+ * measured difference, not a preference — see {@link SIGNERS}.darwin.
34
39
  *
35
40
  * `[]` is an ANSWER and not an omission — the same rule `Layout.metadata`
36
41
  * states. Windows has no equivalent concept at all, and a signer that granted
37
42
  * nothing on macOS would be a different decision from one that has nothing to
38
43
  * grant.
39
44
  */
40
- entitlements: readonly string[];
45
+ entitlements: (adhoc: boolean) => readonly string[];
41
46
  /**
42
47
  * Does this signer seal the ARTIFACT ROOT after signing the images inside it?
43
48
  *
@@ -18,7 +18,10 @@
18
18
  // an ad-hoc `LC_CODE_SIGNATURE` already (they must — `install_name_tool`
19
19
  // invalidates the original during relocation). So the darwin leg re-signs
20
20
  // every image, and the result is new BYTES for the packer rather than a
21
- // container around the old ones.
21
+ // container around the old ones. § A16's corollary of this — "the same
22
+ // identity everywhere, so library validation has nothing to object to" — is
23
+ // measured FALSE for the ad-hoc identity, and the darwin row's
24
+ // `entitlements` is where that correction lives.
22
25
  // 4. THE ORDER IS STRUCTURAL, not conventional (§ A17). `readStage` compares
23
26
  // each file's SIZE against `.gjsify-ship-stage.json`, and a size is no more
24
27
  // re-sign-proof than a digest. BOTH halves are measured and they agree:
@@ -66,10 +69,10 @@
66
69
  // `.dmg` is stapled and the `.zip` is told what a user has to do instead.
67
70
  //
68
71
  // WHAT IS STILL NOT DONE, deliberately: `signtool` is never given a timestamp URL
69
- // (see the win32 row), and the entitlement § A16 is about —
70
- // `com.apple.security.cs.disable-library-validation` — is NOT granted, because
71
- // § A4's re-sign of every image in the closure is the design of record and
72
- // granting the entitlement would quietly make it look unnecessary.
72
+ // (see the win32 row), and the named-identity half of § A16 is untested because it
73
+ // needs a certificate — the GRANT of `disable-library-validation` is the measured
74
+ // ad-hoc answer, and § A4's re-sign of every image stays the design of record for
75
+ // the Developer ID that notarisation will actually require.
73
76
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
74
77
  import { dirname, join, sep } from 'node:path';
75
78
  import { isOnPath } from '../check-system-deps.js';
@@ -123,32 +126,52 @@ export const SIGNERS = {
123
126
  ...(adhoc ? [] : ['--timestamp']),
124
127
  file,
125
128
  ],
126
- // FOUR OF THE REFERENCE'S SIX, and both omissions are decisions:
129
+ // FIVE OF THE REFERENCE'S SIX for an ad-hoc identity, FOUR for a real one,
130
+ // and the difference is the one § A16 left open — now MEASURED, on
131
+ // darwin-arm64 with the published `@gjsify/gtk-runtime-darwin-arm64` staged
132
+ // into the bundle (`utils/ship/layout.ts` carries the rest of that run).
127
133
  //
128
- // * `com.apple.security.cs.get-task-allow` is granted by
129
- // `refs/node/tools/osx-entitlements.plist:15` and is a DEBUGGING
130
- // entitlement — it lets another process attach to this one. Apple's
131
- // notarisation rules refuse a Developer-ID artifact carrying it, so
132
- // shipping it would trade a working debug build for an artifact that
133
- // cannot be distributed. The reference signs its own build; we sign what
134
- // a stranger downloads.
135
- // * `com.apple.security.cs.disable-library-validation` is § A16's open
136
- // question and stays open. § A4's design of record is that every Mach-O
137
- // in the closure is re-signed with the SAME identity as the launcher, so
138
- // library validation has nothing to object to — and granting the
139
- // entitlement anyway would make that re-sign look optional the first
140
- // time somebody reads this list.
134
+ // § A16's premise was that library validation has nothing to object to
135
+ // because every Mach-O carries the SAME identity as the launcher. That is
136
+ // true for a Developer ID and FALSE for `--sign -`: an ad-hoc signature has
137
+ // no team at all, so the running process and the dylib it dlopens report
138
+ // different team IDs and the load is refused —
141
139
  //
142
- // The four that remain are what a shipped V8 needs under a hardened
143
- // runtime, plus the one our own loader story needs:
144
- // `@gjsify/node-gi`'s `maybeReexecForGtkRuntime` sets `DYLD_*` for its
145
- // child on darwin, and a hardened process may not do that without
146
- // `allow-dyld-environment-variables`.
147
- entitlements: [
140
+ // dlopen(…/prebuilds/darwin-arm64/node_gi.node): code signature in
141
+ // <378B2FDA-…> '…node_gi.node' not valid for use in process: mapping
142
+ // process and mapped file (non-platform) have different Team IDs
143
+ //
144
+ // — at `require('@gjsify/node-gi')`, the FIRST thing a shipped `.app` does,
145
+ // with every image correctly signed. Same bundle, same signer, same argv,
146
+ // one more entitlement: `interpreter:`/`gtk-runtime:`/`chrome: ok`/
147
+ // `render: 480x320`/`done: 0`.
148
+ //
149
+ // So it is granted WHERE IT IS NEEDED, which is ad-hoc only. A Developer ID
150
+ // signs the whole closure with one team and needs nothing, and Apple's
151
+ // notarisation rules read a `disable-library-validation` entitlement on a
152
+ // Developer-ID artifact as shipping unsigned code — so granting it there
153
+ // would trade a distributable artifact for a locally runnable one. The
154
+ // GRANT therefore stays what § A16 asked to know: § A4's re-sign of every
155
+ // image is the design of record, and the entitlement is what makes the
156
+ // ad-hoc proof of it RUNNABLE rather than a signature `codesign` accepts and
157
+ // dyld refuses.
158
+ //
159
+ // The four both share are what a shipped V8 needs under a hardened runtime,
160
+ // plus the one our own loader story needs: `@gjsify/node-gi`'s
161
+ // `maybeReexecForGtkRuntime` sets `DYLD_*` for its child on darwin, and a
162
+ // hardened process may not do that without `allow-dyld-environment-variables`.
163
+ //
164
+ // `get-task-allow` is absent from both, and that is a decision:
165
+ // `refs/node/tools/osx-entitlements.plist:15` grants it, it is a DEBUGGING
166
+ // entitlement — it lets another process attach to this one — and Apple's
167
+ // notarisation rules refuse a Developer-ID artifact carrying it. The
168
+ // reference signs its own build; we sign what a stranger downloads.
169
+ entitlements: (adhoc) => [
148
170
  'com.apple.security.cs.allow-jit',
149
171
  'com.apple.security.cs.allow-unsigned-executable-memory',
150
172
  'com.apple.security.cs.disable-executable-page-protection',
151
173
  'com.apple.security.cs.allow-dyld-environment-variables',
174
+ ...(adhoc ? ['com.apple.security.cs.disable-library-validation'] : []),
152
175
  ],
153
176
  // The `.app` is a bundle, and a bundle's signature is the seal over its
154
177
  // whole tree — the images alone leave `codesign --verify` with nothing to
@@ -156,9 +179,10 @@ export const SIGNERS = {
156
179
  sealsBundle: true,
157
180
  sealAddPrefix: (bundleRoot) => `${bundleRoot}/Contents/_CodeSignature/`,
158
181
  configKey: 'gjsify.ship.sign.darwin.identity',
159
- gap: 'the images are signed and the bundle is sealed; UNVERIFIED end to end — no macOS host in this ' +
160
- 'repository has run a `--sign` over a real `.app`, and ADR 0024 § A16 still leaves library ' +
161
- 'validation unmeasured',
182
+ gap: 'the images are signed and the bundle is sealed, and the seal survives both the `.zip` and the ' +
183
+ '`.dmg` round trip; § A16 stands measured in ONE direction only — library validation refuses an ' +
184
+ 'ad-hoc-signed dylib, so the ad-hoc identity grants `disable-library-validation` while a named ' +
185
+ 'one does not, and no run in this repository has ever held the named one',
162
186
  },
163
187
  win32: {
164
188
  layoutOs: 'win32',
@@ -180,8 +204,10 @@ export const SIGNERS = {
180
204
  // Windows has no equivalent concept. An Authenticode signature carries no
181
205
  // capability claims — what a hardened runtime and an entitlement do on
182
206
  // macOS is done on Windows by the manifest and by AppContainer, neither of
183
- // which this signer touches.
184
- entitlements: [],
207
+ // which this signer touches. `() => []` rather than `[]` because the field is
208
+ // a function of the identity (see the darwin row), and this one ignores the
209
+ // argument instead of reading `adhoc` out of it.
210
+ entitlements: () => [],
185
211
  // A Windows program directory is a DIRECTORY and nothing more: there is no
186
212
  // manifest to seal and no per-directory signature format. `signtool` signs
187
213
  // images, one at a time, which is what the loop above already does.
@@ -303,9 +329,10 @@ export async function signPayload(input) {
303
329
  writePayload(workDir, payload, '');
304
330
  const adhoc = input.identity === ADHOC_IDENTITY;
305
331
  let entitlements;
306
- if (signer.entitlements.length > 0) {
332
+ const granted = signer.entitlements(adhoc);
333
+ if (granted.length > 0) {
307
334
  mkdirSync(dirname(input.entitlementsPath), { recursive: true });
308
- writeFileSync(input.entitlementsPath, renderEntitlements(signer.entitlements));
335
+ writeFileSync(input.entitlementsPath, renderEntitlements(granted));
309
336
  entitlements = input.entitlementsPath;
310
337
  }
311
338
  const run = async (file, what) => {
@@ -102,6 +102,18 @@ export interface StageManifest {
102
102
  mtime: number;
103
103
  /** GI namespaces the bundle imports, `Ns-Version` where the specifier pins one. */
104
104
  namespaces: string[];
105
+ /**
106
+ * The subset of {@link namespaces} the BUNDLE marks optional (ADR 0087).
107
+ *
108
+ * OPTIONAL on read, deliberately, and that is the {STAGE_SCHEMA_VERSION} header's
109
+ * "a field an older reader would simply ignore" case: a stage written before the
110
+ * split has no such key, and answering "nothing is optional" makes every
111
+ * namespace a `Depends:` again — today's behaviour, and the SAFE direction. A
112
+ * newer reader packing an OLDER stage therefore over-declares instead of dropping
113
+ * a requirement the artifact cannot run without, which is the one error here that
114
+ * ships a package that dies at first launch.
115
+ */
116
+ optionalNamespaces?: string[];
105
117
  settings: PackSettings;
106
118
  staged: StageFileRecord[];
107
119
  overlay: Partial<Record<FormatId, StageOverlayFile[]>>;
@@ -113,6 +125,8 @@ export interface StageManifestInput {
113
125
  formats: readonly FormatDescriptor[];
114
126
  mtime: number;
115
127
  namespaces: readonly string[];
128
+ /** The optional subset of {@link namespaces} — see {@link StageManifest}. */
129
+ optionalNamespaces?: readonly string[];
116
130
  staged: readonly StagedFile[];
117
131
  /** Per-format overlay, as `planOverlay` produced it. */
118
132
  overlay: ReadonlyMap<FormatId, readonly StagedFile[]>;
@@ -187,6 +187,7 @@ export function writeStageManifest(input) {
187
187
  formats: input.formats.map((format) => format.id),
188
188
  mtime: input.mtime,
189
189
  namespaces: [...input.namespaces],
190
+ optionalNamespaces: [...(input.optionalNamespaces ?? [])],
190
191
  settings: toPackSettings(input.settings),
191
192
  staged: input.staged.map((file) => ({
192
193
  path: file.path,
@@ -276,6 +277,12 @@ export function readStageManifest(stageDir) {
276
277
  }),
277
278
  mtime: expectInteger(data.mtime, at('mtime')),
278
279
  namespaces: expectArray(data.namespaces, at('namespaces')).map((entry, index) => expectString(entry, at(`namespaces[${index}]`))),
280
+ // Absent is the empty set, not an error — see the field's doc comment.
281
+ ...(data.optionalNamespaces === undefined
282
+ ? {}
283
+ : {
284
+ optionalNamespaces: expectArray(data.optionalNamespaces, at('optionalNamespaces')).map((entry, index) => expectString(entry, at(`optionalNamespaces[${index}]`))),
285
+ }),
279
286
  settings: readPackSettings(record(data.settings, at('settings')), at('settings')),
280
287
  staged: expectArray(data.staged, at('staged')).map((entry, index) => {
281
288
  const file = record(entry, at(`staged[${index}]`));
@@ -1,4 +1,4 @@
1
- export type WebextBrowser = 'chrome' | 'edge' | 'firefox' | 'safari';
1
+ export type WebextBrowser = 'chrome' | 'edge' | 'firefox' | 'opera' | 'safari';
2
2
  export interface WebextTarget {
3
3
  /** As spelled in `gjsify.webext.targets` and the output directory name. */
4
4
  readonly id: string;
@@ -4,14 +4,22 @@
4
4
  // `@gjsify/manifest-conformance` (plain `.mjs`, no import from here), so a
5
5
  // target added on one side and not the other is refused by the rule before a
6
6
  // build ever sees it.
7
- export const WEBEXT_BROWSERS = ['chrome', 'edge', 'firefox', 'safari'];
7
+ export const WEBEXT_BROWSERS = ['chrome', 'edge', 'firefox', 'opera', 'safari'];
8
8
  /** What a project gets when it declares no `targets`. */
9
9
  export const DEFAULT_WEBEXT_TARGETS = ['chrome-mv3', 'firefox-mv3'];
10
10
  /**
11
- * Chromium-based browsers no longer load Manifest V2 at all, so a `chrome-mv2`
12
- * folder would build, zip, and then be rejected by the browser and the store.
11
+ * Why a browser takes no Manifest V2 folder, absent where it still does. Chrome
12
+ * and Edge removed MV2 in the browser, so the folder would build, zip, and then
13
+ * be refused on load. Opera still LOADS an MV2 extension but its store is
14
+ * MV3-only (Opera, 2025-09), so the folder is refused a step later instead. The
15
+ * outcome is the same either way, which is why the sentence is per browser and
16
+ * not one claim the three do not all support.
13
17
  */
14
- const MV2_REMOVED = new Set(['chrome', 'edge']);
18
+ const MV2_UNSUPPORTED = new Map([
19
+ ['chrome', 'chrome has removed Manifest V2'],
20
+ ['edge', 'edge has removed Manifest V2'],
21
+ ['opera', "opera's extension store accepts Manifest V3 only"],
22
+ ]);
15
23
  export function parseWebextTarget(id) {
16
24
  const match = /^([a-z]+)-mv([23])$/.exec(id);
17
25
  const browser = match?.[1];
@@ -20,9 +28,9 @@ export function parseWebextTarget(id) {
20
28
  `${WEBEXT_BROWSERS.join(', ')} (e.g. chrome-mv3, firefox-mv2).`);
21
29
  }
22
30
  const manifestVersion = match[2] === '2' ? 2 : 3;
23
- if (manifestVersion === 2 && MV2_REMOVED.has(browser)) {
24
- throw new Error(`gjsify webext: target "${id}" does not exist any more: ${browser} has removed Manifest V2. ` +
25
- `Use ${browser}-mv3.`);
31
+ const why = manifestVersion === 2 ? MV2_UNSUPPORTED.get(browser) : undefined;
32
+ if (why !== undefined) {
33
+ throw new Error(`gjsify webext: target "${id}" does not exist any more: ${why}. Use ${browser}-mv3.`);
26
34
  }
27
35
  return { id, browser, manifestVersion };
28
36
  }
@@ -30,7 +38,11 @@ export function parseWebextTarget(id) {
30
38
  export function webExtRunTarget(target) {
31
39
  if (target.browser === 'firefox')
32
40
  return 'firefox-desktop';
33
- if (target.browser === 'chrome' || target.browser === 'edge')
41
+ // By ENGINE, not by brand: the chromium driver's only browser-specific input is the
42
+ // binary, and `--browser-binary` already carries it — the claim branded Chrome and
43
+ // Edge already make. Refusing opera here would be a claim about the operator's
44
+ // binary, which is not what this switch is about.
45
+ if (target.browser === 'chrome' || target.browser === 'edge' || target.browser === 'opera')
34
46
  return 'chromium';
35
47
  return null;
36
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,21 +142,22 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/buffer": "^0.53.0",
146
- "@gjsify/create-app": "^0.53.0",
147
- "@gjsify/node-globals": "^0.53.0",
148
- "@gjsify/node-polyfills": "^0.53.0",
149
- "@gjsify/npm-registry": "^0.53.0",
150
- "@gjsify/resolve-npm": "^0.53.0",
151
- "@gjsify/rolldown-plugin-gjsify": "^0.53.0",
152
- "@gjsify/vite-plugin-blueprint": "^0.53.0",
153
- "@gjsify/rolldown-plugin-pnp": "^0.53.0",
154
- "@gjsify/semver": "^0.53.0",
155
- "@gjsify/tar": "^0.53.0",
156
- "@gjsify/tsc": "^0.53.0",
157
- "@gjsify/utils": "^0.53.0",
158
- "@gjsify/web-polyfills": "^0.53.0",
159
- "@gjsify/workspace": "^0.53.0",
145
+ "@gjsify/blueprint": "^0.54.0",
146
+ "@gjsify/buffer": "^0.54.0",
147
+ "@gjsify/create-app": "^0.54.0",
148
+ "@gjsify/node-globals": "^0.54.0",
149
+ "@gjsify/node-polyfills": "^0.54.0",
150
+ "@gjsify/npm-registry": "^0.54.0",
151
+ "@gjsify/resolve-npm": "^0.54.0",
152
+ "@gjsify/rolldown-plugin-gjsify": "^0.54.0",
153
+ "@gjsify/vite-plugin-blueprint": "^0.54.0",
154
+ "@gjsify/rolldown-plugin-pnp": "^0.54.0",
155
+ "@gjsify/semver": "^0.54.0",
156
+ "@gjsify/tar": "^0.54.0",
157
+ "@gjsify/tsc": "^0.54.0",
158
+ "@gjsify/utils": "^0.54.0",
159
+ "@gjsify/web-polyfills": "^0.54.0",
160
+ "@gjsify/workspace": "^0.54.0",
160
161
  "acorn": "^8.17.0",
161
162
  "cosmiconfig": "^9.0.2",
162
163
  "get-tsconfig": "^4.14.0",
@@ -165,13 +166,13 @@
165
166
  "yargs": "^18.0.0"
166
167
  },
167
168
  "devDependencies": {
168
- "@gjsify/unit": "^0.53.0",
169
+ "@gjsify/unit": "^0.54.0",
169
170
  "@types/yargs": "^17.0.35",
170
171
  "typescript": "^6.0.3"
171
172
  },
172
173
  "peerDependencies": {
173
- "@gjsify/oxfmt-native": "^0.53.0",
174
- "@gjsify/rolldown-native": "^0.53.0"
174
+ "@gjsify/oxfmt-native": "^0.54.0",
175
+ "@gjsify/rolldown-native": "^0.54.0"
175
176
  },
176
177
  "peerDependenciesMeta": {
177
178
  "@gjsify/oxfmt-native": {