browser-extension-manager 1.1.11 → 1.1.13

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 CHANGED
@@ -52,9 +52,40 @@ When you run `npm run build`, BEM automatically translates your `src/_locales/en
52
52
  Only missing translations are generated - existing translations are preserved.
53
53
 
54
54
  ## 🌎 Publishing your extension
55
+
56
+ ### Manual Upload
55
57
  1. Run `npm run build` in Terminal to build your extension for production.
56
58
  2. Upload the `.zip` file to the browser's extension store.
57
59
 
60
+ ### Automatic Publishing
61
+ BEM can automatically publish to Chrome, Firefox, and Edge stores when `BXM_IS_PUBLISH=true`:
62
+
63
+ ```bash
64
+ BXM_IS_PUBLISH=true npm run build
65
+ ```
66
+
67
+ **Setup:** Add store credentials to your `.env` file:
68
+
69
+ ```bash
70
+ # Chrome Web Store
71
+ CHROME_EXTENSION_ID="your-extension-id"
72
+ CHROME_CLIENT_ID="your-client-id"
73
+ CHROME_CLIENT_SECRET="your-client-secret"
74
+ CHROME_REFRESH_TOKEN="your-refresh-token"
75
+
76
+ # Firefox Add-ons
77
+ FIREFOX_EXTENSION_ID="your-extension-id"
78
+ FIREFOX_API_KEY="your-api-key"
79
+ FIREFOX_API_SECRET="your-api-secret"
80
+
81
+ # Microsoft Edge Add-ons
82
+ EDGE_PRODUCT_ID="your-product-id"
83
+ EDGE_CLIENT_ID="your-client-id"
84
+ EDGE_API_KEY="your-api-key"
85
+ ```
86
+
87
+ Only stores with configured credentials will be published to.
88
+
58
89
  <!-- ## ⛳️ Flags
59
90
  * `--test=false` - Coming soon
60
91
  ```bash
@@ -0,0 +1,82 @@
1
+ name: Build and Publish Extension
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - master
8
+ - main
9
+
10
+ concurrency:
11
+ group: ${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ env:
15
+ NODE_VERSION: '[versions.node]'
16
+ BXM_BUILD_MODE: 'true'
17
+ BXM_IS_PUBLISH: 'true'
18
+
19
+ # Chrome Web Store credentials
20
+ CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
21
+ CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
22
+ CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
23
+ CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
24
+
25
+ # Firefox Add-ons credentials
26
+ FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
27
+ FIREFOX_API_KEY: ${{ secrets.FIREFOX_API_KEY }}
28
+ FIREFOX_API_SECRET: ${{ secrets.FIREFOX_API_SECRET }}
29
+
30
+ # Microsoft Edge Add-ons credentials
31
+ EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }}
32
+ EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
33
+ EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}
34
+
35
+ jobs:
36
+ build:
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 30
39
+ steps:
40
+ - name: Setup git config
41
+ run: |
42
+ git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
43
+ git config --global user.name "$GITHUB_ACTOR"
44
+
45
+ - name: Checkout repository
46
+ uses: actions/checkout@v4
47
+ with:
48
+ fetch-depth: 0
49
+
50
+ - name: Setup Node.js
51
+ uses: actions/setup-node@v4
52
+ with:
53
+ node-version: ${{ env.NODE_VERSION }}
54
+
55
+ - name: Install dependencies
56
+ run: npm install
57
+
58
+ - name: Log dependency versions
59
+ run: |
60
+ echo "Node: $(node -v)"
61
+ echo "NPM: $(npm -v)"
62
+ echo "BXM: $(npx bxm version)"
63
+ echo ""
64
+ echo "NPM Dependencies:"
65
+ npm list --depth=0 || echo ""
66
+
67
+ - name: Build and publish extension
68
+ run: |
69
+ npx bxm setup && npm run build
70
+
71
+ - name: Upload extension artifact
72
+ uses: actions/upload-artifact@v4
73
+ with:
74
+ name: extension-${{ github.sha }}
75
+ path: packaged/extension.zip
76
+ retention-days: 30
77
+
78
+ - name: Purge old artifacts
79
+ uses: kolpav/purge-artifacts-action@v1
80
+ with:
81
+ token: ${{ secrets.GITHUB_TOKEN }}
82
+ expire-in: 7 days
@@ -0,0 +1,22 @@
1
+ # ========== Default Values ==========
2
+ # Chrome Web Store Publishing Credentials
3
+ # Get credentials at: https://developer.chrome.com/docs/webstore/using_webstore_api/
4
+ CHROME_EXTENSION_ID="your-extension-id"
5
+ CHROME_CLIENT_ID="your-client-id"
6
+ CHROME_CLIENT_SECRET="your-client-secret"
7
+ CHROME_REFRESH_TOKEN="your-refresh-token"
8
+
9
+ # Firefox Add-ons Publishing Credentials
10
+ # Get credentials at: https://addons.mozilla.org/developers/addon/api/key/
11
+ FIREFOX_EXTENSION_ID="your-extension-id"
12
+ FIREFOX_API_KEY="your-api-key"
13
+ FIREFOX_API_SECRET="your-api-secret"
14
+
15
+ # Microsoft Edge Add-ons Publishing Credentials
16
+ # Get credentials at: https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api
17
+ EDGE_PRODUCT_ID="your-product-id"
18
+ EDGE_CLIENT_ID="your-client-id"
19
+ EDGE_API_KEY="your-api-key"
20
+
21
+ # ========== Custom Values ==========
22
+ # ...
@@ -1,4 +1,7 @@
1
+ # ========== Default Values ==========
2
+ # macOS
1
3
  .DS_Store
4
+
2
5
  # Logs
3
6
  logs
4
7
  *.log
@@ -11,10 +14,6 @@ firebase-debug.log*
11
14
  .firebase/
12
15
 
13
16
  # Firebase config
14
-
15
- # Uncomment this if you'd like others to create their own Firebase project.
16
- # For a team working on the same Firebase project(s), it is recommended to leave
17
- # it commented so all members can deploy to the same project(s) in .firebaserc.
18
17
  .firebaserc
19
18
 
20
19
  # Runtime data
@@ -32,16 +31,16 @@ coverage
32
31
  # nyc test coverage
33
32
  .nyc_output
34
33
 
35
- # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
34
+ # Grunt intermediate storage
36
35
  .grunt
37
36
 
38
- # Bower dependency directory (https://bower.io/)
37
+ # Bower dependency directory
39
38
  bower_components
40
39
 
41
40
  # node-waf configuration
42
41
  .lock-wscript
43
42
 
44
- # Compiled binary addons (http://nodejs.org/api/addons.html)
43
+ # Compiled binary addons
45
44
  build/Release
46
45
 
47
46
  # Dependency directories
@@ -69,3 +68,8 @@ node_modules/
69
68
  /dist
70
69
  /packaged
71
70
  /.temp
71
+ /.cache
72
+ /_legacy
73
+
74
+ # ========== Custom Values ==========
75
+ # ...
package/dist/gulp/main.js CHANGED
@@ -45,6 +45,7 @@ exports.build = series(
45
45
  parallel(exports.sass, exports.webpack, exports.icons, exports.html),
46
46
  exports.package,
47
47
  exports.audit,
48
+ exports.publish,
48
49
  );
49
50
 
50
51
  // Compose task scheduler
@@ -56,6 +56,11 @@ const FILE_MAP = {
56
56
  // },
57
57
  '_.gitignore': {
58
58
  name: (file) => file.name.replace('_.gitignore', '.gitignore'),
59
+ mergeLines: true,
60
+ },
61
+ '_.env': {
62
+ name: (file) => file.name.replace('_.env', '.env'),
63
+ mergeLines: true,
59
64
  },
60
65
 
61
66
  // Config file with smart merging
@@ -89,6 +94,188 @@ const delay = 250;
89
94
  // Index
90
95
  let index = -1;
91
96
 
97
+ // Helper function to merge line-based files (.gitignore, .env)
98
+ function mergeLineBasedFiles(existingContent, newContent, fileName) {
99
+ // Parse existing content into lines
100
+ const existingLines = existingContent.split('\n');
101
+ const newLines = newContent.split('\n');
102
+
103
+ // For .env files, we track keys; for .gitignore, we track the full line
104
+ const isEnvFile = fileName === '.env';
105
+
106
+ // Markers for separating default values from user custom values
107
+ const DEFAULT_SECTION_MARKER = '# ========== Default Values ==========';
108
+ const CUSTOM_SECTION_MARKER = '# ========== Custom Values ==========';
109
+
110
+ // Parse existing file into default section and custom section
111
+ let defaultSection = [];
112
+ let customSection = [];
113
+ let inCustomSection = false;
114
+ let inDefaultSection = false;
115
+
116
+ const existingDefaultKeys = new Set();
117
+ const existingCustomKeys = new Set();
118
+
119
+ for (const line of existingLines) {
120
+ const trimmed = line.trim();
121
+
122
+ // Check for section markers
123
+ if (trimmed === DEFAULT_SECTION_MARKER) {
124
+ inDefaultSection = true;
125
+ inCustomSection = false;
126
+ continue;
127
+ }
128
+ if (trimmed === CUSTOM_SECTION_MARKER) {
129
+ inCustomSection = true;
130
+ inDefaultSection = false;
131
+ continue;
132
+ }
133
+
134
+ // Add to appropriate section
135
+ if (inCustomSection) {
136
+ customSection.push(line);
137
+ // Track custom keys
138
+ if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
139
+ const key = trimmed.split('=')[0].trim();
140
+ if (key) {
141
+ existingCustomKeys.add(key);
142
+ }
143
+ }
144
+ } else if (inDefaultSection) {
145
+ defaultSection.push(line);
146
+ // Track default keys
147
+ if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
148
+ const key = trimmed.split('=')[0].trim();
149
+ if (key) {
150
+ existingDefaultKeys.add(key);
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ // Parse new content to build complete default section in order
157
+ const newDefaultSection = [];
158
+ const newDefaultKeys = new Set();
159
+
160
+ let inNewDefaultSection = false;
161
+ let inNewCustomSection = false;
162
+
163
+ for (const line of newLines) {
164
+ const trimmed = line.trim();
165
+
166
+ // Check for section markers
167
+ if (trimmed === DEFAULT_SECTION_MARKER) {
168
+ inNewDefaultSection = true;
169
+ inNewCustomSection = false;
170
+ continue;
171
+ }
172
+ if (trimmed === CUSTOM_SECTION_MARKER) {
173
+ inNewCustomSection = true;
174
+ inNewDefaultSection = false;
175
+ continue;
176
+ }
177
+
178
+ // Only process default section from new file
179
+ if (inNewDefaultSection) {
180
+ // For env files, check if key exists
181
+ if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
182
+ const key = trimmed.split('=')[0].trim();
183
+ if (key) {
184
+ newDefaultKeys.add(key);
185
+ // If key exists in user's file (either section), skip the default value
186
+ if (!existingDefaultKeys.has(key) && !existingCustomKeys.has(key)) {
187
+ // New key - add it
188
+ newDefaultSection.push(line);
189
+ } else {
190
+ // Key exists - we'll add user's value later in order
191
+ newDefaultSection.push(null); // Placeholder to maintain order
192
+ }
193
+ } else {
194
+ newDefaultSection.push(line);
195
+ }
196
+ } else {
197
+ // Comments and empty lines
198
+ newDefaultSection.push(line);
199
+ }
200
+ }
201
+ }
202
+
203
+ // Now merge user's existing default values in the correct order
204
+ const mergedDefaultSection = [];
205
+ let defaultSectionIndex = 0;
206
+
207
+ for (const line of newDefaultSection) {
208
+ if (line === null) {
209
+ // Placeholder - insert corresponding user value
210
+ // Find the next user value
211
+ while (defaultSectionIndex < defaultSection.length) {
212
+ const userLine = defaultSection[defaultSectionIndex++];
213
+ const trimmed = userLine.trim();
214
+ if (trimmed && !trimmed.startsWith('#')) {
215
+ mergedDefaultSection.push(userLine);
216
+ break;
217
+ }
218
+ }
219
+ } else {
220
+ mergedDefaultSection.push(line);
221
+ }
222
+ }
223
+
224
+ // Find any user-added lines in default section that aren't in new defaults
225
+ // These should be moved to custom section
226
+ const userAddedToDefaults = [];
227
+
228
+ for (const line of defaultSection) {
229
+ const trimmed = line.trim();
230
+
231
+ // Skip empty lines and comments
232
+ if (!trimmed || trimmed.startsWith('#')) {
233
+ continue;
234
+ }
235
+
236
+ if (isEnvFile) {
237
+ // For .env, check if key exists in new defaults
238
+ const key = trimmed.split('=')[0].trim();
239
+ if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key)) {
240
+ // User added this key to defaults section - move to custom
241
+ userAddedToDefaults.push(line);
242
+ }
243
+ } else {
244
+ // For .gitignore, check if line exists in new defaults
245
+ // We need to check if this exact line appears in the new default section
246
+ const lineExistsInNewDefaults = newLines.some(newLine => {
247
+ return newLine.trim() === trimmed;
248
+ });
249
+
250
+ if (!lineExistsInNewDefaults) {
251
+ // User added this line to defaults section - move to custom
252
+ userAddedToDefaults.push(line);
253
+ }
254
+ }
255
+ }
256
+
257
+ // Build final result
258
+ const result = [];
259
+
260
+ // Add default section
261
+ result.push(DEFAULT_SECTION_MARKER);
262
+ result.push(...mergedDefaultSection);
263
+
264
+ // Add custom section
265
+ result.push('');
266
+ result.push(CUSTOM_SECTION_MARKER);
267
+
268
+ // First add any user lines that were in default section (moved to custom)
269
+ if (userAddedToDefaults.length > 0) {
270
+ result.push(...userAddedToDefaults);
271
+ }
272
+
273
+ // Then add existing custom section
274
+ result.push(...customSection);
275
+
276
+ return result.join('\n');
277
+ }
278
+
92
279
  // Helper function to merge configs intelligently
93
280
  function mergeConfigs(existingConfig, newConfig) {
94
281
  const merged = { ...newConfig };
@@ -139,7 +326,7 @@ function defaults(complete, changedFile) {
139
326
  // return src(input, { base: 'src' })
140
327
  return src(filesToProcess, { base: `${rootPathPackage}/dist/defaults`, dot: true, encoding: false }) // Add base to preserve directory structure
141
328
  .pipe(customTransform())
142
- .pipe(createTemplateTransform({site: config}))
329
+ .pipe(createTemplateTransform({site: config, versions: package.engines}))
143
330
  .pipe(dest(output, { encoding: false }))
144
331
  .on('finish', () => {
145
332
  // Log
@@ -226,8 +413,27 @@ function customTransform() {
226
413
  }
227
414
  }
228
415
 
416
+ // Handle line-based merging (.gitignore, .env)
417
+ if (options.mergeLines && exists && !isBinaryFile) {
418
+ try {
419
+ const existingContent = jetpack.read(fullOutputPath);
420
+ const newContent = file.contents.toString();
421
+
422
+ // Merge line-by-line, passing the filename to handle .env differently
423
+ const mergedContent = mergeLineBasedFiles(existingContent, newContent, item.name);
424
+
425
+ // Update file contents
426
+ file.contents = Buffer.from(mergedContent);
427
+
428
+ logger.log(`Merged line-based file: ${relativePath}`);
429
+ } catch (error) {
430
+ logger.error(`Error merging line-based file ${relativePath}:`, error);
431
+ // Fall through to normal processing if merge fails
432
+ }
433
+ }
434
+
229
435
  // Skip if instructed
230
- if (options.skip || (!options.overwrite && exists && !options.merge)) {
436
+ if (options.skip || (!options.overwrite && exists && !options.merge && !options.mergeLines)) {
231
437
  logger.log(`Skipping file: ${relativePath}`);
232
438
  return callback();
233
439
  }
@@ -292,6 +498,8 @@ function getFileOptions(filePath) {
292
498
  path: null,
293
499
  template: null,
294
500
  skip: false,
501
+ merge: false,
502
+ mergeLines: false,
295
503
  rule: null,
296
504
  };
297
505
 
@@ -0,0 +1,218 @@
1
+ // Libraries
2
+ const Manager = new (require('../../build.js'));
3
+ const logger = Manager.logger('publish');
4
+ const { series } = require('gulp');
5
+ const jetpack = require('fs-jetpack');
6
+ const path = require('path');
7
+ const { execute } = require('node-powertools');
8
+
9
+ // Load package
10
+ const project = Manager.getPackage('project');
11
+
12
+ // Paths
13
+ const zipPath = path.join(process.cwd(), 'packaged', 'extension.zip');
14
+
15
+ // Store configurations - all credentials come from .env file
16
+ const STORES = {
17
+ chrome: {
18
+ name: 'Chrome Web Store',
19
+ enabled: () => !!(process.env.CHROME_EXTENSION_ID && process.env.CHROME_CLIENT_ID),
20
+ },
21
+ firefox: {
22
+ name: 'Firefox Add-ons',
23
+ enabled: () => !!(process.env.FIREFOX_API_KEY && process.env.FIREFOX_API_SECRET),
24
+ },
25
+ edge: {
26
+ name: 'Microsoft Edge Add-ons',
27
+ enabled: () => !!(process.env.EDGE_PRODUCT_ID && process.env.EDGE_CLIENT_ID),
28
+ },
29
+ };
30
+
31
+ // Main publish task
32
+ async function publish(complete) {
33
+ // Check if publish mode is enabled
34
+ if (!process.env.BXM_IS_PUBLISH) {
35
+ logger.log('Skipping publish (BXM_IS_PUBLISH not set)');
36
+ return complete();
37
+ }
38
+
39
+ // Log
40
+ logger.log('Starting publish...');
41
+
42
+ // Check if zip exists
43
+ if (!jetpack.exists(zipPath)) {
44
+ logger.error(`Extension zip not found at ${zipPath}. Run build first.`);
45
+ return complete();
46
+ }
47
+
48
+ // Log version
49
+ logger.log(`Publishing version ${project.version}`);
50
+
51
+ // Get enabled stores
52
+ const enabledStores = Object.entries(STORES)
53
+ .filter(([key, store]) => store.enabled())
54
+ .map(([key]) => key);
55
+
56
+ if (enabledStores.length === 0) {
57
+ logger.warn('No stores configured for publishing. Add credentials to .env file');
58
+ logger.log('Required environment variables:');
59
+ logger.log(' Chrome: CHROME_EXTENSION_ID, CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN');
60
+ logger.log(' Firefox: FIREFOX_EXTENSION_ID, FIREFOX_API_KEY, FIREFOX_API_SECRET');
61
+ logger.log(' Edge: EDGE_PRODUCT_ID, EDGE_CLIENT_ID, EDGE_API_KEY');
62
+ return complete();
63
+ }
64
+
65
+ logger.log(`Publishing to: ${enabledStores.join(', ')}`);
66
+
67
+ // Run publish tasks in parallel
68
+ const publishTasks = enabledStores.map(async (store) => {
69
+ try {
70
+ switch (store) {
71
+ case 'chrome':
72
+ await publishToChrome();
73
+ break;
74
+ case 'firefox':
75
+ await publishToFirefox();
76
+ break;
77
+ case 'edge':
78
+ await publishToEdge();
79
+ break;
80
+ }
81
+ logger.log(`[${store}] Published successfully`);
82
+ } catch (e) {
83
+ logger.error(`[${store}] Publish failed: ${e.message}`);
84
+ }
85
+ });
86
+
87
+ await Promise.all(publishTasks);
88
+
89
+ // Log
90
+ logger.log('Publish finished!');
91
+
92
+ // Complete
93
+ return complete();
94
+ }
95
+
96
+ // Publish to Chrome Web Store
97
+ async function publishToChrome() {
98
+ // Get credentials from env
99
+ const extensionId = process.env.CHROME_EXTENSION_ID;
100
+ const clientId = process.env.CHROME_CLIENT_ID;
101
+ const clientSecret = process.env.CHROME_CLIENT_SECRET;
102
+ const refreshToken = process.env.CHROME_REFRESH_TOKEN;
103
+
104
+ // Validate
105
+ if (!extensionId || !clientId || !clientSecret || !refreshToken) {
106
+ throw new Error('Missing Chrome credentials. Set CHROME_EXTENSION_ID, CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN in .env');
107
+ }
108
+
109
+ logger.log('[chrome] Uploading to Chrome Web Store...');
110
+
111
+ // Use chrome-webstore-upload-cli
112
+ const command = [
113
+ 'npx chrome-webstore-upload-cli',
114
+ `--source "${zipPath}"`,
115
+ `--extension-id "${extensionId}"`,
116
+ `--client-id "${clientId}"`,
117
+ `--client-secret "${clientSecret}"`,
118
+ `--refresh-token "${refreshToken}"`,
119
+ ].join(' ');
120
+
121
+ await execute(command);
122
+
123
+ logger.log('[chrome] Upload complete');
124
+ }
125
+
126
+ // Publish to Firefox Add-ons
127
+ async function publishToFirefox() {
128
+ // Get credentials from env
129
+ const extensionId = process.env.FIREFOX_EXTENSION_ID;
130
+ const apiKey = process.env.FIREFOX_API_KEY;
131
+ const apiSecret = process.env.FIREFOX_API_SECRET;
132
+ const channel = process.env.FIREFOX_CHANNEL || 'listed';
133
+
134
+ // Validate
135
+ if (!apiKey || !apiSecret) {
136
+ throw new Error('Missing Firefox credentials. Set FIREFOX_API_KEY, FIREFOX_API_SECRET in .env');
137
+ }
138
+
139
+ logger.log('[firefox] Uploading to Firefox Add-ons...');
140
+
141
+ // Use web-ext sign
142
+ const rawDir = path.join(process.cwd(), 'packaged', 'raw');
143
+ const command = [
144
+ 'npx web-ext sign',
145
+ `--source-dir "${rawDir}"`,
146
+ `--api-key "${apiKey}"`,
147
+ `--api-secret "${apiSecret}"`,
148
+ `--channel "${channel}"`,
149
+ extensionId ? `--id "${extensionId}"` : '',
150
+ ].filter(Boolean).join(' ');
151
+
152
+ await execute(command);
153
+
154
+ logger.log('[firefox] Upload complete');
155
+ }
156
+
157
+ // Publish to Microsoft Edge Add-ons
158
+ async function publishToEdge() {
159
+ // Get credentials from env
160
+ const productId = process.env.EDGE_PRODUCT_ID;
161
+ const clientId = process.env.EDGE_CLIENT_ID;
162
+ const apiKey = process.env.EDGE_API_KEY;
163
+
164
+ // Validate
165
+ if (!productId || !clientId || !apiKey) {
166
+ throw new Error('Missing Edge credentials. Set EDGE_PRODUCT_ID, EDGE_CLIENT_ID, EDGE_API_KEY in .env');
167
+ }
168
+
169
+ logger.log('[edge] Uploading to Microsoft Edge Add-ons...');
170
+
171
+ // Read zip file
172
+ const zipBuffer = jetpack.read(zipPath, 'buffer');
173
+
174
+ // Edge API v1.1 endpoint
175
+ const uploadUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions/draft/package`;
176
+
177
+ // Upload using fetch
178
+ const response = await fetch(uploadUrl, {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Authorization': `ApiKey ${apiKey}`,
182
+ 'X-ClientID': clientId,
183
+ 'Content-Type': 'application/zip',
184
+ },
185
+ body: zipBuffer,
186
+ });
187
+
188
+ if (!response.ok) {
189
+ const errorText = await response.text();
190
+ throw new Error(`Edge API error: ${response.status} - ${errorText}`);
191
+ }
192
+
193
+ logger.log('[edge] Package uploaded, submitting for review...');
194
+
195
+ // Submit for review
196
+ const publishUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions`;
197
+ const publishResponse = await fetch(publishUrl, {
198
+ method: 'POST',
199
+ headers: {
200
+ 'Authorization': `ApiKey ${apiKey}`,
201
+ 'X-ClientID': clientId,
202
+ 'Content-Type': 'application/json',
203
+ },
204
+ body: JSON.stringify({
205
+ notes: `Automated publish of version ${project.version}`,
206
+ }),
207
+ });
208
+
209
+ if (!publishResponse.ok) {
210
+ const errorText = await publishResponse.text();
211
+ throw new Error(`Edge publish error: ${publishResponse.status} - ${errorText}`);
212
+ }
213
+
214
+ logger.log('[edge] Upload complete');
215
+ }
216
+
217
+ // Export task
218
+ module.exports = series(publish);
@@ -150,11 +150,29 @@ ${JSON.stringify(messages, null, 2)}
150
150
 
151
151
  Output the translated JSON:`;
152
152
 
153
+ // Write prompt to temp file to avoid shell escaping issues
154
+ const tempDir = path.join(process.cwd(), '.temp');
155
+ const tempFile = path.join(tempDir, `translate-${targetLang}.txt`);
156
+
153
157
  try {
158
+ // Ensure temp dir exists and write prompt
159
+ jetpack.dir(tempDir);
160
+ jetpack.write(tempFile, prompt);
161
+
162
+ // Build command - pipe from file
163
+ const command = `cat "${tempFile}" | claude -p -`;
164
+
165
+ // Log start
166
+ logger.log(`[${targetLang}] Calling Claude CLI...`);
167
+
154
168
  // Run Claude CLI
155
- const result = await execute(`claude -p "${prompt.replace(/"/g, '\\"')}"`, {
156
- timeout: 120000,
157
- });
169
+ const result = await execute(command);
170
+
171
+ // Log response received
172
+ logger.log(`[${targetLang}] Claude CLI responded (${result.length} chars)`);
173
+
174
+ // Clean up temp file
175
+ jetpack.remove(tempFile);
158
176
 
159
177
  // Parse result - extract JSON from response
160
178
  const jsonMatch = result.match(/\{[\s\S]*\}/);
@@ -162,8 +180,13 @@ Output the translated JSON:`;
162
180
  throw new Error('No JSON found in Claude response');
163
181
  }
164
182
 
183
+ // Log success
184
+ logger.log(`[${targetLang}] Parsed JSON successfully`);
185
+
165
186
  return JSON.parse(jsonMatch[0]);
166
187
  } catch (e) {
188
+ // Clean up temp file on error
189
+ jetpack.remove(tempFile);
167
190
  throw new Error(`Claude CLI failed: ${e.message}`);
168
191
  }
169
192
  }
@@ -7,7 +7,7 @@ const path = require('path');
7
7
  * Creates a through2 transform stream that processes template variables in files
8
8
  **/
9
9
  function createTemplateTransform(data) {
10
- const extensions = ['html', 'md', 'liquid', 'json']
10
+ const extensions = ['html', 'md', 'liquid', 'json', 'yml', 'yaml']
11
11
 
12
12
  return through2.obj(function(file, encoding, callback) {
13
13
  // Skip directories
@@ -796,3 +796,31 @@
796
796
  [debug] [2025-11-25T10:40:43.620Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
797
797
  [debug] [2025-11-25T10:40:43.621Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
798
798
  [debug] [2025-11-25T10:40:43.621Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
799
+ [debug] [2025-11-26T00:17:51.793Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
800
+ [debug] [2025-11-26T00:17:51.796Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
801
+ [debug] [2025-11-26T00:17:51.796Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
802
+ [debug] [2025-11-26T00:17:51.796Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
803
+ [debug] [2025-11-26T00:17:51.819Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
804
+ [debug] [2025-11-26T00:17:51.819Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
805
+ [debug] [2025-11-26T00:17:51.908Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
806
+ [debug] [2025-11-26T00:17:51.908Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
807
+ [debug] [2025-11-26T00:17:51.909Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
808
+ [debug] [2025-11-26T00:17:51.910Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
809
+ [debug] [2025-11-26T00:17:51.911Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
810
+ [debug] [2025-11-26T00:17:51.911Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
811
+ [debug] [2025-11-26T00:17:51.912Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
812
+ [debug] [2025-11-26T00:17:51.912Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
813
+ [debug] [2025-11-26T00:17:52.839Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
814
+ [debug] [2025-11-26T00:17:52.842Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
815
+ [debug] [2025-11-26T00:17:52.842Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
816
+ [debug] [2025-11-26T00:17:52.842Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
817
+ [debug] [2025-11-26T00:17:52.850Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
818
+ [debug] [2025-11-26T00:17:52.851Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
819
+ [debug] [2025-11-26T00:17:52.895Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
820
+ [debug] [2025-11-26T00:17:52.896Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
821
+ [debug] [2025-11-26T00:17:52.897Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
822
+ [debug] [2025-11-26T00:17:52.897Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
823
+ [debug] [2025-11-26T00:17:52.900Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
824
+ [debug] [2025-11-26T00:17:52.900Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
825
+ [debug] [2025-11-26T00:17:52.900Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
826
+ [debug] [2025-11-26T00:17:52.900Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-extension-manager",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "Browser Extension Manager dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {