chore: import upstream snapshot with attribution
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const extensionDir = path.resolve(__dirname, '..');
|
||||
const repoRoot = path.resolve(extensionDir, '..');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { outDir: path.join(repoRoot, 'extension-package') };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--out' && argv[i + 1]) {
|
||||
const outDir = argv[++i];
|
||||
args.outDir = path.isAbsolute(outDir)
|
||||
? outDir
|
||||
: path.resolve(process.cwd(), outDir);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
async function exists(targetPath) {
|
||||
try {
|
||||
await fs.access(targetPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalAsset(ref) {
|
||||
return typeof ref === 'string'
|
||||
&& ref.length > 0
|
||||
&& !ref.startsWith('http://')
|
||||
&& !ref.startsWith('https://')
|
||||
&& !ref.startsWith('//')
|
||||
&& !ref.startsWith('chrome://')
|
||||
&& !ref.startsWith('chrome-extension://')
|
||||
&& !ref.startsWith('data:')
|
||||
&& !ref.startsWith('#');
|
||||
}
|
||||
|
||||
function addLocalAsset(files, ref) {
|
||||
if (isLocalAsset(ref)) files.add(ref);
|
||||
}
|
||||
|
||||
function collectManifestEntrypoints(manifest) {
|
||||
const files = new Set(['manifest.json']);
|
||||
|
||||
addLocalAsset(files, manifest.background?.service_worker);
|
||||
addLocalAsset(files, manifest.action?.default_popup);
|
||||
addLocalAsset(files, manifest.options_page);
|
||||
addLocalAsset(files, manifest.devtools_page);
|
||||
addLocalAsset(files, manifest.side_panel?.default_path);
|
||||
|
||||
for (const ref of Object.values(manifest.icons ?? {})) addLocalAsset(files, ref);
|
||||
for (const ref of Object.values(manifest.action?.default_icon ?? {})) addLocalAsset(files, ref);
|
||||
for (const contentScript of manifest.content_scripts ?? []) {
|
||||
for (const jsFile of contentScript.js ?? []) addLocalAsset(files, jsFile);
|
||||
for (const cssFile of contentScript.css ?? []) addLocalAsset(files, cssFile);
|
||||
}
|
||||
for (const page of manifest.sandbox?.pages ?? []) addLocalAsset(files, page);
|
||||
for (const overridePage of Object.values(manifest.chrome_url_overrides ?? {})) addLocalAsset(files, overridePage);
|
||||
for (const entry of manifest.web_accessible_resources ?? []) {
|
||||
for (const resource of entry.resources ?? []) addLocalAsset(files, resource);
|
||||
}
|
||||
if (manifest.default_locale) files.add('_locales');
|
||||
|
||||
return [...files];
|
||||
}
|
||||
|
||||
async function collectHtmlDependencies(relativeHtmlPath, files, visited) {
|
||||
if (visited.has(relativeHtmlPath)) return;
|
||||
visited.add(relativeHtmlPath);
|
||||
|
||||
const htmlPath = path.join(extensionDir, relativeHtmlPath);
|
||||
const html = await fs.readFile(htmlPath, 'utf8');
|
||||
const attrRe = /\b(?:src|href)=["']([^"'#?]+(?:\?[^"']*)?)["']/gi;
|
||||
|
||||
for (const match of html.matchAll(attrRe)) {
|
||||
const rawRef = match[1];
|
||||
const cleanRef = rawRef.split('?')[0];
|
||||
if (!isLocalAsset(cleanRef)) continue;
|
||||
|
||||
const resolvedRelativePath = cleanRef.startsWith('/')
|
||||
? cleanRef.slice(1)
|
||||
: path.posix.normalize(path.posix.join(path.posix.dirname(relativeHtmlPath), cleanRef));
|
||||
|
||||
addLocalAsset(files, resolvedRelativePath);
|
||||
if (resolvedRelativePath.endsWith('.html')) {
|
||||
await collectHtmlDependencies(resolvedRelativePath, files, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function collectManifestAssets(manifest) {
|
||||
const files = new Set(collectManifestEntrypoints(manifest));
|
||||
const htmlPages = [];
|
||||
|
||||
if (manifest.action?.default_popup) {
|
||||
htmlPages.push(manifest.action.default_popup);
|
||||
}
|
||||
if (manifest.options_page) htmlPages.push(manifest.options_page);
|
||||
if (manifest.devtools_page) htmlPages.push(manifest.devtools_page);
|
||||
if (manifest.side_panel?.default_path) htmlPages.push(manifest.side_panel.default_path);
|
||||
for (const page of manifest.sandbox?.pages ?? []) htmlPages.push(page);
|
||||
for (const overridePage of Object.values(manifest.chrome_url_overrides ?? {})) htmlPages.push(overridePage);
|
||||
|
||||
const visited = new Set();
|
||||
for (const htmlPage of htmlPages) {
|
||||
if (isLocalAsset(htmlPage)) {
|
||||
await collectHtmlDependencies(htmlPage, files, visited);
|
||||
}
|
||||
}
|
||||
|
||||
return [...files];
|
||||
}
|
||||
|
||||
async function copyEntry(relativePath, outDir) {
|
||||
const fromPath = path.join(extensionDir, relativePath);
|
||||
const toPath = path.join(outDir, relativePath);
|
||||
const stats = await fs.stat(fromPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await fs.cp(fromPath, toPath, { recursive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(toPath), { recursive: true });
|
||||
await fs.copyFile(fromPath, toPath);
|
||||
}
|
||||
|
||||
async function findMissingEntries(baseDir, entries) {
|
||||
const missingEntries = [];
|
||||
for (const relativePath of entries) {
|
||||
const absolutePath = path.join(baseDir, relativePath);
|
||||
if (!(await exists(absolutePath))) {
|
||||
missingEntries.push(relativePath);
|
||||
}
|
||||
}
|
||||
return missingEntries;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { outDir } = parseArgs(process.argv.slice(2));
|
||||
const manifestPath = path.join(extensionDir, 'manifest.json');
|
||||
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
||||
|
||||
const requiredEntries = await collectManifestAssets(manifest);
|
||||
const missingEntries = await findMissingEntries(extensionDir, requiredEntries);
|
||||
|
||||
if (missingEntries.length > 0) {
|
||||
console.error('Missing files referenced by the extension package:');
|
||||
for (const missingEntry of missingEntries) console.error(`- ${missingEntry}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await fs.rm(outDir, { recursive: true, force: true });
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
|
||||
for (const relativePath of requiredEntries) {
|
||||
await copyEntry(relativePath, outDir);
|
||||
}
|
||||
|
||||
// Guard against regressions where manifest entry files (e.g. action.default_popup)
|
||||
// are accidentally omitted from the packaged directory.
|
||||
const packagedEntrypoints = collectManifestEntrypoints(manifest);
|
||||
const missingPackagedEntrypoints = await findMissingEntries(outDir, packagedEntrypoints);
|
||||
if (missingPackagedEntrypoints.length > 0) {
|
||||
console.error('Packaged extension is missing files referenced by manifest.json:');
|
||||
for (const missingEntry of missingPackagedEntrypoints) console.error(`- ${missingEntry}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Extension package prepared at ${path.relative(repoRoot, outDir) || outDir}`);
|
||||
}
|
||||
|
||||
await main();
|
||||
Reference in New Issue
Block a user