extension 4.0.9 → 4.0.10
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 +1 -1
- package/dist/browsers.cjs +114 -3
- package/dist/cli.cjs +325 -58
- package/dist/extension/browsers/browsers-lib/manifest-refusal.d.ts +45 -7
- package/dist/extension/browsers/browsers-lib/messages.d.ts +8 -1
- package/dist/extension/browsers/run-safari/safari-launch/index.d.ts +13 -0
- package/dist/extension/browsers/run-safari/safari-launch/safari-config.d.ts +24 -0
- package/dist/extension/browsers/run-safari/safari-types.d.ts +3 -0
- package/dist/extension/helpers/messages.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ npx extension@latest dev --gecko-binary "/Applications/Firefox.app/Contents/MacO
|
|
|
94
94
|
|
|
95
95
|
| <img alt="Chrome" src="https://media.extension.land/logos/browsers/chrome.svg" width="70"> | <img alt="Edge" src="https://media.extension.land/logos/browsers/edge.svg" width="70"> | <img alt="Firefox" src="https://media.extension.land/logos/browsers/firefox.svg" width="70"> | <img alt="Safari" src="https://media.extension.land/logos/browsers/safari.svg" width="70"> | <img alt="Chromium" src="https://media.extension.land/logos/browsers/chromium.svg" width="70"> | <img alt="Gecko" src="https://media.extension.land/logos/browsers/firefox.svg" width="70"> |
|
|
96
96
|
| :-: | :-: | :-: | :-: | :-: | :-: |
|
|
97
|
-
| Google Chrome<br>✅ Supported | Microsoft Edge<br>✅ Supported | Mozilla Firefox<br>✅ Supported | Apple Safari<br>
|
|
97
|
+
| Google Chrome<br>✅ Supported | Microsoft Edge<br>✅ Supported | Mozilla Firefox<br>✅ Supported | Apple Safari<br> 🧪 Alpha | Chromium-based<br>✅ Supported | Gecko-based<br>✅ Supported |
|
|
98
98
|
|
|
99
99
|
</div>
|
|
100
100
|
|
package/dist/browsers.cjs
CHANGED
|
@@ -463,6 +463,10 @@ var __webpack_modules__ = {
|
|
|
463
463
|
function mv3BackgroundScriptsNotSupportedByChromium(extensionPath) {
|
|
464
464
|
return `${getLoggingPrefix('warn')} ${pintor__rspack_import_4_default().brightYellow("This MV3 extension declares Firefox-style background.scripts with no service_worker — Chromium refuses to load it.")}\n${pintor__rspack_import_4_default().gray('PATH')} ${pintor__rspack_import_4_default().underline(extensionPath)}\nThe browser rejects it at launch with no console error, so the dev session cannot attach.\nRun it on the browser it targets (${pintor__rspack_import_4_default().blue('--browser firefox')}) or declare ${pintor__rspack_import_4_default().blue('background.service_worker')} for Chromium.`;
|
|
465
465
|
}
|
|
466
|
+
function unsupportedManifestVersionOnChromium(extensionPath, declared) {
|
|
467
|
+
const value = void 0 === declared ? 'no manifest_version' : `manifest_version ${JSON.stringify(declared)}`;
|
|
468
|
+
return `${getLoggingPrefix('warn')} ${pintor__rspack_import_4_default().brightYellow(`This extension declares ${value}, which Chromium refuses as an unsupported manifest version.`)}\n${pintor__rspack_import_4_default().gray('PATH')} ${pintor__rspack_import_4_default().underline(extensionPath)}\nThe browser rejects it at launch (a native dialog, not a console error), so the dev session cannot attach.\nDeclare ${pintor__rspack_import_4_default().blue('"manifest_version": 3')} in the source manifest.`;
|
|
469
|
+
}
|
|
466
470
|
function chromiumInvalidMatchPatterns(extensionPath, patterns) {
|
|
467
471
|
const shown = patterns.slice(0, 6);
|
|
468
472
|
const more = patterns.length - shown.length;
|
|
@@ -731,6 +735,7 @@ var __webpack_modules__ = {
|
|
|
731
735
|
$w: ()=>bestEffortBannerPrintFailed,
|
|
732
736
|
A9: ()=>firefoxLaunchCalled,
|
|
733
737
|
AG: ()=>enhancedProcessManagementForceKill,
|
|
738
|
+
CN: ()=>unsupportedManifestVersionOnChromium,
|
|
734
739
|
CY: ()=>devChromiumDebugPort,
|
|
735
740
|
Cn: ()=>chromeProcessError,
|
|
736
741
|
Dh: ()=>invalidGeckoBinaryPath,
|
|
@@ -1768,6 +1773,7 @@ var __webpack_modules__ = {
|
|
|
1768
1773
|
const m = manifest;
|
|
1769
1774
|
if (2 === Number(m?.manifest_version)) return 'mv2';
|
|
1770
1775
|
if (3 === Number(m?.manifest_version) && Array.isArray(m?.background?.scripts) && m.background.scripts.length > 0 && !m?.background?.service_worker) return "mv3-background-scripts";
|
|
1776
|
+
if (m && 'object' == typeof m && !Array.isArray(m) && 3 !== Number(m.manifest_version)) return 'unsupported-manifest-version';
|
|
1771
1777
|
return null;
|
|
1772
1778
|
}
|
|
1773
1779
|
function findInvalidMatchPatterns(manifest) {
|
|
@@ -1802,9 +1808,42 @@ var __webpack_modules__ = {
|
|
|
1802
1808
|
if (!host.includes('*')) return false;
|
|
1803
1809
|
return '*' !== host && !/^\*\.[^*]+$/.test(host);
|
|
1804
1810
|
}
|
|
1805
|
-
function findChromiumLoadBlockers(manifest) {
|
|
1811
|
+
function findChromiumLoadBlockers(manifest, browserVersion) {
|
|
1806
1812
|
const m = manifest;
|
|
1807
1813
|
const blockers = [];
|
|
1814
|
+
if (!m || 'object' != typeof m || Array.isArray(m)) return blockers;
|
|
1815
|
+
if ('string' != typeof m.name || '' === m.name) blockers.push("name: missing, empty, or not a string — Chrome requires a non-empty string name and refuses the extension.");
|
|
1816
|
+
if ('string' != typeof m.version || !isValidDottedVersion(m.version)) blockers.push("version: missing or invalid — Chrome requires 1-4 dot-separated integers (0-65535) and refuses the extension.");
|
|
1817
|
+
if (3 === Number(m.manifest_version) && Array.isArray(m.web_accessible_resources)) m.web_accessible_resources.forEach((entry, index)=>{
|
|
1818
|
+
if (!entry || 'object' != typeof entry || Array.isArray(entry)) return void blockers.push(`web_accessible_resources[${index}]: MV2-style entry — MV3 requires {resources, matches|extension_ids|use_dynamic_url} dictionaries.`);
|
|
1819
|
+
if (void 0 === entry.resources) blockers.push(`web_accessible_resources[${index}]: 'resources' is required — Chrome refuses the extension without it.`);
|
|
1820
|
+
if (void 0 === entry.matches && void 0 === entry.extension_ids && void 0 === entry.use_dynamic_url) blockers.push(`web_accessible_resources[${index}]: needs one of 'matches', 'extension_ids', or 'use_dynamic_url' beside resources — Chrome refuses the extension without it.`);
|
|
1821
|
+
});
|
|
1822
|
+
const CS_RUN_AT = [
|
|
1823
|
+
'document_start',
|
|
1824
|
+
'document_end',
|
|
1825
|
+
'document_idle'
|
|
1826
|
+
];
|
|
1827
|
+
const csGroups = Array.isArray(m.content_scripts) ? m.content_scripts : [];
|
|
1828
|
+
csGroups.forEach((group, index)=>{
|
|
1829
|
+
if (!group || 'object' != typeof group) return;
|
|
1830
|
+
if (void 0 === group.matches) blockers.push(`content_scripts[${index}]: 'matches' is required — Chrome refuses the extension without it.`);
|
|
1831
|
+
else if (Array.isArray(group.matches) && 0 === group.matches.length) blockers.push(`content_scripts[${index}].matches: there must be at least one match — Chrome refuses the extension over an empty list.`);
|
|
1832
|
+
for (const listKey of [
|
|
1833
|
+
'js',
|
|
1834
|
+
'css'
|
|
1835
|
+
]){
|
|
1836
|
+
const list = group[listKey];
|
|
1837
|
+
if (Array.isArray(list)) list.forEach((entry, entryIndex)=>{
|
|
1838
|
+
if ('string' != typeof entry) blockers.push(`content_scripts[${index}].${listKey}[${entryIndex}]: expected a string, got ${typeof entry} — Chrome refuses the extension.`);
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
if (void 0 !== group.run_at && !CS_RUN_AT.includes(group.run_at)) blockers.push(`content_scripts[${index}].run_at: expected "document_start", "document_end" or "document_idle", got ${JSON.stringify(group.run_at)} — Chrome refuses the extension.`);
|
|
1842
|
+
});
|
|
1843
|
+
const minVersion = m.minimum_chrome_version;
|
|
1844
|
+
if (void 0 !== minVersion) if ('string' == typeof minVersion && isValidDottedVersion(minVersion)) {
|
|
1845
|
+
if (browserVersion && isValidDottedVersion(browserVersion) && compareDottedVersions(minVersion, browserVersion) > 0) blockers.push(`minimum_chrome_version: requires ${minVersion} but the resolved browser is ${browserVersion} — the browser refuses the extension.`);
|
|
1846
|
+
} else blockers.push(`minimum_chrome_version: invalid value ${JSON.stringify(minVersion)} — Chrome refuses the extension.`);
|
|
1808
1847
|
const commands = m?.commands;
|
|
1809
1848
|
if (commands && 'object' == typeof commands) {
|
|
1810
1849
|
const withKeys = Object.values(commands).filter((command)=>command?.suggested_key);
|
|
@@ -1851,6 +1890,74 @@ var __webpack_modules__ = {
|
|
|
1851
1890
|
}
|
|
1852
1891
|
return findings;
|
|
1853
1892
|
}
|
|
1893
|
+
function findLocaleLoadBlockers(manifest, extensionDir) {
|
|
1894
|
+
const m = manifest;
|
|
1895
|
+
const blockers = [];
|
|
1896
|
+
if (!m || 'object' != typeof m || Array.isArray(m)) return blockers;
|
|
1897
|
+
const localesDir = external_path_.join(extensionDir, '_locales');
|
|
1898
|
+
const defaultLocale = m.default_locale;
|
|
1899
|
+
if ('string' == typeof defaultLocale && '' !== defaultLocale.trim()) {
|
|
1900
|
+
const catalogPath = external_path_.join(localesDir, defaultLocale, 'messages.json');
|
|
1901
|
+
let catalogKeys = null;
|
|
1902
|
+
try {
|
|
1903
|
+
if (external_fs_.existsSync(catalogPath)) try {
|
|
1904
|
+
const raw = external_fs_.readFileSync(catalogPath, 'utf8').replace(/^\uFEFF/, '');
|
|
1905
|
+
const catalog = JSON.parse(raw);
|
|
1906
|
+
catalogKeys = new Set(Object.keys(catalog || {}).map((key)=>key.toLowerCase()));
|
|
1907
|
+
} catch {
|
|
1908
|
+
blockers.push(`default_locale: _locales/${defaultLocale}/messages.json is not valid JSON — Chrome refuses the whole extension.`);
|
|
1909
|
+
}
|
|
1910
|
+
else blockers.push(`default_locale: "${defaultLocale}" is declared but _locales/${defaultLocale}/messages.json is missing — Chrome refuses the whole extension.`);
|
|
1911
|
+
} catch {
|
|
1912
|
+
return blockers;
|
|
1913
|
+
}
|
|
1914
|
+
if (catalogKeys) {
|
|
1915
|
+
const refs = new Set();
|
|
1916
|
+
collectMsgRefs(m, refs);
|
|
1917
|
+
for (const ref of refs)if (!catalogKeys.has(ref.toLowerCase())) blockers.push(`__MSG_${ref}__: used in the manifest but not defined in _locales/${defaultLocale}/messages.json — Chrome refuses the whole extension.`);
|
|
1918
|
+
}
|
|
1919
|
+
} else try {
|
|
1920
|
+
if (external_fs_.existsSync(localesDir)) {
|
|
1921
|
+
const hasCatalog = external_fs_.readdirSync(localesDir).some((entry)=>external_fs_.existsSync(external_path_.join(localesDir, entry, 'messages.json')));
|
|
1922
|
+
if (hasCatalog) blockers.push("_locales: a locales tree exists but the manifest declares no default_locale — Chrome refuses the whole extension.");
|
|
1923
|
+
}
|
|
1924
|
+
} catch {}
|
|
1925
|
+
return blockers;
|
|
1926
|
+
}
|
|
1927
|
+
function findMissingManagedSchema(manifest, extensionDir) {
|
|
1928
|
+
const m = manifest;
|
|
1929
|
+
const schema = m?.storage?.managed_schema;
|
|
1930
|
+
if ('string' != typeof schema || '' === schema.trim()) return [];
|
|
1931
|
+
try {
|
|
1932
|
+
const abs = external_path_.join(extensionDir, schema.replace(/^\//, ''));
|
|
1933
|
+
if (!external_fs_.existsSync(abs)) return [
|
|
1934
|
+
`storage.managed_schema: "${schema}" does not exist in the extension directory — Chrome refuses the whole extension.`
|
|
1935
|
+
];
|
|
1936
|
+
} catch {}
|
|
1937
|
+
return [];
|
|
1938
|
+
}
|
|
1939
|
+
function collectMsgRefs(value, out) {
|
|
1940
|
+
if ('string' == typeof value) {
|
|
1941
|
+
const match = /^__MSG_(.+)__$/.exec(value.trim());
|
|
1942
|
+
if (match && !match[1].startsWith('@@')) out.add(match[1]);
|
|
1943
|
+
} else if (Array.isArray(value)) for (const item of value)collectMsgRefs(item, out);
|
|
1944
|
+
else if (value && 'object' == typeof value) for (const item of Object.values(value))collectMsgRefs(item, out);
|
|
1945
|
+
}
|
|
1946
|
+
function isValidDottedVersion(version) {
|
|
1947
|
+
if (!version) return false;
|
|
1948
|
+
const parts = version.split('.');
|
|
1949
|
+
if (parts.length > 4) return false;
|
|
1950
|
+
return parts.every((part)=>/^\d{1,5}$/.test(part) && Number(part) <= 65535);
|
|
1951
|
+
}
|
|
1952
|
+
function compareDottedVersions(a, b) {
|
|
1953
|
+
const pa = a.split('.').map(Number);
|
|
1954
|
+
const pb = b.split('.').map(Number);
|
|
1955
|
+
for(let i = 0; i < 4; i++){
|
|
1956
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
1957
|
+
if (0 !== diff) return diff;
|
|
1958
|
+
}
|
|
1959
|
+
return 0;
|
|
1960
|
+
}
|
|
1854
1961
|
function isValidBase64(value) {
|
|
1855
1962
|
if (0 === value.length || value.length % 4 !== 0) return false;
|
|
1856
1963
|
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return false;
|
|
@@ -2644,16 +2751,20 @@ var __webpack_modules__ = {
|
|
|
2644
2751
|
console.log(messages.iL(browser, browserBinaryLocation, browserVersionLine));
|
|
2645
2752
|
const extensionsToLoad = (0, runtime_options.fT)(this.options.extension);
|
|
2646
2753
|
(0, runtime_options.sl)(extensionsToLoad, this.ctx.setExtensionRoot);
|
|
2754
|
+
const resolvedBrowserVersion = /(\d+(?:\.\d+){0,3})/.exec(browserVersionLine || '')?.[1];
|
|
2647
2755
|
for (const extPath of extensionsToLoad)try {
|
|
2648
2756
|
const m = JSON.parse(external_fs_.readFileSync(external_path_.join(String(extPath), 'manifest.json'), 'utf8'));
|
|
2649
2757
|
const refusal = diagnoseChromiumManifestRefusal(m);
|
|
2650
2758
|
if ('mv2' === refusal) console.warn(messages.sP(String(extPath)));
|
|
2651
2759
|
else if ("mv3-background-scripts" === refusal) console.warn(messages.NO(String(extPath)));
|
|
2760
|
+
else if ('unsupported-manifest-version' === refusal) console.warn(messages.CN(String(extPath), m?.manifest_version));
|
|
2652
2761
|
const invalidPatterns = findInvalidMatchPatterns(m);
|
|
2653
2762
|
if (invalidPatterns.length) console.warn(messages.qR(String(extPath), invalidPatterns));
|
|
2654
2763
|
const loadBlockers = [
|
|
2655
|
-
...findChromiumLoadBlockers(m),
|
|
2656
|
-
...findUnloadableIconFiles(m, String(extPath))
|
|
2764
|
+
...findChromiumLoadBlockers(m, resolvedBrowserVersion),
|
|
2765
|
+
...findUnloadableIconFiles(m, String(extPath)),
|
|
2766
|
+
...findLocaleLoadBlockers(m, String(extPath)),
|
|
2767
|
+
...findMissingManagedSchema(m, String(extPath))
|
|
2657
2768
|
];
|
|
2658
2769
|
if (loadBlockers.length) console.warn(messages.L(String(extPath), loadBlockers));
|
|
2659
2770
|
} catch {}
|
package/dist/cli.cjs
CHANGED
|
@@ -464,6 +464,10 @@ var __webpack_modules__ = {
|
|
|
464
464
|
function mv3BackgroundScriptsNotSupportedByChromium(extensionPath) {
|
|
465
465
|
return `${getLoggingPrefix('warn')} ${pintor__rspack_import_4_default().brightYellow("This MV3 extension declares Firefox-style background.scripts with no service_worker — Chromium refuses to load it.")}\n${pintor__rspack_import_4_default().gray('PATH')} ${pintor__rspack_import_4_default().underline(extensionPath)}\nThe browser rejects it at launch with no console error, so the dev session cannot attach.\nRun it on the browser it targets (${pintor__rspack_import_4_default().blue('--browser firefox')}) or declare ${pintor__rspack_import_4_default().blue('background.service_worker')} for Chromium.`;
|
|
466
466
|
}
|
|
467
|
+
function unsupportedManifestVersionOnChromium(extensionPath, declared) {
|
|
468
|
+
const value = void 0 === declared ? 'no manifest_version' : `manifest_version ${JSON.stringify(declared)}`;
|
|
469
|
+
return `${getLoggingPrefix('warn')} ${pintor__rspack_import_4_default().brightYellow(`This extension declares ${value}, which Chromium refuses as an unsupported manifest version.`)}\n${pintor__rspack_import_4_default().gray('PATH')} ${pintor__rspack_import_4_default().underline(extensionPath)}\nThe browser rejects it at launch (a native dialog, not a console error), so the dev session cannot attach.\nDeclare ${pintor__rspack_import_4_default().blue('"manifest_version": 3')} in the source manifest.`;
|
|
470
|
+
}
|
|
467
471
|
function chromiumInvalidMatchPatterns(extensionPath, patterns) {
|
|
468
472
|
const shown = patterns.slice(0, 6);
|
|
469
473
|
const more = patterns.length - shown.length;
|
|
@@ -607,7 +611,10 @@ var __webpack_modules__ = {
|
|
|
607
611
|
return platform;
|
|
608
612
|
}
|
|
609
613
|
function safariRequiresMacOS(platform) {
|
|
610
|
-
return `${getLoggingPrefix('warn')} Safari extensions can only be built on macOS.\nDetected ${pintor__rspack_import_4_default().gray(prettyPlatform(platform))}
|
|
614
|
+
return `${getLoggingPrefix('warn')} Safari extensions can only be built on macOS.\nDetected ${pintor__rspack_import_4_default().gray(prettyPlatform(platform))}. Target another browser via ${pintor__rspack_import_4_default().blue('--browser')} ${pintor__rspack_import_4_default().gray('<chrome|edge|firefox>')}, or run this command on a Mac with Xcode.`;
|
|
615
|
+
}
|
|
616
|
+
function safariPackagingSkippedNonMac(platform) {
|
|
617
|
+
return `${getLoggingPrefix('warn')} Safari packaging needs macOS with Xcode — detected ${pintor__rspack_import_4_default().gray(prettyPlatform(platform))}, so the Xcode packaging step is skipped.\nThe web-extension build in ${pintor__rspack_import_4_default().yellow('dist/safari')} is still complete and can be packaged later on a Mac with ${pintor__rspack_import_4_default().blue('extension build --browser=safari')}.`;
|
|
611
618
|
}
|
|
612
619
|
function safariXcodeRequired(developerDir) {
|
|
613
620
|
const current = developerDir ? `${pintor__rspack_import_4_default().gray('Active toolchain:')} ${pintor__rspack_import_4_default().underline(developerDir)}` : `${pintor__rspack_import_4_default().gray('No active developer directory was found.')}`;
|
|
@@ -631,8 +638,19 @@ var __webpack_modules__ = {
|
|
|
631
638
|
function safariOpening(target) {
|
|
632
639
|
return `${getLoggingPrefix('info')} Opening ${pintor__rspack_import_4_default().underline(target)}`;
|
|
633
640
|
}
|
|
634
|
-
function
|
|
635
|
-
|
|
641
|
+
function safariToolFailed(tool, exitCode, outputTail) {
|
|
642
|
+
const code = null === exitCode ? 'no exit code' : `exit ${exitCode}`;
|
|
643
|
+
const tail = outputTail.trim().length ? `\n${pintor__rspack_import_4_default().gray('── last output ──')}\n${outputTail}` : `\n${pintor__rspack_import_4_default().gray('(no output captured)')}`;
|
|
644
|
+
return `${getLoggingPrefix('error')} Safari packaging tool ${pintor__rspack_import_4_default().underline(tool)} failed (${pintor__rspack_import_4_default().red(code)}).${tail}`;
|
|
645
|
+
}
|
|
646
|
+
function safariConverterWarnings(warnings) {
|
|
647
|
+
return `${getLoggingPrefix('warn')} safari-web-extension-converter reported ${pintor__rspack_import_4_default().yellow(String(warnings.length))} warning(s) — some manifest keys/APIs may not be supported by Safari:\n` + warnings.map((line)=>` ${pintor__rspack_import_4_default().gray('•')} ${line}`).join('\n');
|
|
648
|
+
}
|
|
649
|
+
function safariDefaultBundleIdNote(bundleId) {
|
|
650
|
+
return `${getLoggingPrefix('info')} Using the generated bundle id ${pintor__rspack_import_4_default().gray(bundleId)}. For an app you plan to distribute, set your own with ${pintor__rspack_import_4_default().blue('--bundle-id')} now — changing it later makes Safari treat the extension as a new identity.`;
|
|
651
|
+
}
|
|
652
|
+
function safariOpenHint(appPath, appName) {
|
|
653
|
+
return `${getLoggingPrefix('info')} Launch it once to register with Safari: ${pintor__rspack_import_4_default().blue('open')} ${pintor__rspack_import_4_default().underline(`"${appPath}"`)}\nThen enable ${pintor__rspack_import_4_default().brightBlue(appName)} via Safari ▸ Develop ▸ ${pintor__rspack_import_4_default().yellow('Allow Unsigned Extensions')} and Safari ▸ Settings ▸ Extensions.`;
|
|
636
654
|
}
|
|
637
655
|
function safariDryRunNotBuilding() {
|
|
638
656
|
return `${getLoggingPrefix('info')} [browser] Dry run: not building Safari app`;
|
|
@@ -656,7 +674,13 @@ var __webpack_modules__ = {
|
|
|
656
674
|
return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_4_default().brightBlue(appName)} — reload the page (or toggle the extension) in Safari to see changes.`;
|
|
657
675
|
}
|
|
658
676
|
function safariProjectStale() {
|
|
659
|
-
return `${getLoggingPrefix('info')} manifest.json changed since the Xcode project was generated — regenerating project.`;
|
|
677
|
+
return `${getLoggingPrefix('info')} manifest.json or identity options changed since the Xcode project was generated — regenerating project.`;
|
|
678
|
+
}
|
|
679
|
+
function safariForcedRegeneration() {
|
|
680
|
+
return `${getLoggingPrefix('info')} --force-regenerate set — regenerating the Xcode project.`;
|
|
681
|
+
}
|
|
682
|
+
function safariRegenerationDiscards(preservedKeys) {
|
|
683
|
+
return `${getLoggingPrefix('warn')} Regenerating replaces the Xcode project: customizations made in Xcode (entitlements, capabilities, added files/targets) are ${pintor__rspack_import_4_default().red('discarded')}.\nPreserved automatically: ${pintor__rspack_import_4_default().yellow(preservedKeys.join(', '))}. If you customized the project, back it up before continuing.`;
|
|
660
684
|
}
|
|
661
685
|
function safariSettingsPreserved(keys) {
|
|
662
686
|
return `${getLoggingPrefix('info')} Preserved Xcode build settings across regeneration: ${pintor__rspack_import_4_default().yellow(keys.join(', '))}.\nIf any other project-level tweaks were lost, reconfigure them in Xcode.`;
|
|
@@ -795,10 +819,13 @@ var __webpack_modules__ = {
|
|
|
795
819
|
return `${getLoggingPrefix('error')} Invalid RDP request payload`;
|
|
796
820
|
}
|
|
797
821
|
__webpack_require__.d(__webpack_exports__, {
|
|
822
|
+
$q: ()=>safariDefaultBundleIdNote,
|
|
798
823
|
$w: ()=>bestEffortBannerPrintFailed,
|
|
824
|
+
A: ()=>safariToolFailed,
|
|
799
825
|
A9: ()=>firefoxLaunchCalled,
|
|
800
826
|
AG: ()=>enhancedProcessManagementForceKill,
|
|
801
827
|
Aj: ()=>safariDryRunXcodebuild,
|
|
828
|
+
CN: ()=>unsupportedManifestVersionOnChromium,
|
|
802
829
|
CY: ()=>devChromiumDebugPort,
|
|
803
830
|
Cn: ()=>chromeProcessError,
|
|
804
831
|
Cs: ()=>safariToolchainMissing,
|
|
@@ -811,6 +838,7 @@ var __webpack_modules__ = {
|
|
|
811
838
|
Fm: ()=>connectionClosedError,
|
|
812
839
|
G3: ()=>safariRequiresMacOS,
|
|
813
840
|
Gq: ()=>locatingBrowser,
|
|
841
|
+
H1: ()=>safariRegenerationDiscards,
|
|
814
842
|
HH: ()=>messageWithoutSenderError,
|
|
815
843
|
Ih: ()=>browserNotInstalledError,
|
|
816
844
|
JB: ()=>devChannelSnapshotInUse,
|
|
@@ -826,12 +854,13 @@ var __webpack_modules__ = {
|
|
|
826
854
|
NO: ()=>mv3BackgroundScriptsNotSupportedByChromium,
|
|
827
855
|
Nk: ()=>chromiumDryRunNotLaunching,
|
|
828
856
|
Q0: ()=>chromiumDryRunBinary,
|
|
829
|
-
Qr: ()=>safariFailed,
|
|
830
857
|
Rl: ()=>safariBuilt,
|
|
831
858
|
Sp: ()=>invalidChromiumBinaryPath,
|
|
832
859
|
TF: ()=>enhancedProcessManagementCleanupError,
|
|
833
860
|
Te: ()=>cdpClientTargetWebSocketUrlStored,
|
|
834
861
|
Th: ()=>browserInstanceExited,
|
|
862
|
+
Tq: ()=>safariForcedRegeneration,
|
|
863
|
+
UW: ()=>safariPackagingSkippedNonMac,
|
|
835
864
|
Ug: ()=>usingManagedChromiumFamilyFallback,
|
|
836
865
|
Vk: ()=>enhancedProcessManagementCleanup,
|
|
837
866
|
WV: ()=>addonInstallError,
|
|
@@ -840,6 +869,7 @@ var __webpack_modules__ = {
|
|
|
840
869
|
Z5: ()=>cdpClientExtensionInfoFailed,
|
|
841
870
|
_D: ()=>browserLaunchError,
|
|
842
871
|
aI: ()=>devChromeProfilePath,
|
|
872
|
+
aO: ()=>safariOpenHint,
|
|
843
873
|
bv: ()=>cdpUnifiedExtensionLog,
|
|
844
874
|
cD: ()=>targetActorHasActiveRequestError,
|
|
845
875
|
cF: ()=>safariRebuilt,
|
|
@@ -882,6 +912,7 @@ var __webpack_modules__ = {
|
|
|
882
912
|
wk: ()=>chromeFailedToSpawn,
|
|
883
913
|
xx: ()=>cdpFailedToHandleMessage,
|
|
884
914
|
xy: ()=>chromeInitializingEnhancedReload,
|
|
915
|
+
y1: ()=>safariConverterWarnings,
|
|
885
916
|
y7: ()=>skippingBrowserLaunchDueToCompileErrors,
|
|
886
917
|
yc: ()=>safariDryRunNotBuilding
|
|
887
918
|
});
|
|
@@ -1853,6 +1884,7 @@ var __webpack_modules__ = {
|
|
|
1853
1884
|
const m = manifest;
|
|
1854
1885
|
if (2 === Number(m?.manifest_version)) return 'mv2';
|
|
1855
1886
|
if (3 === Number(m?.manifest_version) && Array.isArray(m?.background?.scripts) && m.background.scripts.length > 0 && !m?.background?.service_worker) return "mv3-background-scripts";
|
|
1887
|
+
if (m && 'object' == typeof m && !Array.isArray(m) && 3 !== Number(m.manifest_version)) return 'unsupported-manifest-version';
|
|
1856
1888
|
return null;
|
|
1857
1889
|
}
|
|
1858
1890
|
function findInvalidMatchPatterns(manifest) {
|
|
@@ -1887,9 +1919,42 @@ var __webpack_modules__ = {
|
|
|
1887
1919
|
if (!host.includes('*')) return false;
|
|
1888
1920
|
return '*' !== host && !/^\*\.[^*]+$/.test(host);
|
|
1889
1921
|
}
|
|
1890
|
-
function findChromiumLoadBlockers(manifest) {
|
|
1922
|
+
function findChromiumLoadBlockers(manifest, browserVersion) {
|
|
1891
1923
|
const m = manifest;
|
|
1892
1924
|
const blockers = [];
|
|
1925
|
+
if (!m || 'object' != typeof m || Array.isArray(m)) return blockers;
|
|
1926
|
+
if ('string' != typeof m.name || '' === m.name) blockers.push("name: missing, empty, or not a string — Chrome requires a non-empty string name and refuses the extension.");
|
|
1927
|
+
if ('string' != typeof m.version || !isValidDottedVersion(m.version)) blockers.push("version: missing or invalid — Chrome requires 1-4 dot-separated integers (0-65535) and refuses the extension.");
|
|
1928
|
+
if (3 === Number(m.manifest_version) && Array.isArray(m.web_accessible_resources)) m.web_accessible_resources.forEach((entry, index)=>{
|
|
1929
|
+
if (!entry || 'object' != typeof entry || Array.isArray(entry)) return void blockers.push(`web_accessible_resources[${index}]: MV2-style entry — MV3 requires {resources, matches|extension_ids|use_dynamic_url} dictionaries.`);
|
|
1930
|
+
if (void 0 === entry.resources) blockers.push(`web_accessible_resources[${index}]: 'resources' is required — Chrome refuses the extension without it.`);
|
|
1931
|
+
if (void 0 === entry.matches && void 0 === entry.extension_ids && void 0 === entry.use_dynamic_url) blockers.push(`web_accessible_resources[${index}]: needs one of 'matches', 'extension_ids', or 'use_dynamic_url' beside resources — Chrome refuses the extension without it.`);
|
|
1932
|
+
});
|
|
1933
|
+
const CS_RUN_AT = [
|
|
1934
|
+
'document_start',
|
|
1935
|
+
'document_end',
|
|
1936
|
+
'document_idle'
|
|
1937
|
+
];
|
|
1938
|
+
const csGroups = Array.isArray(m.content_scripts) ? m.content_scripts : [];
|
|
1939
|
+
csGroups.forEach((group, index)=>{
|
|
1940
|
+
if (!group || 'object' != typeof group) return;
|
|
1941
|
+
if (void 0 === group.matches) blockers.push(`content_scripts[${index}]: 'matches' is required — Chrome refuses the extension without it.`);
|
|
1942
|
+
else if (Array.isArray(group.matches) && 0 === group.matches.length) blockers.push(`content_scripts[${index}].matches: there must be at least one match — Chrome refuses the extension over an empty list.`);
|
|
1943
|
+
for (const listKey of [
|
|
1944
|
+
'js',
|
|
1945
|
+
'css'
|
|
1946
|
+
]){
|
|
1947
|
+
const list = group[listKey];
|
|
1948
|
+
if (Array.isArray(list)) list.forEach((entry, entryIndex)=>{
|
|
1949
|
+
if ('string' != typeof entry) blockers.push(`content_scripts[${index}].${listKey}[${entryIndex}]: expected a string, got ${typeof entry} — Chrome refuses the extension.`);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
if (void 0 !== group.run_at && !CS_RUN_AT.includes(group.run_at)) blockers.push(`content_scripts[${index}].run_at: expected "document_start", "document_end" or "document_idle", got ${JSON.stringify(group.run_at)} — Chrome refuses the extension.`);
|
|
1953
|
+
});
|
|
1954
|
+
const minVersion = m.minimum_chrome_version;
|
|
1955
|
+
if (void 0 !== minVersion) if ('string' == typeof minVersion && isValidDottedVersion(minVersion)) {
|
|
1956
|
+
if (browserVersion && isValidDottedVersion(browserVersion) && compareDottedVersions(minVersion, browserVersion) > 0) blockers.push(`minimum_chrome_version: requires ${minVersion} but the resolved browser is ${browserVersion} — the browser refuses the extension.`);
|
|
1957
|
+
} else blockers.push(`minimum_chrome_version: invalid value ${JSON.stringify(minVersion)} — Chrome refuses the extension.`);
|
|
1893
1958
|
const commands = m?.commands;
|
|
1894
1959
|
if (commands && 'object' == typeof commands) {
|
|
1895
1960
|
const withKeys = Object.values(commands).filter((command)=>command?.suggested_key);
|
|
@@ -1936,6 +2001,74 @@ var __webpack_modules__ = {
|
|
|
1936
2001
|
}
|
|
1937
2002
|
return findings;
|
|
1938
2003
|
}
|
|
2004
|
+
function findLocaleLoadBlockers(manifest, extensionDir) {
|
|
2005
|
+
const m = manifest;
|
|
2006
|
+
const blockers = [];
|
|
2007
|
+
if (!m || 'object' != typeof m || Array.isArray(m)) return blockers;
|
|
2008
|
+
const localesDir = external_path_.join(extensionDir, '_locales');
|
|
2009
|
+
const defaultLocale = m.default_locale;
|
|
2010
|
+
if ('string' == typeof defaultLocale && '' !== defaultLocale.trim()) {
|
|
2011
|
+
const catalogPath = external_path_.join(localesDir, defaultLocale, 'messages.json');
|
|
2012
|
+
let catalogKeys = null;
|
|
2013
|
+
try {
|
|
2014
|
+
if (external_fs_.existsSync(catalogPath)) try {
|
|
2015
|
+
const raw = external_fs_.readFileSync(catalogPath, 'utf8').replace(/^\uFEFF/, '');
|
|
2016
|
+
const catalog = JSON.parse(raw);
|
|
2017
|
+
catalogKeys = new Set(Object.keys(catalog || {}).map((key)=>key.toLowerCase()));
|
|
2018
|
+
} catch {
|
|
2019
|
+
blockers.push(`default_locale: _locales/${defaultLocale}/messages.json is not valid JSON — Chrome refuses the whole extension.`);
|
|
2020
|
+
}
|
|
2021
|
+
else blockers.push(`default_locale: "${defaultLocale}" is declared but _locales/${defaultLocale}/messages.json is missing — Chrome refuses the whole extension.`);
|
|
2022
|
+
} catch {
|
|
2023
|
+
return blockers;
|
|
2024
|
+
}
|
|
2025
|
+
if (catalogKeys) {
|
|
2026
|
+
const refs = new Set();
|
|
2027
|
+
collectMsgRefs(m, refs);
|
|
2028
|
+
for (const ref of refs)if (!catalogKeys.has(ref.toLowerCase())) blockers.push(`__MSG_${ref}__: used in the manifest but not defined in _locales/${defaultLocale}/messages.json — Chrome refuses the whole extension.`);
|
|
2029
|
+
}
|
|
2030
|
+
} else try {
|
|
2031
|
+
if (external_fs_.existsSync(localesDir)) {
|
|
2032
|
+
const hasCatalog = external_fs_.readdirSync(localesDir).some((entry)=>external_fs_.existsSync(external_path_.join(localesDir, entry, 'messages.json')));
|
|
2033
|
+
if (hasCatalog) blockers.push("_locales: a locales tree exists but the manifest declares no default_locale — Chrome refuses the whole extension.");
|
|
2034
|
+
}
|
|
2035
|
+
} catch {}
|
|
2036
|
+
return blockers;
|
|
2037
|
+
}
|
|
2038
|
+
function findMissingManagedSchema(manifest, extensionDir) {
|
|
2039
|
+
const m = manifest;
|
|
2040
|
+
const schema = m?.storage?.managed_schema;
|
|
2041
|
+
if ('string' != typeof schema || '' === schema.trim()) return [];
|
|
2042
|
+
try {
|
|
2043
|
+
const abs = external_path_.join(extensionDir, schema.replace(/^\//, ''));
|
|
2044
|
+
if (!external_fs_.existsSync(abs)) return [
|
|
2045
|
+
`storage.managed_schema: "${schema}" does not exist in the extension directory — Chrome refuses the whole extension.`
|
|
2046
|
+
];
|
|
2047
|
+
} catch {}
|
|
2048
|
+
return [];
|
|
2049
|
+
}
|
|
2050
|
+
function collectMsgRefs(value, out) {
|
|
2051
|
+
if ('string' == typeof value) {
|
|
2052
|
+
const match = /^__MSG_(.+)__$/.exec(value.trim());
|
|
2053
|
+
if (match && !match[1].startsWith('@@')) out.add(match[1]);
|
|
2054
|
+
} else if (Array.isArray(value)) for (const item of value)collectMsgRefs(item, out);
|
|
2055
|
+
else if (value && 'object' == typeof value) for (const item of Object.values(value))collectMsgRefs(item, out);
|
|
2056
|
+
}
|
|
2057
|
+
function isValidDottedVersion(version) {
|
|
2058
|
+
if (!version) return false;
|
|
2059
|
+
const parts = version.split('.');
|
|
2060
|
+
if (parts.length > 4) return false;
|
|
2061
|
+
return parts.every((part)=>/^\d{1,5}$/.test(part) && Number(part) <= 65535);
|
|
2062
|
+
}
|
|
2063
|
+
function compareDottedVersions(a, b) {
|
|
2064
|
+
const pa = a.split('.').map(Number);
|
|
2065
|
+
const pb = b.split('.').map(Number);
|
|
2066
|
+
for(let i = 0; i < 4; i++){
|
|
2067
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
2068
|
+
if (0 !== diff) return diff;
|
|
2069
|
+
}
|
|
2070
|
+
return 0;
|
|
2071
|
+
}
|
|
1939
2072
|
function isValidBase64(value) {
|
|
1940
2073
|
if (0 === value.length || value.length % 4 !== 0) return false;
|
|
1941
2074
|
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return false;
|
|
@@ -2729,16 +2862,20 @@ var __webpack_modules__ = {
|
|
|
2729
2862
|
console.log(messages.iL(browser, browserBinaryLocation, browserVersionLine));
|
|
2730
2863
|
const extensionsToLoad = (0, runtime_options.fT)(this.options.extension);
|
|
2731
2864
|
(0, runtime_options.sl)(extensionsToLoad, this.ctx.setExtensionRoot);
|
|
2865
|
+
const resolvedBrowserVersion = /(\d+(?:\.\d+){0,3})/.exec(browserVersionLine || '')?.[1];
|
|
2732
2866
|
for (const extPath of extensionsToLoad)try {
|
|
2733
2867
|
const m = JSON.parse(external_fs_.readFileSync(external_path_.join(String(extPath), 'manifest.json'), 'utf8'));
|
|
2734
2868
|
const refusal = diagnoseChromiumManifestRefusal(m);
|
|
2735
2869
|
if ('mv2' === refusal) console.warn(messages.sP(String(extPath)));
|
|
2736
2870
|
else if ("mv3-background-scripts" === refusal) console.warn(messages.NO(String(extPath)));
|
|
2871
|
+
else if ('unsupported-manifest-version' === refusal) console.warn(messages.CN(String(extPath), m?.manifest_version));
|
|
2737
2872
|
const invalidPatterns = findInvalidMatchPatterns(m);
|
|
2738
2873
|
if (invalidPatterns.length) console.warn(messages.qR(String(extPath), invalidPatterns));
|
|
2739
2874
|
const loadBlockers = [
|
|
2740
|
-
...findChromiumLoadBlockers(m),
|
|
2741
|
-
...findUnloadableIconFiles(m, String(extPath))
|
|
2875
|
+
...findChromiumLoadBlockers(m, resolvedBrowserVersion),
|
|
2876
|
+
...findUnloadableIconFiles(m, String(extPath)),
|
|
2877
|
+
...findLocaleLoadBlockers(m, String(extPath)),
|
|
2878
|
+
...findMissingManagedSchema(m, String(extPath))
|
|
2742
2879
|
];
|
|
2743
2880
|
if (loadBlockers.length) console.warn(messages.L(String(extPath), loadBlockers));
|
|
2744
2881
|
} catch {}
|
|
@@ -4973,6 +5110,12 @@ AI Assistants
|
|
|
4973
5110
|
function unsupportedBrowserFlag(value, supported) {
|
|
4974
5111
|
return `${getLoggingPrefix('error')} Unsupported --browser value: ${value}. Supported: ${supported.join(', ')}.`;
|
|
4975
5112
|
}
|
|
5113
|
+
function safariOnlyOption(flags) {
|
|
5114
|
+
return `${getLoggingPrefix('error')} ${flags.map(messages_code).join(', ')} only appl${1 === flags.length ? 'ies' : 'y'} to Safari targets. Add ${messages_code('--browser safari')} (or ${messages_code('webkit-based')}).`;
|
|
5115
|
+
}
|
|
5116
|
+
function safariInvalidBundleId(bundleId) {
|
|
5117
|
+
return `${getLoggingPrefix('error')} Invalid bundle identifier: ${messages_code(bundleId)}\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')}).`;
|
|
5118
|
+
}
|
|
4976
5119
|
function safariCommandNotSupported(command) {
|
|
4977
5120
|
return `${getLoggingPrefix('error')} ${messages_code(command)} can't load an extension into Safari automatically.\nSafari extensions ship inside a signed app and are enabled by hand, so there's no live browser session to load into — unlike Chromium and Firefox.\nBuild the Safari app instead: ${messages_code('extension build --browser safari')}\nThen open the generated app and enable it in Safari → Settings → Extensions.`;
|
|
4978
5121
|
}
|
|
@@ -6156,6 +6299,9 @@ Cross-Browser Compatibility
|
|
|
6156
6299
|
function deriveBundleId(appName) {
|
|
6157
6300
|
return `dev.extensionjs.${bundleSegment(appName)}`;
|
|
6158
6301
|
}
|
|
6302
|
+
function isValidBundleId(value) {
|
|
6303
|
+
return /^[A-Za-z][A-Za-z0-9-]*(\.[A-Za-z][A-Za-z0-9-]*)+$/.test(value);
|
|
6304
|
+
}
|
|
6159
6305
|
function readManifest(extensionDir) {
|
|
6160
6306
|
try {
|
|
6161
6307
|
const manifestPath = external_path_.join(extensionDir, 'manifest.json');
|
|
@@ -6167,13 +6313,16 @@ Cross-Browser Compatibility
|
|
|
6167
6313
|
const extensionDir = String(compilation?.options?.output?.path || '');
|
|
6168
6314
|
const manifest = readManifest(extensionDir);
|
|
6169
6315
|
const appName = sanitizeAppName(String(host.appName || manifest?.name || 'Extension'));
|
|
6170
|
-
const
|
|
6316
|
+
const userBundleId = String(host.bundleId || '').trim();
|
|
6317
|
+
const bundleIdDerived = !userBundleId;
|
|
6318
|
+
const bundleIdentifier = bundleIdDerived ? deriveBundleId(appName) : userBundleId;
|
|
6171
6319
|
const projectLocation = `${extensionDir.replace(/[\\/]+$/, '')}-xcode`;
|
|
6172
6320
|
return {
|
|
6173
6321
|
extensionDir,
|
|
6174
6322
|
projectLocation,
|
|
6175
6323
|
appName,
|
|
6176
6324
|
bundleIdentifier,
|
|
6325
|
+
bundleIdDerived,
|
|
6177
6326
|
macOsOnly: false !== host.macOsOnly,
|
|
6178
6327
|
language: 'swift',
|
|
6179
6328
|
open: !host.noOpen,
|
|
@@ -6250,19 +6399,31 @@ Cross-Browser Compatibility
|
|
|
6250
6399
|
return raw;
|
|
6251
6400
|
}
|
|
6252
6401
|
}
|
|
6402
|
+
function composeProjectFingerprint(config) {
|
|
6403
|
+
return JSON.stringify({
|
|
6404
|
+
v: 2,
|
|
6405
|
+
identity: {
|
|
6406
|
+
appName: config.appName,
|
|
6407
|
+
bundleId: config.bundleIdentifier,
|
|
6408
|
+
macOsOnly: config.macOsOnly
|
|
6409
|
+
},
|
|
6410
|
+
manifest: normalizeManifest(readManifestRaw(config.extensionDir))
|
|
6411
|
+
});
|
|
6412
|
+
}
|
|
6253
6413
|
function saveManifestFingerprint(config) {
|
|
6254
|
-
const raw = readManifestRaw(config.extensionDir);
|
|
6255
6414
|
external_fs_.mkdirSync(external_path_.dirname(manifestFingerprintPath(config)), {
|
|
6256
6415
|
recursive: true
|
|
6257
6416
|
});
|
|
6258
|
-
external_fs_.writeFileSync(manifestFingerprintPath(config),
|
|
6417
|
+
external_fs_.writeFileSync(manifestFingerprintPath(config), composeProjectFingerprint(config), 'utf8');
|
|
6259
6418
|
}
|
|
6260
6419
|
function isProjectStale(config) {
|
|
6261
6420
|
const fpPath = manifestFingerprintPath(config);
|
|
6262
6421
|
if (!external_fs_.existsSync(fpPath)) return true;
|
|
6263
6422
|
const stored = external_fs_.readFileSync(fpPath, 'utf8');
|
|
6264
|
-
|
|
6265
|
-
|
|
6423
|
+
return stored !== composeProjectFingerprint(config);
|
|
6424
|
+
}
|
|
6425
|
+
function alignBundleIdentifiers(pbxprojContent, bundleId) {
|
|
6426
|
+
return pbxprojContent.replace(/PRODUCT_BUNDLE_IDENTIFIER = ("?)([^;"]+)\1;/g, (_match, _quote, value)=>/\.Extension$/.test(String(value)) ? `PRODUCT_BUNDLE_IDENTIFIER = "${bundleId}.Extension";` : `PRODUCT_BUNDLE_IDENTIFIER = "${bundleId}";`);
|
|
6266
6427
|
}
|
|
6267
6428
|
const PRESERVED_SETTINGS = [
|
|
6268
6429
|
'DEVELOPMENT_TEAM',
|
|
@@ -6319,19 +6480,17 @@ Cross-Browser Compatibility
|
|
|
6319
6480
|
function delay(ms) {
|
|
6320
6481
|
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
6321
6482
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
child.on('error', ()=>resolve(false));
|
|
6329
|
-
child.on('close', (code)=>resolve(0 === code));
|
|
6330
|
-
});
|
|
6483
|
+
const TOOL_TAIL_LINES = 50;
|
|
6484
|
+
const TOOL_TAIL_BYTES = 8192;
|
|
6485
|
+
function toolOutputTail(output) {
|
|
6486
|
+
const lines = output.slice(4 * -TOOL_TAIL_BYTES).split(/\r?\n/).filter((line)=>line.trim().length > 0);
|
|
6487
|
+
const tail = lines.slice(-TOOL_TAIL_LINES).join('\n');
|
|
6488
|
+
return tail.length > TOOL_TAIL_BYTES ? tail.slice(-TOOL_TAIL_BYTES) : tail;
|
|
6331
6489
|
}
|
|
6332
|
-
function
|
|
6490
|
+
function runTool(bin, args, opts) {
|
|
6491
|
+
const streamOutput = 'true' === process.env.EXTENSION_AUTHOR_MODE && !opts?.quiet;
|
|
6333
6492
|
return new Promise((resolve)=>{
|
|
6334
|
-
let
|
|
6493
|
+
let output = '';
|
|
6335
6494
|
const child = (0, external_child_process_.spawn)(bin, args, {
|
|
6336
6495
|
stdio: [
|
|
6337
6496
|
'ignore',
|
|
@@ -6339,26 +6498,38 @@ Cross-Browser Compatibility
|
|
|
6339
6498
|
'pipe'
|
|
6340
6499
|
]
|
|
6341
6500
|
});
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6501
|
+
const onChunk = (chunk)=>{
|
|
6502
|
+
const text = String(chunk);
|
|
6503
|
+
output += text;
|
|
6504
|
+
if (output.length > 8 * TOOL_TAIL_BYTES) output = output.slice(4 * -TOOL_TAIL_BYTES);
|
|
6505
|
+
if (streamOutput) process.stdout.write(text);
|
|
6506
|
+
};
|
|
6507
|
+
child.stdout?.on('data', onChunk);
|
|
6508
|
+
child.stderr?.on('data', onChunk);
|
|
6509
|
+
child.on('error', (error)=>resolve({
|
|
6346
6510
|
ok: false,
|
|
6347
|
-
|
|
6511
|
+
code: null,
|
|
6512
|
+
output: `${output}${String(error)}`
|
|
6348
6513
|
}));
|
|
6349
6514
|
child.on('close', (code)=>resolve({
|
|
6350
6515
|
ok: 0 === code,
|
|
6351
|
-
|
|
6516
|
+
code,
|
|
6517
|
+
output
|
|
6352
6518
|
}));
|
|
6353
6519
|
});
|
|
6354
6520
|
}
|
|
6521
|
+
function converterWarnings(output) {
|
|
6522
|
+
return output.split(/\r?\n/).filter((line)=>/warning/i.test(line)).map((line)=>line.trim()).filter((line)=>line.length > 0);
|
|
6523
|
+
}
|
|
6355
6524
|
async function confirmRegisteredWithSafari(bundleIdentifier) {
|
|
6356
6525
|
const needle = `${bundleIdentifier}.Extension`;
|
|
6357
6526
|
for(let attempt = 0; attempt < 6; attempt += 1){
|
|
6358
|
-
const { ok,
|
|
6527
|
+
const { ok, output } = await runTool('pluginkit', [
|
|
6359
6528
|
'-m'
|
|
6360
|
-
]
|
|
6361
|
-
|
|
6529
|
+
], {
|
|
6530
|
+
quiet: true
|
|
6531
|
+
});
|
|
6532
|
+
if (ok && output.includes(needle)) return true;
|
|
6362
6533
|
await delay(800);
|
|
6363
6534
|
}
|
|
6364
6535
|
return false;
|
|
@@ -6372,6 +6543,20 @@ Cross-Browser Compatibility
|
|
|
6372
6543
|
}
|
|
6373
6544
|
return null;
|
|
6374
6545
|
}
|
|
6546
|
+
function safariBuildPreflight() {
|
|
6547
|
+
const tc = detectSafariToolchain();
|
|
6548
|
+
if (!tc.platformOk) return {
|
|
6549
|
+
severity: 'skip',
|
|
6550
|
+
message: messages.UW(process.platform)
|
|
6551
|
+
};
|
|
6552
|
+
if (!tc.ok) return {
|
|
6553
|
+
severity: 'fatal',
|
|
6554
|
+
message: tc.needsFullXcode ? messages.Xi(tc.developerDir) : messages.Cs(tc.converter ? 'xcodebuild' : 'safari-web-extension-converter')
|
|
6555
|
+
};
|
|
6556
|
+
return {
|
|
6557
|
+
severity: 'ok'
|
|
6558
|
+
};
|
|
6559
|
+
}
|
|
6375
6560
|
async function runSafariPipeline(compilation, host, logger, mode) {
|
|
6376
6561
|
const config = resolveSafariBuildConfig(compilation, host);
|
|
6377
6562
|
const converterArgs = composeConverterArgs(config);
|
|
@@ -6390,10 +6575,24 @@ Cross-Browser Compatibility
|
|
|
6390
6575
|
const projectExists = external_fs_.existsSync(xcodeProjectPath(config));
|
|
6391
6576
|
const needsConversion = !projectExists || host.forceRegenerate || isProjectStale(config);
|
|
6392
6577
|
if (needsConversion) {
|
|
6393
|
-
if (projectExists)
|
|
6578
|
+
if (projectExists) {
|
|
6579
|
+
logger.info?.(host.forceRegenerate ? messages.Tq() : messages.jc());
|
|
6580
|
+
logger.warn?.(messages.H1([
|
|
6581
|
+
...PRESERVED_SETTINGS
|
|
6582
|
+
]));
|
|
6583
|
+
}
|
|
6394
6584
|
const { saved, restore } = backupAndRestoreXcodeSettings(config);
|
|
6395
6585
|
logger.info?.(messages.uK(config.extensionDir));
|
|
6396
|
-
|
|
6586
|
+
const converted = await runTool('xcrun', converterArgs);
|
|
6587
|
+
if (!converted.ok) {
|
|
6588
|
+
const tail = toolOutputTail(converted.output);
|
|
6589
|
+
logger.error?.(messages.A('safari-web-extension-converter', converted.code, tail));
|
|
6590
|
+
throw new Error(`safari-web-extension-converter failed (exit ${converted.code})\n${tail}`);
|
|
6591
|
+
}
|
|
6592
|
+
const warnings = converterWarnings(converted.output);
|
|
6593
|
+
if (warnings.length > 0) logger.warn?.(messages.y1(warnings));
|
|
6594
|
+
const projFile = pbxprojPath(config);
|
|
6595
|
+
if (external_fs_.existsSync(projFile)) external_fs_.writeFileSync(projFile, alignBundleIdentifiers(external_fs_.readFileSync(projFile, 'utf8'), config.bundleIdentifier), 'utf8');
|
|
6397
6596
|
restore();
|
|
6398
6597
|
const preservedKeys = Object.keys(saved);
|
|
6399
6598
|
if (preservedKeys.length > 0) logger.info?.(messages.oB(preservedKeys));
|
|
@@ -6401,21 +6600,26 @@ Cross-Browser Compatibility
|
|
|
6401
6600
|
logger.info?.(messages.l_(config.projectLocation));
|
|
6402
6601
|
} else logger.info?.(messages.oM());
|
|
6403
6602
|
if ('full' === mode) logger.info?.(messages.jR(macOsSchemeName(config)));
|
|
6404
|
-
|
|
6603
|
+
const built = await runTool('xcodebuild', xcodebuildArgs);
|
|
6604
|
+
if (!built.ok) {
|
|
6605
|
+
const tail = toolOutputTail(built.output);
|
|
6606
|
+
logger.error?.(messages.A('xcodebuild', built.code, tail));
|
|
6607
|
+
throw new Error(`xcodebuild failed (exit ${built.code})\n${tail}`);
|
|
6608
|
+
}
|
|
6405
6609
|
const appPath = builtAppPath(config);
|
|
6406
6610
|
if ('resync' === mode) return void logger.info?.(messages.cF(config.appName));
|
|
6407
6611
|
logger.info?.(messages.Rl(appPath));
|
|
6408
|
-
if (config.
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6612
|
+
if (config.bundleIdDerived) logger.info?.(messages.$q(config.bundleIdentifier));
|
|
6613
|
+
if (!config.open) return void logger.info?.(messages.aO(appPath, config.appName));
|
|
6614
|
+
const target = external_fs_.existsSync(appPath) ? appPath : xcodeProjectPath(config);
|
|
6615
|
+
logger.info?.(messages.fw(target));
|
|
6616
|
+
await runTool('open', [
|
|
6617
|
+
target
|
|
6618
|
+
]);
|
|
6619
|
+
if (config.safariBinary) await runTool('open', [
|
|
6620
|
+
'-a',
|
|
6621
|
+
config.safariBinary
|
|
6622
|
+
]);
|
|
6419
6623
|
logger.info?.(messages.fO(config.appName));
|
|
6420
6624
|
if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
|
|
6421
6625
|
else logger.warn?.(messages.qY(config.appName));
|
|
@@ -6454,7 +6658,7 @@ Cross-Browser Compatibility
|
|
|
6454
6658
|
return values.length > 0 ? values : void 0;
|
|
6455
6659
|
}
|
|
6456
6660
|
function registerDevCommand(program) {
|
|
6457
|
-
program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --no-reload emit a dev-mode dist without the content-script reload runtime; tabs need manual reload to see changes\n --wait wait for ready contract and exit\n --wait-format pretty|json output for wait mode\n").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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-open', 'do not open the browser automatically (default: open)').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('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').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` (runs arbitrary code in a context; writes a 0600 session token)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
|
|
6661
|
+
program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --no-reload emit a dev-mode dist without the content-script reload runtime; tabs need manual reload to see changes\n --wait wait for ready contract and exit\n --wait-format pretty|json output for wait mode\n").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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').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').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-open', 'do not open the browser automatically (default: open)').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('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').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` (runs arbitrary code in a context; writes a 0600 session token)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
|
|
6458
6662
|
if (devOptions.author || devOptions['authorMode']) {
|
|
6459
6663
|
process.env.EXTENSION_AUTHOR_MODE = 'true';
|
|
6460
6664
|
if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
|
|
@@ -6463,6 +6667,33 @@ Cross-Browser Compatibility
|
|
|
6463
6667
|
validateVendorsOrExit(list, (invalid, supported)=>{
|
|
6464
6668
|
console.error(unsupportedBrowserFlag(invalid, supported));
|
|
6465
6669
|
});
|
|
6670
|
+
const opts = devOptions;
|
|
6671
|
+
const safariOnlyFlags = [
|
|
6672
|
+
[
|
|
6673
|
+
'--safari-binary',
|
|
6674
|
+
opts.safariBinary
|
|
6675
|
+
],
|
|
6676
|
+
[
|
|
6677
|
+
'--app-name',
|
|
6678
|
+
opts.appName
|
|
6679
|
+
],
|
|
6680
|
+
[
|
|
6681
|
+
'--bundle-id',
|
|
6682
|
+
opts.bundleId
|
|
6683
|
+
],
|
|
6684
|
+
[
|
|
6685
|
+
'--force-regenerate',
|
|
6686
|
+
opts.forceRegenerate
|
|
6687
|
+
]
|
|
6688
|
+
].filter(([, value])=>void 0 !== value && false !== value);
|
|
6689
|
+
if (safariOnlyFlags.length > 0 && !list.some(isSafariVendor)) {
|
|
6690
|
+
console.error(safariOnlyOption(safariOnlyFlags.map(([flag])=>flag)));
|
|
6691
|
+
process.exit(1);
|
|
6692
|
+
}
|
|
6693
|
+
if (opts.bundleId && !isValidBundleId(opts.bundleId)) {
|
|
6694
|
+
console.error(safariInvalidBundleId(opts.bundleId));
|
|
6695
|
+
process.exit(1);
|
|
6696
|
+
}
|
|
6466
6697
|
if (list.some(isSafariVendor)) {
|
|
6467
6698
|
const issue = safariPreflightError();
|
|
6468
6699
|
if (issue) {
|
|
@@ -6514,14 +6745,15 @@ Cross-Browser Compatibility
|
|
|
6514
6745
|
logUrl: devOptions.logUrl,
|
|
6515
6746
|
logTab: devOptions.logTab,
|
|
6516
6747
|
launcher: noBrowser ? void 0 : browsers.launchBrowser,
|
|
6517
|
-
safariPackager: async (distPath, mode)=>{
|
|
6748
|
+
safariPackager: async (distPath, mode, overrides)=>{
|
|
6518
6749
|
await packageSafariExtension({
|
|
6519
6750
|
extension: [
|
|
6520
6751
|
distPath
|
|
6521
6752
|
],
|
|
6522
6753
|
browser: vendor,
|
|
6523
6754
|
noOpen: false === devOptions.open,
|
|
6524
|
-
dryRun: false
|
|
6755
|
+
dryRun: false,
|
|
6756
|
+
...overrides
|
|
6525
6757
|
}, distPath, void 0, mode);
|
|
6526
6758
|
}
|
|
6527
6759
|
};
|
|
@@ -6645,7 +6877,7 @@ Cross-Browser Compatibility
|
|
|
6645
6877
|
});
|
|
6646
6878
|
}
|
|
6647
6879
|
function registerBuildCommand(program) {
|
|
6648
|
-
program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', '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('--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]', 'whether or not to open the browser automatically. Defaults to `false`', parseOptionalBoolean).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('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...buildOptions }) {
|
|
6880
|
+
program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', '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('--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]', 'whether or not to open the browser automatically. Defaults to `false`', parseOptionalBoolean).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('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...buildOptions }) {
|
|
6649
6881
|
if (buildOptions.author || buildOptions['authorMode']) {
|
|
6650
6882
|
process.env.EXTENSION_AUTHOR_MODE = 'true';
|
|
6651
6883
|
if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
|
|
@@ -6663,12 +6895,43 @@ Cross-Browser Compatibility
|
|
|
6663
6895
|
process.exit(1);
|
|
6664
6896
|
}
|
|
6665
6897
|
}
|
|
6898
|
+
const safariOnlyFlags = [
|
|
6899
|
+
[
|
|
6900
|
+
'--open',
|
|
6901
|
+
buildOptions.open
|
|
6902
|
+
],
|
|
6903
|
+
[
|
|
6904
|
+
'--app-name',
|
|
6905
|
+
buildOptions.appName
|
|
6906
|
+
],
|
|
6907
|
+
[
|
|
6908
|
+
'--bundle-id',
|
|
6909
|
+
buildOptions.bundleId
|
|
6910
|
+
],
|
|
6911
|
+
[
|
|
6912
|
+
'--force-regenerate',
|
|
6913
|
+
buildOptions.forceRegenerate
|
|
6914
|
+
]
|
|
6915
|
+
].filter(([, value])=>void 0 !== value && false !== value);
|
|
6916
|
+
if (safariOnlyFlags.length > 0 && !list.some(isSafariVendor)) {
|
|
6917
|
+
console.error(safariOnlyOption(safariOnlyFlags.map(([flag])=>flag)));
|
|
6918
|
+
process.exit(1);
|
|
6919
|
+
}
|
|
6920
|
+
if (buildOptions.bundleId && !isValidBundleId(buildOptions.bundleId)) {
|
|
6921
|
+
console.error(safariInvalidBundleId(buildOptions.bundleId));
|
|
6922
|
+
process.exit(1);
|
|
6923
|
+
}
|
|
6924
|
+
let safariPackagingEnabled = true;
|
|
6666
6925
|
if (list.some(isSafariVendor)) {
|
|
6667
|
-
const
|
|
6668
|
-
if (
|
|
6669
|
-
console.error(
|
|
6926
|
+
const preflight = safariBuildPreflight();
|
|
6927
|
+
if ('fatal' === preflight.severity) {
|
|
6928
|
+
console.error(preflight.message);
|
|
6670
6929
|
process.exit(1);
|
|
6671
6930
|
}
|
|
6931
|
+
if ('skip' === preflight.severity) {
|
|
6932
|
+
safariPackagingEnabled = false;
|
|
6933
|
+
console.warn(preflight.message);
|
|
6934
|
+
}
|
|
6672
6935
|
}
|
|
6673
6936
|
const { extensionBuild } = await loadExtensionDevelopModule();
|
|
6674
6937
|
for (const vendor of list)await extensionBuild(pathOrRemoteUrl, {
|
|
@@ -6681,16 +6944,20 @@ Cross-Browser Compatibility
|
|
|
6681
6944
|
install: buildOptions.install,
|
|
6682
6945
|
extensions: parseExtensionsList(buildOptions.extensions),
|
|
6683
6946
|
mode,
|
|
6684
|
-
|
|
6947
|
+
appName: buildOptions.appName,
|
|
6948
|
+
bundleId: buildOptions.bundleId,
|
|
6949
|
+
forceRegenerate: buildOptions.forceRegenerate,
|
|
6950
|
+
safariPackager: safariPackagingEnabled ? async (distPath, packagerMode, overrides)=>{
|
|
6685
6951
|
await packageSafariExtension({
|
|
6686
6952
|
extension: [
|
|
6687
6953
|
distPath
|
|
6688
6954
|
],
|
|
6689
6955
|
browser: vendor,
|
|
6690
|
-
noOpen:
|
|
6691
|
-
dryRun: false
|
|
6956
|
+
noOpen: true !== buildOptions.open,
|
|
6957
|
+
dryRun: false,
|
|
6958
|
+
...overrides
|
|
6692
6959
|
}, distPath, void 0, packagerMode);
|
|
6693
|
-
}
|
|
6960
|
+
} : void 0
|
|
6694
6961
|
});
|
|
6695
6962
|
});
|
|
6696
6963
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* dev session just wedges with no CDP target. Diagnose them before spawn
|
|
5
5
|
* and say why, like the resolved-binary line.
|
|
6
6
|
*/
|
|
7
|
-
export type ChromiumManifestRefusal = 'mv2' | 'mv3-background-scripts' | null;
|
|
7
|
+
export type ChromiumManifestRefusal = 'mv2' | 'mv3-background-scripts' | 'unsupported-manifest-version' | null;
|
|
8
8
|
export declare function diagnoseChromiumManifestRefusal(manifest: unknown): ChromiumManifestRefusal;
|
|
9
9
|
/**
|
|
10
10
|
* Match patterns Chrome's grammar refuses. ONE invalid pattern in
|
|
@@ -27,13 +27,37 @@ export declare function diagnoseChromiumManifestRefusal(manifest: unknown): Chro
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function findInvalidMatchPatterns(manifest: unknown): string[];
|
|
29
29
|
/**
|
|
30
|
-
* Other manifest shapes Chrome refuses outright, each proven
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* it instead of printing an ID for an
|
|
30
|
+
* Other manifest shapes Chrome refuses outright, each proven with CDP
|
|
31
|
+
* `Extensions.loadUnpacked` (which, unlike --load-extension, reports the
|
|
32
|
+
* reason) — the wild-subject shapes on Chrome 150 (2026-07-11) and the
|
|
33
|
+
* fixture batch on Chrome 150 (2026-07-13). The refusal is silent under
|
|
34
|
+
* --load-extension, so dev must name it instead of printing an ID for an
|
|
35
|
+
* extension that never loads.
|
|
36
|
+
*
|
|
37
|
+
* NOT refusals — verified to LOAD on Chrome 150 (2026-07-13, CDP
|
|
38
|
+
* loadUnpacked); do NOT add these however fatal they look:
|
|
39
|
+
* - MV3 `background.page`; `background.persistent` true or false
|
|
40
|
+
* - icon files with undecodable bytes, and SVG icons — only a MISSING or
|
|
41
|
+
* 0-byte icon file refuses
|
|
42
|
+
* - unknown `permissions` entries; MV2 keys like `browser_action` under MV3
|
|
43
|
+
* - a WAR dictionary with only `use_dynamic_url` beside `resources`
|
|
44
|
+
* - `__MSG_@@predefined__` variables; catalog-key case differences (message
|
|
45
|
+
* lookup is case-insensitive)
|
|
46
|
+
* And on Firefox 147 (2026-07-13, RDP installTemporaryAddon): explicit AND
|
|
47
|
+
* wildcard ports in match patterns install fine — the old "host must not
|
|
48
|
+
* include a port" grammar is gone; do not resurrect it for gecko.
|
|
49
|
+
*
|
|
50
|
+
* Safari (2026-07-13, macOS 15.7.7): safari-web-extension-converter
|
|
51
|
+
* CONVERTS every one of these shapes with exit 0 — missing name, bad locale
|
|
52
|
+
* catalogs, all of it. The converter is not a refusal surface and must not
|
|
53
|
+
* be gated on manifest shapes; Safari's real refusals happen at runtime
|
|
54
|
+
* inside Safari, which has no scriptable install path to verify against.
|
|
55
|
+
* Converter/xcodebuild exit codes still matter (toolchain failures), but
|
|
56
|
+
* they say nothing about manifest validity.
|
|
57
|
+
*
|
|
58
|
+
* `browserVersion` (when known) enables the minimum_chrome_version compare.
|
|
35
59
|
*/
|
|
36
|
-
export declare function findChromiumLoadBlockers(manifest: unknown): string[];
|
|
60
|
+
export declare function findChromiumLoadBlockers(manifest: unknown, browserVersion?: string): string[];
|
|
37
61
|
/**
|
|
38
62
|
* Icon files Chrome cannot load — missing from the extension directory or
|
|
39
63
|
* present but empty (0 bytes, undecodable). Either one makes Chrome refuse
|
|
@@ -44,3 +68,17 @@ export declare function findChromiumLoadBlockers(manifest: unknown): string[];
|
|
|
44
68
|
* validates at install: `icons` and `*_action.default_icon`.
|
|
45
69
|
*/
|
|
46
70
|
export declare function findUnloadableIconFiles(manifest: unknown, extensionDir: string): string[];
|
|
71
|
+
/**
|
|
72
|
+
* The locale shapes Chrome refuses the whole extension over, all verified
|
|
73
|
+
* live on Chrome 150 (2026-07-13, CDP loadUnpacked; fixtures 03/04/05/20/
|
|
74
|
+
* 22/29). Common in converted/repacked extensions that lost their _locales
|
|
75
|
+
* tree. Verified tolerances: an EMPTY messages.json loads; message-key
|
|
76
|
+
* lookup is case-insensitive; `__MSG_@@predefined__` never needs a catalog.
|
|
77
|
+
*/
|
|
78
|
+
export declare function findLocaleLoadBlockers(manifest: unknown, extensionDir: string): string[];
|
|
79
|
+
/**
|
|
80
|
+
* "File does not exist: <path>/schema.json" — a storage.managed_schema
|
|
81
|
+
* pointing at a file that is not in the extension directory refuses the
|
|
82
|
+
* whole extension (fixture 19, Chrome 150).
|
|
83
|
+
*/
|
|
84
|
+
export declare function findMissingManagedSchema(manifest: unknown, extensionDir: string): string[];
|
|
@@ -17,6 +17,7 @@ export declare function resolvedBrowserBinary(browser: Browser, binaryPath: stri
|
|
|
17
17
|
export declare function preferringSystemBrowserOverSnapshot(systemBinary: string, snapshotBinary: string): string;
|
|
18
18
|
export declare function mv2NotSupportedByChromium(extensionPath: string): string;
|
|
19
19
|
export declare function mv3BackgroundScriptsNotSupportedByChromium(extensionPath: string): string;
|
|
20
|
+
export declare function unsupportedManifestVersionOnChromium(extensionPath: string, declared: unknown): string;
|
|
20
21
|
export declare function chromiumInvalidMatchPatterns(extensionPath: string, patterns: string[]): string;
|
|
21
22
|
export declare function chromiumManifestLoadBlockers(extensionPath: string, blockers: string[]): string;
|
|
22
23
|
export declare function devChannelSnapshotInUse(binaryPath: string): string;
|
|
@@ -58,6 +59,7 @@ export declare function firefoxDryRunBinary(path: string): string;
|
|
|
58
59
|
export declare function firefoxDryRunConfig(cfg: string): string;
|
|
59
60
|
export declare function safariBuildCalled(): string;
|
|
60
61
|
export declare function safariRequiresMacOS(platform: string): string;
|
|
62
|
+
export declare function safariPackagingSkippedNonMac(platform: string): string;
|
|
61
63
|
export declare function safariXcodeRequired(developerDir: string | null): string;
|
|
62
64
|
export declare function safariToolchainMissing(tool: string): string;
|
|
63
65
|
export declare function safariConverting(extensionDir: string): string;
|
|
@@ -65,7 +67,10 @@ export declare function safariConverted(projectDir: string): string;
|
|
|
65
67
|
export declare function safariBuilding(scheme: string): string;
|
|
66
68
|
export declare function safariBuilt(appPath: string): string;
|
|
67
69
|
export declare function safariOpening(target: string): string;
|
|
68
|
-
export declare function
|
|
70
|
+
export declare function safariToolFailed(tool: string, exitCode: number | null, outputTail: string): string;
|
|
71
|
+
export declare function safariConverterWarnings(warnings: string[]): string;
|
|
72
|
+
export declare function safariDefaultBundleIdNote(bundleId: string): string;
|
|
73
|
+
export declare function safariOpenHint(appPath: string, appName: string): string;
|
|
69
74
|
export declare function safariDryRunNotBuilding(): string;
|
|
70
75
|
export declare function safariDryRunConverter(cmd: string): string;
|
|
71
76
|
export declare function safariDryRunXcodebuild(cmd: string): string;
|
|
@@ -74,6 +79,8 @@ export declare function safariRegistered(appName: string): string;
|
|
|
74
79
|
export declare function safariNotYetRegistered(appName: string): string;
|
|
75
80
|
export declare function safariRebuilt(appName: string): string;
|
|
76
81
|
export declare function safariProjectStale(): string;
|
|
82
|
+
export declare function safariForcedRegeneration(): string;
|
|
83
|
+
export declare function safariRegenerationDiscards(preservedKeys: string[]): string;
|
|
77
84
|
export declare function safariSettingsPreserved(keys: string[]): string;
|
|
78
85
|
export declare function safariSkippingConversion(): string;
|
|
79
86
|
export declare function cdpClientFoundTargets(count: number): string;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { BrowserLogger } from '../../browsers-types';
|
|
2
2
|
import type { SafariPluginLike } from '../safari-types';
|
|
3
|
+
export declare function toolOutputTail(output: string): string;
|
|
3
4
|
export type SafariPipelineMode = 'full' | 'resync';
|
|
4
5
|
export declare function safariPreflightError(): string | null;
|
|
6
|
+
export interface SafariBuildPreflight {
|
|
7
|
+
severity: 'ok' | 'skip' | 'fatal';
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Preflight for `build`: a non-macOS host is not an error — the web-extension
|
|
12
|
+
* bundle is still produced and can be packaged later on a Mac — so packaging
|
|
13
|
+
* is skipped with a warning. A macOS host with a broken/missing Xcode stays
|
|
14
|
+
* fatal because the user can act on it locally. `dev` keeps the stricter
|
|
15
|
+
* safariPreflightError(): a Safari dev loop without packaging is pointless.
|
|
16
|
+
*/
|
|
17
|
+
export declare function safariBuildPreflight(): SafariBuildPreflight;
|
|
5
18
|
export declare function packageSafariExtension(host: SafariPluginLike, outputPath: string, logger?: BrowserLogger, mode?: SafariPipelineMode): Promise<void>;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { CompilationLike } from '../../browsers-types';
|
|
2
2
|
import type { SafariBuildConfig, SafariPluginLike } from '../safari-types';
|
|
3
|
+
/**
|
|
4
|
+
* Apple bundle identifiers: dot-separated segments of alphanumerics and
|
|
5
|
+
* hyphens, each starting with a letter, at least two segments (reverse-DNS).
|
|
6
|
+
*/
|
|
7
|
+
export declare function isValidBundleId(value: string): boolean;
|
|
3
8
|
export declare function resolveSafariBuildConfig(compilation: CompilationLike, host: SafariPluginLike): SafariBuildConfig;
|
|
4
9
|
export declare function composeConverterArgs(config: SafariBuildConfig): string[];
|
|
5
10
|
export declare function macOsSchemeName(config: SafariBuildConfig): string;
|
|
@@ -15,8 +20,27 @@ export declare function builtAppPath(config: SafariBuildConfig): string;
|
|
|
15
20
|
*/
|
|
16
21
|
export declare function composeXcodebuildArgs(config: SafariBuildConfig): string[];
|
|
17
22
|
export declare function manifestFingerprintPath(config: SafariBuildConfig): string;
|
|
23
|
+
/**
|
|
24
|
+
* v2 fingerprint: manifest content PLUS the identity inputs baked into the
|
|
25
|
+
* generated Xcode project (app name, bundle id, platform). An identity change
|
|
26
|
+
* must re-run the converter or the project keeps shipping the old identity.
|
|
27
|
+
* A v1 fingerprint (raw normalized manifest, no JSON envelope) never matches
|
|
28
|
+
* this shape, so old projects regenerate once and migrate automatically.
|
|
29
|
+
*/
|
|
30
|
+
export declare function composeProjectFingerprint(config: SafariBuildConfig): string;
|
|
18
31
|
export declare function saveManifestFingerprint(config: SafariBuildConfig): void;
|
|
19
32
|
export declare function isProjectStale(config: SafariBuildConfig): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The converter does not honor --bundle-identifier verbatim: it writes the
|
|
35
|
+
* appex id as `<identifier>.Extension` but derives the PARENT app id from the
|
|
36
|
+
* identifier's namespace plus the app-name segment (observed on Xcode 16/26).
|
|
37
|
+
* With a user-provided id whose last segment differs from the app name, the
|
|
38
|
+
* prefixes mismatch and ValidateEmbeddedBinary fails the build. Rewrite both
|
|
39
|
+
* PRODUCT_BUNDLE_IDENTIFIERs so the project always carries exactly the
|
|
40
|
+
* configured identity: app = <bundleId>, appex = <bundleId>.Extension.
|
|
41
|
+
*/
|
|
42
|
+
export declare function alignBundleIdentifiers(pbxprojContent: string, bundleId: string): string;
|
|
43
|
+
export declare const PRESERVED_SETTINGS: readonly ["DEVELOPMENT_TEAM", "CODE_SIGN_STYLE", "PROVISIONING_PROFILE_SPECIFIER"];
|
|
20
44
|
export declare function pbxprojPath(config: SafariBuildConfig): string;
|
|
21
45
|
export declare function extractXcodeUserSettings(pbxprojContent: string): Record<string, string>;
|
|
22
46
|
export declare function applyXcodeUserSettings(pbxprojContent: string, settings: Record<string, string>): string;
|
|
@@ -3,6 +3,7 @@ export type SafariPluginLike = Pick<PluginInterface, 'extension' | 'noOpen' | 'i
|
|
|
3
3
|
browser: PluginInterface['browser'];
|
|
4
4
|
safariBinary?: string;
|
|
5
5
|
appName?: string;
|
|
6
|
+
bundleId?: string;
|
|
6
7
|
macOsOnly?: boolean;
|
|
7
8
|
forceRegenerate?: boolean;
|
|
8
9
|
};
|
|
@@ -11,6 +12,8 @@ export interface SafariBuildConfig {
|
|
|
11
12
|
projectLocation: string;
|
|
12
13
|
appName: string;
|
|
13
14
|
bundleIdentifier: string;
|
|
15
|
+
/** True when the bundle id was derived (dev.extensionjs.*), not user-set. */
|
|
16
|
+
bundleIdDerived: boolean;
|
|
14
17
|
macOsOnly: boolean;
|
|
15
18
|
language: 'swift' | 'objc';
|
|
16
19
|
open: boolean;
|
|
@@ -36,6 +36,8 @@ export declare function noURLWithoutStart(argument: string): string;
|
|
|
36
36
|
export declare function notImplemented(argument: string): string;
|
|
37
37
|
export declare function programUserHelp(): string;
|
|
38
38
|
export declare function unsupportedBrowserFlag(value: string, supported: string[]): string;
|
|
39
|
+
export declare function safariOnlyOption(flags: string[]): string;
|
|
40
|
+
export declare function safariInvalidBundleId(bundleId: string): string;
|
|
39
41
|
export declare function safariCommandNotSupported(command: 'dev' | 'preview' | 'start'): string;
|
|
40
42
|
export declare function programAIHelp(): string;
|
|
41
43
|
export type ProgramAIHelpJSON = {
|
package/package.json
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"extension": "./bin/extension.cjs"
|
|
39
39
|
},
|
|
40
40
|
"name": "extension",
|
|
41
|
-
"version": "4.0.
|
|
41
|
+
"version": "4.0.10",
|
|
42
42
|
"description": "The cross-browser extension framework. Build Chrome, Edge, Firefox, and Safari extensions with no build configuration.",
|
|
43
43
|
"homepage": "https://extension.js.org/",
|
|
44
44
|
"bugs": {
|
|
@@ -109,9 +109,9 @@
|
|
|
109
109
|
"vivaldi-location2": "2.1.0",
|
|
110
110
|
"waterfox-location": "2.1.0",
|
|
111
111
|
"yandex-location": "2.1.0",
|
|
112
|
-
"extension-create": "4.0.
|
|
113
|
-
"extension-develop": "4.0.
|
|
114
|
-
"extension-install": "4.0.
|
|
112
|
+
"extension-create": "4.0.10",
|
|
113
|
+
"extension-develop": "4.0.10",
|
|
114
|
+
"extension-install": "4.0.10",
|
|
115
115
|
"commander": "^15.0.0",
|
|
116
116
|
"pintor": "0.3.0",
|
|
117
117
|
"semver": "^7.7.3",
|