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 +31 -0
- package/dist/defaults/.github/workflows/publish.yml +82 -0
- package/dist/defaults/_.env +22 -0
- package/dist/defaults/_.gitignore +11 -7
- package/dist/gulp/main.js +1 -0
- package/dist/gulp/tasks/defaults.js +210 -2
- package/dist/gulp/tasks/publish.js +218 -0
- package/dist/gulp/tasks/translate.js +26 -3
- package/dist/gulp/tasks/utils/template-transform.js +1 -1
- package/firebase-debug.log +28 -0
- package/package.json +1 -1
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
|
|
34
|
+
# Grunt intermediate storage
|
|
36
35
|
.grunt
|
|
37
36
|
|
|
38
|
-
# Bower dependency directory
|
|
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
|
|
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
|
@@ -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(
|
|
156
|
-
|
|
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
|
package/firebase-debug.log
CHANGED
|
@@ -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)
|