chore: scaffold MV3 extension with TypeScript + esbuild toolchain

This commit is contained in:
schmop 2026-04-23 01:24:59 +02:00
commit 8c05cffa67
6 changed files with 5512 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
build/
web-ext-artifacts/
*.xpi
.DS_Store
.claude/settings.local.json

57
esbuild.config.mjs Normal file
View File

@ -0,0 +1,57 @@
import esbuild from 'esbuild';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
const watch = process.argv.includes('--watch');
const __dirname = dirname(fileURLToPath(import.meta.url));
// isomorphic-git's `.` subpath export only points at the CJS build, which
// does a top-level `require('crypto')` that esbuild can't bundle for the
// browser. The package ships an ESM entry at ./index.js but the exports
// map hides it. Redirect the bare specifier to the ESM file by hand.
const isogitEsmPlugin = {
name: 'isogit-esm',
setup(build) {
const esmEntry = resolve(__dirname, 'node_modules/isomorphic-git/index.js');
build.onResolve({ filter: /^isomorphic-git$/ }, () => ({ path: esmEntry }));
},
};
const common = {
bundle: true,
format: 'iife',
target: ['firefox115'],
platform: 'browser',
mainFields: ['browser', 'module', 'main'],
conditions: ['browser', 'import', 'default'],
logLevel: 'info',
sourcemap: true,
minify: false,
define: {
'process.env.NODE_ENV': '"production"',
'global': 'globalThis',
},
plugins: [isogitEsmPlugin],
};
// Only the background bundle pulls in isomorphic-git and therefore needs
// the Buffer polyfill. popup/options talk to the background via runtime
// messages and stay tiny.
const polyfillInject = [resolve(__dirname, 'src/common/polyfills.ts')];
const entries = [
{ entryPoints: ['src/background/index.ts'], outfile: 'build/background.js', inject: polyfillInject },
{ entryPoints: ['src/popup/popup.ts'], outfile: 'build/popup.js' },
{ entryPoints: ['src/options/options.ts'], outfile: 'build/options.js' },
];
if (watch) {
for (const e of entries) {
const ctx = await esbuild.context({ ...common, ...e });
await ctx.watch();
}
console.log('watching…');
} else {
await Promise.all(entries.map(e => esbuild.build({ ...common, ...e })));
console.log('build ok');
}

15
manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"manifest_version": 3,
"name": "Passchmop",
"version": "0.1.0",
"description": "Git-backed, client-side-encrypted password manager.",
"browser_specific_settings": {
"gecko": {
"id": "passchmop@schmop",
"strict_min_version": "115.0",
"data_collection_permissions": {
"required": ["none"]
}
}
}
}

5386
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "passchmop",
"version": "0.1.0",
"description": "Firefox password manager with a client-side-encrypted git-backed vault",
"private": true,
"type": "module",
"scripts": {
"build": "node esbuild.config.mjs",
"watch": "node esbuild.config.mjs --watch",
"typecheck": "tsc --noEmit",
"run": "web-ext run --source-dir=. --start-url=about:debugging",
"package": "web-ext build --source-dir=. --overwrite-dest"
},
"dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"buffer": "^6.0.3",
"isomorphic-git": "^1.25.0"
},
"devDependencies": {
"@types/firefox-webext-browser": "^143.0.0",
"esbuild": "^0.25.0",
"typescript": "^6.0.3",
"web-ext": "^8.0.0"
}
}

23
tsconfig.json Normal file
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["firefox-webext-browser"],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}