@camstack/server 1.1.56 → 1.1.58

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.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AddonWidgetsService = void 0;
37
+ exports.stripBundleVersionSegment = stripBundleVersionSegment;
37
38
  const path = __importStar(require("node:path"));
38
39
  const fs = __importStar(require("node:fs"));
39
40
  /**
@@ -45,6 +46,23 @@ function stripNodeSuffix(registryKey) {
45
46
  const at = registryKey.indexOf('@');
46
47
  return at === -1 ? registryKey : registryKey.slice(0, at);
47
48
  }
49
+ /** A content-version namespace segment: `v<hex>` (the aggregator's dir hash). */
50
+ const VERSION_SEGMENT_RE = /^v[0-9a-f]{6,}$/;
51
+ /**
52
+ * Strip the optional leading content-version segment (`v<hash>/`) from a
53
+ * requested bundle path. The version is a cache-busting NAMESPACE only — the
54
+ * real file lives at `distDir/<rest>`; MF relative imports resolve against the
55
+ * versioned URL, so `v<hash>/_stub.js` must resolve to `_stub.js`. Returns
56
+ * `{ filePath, versioned }` — `versioned` lets the route serve content-
57
+ * addressed files as `immutable`. Pure + exported for the spec.
58
+ */
59
+ function stripBundleVersionSegment(rawPath) {
60
+ const slash = rawPath.indexOf('/');
61
+ if (slash > 0 && VERSION_SEGMENT_RE.test(rawPath.slice(0, slash))) {
62
+ return { filePath: rawPath.slice(slash + 1), versioned: true };
63
+ }
64
+ return { filePath: rawPath, versioned: false };
65
+ }
48
66
  /**
49
67
  * AddonWidgetsService — server-side helper that backs the static file
50
68
  * route `/api/addon-widgets/:addonId/*` (registered in `main.ts`).
@@ -103,8 +121,11 @@ class AddonWidgetsService {
103
121
  this.logger.warn('Bundle resolve failed: addon install path not found', { tags: { addonId } });
104
122
  return null;
105
123
  }
124
+ // Drop the content-version namespace (`v<hash>/`) the aggregator stamps —
125
+ // it exists only to atomically cache-bust the bundle on update.
126
+ const { filePath: bareFilePath } = stripBundleVersionSegment(filePath);
106
127
  const resolvedBase = path.resolve(installPath.distDir);
107
- const resolvedFile = path.resolve(installPath.distDir, filePath);
128
+ const resolvedFile = path.resolve(installPath.distDir, bareFilePath);
108
129
  // Path traversal protection
109
130
  if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
110
131
  this.logger.warn('Path traversal denied for addon', { tags: { addonId }, meta: { filePath } });
package/dist/main.js CHANGED
@@ -475,11 +475,17 @@ async function bootstrap() {
475
475
  : ext === '.html'
476
476
  ? 'text/html'
477
477
  : 'application/octet-stream';
478
+ // A content-versioned request (`v<hash>/…`) is content-addressed: the
479
+ // URL changes iff the bundle changed, so EVERYTHING under it — even the
480
+ // fixed-name `_stub.js`/`remoteEntry.js` — is safely immutable (this
481
+ // defeats a CDN edge serving a stale stub). Legacy unversioned requests
482
+ // keep the per-file revalidation policy.
483
+ const { versioned } = (0, addon_widgets_service_1.stripBundleVersionSegment)(filePath);
484
+ const cacheControl = versioned
485
+ ? 'public, max-age=31536000, immutable'
486
+ : (0, spa_static_1.addonBundleCacheControl)(resolved);
478
487
  const stream = fs.createReadStream(resolved);
479
- return reply
480
- .header('cache-control', (0, spa_static_1.addonBundleCacheControl)(resolved))
481
- .type(contentType)
482
- .send(stream);
488
+ return reply.header('cache-control', cacheControl).type(contentType).send(stream);
483
489
  });
484
490
  // Serve static assets (e.g. SVG icons) from an addon's package directory
485
491
  fastify.get('/api/addon-assets/:addonId/*', async (request, reply) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.1.56",
3
+ "version": "1.1.58",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -28,12 +28,12 @@
28
28
  "@camstack/addon-auth": "1.1.6",
29
29
  "@camstack/addon-decoder-nodeav": "1.1.9",
30
30
  "@camstack/addon-notifiers": "1.1.21",
31
- "@camstack/addon-pipeline": "1.1.51",
32
- "@camstack/addon-pipeline-orchestrator": "1.1.41",
31
+ "@camstack/addon-pipeline": "1.1.52",
32
+ "@camstack/addon-pipeline-orchestrator": "1.1.42",
33
33
  "@camstack/addon-post-analysis": "1.1.24",
34
34
  "@camstack/sdk": "1.1.22",
35
35
  "@camstack/shm-ring": "1.0.21",
36
- "@camstack/system": "1.1.43",
36
+ "@camstack/system": "1.1.45",
37
37
  "@camstack/types": "1.1.40",
38
38
  "@camstack/ui-library": "1.1.32",
39
39
  "@fastify/compress": "^9.0.0",