Skip to content
This repository was archived by the owner on Dec 29, 2025. It is now read-only.

Commit 0e3d02c

Browse files
committed
Implement dynamic manifest generation for Chrome/Firefox and dev/prod environments
1 parent e4272d8 commit 0e3d02c

6 files changed

Lines changed: 223 additions & 65 deletions

File tree

project/TASKS.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
at ThreadListReplyFetcher.ts:259:32
88
fetchAndDisplayReplies @ ThreadListReplyFetcher.ts:154
99
```
10-
- [ ] We have a single static manifest.json, but we actually need four different configurations, varying by browser and by environment, with two choices each.
11-
- [ ] By browser: Firefox and Chrome seem mutually incompatible. Firefox has this error: "background.service_worker is currently disabled. Add background.scripts." Meanwhile, background.scripts doesn't work in Chrome and we need to use service_worker there. So it seems a single manifest.json won't work in both browsers.
12-
- [ ] Split our single manifest.json into two: one for Chrome and one for Firefox, our two supported browsers. Fix the issue with scripts vs. service_worker.
13-
- [ ] In release.sh, build both versions and zip them separately. Only Firefox needs the source zip.
14-
- [ ] By environment: We have "http://localhost" for development and "https://api.threadloaf.com" for production. Development builds should only have the former and production builds should only have the latter.
15-
- [ ] Find a way to manage this without making four duplicate copies of the whole manifest.json. There are only minor differences between these configurations with most of the content being identical between all four.
10+
- [x] We have a single static manifest.json, but we actually need four different configurations, varying by browser and by environment, with two choices each.
11+
- [x] By browser: Firefox and Chrome seem mutually incompatible. Firefox has this error: "background.service_worker is currently disabled. Add background.scripts." Meanwhile, background.scripts doesn't work in Chrome and we need to use service_worker there. So it seems a single manifest.json won't work in both browsers.
12+
- [x] Split our single manifest.json into two: one for Chrome and one for Firefox, our two supported browsers. Fix the issue with scripts vs. service_worker.
13+
- [x] In release.sh, build both versions and zip them separately. Only Firefox needs the source zip.
14+
- [x] By environment: We have "http://localhost" for development and "https://api.threadloaf.com" for production. Development builds should only have the former and production builds should only have the latter.
15+
- [x] Find a way to manage this without making four duplicate copies of the whole manifest.json. There are only minor differences between these configurations with most of the content being identical between all four.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env node
2+
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
import { fileURLToPath } from "url";
6+
7+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
8+
9+
type Browser = "chrome" | "firefox";
10+
type Environment = "development" | "production";
11+
12+
interface ManifestConfig {
13+
browser: Browser;
14+
environment: Environment;
15+
}
16+
17+
function generateManifest(config: ManifestConfig): object {
18+
// Read the template
19+
const templatePath = path.join(currentDir, "manifest.template.json");
20+
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
21+
22+
// Clone the template
23+
const manifest = JSON.parse(JSON.stringify(template));
24+
25+
// Add browser-specific settings
26+
if (config.browser === "firefox") {
27+
manifest.browser_specific_settings = {
28+
gecko: {
29+
id: "{6384c57b-f03b-4de7-b146-d0159cde0ca2}",
30+
strict_min_version: "130.0",
31+
},
32+
};
33+
manifest.background = {
34+
scripts: ["background.js"],
35+
};
36+
} else {
37+
manifest.background = {
38+
service_worker: "background.js",
39+
};
40+
}
41+
42+
// Add environment-specific settings
43+
if (config.environment === "development") {
44+
manifest.host_permissions = ["http://localhost/*", "https://api.threadloaf.com/*"];
45+
manifest.content_scripts.push({
46+
matches: ["http://localhost/auth/callback*", "https://api.threadloaf.com/auth/callback*"],
47+
js: ["oauth_callback.js"],
48+
});
49+
} else {
50+
manifest.host_permissions = ["https://api.threadloaf.com/*"];
51+
manifest.content_scripts.push({
52+
matches: ["https://api.threadloaf.com/auth/callback*"],
53+
js: ["oauth_callback.js"],
54+
});
55+
}
56+
57+
return manifest;
58+
}
59+
60+
function main(): void {
61+
const args = process.argv.slice(2);
62+
63+
if (args.length !== 3) {
64+
console.error("Usage: generate-manifest.ts <browser> <environment> <output-file>");
65+
console.error("Browser: chrome | firefox");
66+
console.error("Environment: development | production");
67+
process.exit(1);
68+
}
69+
70+
const [browser, environment, outputFile] = args;
71+
72+
if (!["chrome", "firefox"].includes(browser)) {
73+
console.error("Invalid browser. Must be chrome or firefox");
74+
process.exit(1);
75+
}
76+
77+
if (!["development", "production"].includes(environment)) {
78+
console.error("Invalid environment. Must be development or production");
79+
process.exit(1);
80+
}
81+
82+
const config: ManifestConfig = {
83+
browser: browser as Browser,
84+
environment: environment as Environment,
85+
};
86+
87+
const manifest = generateManifest(config);
88+
fs.writeFileSync(outputFile, JSON.stringify(manifest, null, 2));
89+
console.log(`Generated ${browser} ${environment} manifest: ${outputFile}`);
90+
}
91+
92+
// Run main if this file is being executed directly
93+
if (import.meta.url === `file://${process.argv[1]}`) {
94+
main();
95+
}

src/threadloaf/manifest.json

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,47 @@
11
{
2-
"manifest_version": 3,
3-
"name": "Threadloaf",
4-
"version": "1.1.0",
5-
"description": "Enhances Discord with a threaded discussion view",
6-
"browser_specific_settings": {
7-
"gecko": {
8-
"id": "{6384c57b-f03b-4de7-b146-d0159cde0ca2}",
9-
"strict_min_version": "130.0"
10-
}
11-
},
12-
"icons": {
13-
"16": "threadloaf-16.png",
14-
"32": "threadloaf-32.png",
15-
"48": "threadloaf-48.png",
16-
"128": "threadloaf-128.png"
17-
},
18-
"action": {
19-
"default_popup": "popup.html",
20-
"default_icon": {
21-
"16": "threadloaf-16.png",
22-
"32": "threadloaf-32.png",
23-
"48": "threadloaf-48.png",
24-
"128": "threadloaf-128.png"
25-
}
26-
},
27-
"permissions": ["storage"],
28-
"host_permissions": [
29-
"http://localhost/*",
30-
"https://api.threadloaf.com/*"
31-
],
32-
"background": {
33-
"service_worker": "background.js"
2+
"manifest_version": 3,
3+
"name": "Threadloaf",
4+
"version": "1.1.0",
5+
"description": "Enhances Discord with a threaded discussion view",
6+
"icons": {
7+
"16": "threadloaf-16.png",
8+
"32": "threadloaf-32.png",
9+
"48": "threadloaf-48.png",
10+
"128": "threadloaf-128.png"
11+
},
12+
"action": {
13+
"default_popup": "popup.html",
14+
"default_icon": {
15+
"16": "threadloaf-16.png",
16+
"32": "threadloaf-32.png",
17+
"48": "threadloaf-48.png",
18+
"128": "threadloaf-128.png"
19+
}
20+
},
21+
"permissions": [
22+
"storage"
23+
],
24+
"content_scripts": [
25+
{
26+
"matches": [
27+
"*://discord.com/channels/*"
28+
],
29+
"js": [
30+
"content_script.js"
31+
]
3432
},
35-
"content_scripts": [
36-
{
37-
"matches": ["*://discord.com/channels/*"],
38-
"js": ["content_script.js"]
39-
},
40-
{
41-
"matches": [
42-
"http://localhost/auth/callback*",
43-
"https://api.threadloaf.com/auth/callback*"
44-
],
45-
"js": ["oauth_callback.js"]
46-
}
47-
],
48-
"web_accessible_resources": [{
33+
{
34+
"matches": [
35+
"http://localhost/auth/callback*",
36+
"https://api.threadloaf.com/auth/callback*"
37+
],
38+
"js": [
39+
"oauth_callback.js"
40+
]
41+
}
42+
],
43+
"web_accessible_resources": [
44+
{
4945
"resources": [
5046
"styles.css",
5147
"threadloaf-16.png",
@@ -57,7 +53,16 @@
5753
"popup.js.map",
5854
"oauth_callback.js.map"
5955
],
60-
"matches": ["*://discord.com/*"]
61-
}]
62-
}
63-
56+
"matches": [
57+
"*://discord.com/*"
58+
]
59+
}
60+
],
61+
"background": {
62+
"service_worker": "background.js"
63+
},
64+
"host_permissions": [
65+
"http://localhost/*",
66+
"https://api.threadloaf.com/*"
67+
]
68+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Threadloaf",
4+
"version": "1.1.0",
5+
"description": "Enhances Discord with a threaded discussion view",
6+
"icons": {
7+
"16": "threadloaf-16.png",
8+
"32": "threadloaf-32.png",
9+
"48": "threadloaf-48.png",
10+
"128": "threadloaf-128.png"
11+
},
12+
"action": {
13+
"default_popup": "popup.html",
14+
"default_icon": {
15+
"16": "threadloaf-16.png",
16+
"32": "threadloaf-32.png",
17+
"48": "threadloaf-48.png",
18+
"128": "threadloaf-128.png"
19+
}
20+
},
21+
"permissions": ["storage"],
22+
"content_scripts": [
23+
{
24+
"matches": ["*://discord.com/channels/*"],
25+
"js": ["content_script.js"]
26+
}
27+
],
28+
"web_accessible_resources": [{
29+
"resources": [
30+
"styles.css",
31+
"threadloaf-16.png",
32+
"threadloaf-32.png",
33+
"threadloaf-48.png",
34+
"threadloaf-128.png",
35+
"test-data/*",
36+
"content_script.js.map",
37+
"popup.js.map",
38+
"oauth_callback.js.map"
39+
],
40+
"matches": ["*://discord.com/*"]
41+
}]
42+
}

src/threadloaf/package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
{
22
"scripts": {
33
"build": "npm run build:dev",
4-
"build:dev": "tsc --noEmit && npm run lint && npm run bundle:dev && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
5-
"build:prod": "tsc --noEmit && npm run lint && npm run bundle:prod && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
4+
"build:dev": "npm run build:dev:chrome",
5+
"build:dev:chrome": "tsc --noEmit && npm run lint && npm run bundle:dev && npm run generate:manifest:chrome:dev && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
6+
"build:dev:firefox": "tsc --noEmit && npm run lint && npm run bundle:dev && npm run generate:manifest:firefox:dev && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
7+
"build:prod:chrome": "tsc --noEmit && npm run lint && npm run bundle:prod && npm run generate:manifest:chrome:prod && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
8+
"build:prod:firefox": "tsc --noEmit && npm run lint && npm run bundle:prod && npm run generate:manifest:firefox:prod && cp -rf manifest.json styles.css threadloaf-16.png threadloaf-32.png threadloaf-48.png threadloaf-128.png popup.html test-data ../../dist/",
9+
"build:prod": "npm run build:prod:chrome",
610
"bundle": "npm run bundle:dev",
711
"bundle:dev": "esbuild content_script.ts --bundle --outfile=../../dist/content_script.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"http://localhost:3000\"' && esbuild popup.ts --bundle --outfile=../../dist/popup.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"http://localhost:3000\"' && esbuild oauth_callback.ts --bundle --outfile=../../dist/oauth_callback.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"http://localhost:3000\"' && esbuild background.ts --bundle --outfile=../../dist/background.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"http://localhost:3000\"'",
812
"bundle:prod": "esbuild content_script.ts --bundle --outfile=../../dist/content_script.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"https://api.threadloaf.com\"' && esbuild popup.ts --bundle --outfile=../../dist/popup.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"https://api.threadloaf.com\"' && esbuild oauth_callback.ts --bundle --outfile=../../dist/oauth_callback.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"https://api.threadloaf.com\"' && esbuild background.ts --bundle --outfile=../../dist/background.js --format=iife --platform=browser --sourcemap --define:API_BASE_URL='\"https://api.threadloaf.com\"'",
913
"format": "prettier --write \"**/*.ts\"",
1014
"lint": "eslint .",
15+
"generate:manifest:chrome:dev": "node --loader ts-node/esm generate-manifest.ts chrome development manifest.json",
16+
"generate:manifest:chrome:prod": "node --loader ts-node/esm generate-manifest.ts chrome production manifest.json",
17+
"generate:manifest:firefox:dev": "node --loader ts-node/esm generate-manifest.ts firefox development manifest.json",
18+
"generate:manifest:firefox:prod": "node --loader ts-node/esm generate-manifest.ts firefox production manifest.json",
1119
"bundle-tests": "mkdir -p ../temp && esbuild runTests.ts --bundle --outfile=../temp/runTests.js --format=iife --platform=browser && cp test.html ../temp/",
1220
"test": "npm run bundle-tests && node --loader ts-node/esm runTestsHeadless.ts"
1321
},

src/threadloaf/release.sh

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,25 @@ echo "📦 Installing dependencies..."
1818
cd "$SCRIPT_DIR"
1919
npm install
2020

21-
echo "📚 Creating source archive..."
22-
cd "$ROOT_DIR"
23-
zip -r publish/source.zip . -x "publish/*" "*.git*" "terraform/*" "src/api/*"
21+
echo "🔨 Building Chrome extension for production..."
22+
cd "$SCRIPT_DIR"
23+
npm run build:prod:chrome
24+
25+
echo "📦 Creating Chrome extension archive..."
26+
cd "$ROOT_DIR/dist"
27+
zip -r ../publish/extension-chrome.zip .
2428

25-
echo "🔨 Building extension for production..."
29+
echo "🔨 Building Firefox extension for production..."
2630
cd "$SCRIPT_DIR"
27-
npm run build:prod
31+
npm run build:prod:firefox
2832

29-
echo "📦 Creating extension archive..."
33+
echo "📦 Creating Firefox extension archive..."
3034
cd "$ROOT_DIR/dist"
31-
zip -r ../publish/extension.zip .
35+
zip -r ../publish/extension-firefox.zip .
36+
37+
echo "📚 Creating Firefox source archive..."
38+
cd "$ROOT_DIR"
39+
zip -r publish/source-firefox.zip . -x "publish/*" "*.git*" "terraform/*" "src/api/*"
3240

3341
echo "✨ Done! Release artifacts are in the publish/ directory:"
3442
cd "$ROOT_DIR"

0 commit comments

Comments
 (0)