browser-extension-manager 1.1.12 → 1.2.0
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/CHANGELOG.md +11 -0
- 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/utils/template-transform.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
17
|
---
|
|
18
|
+
## [1.1.13] - 2025-11-26
|
|
19
|
+
### Added
|
|
20
|
+
- Default `.env` file with publish credential templates for all stores
|
|
21
|
+
- Intelligent line-based merging for `.gitignore` and `.env` files with section markers
|
|
22
|
+
- Template variable support for `.yml` and `.yaml` files
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Credentials now read from `.env` file instead of config (better security)
|
|
26
|
+
- `.gitignore` and `.env` files use section markers to separate defaults from custom values
|
|
27
|
+
- Template transform now passes `versions` data for workflow templating
|
|
28
|
+
|
|
18
29
|
## [1.1.6] - 2025-11-14
|
|
19
30
|
### Added
|
|
20
31
|
- Bootstrap exposure to `window.bootstrap` in all UI components (popup, options, sidepanel, page)
|
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);
|
|
@@ -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
|