extension 4.1.19 → 4.1.21
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/README.md +23 -20
- package/bin/extension.cjs +2 -0
- package/dist/84.cjs +2 -1
- package/dist/browsers.cjs +179 -95
- package/dist/cli.cjs +454 -238
- package/dist/extension/browsers/__spec__/safari-fake-tools.d.ts +27 -0
- package/dist/extension/browsers/browsers-lib/constants.d.ts +0 -2
- package/dist/extension/browsers/browsers-lib/messages.d.ts +2 -1
- package/dist/extension/browsers/browsers-lib/resolve-profile.d.ts +0 -35
- package/dist/extension/browsers/browsers-lib/write-json-atomic.d.ts +1 -0
- package/dist/extension/browsers/browsers-types.d.ts +0 -89
- package/dist/extension/browsers/index.d.ts +1 -29
- package/dist/extension/browsers/run-chromium/chromium-launch/extension-output-path.d.ts +0 -6
- package/dist/extension/browsers/run-chromium/chromium-launch/index.d.ts +0 -9
- package/dist/extension/browsers/run-chromium/chromium-types.d.ts +0 -11
- package/dist/extension/browsers/run-safari/safari-launch/index.d.ts +2 -7
- package/dist/extension/browsers/run-safari/safari-launch/tools.d.ts +17 -0
- package/dist/extension/browsers/run-safari/safari-packager.d.ts +2 -16
- package/dist/extension/browsers/run-safari/safari-types.d.ts +2 -1
- package/dist/extension/commands/act.d.ts +0 -6
- package/dist/extension/commands/doctor.d.ts +0 -7
- package/dist/extension/commands/publish.d.ts +0 -6
- package/dist/extension/config-types.d.ts +5 -2
- package/dist/extension/helpers/cli-explicit.d.ts +0 -15
- package/dist/extension/helpers/messages.d.ts +7 -0
- package/dist/extension/helpers/node-version-guard.d.ts +7 -2
- package/dist/extension/helpers/template-catalog.d.ts +1 -1
- package/dist/extension/helpers/template-corpus.generated.d.ts +1 -1
- package/dist/extension/helpers/vendors.d.ts +1 -0
- package/package.json +4 -6
package/dist/cli.cjs
CHANGED
|
@@ -741,16 +741,19 @@ var __webpack_modules__ = {
|
|
|
741
741
|
const tail = outputTail.trim().length ? `\n${pintor__rspack_import_8_default().gray('── last output ──')}\n${outputTail}` : `\n${pintor__rspack_import_8_default().gray('(no output captured)')}`;
|
|
742
742
|
return `${getLoggingPrefix('error')} Safari packaging tool ${pintor__rspack_import_8_default().underline(tool)} failed (${pintor__rspack_import_8_default().red(code)}).${tail}`;
|
|
743
743
|
}
|
|
744
|
+
const SAFARI_KEPT_ON_PURPOSE = {
|
|
745
|
+
world: 'Safari has honored the MAIN world since Safari 18, so the key stays'
|
|
746
|
+
};
|
|
744
747
|
function safariConverterWarnings(warnings) {
|
|
745
|
-
|
|
748
|
+
const kept = warnings.map((line)=>line.trim()).filter((line)=>line in SAFARI_KEPT_ON_PURPOSE);
|
|
749
|
+
const keptLines = kept.map((line)=>` ${pintor__rspack_import_8_default().yellow(line)} ${pintor__rspack_import_8_default().gray(SAFARI_KEPT_ON_PURPOSE[line])}`).join('\n');
|
|
750
|
+
return `${getLoggingPrefix('warn')} safari-web-extension-converter reported ${pintor__rspack_import_8_default().yellow(String(warnings.length))} ${1 === warnings.length ? 'warning' : 'warnings'}, some manifest keys/APIs may not be supported by Safari:\n` + warnings.map((line)=>` ${pintor__rspack_import_8_default().gray('•')} ${line}`).join('\n') + (kept.length ? `\nExtension.js kept ${1 === kept.length ? 'one of these keys' : 'some of these keys'} on purpose:\n${keptLines}` : '');
|
|
746
751
|
}
|
|
747
752
|
function safariDefaultBundleIdNote(bundleId) {
|
|
748
|
-
return `${getLoggingPrefix('
|
|
753
|
+
return `${getLoggingPrefix('info')} Bundle id ${pintor__rspack_import_8_default().gray(bundleId)} (generated, set ${pintor__rspack_import_8_default().blue('--bundle-id')} to own it).`;
|
|
749
754
|
}
|
|
750
|
-
function safariOpenHint(appPath, appName
|
|
751
|
-
|
|
752
|
-
if (signed) return launch + `Then turn on ${pintor__rspack_import_8_default().yellow(appName)} in Safari ▸ Settings ▸ Extensions.`;
|
|
753
|
-
return launch + `Then enable ${pintor__rspack_import_8_default().yellow(appName)} via Safari ▸ Develop ▸ ` + `${pintor__rspack_import_8_default().yellow('Allow Unsigned Extensions')} and Safari ▸ Settings ▸ Extensions.`;
|
|
755
|
+
function safariOpenHint(appPath, appName) {
|
|
756
|
+
return `${getLoggingPrefix('info')} Launch it once to register with Safari: ${pintor__rspack_import_8_default().blue('open')} ${pintor__rspack_import_8_default().underline(`"${appPath}"`)}\nThen turn on ${pintor__rspack_import_8_default().yellow(appName)} in Safari ▸ Settings ▸ Extensions.`;
|
|
754
757
|
}
|
|
755
758
|
function safariDryRunNotBuilding() {
|
|
756
759
|
return `${getLoggingPrefix('info')} Dry run: skip the Safari app build.`;
|
|
@@ -762,17 +765,21 @@ var __webpack_modules__ = {
|
|
|
762
765
|
return `${getLoggingPrefix('info')} xcodebuild: ${pintor__rspack_import_8_default().gray(cmd)}.`;
|
|
763
766
|
}
|
|
764
767
|
function safariNextSteps(appName, signed) {
|
|
765
|
-
|
|
766
|
-
|
|
768
|
+
const steps = `${getLoggingPrefix('info')} One-time setup to load ${pintor__rspack_import_8_default().yellow(appName)} in Safari:\n ${pintor__rspack_import_8_default().gray('1.')} Safari ▸ Settings ▸ Extensions ▸ turn on ${pintor__rspack_import_8_default().yellow(appName)}\n`;
|
|
769
|
+
if (signed) return steps + ` ${pintor__rspack_import_8_default().gray('→')} Signed with your team id, so the identity is stable for distribution.`;
|
|
770
|
+
return steps + ` ${pintor__rspack_import_8_default().gray('→')} Ad-hoc signed, so it stays enabled across restarts.`;
|
|
767
771
|
}
|
|
768
772
|
function safariRegistered(appName) {
|
|
769
773
|
return `${getLoggingPrefix('success')} Safari recognizes ${pintor__rspack_import_8_default().yellow(appName)}, finish enabling it with the steps above.`;
|
|
770
774
|
}
|
|
775
|
+
function safariPidUnresolved(appName) {
|
|
776
|
+
return `${getLoggingPrefix('warn')} Opened ${pintor__rspack_import_8_default().yellow(appName)} but could not find its process id.\nThe session's ready.json names the app and the extension without a ${pintor__rspack_import_8_default().gray('browserPid')}.`;
|
|
777
|
+
}
|
|
771
778
|
function safariNotYetRegistered(appName) {
|
|
772
779
|
return `${getLoggingPrefix('info')} Safari hasn't picked up ${pintor__rspack_import_8_default().yellow(appName)} yet.\nOpen the app once, then check Safari ▸ Settings ▸ Extensions.`;
|
|
773
780
|
}
|
|
774
781
|
function safariRebuilt(appName) {
|
|
775
|
-
return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_8_default().yellow(appName)}
|
|
782
|
+
return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_8_default().yellow(appName)}.`;
|
|
776
783
|
}
|
|
777
784
|
function safariProjectStale() {
|
|
778
785
|
return `${getLoggingPrefix('info')} Regenerating the Xcode project, manifest.json or identity options changed since it was generated.`;
|
|
@@ -1029,6 +1036,7 @@ var __webpack_modules__ = {
|
|
|
1029
1036
|
jR: ()=>safariBuilding,
|
|
1030
1037
|
jc: ()=>safariProjectStale,
|
|
1031
1038
|
jk: ()=>requireGeckoBinaryForGeckoBased,
|
|
1039
|
+
jn: ()=>safariPidUnresolved,
|
|
1032
1040
|
kK: ()=>preferringSystemBrowserOverSnapshot,
|
|
1033
1041
|
l9: ()=>parsingPacketError,
|
|
1034
1042
|
lO: ()=>firefoxDryRunArgs,
|
|
@@ -1401,6 +1409,7 @@ var __webpack_modules__ = {
|
|
|
1401
1409
|
"./browsers/browsers-lib/ready-stamp.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1402
1410
|
var node_fs__rspack_import_0 = __webpack_require__("node:fs");
|
|
1403
1411
|
var node_path__rspack_import_1 = __webpack_require__("node:path");
|
|
1412
|
+
var _write_json_atomic__rspack_import_2 = __webpack_require__("./browsers/browsers-lib/write-json-atomic.ts");
|
|
1404
1413
|
function readyPathFor(extensionOutputPath) {
|
|
1405
1414
|
return node_path__rspack_import_1.join(node_path__rspack_import_1.dirname(extensionOutputPath), 'extension-js', node_path__rspack_import_1.basename(extensionOutputPath), 'ready.json');
|
|
1406
1415
|
}
|
|
@@ -1412,7 +1421,7 @@ var __webpack_modules__ = {
|
|
|
1412
1421
|
const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
|
|
1413
1422
|
if (ready.rdpPort === rdpPort) return;
|
|
1414
1423
|
ready.rdpPort = rdpPort;
|
|
1415
|
-
|
|
1424
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1416
1425
|
} catch {}
|
|
1417
1426
|
}
|
|
1418
1427
|
function stampReadyBrowserLaunch(extensionOutputPath, details) {
|
|
@@ -1430,7 +1439,7 @@ var __webpack_modules__ = {
|
|
|
1430
1439
|
if (binary) ready.binary = binary;
|
|
1431
1440
|
const provenance = String(details?.binaryProvenance || '').trim();
|
|
1432
1441
|
if (provenance) ready.binaryProvenance = provenance;
|
|
1433
|
-
|
|
1442
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1434
1443
|
} catch {}
|
|
1435
1444
|
}
|
|
1436
1445
|
function stampReadyExtensionId(extensionOutputPath, extensionId) {
|
|
@@ -1442,7 +1451,7 @@ var __webpack_modules__ = {
|
|
|
1442
1451
|
const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
|
|
1443
1452
|
if (ready.extensionId === id) return;
|
|
1444
1453
|
ready.extensionId = id;
|
|
1445
|
-
|
|
1454
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1446
1455
|
} catch {}
|
|
1447
1456
|
}
|
|
1448
1457
|
function stampReadyExtensionLoadRefused(extensionOutputPath, reason) {
|
|
@@ -1457,7 +1466,7 @@ var __webpack_modules__ = {
|
|
|
1457
1466
|
ready.message = `${browserLabel.charAt(0).toUpperCase() + browserLabel.slice(1)} refused to load the extension at ${extensionOutputPath}${reason ? `: ${reason}` : ''}`;
|
|
1458
1467
|
ready.extensionLoadRefusedAt = new Date().toISOString();
|
|
1459
1468
|
if (reason) ready.extensionLoadRefusedReason = reason;
|
|
1460
|
-
|
|
1469
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1461
1470
|
} catch {}
|
|
1462
1471
|
}
|
|
1463
1472
|
function stampReadyProfileLocked(extensionOutputPath, details) {
|
|
@@ -1471,7 +1480,7 @@ var __webpack_modules__ = {
|
|
|
1471
1480
|
ready.message = String(details?.message || '').trim() || 'the browser profile is already in use by another session';
|
|
1472
1481
|
ready.profileLockedAt = new Date().toISOString();
|
|
1473
1482
|
if (details?.owner) ready.profileLockOwner = details.owner;
|
|
1474
|
-
|
|
1483
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1475
1484
|
} catch {}
|
|
1476
1485
|
}
|
|
1477
1486
|
function stampReadyBrowserExited(extensionOutputPath, code) {
|
|
@@ -1487,7 +1496,7 @@ var __webpack_modules__ = {
|
|
|
1487
1496
|
ready.code = 'browser_exited';
|
|
1488
1497
|
ready.message = `the ${ready.browser || 'browser'} process exited (code ${code ?? 'unknown'}); nothing is running`;
|
|
1489
1498
|
}
|
|
1490
|
-
|
|
1499
|
+
(0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
|
|
1491
1500
|
} catch {}
|
|
1492
1501
|
}
|
|
1493
1502
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -1951,6 +1960,27 @@ var __webpack_modules__ = {
|
|
|
1951
1960
|
yO: ()=>probeChromiumBinaryVersion
|
|
1952
1961
|
});
|
|
1953
1962
|
},
|
|
1963
|
+
"./browsers/browsers-lib/write-json-atomic.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1964
|
+
var node_fs__rspack_import_0 = __webpack_require__("node:fs");
|
|
1965
|
+
function writeJsonAtomic(filePath, value) {
|
|
1966
|
+
const tmpPath = `${filePath}.tmp-${process.pid}`;
|
|
1967
|
+
try {
|
|
1968
|
+
node_fs__rspack_import_0.writeFileSync(tmpPath, JSON.stringify(value, null, 2), 'utf-8');
|
|
1969
|
+
node_fs__rspack_import_0.renameSync(tmpPath, filePath);
|
|
1970
|
+
return true;
|
|
1971
|
+
} catch {
|
|
1972
|
+
try {
|
|
1973
|
+
node_fs__rspack_import_0.rmSync(tmpPath, {
|
|
1974
|
+
force: true
|
|
1975
|
+
});
|
|
1976
|
+
} catch {}
|
|
1977
|
+
return false;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
1981
|
+
Z: ()=>writeJsonAtomic
|
|
1982
|
+
});
|
|
1983
|
+
},
|
|
1954
1984
|
"./browsers/browsers-lib/wsl-support.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1955
1985
|
__webpack_require__.d(__webpack_exports__, {
|
|
1956
1986
|
qc: ()=>external_wsl_support_namespaceObject.hasGuiDisplay,
|
|
@@ -5803,7 +5833,6 @@ var __webpack_modules__ = {
|
|
|
5803
5833
|
QW: ()=>safariBuildPreflight,
|
|
5804
5834
|
iF: ()=>safariPreflightError
|
|
5805
5835
|
});
|
|
5806
|
-
var external_node_child_process_ = __webpack_require__("node:child_process");
|
|
5807
5836
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
5808
5837
|
var messaging = __webpack_require__("./helpers/messaging.ts");
|
|
5809
5838
|
var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
|
|
@@ -5817,6 +5846,7 @@ var __webpack_modules__ = {
|
|
|
5817
5846
|
(0, messaging._w)(messages.Aj(xcodebuildCmd));
|
|
5818
5847
|
}
|
|
5819
5848
|
var safari_config = __webpack_require__("./browsers/run-safari/safari-launch/safari-config.ts");
|
|
5849
|
+
var external_node_child_process_ = __webpack_require__("node:child_process");
|
|
5820
5850
|
function isMacOS() {
|
|
5821
5851
|
return 'darwin' === process.platform;
|
|
5822
5852
|
}
|
|
@@ -5874,20 +5904,6 @@ var __webpack_modules__ = {
|
|
|
5874
5904
|
ok: Boolean(converter && xcodebuild)
|
|
5875
5905
|
};
|
|
5876
5906
|
}
|
|
5877
|
-
function fallbackLogger() {
|
|
5878
|
-
return {
|
|
5879
|
-
info: (...a)=>(0, messaging._w)(...a),
|
|
5880
|
-
warn: (...a)=>(0, messaging.mR)(...a),
|
|
5881
|
-
error: (...a)=>(0, messaging.Gg)(...a),
|
|
5882
|
-
debug: (...a)=>console?.debug?.(...a)
|
|
5883
|
-
};
|
|
5884
|
-
}
|
|
5885
|
-
function isTestEnv() {
|
|
5886
|
-
return Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
|
|
5887
|
-
}
|
|
5888
|
-
function delay(ms) {
|
|
5889
|
-
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
5890
|
-
}
|
|
5891
5907
|
const TOOL_TAIL_LINES = 50;
|
|
5892
5908
|
const TOOL_TAIL_BYTES = 8192;
|
|
5893
5909
|
function toolOutputTail(output) {
|
|
@@ -5926,18 +5942,85 @@ var __webpack_modules__ = {
|
|
|
5926
5942
|
}));
|
|
5927
5943
|
});
|
|
5928
5944
|
}
|
|
5945
|
+
async function resolvePidForBundle(bundleId) {
|
|
5946
|
+
for(let attempt = 0; attempt < 10; attempt += 1){
|
|
5947
|
+
const pid = await new Promise((resolve)=>{
|
|
5948
|
+
const child = (0, external_node_child_process_.spawn)("osascript", [
|
|
5949
|
+
'-e',
|
|
5950
|
+
`tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
|
|
5951
|
+
]);
|
|
5952
|
+
let out = '';
|
|
5953
|
+
child.stdout.on('data', (d)=>out += String(d));
|
|
5954
|
+
child.on('error', ()=>resolve(null));
|
|
5955
|
+
child.on('close', ()=>{
|
|
5956
|
+
const n = Number(String(out).trim());
|
|
5957
|
+
resolve(Number.isFinite(n) && n > 0 ? n : null);
|
|
5958
|
+
});
|
|
5959
|
+
});
|
|
5960
|
+
if (pid) return pid;
|
|
5961
|
+
await new Promise((r)=>setTimeout(r, 500));
|
|
5962
|
+
}
|
|
5963
|
+
return null;
|
|
5964
|
+
}
|
|
5965
|
+
function createSafariTools() {
|
|
5966
|
+
return {
|
|
5967
|
+
detectToolchain: ()=>detectSafariToolchain(),
|
|
5968
|
+
runConverter: (args)=>runTool('xcrun', args),
|
|
5969
|
+
runXcodebuild: (args)=>runTool('xcodebuild', args),
|
|
5970
|
+
openApp: (target)=>runTool('open', [
|
|
5971
|
+
target
|
|
5972
|
+
]),
|
|
5973
|
+
openSafari: (binary)=>runTool('open', [
|
|
5974
|
+
'-a',
|
|
5975
|
+
binary
|
|
5976
|
+
]),
|
|
5977
|
+
resolvePid: (bundleId)=>resolvePidForBundle(bundleId),
|
|
5978
|
+
pluginkitList: async ()=>{
|
|
5979
|
+
const { ok, output } = await runTool('pluginkit', [
|
|
5980
|
+
'-m'
|
|
5981
|
+
], {
|
|
5982
|
+
quiet: true
|
|
5983
|
+
});
|
|
5984
|
+
return ok ? output : '';
|
|
5985
|
+
}
|
|
5986
|
+
};
|
|
5987
|
+
}
|
|
5988
|
+
function fallbackLogger() {
|
|
5989
|
+
return {
|
|
5990
|
+
info: (...a)=>(0, messaging._w)(...a),
|
|
5991
|
+
warn: (...a)=>(0, messaging.mR)(...a),
|
|
5992
|
+
error: (...a)=>(0, messaging.Gg)(...a),
|
|
5993
|
+
debug: (...a)=>console?.debug?.(...a)
|
|
5994
|
+
};
|
|
5995
|
+
}
|
|
5996
|
+
function delay(ms) {
|
|
5997
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
5998
|
+
}
|
|
5929
5999
|
function converterWarnings(output) {
|
|
5930
|
-
|
|
6000
|
+
const lines = output.split(/\r?\n/);
|
|
6001
|
+
const collected = [];
|
|
6002
|
+
let inWarning = false;
|
|
6003
|
+
for (const line of lines){
|
|
6004
|
+
const trimmed = line.trim();
|
|
6005
|
+
if (/warning/i.test(line)) {
|
|
6006
|
+
inWarning = true;
|
|
6007
|
+
if (trimmed.length > 0) collected.push(trimmed);
|
|
6008
|
+
continue;
|
|
6009
|
+
}
|
|
6010
|
+
const isContinuation = inWarning && /^\s/.test(line) && trimmed.length > 0;
|
|
6011
|
+
if (isContinuation) {
|
|
6012
|
+
collected.push(trimmed);
|
|
6013
|
+
continue;
|
|
6014
|
+
}
|
|
6015
|
+
inWarning = false;
|
|
6016
|
+
}
|
|
6017
|
+
return collected;
|
|
5931
6018
|
}
|
|
5932
|
-
async function confirmRegisteredWithSafari(bundleIdentifier) {
|
|
6019
|
+
async function confirmRegisteredWithSafari(tools, bundleIdentifier) {
|
|
5933
6020
|
const needle = `${bundleIdentifier}.Extension`;
|
|
5934
6021
|
for(let attempt = 0; attempt < 6; attempt += 1){
|
|
5935
|
-
const
|
|
5936
|
-
|
|
5937
|
-
], {
|
|
5938
|
-
quiet: true
|
|
5939
|
-
});
|
|
5940
|
-
if (ok && output.includes(needle)) return true;
|
|
6022
|
+
const listing = await tools.pluginkitList();
|
|
6023
|
+
if (listing.includes(needle)) return true;
|
|
5941
6024
|
await delay(800);
|
|
5942
6025
|
}
|
|
5943
6026
|
return false;
|
|
@@ -5952,6 +6035,10 @@ var __webpack_modules__ = {
|
|
|
5952
6035
|
macOsOnly: config.macOsOnly
|
|
5953
6036
|
};
|
|
5954
6037
|
}
|
|
6038
|
+
function completePackage(config, logger, mode) {
|
|
6039
|
+
if ('full' === mode && config.bundleIdDerived) logger.info?.(messages.$q(config.bundleIdentifier));
|
|
6040
|
+
return describePackage(config);
|
|
6041
|
+
}
|
|
5955
6042
|
function safariPreflightError() {
|
|
5956
6043
|
const tc = detectSafariToolchain();
|
|
5957
6044
|
if (!tc.platformOk) return messages.G3(process.platform);
|
|
@@ -5975,26 +6062,6 @@ var __webpack_modules__ = {
|
|
|
5975
6062
|
severity: 'ok'
|
|
5976
6063
|
};
|
|
5977
6064
|
}
|
|
5978
|
-
async function resolvePidForBundle(bundleId) {
|
|
5979
|
-
for(let attempt = 0; attempt < 10; attempt += 1){
|
|
5980
|
-
const pid = await new Promise((resolve)=>{
|
|
5981
|
-
const child = (0, external_node_child_process_.spawn)("osascript", [
|
|
5982
|
-
'-e',
|
|
5983
|
-
`tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
|
|
5984
|
-
]);
|
|
5985
|
-
let out = '';
|
|
5986
|
-
child.stdout.on('data', (d)=>out += String(d));
|
|
5987
|
-
child.on('error', ()=>resolve(null));
|
|
5988
|
-
child.on('close', ()=>{
|
|
5989
|
-
const n = Number(String(out).trim());
|
|
5990
|
-
resolve(Number.isFinite(n) && n > 0 ? n : null);
|
|
5991
|
-
});
|
|
5992
|
-
});
|
|
5993
|
-
if (pid) return pid;
|
|
5994
|
-
await new Promise((r)=>setTimeout(r, 500));
|
|
5995
|
-
}
|
|
5996
|
-
return null;
|
|
5997
|
-
}
|
|
5998
6065
|
async function announceSafariDevSession(host, config, appPath) {
|
|
5999
6066
|
try {
|
|
6000
6067
|
await (0, banner.ai)({
|
|
@@ -6010,16 +6077,16 @@ var __webpack_modules__ = {
|
|
|
6010
6077
|
} catch {}
|
|
6011
6078
|
}
|
|
6012
6079
|
async function runSafariPipeline(compilation, host, logger, mode) {
|
|
6080
|
+
const tools = host.tools || createSafariTools();
|
|
6013
6081
|
const config = (0, safari_config.F7)(compilation, host);
|
|
6014
6082
|
const converterArgs = (0, safari_config.w7)(config);
|
|
6015
6083
|
const xcodebuildArgs = (0, safari_config.vx)(config);
|
|
6016
6084
|
const isSigned = Boolean(config.developmentTeam);
|
|
6017
|
-
if (
|
|
6018
|
-
if (host.dryRun || isTestEnv()) {
|
|
6085
|
+
if (host.dryRun) {
|
|
6019
6086
|
logSafariDryRun(`xcrun ${converterArgs.join(' ')}`, `xcodebuild ${xcodebuildArgs.join(' ')}`);
|
|
6020
|
-
return
|
|
6087
|
+
return completePackage(config, logger, mode);
|
|
6021
6088
|
}
|
|
6022
|
-
const toolchain =
|
|
6089
|
+
const toolchain = tools.detectToolchain();
|
|
6023
6090
|
if (!toolchain.platformOk) {
|
|
6024
6091
|
logger.warn?.(messages.G3(process.platform));
|
|
6025
6092
|
return describePackage(config);
|
|
@@ -6043,7 +6110,7 @@ var __webpack_modules__ = {
|
|
|
6043
6110
|
}
|
|
6044
6111
|
const { saved, restore } = (0, safari_config._D)(config);
|
|
6045
6112
|
logger.info?.(messages.uK(config.extensionDir));
|
|
6046
|
-
const converted = await
|
|
6113
|
+
const converted = await tools.runConverter(converterArgs);
|
|
6047
6114
|
if (!converted.ok) {
|
|
6048
6115
|
const tail = toolOutputTail(converted.output);
|
|
6049
6116
|
logger.error?.(messages.A('safari-web-extension-converter', converted.code, tail));
|
|
@@ -6058,9 +6125,9 @@ var __webpack_modules__ = {
|
|
|
6058
6125
|
if (preservedKeys.length > 0) logger.info?.(messages.oB(preservedKeys));
|
|
6059
6126
|
(0, safari_config.x7)(config);
|
|
6060
6127
|
logger.info?.(messages.l_(config.projectLocation));
|
|
6061
|
-
} else logger.info?.(messages.oM());
|
|
6128
|
+
} else if ('full' === mode) logger.info?.(messages.oM());
|
|
6062
6129
|
if ('full' === mode) logger.info?.(messages.jR((0, safari_config.aD)(config)));
|
|
6063
|
-
const built = await
|
|
6130
|
+
const built = await tools.runXcodebuild(xcodebuildArgs);
|
|
6064
6131
|
if (!built.ok) {
|
|
6065
6132
|
const tail = toolOutputTail(built.output);
|
|
6066
6133
|
logger.error?.(messages.A('xcodebuild', built.code, tail));
|
|
@@ -6073,32 +6140,28 @@ var __webpack_modules__ = {
|
|
|
6073
6140
|
}
|
|
6074
6141
|
logger.info?.(messages.Rl(appPath));
|
|
6075
6142
|
if (!config.open) {
|
|
6076
|
-
logger.info?.(messages.aO(appPath, config.appName
|
|
6143
|
+
logger.info?.(messages.aO(appPath, config.appName));
|
|
6077
6144
|
if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
|
|
6078
|
-
return
|
|
6145
|
+
return completePackage(config, logger, mode);
|
|
6079
6146
|
}
|
|
6080
6147
|
const target = external_node_fs_.existsSync(appPath) ? appPath : (0, safari_config.Gi)(config);
|
|
6081
6148
|
logger.info?.(messages.fw(target));
|
|
6082
|
-
await
|
|
6083
|
-
|
|
6084
|
-
]);
|
|
6085
|
-
if (config.safariBinary) await runTool('open', [
|
|
6086
|
-
'-a',
|
|
6087
|
-
config.safariBinary
|
|
6088
|
-
]);
|
|
6149
|
+
await tools.openApp(target);
|
|
6150
|
+
if (config.safariBinary) await tools.openSafari(config.safariBinary);
|
|
6089
6151
|
const raisedBundleId = config.safariBinary ? 'com.apple.Safari' : config.bundleIdentifier;
|
|
6090
|
-
const browserPid = await
|
|
6091
|
-
|
|
6092
|
-
browserPid,
|
|
6152
|
+
const browserPid = await tools.resolvePid(raisedBundleId);
|
|
6153
|
+
(0, ready_stamp.M)(config.extensionDir, {
|
|
6154
|
+
browserPid: browserPid || void 0,
|
|
6093
6155
|
binary: config.safariBinary || appPath,
|
|
6094
6156
|
binaryProvenance: config.safariBinary ? 'pinned' : 'system',
|
|
6095
6157
|
extensionId: `${config.bundleIdentifier}.Extension`
|
|
6096
6158
|
});
|
|
6159
|
+
if (!browserPid) logger.warn?.(messages.jn(config.appName));
|
|
6097
6160
|
logger.info?.(messages.fO(config.appName, isSigned));
|
|
6098
|
-
if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
|
|
6161
|
+
if (await confirmRegisteredWithSafari(tools, config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
|
|
6099
6162
|
else logger.info?.(messages.qY(config.appName));
|
|
6100
6163
|
if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
|
|
6101
|
-
return
|
|
6164
|
+
return completePackage(config, logger, mode);
|
|
6102
6165
|
}
|
|
6103
6166
|
async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
|
|
6104
6167
|
const compilation = {
|
|
@@ -6218,36 +6281,56 @@ var __webpack_modules__ = {
|
|
|
6218
6281
|
function manifestFingerprintPath(config) {
|
|
6219
6282
|
return node_path__rspack_import_1.join(config.projectLocation, '.manifest-fingerprint');
|
|
6220
6283
|
}
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
if ('object' != typeof obj) return JSON.stringify(obj);
|
|
6224
|
-
if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(',')}]`;
|
|
6225
|
-
const keys = Object.keys(obj).sort();
|
|
6226
|
-
return `{${keys.map((k)=>`${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
|
|
6227
|
-
}
|
|
6228
|
-
function readManifestRaw(extensionDir) {
|
|
6284
|
+
const HOT_UPDATE_DIR = 'hot';
|
|
6285
|
+
function topLevelEntries(extensionDir) {
|
|
6229
6286
|
try {
|
|
6230
|
-
return node_fs__rspack_import_0.
|
|
6287
|
+
return node_fs__rspack_import_0.readdirSync(extensionDir).filter((entry)=>entry !== HOT_UPDATE_DIR).sort();
|
|
6231
6288
|
} catch {
|
|
6232
|
-
return
|
|
6289
|
+
return [];
|
|
6233
6290
|
}
|
|
6234
6291
|
}
|
|
6235
|
-
function
|
|
6292
|
+
function iconsFingerprint(extensionDir) {
|
|
6236
6293
|
try {
|
|
6237
|
-
|
|
6294
|
+
const raw = node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(extensionDir, 'manifest.json'), 'utf8');
|
|
6295
|
+
const icons = JSON.parse(raw).icons;
|
|
6296
|
+
if (!icons || 'object' != typeof icons) return '';
|
|
6297
|
+
return Object.keys(icons).sort().map((size)=>`${size}:${String(icons[size])}`).join(',');
|
|
6238
6298
|
} catch {
|
|
6239
|
-
return
|
|
6299
|
+
return '';
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
function asRecord(value) {
|
|
6303
|
+
return value && 'object' == typeof value && !Array.isArray(value) ? value : {};
|
|
6304
|
+
}
|
|
6305
|
+
function judgedManifestSurface(extensionDir) {
|
|
6306
|
+
const manifest = readManifest(extensionDir);
|
|
6307
|
+
const parts = [
|
|
6308
|
+
Object.keys(manifest).sort().join(',')
|
|
6309
|
+
];
|
|
6310
|
+
for (const listName of [
|
|
6311
|
+
'permissions',
|
|
6312
|
+
'optional_permissions'
|
|
6313
|
+
]){
|
|
6314
|
+
const list = manifest[listName];
|
|
6315
|
+
const names = Array.isArray(list) ? list.filter((entry)=>'string' == typeof entry).sort() : [];
|
|
6316
|
+
parts.push(`${listName}:${names.join(',')}`);
|
|
6240
6317
|
}
|
|
6318
|
+
const contentScripts = manifest.content_scripts;
|
|
6319
|
+
if (Array.isArray(contentScripts)) parts.push(contentScripts.map((entry)=>Object.keys(asRecord(entry)).sort().join('+')).join(';'));
|
|
6320
|
+
parts.push(Object.keys(asRecord(manifest.options_ui)).sort().join(','));
|
|
6321
|
+
return parts.join('|');
|
|
6241
6322
|
}
|
|
6242
6323
|
function composeProjectFingerprint(config) {
|
|
6243
6324
|
return JSON.stringify({
|
|
6244
|
-
v:
|
|
6325
|
+
v: 4,
|
|
6245
6326
|
identity: {
|
|
6246
6327
|
appName: config.appName,
|
|
6247
6328
|
bundleId: config.bundleIdentifier,
|
|
6248
6329
|
macOsOnly: config.macOsOnly
|
|
6249
6330
|
},
|
|
6250
|
-
|
|
6331
|
+
entries: topLevelEntries(config.extensionDir),
|
|
6332
|
+
icons: iconsFingerprint(config.extensionDir),
|
|
6333
|
+
judged: judgedManifestSurface(config.extensionDir)
|
|
6251
6334
|
});
|
|
6252
6335
|
}
|
|
6253
6336
|
function saveManifestFingerprint(config) {
|
|
@@ -6330,7 +6413,7 @@ var __webpack_modules__ = {
|
|
|
6330
6413
|
return Object.fromEntries(Object.entries(input).filter(([, value])=>void 0 !== value));
|
|
6331
6414
|
}
|
|
6332
6415
|
function createSafariPackager(options = {}) {
|
|
6333
|
-
const { browser = 'safari', noOpen = true, dryRun = false, logger, ...identity } = options;
|
|
6416
|
+
const { browser = 'safari', noOpen = true, dryRun = false, logger, tools, ...identity } = options;
|
|
6334
6417
|
return async (distPath, mode = 'full', overrides)=>await (0, _safari_launch__rspack_import_0.Ah)({
|
|
6335
6418
|
extension: [
|
|
6336
6419
|
distPath
|
|
@@ -6338,6 +6421,7 @@ var __webpack_modules__ = {
|
|
|
6338
6421
|
browser,
|
|
6339
6422
|
noOpen,
|
|
6340
6423
|
dryRun,
|
|
6424
|
+
tools,
|
|
6341
6425
|
...withoutUndefined(identity),
|
|
6342
6426
|
...withoutUndefined(overrides)
|
|
6343
6427
|
}, distPath, logger, mode);
|
|
@@ -6663,23 +6747,62 @@ var __webpack_modules__ = {
|
|
|
6663
6747
|
"./index.ts" (__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
6664
6748
|
const MIN_NODE_MAJOR = 22;
|
|
6665
6749
|
const MIN_NODE_MINOR = 12;
|
|
6750
|
+
const MIN_DENO_MAJOR = 2;
|
|
6751
|
+
const MIN_DENO_MINOR = 5;
|
|
6752
|
+
const MIN_BUN_MAJOR = 1;
|
|
6753
|
+
const MIN_BUN_MINOR = 2;
|
|
6754
|
+
const BLOCKED_DENO_VERSIONS = [
|
|
6755
|
+
'2.8.0'
|
|
6756
|
+
];
|
|
6757
|
+
function meetsFloor(version, major, minor) {
|
|
6758
|
+
const [actualMajor, actualMinor] = version.split('.').map((part)=>parseInt(part, 10));
|
|
6759
|
+
if (!Number.isFinite(actualMajor)) return true;
|
|
6760
|
+
if (actualMajor !== major) return actualMajor > major;
|
|
6761
|
+
return (Number.isFinite(actualMinor) ? actualMinor : 0) >= minor;
|
|
6762
|
+
}
|
|
6666
6763
|
function isSupportedNodeVersion(version) {
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6764
|
+
return meetsFloor(version, MIN_NODE_MAJOR, MIN_NODE_MINOR);
|
|
6765
|
+
}
|
|
6766
|
+
function isSupportedDenoVersion(version) {
|
|
6767
|
+
if (BLOCKED_DENO_VERSIONS.includes(version)) return false;
|
|
6768
|
+
return meetsFloor(version, MIN_DENO_MAJOR, MIN_DENO_MINOR);
|
|
6769
|
+
}
|
|
6770
|
+
function isSupportedBunVersion(version) {
|
|
6771
|
+
return meetsFloor(version, MIN_BUN_MAJOR, MIN_BUN_MINOR);
|
|
6671
6772
|
}
|
|
6672
6773
|
function detectBunVersion(versions = process.versions) {
|
|
6673
6774
|
const bunVersion = versions.bun;
|
|
6674
6775
|
return 'string' == typeof bunVersion && bunVersion.length > 0 ? bunVersion : void 0;
|
|
6675
6776
|
}
|
|
6676
|
-
function
|
|
6677
|
-
|
|
6777
|
+
function detectDenoVersion(versions = process.versions) {
|
|
6778
|
+
const denoVersion = versions.deno;
|
|
6779
|
+
return 'string' == typeof denoVersion && denoVersion.length > 0 ? denoVersion : void 0;
|
|
6780
|
+
}
|
|
6781
|
+
function unsupportedDenoVersionMessage(version) {
|
|
6782
|
+
if (BLOCKED_DENO_VERSIONS.includes(version)) return `[Extension.js] Deno ${version} cannot load node:querystring inside the bundler, so every build fails on it. Deno fixed that in 2.8.1. Run deno upgrade to build the extension on Deno.`;
|
|
6783
|
+
return `[Extension.js] Requires Deno >= ${MIN_DENO_MAJOR}.${MIN_DENO_MINOR} (you are on ${version}). Run deno upgrade, or run the extension CLI on Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} instead.`;
|
|
6784
|
+
}
|
|
6785
|
+
function unsupportedBunVersionMessage(version) {
|
|
6786
|
+
return `[Extension.js] Requires Bun >= ${MIN_BUN_MAJOR}.${MIN_BUN_MINOR} (you are on ${version}). Run bun upgrade, or run the extension CLI on Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} instead.`;
|
|
6787
|
+
}
|
|
6788
|
+
function unsupportedNodeVersionMessage(version) {
|
|
6678
6789
|
return `[Extension.js] Requires Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} (you are on ${version}). Upgrade Node.js to run the extension CLI.`;
|
|
6679
6790
|
}
|
|
6680
|
-
function enforceSupportedNodeVersion(version = process.versions.node, bunVersion = detectBunVersion()) {
|
|
6681
|
-
if (
|
|
6682
|
-
|
|
6791
|
+
function enforceSupportedNodeVersion(version = process.versions.node, bunVersion = detectBunVersion(), denoVersion = detectDenoVersion()) {
|
|
6792
|
+
if (denoVersion) {
|
|
6793
|
+
if (isSupportedDenoVersion(denoVersion)) return;
|
|
6794
|
+
console.error(unsupportedDenoVersionMessage(denoVersion));
|
|
6795
|
+
process.exit(1);
|
|
6796
|
+
return;
|
|
6797
|
+
}
|
|
6798
|
+
if (bunVersion) {
|
|
6799
|
+
if (isSupportedBunVersion(bunVersion)) return;
|
|
6800
|
+
console.error(unsupportedBunVersionMessage(bunVersion));
|
|
6801
|
+
process.exit(1);
|
|
6802
|
+
return;
|
|
6803
|
+
}
|
|
6804
|
+
if (isSupportedNodeVersion(version)) return;
|
|
6805
|
+
console.error(unsupportedNodeVersionMessage(version));
|
|
6683
6806
|
process.exit(1);
|
|
6684
6807
|
}
|
|
6685
6808
|
enforceSupportedNodeVersion();
|
|
@@ -7075,7 +7198,7 @@ var __webpack_modules__ = {
|
|
|
7075
7198
|
return Math.min(Math.max(n, min), max);
|
|
7076
7199
|
}
|
|
7077
7200
|
const TEMPLATE_CORPUS_REPO = 'extension-js/examples';
|
|
7078
|
-
const TEMPLATE_CORPUS_REF = '
|
|
7201
|
+
const TEMPLATE_CORPUS_REF = '06d33e35894516a460409dca06d1614e291e6252';
|
|
7079
7202
|
const TEMPLATE_CORPUS_SLUGS = [
|
|
7080
7203
|
'action',
|
|
7081
7204
|
'action-locales',
|
|
@@ -8066,7 +8189,7 @@ AI assistants
|
|
|
8066
8189
|
return `${getLoggingPrefix('error')} Can't use ${messages_code(bundleId)} as a bundle identifier.\nUse reverse-DNS form: dot-separated segments of letters, digits and hyphens, each starting with a letter (e.g. ${messages_code('com.example.my-extension')}).`;
|
|
8067
8190
|
}
|
|
8068
8191
|
function safariCommandNotSupported(command) {
|
|
8069
|
-
return `${getLoggingPrefix('error')} ${messages_code(command)} can't load an extension into Safari
|
|
8192
|
+
return `${getLoggingPrefix('error')} ${messages_code(command)} can't load an extension into Safari.\nSafari loads an extension only from an app it has registered, and that app is enabled by hand once installed.\nIts automation route can load a folder but grants no website access, so content scripts never run.\nRun a live Safari session: ${messages_code('extension dev --browser safari')}\nOr package the app and open it: ${messages_code('extension build --browser safari --open')}\nBoth need macOS with Xcode, and Safari takes the enable gesture once in Safari ▸ Settings ▸ Extensions.`;
|
|
8070
8193
|
}
|
|
8071
8194
|
function programAIHelp() {
|
|
8072
8195
|
return `\n${getLoggingPrefix('info')} ${external_pintor_default().gray('Development tips for extension developers and AI assistants')}
|
|
@@ -8188,7 +8311,10 @@ Docker / Devcontainers / Codespaces
|
|
|
8188
8311
|
- Use ${messages_code('--host 0.0.0.0')} to bind the dev server on all interfaces so HMR is reachable from the host.
|
|
8189
8312
|
- Use ${messages_code('--no-browser')} inside the container and load the extension manually from ${messages_code('dist/<browser>/')} in your host browser.
|
|
8190
8313
|
- Chromium sandbox flags (${messages_code('--no-sandbox')}) are added automatically when Docker, Podman, devcontainers, or Codespaces are detected.
|
|
8191
|
-
- File watching uses
|
|
8314
|
+
- File watching uses native file events by default, which keeps rebuilds fast.
|
|
8315
|
+
- Set ${messages_code(messages_arg('EXTENSION_WATCH_POLL=true'))} to poll instead when native events do not fire,
|
|
8316
|
+
as in a container, a virtual machine, or a network or bind mount.
|
|
8317
|
+
Tune it with ${messages_code(messages_arg('EXTENSION_WATCH_POLL_INTERVAL'))} (default 1000 ms).
|
|
8192
8318
|
- Example: ${messages_code('extension dev ./my-ext --host 0.0.0.0 --no-browser --port 8080')}
|
|
8193
8319
|
|
|
8194
8320
|
Flatpak Firefox
|
|
@@ -8361,7 +8487,9 @@ Cross-browser compatibility
|
|
|
8361
8487
|
],
|
|
8362
8488
|
notes: [
|
|
8363
8489
|
'Use --no-browser inside the container and load dist/<browser>/ in the host browser',
|
|
8364
|
-
'File watching uses
|
|
8490
|
+
'File watching uses native file events by default',
|
|
8491
|
+
'Set EXTENSION_WATCH_POLL=true to poll instead when native events do not fire, as in a container, a virtual machine, or a network or bind mount',
|
|
8492
|
+
'EXTENSION_WATCH_POLL_INTERVAL sets the poll interval in ms (default 1000)',
|
|
8365
8493
|
'--no-sandbox is added automatically when a container environment is detected'
|
|
8366
8494
|
]
|
|
8367
8495
|
}
|
|
@@ -8392,12 +8520,45 @@ Cross-browser compatibility
|
|
|
8392
8520
|
}
|
|
8393
8521
|
const OPEN_SURFACE_NOUN = {
|
|
8394
8522
|
popup: 'popup',
|
|
8523
|
+
options: 'options page',
|
|
8395
8524
|
sidebar: 'side panel',
|
|
8396
8525
|
action: 'action popup'
|
|
8397
8526
|
};
|
|
8398
8527
|
function openSurfaceNoun(surface) {
|
|
8399
8528
|
return OPEN_SURFACE_NOUN[surface] || surface;
|
|
8400
8529
|
}
|
|
8530
|
+
function openSurfaceAction(surface) {
|
|
8531
|
+
return 'command' === surface ? 'trigger the command' : `open the ${openSurfaceNoun(surface)}`;
|
|
8532
|
+
}
|
|
8533
|
+
function asSentence(text) {
|
|
8534
|
+
const trimmed = text.trim();
|
|
8535
|
+
const capitalized = trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
8536
|
+
return capitalized.endsWith('.') ? capitalized : `${capitalized}.`;
|
|
8537
|
+
}
|
|
8538
|
+
function listenerCount(count, event) {
|
|
8539
|
+
return `${count} ${event} ${1 === count ? 'listener' : 'listeners'}`;
|
|
8540
|
+
}
|
|
8541
|
+
function openedSurface(surface) {
|
|
8542
|
+
return `${getLoggingPrefix('success')} Opened the ${openSurfaceNoun(surface)}.`;
|
|
8543
|
+
}
|
|
8544
|
+
function replayedActionClick(listeners) {
|
|
8545
|
+
return `${getLoggingPrefix('success')} Replayed ${listenerCount(listeners, 'onClicked')} without a user gesture.`;
|
|
8546
|
+
}
|
|
8547
|
+
function replayedCommand(listeners, command) {
|
|
8548
|
+
const name = command ? ` for ${messages_arg(command)}` : '';
|
|
8549
|
+
return `${getLoggingPrefix('success')} Replayed ${listenerCount(listeners, 'onCommand')}${name} without a user gesture.`;
|
|
8550
|
+
}
|
|
8551
|
+
function openSurfaceWarning(warning) {
|
|
8552
|
+
return `${getLoggingPrefix('warn')} ${asSentence(warning)}`;
|
|
8553
|
+
}
|
|
8554
|
+
function openSurfaceFailed(surface, reason, engine, remedy) {
|
|
8555
|
+
const engineNote = engine ? messages_arg(` (engine ${engine})`) : '';
|
|
8556
|
+
return [
|
|
8557
|
+
`${getLoggingPrefix('error')} Can't ${openSurfaceAction(surface)}.`,
|
|
8558
|
+
`${messaging.Sc.label('REASON')} ${messaging.Sc.val(reason)}${engineNote}`,
|
|
8559
|
+
remedy
|
|
8560
|
+
].filter(Boolean).join('\n');
|
|
8561
|
+
}
|
|
8401
8562
|
function openSurfaceGestureStep(surface) {
|
|
8402
8563
|
return `Click the extension's toolbar entry, or its row in the extensions menu, to open the ${openSurfaceNoun(surface)}.`;
|
|
8403
8564
|
}
|
|
@@ -8407,6 +8568,12 @@ Cross-browser compatibility
|
|
|
8407
8568
|
function openSurfaceNeedsGesture(surface) {
|
|
8408
8569
|
return `${getLoggingPrefix('error')} Chromium opens the ${openSurfaceNoun(surface)} only in response to a user gesture, so ${messages_code(`extension open ${surface}`)} can't open it from the command line.\n` + openSurfaceGestureStep(surface);
|
|
8409
8570
|
}
|
|
8571
|
+
function doctorHeader(browser, passes, total) {
|
|
8572
|
+
return `doctor (${browser}), ${passes}/${total} checks passed`;
|
|
8573
|
+
}
|
|
8574
|
+
function doctorRemedy(check, remediation) {
|
|
8575
|
+
return `\n${check}: ${remediation}`;
|
|
8576
|
+
}
|
|
8410
8577
|
function sessionOnPort(browser, port) {
|
|
8411
8578
|
return port ? `for ${browser} on port ${port}` : `for ${browser}`;
|
|
8412
8579
|
}
|
|
@@ -8483,6 +8650,96 @@ Cross-browser compatibility
|
|
|
8483
8650
|
function sessionLogsPath(bridge, projectPath, browser) {
|
|
8484
8651
|
return sessionFilePath(bridge, 'logsPath', projectPath, browser, 'logs.ndjson');
|
|
8485
8652
|
}
|
|
8653
|
+
function isSafariVendor(value) {
|
|
8654
|
+
return 'safari' === value || 'webkit-based' === value;
|
|
8655
|
+
}
|
|
8656
|
+
const SUPPORTED_BROWSER_TARGETS = [
|
|
8657
|
+
'chrome',
|
|
8658
|
+
'chromium',
|
|
8659
|
+
'edge',
|
|
8660
|
+
'firefox',
|
|
8661
|
+
'brave',
|
|
8662
|
+
'opera',
|
|
8663
|
+
'vivaldi',
|
|
8664
|
+
'yandex',
|
|
8665
|
+
'waterfox',
|
|
8666
|
+
'librewolf',
|
|
8667
|
+
'chromium-based',
|
|
8668
|
+
'gecko-based',
|
|
8669
|
+
'firefox-based',
|
|
8670
|
+
'safari',
|
|
8671
|
+
'webkit-based'
|
|
8672
|
+
];
|
|
8673
|
+
const BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.join(' | ');
|
|
8674
|
+
const SESSION_BROWSER_TARGETS_HELP = "chrome | chromium | edge | firefox | safari";
|
|
8675
|
+
const NO_SAFARI_BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.filter((target)=>!isSafariVendor(target)).join(' | ');
|
|
8676
|
+
function parseOptionalBoolean(value) {
|
|
8677
|
+
if (void 0 === value) return true;
|
|
8678
|
+
const normalized = String(value).trim().toLowerCase();
|
|
8679
|
+
return ![
|
|
8680
|
+
'false',
|
|
8681
|
+
'0',
|
|
8682
|
+
'no',
|
|
8683
|
+
'off'
|
|
8684
|
+
].includes(normalized);
|
|
8685
|
+
}
|
|
8686
|
+
const vendors = (browser)=>{
|
|
8687
|
+
const value = browser ?? 'chromium';
|
|
8688
|
+
return 'all' === value ? [
|
|
8689
|
+
'chrome',
|
|
8690
|
+
'edge',
|
|
8691
|
+
'firefox'
|
|
8692
|
+
] : String(value).split(',').map((part)=>part.trim()).filter(Boolean);
|
|
8693
|
+
};
|
|
8694
|
+
const MANAGED_INSTALL_TARGETS = [
|
|
8695
|
+
'chrome',
|
|
8696
|
+
'chromium',
|
|
8697
|
+
'edge',
|
|
8698
|
+
'firefox',
|
|
8699
|
+
'chromium-based',
|
|
8700
|
+
'gecko-based',
|
|
8701
|
+
'firefox-based'
|
|
8702
|
+
];
|
|
8703
|
+
const MANAGED_INSTALL_TARGETS_HELP = [
|
|
8704
|
+
...MANAGED_INSTALL_TARGETS,
|
|
8705
|
+
'all'
|
|
8706
|
+
].join(' | ');
|
|
8707
|
+
const MANAGED_INSTALL_BINARIES = [
|
|
8708
|
+
'chrome',
|
|
8709
|
+
'chromium',
|
|
8710
|
+
'edge',
|
|
8711
|
+
'firefox'
|
|
8712
|
+
];
|
|
8713
|
+
const installTargets = (browser)=>'all' === browser ? [
|
|
8714
|
+
...MANAGED_INSTALL_BINARIES
|
|
8715
|
+
] : vendors(browser);
|
|
8716
|
+
function validateVendors(vendorsList, onInvalid) {
|
|
8717
|
+
const supported = SUPPORTED_BROWSER_TARGETS;
|
|
8718
|
+
for (const v of vendorsList)if (!supported.includes(v)) {
|
|
8719
|
+
onInvalid(v, supported);
|
|
8720
|
+
return false;
|
|
8721
|
+
}
|
|
8722
|
+
return true;
|
|
8723
|
+
}
|
|
8724
|
+
const MANAGED_INSTALL_TARGET_SET = new Set(MANAGED_INSTALL_TARGETS);
|
|
8725
|
+
const SUPPORTED_BROWSER_TARGET_SET = new Set(SUPPORTED_BROWSER_TARGETS);
|
|
8726
|
+
function classifyManagedInstallTarget(name) {
|
|
8727
|
+
const value = String(name || '').trim().toLowerCase();
|
|
8728
|
+
if (!value) return 'unknown';
|
|
8729
|
+
if (MANAGED_INSTALL_TARGET_SET.has(value)) return 'managed';
|
|
8730
|
+
if (SUPPORTED_BROWSER_TARGET_SET.has(value)) return 'not-installable';
|
|
8731
|
+
return 'unknown';
|
|
8732
|
+
}
|
|
8733
|
+
function firstNonManagedInstallTarget(targetsList) {
|
|
8734
|
+
for (const name of targetsList){
|
|
8735
|
+
const kind = classifyManagedInstallTarget(name);
|
|
8736
|
+
if ('managed' !== kind) return {
|
|
8737
|
+
name,
|
|
8738
|
+
kind
|
|
8739
|
+
};
|
|
8740
|
+
}
|
|
8741
|
+
return null;
|
|
8742
|
+
}
|
|
8486
8743
|
function parseLogSinceLocal(value) {
|
|
8487
8744
|
if (null == value || '' === value) return null;
|
|
8488
8745
|
if ('number' == typeof value) return Number.isFinite(value) ? {
|
|
@@ -8579,11 +8836,20 @@ Cross-browser compatibility
|
|
|
8579
8836
|
if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
|
|
8580
8837
|
console.log(formatPrettyLogLine(event));
|
|
8581
8838
|
}
|
|
8839
|
+
function levelToken(level) {
|
|
8840
|
+
const word = level.toUpperCase();
|
|
8841
|
+
if ('error' === level) return external_pintor_default().red(word);
|
|
8842
|
+
if ('warn' === level) return external_pintor_default().brightYellow(word);
|
|
8843
|
+
if ('info' === level) return external_pintor_default().gray(word);
|
|
8844
|
+
if ('debug' === level) return external_pintor_default().dim(external_pintor_default().gray(word));
|
|
8845
|
+
return word;
|
|
8846
|
+
}
|
|
8582
8847
|
function formatPrettyLogLine(event) {
|
|
8583
8848
|
const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
|
|
8584
8849
|
const code = event.code ? ` ${event.code}` : '';
|
|
8585
8850
|
const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
|
|
8586
|
-
|
|
8851
|
+
const level = String(event.level || 'log').toLowerCase();
|
|
8852
|
+
return `[${event.seq ?? '-'}] ${levelToken(level)} ${external_pintor_default().dim(`(${event.context})`)}${code} ${parts}${remediation}`;
|
|
8587
8853
|
}
|
|
8588
8854
|
function writeFrame(frame) {
|
|
8589
8855
|
try {
|
|
@@ -8593,7 +8859,7 @@ Cross-browser compatibility
|
|
|
8593
8859
|
}
|
|
8594
8860
|
}
|
|
8595
8861
|
function registerLogsCommand(program) {
|
|
8596
|
-
program.command('logs').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.logs).option(
|
|
8862
|
+
program.command('logs').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.logs).option(`--browser <${SESSION_BROWSER_TARGETS_HELP}>`, 'which dist/extension-js/<browser> to read. Defaults to `chromium`').option('--follow', 'stream live via the control channel instead of printing and exiting').option('--context <list>', 'comma-separated contexts (background, content, popup, options, sidebar, devtools, page)').option('--level <off|error|warn|info|debug|trace|all>', 'minimum severity to show. Defaults to `all`').option('--signals-only', '[experimental] show only structured dx.signal diagnostics. No emitter ships yet, so this currently prints nothing').option('--since <seq|iso>', 'only show events after this sequence number or ISO timestamp').option('--url <glob|substring>', 'only events whose url/hostname matches (glob with * or plain substring)').option('--tab <id>', 'only events from this tab id').option('--output <pretty|json|ndjson>', 'output format. Defaults to pretty on a TTY, ndjson when piped').action(async (projectPathArg, options)=>{
|
|
8597
8863
|
const bridge = await loadExtensionDevelopBridgeModule();
|
|
8598
8864
|
const projectPath = resolveSessionProjectPath(bridge, projectPathArg);
|
|
8599
8865
|
const browser = options.browser || 'chromium';
|
|
@@ -8666,7 +8932,7 @@ Cross-browser compatibility
|
|
|
8666
8932
|
if (matches(event)) printEvent(event, format);
|
|
8667
8933
|
},
|
|
8668
8934
|
onGap: (gap)=>{
|
|
8669
|
-
console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
|
|
8935
|
+
console.error(external_pintor_default().dim(external_pintor_default().gray(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`)));
|
|
8670
8936
|
},
|
|
8671
8937
|
onClose: (close)=>{
|
|
8672
8938
|
if (!refusals.has(close.code) && sessionStillNamed()) return;
|
|
@@ -8838,11 +9104,33 @@ Cross-browser compatibility
|
|
|
8838
9104
|
hint: openSurfaceGestureStep(surface)
|
|
8839
9105
|
};
|
|
8840
9106
|
}
|
|
8841
|
-
function
|
|
9107
|
+
function openResultLines(value) {
|
|
9108
|
+
if (!value || 'object' != typeof value) return;
|
|
9109
|
+
const reply = value;
|
|
9110
|
+
if ('string' == typeof reply.opened) return [
|
|
9111
|
+
openedSurface(reply.opened)
|
|
9112
|
+
];
|
|
9113
|
+
if ('popup' === reply.triggered) return [
|
|
9114
|
+
openedSurface('action')
|
|
9115
|
+
];
|
|
9116
|
+
if ('number' != typeof reply.listeners) return;
|
|
9117
|
+
const lines = [];
|
|
9118
|
+
if ('onClicked' === reply.triggered) lines.push(replayedActionClick(reply.listeners));
|
|
9119
|
+
else {
|
|
9120
|
+
if ('command' !== reply.triggered) return;
|
|
9121
|
+
lines.push(replayedCommand(reply.listeners, 'string' == typeof reply.command ? reply.command : void 0));
|
|
9122
|
+
}
|
|
9123
|
+
if ('string' == typeof reply.warning && reply.warning) lines.push(openSurfaceWarning(reply.warning));
|
|
9124
|
+
return lines;
|
|
9125
|
+
}
|
|
9126
|
+
function printResult(result, output, command, pretty) {
|
|
8842
9127
|
if ('json' === normalizeOutputFormat(output)) return void console.log(JSON.stringify(buildActEnvelope(command, result)));
|
|
8843
9128
|
if (result.ok) {
|
|
8844
9129
|
const value = result.value;
|
|
8845
|
-
|
|
9130
|
+
const lines = pretty?.value(value) ?? [
|
|
9131
|
+
'string' == typeof value ? value : JSON.stringify(value, null, 2)
|
|
9132
|
+
];
|
|
9133
|
+
for (const line of lines)console.log(line);
|
|
8846
9134
|
const consoleRecords = result.console;
|
|
8847
9135
|
if (Array.isArray(consoleRecords)) {
|
|
8848
9136
|
if (0 === consoleRecords.length) console.log('(no console lines captured)');
|
|
@@ -8851,6 +9139,11 @@ Cross-browser compatibility
|
|
|
8851
9139
|
if (result.truncated) console.error('… result truncated (byte cap)');
|
|
8852
9140
|
return;
|
|
8853
9141
|
}
|
|
9142
|
+
if (pretty) {
|
|
9143
|
+
const { error } = buildActEnvelope(command, result);
|
|
9144
|
+
console.error(pretty.failure(error));
|
|
9145
|
+
return;
|
|
9146
|
+
}
|
|
8854
9147
|
const err = result.error || {
|
|
8855
9148
|
name: 'Error',
|
|
8856
9149
|
message: 'command failed'
|
|
@@ -8924,7 +9217,7 @@ Cross-browser compatibility
|
|
|
8924
9217
|
if (result?.ok && input.augment) try {
|
|
8925
9218
|
Object.assign(result, input.augment(projectPath, browser, result));
|
|
8926
9219
|
} catch {}
|
|
8927
|
-
printResult(result, input.opts.output, input.command);
|
|
9220
|
+
printResult(result, input.opts.output, input.command, input.pretty);
|
|
8928
9221
|
await exitAfterDrain(result.ok ? 0 : 1);
|
|
8929
9222
|
}
|
|
8930
9223
|
function targetFrom(opts, fallback = 'background') {
|
|
@@ -8936,7 +9229,7 @@ Cross-browser compatibility
|
|
|
8936
9229
|
if (null != opts.tab && '' !== opts.tab) target.tabId = Number(opts.tab);
|
|
8937
9230
|
return target;
|
|
8938
9231
|
}
|
|
8939
|
-
const commonOptions = (cmd)=>cmd.option(
|
|
9232
|
+
const commonOptions = (cmd)=>cmd.option(`--browser <${SESSION_BROWSER_TARGETS_HELP}>`, 'which session to target (default chromium)').option('--timeout <ms>', 'command timeout in milliseconds (default 5000)').option('--output <pretty|json>', 'output format (default pretty)');
|
|
8940
9233
|
function registerActCommands(program) {
|
|
8941
9234
|
commonOptions(program.command('eval').arguments('<expression> [project-path]').description(commandDescriptions.eval).option('--context <background|popup|options|sidebar|devtools|newtab|history|bookmarks|content|page>', 'target context (default background). Extension pages (popup/options/sidebar/devtools/newtab/history/bookmarks) answer via their own in-page relay and must be open').option('--url <glob|substring>', 'for content/page: document to target (resolved to its tab)').option('--tab <id>', 'for content/page: a specific tab (default: the --url match, else the active tab)')).action(async (expression, projectPathArg, opts)=>{
|
|
8942
9235
|
await runCommand({
|
|
@@ -9069,7 +9362,11 @@ Cross-browser compatibility
|
|
|
9069
9362
|
},
|
|
9070
9363
|
args,
|
|
9071
9364
|
opts,
|
|
9072
|
-
preflight: (bridge, projectPath, browser)=>gestureRefusal(surface, bridge, projectPath, browser)
|
|
9365
|
+
preflight: (bridge, projectPath, browser)=>gestureRefusal(surface, bridge, projectPath, browser),
|
|
9366
|
+
pretty: {
|
|
9367
|
+
value: openResultLines,
|
|
9368
|
+
failure: (error)=>openSurfaceFailed(surface, error.message, error.engine, error.hint)
|
|
9369
|
+
}
|
|
9073
9370
|
});
|
|
9074
9371
|
});
|
|
9075
9372
|
}
|
|
@@ -9107,97 +9404,8 @@ Cross-browser compatibility
|
|
|
9107
9404
|
const values = String(raw).split(',').map((value)=>value.trim()).filter((value)=>value.length > 0);
|
|
9108
9405
|
return values.length > 0 ? values : void 0;
|
|
9109
9406
|
}
|
|
9110
|
-
function isSafariVendor(value) {
|
|
9111
|
-
return 'safari' === value || 'webkit-based' === value;
|
|
9112
|
-
}
|
|
9113
|
-
const SUPPORTED_BROWSER_TARGETS = [
|
|
9114
|
-
'chrome',
|
|
9115
|
-
'chromium',
|
|
9116
|
-
'edge',
|
|
9117
|
-
'firefox',
|
|
9118
|
-
'brave',
|
|
9119
|
-
'opera',
|
|
9120
|
-
'vivaldi',
|
|
9121
|
-
'yandex',
|
|
9122
|
-
'waterfox',
|
|
9123
|
-
'librewolf',
|
|
9124
|
-
'chromium-based',
|
|
9125
|
-
'gecko-based',
|
|
9126
|
-
'firefox-based',
|
|
9127
|
-
'safari',
|
|
9128
|
-
'webkit-based'
|
|
9129
|
-
];
|
|
9130
|
-
const BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.join(' | ');
|
|
9131
|
-
const NO_SAFARI_BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.filter((target)=>!isSafariVendor(target)).join(' | ');
|
|
9132
|
-
function parseOptionalBoolean(value) {
|
|
9133
|
-
if (void 0 === value) return true;
|
|
9134
|
-
const normalized = String(value).trim().toLowerCase();
|
|
9135
|
-
return ![
|
|
9136
|
-
'false',
|
|
9137
|
-
'0',
|
|
9138
|
-
'no',
|
|
9139
|
-
'off'
|
|
9140
|
-
].includes(normalized);
|
|
9141
|
-
}
|
|
9142
|
-
const vendors = (browser)=>{
|
|
9143
|
-
const value = browser ?? 'chromium';
|
|
9144
|
-
return 'all' === value ? [
|
|
9145
|
-
'chrome',
|
|
9146
|
-
'edge',
|
|
9147
|
-
'firefox'
|
|
9148
|
-
] : String(value).split(',').map((part)=>part.trim()).filter(Boolean);
|
|
9149
|
-
};
|
|
9150
|
-
const MANAGED_INSTALL_TARGETS = [
|
|
9151
|
-
'chrome',
|
|
9152
|
-
'chromium',
|
|
9153
|
-
'edge',
|
|
9154
|
-
'firefox',
|
|
9155
|
-
'chromium-based',
|
|
9156
|
-
'gecko-based',
|
|
9157
|
-
'firefox-based'
|
|
9158
|
-
];
|
|
9159
|
-
const MANAGED_INSTALL_TARGETS_HELP = [
|
|
9160
|
-
...MANAGED_INSTALL_TARGETS,
|
|
9161
|
-
'all'
|
|
9162
|
-
].join(' | ');
|
|
9163
|
-
const MANAGED_INSTALL_BINARIES = [
|
|
9164
|
-
'chrome',
|
|
9165
|
-
'chromium',
|
|
9166
|
-
'edge',
|
|
9167
|
-
'firefox'
|
|
9168
|
-
];
|
|
9169
|
-
const installTargets = (browser)=>'all' === browser ? [
|
|
9170
|
-
...MANAGED_INSTALL_BINARIES
|
|
9171
|
-
] : vendors(browser);
|
|
9172
|
-
function validateVendors(vendorsList, onInvalid) {
|
|
9173
|
-
const supported = SUPPORTED_BROWSER_TARGETS;
|
|
9174
|
-
for (const v of vendorsList)if (!supported.includes(v)) {
|
|
9175
|
-
onInvalid(v, supported);
|
|
9176
|
-
return false;
|
|
9177
|
-
}
|
|
9178
|
-
return true;
|
|
9179
|
-
}
|
|
9180
|
-
const MANAGED_INSTALL_TARGET_SET = new Set(MANAGED_INSTALL_TARGETS);
|
|
9181
|
-
const SUPPORTED_BROWSER_TARGET_SET = new Set(SUPPORTED_BROWSER_TARGETS);
|
|
9182
|
-
function classifyManagedInstallTarget(name) {
|
|
9183
|
-
const value = String(name || '').trim().toLowerCase();
|
|
9184
|
-
if (!value) return 'unknown';
|
|
9185
|
-
if (MANAGED_INSTALL_TARGET_SET.has(value)) return 'managed';
|
|
9186
|
-
if (SUPPORTED_BROWSER_TARGET_SET.has(value)) return 'not-installable';
|
|
9187
|
-
return 'unknown';
|
|
9188
|
-
}
|
|
9189
|
-
function firstNonManagedInstallTarget(targetsList) {
|
|
9190
|
-
for (const name of targetsList){
|
|
9191
|
-
const kind = classifyManagedInstallTarget(name);
|
|
9192
|
-
if ('managed' !== kind) return {
|
|
9193
|
-
name,
|
|
9194
|
-
kind
|
|
9195
|
-
};
|
|
9196
|
-
}
|
|
9197
|
-
return null;
|
|
9198
|
-
}
|
|
9199
9407
|
function registerBuildCommand(program) {
|
|
9200
|
-
program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--addon-lint [boolean]', 'check Firefox builds against addons.mozilla.org rules with addons-linter when it is installed. Findings print as warnings. Defaults to `true`', parseOptionalBoolean).option('--no-addon-lint', 'skip the addons.mozilla.org lint of Firefox builds').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari
|
|
9408
|
+
program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--addon-lint [boolean]', 'check Firefox builds against addons.mozilla.org rules with addons-linter when it is installed. Findings print as warnings. Defaults to `true`', parseOptionalBoolean).option('--no-addon-lint', 'skip the addons.mozilla.org lint of Firefox builds').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id shared by every build of the same source, so set your own before the first submission').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, { browser: cliBrowser, ...buildOptions })=>{
|
|
9201
9409
|
const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'build') ?? 'chromium';
|
|
9202
9410
|
if (buildOptions.debug || buildOptions.author || buildOptions.authorMode) {
|
|
9203
9411
|
process.env.EXTENSION_DEBUG = '1';
|
|
@@ -9759,37 +9967,45 @@ Cross-browser compatibility
|
|
|
9759
9967
|
async function waitForReadyContract(options) {
|
|
9760
9968
|
const readyPath = sessionReadyPath(options.bridge, options.projectPath, options.browser);
|
|
9761
9969
|
const start = Date.now();
|
|
9970
|
+
let lastReadFailure = '';
|
|
9762
9971
|
while(Date.now() - start < options.timeoutMs){
|
|
9763
|
-
if (external_node_fs_.existsSync(readyPath))
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9972
|
+
if (external_node_fs_.existsSync(readyPath)) {
|
|
9973
|
+
let payload = null;
|
|
9974
|
+
try {
|
|
9975
|
+
payload = JSON.parse(external_node_fs_.readFileSync(readyPath, 'utf8'));
|
|
9976
|
+
lastReadFailure = '';
|
|
9977
|
+
} catch (error) {
|
|
9978
|
+
payload = null;
|
|
9979
|
+
lastReadFailure = error instanceof Error ? error.message : String(error);
|
|
9770
9980
|
}
|
|
9771
|
-
if (
|
|
9772
|
-
|
|
9981
|
+
if (payload) {
|
|
9982
|
+
const isLive = isProcessLikelyAlive(payload.pid);
|
|
9983
|
+
const isFresh = isFreshContractPayload(payload);
|
|
9984
|
+
if (payload.command !== options.command) {
|
|
9773
9985
|
await new Promise((resolve)=>setTimeout(resolve, 250));
|
|
9774
9986
|
continue;
|
|
9775
9987
|
}
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9988
|
+
if (!isLive) {
|
|
9989
|
+
if ('start' !== options.command || !isFresh) {
|
|
9990
|
+
await new Promise((resolve)=>setTimeout(resolve, 250));
|
|
9991
|
+
continue;
|
|
9992
|
+
}
|
|
9993
|
+
}
|
|
9994
|
+
if ('ready' === payload.status) return payload;
|
|
9995
|
+
if ('error' === payload.status) {
|
|
9996
|
+
if (!isLive && 'start' !== options.command) {
|
|
9997
|
+
await new Promise((resolve)=>setTimeout(resolve, 250));
|
|
9998
|
+
continue;
|
|
9999
|
+
}
|
|
10000
|
+
const detail = payload.message || payload.errors?.[0] || 'unknown error';
|
|
10001
|
+
throw new WaitModeError(String(detail), messaging.Lp.E_INTERNAL);
|
|
9782
10002
|
}
|
|
9783
|
-
const detail = payload.message || payload.errors?.[0] || 'unknown error';
|
|
9784
|
-
throw new WaitModeError(String(detail), messaging.Lp.E_INTERNAL);
|
|
9785
10003
|
}
|
|
9786
|
-
} catch (error) {
|
|
9787
|
-
if (error instanceof Error) throw error;
|
|
9788
|
-
throw new Error(String(error));
|
|
9789
10004
|
}
|
|
9790
10005
|
await new Promise((resolve)=>setTimeout(resolve, 250));
|
|
9791
10006
|
}
|
|
9792
|
-
|
|
10007
|
+
const timeoutDetail = lastReadFailure ? `. The last read of the file failed to parse as JSON: ${lastReadFailure}` : '';
|
|
10008
|
+
throw new WaitModeError(`Timed out waiting for ready contract at ${readyPath} (${options.timeoutMs} ms)${timeoutDetail}`, messaging.Lp.E_READY_TIMEOUT);
|
|
9793
10009
|
}
|
|
9794
10010
|
async function runWaitMode(options) {
|
|
9795
10011
|
if (isHttpUrl(options.pathOrRemoteUrl)) throw new WaitModeError('--wait requires a local project path (remote URLs are not supported)', messaging.Lp.E_ARGS);
|
|
@@ -9833,7 +10049,7 @@ Cross-browser compatibility
|
|
|
9833
10049
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
|
|
9834
10050
|
}
|
|
9835
10051
|
function registerDevCommand(program) {
|
|
9836
|
-
program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the dev server still starts\n --no-reload emit a dev-mode dist without the content-script reload runtime, tabs need a manual reload to see changes\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only
|
|
10052
|
+
program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the dev server still starts\n --no-reload emit a dev-mode dist without the content-script reload runtime, tabs need a manual reload to see changes\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id shared by every build of the same source, so set your own before the first submission').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
|
|
9837
10053
|
const { browser: cliBrowser, ...devOptions } = options;
|
|
9838
10054
|
const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'dev') ?? 'chromium';
|
|
9839
10055
|
if (devOptions.debug || devOptions.author || devOptions.authorMode) {
|
|
@@ -10289,19 +10505,19 @@ Cross-browser compatibility
|
|
|
10289
10505
|
});
|
|
10290
10506
|
return results;
|
|
10291
10507
|
}
|
|
10508
|
+
function checkGlyph(status) {
|
|
10509
|
+
if ('pass' === status) return external_pintor_default().green('✓');
|
|
10510
|
+
if ('fail' === status) return external_pintor_default().red('✗');
|
|
10511
|
+
if ('warn' === status) return external_pintor_default().brightYellow('!');
|
|
10512
|
+
return external_pintor_default().gray('–');
|
|
10513
|
+
}
|
|
10292
10514
|
function printPretty(results, browser) {
|
|
10293
10515
|
const passes = results.filter((r)=>'pass' === r.status).length;
|
|
10294
|
-
|
|
10295
|
-
pass: '✓',
|
|
10296
|
-
fail: '✗',
|
|
10297
|
-
warn: '!',
|
|
10298
|
-
skip: '–'
|
|
10299
|
-
};
|
|
10300
|
-
console.log(`doctor (${browser}), ${passes}/${results.length} checks passed`);
|
|
10516
|
+
console.log(doctorHeader(browser, passes, results.length));
|
|
10301
10517
|
const width = Math.max(...results.map((r)=>r.check.length));
|
|
10302
|
-
for (const r of results)console.log(` ${
|
|
10518
|
+
for (const r of results)console.log(` ${checkGlyph(r.status)} ${r.check.padEnd(width)} ${r.detail}`);
|
|
10303
10519
|
const advisory = results.find((r)=>'fail' === r.status) ?? results.find((r)=>'warn' === r.status);
|
|
10304
|
-
if (advisory?.remediation) console.log(
|
|
10520
|
+
if (advisory?.remediation) console.log(doctorRemedy(advisory.check, advisory.remediation));
|
|
10305
10521
|
}
|
|
10306
10522
|
const CHECK_CODES = {
|
|
10307
10523
|
'ready-contract': messaging.Lp.E_SESSION_NOT_FOUND,
|
|
@@ -10313,7 +10529,7 @@ Cross-browser compatibility
|
|
|
10313
10529
|
browser: messaging.Lp.E_BROWSER_LAUNCH
|
|
10314
10530
|
};
|
|
10315
10531
|
function registerDoctorCommand(program) {
|
|
10316
|
-
program.command('doctor').argument('[project-path]', 'path to the extension project root or the folder holding its manifest').option(
|
|
10532
|
+
program.command('doctor').argument('[project-path]', 'path to the extension project root or the folder holding its manifest').option(`--browser <${SESSION_BROWSER_TARGETS_HELP}>`, 'which session to diagnose (defaults to the single live session, else chromium)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').description(commandDescriptions.doctor).action(async (projectPathArg, opts)=>{
|
|
10317
10533
|
const asJson = isJsonOutput(opts);
|
|
10318
10534
|
let results;
|
|
10319
10535
|
try {
|