browser-extension-manager 1.2.12 → 1.2.14

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.
@@ -90,7 +90,7 @@
90
90
  // IDs
91
91
  browser_specific_settings: {
92
92
  gecko: {
93
- id: 'my-addon@example.com',
93
+ // id: 'my-addon@example.com',
94
94
  strict_min_version: '91.0',
95
95
  // Required for new Firefox extensions as of Nov 2025
96
96
  // https://blog.mozilla.org/addons/2025/10/23/data-collection-consent-changes-for-new-firefox-extensions/
@@ -0,0 +1,30 @@
1
+ // Locale field limits (Chrome Web Store requirements)
2
+ // https://developer.chrome.com/docs/webstore/i18n
3
+ module.exports = {
4
+ // Field character limits
5
+ limits: {
6
+ appName: 50,
7
+ appNameShort: 25,
8
+ appDescription: 200,
9
+ },
10
+
11
+ // Languages to translate (code: name)
12
+ languages: {
13
+ zh: 'Chinese (Simplified)',
14
+ es: 'Spanish',
15
+ hi: 'Hindi',
16
+ ar: 'Arabic',
17
+ pt: 'Portuguese',
18
+ ru: 'Russian',
19
+ ja: 'Japanese',
20
+ de: 'German',
21
+ fr: 'French',
22
+ ko: 'Korean',
23
+ ur: 'Urdu',
24
+ id: 'Indonesian',
25
+ bn: 'Bengali',
26
+ tl: 'Tagalog/Filipino',
27
+ vi: 'Vietnamese',
28
+ it: 'Italian',
29
+ },
30
+ };
@@ -15,6 +15,7 @@ const rootPathProject = Manager.getRootPath('project');
15
15
  // Audit results tracker
16
16
  const auditResults = {
17
17
  externalScripts: [],
18
+ localeWarnings: [],
18
19
  };
19
20
 
20
21
  // Patterns to detect external script references
@@ -38,6 +39,44 @@ const EXTERNAL_SCRIPT_PATTERNS = [
38
39
  /["'](https?:\/\/(?:www\.)?(?:google\.com\/recaptcha|apis\.google\.com)[^"']*)["']/gi,
39
40
  ];
40
41
 
42
+ // Locale config (shared with translate.js)
43
+ const { limits: LOCALE_LIMITS } = require('../config/locales.js');
44
+
45
+ // Check locale files for warnings
46
+ function checkLocaleFiles(packagedDir) {
47
+ const localesDir = path.join(packagedDir, '_locales');
48
+
49
+ if (!jetpack.exists(localesDir)) {
50
+ return;
51
+ }
52
+
53
+ const localeFiles = jetpack.find(localesDir, { matching: '*/messages.json' });
54
+
55
+ localeFiles.forEach(filePath => {
56
+ try {
57
+ const content = jetpack.read(filePath);
58
+ const messages = JSON.parse(content);
59
+
60
+ // Check each field against its limit
61
+ Object.entries(LOCALE_LIMITS).forEach(([field, limit]) => {
62
+ const message = messages[field]?.message;
63
+
64
+ if (message && message.length > limit) {
65
+ auditResults.localeWarnings.push({
66
+ file: path.relative(rootPathProject, filePath),
67
+ field: field,
68
+ length: message.length,
69
+ limit: limit,
70
+ value: message,
71
+ });
72
+ }
73
+ });
74
+ } catch (e) {
75
+ logger.warn(`Error parsing locale file ${filePath}: ${e.message}`);
76
+ }
77
+ });
78
+ }
79
+
41
80
  // Check a single file for external script references
42
81
  function checkFileForExternalScripts(filePath) {
43
82
  try {
@@ -86,10 +125,11 @@ async function auditFn(complete) {
86
125
 
87
126
  // Reset results
88
127
  auditResults.externalScripts = [];
128
+ auditResults.localeWarnings = [];
89
129
 
90
130
  try {
91
- // Find all files in packaged directory (JS, HTML, etc.)
92
- const packagedDir = path.join(rootPathProject, 'packaged', 'raw');
131
+ // Audit chromium build (code is same for both targets, only manifest differs)
132
+ const packagedDir = path.join(rootPathProject, 'packaged', 'chromium', 'raw');
93
133
 
94
134
  if (!jetpack.exists(packagedDir)) {
95
135
  logger.log(chalk.yellow('⚠️ Packaged directory not found. Run package task first.'));
@@ -100,7 +140,7 @@ async function auditFn(complete) {
100
140
 
101
141
  logger.log(`Auditing ${files.length} files (JS, HTML)...`);
102
142
 
103
- // Check each file
143
+ // Check each file for external scripts
104
144
  files.forEach(filePath => {
105
145
  const externalScripts = checkFileForExternalScripts(filePath);
106
146
  if (externalScripts.length > 0) {
@@ -108,6 +148,9 @@ async function auditFn(complete) {
108
148
  }
109
149
  });
110
150
 
151
+ // Check locale files
152
+ checkLocaleFiles(packagedDir);
153
+
111
154
  // Display results
112
155
  displayAuditResults();
113
156
 
@@ -142,6 +185,23 @@ function displayAuditResults() {
142
185
  console.log(chalk.green('✅ No external scripts detected'));
143
186
  }
144
187
 
188
+ console.log('');
189
+
190
+ // Locale Warnings
191
+ if (auditResults.localeWarnings.length > 0) {
192
+ console.log(chalk.yellow.bold('⚠️ LOCALE FIELD LENGTH WARNINGS'));
193
+ console.log(chalk.gray('Some locale fields exceed recommended character limits.\n'));
194
+
195
+ auditResults.localeWarnings.forEach((item, index) => {
196
+ console.log(chalk.yellow(` ${index + 1}. ${item.file}`));
197
+ console.log(chalk.gray(` Field: ${item.field} (${item.length}/${item.limit} chars)`));
198
+ console.log(chalk.gray(` Value: "${item.value}"`));
199
+ console.log('');
200
+ });
201
+ } else {
202
+ console.log(chalk.green('✅ All locale fields within limits'));
203
+ }
204
+
145
205
  // Summary
146
206
  console.log(chalk.bold('\n───────────────────────────────────────────────────'));
147
207
  console.log(chalk.bold('SUMMARY'));
@@ -151,6 +211,10 @@ function displayAuditResults() {
151
211
  const externalScriptColor = externalScriptCount > 0 ? chalk.red : chalk.green;
152
212
  console.log(externalScriptColor(`External Scripts: ${externalScriptCount}`));
153
213
 
214
+ const localeWarningCount = auditResults.localeWarnings.length;
215
+ const localeWarningColor = localeWarningCount > 0 ? chalk.yellow : chalk.green;
216
+ console.log(localeWarningColor(`Locale Warnings: ${localeWarningCount}`));
217
+
154
218
  console.log(chalk.bold('═══════════════════════════════════════════════════\n'));
155
219
  }
156
220
 
@@ -152,8 +152,11 @@ function getPackageVersion(packageName) {
152
152
  }
153
153
  }
154
154
 
155
+ // Build targets
156
+ const TARGETS = ['chromium', 'firefox'];
157
+
155
158
  // Special Compilation Task for manifest.json with default settings
156
- async function compileManifest(outputDir) {
159
+ async function compileManifest(outputDir, target) {
157
160
  try {
158
161
  const manifestPath = path.join('dist', 'manifest.json');
159
162
  const outputPath = path.join(outputDir, 'manifest.json');
@@ -189,10 +192,27 @@ async function compileManifest(outputDir) {
189
192
  // Add package version to manifest
190
193
  manifest.version = project.version;
191
194
 
195
+ // Apply target-specific manifest adjustments
196
+ if (manifest.background) {
197
+ if (target === 'chromium') {
198
+ // Chrome/Edge uses service_worker only
199
+ delete manifest.background.scripts;
200
+ } else if (target === 'firefox') {
201
+ // Firefox uses scripts array only
202
+ if (manifest.background.service_worker) {
203
+ // Ensure scripts array exists (derive from service_worker if needed)
204
+ if (!manifest.background.scripts) {
205
+ manifest.background.scripts = [manifest.background.service_worker];
206
+ }
207
+ delete manifest.background.service_worker;
208
+ }
209
+ }
210
+ }
211
+
192
212
  // Save as regular JSON
193
213
  jetpack.write(outputPath, JSON.stringify(manifest, null, 2));
194
214
 
195
- logger.log(`Manifest compiled with defaults`);
215
+ logger.log(`[${target}] Manifest compiled with defaults`);
196
216
  } catch (e) {
197
217
  logger.error(`Error compiling manifest`, e);
198
218
  }
@@ -225,76 +245,92 @@ async function compileLocales(outputDir) {
225
245
  }
226
246
  }
227
247
 
228
- // Package Task for raw
248
+ // Package Task for raw - creates both chromium and firefox builds
229
249
  async function packageRaw() {
230
250
  // Log
231
251
  logger.log(`Starting raw packaging...`);
232
252
 
233
253
  try {
234
- const outputDir = 'packaged/raw';
254
+ // Build for each target
255
+ for (const target of TARGETS) {
256
+ await packageRawForTarget(target);
257
+ }
235
258
 
236
- // Ensure the directory exists
237
- jetpack.dir(outputDir);
259
+ // Log completion
260
+ logger.log(`Finished raw packaging for all targets`);
261
+ } catch (e) {
262
+ logger.error(`Error during raw packaging`, e);
263
+ }
264
+ }
265
+
266
+ // Package raw for a specific target (chromium or firefox)
267
+ async function packageRawForTarget(target) {
268
+ logger.log(`[${target}] Starting raw packaging...`);
269
+
270
+ const outputDir = `packaged/${target}/raw`;
238
271
 
239
- // Copy files to raw package directory
240
- await execute(`cp -r dist/* ${outputDir}`);
272
+ // Ensure the directory exists (this also cleans it)
273
+ jetpack.dir(outputDir, { empty: true });
241
274
 
242
- // Loop thru outputDir/dist/assets/js all JS files
243
- const jsFiles = jetpack.find(path.join(outputDir, 'assets', 'js'), { matching: '*.js' });
244
- const redactions = getRedactions();
275
+ // Copy files to raw package directory
276
+ await execute(`cp -r dist/* ${outputDir}`);
245
277
 
246
- jsFiles.forEach(filePath => {
247
- // Load the content
248
- let content = jetpack.read(filePath);
278
+ // Loop thru outputDir/assets/js all JS files for redactions
279
+ const jsFiles = jetpack.find(path.join(outputDir, 'assets', 'js'), { matching: '*.js' });
280
+ const redactions = getRedactions();
249
281
 
250
- // Replace keys with their corresponding values
251
- Object.keys(redactions).forEach(key => {
252
- const value = redactions[key];
253
- const regex = new RegExp(key, 'g'); // Create a global regex for the key
254
- content = content.replace(regex, value);
282
+ jsFiles.forEach(filePath => {
283
+ // Load the content
284
+ let content = jetpack.read(filePath);
255
285
 
256
- // Log replacement
257
- logger.log(`Redacted ${key} in ${filePath}`);
258
- });
286
+ // Replace keys with their corresponding values
287
+ Object.keys(redactions).forEach(key => {
288
+ const value = redactions[key];
289
+ const regex = new RegExp(key, 'g'); // Create a global regex for the key
290
+ content = content.replace(regex, value);
259
291
 
260
- // Write the new content to the file
261
- jetpack.write(filePath, content);
292
+ // Log replacement
293
+ logger.log(`[${target}] Redacted ${key} in ${filePath}`);
262
294
  });
263
295
 
264
- // Generate build.js and build.json
265
- await generateBuildJs(outputDir);
296
+ // Write the new content to the file
297
+ jetpack.write(filePath, content);
298
+ });
266
299
 
267
- // Compile manifest and locales
268
- await compileManifest(outputDir);
269
- await compileLocales(outputDir);
300
+ // Generate build.js and build.json
301
+ await generateBuildJs(outputDir);
270
302
 
271
- // Log completion
272
- logger.log(`Finished raw packaging`);
273
- } catch (e) {
274
- logger.error(`Error during raw packaging`, e);
275
- }
303
+ // Compile manifest (target-specific) and locales
304
+ await compileManifest(outputDir, target);
305
+ await compileLocales(outputDir);
306
+
307
+ logger.log(`[${target}] Finished raw packaging`);
276
308
  }
277
309
 
278
- // Create zipped version of raw package
310
+ // Create zipped version of raw packages for each target
279
311
  async function packageZip() {
280
312
  // Log
281
- logger.log(`Zipping raw package...`);
313
+ logger.log(`Zipping raw packages...`);
314
+
315
+ // Skip if not in build mode
316
+ if (!Manager.isBuildMode()) {
317
+ logger.log(`Skipping zip (not in build mode)`);
318
+ return;
319
+ }
282
320
 
283
321
  try {
284
- const inputDir = 'packaged/raw';
285
- const zipPath = 'packaged/extension.zip';
322
+ // Create zip for each target
323
+ for (const target of TARGETS) {
324
+ const inputDir = `packaged/${target}/raw`;
325
+ const zipPath = `packaged/${target}/extension.zip`;
286
326
 
287
- // Create packed extension (.zip)
288
- if (Manager.isBuildMode()) {
289
327
  // Remove existing zip if it exists
290
328
  jetpack.remove(zipPath);
291
329
 
292
330
  // Zip contents of raw directory (not the directory itself)
293
331
  // This ensures manifest.json is at the root of the zip
294
- await execute(`cd ${inputDir} && zip -r ../../${zipPath} .`);
295
- logger.log(`Zipped package created at ${zipPath}`);
296
- } else {
297
- logger.log(`Skipping zip (not in build mode)`);
332
+ await execute(`cd ${inputDir} && zip -r ../extension.zip .`);
333
+ logger.log(`[${target}] Zipped package created at ${zipPath}`);
298
334
  }
299
335
  } catch (e) {
300
336
  logger.error(`Error zipping package`, e);
@@ -9,8 +9,17 @@ const { execute } = require('node-powertools');
9
9
  // Load package
10
10
  const project = Manager.getPackage('project');
11
11
 
12
- // Paths
13
- const zipPath = path.join(process.cwd(), 'packaged', 'extension.zip');
12
+ // Paths for each target
13
+ const PATHS = {
14
+ chromium: {
15
+ zip: path.join(process.cwd(), 'packaged', 'chromium', 'extension.zip'),
16
+ raw: path.join(process.cwd(), 'packaged', 'chromium', 'raw'),
17
+ },
18
+ firefox: {
19
+ zip: path.join(process.cwd(), 'packaged', 'firefox', 'extension.zip'),
20
+ raw: path.join(process.cwd(), 'packaged', 'firefox', 'raw'),
21
+ },
22
+ };
14
23
 
15
24
  // Helper to check if a credential is valid (not empty or placeholder)
16
25
  function isValidCredential(value) {
@@ -56,9 +65,13 @@ async function publish(complete) {
56
65
  // Log
57
66
  logger.log('Starting publish...');
58
67
 
59
- // Check if zip exists
60
- if (!jetpack.exists(zipPath)) {
61
- logger.error(`Extension zip not found at ${zipPath}. Run build first.`);
68
+ // Check if zips exist for each target
69
+ const missingZips = Object.entries(PATHS)
70
+ .filter(([, paths]) => !jetpack.exists(paths.zip))
71
+ .map(([target]) => target);
72
+
73
+ if (missingZips.length > 0) {
74
+ logger.error(`Extension zips not found for: ${missingZips.join(', ')}. Run build first.`);
62
75
  return complete();
63
76
  }
64
77
 
@@ -158,10 +171,10 @@ async function publishToChrome() {
158
171
 
159
172
  logger.log('[chrome] Uploading to Chrome Web Store...');
160
173
 
161
- // Use chrome-webstore-upload-cli
174
+ // Use chrome-webstore-upload-cli with chromium build
162
175
  const command = [
163
176
  'npx chrome-webstore-upload-cli',
164
- `--source "${zipPath}"`,
177
+ `--source "${PATHS.chromium.zip}"`,
165
178
  `--extension-id "${extensionId}"`,
166
179
  `--client-id "${clientId}"`,
167
180
  `--client-secret "${clientSecret}"`,
@@ -188,11 +201,10 @@ async function publishToFirefox() {
188
201
 
189
202
  logger.log('[firefox] Uploading to Firefox Add-ons...');
190
203
 
191
- // Use web-ext sign
192
- const rawDir = path.join(process.cwd(), 'packaged', 'raw');
204
+ // Use web-ext sign with firefox build
193
205
  const command = [
194
206
  'npx web-ext sign',
195
- `--source-dir "${rawDir}"`,
207
+ `--source-dir "${PATHS.firefox.raw}"`,
196
208
  `--api-key "${apiKey}"`,
197
209
  `--api-secret "${apiSecret}"`,
198
210
  `--channel "${channel}"`,
@@ -218,8 +230,8 @@ async function publishToEdge() {
218
230
 
219
231
  logger.log('[edge] Uploading to Microsoft Edge Add-ons...');
220
232
 
221
- // Read zip file
222
- const zipBuffer = jetpack.read(zipPath, 'buffer');
233
+ // Read chromium zip file (Edge uses same build as Chrome)
234
+ const zipBuffer = jetpack.read(PATHS.chromium.zip, 'buffer');
223
235
 
224
236
  // Edge API v1.1 endpoint
225
237
  const uploadUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions/draft/package`;
@@ -7,25 +7,8 @@ const path = require('path');
7
7
  const { execute } = require('node-powertools');
8
8
  const JSON5 = require('json5');
9
9
 
10
- // Languages to translate
11
- const LANGUAGES = [
12
- 'zh',
13
- 'es',
14
- 'hi',
15
- 'ar',
16
- 'pt',
17
- 'ru',
18
- 'ja',
19
- 'de',
20
- 'fr',
21
- 'ko',
22
- 'ur',
23
- 'id',
24
- 'bn',
25
- 'tl',
26
- 'vi',
27
- 'it',
28
- ];
10
+ // Locale config (shared with audit.js)
11
+ const { limits: LOCALE_LIMITS, languages: LANGUAGES } = require('../config/locales.js');
29
12
 
30
13
  // Paths
31
14
  const localesDir = path.join(process.cwd(), 'src', '_locales');
@@ -80,7 +63,7 @@ async function translate(complete) {
80
63
  // Check which languages need translation
81
64
  const languagesToTranslate = [];
82
65
 
83
- for (const lang of LANGUAGES) {
66
+ for (const lang of Object.keys(LANGUAGES)) {
84
67
  const langDir = path.join(localesDir, lang);
85
68
  const langMessagesPath = path.join(langDir, 'messages.json');
86
69
 
@@ -156,21 +139,30 @@ async function translate(complete) {
156
139
  async function translateAllWithClaude(enMessages, languagesToTranslate) {
157
140
  // Build language info for prompt
158
141
  const languageList = languagesToTranslate.map(({ lang }) =>
159
- `- "${lang}": ${getLanguageName(lang)}`
142
+ `- "${lang}": ${LANGUAGES[lang]}`
160
143
  ).join('\n');
161
144
 
145
+ // Build character limits info
146
+ const limitsInfo = Object.entries(LOCALE_LIMITS)
147
+ .map(([field, limit]) => `- ${field}: max ${limit} characters`)
148
+ .join('\n');
149
+
162
150
  const prompt = `Translate the following Chrome extension messages.json content from English to multiple languages.
163
151
 
164
152
  TARGET LANGUAGES:
165
153
  ${languageList}
166
154
 
155
+ CHARACTER LIMITS (Chrome Web Store requirements):
156
+ ${limitsInfo}
157
+
167
158
  IMPORTANT RULES:
168
159
  1. Only translate the "message" field values
169
160
  2. Keep the "description" field values in English (they are for developers)
170
161
  3. Keep all JSON keys exactly as they are
171
162
  4. Return ONLY valid JSON, no markdown, no explanation
172
163
  5. Preserve any placeholders like $1, $2, etc.
173
- 6. Return a JSON object where each key is the language code and the value is the translated messages object
164
+ 6. IMPORTANT: Respect the character limits above for each field
165
+ 7. Return a JSON object where each key is the language code and the value is the translated messages object
174
166
 
175
167
  INPUT (English):
176
168
  ${JSON.stringify(enMessages, null, 2)}
@@ -225,29 +217,5 @@ Output the translated JSON:`;
225
217
  }
226
218
  }
227
219
 
228
- // Get full language name
229
- function getLanguageName(code) {
230
- const names = {
231
- zh: 'Chinese (Simplified)',
232
- es: 'Spanish',
233
- hi: 'Hindi',
234
- ar: 'Arabic',
235
- pt: 'Portuguese',
236
- ru: 'Russian',
237
- ja: 'Japanese',
238
- de: 'German',
239
- fr: 'French',
240
- ko: 'Korean',
241
- ur: 'Urdu',
242
- id: 'Indonesian',
243
- bn: 'Bengali',
244
- tl: 'Tagalog/Filipino',
245
- vi: 'Vietnamese',
246
- it: 'Italian',
247
- };
248
-
249
- return names[code] || code;
250
- }
251
-
252
220
  // Export task
253
221
  module.exports = series(translate);
@@ -25,8 +25,9 @@ npm install
25
25
  ```sh
26
26
  npm run build
27
27
  ```
28
- 5. The built extension will be in `packaged/raw/` directory
29
- 6. The packaged zip will be at `packaged/extension.zip`
28
+ 5. The built extensions will be in `packaged/` directory:
29
+ - **Firefox:** `packaged/firefox/raw/` (unpacked) and `packaged/firefox/extension.zip`
30
+ - **Chrome/Edge:** `packaged/chromium/raw/` (unpacked) and `packaged/chromium/extension.zip`
30
31
 
31
32
  ## Loading the Extension
32
33
 
@@ -34,13 +35,19 @@ npm run build
34
35
  1. Go to `about:debugging`
35
36
  2. Click "This Firefox"
36
37
  3. Click "Load Temporary Add-on"
37
- 4. Select `packaged/raw/manifest.json`
38
+ 4. Select `packaged/firefox/raw/manifest.json`
38
39
 
39
40
  ### Chrome
40
41
  1. Go to `chrome://extensions`
41
42
  2. Enable "Developer mode"
42
43
  3. Click "Load unpacked"
43
- 4. Select the `packaged/raw/` directory
44
+ 4. Select the `packaged/chromium/raw/` directory
45
+
46
+ ### Edge
47
+ 1. Go to `edge://extensions`
48
+ 2. Enable "Developer mode"
49
+ 3. Click "Load unpacked"
50
+ 4. Select the `packaged/chromium/raw/` directory
44
51
 
45
52
  ## Questions
46
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-extension-manager",
3
- "version": "1.2.12",
3
+ "version": "1.2.14",
4
4
  "description": "Browser Extension Manager dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {