@gjsify/cli 0.52.0 → 0.53.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 +329 -298
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +37 -1
- package/lib/bundler-pick.js +108 -17
- package/lib/cli-app.js +5 -2
- package/lib/commands/env.d.ts +31 -0
- package/lib/commands/env.js +114 -0
- package/lib/commands/exec.d.ts +10 -0
- package/lib/commands/exec.js +208 -0
- package/lib/commands/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +3 -0
- package/lib/commands/index.js +3 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +5 -5
- package/lib/commands/self-update.js +2 -1
- package/lib/commands/test.d.ts +18 -9
- package/lib/commands/test.js +56 -11
- package/lib/commands/tsc.js +4 -4
- package/lib/commands/upgrade.d.ts +15 -0
- package/lib/commands/upgrade.js +76 -2
- package/lib/commands/webext/index.d.ts +2 -0
- package/lib/commands/webext/index.js +15 -0
- package/lib/commands/webext/subcommands.d.ts +16 -0
- package/lib/commands/webext/subcommands.js +204 -0
- package/lib/types/config-data.d.ts +1 -1
- package/lib/utils/bin-shim.d.ts +8 -2
- package/lib/utils/bin-shim.js +6 -5
- package/lib/utils/bundle-inputs.d.ts +84 -0
- package/lib/utils/bundle-inputs.js +268 -0
- package/lib/utils/check-system-deps.d.ts +4 -2
- package/lib/utils/check-system-deps.js +21 -26
- package/lib/utils/dev-link.d.ts +9 -6
- package/lib/utils/dev-link.js +71 -19
- package/lib/utils/exec-bin.d.ts +137 -0
- package/lib/utils/exec-bin.js +321 -0
- package/lib/utils/gi-runtime-paths.js +3 -2
- package/lib/utils/gitignore.d.ts +15 -0
- package/lib/utils/gitignore.js +178 -0
- package/lib/utils/gjsify-shim.d.ts +47 -0
- package/lib/utils/gjsify-shim.js +97 -8
- package/lib/utils/install-backend-native.d.ts +19 -1
- package/lib/utils/install-backend-native.js +93 -7
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/native-skip-scan.d.ts +20 -0
- package/lib/utils/native-skip-scan.js +343 -0
- package/lib/utils/node-script.js +1 -1
- package/lib/utils/oxc-resolve.js +42 -3
- package/lib/utils/process-table.d.ts +22 -0
- package/lib/utils/process-table.js +188 -0
- package/lib/utils/prune-prefix.js +1 -1
- package/lib/utils/run-gjs.d.ts +15 -1
- package/lib/utils/run-gjs.js +16 -7
- package/lib/utils/run-node.d.ts +2 -0
- package/lib/utils/run-node.js +2 -1
- package/lib/utils/runtimes.js +14 -13
- package/lib/utils/ship/gzip.js +1 -1
- package/lib/utils/ship/icons.d.ts +6 -0
- package/lib/utils/ship/icons.js +6 -5
- package/lib/utils/spawn.d.ts +13 -0
- package/lib/utils/spawn.js +10 -5
- package/lib/utils/watch-loop.d.ts +15 -1
- package/lib/utils/watch-loop.js +9 -5
- package/lib/utils/webext/build.d.ts +42 -0
- package/lib/utils/webext/build.js +120 -0
- package/lib/utils/webext/config.d.ts +72 -0
- package/lib/utils/webext/config.js +234 -0
- package/lib/utils/webext/icons.d.ts +24 -0
- package/lib/utils/webext/icons.js +83 -0
- package/lib/utils/webext/launch.d.ts +32 -0
- package/lib/utils/webext/launch.js +77 -0
- package/lib/utils/webext/manifest.d.ts +53 -0
- package/lib/utils/webext/manifest.js +219 -0
- package/lib/utils/webext/pages.d.ts +26 -0
- package/lib/utils/webext/pages.js +61 -0
- package/lib/utils/webext/targets.d.ts +13 -0
- package/lib/utils/webext/targets.js +36 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- package/package.json +19 -19
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// The manifest per target (ADR 0077 § 3): a module or a JSON template,
|
|
2
|
+
// composed WITHOUT converting keys between manifest versions, then checked
|
|
3
|
+
// against the folder it describes.
|
|
4
|
+
//
|
|
5
|
+
// The checks are the ones a browser would otherwise report against the
|
|
6
|
+
// manifest, far from the build that caused them: Chrome refuses to load a
|
|
7
|
+
// folder whose background script is missing ("Could not load background
|
|
8
|
+
// script") or that has `_locales/` without `default_locale`, and the store
|
|
9
|
+
// rejects the same zip for the same reasons, days later.
|
|
10
|
+
import { copyFileSync, existsSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
11
|
+
import { basename, dirname, join } from 'node:path';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Deep-merge `over` onto `base`: objects merge, arrays and scalars replace,
|
|
19
|
+
* `null` deletes. Arrays replace because "add a permission for Firefox" and
|
|
20
|
+
* "drop a permission for Firefox" cannot both be spelled by concatenation.
|
|
21
|
+
*/
|
|
22
|
+
export function mergeManifest(base, over) {
|
|
23
|
+
const out = { ...base };
|
|
24
|
+
for (const [key, value] of Object.entries(over)) {
|
|
25
|
+
if (value === null)
|
|
26
|
+
delete out[key];
|
|
27
|
+
else if (isRecord(value) && isRecord(out[key]))
|
|
28
|
+
out[key] = mergeManifest(out[key], value);
|
|
29
|
+
else
|
|
30
|
+
out[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** Apply a JSON template's `$targets` block for one target, and drop the block. */
|
|
35
|
+
export function applyTargetOverrides(template, target) {
|
|
36
|
+
const { $targets, ...base } = template;
|
|
37
|
+
if ($targets === undefined)
|
|
38
|
+
return base;
|
|
39
|
+
if (!isRecord($targets))
|
|
40
|
+
throw new Error("gjsify webext: the manifest's `$targets` must be an object.");
|
|
41
|
+
const over = $targets[target];
|
|
42
|
+
if (over === undefined)
|
|
43
|
+
return base;
|
|
44
|
+
if (!isRecord(over))
|
|
45
|
+
throw new Error(`gjsify webext: \`$targets["${target}"]\` in the manifest must be an object.`);
|
|
46
|
+
return mergeManifest(base, over);
|
|
47
|
+
}
|
|
48
|
+
/** Produce the manifest for one target from a loaded template. */
|
|
49
|
+
export async function composeManifest(template, ctx) {
|
|
50
|
+
const raw = typeof template === 'function' ? await template(ctx) : applyTargetOverrides(template, ctx.target);
|
|
51
|
+
if (!isRecord(raw)) {
|
|
52
|
+
throw new Error(`gjsify webext: the manifest for ${ctx.target} is not an object (got ${typeof raw}).`);
|
|
53
|
+
}
|
|
54
|
+
if (raw.version !== undefined)
|
|
55
|
+
return raw;
|
|
56
|
+
// Beside `name`, where a reader of manifest.json looks for it, rather than last.
|
|
57
|
+
const out = {};
|
|
58
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
59
|
+
out[key] = value;
|
|
60
|
+
if (key === 'name')
|
|
61
|
+
out.version = ctx.version;
|
|
62
|
+
}
|
|
63
|
+
if (out.version === undefined)
|
|
64
|
+
out.version = ctx.version;
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
let loadCounter = 0;
|
|
68
|
+
/**
|
|
69
|
+
* Load a manifest template. A `.json` is parsed; a module is imported and its
|
|
70
|
+
* default export used.
|
|
71
|
+
*
|
|
72
|
+
* Every load is FRESH, because `webext dev` reloads the template on each
|
|
73
|
+
* rebuild and both hosts cache modules by URL: on Node a query string makes a
|
|
74
|
+
* new URL; under GJS the module is bundled (GJS cannot import `.ts`, nor a
|
|
75
|
+
* module with `node:` imports) and the bundle copied to a unique file name,
|
|
76
|
+
* because whether GJS's loader keeps a query string on a `file:` URL is not a
|
|
77
|
+
* thing to depend on.
|
|
78
|
+
*/
|
|
79
|
+
export async function loadManifestTemplate(path) {
|
|
80
|
+
if (/\.json$/i.test(path)) {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
throw new Error(`gjsify webext: ${path} is not valid JSON: ${err.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let url;
|
|
89
|
+
let cleanup = null;
|
|
90
|
+
if (isGjs()) {
|
|
91
|
+
const { BuildAction } = await import('../../actions/build.js');
|
|
92
|
+
const bundled = await BuildAction.bundleFileForGjsCached(path, {
|
|
93
|
+
cacheSubdir: 'webext',
|
|
94
|
+
label: basename(path),
|
|
95
|
+
cache: false,
|
|
96
|
+
define: { 'import.meta.url': JSON.stringify(pathToFileURL(path).href) },
|
|
97
|
+
});
|
|
98
|
+
const copy = join(dirname(bundled), `manifest-${process.pid}-${++loadCounter}.mjs`);
|
|
99
|
+
copyFileSync(bundled, copy);
|
|
100
|
+
url = pathToFileURL(copy).href;
|
|
101
|
+
cleanup = () => rmSync(copy, { force: true });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
url = `${pathToFileURL(path).href}?t=${statSync(path).mtimeMs}-${++loadCounter}`;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const mod = (await import(url));
|
|
108
|
+
const template = mod.default;
|
|
109
|
+
if (typeof template !== 'function' && !isRecord(template)) {
|
|
110
|
+
throw new Error(`gjsify webext: ${path} has no default export. Export the manifest object, or a function ` +
|
|
111
|
+
'`(ctx) => manifest` that receives the target.');
|
|
112
|
+
}
|
|
113
|
+
return template;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
cleanup?.();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Keys that name a page, script, stylesheet or image inside the extension. */
|
|
120
|
+
function addPath(out, value) {
|
|
121
|
+
if (typeof value !== 'string' || value === '')
|
|
122
|
+
return;
|
|
123
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.includes('*'))
|
|
124
|
+
return;
|
|
125
|
+
out.add(value.replace(/^\//, '').replace(/[?#].*$/, ''));
|
|
126
|
+
}
|
|
127
|
+
function addIconValue(out, value) {
|
|
128
|
+
if (typeof value === 'string')
|
|
129
|
+
addPath(out, value);
|
|
130
|
+
else if (isRecord(value))
|
|
131
|
+
for (const v of Object.values(value))
|
|
132
|
+
addPath(out, v);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every file inside the extension the manifest refers to by path.
|
|
136
|
+
*
|
|
137
|
+
* Deliberately a list of the keys browsers LOAD from, not a walk over every
|
|
138
|
+
* string: `description` or a `commands` label that happens to look like a path
|
|
139
|
+
* is not a reference.
|
|
140
|
+
*/
|
|
141
|
+
export function manifestFileReferences(manifest) {
|
|
142
|
+
const out = new Set();
|
|
143
|
+
const background = manifest.background;
|
|
144
|
+
if (isRecord(background)) {
|
|
145
|
+
addPath(out, background.service_worker);
|
|
146
|
+
addPath(out, background.page);
|
|
147
|
+
if (Array.isArray(background.scripts))
|
|
148
|
+
for (const s of background.scripts)
|
|
149
|
+
addPath(out, s);
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(manifest.content_scripts)) {
|
|
152
|
+
for (const cs of manifest.content_scripts) {
|
|
153
|
+
if (!isRecord(cs))
|
|
154
|
+
continue;
|
|
155
|
+
for (const key of ['js', 'css'])
|
|
156
|
+
if (Array.isArray(cs[key]))
|
|
157
|
+
for (const f of cs[key])
|
|
158
|
+
addPath(out, f);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const key of ['action', 'browser_action', 'page_action', 'sidebar_action']) {
|
|
162
|
+
const action = manifest[key];
|
|
163
|
+
if (!isRecord(action))
|
|
164
|
+
continue;
|
|
165
|
+
addPath(out, action.default_popup);
|
|
166
|
+
addPath(out, action.default_panel);
|
|
167
|
+
addIconValue(out, action.default_icon);
|
|
168
|
+
}
|
|
169
|
+
if (isRecord(manifest.options_ui))
|
|
170
|
+
addPath(out, manifest.options_ui.page);
|
|
171
|
+
addPath(out, manifest.options_page);
|
|
172
|
+
addPath(out, manifest.devtools_page);
|
|
173
|
+
if (isRecord(manifest.side_panel))
|
|
174
|
+
addPath(out, manifest.side_panel.default_path);
|
|
175
|
+
addIconValue(out, manifest.icons);
|
|
176
|
+
if (isRecord(manifest.chrome_url_overrides))
|
|
177
|
+
for (const v of Object.values(manifest.chrome_url_overrides))
|
|
178
|
+
addPath(out, v);
|
|
179
|
+
if (Array.isArray(manifest.web_accessible_resources)) {
|
|
180
|
+
for (const entry of manifest.web_accessible_resources) {
|
|
181
|
+
if (typeof entry === 'string')
|
|
182
|
+
addPath(out, entry);
|
|
183
|
+
else if (isRecord(entry) && Array.isArray(entry.resources))
|
|
184
|
+
for (const r of entry.resources)
|
|
185
|
+
addPath(out, r);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [...out].sort();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* What is wrong with a composed manifest for the folder it will sit in.
|
|
192
|
+
* Returns problem sentences; empty means the browser has nothing to refuse
|
|
193
|
+
* that this build could have known about.
|
|
194
|
+
*/
|
|
195
|
+
export function checkManifest(manifest, target, dir) {
|
|
196
|
+
const problems = [];
|
|
197
|
+
if (manifest.manifest_version !== target.manifestVersion) {
|
|
198
|
+
problems.push(`manifest_version is ${JSON.stringify(manifest.manifest_version)}, and target ${target.id} is Manifest V${target.manifestVersion}. ` +
|
|
199
|
+
'gjsify does not convert keys between versions — branch on `ctx.manifestVersion` in the manifest.');
|
|
200
|
+
}
|
|
201
|
+
for (const ref of manifestFileReferences(manifest)) {
|
|
202
|
+
if (!existsSync(join(dir, ref))) {
|
|
203
|
+
problems.push(`the manifest names "${ref}", which the build did not write into ${target.id}/.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const hasLocales = existsSync(join(dir, '_locales'));
|
|
207
|
+
const defaultLocale = manifest.default_locale;
|
|
208
|
+
if (hasLocales && typeof defaultLocale !== 'string') {
|
|
209
|
+
problems.push('_locales/ exists but the manifest sets no default_locale; the browser refuses to load it.');
|
|
210
|
+
}
|
|
211
|
+
else if (!hasLocales && defaultLocale !== undefined) {
|
|
212
|
+
problems.push(`the manifest sets default_locale "${String(defaultLocale)}" but there is no _locales/ directory.`);
|
|
213
|
+
}
|
|
214
|
+
else if (typeof defaultLocale === 'string' &&
|
|
215
|
+
!existsSync(join(dir, '_locales', defaultLocale, 'messages.json'))) {
|
|
216
|
+
problems.push(`default_locale is "${defaultLocale}" but _locales/${defaultLocale}/messages.json does not exist.`);
|
|
217
|
+
}
|
|
218
|
+
return problems;
|
|
219
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PageScript {
|
|
2
|
+
/** Absolute path of the source the tag points at. */
|
|
3
|
+
entry: string;
|
|
4
|
+
/** `esm` for a `type="module"` tag, `iife` for a classic one. */
|
|
5
|
+
format: 'esm' | 'iife';
|
|
6
|
+
/** File name written into the target root. */
|
|
7
|
+
output: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PageStylesheet {
|
|
10
|
+
source: string;
|
|
11
|
+
output: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PagePlan {
|
|
14
|
+
html: string;
|
|
15
|
+
scripts: PageScript[];
|
|
16
|
+
stylesheets: PageStylesheet[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Plan one page: which scripts to bundle and which stylesheets to copy, and the
|
|
20
|
+
* page with their references rewritten.
|
|
21
|
+
*
|
|
22
|
+
* Every relative `src` naming a JS/TS file is bundled. A prebuilt script that
|
|
23
|
+
* must load verbatim goes in `public/` and is referenced by an absolute
|
|
24
|
+
* extension path (`/vendor/x.js`), which is left alone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function planPage(name: string, htmlPath: string, html: string): PagePlan;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// An extension page (popup, options, side panel …) as the build sees it: the
|
|
2
|
+
// local scripts it loads, the local stylesheets it links, and the page with
|
|
3
|
+
// both rewritten to point at the built files in the target root.
|
|
4
|
+
//
|
|
5
|
+
// Regex-based on purpose, like `utils/html-entry.ts`: this rewrites attribute
|
|
6
|
+
// values in author-written shell pages, it is not an HTML pipeline. Everything
|
|
7
|
+
// not matched (inline `<style>`, inline scripts, remote URLs) passes through.
|
|
8
|
+
import { basename, dirname, isAbsolute, resolve } from 'node:path';
|
|
9
|
+
const SCRIPT_TAG_RE = /<script\b[^>]*\bsrc\s*=\s*["'][^"']+["'][^>]*>/gi;
|
|
10
|
+
const LINK_TAG_RE = /<link\b[^>]*>/gi;
|
|
11
|
+
const SRC_RE = /\bsrc\s*=\s*(["'])([^"']+)\1/i;
|
|
12
|
+
const HREF_RE = /\bhref\s*=\s*(["'])([^"']+)\1/i;
|
|
13
|
+
const MODULE_RE = /\btype\s*=\s*["']module["']/i;
|
|
14
|
+
const STYLESHEET_RE = /\brel\s*=\s*["'][^"']*\bstylesheet\b[^"']*["']/i;
|
|
15
|
+
/** Remote, protocol-relative, data: and extension-absolute URLs are not ours to bundle. */
|
|
16
|
+
const NOT_LOCAL_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
17
|
+
const SOURCE_EXT_RE = /\.(?:[cm]?[jt]sx?)$/i;
|
|
18
|
+
/**
|
|
19
|
+
* Plan one page: which scripts to bundle and which stylesheets to copy, and the
|
|
20
|
+
* page with their references rewritten.
|
|
21
|
+
*
|
|
22
|
+
* Every relative `src` naming a JS/TS file is bundled. A prebuilt script that
|
|
23
|
+
* must load verbatim goes in `public/` and is referenced by an absolute
|
|
24
|
+
* extension path (`/vendor/x.js`), which is left alone.
|
|
25
|
+
*/
|
|
26
|
+
export function planPage(name, htmlPath, html) {
|
|
27
|
+
const dir = dirname(htmlPath);
|
|
28
|
+
const scripts = [];
|
|
29
|
+
const stylesheets = [];
|
|
30
|
+
const local = (value) => {
|
|
31
|
+
if (NOT_LOCAL_RE.test(value) || value.startsWith('/'))
|
|
32
|
+
return null;
|
|
33
|
+
const path = value.replace(/[?#].*$/, '');
|
|
34
|
+
return isAbsolute(path) ? path : resolve(dir, path);
|
|
35
|
+
};
|
|
36
|
+
let out = html.replace(SCRIPT_TAG_RE, (tag) => {
|
|
37
|
+
const src = SRC_RE.exec(tag);
|
|
38
|
+
if (!src)
|
|
39
|
+
return tag;
|
|
40
|
+
const entry = local(src[2]);
|
|
41
|
+
if (entry === null || !SOURCE_EXT_RE.test(entry))
|
|
42
|
+
return tag;
|
|
43
|
+
const output = scripts.length === 0 ? `${name}.js` : `${name}-${scripts.length + 1}.js`;
|
|
44
|
+
scripts.push({ entry, format: MODULE_RE.test(tag) ? 'esm' : 'iife', output });
|
|
45
|
+
return tag.replace(SRC_RE, (_whole, quote) => `src=${quote}${output}${quote}`);
|
|
46
|
+
});
|
|
47
|
+
out = out.replace(LINK_TAG_RE, (tag) => {
|
|
48
|
+
if (!STYLESHEET_RE.test(tag))
|
|
49
|
+
return tag;
|
|
50
|
+
const href = HREF_RE.exec(tag);
|
|
51
|
+
if (!href)
|
|
52
|
+
return tag;
|
|
53
|
+
const source = local(href[2]);
|
|
54
|
+
if (source === null)
|
|
55
|
+
return tag;
|
|
56
|
+
const output = basename(source);
|
|
57
|
+
stylesheets.push({ source, output });
|
|
58
|
+
return tag.replace(HREF_RE, (_whole, quote) => `href=${quote}${output}${quote}`);
|
|
59
|
+
});
|
|
60
|
+
return { html: out, scripts, stylesheets };
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type WebextBrowser = 'chrome' | 'edge' | 'firefox' | 'safari';
|
|
2
|
+
export interface WebextTarget {
|
|
3
|
+
/** As spelled in `gjsify.webext.targets` and the output directory name. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly browser: WebextBrowser;
|
|
6
|
+
readonly manifestVersion: 2 | 3;
|
|
7
|
+
}
|
|
8
|
+
export declare const WEBEXT_BROWSERS: readonly WebextBrowser[];
|
|
9
|
+
/** What a project gets when it declares no `targets`. */
|
|
10
|
+
export declare const DEFAULT_WEBEXT_TARGETS: readonly string[];
|
|
11
|
+
export declare function parseWebextTarget(id: string): WebextTarget;
|
|
12
|
+
/** Whether `web-ext run` launches this target through its Firefox or its Chromium driver. */
|
|
13
|
+
export declare function webExtRunTarget(target: WebextTarget): 'firefox-desktop' | 'chromium' | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The build targets of `gjsify webext` (ADR 0077 § 2): `<browser>-mv<2|3>`.
|
|
2
|
+
//
|
|
3
|
+
// The vocabulary is mirrored by the `webext` conformance rule in
|
|
4
|
+
// `@gjsify/manifest-conformance` (plain `.mjs`, no import from here), so a
|
|
5
|
+
// target added on one side and not the other is refused by the rule before a
|
|
6
|
+
// build ever sees it.
|
|
7
|
+
export const WEBEXT_BROWSERS = ['chrome', 'edge', 'firefox', 'safari'];
|
|
8
|
+
/** What a project gets when it declares no `targets`. */
|
|
9
|
+
export const DEFAULT_WEBEXT_TARGETS = ['chrome-mv3', 'firefox-mv3'];
|
|
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.
|
|
13
|
+
*/
|
|
14
|
+
const MV2_REMOVED = new Set(['chrome', 'edge']);
|
|
15
|
+
export function parseWebextTarget(id) {
|
|
16
|
+
const match = /^([a-z]+)-mv([23])$/.exec(id);
|
|
17
|
+
const browser = match?.[1];
|
|
18
|
+
if (!match || browser === undefined || !WEBEXT_BROWSERS.includes(browser)) {
|
|
19
|
+
throw new Error(`gjsify webext: unknown target "${id}". A target is <browser>-mv<2|3> with browser one of ` +
|
|
20
|
+
`${WEBEXT_BROWSERS.join(', ')} (e.g. chrome-mv3, firefox-mv2).`);
|
|
21
|
+
}
|
|
22
|
+
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.`);
|
|
26
|
+
}
|
|
27
|
+
return { id, browser, manifestVersion };
|
|
28
|
+
}
|
|
29
|
+
/** Whether `web-ext run` launches this target through its Firefox or its Chromium driver. */
|
|
30
|
+
export function webExtRunTarget(target) {
|
|
31
|
+
if (target.browser === 'firefox')
|
|
32
|
+
return 'firefox-desktop';
|
|
33
|
+
if (target.browser === 'chrome' || target.browser === 'edge')
|
|
34
|
+
return 'chromium';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
@@ -38,6 +38,23 @@ export declare function escapeCmdArgument(arg: string, doubleEscapeMetaChars?: b
|
|
|
38
38
|
* `args` are the caller's UNESCAPED arguments; `comspec` is normally `%COMSPEC%`.
|
|
39
39
|
*/
|
|
40
40
|
export declare function buildCmdExeInvocation(commandFile: string, args: readonly string[], comspec?: string): Win32Invocation;
|
|
41
|
+
/**
|
|
42
|
+
* Find `cmd` on `ctx.env.PATH` the way the host's process creation would, or
|
|
43
|
+
* `undefined`.
|
|
44
|
+
*
|
|
45
|
+
* On win32 a bare name is tried with each `PATHEXT` extension per directory — the
|
|
46
|
+
* order `CreateProcess` itself uses — and NEVER as-is: the extensionless member of
|
|
47
|
+
* npm's shim trio (`npm`, `gjsify`) is a `sh` script that exists on disk and cannot
|
|
48
|
+
* be executed, so an existence probe that accepts it answers "yes" for a command
|
|
49
|
+
* that is ENOENT to `spawn`. A name that already carries an extension, and every
|
|
50
|
+
* name off win32, is probed verbatim.
|
|
51
|
+
*
|
|
52
|
+
* Exported because "is X on PATH" is asked outside the spawn path too
|
|
53
|
+
* (`isOnPath` in `check-system-deps.ts`), and a second, PATHEXT-blind walk there
|
|
54
|
+
* reported `signtool` missing on a Windows host with `signtool.exe` on PATH —
|
|
55
|
+
* `gjsify ship win32 --sign` refused to run on the one OS it can sign on.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findOnPath(cmd: string, ctx: Win32ResolveContext): string | undefined;
|
|
41
58
|
/**
|
|
42
59
|
* Rewrite a bare command name into something Windows can actually spawn.
|
|
43
60
|
*
|
|
@@ -90,12 +90,27 @@ function isBareName(cmd) {
|
|
|
90
90
|
return !cmd.includes('/') && !cmd.includes('\\') && !/\.[^.]+$/.test(cmd);
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
* Find `cmd` on the
|
|
94
|
-
*
|
|
93
|
+
* Find `cmd` on `ctx.env.PATH` the way the host's process creation would, or
|
|
94
|
+
* `undefined`.
|
|
95
|
+
*
|
|
96
|
+
* On win32 a bare name is tried with each `PATHEXT` extension per directory — the
|
|
97
|
+
* order `CreateProcess` itself uses — and NEVER as-is: the extensionless member of
|
|
98
|
+
* npm's shim trio (`npm`, `gjsify`) is a `sh` script that exists on disk and cannot
|
|
99
|
+
* be executed, so an existence probe that accepts it answers "yes" for a command
|
|
100
|
+
* that is ENOENT to `spawn`. A name that already carries an extension, and every
|
|
101
|
+
* name off win32, is probed verbatim.
|
|
102
|
+
*
|
|
103
|
+
* Exported because "is X on PATH" is asked outside the spawn path too
|
|
104
|
+
* (`isOnPath` in `check-system-deps.ts`), and a second, PATHEXT-blind walk there
|
|
105
|
+
* reported `signtool` missing on a Windows host with `signtool.exe` on PATH —
|
|
106
|
+
* `gjsify ship win32 --sign` refused to run on the one OS it can sign on.
|
|
95
107
|
*/
|
|
96
|
-
function
|
|
97
|
-
const
|
|
98
|
-
const
|
|
108
|
+
export function findOnPath(cmd, ctx) {
|
|
109
|
+
const win = ctx.platform === 'win32';
|
|
110
|
+
const dirs = (lookupEnv(ctx.env, 'PATH') ?? '').split(win ? ';' : ':').filter(Boolean);
|
|
111
|
+
const exts = win && !/\.[^.\\/]+$/.test(cmd)
|
|
112
|
+
? (lookupEnv(ctx.env, 'PATHEXT') ?? DEFAULT_PATHEXT).split(';').filter(Boolean)
|
|
113
|
+
: [''];
|
|
99
114
|
for (const dir of dirs) {
|
|
100
115
|
for (const ext of exts) {
|
|
101
116
|
const candidate = ctx.join(dir, cmd + ext);
|
|
@@ -115,7 +130,7 @@ function lookupOnPath(cmd, ctx) {
|
|
|
115
130
|
export function resolveWin32Command(cmd, args, ctx) {
|
|
116
131
|
if (ctx.platform !== 'win32' || !isBareName(cmd))
|
|
117
132
|
return undefined;
|
|
118
|
-
const resolved =
|
|
133
|
+
const resolved = findOnPath(cmd, ctx);
|
|
119
134
|
if (!resolved)
|
|
120
135
|
return undefined;
|
|
121
136
|
// `.exe`/`.com` run without an interpreter; spawning the resolved absolute path skips a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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.
|
|
146
|
-
"@gjsify/create-app": "^0.
|
|
147
|
-
"@gjsify/node-globals": "^0.
|
|
148
|
-
"@gjsify/node-polyfills": "^0.
|
|
149
|
-
"@gjsify/npm-registry": "^0.
|
|
150
|
-
"@gjsify/resolve-npm": "^0.
|
|
151
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
152
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
153
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
154
|
-
"@gjsify/semver": "^0.
|
|
155
|
-
"@gjsify/tar": "^0.
|
|
156
|
-
"@gjsify/tsc": "^0.
|
|
157
|
-
"@gjsify/utils": "^0.
|
|
158
|
-
"@gjsify/web-polyfills": "^0.
|
|
159
|
-
"@gjsify/workspace": "^0.
|
|
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",
|
|
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.
|
|
168
|
+
"@gjsify/unit": "^0.53.0",
|
|
169
169
|
"@types/yargs": "^17.0.35",
|
|
170
170
|
"typescript": "^6.0.3"
|
|
171
171
|
},
|
|
172
172
|
"peerDependencies": {
|
|
173
|
-
"@gjsify/oxfmt-native": "^0.
|
|
174
|
-
"@gjsify/rolldown-native": "^0.
|
|
173
|
+
"@gjsify/oxfmt-native": "^0.53.0",
|
|
174
|
+
"@gjsify/rolldown-native": "^0.53.0"
|
|
175
175
|
},
|
|
176
176
|
"peerDependenciesMeta": {
|
|
177
177
|
"@gjsify/oxfmt-native": {
|