@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.
- package/dist/cli.gjs.mjs +263 -248
- package/lib/bundler-pick.d.ts +35 -0
- package/lib/bundler-pick.js +74 -7
- package/lib/cli-app.js +3 -1
- package/lib/commands/blueprint/index.d.ts +4 -0
- package/lib/commands/blueprint/index.js +15 -0
- package/lib/commands/blueprint/types.d.ts +8 -0
- package/lib/commands/blueprint/types.js +124 -0
- package/lib/commands/devtools.d.ts +21 -0
- package/lib/commands/devtools.js +146 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/ship.js +51 -8
- package/lib/commands/webext/index.js +1 -1
- package/lib/utils/oxc-resolve.js +9 -7
- package/lib/utils/ship/app-runtime.d.ts +7 -5
- package/lib/utils/ship/app-runtime.js +11 -8
- package/lib/utils/ship/appimage.d.ts +9 -0
- package/lib/utils/ship/appimage.js +2 -1
- package/lib/utils/ship/deb.d.ts +10 -0
- package/lib/utils/ship/deb.js +7 -0
- package/lib/utils/ship/depends.d.ts +37 -3
- package/lib/utils/ship/depends.js +21 -9
- package/lib/utils/ship/gi-namespaces.d.ts +16 -0
- package/lib/utils/ship/gi-namespaces.js +64 -1
- package/lib/utils/ship/launcher.js +2 -2
- package/lib/utils/ship/layout.js +25 -2
- package/lib/utils/ship/rpm.d.ts +5 -0
- package/lib/utils/ship/rpm.js +42 -0
- package/lib/utils/ship/settings.js +6 -0
- package/lib/utils/ship/signing.d.ts +7 -2
- package/lib/utils/ship/signing.js +59 -32
- package/lib/utils/ship/stage-manifest.d.ts +14 -0
- package/lib/utils/ship/stage-manifest.js +7 -0
- package/lib/utils/webext/targets.d.ts +1 -1
- package/lib/utils/webext/targets.js +20 -8
- package/package.json +20 -19
package/lib/utils/ship/rpm.d.ts
CHANGED
|
@@ -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;
|
package/lib/utils/ship/rpm.js
CHANGED
|
@@ -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,
|
|
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
|
|
70
|
-
// `
|
|
71
|
-
// § A4's re-sign of every image
|
|
72
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
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
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
// `
|
|
147
|
-
|
|
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
|
|
160
|
-
'
|
|
161
|
-
'validation
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
const granted = signer.entitlements(adhoc);
|
|
333
|
+
if (granted.length > 0) {
|
|
307
334
|
mkdirSync(dirname(input.entitlementsPath), { recursive: true });
|
|
308
|
-
writeFileSync(input.entitlementsPath, renderEntitlements(
|
|
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
|
-
*
|
|
12
|
-
* folder would build, zip, and then
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
146
|
-
"@gjsify/
|
|
147
|
-
"@gjsify/
|
|
148
|
-
"@gjsify/node-
|
|
149
|
-
"@gjsify/
|
|
150
|
-
"@gjsify/
|
|
151
|
-
"@gjsify/
|
|
152
|
-
"@gjsify/
|
|
153
|
-
"@gjsify/
|
|
154
|
-
"@gjsify/
|
|
155
|
-
"@gjsify/
|
|
156
|
-
"@gjsify/
|
|
157
|
-
"@gjsify/
|
|
158
|
-
"@gjsify/
|
|
159
|
-
"@gjsify/
|
|
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.
|
|
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.
|
|
174
|
-
"@gjsify/rolldown-native": "^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": {
|