browser-extension-manager 1.3.13 → 1.3.15

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/CLAUDE.md CHANGED
@@ -209,8 +209,33 @@ When adding a new component type to the framework:
209
209
  - **distribute** ([gulp/tasks/distribute.js](src/gulp/tasks/distribute.js)) - Copies project files to `dist/`
210
210
  - **sass** ([gulp/tasks/sass.js](src/gulp/tasks/sass.js)) - Compiles SCSS with sophisticated load path system
211
211
  - **webpack** ([gulp/tasks/webpack.js](src/gulp/tasks/webpack.js)) - Bundles JavaScript with Babel
212
- - **html** ([gulp/tasks/html.js](src/gulp/tasks/html.js)) - Processes HTML views into templates
212
+ - **html** ([gulp/tasks/html.js](src/gulp/tasks/html.js)) - Processes HTML views into templates (see HTML Templating below)
213
213
  - **package** ([gulp/tasks/package.js](src/gulp/tasks/package.js)) - Creates packaged extension
214
+
215
+ ### HTML Templating
216
+
217
+ HTML views in `src/views/` are processed through a two-step templating system using `{{ }}` brackets.
218
+
219
+ **Available variables:**
220
+ - `{{ brand.name }}` - Brand name from config
221
+ - `{{ brand.url }}` - Brand URL from config
222
+ - `{{ page.name }}` - Component name (e.g., `popup`, `pages/index`)
223
+ - `{{ page.path }}` - Full view path
224
+ - `{{ page.title }}` - Page title (defaults to brand name)
225
+ - `{{ theme.appearance }}` - Theme appearance (`dark` or `light`)
226
+ - `{{ cacheBust }}` - Cache-busting timestamp
227
+
228
+ **Example usage in views:**
229
+ ```html
230
+ <a href="{{ brand.url }}/pricing">Upgrade to Premium</a>
231
+ <p>Welcome to {{ brand.name }}</p>
232
+ ```
233
+
234
+ **How it works:**
235
+ 1. Your view file (`src/views/[component]/index.html`) is templated first
236
+ 2. The result is injected into [page-template.html](src/config/page-template.html)
237
+ 3. The outer template is processed with the same variables
238
+
214
239
  - **serve** ([gulp/tasks/serve.js](src/gulp/tasks/serve.js)) - WebSocket server for live reload
215
240
 
216
241
  ### Modifying Themes
@@ -105,6 +105,15 @@ function processHtml(templateContent) {
105
105
  };
106
106
 
107
107
  // Apply template with custom brackets
108
+ // First, template the body content to replace any {{ }} placeholders in the view
109
+ const templatedBody = template(bodyContent, data, {
110
+ brackets: ['{{', '}}'],
111
+ });
112
+
113
+ // Update data with templated body
114
+ data.content = templatedBody;
115
+
116
+ // Then template the outer page template
108
117
  const rendered = template(templateContent, data, {
109
118
  brackets: ['{{', '}}'],
110
119
  });
@@ -314,59 +314,57 @@ async function publishToEdge() {
314
314
  throw new Error('Missing Edge credentials. Set EDGE_PRODUCT_ID, EDGE_CLIENT_ID, EDGE_API_KEY in .env');
315
315
  }
316
316
 
317
- // Check current submission status first
318
- logger.log('[edge] Checking submission status...');
319
- const statusUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions`;
320
- const statusResponse = await fetch(statusUrl, {
321
- method: 'GET',
322
- headers: {
323
- 'Authorization': `ApiKey ${apiKey}`,
324
- 'X-ClientID': clientId,
325
- },
326
- });
317
+ // Helper for Edge API requests
318
+ const edgeHeaders = {
319
+ 'Authorization': `ApiKey ${apiKey}`,
320
+ 'X-ClientID': clientId,
321
+ };
322
+
323
+ // Helper to parse Edge API response (handles empty bodies)
324
+ async function parseEdgeResponse(response, label) {
325
+ const text = await response.text();
326
+ logger.log(`[edge] ${label} - Status: ${response.status}, Body: ${text || '(empty)'}`);
327
327
 
328
- if (statusResponse.ok) {
329
- const statusData = await statusResponse.json();
330
- // Check if there's a pending submission (InReview, PendingPublish, etc.)
331
- const pendingStatuses = ['InReview', 'PendingPublish', 'Submitted', 'InProgress'];
332
- if (statusData && pendingStatuses.includes(statusData.status)) {
333
- throw new Error(`Extension already has a pending submission (status: ${statusData.status}). Wait for it to complete.`);
328
+ if (!text) {
329
+ return { status: response.status, data: null };
330
+ }
331
+
332
+ try {
333
+ return { status: response.status, data: JSON.parse(text) };
334
+ } catch (e) {
335
+ return { status: response.status, data: text };
334
336
  }
335
337
  }
336
338
 
339
+ // Step 1: Upload the package first
337
340
  logger.log('[edge] Uploading to Microsoft Edge Add-ons...');
338
341
 
339
- // Read chromium zip file (Edge uses same build as Chrome)
340
342
  const zipBuffer = jetpack.read(PATHS.chromium.zip, 'buffer');
341
-
342
- // Edge API v1.1 endpoint
343
343
  const uploadUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions/draft/package`;
344
344
 
345
- // Upload using fetch
346
- const response = await fetch(uploadUrl, {
345
+ const uploadResponse = await fetch(uploadUrl, {
347
346
  method: 'POST',
348
347
  headers: {
349
- 'Authorization': `ApiKey ${apiKey}`,
350
- 'X-ClientID': clientId,
348
+ ...edgeHeaders,
351
349
  'Content-Type': 'application/zip',
352
350
  },
353
351
  body: zipBuffer,
354
352
  });
355
353
 
356
- if (!response.ok) {
357
- const errorText = await response.text();
358
- throw new Error(`Edge API error: ${response.status} - ${errorText}`);
354
+ const upload = await parseEdgeResponse(uploadResponse, 'Upload response');
355
+
356
+ if (!uploadResponse.ok) {
357
+ throw new Error(`Edge upload error: ${upload.status} - ${JSON.stringify(upload.data)}`);
359
358
  }
360
359
 
361
360
  logger.log('[edge] Package uploaded, submitting for review...');
362
361
 
363
- // Submit for review
362
+ // Step 2: Submit for review - this is where we'll get InProgressSubmission if there's a pending review
364
363
  const publishUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${productId}/submissions`;
365
364
  const publishResponse = await fetch(publishUrl, {
366
365
  method: 'POST',
367
366
  headers: {
368
- 'Authorization': `ApiKey ${apiKey}`,
369
- 'X-ClientID': clientId,
367
+ ...edgeHeaders,
370
368
  'Content-Type': 'application/json',
371
369
  },
372
370
  body: JSON.stringify({
@@ -374,9 +372,33 @@ async function publishToEdge() {
374
372
  }),
375
373
  });
376
374
 
375
+ const publish = await parseEdgeResponse(publishResponse, 'Publish response');
376
+
377
+ // Check for HTTP errors (4xx, 5xx)
377
378
  if (!publishResponse.ok) {
378
- const errorText = await publishResponse.text();
379
- throw new Error(`Edge publish error: ${publishResponse.status} - ${errorText}`);
379
+ // Check if it's a 409 Conflict or similar indicating in-progress submission
380
+ if (publish.status === 409) {
381
+ throw new Error('Extension already has a pending submission in review. Wait for it to complete before publishing again.');
382
+ }
383
+ throw new Error(`Edge publish error: ${publish.status} - ${JSON.stringify(publish.data)}`);
384
+ }
385
+
386
+ // Check for API-level failures (HTTP 200/202 but status: "Failed" in body)
387
+ if (publish.data && typeof publish.data === 'object') {
388
+ if (publish.data.status === 'Failed') {
389
+ if (publish.data.errorCode === 'InProgressSubmission') {
390
+ throw new Error('Extension already has a pending submission in review. Wait for it to complete before publishing again.');
391
+ }
392
+ if (publish.data.errorCode === 'UnpublishInProgress') {
393
+ throw new Error('Extension is being unpublished. Wait for unpublish to complete before publishing.');
394
+ }
395
+ throw new Error(`Edge publish failed: ${publish.data.message || publish.data.errorCode || 'Unknown error'}`);
396
+ }
397
+ }
398
+
399
+ // HTTP 202 Accepted means submission was queued successfully
400
+ if (publish.status === 202) {
401
+ logger.log('[edge] Submission accepted and queued for review');
380
402
  }
381
403
 
382
404
  logger.log('[edge] Upload complete');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-extension-manager",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "description": "Browser Extension Manager dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {