@_linked/cli 1.45.2 → 1.45.3
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.45.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#132](https://github.com/linked-fw/cli/pull/132) [`283e7ad`](https://github.com/linked-fw/cli/commit/283e7adfaa9f10bbf6c1d4ca90a9b708542c576b) Thanks [@abdipramana](https://github.com/abdipramana)! - Preserve validated release object paths exactly when publishing app artifacts, preventing file-store sanitization from breaking the URLs embedded in production bundles.
|
|
8
|
+
|
|
3
9
|
## 1.45.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -195,9 +195,10 @@ release is never marked complete, and re-running `publish-app` resumes it.
|
|
|
195
195
|
|
|
196
196
|
**The store must keep keys verbatim.** A release object has to be stored under exactly the key it
|
|
197
197
|
was given, or the URLs baked into the bundle point at nothing, so publishing passes
|
|
198
|
-
`preventDuplicates: false` and
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
`preventDuplicates: false` and `preservePath: true`. A compatible store preserves the safe relative
|
|
199
|
+
key exactly and rejects absolute or traversal paths. Publishing fails with a clear message if the
|
|
200
|
+
store reports a different location. `@_linked/server`'s `LocalFileStore` supports this contract;
|
|
201
|
+
stores that do not support exact keys cannot publish a linked release.
|
|
201
202
|
|
|
202
203
|
**Cache policy follows the origin, not the filename.** Files listed in the Vite manifest are
|
|
203
204
|
content-hashed by construction and get `public, max-age=31536000, immutable`. Everything else —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../../src/app-release/publisher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AASpE,OAAO,KAAK,EAEV,wBAAwB,EACxB,WAAW,EACX,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,wBAAwB,CAAC;CACpC;AAED,MAAM,WAAW,qBAAsB,SAAQ,yBAAyB;IACtE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAkCD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,MAAM,EACf,qBAAmC,KAClC,qBA6BF,CAAC;AA2CF,eAAO,MAAM,kBAAkB,GAC7B,SAAS,yBAAyB,KACjC,WAsBF,CAAC;AAkCF,eAAO,MAAM,cAAc,GACzB,SAAS,qBAAqB,KAC7B,OAAO,CAAC,aAAa,
|
|
1
|
+
{"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../../src/app-release/publisher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AASpE,OAAO,KAAK,EAEV,wBAAwB,EACxB,WAAW,EACX,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,wBAAwB,CAAC;CACpC;AAED,MAAM,WAAW,qBAAsB,SAAQ,yBAAyB;IACtE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAkCD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,MAAM,EACf,qBAAmC,KAClC,qBA6BF,CAAC;AA2CF,eAAO,MAAM,kBAAkB,GAC7B,SAAS,yBAAyB,KACjC,WAsBF,CAAC;AAkCF,eAAO,MAAM,cAAc,GACzB,SAAS,qBAAqB,KAC7B,OAAO,CAAC,aAAa,CAiJvB,CAAC"}
|
|
@@ -147,13 +147,14 @@ export const publishRelease = (options) => __awaiter(void 0, void 0, void 0, fun
|
|
|
147
147
|
'checked against the manifest before upload; the remote copy is not re-checked.');
|
|
148
148
|
};
|
|
149
149
|
const upload = (objectKey, content, contentType, cacheControl, sha256) => __awaiter(void 0, void 0, void 0, function* () {
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
150
|
+
// A release key must be exactly the key in the manifest. Disable duplicate
|
|
151
|
+
// renaming and ask the store to preserve the validated relative path; any
|
|
152
|
+
// suffix or sanitisation would break the URL and make verification useless.
|
|
153
153
|
const storedURL = yield options.store.saveFile(objectKey, content, {
|
|
154
154
|
mimeType: contentType,
|
|
155
155
|
cacheControl,
|
|
156
156
|
preventDuplicates: false,
|
|
157
|
+
preservePath: true,
|
|
157
158
|
});
|
|
158
159
|
// A release key has to survive the round trip verbatim, or the published
|
|
159
160
|
// URLs in the bundle point at nothing. Some stores sanitise or suffix the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../../src/app-release/publisher.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAC,oBAAoB,EAAE,6BAA6B,EAAC,MAAM,YAAY,CAAC;AA+B/E;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAG,CACxB,aAAqB,EACrB,IAA0B,EACpB,EAAE;;IACR,MAAM,UAAU,GAAG,oBAAoB,CACrC,MAAM,CAAC,MAAA,IAAI,CAAC,UAAU,mCAAI,EAAE,CAAC,EAC7B,aAAa,CACd,CAAC;IACF,MAAM,SAAS,GAAG,oBAAoB,CACpC,MAAM,CAAC,MAAA,IAAI,CAAC,SAAS,mCAAI,EAAE,CAAC,EAC5B,YAAY,CACb,CAAC;IACF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IAC7D,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,+DAA+D;YAC7D,IAAI,IAAI,CAAC,SAAS,gBAAgB,QAAQ,2BAA2B;YACrE,+CAA+C,CAClD,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,OAAe,EACf,YAAY,GAAG,oBAAoB,EACZ,EAAE;;IACzB,MAAM,YAAY,GAAG,6BAA6B,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CACV,CAAC;IAE9B,IAAI,QAAQ,CAAC,aAAa,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,wCAAwC,QAAQ,CAAC,aAAa,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,CAAA,MAAA,QAAQ,CAAC,WAAW,0CAAE,aAAa,CAAA,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,aAAa,GAAG,oBAAoB,CACxC,QAAQ,CAAC,WAAW,CAAC,aAAa,EAClC,gBAAgB,CACjB,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,CAAC;AAClC,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,CAC1B,QAAkC,EAClC,KAAiB,EACjB,EAAE;IACF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,2EACE,QAAQ,IAAI,SACd,8CAA8C,MAAM,IAAI,SAAS,GAAG,CACrE,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,OAAe,EAAU,EAAE,CAC7C,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE5D;;;GAGG;AACH,MAAM,iBAAiB,GAAG,CACxB,OAAe,EACf,IAA0B,EAClB,EAAE;IACV,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,YAAY,GAAG,6BAA6B,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,OAAkC,EACrB,EAAE;IACf,MAAM,EAAC,YAAY,EAAE,QAAQ,EAAC,GAAG,mBAAmB,CAClD,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,YAAY,CACrB,CAAC;IACF,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAE7C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,YAAY,EAAE,YAAY;QAC1B,iBAAiB,EAAE,gBAAgB,CACjC,QAAQ,CAAC,WAAW,CAAC,aAAa,EAClC,oBAAoB,CACrB;QACD,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;KACzE,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAG,CACnB,KAAiB,EACjB,SAAiB,EACjB,QAAwC,EACS,EAAE;IACnD,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,UAAU;QAAE,OAAO,aAAa,CAAC;IAE/D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,gDAAgD,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,uDAAuD,SAAS,EAAE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,SAAS,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC,CAAA,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,OAA8B,EACN,EAAE;;IAC1B,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,CAAC;YAChB,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,uBAAuB,GAAG,KAAK,CAAC;IACpC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,MAAc,EAAE,EAAE;;QAC3D,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3B,IAAI,uBAAuB;YAAE,OAAO;QACpC,uBAAuB,GAAG,IAAI,CAAC;QAC/B,MAAA,OAAO,CAAC,SAAS,wDACf,+CAA+C,MAAM,qBAAqB;YACxE,gFAAgF,CACnF,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CACb,SAAiB,EACjB,OAAe,EACf,WAAmB,EACnB,YAAoB,EACpB,MAAc,EACd,EAAE;QACF,4EAA4E;QAC5E,2EAA2E;QAC3E,oEAAoE;QACpE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE;YACjE,QAAQ,EAAE,WAAW;YACrB,YAAY;YACZ,iBAAiB,EAAE,KAAK;SACzB,CAAC,CAAC;QACH,yEAAyE;QACzE,0EAA0E;QAC1E,sEAAsE;QACtE,sCAAsC;QACtC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CACb,qEAAqE;gBACnE,kCAAkC,SAAS,WAAW,SAAS,KAAK;gBACpE,iDAAiD,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE;YAC3D,IAAI,EAAE,OAAO,CAAC,UAAU;YACxB,MAAM;SACP,CAAC,CAAC;QACH,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC9B,cAAc,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;QACtE,CAAC;aAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,cAAc,CAAC,SAAS,EAAE,gDAAgD,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC,CAAA,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3C,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB;;;;;OAKG;IACH,MAAM,eAAe,GAAG,CACtB,KAAc,EACd,IAAY,EACZ,SAAiB,EACV,EAAE;QACT,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,KAAK,CACvB,yCAAyC,IAAI,QAAQ,SAAS,IAAI;YAChE,UAAU,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,gBAAgB,MAAM,KAAK,CAAC,OAAO,IAAI;YAC5E,GAAG,gBAAgB,OAAO,YAAY,uCAAuC;YAC7E,IAAI,IAAI,CAAC,WAAW,CAAC,aAAa,mDAAmD;YACrF,6EAA6E;YAC7E,gFAAgF,CACnF,CAAC;QACD,OAAqC,CAAC,KAAK,GAAG,KAAK,CAAC;QACrD,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;YACzB,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,gBAAgB,KAAK,CAAC,KAAK,EAAE;YACjD,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;QAClB,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,6BAA6B,CAChD,OAAO,CAAC,OAAO,EACf,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,IAAI,CAAC;YACH,MAAM,MAAM,CACV,IAAI,CAAC,SAAS,EACd,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,EAC7B,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,MAAM,CACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC;QACD,gBAAgB,IAAI,CAAC,CAAC;QACtB,MAAA,OAAO,CAAC,UAAU,wDAAG;YACnB,SAAS,EAAE,gBAAgB;YAC3B,KAAK,EAAE,YAAY;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,2BAA2B;IAC3B,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,CACV,IAAI,CAAC,iBAAiB,EACtB,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,CAAC,YAAY,CAAC,CACzB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,eAAe,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC/E,CAAC;IACD,gBAAgB,IAAI,CAAC,CAAC;IACtB,MAAA,OAAO,CAAC,UAAU,wDAAG;QACnB,SAAS,EAAE,gBAAgB;QAC3B,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,IAAI,CAAC,iBAAiB;KAClC,CAAC,CAAC;IAEH,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,YAAY;QAC3B,aAAa,EAAE,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,UAAU;QACxD,MAAM,EAAE,KAAK;QACb,UAAU;KACX,CAAC;AACJ,CAAC,CAAA,CAAC","sourcesContent":["import crypto from 'crypto';\nimport fs from 'fs';\nimport type {IFileStore} from '@_linked/core/interfaces/IFileStore';\nimport {normalizeAccessURL} from './app-assets-store.js';\nimport {\n MANIFEST_CONTENT_TYPE,\n MANIFEST_SOURCE_PATH,\n ENTRY_CACHE_CONTROL,\n releaseObjectKey,\n} from './create-release-manifest.js';\nimport {normalizeReleasePath, resolveExistingPathWithinRoot} from './paths.js';\nimport type {\n LinkedAppReleaseFile,\n LinkedAppReleaseManifest,\n PublishPlan,\n PublishResult,\n} from './types.js';\n\nexport interface PlanReleasePublishOptions {\n appRoot: string;\n manifestPath?: string;\n store: IFileStore;\n}\n\nexport interface LoadedReleaseManifest {\n absolutePath: string;\n manifest: LinkedAppReleaseManifest;\n}\n\nexport interface PublishReleaseOptions extends PlanReleasePublishOptions {\n yes?: boolean;\n onProgress?: (progress: PublishProgress) => void;\n onWarning?: (message: string) => void;\n}\n\nexport interface PublishProgress {\n completed: number;\n total: number;\n objectKey: string;\n}\n\n/**\n * Every object of a release must land under that release's own prefix.\n *\n * The manifest is a plain file on disk: a hand-edited or foreign one can name\n * any `objectKey` it likes, and nothing else catches it — the `accessURL` check\n * only proves the store is the right store, and the stored-location check only\n * proves the store kept the key it was handed. So the key is not merely checked\n * for shape, it is recomputed: it has to be exactly what this release prefix and\n * this source path produce, or a release could overwrite a *different* release.\n */\nconst validateObjectKey = (\n releasePrefix: string,\n file: LinkedAppReleaseFile,\n): void => {\n const sourcePath = normalizeReleasePath(\n String(file.sourcePath ?? ''),\n 'source path',\n );\n const objectKey = normalizeReleasePath(\n String(file.objectKey ?? ''),\n 'object key',\n );\n const expected = releaseObjectKey(releasePrefix, sourcePath);\n if (objectKey !== expected) {\n throw new Error(\n `Release manifest object key is not under the release prefix: ` +\n `\"${file.objectKey}\" (expected \"${expected}\"). Rebuild the release; ` +\n 'a manifest may only upload to its own prefix.',\n );\n }\n};\n\nexport const loadReleaseManifest = (\n appRoot: string,\n manifestPath = MANIFEST_SOURCE_PATH,\n): LoadedReleaseManifest => {\n const absolutePath = resolveExistingPathWithinRoot(appRoot, manifestPath);\n const manifest = JSON.parse(\n fs.readFileSync(absolutePath, 'utf8'),\n ) as LinkedAppReleaseManifest;\n\n if (manifest.schemaVersion !== 1) {\n throw new Error(\n `Unsupported release manifest schema: ${manifest.schemaVersion}`,\n );\n }\n if (manifest.target !== 'web' || manifest.publishable !== true) {\n throw new Error('Only publishable web release manifests may be uploaded');\n }\n if (!Array.isArray(manifest.files) || manifest.files.length === 0) {\n throw new Error('Release manifest contains no files');\n }\n if (!manifest.destination?.releasePrefix) {\n throw new Error('Release manifest has no release prefix');\n }\n const releasePrefix = normalizeReleasePath(\n manifest.destination.releasePrefix,\n 'release prefix',\n );\n for (const file of manifest.files) {\n validateObjectKey(releasePrefix, file);\n }\n\n return {absolutePath, manifest};\n};\n\n/**\n * The manifest records the `accessURL` of the store it was built for. If the\n * configured store now writes somewhere else, the release is not the one that\n * was built and publishing stops. Trailing slashes are not significant.\n */\nconst validateDestination = (\n manifest: LinkedAppReleaseManifest,\n store: IFileStore,\n) => {\n const expected = normalizeAccessURL(manifest.destination.accessURL);\n const actual = normalizeAccessURL(store.accessURL);\n if (expected !== actual) {\n throw new Error(\n `Release destination does not match the manifest (manifest was built for ${\n expected || '(empty)'\n }, the configured appAssets store writes to ${actual || '(empty)'})`,\n );\n }\n};\n\nconst hashBuffer = (content: Buffer): string =>\n crypto.createHash('sha256').update(content).digest('hex');\n\n/**\n * Re-hash the file on disk before uploading. This needs no support from the\n * store and is what actually guarantees the bytes match the manifest.\n */\nconst validateLocalFile = (\n appRoot: string,\n file: LinkedAppReleaseFile,\n): string => {\n // `resolveExistingPathWithinRoot` resolves symlinks, so a source path that\n // points out of the app root through one is refused here too.\n const absolutePath = resolveExistingPathWithinRoot(appRoot, file.sourcePath);\n const content = fs.readFileSync(absolutePath);\n if (content.byteLength !== file.size || hashBuffer(content) !== file.sha256) {\n throw new Error(`Release artifact changed after build: ${file.sourcePath}`);\n }\n return absolutePath;\n};\n\nexport const planReleasePublish = (\n options: PlanReleasePublishOptions,\n): PublishPlan => {\n const {absolutePath, manifest} = loadReleaseManifest(\n options.appRoot,\n options.manifestPath,\n );\n validateDestination(manifest, options.store);\n\n for (const file of manifest.files) {\n validateLocalFile(options.appRoot, file);\n }\n\n return {\n releaseId: manifest.releaseId,\n manifestPath: absolutePath,\n manifestObjectKey: releaseObjectKey(\n manifest.destination.releasePrefix,\n MANIFEST_SOURCE_PATH,\n ),\n destination: manifest.destination,\n files: manifest.files,\n totalBytes: manifest.files.reduce((total, file) => total + file.size, 0),\n };\n};\n\n/**\n * Verify one uploaded object against the hash in the manifest.\n *\n * `statFile` is optional on `IFileStore`, and a store that has it may still\n * report no `sha256` (S3 only returns one when the object carries a checksum).\n * Neither case is a failure: verification is skipped, the key is reported back\n * as unverified, and the caller warns once. An `etag` is deliberately not used\n * as a substitute — it is not a content hash.\n */\nconst verifyUpload = async (\n store: IFileStore,\n objectKey: string,\n expected: {size: number; sha256: string},\n): Promise<'verified' | 'unsupported' | 'no-hash'> => {\n if (typeof store.statFile !== 'function') return 'unsupported';\n\n const stat = await store.statFile(objectKey);\n if (!stat) {\n throw new Error(`Uploaded artifact is missing from the store: ${objectKey}`);\n }\n if (stat.size !== expected.size) {\n throw new Error(\n `Uploaded artifact size does not match the manifest: ${objectKey}`,\n );\n }\n if (!stat.sha256) return 'no-hash';\n if (stat.sha256 !== expected.sha256) {\n throw new Error(`Uploaded artifact verification failed: ${objectKey}`);\n }\n return 'verified';\n};\n\nexport const publishRelease = async (\n options: PublishReleaseOptions,\n): Promise<PublishResult> => {\n const plan = planReleasePublish(options);\n\n if (!options.yes) {\n return {\n releaseId: plan.releaseId,\n uploadedFiles: 0,\n uploadedBytes: 0,\n dryRun: true,\n unverified: [],\n };\n }\n\n const unverified: string[] = [];\n let warnedAboutVerification = false;\n const noteUnverified = (objectKey: string, reason: string) => {\n unverified.push(objectKey);\n if (warnedAboutVerification) return;\n warnedAboutVerification = true;\n options.onWarning?.(\n `Cannot verify uploads against a store hash (${reason}). Files are still ` +\n 'checked against the manifest before upload; the remote copy is not re-checked.',\n );\n };\n\n const upload = async (\n objectKey: string,\n content: Buffer,\n contentType: string,\n cacheControl: string,\n sha256: string,\n ) => {\n // `preventDuplicates: false` is explicit: a release key must be exactly the\n // key in the manifest. `LocalFileStore` otherwise appends a random suffix,\n // which would both break the URL and make verification meaningless.\n const storedURL = await options.store.saveFile(objectKey, content, {\n mimeType: contentType,\n cacheControl,\n preventDuplicates: false,\n });\n // A release key has to survive the round trip verbatim, or the published\n // URLs in the bundle point at nothing. Some stores sanitise or suffix the\n // path they are given; say so plainly instead of failing later with a\n // confusing \"missing from the store\".\n if (typeof storedURL === 'string' && !storedURL.endsWith(objectKey)) {\n throw new Error(\n `The file store did not store the release object under its own key. ` +\n `Expected a location ending in \"${objectKey}\", got \"${storedURL}\". ` +\n 'A release store must keep object keys verbatim.',\n );\n }\n const outcome = await verifyUpload(options.store, objectKey, {\n size: content.byteLength,\n sha256,\n });\n if (outcome === 'unsupported') {\n noteUnverified(objectKey, 'this store does not implement statFile');\n } else if (outcome === 'no-hash') {\n noteUnverified(objectKey, 'the store reports no sha256 for stored objects');\n }\n };\n\n const totalUploads = plan.files.length + 1;\n let completedUploads = 0;\n\n /**\n * A failure halfway through leaves a partial prefix behind, so the message\n * has to say which object failed, where it was going and how much is already\n * there — and that a re-run finishes the job, since keys are deterministic\n * and every object is overwritten with identical bytes.\n */\n const describeFailure = (\n error: unknown,\n what: string,\n objectKey: string,\n ): Error => {\n const cause = error instanceof Error ? error : new Error(String(error));\n const failure = new Error(\n `Release upload failed while uploading ${what} to \"${objectKey}\" ` +\n `(store ${options.store.accessURL || '(no accessURL)'}): ${cause.message}. ` +\n `${completedUploads} of ${totalUploads} objects were already uploaded under ` +\n `\"${plan.destination.releasePrefix}\"; the release manifest was not uploaded, so the ` +\n 'release is incomplete. Re-running publish-app resumes it — object keys are ' +\n 'deterministic and already-uploaded objects are rewritten with identical bytes.',\n );\n (failure as Error & {cause?: unknown}).cause = cause;\n failure.stack = cause.stack\n ? `${failure.message}\\nCaused by: ${cause.stack}`\n : failure.stack;\n return failure;\n };\n\n for (const file of plan.files) {\n const absolutePath = resolveExistingPathWithinRoot(\n options.appRoot,\n file.sourcePath,\n );\n try {\n await upload(\n file.objectKey,\n fs.readFileSync(absolutePath),\n file.contentType,\n file.cacheControl,\n file.sha256,\n );\n } catch (error) {\n throw describeFailure(error, file.sourcePath, file.objectKey);\n }\n completedUploads += 1;\n options.onProgress?.({\n completed: completedUploads,\n total: totalUploads,\n objectKey: file.objectKey,\n });\n }\n\n // The manifest goes last: its presence under the release prefix is what marks\n // the release as complete.\n const manifestBody = fs.readFileSync(plan.manifestPath);\n try {\n await upload(\n plan.manifestObjectKey,\n manifestBody,\n MANIFEST_CONTENT_TYPE,\n ENTRY_CACHE_CONTROL,\n hashBuffer(manifestBody),\n );\n } catch (error) {\n throw describeFailure(error, 'the release manifest', plan.manifestObjectKey);\n }\n completedUploads += 1;\n options.onProgress?.({\n completed: completedUploads,\n total: totalUploads,\n objectKey: plan.manifestObjectKey,\n });\n\n return {\n releaseId: plan.releaseId,\n uploadedFiles: totalUploads,\n uploadedBytes: plan.totalBytes + manifestBody.byteLength,\n dryRun: false,\n unverified,\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../../src/app-release/publisher.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAC,oBAAoB,EAAE,6BAA6B,EAAC,MAAM,YAAY,CAAC;AA+B/E;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAG,CACxB,aAAqB,EACrB,IAA0B,EACpB,EAAE;;IACR,MAAM,UAAU,GAAG,oBAAoB,CACrC,MAAM,CAAC,MAAA,IAAI,CAAC,UAAU,mCAAI,EAAE,CAAC,EAC7B,aAAa,CACd,CAAC;IACF,MAAM,SAAS,GAAG,oBAAoB,CACpC,MAAM,CAAC,MAAA,IAAI,CAAC,SAAS,mCAAI,EAAE,CAAC,EAC5B,YAAY,CACb,CAAC;IACF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IAC7D,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,+DAA+D;YAC7D,IAAI,IAAI,CAAC,SAAS,gBAAgB,QAAQ,2BAA2B;YACrE,+CAA+C,CAClD,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,OAAe,EACf,YAAY,GAAG,oBAAoB,EACZ,EAAE;;IACzB,MAAM,YAAY,GAAG,6BAA6B,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CACV,CAAC;IAE9B,IAAI,QAAQ,CAAC,aAAa,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,wCAAwC,QAAQ,CAAC,aAAa,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,CAAA,MAAA,QAAQ,CAAC,WAAW,0CAAE,aAAa,CAAA,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,aAAa,GAAG,oBAAoB,CACxC,QAAQ,CAAC,WAAW,CAAC,aAAa,EAClC,gBAAgB,CACjB,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,CAAC;AAClC,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,CAC1B,QAAkC,EAClC,KAAiB,EACjB,EAAE;IACF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,2EACE,QAAQ,IAAI,SACd,8CAA8C,MAAM,IAAI,SAAS,GAAG,CACrE,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,OAAe,EAAU,EAAE,CAC7C,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE5D;;;GAGG;AACH,MAAM,iBAAiB,GAAG,CACxB,OAAe,EACf,IAA0B,EAClB,EAAE;IACV,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,YAAY,GAAG,6BAA6B,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,OAAkC,EACrB,EAAE;IACf,MAAM,EAAC,YAAY,EAAE,QAAQ,EAAC,GAAG,mBAAmB,CAClD,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,YAAY,CACrB,CAAC;IACF,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAE7C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,YAAY,EAAE,YAAY;QAC1B,iBAAiB,EAAE,gBAAgB,CACjC,QAAQ,CAAC,WAAW,CAAC,aAAa,EAClC,oBAAoB,CACrB;QACD,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;KACzE,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAG,CACnB,KAAiB,EACjB,SAAiB,EACjB,QAAwC,EACS,EAAE;IACnD,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,UAAU;QAAE,OAAO,aAAa,CAAC;IAE/D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,gDAAgD,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,uDAAuD,SAAS,EAAE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,SAAS,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC,CAAA,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,OAA8B,EACN,EAAE;;IAC1B,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,CAAC;YAChB,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,uBAAuB,GAAG,KAAK,CAAC;IACpC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,MAAc,EAAE,EAAE;;QAC3D,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3B,IAAI,uBAAuB;YAAE,OAAO;QACpC,uBAAuB,GAAG,IAAI,CAAC;QAC/B,MAAA,OAAO,CAAC,SAAS,wDACf,+CAA+C,MAAM,qBAAqB;YACxE,gFAAgF,CACnF,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CACb,SAAiB,EACjB,OAAe,EACf,WAAmB,EACnB,YAAoB,EACpB,MAAc,EACd,EAAE;QACF,2EAA2E;QAC3E,0EAA0E;QAC1E,4EAA4E;QAC5E,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE;YACjE,QAAQ,EAAE,WAAW;YACrB,YAAY;YACZ,iBAAiB,EAAE,KAAK;YACxB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QACH,yEAAyE;QACzE,0EAA0E;QAC1E,sEAAsE;QACtE,sCAAsC;QACtC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CACb,qEAAqE;gBACnE,kCAAkC,SAAS,WAAW,SAAS,KAAK;gBACpE,iDAAiD,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE;YAC3D,IAAI,EAAE,OAAO,CAAC,UAAU;YACxB,MAAM;SACP,CAAC,CAAC;QACH,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC9B,cAAc,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;QACtE,CAAC;aAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,cAAc,CAAC,SAAS,EAAE,gDAAgD,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC,CAAA,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3C,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB;;;;;OAKG;IACH,MAAM,eAAe,GAAG,CACtB,KAAc,EACd,IAAY,EACZ,SAAiB,EACV,EAAE;QACT,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,KAAK,CACvB,yCAAyC,IAAI,QAAQ,SAAS,IAAI;YAChE,UAAU,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,gBAAgB,MAAM,KAAK,CAAC,OAAO,IAAI;YAC5E,GAAG,gBAAgB,OAAO,YAAY,uCAAuC;YAC7E,IAAI,IAAI,CAAC,WAAW,CAAC,aAAa,mDAAmD;YACrF,6EAA6E;YAC7E,gFAAgF,CACnF,CAAC;QACD,OAAqC,CAAC,KAAK,GAAG,KAAK,CAAC;QACrD,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;YACzB,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,gBAAgB,KAAK,CAAC,KAAK,EAAE;YACjD,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;QAClB,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,6BAA6B,CAChD,OAAO,CAAC,OAAO,EACf,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,IAAI,CAAC;YACH,MAAM,MAAM,CACV,IAAI,CAAC,SAAS,EACd,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,EAC7B,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,MAAM,CACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC;QACD,gBAAgB,IAAI,CAAC,CAAC;QACtB,MAAA,OAAO,CAAC,UAAU,wDAAG;YACnB,SAAS,EAAE,gBAAgB;YAC3B,KAAK,EAAE,YAAY;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,2BAA2B;IAC3B,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,CACV,IAAI,CAAC,iBAAiB,EACtB,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,CAAC,YAAY,CAAC,CACzB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,eAAe,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC/E,CAAC;IACD,gBAAgB,IAAI,CAAC,CAAC;IACtB,MAAA,OAAO,CAAC,UAAU,wDAAG;QACnB,SAAS,EAAE,gBAAgB;QAC3B,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,IAAI,CAAC,iBAAiB;KAClC,CAAC,CAAC;IAEH,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,YAAY;QAC3B,aAAa,EAAE,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,UAAU;QACxD,MAAM,EAAE,KAAK;QACb,UAAU;KACX,CAAC;AACJ,CAAC,CAAA,CAAC","sourcesContent":["import crypto from 'crypto';\nimport fs from 'fs';\nimport type {IFileStore} from '@_linked/core/interfaces/IFileStore';\nimport {normalizeAccessURL} from './app-assets-store.js';\nimport {\n MANIFEST_CONTENT_TYPE,\n MANIFEST_SOURCE_PATH,\n ENTRY_CACHE_CONTROL,\n releaseObjectKey,\n} from './create-release-manifest.js';\nimport {normalizeReleasePath, resolveExistingPathWithinRoot} from './paths.js';\nimport type {\n LinkedAppReleaseFile,\n LinkedAppReleaseManifest,\n PublishPlan,\n PublishResult,\n} from './types.js';\n\nexport interface PlanReleasePublishOptions {\n appRoot: string;\n manifestPath?: string;\n store: IFileStore;\n}\n\nexport interface LoadedReleaseManifest {\n absolutePath: string;\n manifest: LinkedAppReleaseManifest;\n}\n\nexport interface PublishReleaseOptions extends PlanReleasePublishOptions {\n yes?: boolean;\n onProgress?: (progress: PublishProgress) => void;\n onWarning?: (message: string) => void;\n}\n\nexport interface PublishProgress {\n completed: number;\n total: number;\n objectKey: string;\n}\n\n/**\n * Every object of a release must land under that release's own prefix.\n *\n * The manifest is a plain file on disk: a hand-edited or foreign one can name\n * any `objectKey` it likes, and nothing else catches it — the `accessURL` check\n * only proves the store is the right store, and the stored-location check only\n * proves the store kept the key it was handed. So the key is not merely checked\n * for shape, it is recomputed: it has to be exactly what this release prefix and\n * this source path produce, or a release could overwrite a *different* release.\n */\nconst validateObjectKey = (\n releasePrefix: string,\n file: LinkedAppReleaseFile,\n): void => {\n const sourcePath = normalizeReleasePath(\n String(file.sourcePath ?? ''),\n 'source path',\n );\n const objectKey = normalizeReleasePath(\n String(file.objectKey ?? ''),\n 'object key',\n );\n const expected = releaseObjectKey(releasePrefix, sourcePath);\n if (objectKey !== expected) {\n throw new Error(\n `Release manifest object key is not under the release prefix: ` +\n `\"${file.objectKey}\" (expected \"${expected}\"). Rebuild the release; ` +\n 'a manifest may only upload to its own prefix.',\n );\n }\n};\n\nexport const loadReleaseManifest = (\n appRoot: string,\n manifestPath = MANIFEST_SOURCE_PATH,\n): LoadedReleaseManifest => {\n const absolutePath = resolveExistingPathWithinRoot(appRoot, manifestPath);\n const manifest = JSON.parse(\n fs.readFileSync(absolutePath, 'utf8'),\n ) as LinkedAppReleaseManifest;\n\n if (manifest.schemaVersion !== 1) {\n throw new Error(\n `Unsupported release manifest schema: ${manifest.schemaVersion}`,\n );\n }\n if (manifest.target !== 'web' || manifest.publishable !== true) {\n throw new Error('Only publishable web release manifests may be uploaded');\n }\n if (!Array.isArray(manifest.files) || manifest.files.length === 0) {\n throw new Error('Release manifest contains no files');\n }\n if (!manifest.destination?.releasePrefix) {\n throw new Error('Release manifest has no release prefix');\n }\n const releasePrefix = normalizeReleasePath(\n manifest.destination.releasePrefix,\n 'release prefix',\n );\n for (const file of manifest.files) {\n validateObjectKey(releasePrefix, file);\n }\n\n return {absolutePath, manifest};\n};\n\n/**\n * The manifest records the `accessURL` of the store it was built for. If the\n * configured store now writes somewhere else, the release is not the one that\n * was built and publishing stops. Trailing slashes are not significant.\n */\nconst validateDestination = (\n manifest: LinkedAppReleaseManifest,\n store: IFileStore,\n) => {\n const expected = normalizeAccessURL(manifest.destination.accessURL);\n const actual = normalizeAccessURL(store.accessURL);\n if (expected !== actual) {\n throw new Error(\n `Release destination does not match the manifest (manifest was built for ${\n expected || '(empty)'\n }, the configured appAssets store writes to ${actual || '(empty)'})`,\n );\n }\n};\n\nconst hashBuffer = (content: Buffer): string =>\n crypto.createHash('sha256').update(content).digest('hex');\n\n/**\n * Re-hash the file on disk before uploading. This needs no support from the\n * store and is what actually guarantees the bytes match the manifest.\n */\nconst validateLocalFile = (\n appRoot: string,\n file: LinkedAppReleaseFile,\n): string => {\n // `resolveExistingPathWithinRoot` resolves symlinks, so a source path that\n // points out of the app root through one is refused here too.\n const absolutePath = resolveExistingPathWithinRoot(appRoot, file.sourcePath);\n const content = fs.readFileSync(absolutePath);\n if (content.byteLength !== file.size || hashBuffer(content) !== file.sha256) {\n throw new Error(`Release artifact changed after build: ${file.sourcePath}`);\n }\n return absolutePath;\n};\n\nexport const planReleasePublish = (\n options: PlanReleasePublishOptions,\n): PublishPlan => {\n const {absolutePath, manifest} = loadReleaseManifest(\n options.appRoot,\n options.manifestPath,\n );\n validateDestination(manifest, options.store);\n\n for (const file of manifest.files) {\n validateLocalFile(options.appRoot, file);\n }\n\n return {\n releaseId: manifest.releaseId,\n manifestPath: absolutePath,\n manifestObjectKey: releaseObjectKey(\n manifest.destination.releasePrefix,\n MANIFEST_SOURCE_PATH,\n ),\n destination: manifest.destination,\n files: manifest.files,\n totalBytes: manifest.files.reduce((total, file) => total + file.size, 0),\n };\n};\n\n/**\n * Verify one uploaded object against the hash in the manifest.\n *\n * `statFile` is optional on `IFileStore`, and a store that has it may still\n * report no `sha256` (S3 only returns one when the object carries a checksum).\n * Neither case is a failure: verification is skipped, the key is reported back\n * as unverified, and the caller warns once. An `etag` is deliberately not used\n * as a substitute — it is not a content hash.\n */\nconst verifyUpload = async (\n store: IFileStore,\n objectKey: string,\n expected: {size: number; sha256: string},\n): Promise<'verified' | 'unsupported' | 'no-hash'> => {\n if (typeof store.statFile !== 'function') return 'unsupported';\n\n const stat = await store.statFile(objectKey);\n if (!stat) {\n throw new Error(`Uploaded artifact is missing from the store: ${objectKey}`);\n }\n if (stat.size !== expected.size) {\n throw new Error(\n `Uploaded artifact size does not match the manifest: ${objectKey}`,\n );\n }\n if (!stat.sha256) return 'no-hash';\n if (stat.sha256 !== expected.sha256) {\n throw new Error(`Uploaded artifact verification failed: ${objectKey}`);\n }\n return 'verified';\n};\n\nexport const publishRelease = async (\n options: PublishReleaseOptions,\n): Promise<PublishResult> => {\n const plan = planReleasePublish(options);\n\n if (!options.yes) {\n return {\n releaseId: plan.releaseId,\n uploadedFiles: 0,\n uploadedBytes: 0,\n dryRun: true,\n unverified: [],\n };\n }\n\n const unverified: string[] = [];\n let warnedAboutVerification = false;\n const noteUnverified = (objectKey: string, reason: string) => {\n unverified.push(objectKey);\n if (warnedAboutVerification) return;\n warnedAboutVerification = true;\n options.onWarning?.(\n `Cannot verify uploads against a store hash (${reason}). Files are still ` +\n 'checked against the manifest before upload; the remote copy is not re-checked.',\n );\n };\n\n const upload = async (\n objectKey: string,\n content: Buffer,\n contentType: string,\n cacheControl: string,\n sha256: string,\n ) => {\n // A release key must be exactly the key in the manifest. Disable duplicate\n // renaming and ask the store to preserve the validated relative path; any\n // suffix or sanitisation would break the URL and make verification useless.\n const storedURL = await options.store.saveFile(objectKey, content, {\n mimeType: contentType,\n cacheControl,\n preventDuplicates: false,\n preservePath: true,\n });\n // A release key has to survive the round trip verbatim, or the published\n // URLs in the bundle point at nothing. Some stores sanitise or suffix the\n // path they are given; say so plainly instead of failing later with a\n // confusing \"missing from the store\".\n if (typeof storedURL === 'string' && !storedURL.endsWith(objectKey)) {\n throw new Error(\n `The file store did not store the release object under its own key. ` +\n `Expected a location ending in \"${objectKey}\", got \"${storedURL}\". ` +\n 'A release store must keep object keys verbatim.',\n );\n }\n const outcome = await verifyUpload(options.store, objectKey, {\n size: content.byteLength,\n sha256,\n });\n if (outcome === 'unsupported') {\n noteUnverified(objectKey, 'this store does not implement statFile');\n } else if (outcome === 'no-hash') {\n noteUnverified(objectKey, 'the store reports no sha256 for stored objects');\n }\n };\n\n const totalUploads = plan.files.length + 1;\n let completedUploads = 0;\n\n /**\n * A failure halfway through leaves a partial prefix behind, so the message\n * has to say which object failed, where it was going and how much is already\n * there — and that a re-run finishes the job, since keys are deterministic\n * and every object is overwritten with identical bytes.\n */\n const describeFailure = (\n error: unknown,\n what: string,\n objectKey: string,\n ): Error => {\n const cause = error instanceof Error ? error : new Error(String(error));\n const failure = new Error(\n `Release upload failed while uploading ${what} to \"${objectKey}\" ` +\n `(store ${options.store.accessURL || '(no accessURL)'}): ${cause.message}. ` +\n `${completedUploads} of ${totalUploads} objects were already uploaded under ` +\n `\"${plan.destination.releasePrefix}\"; the release manifest was not uploaded, so the ` +\n 'release is incomplete. Re-running publish-app resumes it — object keys are ' +\n 'deterministic and already-uploaded objects are rewritten with identical bytes.',\n );\n (failure as Error & {cause?: unknown}).cause = cause;\n failure.stack = cause.stack\n ? `${failure.message}\\nCaused by: ${cause.stack}`\n : failure.stack;\n return failure;\n };\n\n for (const file of plan.files) {\n const absolutePath = resolveExistingPathWithinRoot(\n options.appRoot,\n file.sourcePath,\n );\n try {\n await upload(\n file.objectKey,\n fs.readFileSync(absolutePath),\n file.contentType,\n file.cacheControl,\n file.sha256,\n );\n } catch (error) {\n throw describeFailure(error, file.sourcePath, file.objectKey);\n }\n completedUploads += 1;\n options.onProgress?.({\n completed: completedUploads,\n total: totalUploads,\n objectKey: file.objectKey,\n });\n }\n\n // The manifest goes last: its presence under the release prefix is what marks\n // the release as complete.\n const manifestBody = fs.readFileSync(plan.manifestPath);\n try {\n await upload(\n plan.manifestObjectKey,\n manifestBody,\n MANIFEST_CONTENT_TYPE,\n ENTRY_CACHE_CONTROL,\n hashBuffer(manifestBody),\n );\n } catch (error) {\n throw describeFailure(error, 'the release manifest', plan.manifestObjectKey);\n }\n completedUploads += 1;\n options.onProgress?.({\n completed: completedUploads,\n total: totalUploads,\n objectKey: plan.manifestObjectKey,\n });\n\n return {\n releaseId: plan.releaseId,\n uploadedFiles: totalUploads,\n uploadedBytes: plan.totalBytes + manifestBody.byteLength,\n dryRun: false,\n unverified,\n };\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_linked/cli",
|
|
3
|
-
"version": "1.45.
|
|
3
|
+
"version": "1.45.3",
|
|
4
4
|
"packageManager": "npm@11.21.0",
|
|
5
5
|
"description": "Command line tools for the @_linked/* packages and apps",
|
|
6
6
|
"main": "lib/esm/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"LICENSE"
|
|
71
71
|
],
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@_linked/core": "^2.
|
|
73
|
+
"@_linked/core": "^2.28.0",
|
|
74
74
|
"@tailwindcss/vite": "^4.1.4",
|
|
75
75
|
"@types/react": "^19.3.0",
|
|
76
76
|
"@types/react-dom": "^19.3.0",
|