@vendure/cloud-config 0.0.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.
Files changed (70) hide show
  1. package/README.md +298 -0
  2. package/dist/cloud-config.d.ts +55 -0
  3. package/dist/cloud-config.d.ts.map +1 -0
  4. package/dist/cloud-config.js +2 -0
  5. package/dist/cloud-config.js.map +1 -0
  6. package/dist/cloud-dashboard-plugin.d.ts +28 -0
  7. package/dist/cloud-dashboard-plugin.d.ts.map +1 -0
  8. package/dist/cloud-dashboard-plugin.js +91 -0
  9. package/dist/cloud-dashboard-plugin.js.map +1 -0
  10. package/dist/cloud-plugin-declaration.d.ts +12 -0
  11. package/dist/cloud-plugin-declaration.d.ts.map +1 -0
  12. package/dist/cloud-plugin-declaration.js +53 -0
  13. package/dist/cloud-plugin-declaration.js.map +1 -0
  14. package/dist/composition.d.ts +23 -0
  15. package/dist/composition.d.ts.map +1 -0
  16. package/dist/composition.js +147 -0
  17. package/dist/composition.js.map +1 -0
  18. package/dist/define-cloud-config.d.ts +19 -0
  19. package/dist/define-cloud-config.d.ts.map +1 -0
  20. package/dist/define-cloud-config.js +47 -0
  21. package/dist/define-cloud-config.js.map +1 -0
  22. package/dist/errors.d.ts +4 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +13 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +14 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/migrations.d.ts +32 -0
  31. package/dist/migrations.d.ts.map +1 -0
  32. package/dist/migrations.js +250 -0
  33. package/dist/migrations.js.map +1 -0
  34. package/dist/outbound-hosts.d.ts +16 -0
  35. package/dist/outbound-hosts.d.ts.map +1 -0
  36. package/dist/outbound-hosts.js +211 -0
  37. package/dist/outbound-hosts.js.map +1 -0
  38. package/dist/path.d.ts +2 -0
  39. package/dist/path.d.ts.map +1 -0
  40. package/dist/path.js +18 -0
  41. package/dist/path.js.map +1 -0
  42. package/dist/platform-fields.d.ts +71 -0
  43. package/dist/platform-fields.d.ts.map +1 -0
  44. package/dist/platform-fields.js +94 -0
  45. package/dist/platform-fields.js.map +1 -0
  46. package/dist/plugin-attribution.d.ts +27 -0
  47. package/dist/plugin-attribution.d.ts.map +1 -0
  48. package/dist/plugin-attribution.js +97 -0
  49. package/dist/plugin-attribution.js.map +1 -0
  50. package/dist/runtime-backstop.d.ts +8 -0
  51. package/dist/runtime-backstop.d.ts.map +1 -0
  52. package/dist/runtime-backstop.js +20 -0
  53. package/dist/runtime-backstop.js.map +1 -0
  54. package/dist/secrets.d.ts +14 -0
  55. package/dist/secrets.d.ts.map +1 -0
  56. package/dist/secrets.js +109 -0
  57. package/dist/secrets.js.map +1 -0
  58. package/dist/stack.d.ts +3 -0
  59. package/dist/stack.d.ts.map +1 -0
  60. package/dist/stack.js +58 -0
  61. package/dist/stack.js.map +1 -0
  62. package/dist/superseded-plugins.d.ts +19 -0
  63. package/dist/superseded-plugins.d.ts.map +1 -0
  64. package/dist/superseded-plugins.js +41 -0
  65. package/dist/superseded-plugins.js.map +1 -0
  66. package/dist/vendure-extension.d.ts +38 -0
  67. package/dist/vendure-extension.d.ts.map +1 -0
  68. package/dist/vendure-extension.js +111 -0
  69. package/dist/vendure-extension.js.map +1 -0
  70. package/package.json +64 -0
@@ -0,0 +1,16 @@
1
+ import type { VendureExtension } from "./vendure-extension.js";
2
+ export interface UndeclaredHostReport {
3
+ readonly host: string;
4
+ readonly extensionId: string | undefined;
5
+ readonly declaredHosts: readonly string[];
6
+ readonly message: string;
7
+ }
8
+ export type UndeclaredHostReporter = (report: UndeclaredHostReport) => void;
9
+ export interface OutboundHostCheckOptions {
10
+ readonly extensions: readonly VendureExtension[];
11
+ readonly report?: UndeclaredHostReporter;
12
+ }
13
+ type Uninstall = () => void;
14
+ export declare function installOutboundHostCheck(options: OutboundHostCheckOptions): Uninstall;
15
+ export {};
16
+ //# sourceMappingURL=outbound-hosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-hosts.d.ts","sourceRoot":"","sources":["../src/outbound-hosts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAa/D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAE5E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAGjD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAmKD,KAAK,SAAS,GAAG,MAAM,IAAI,CAAC;AAQ5B,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,wBAAwB,GAAG,SAAS,CAwErF"}
@@ -0,0 +1,211 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { stackFrameFiles } from "./stack.js";
5
+ // A hostname as declared and as dialled, compared the way DNS does: case-insensitively, and
6
+ // ignoring the trailing dot of a fully qualified name. Matching is exact — there is no
7
+ // wildcard and no implied subdomain, because "declared api.acme.com" allowing
8
+ // "evil.api.acme.com" would be a surprise in the direction that matters.
9
+ function normalizeHost(host) {
10
+ return host.trim().toLowerCase().replace(/\.$/, "");
11
+ }
12
+ // How far down the stack to look for an extension. A plugin calling out through a client
13
+ // library sits under however many frames that library adds, and 30 covers the ones measured
14
+ // here with room to spare; past that the call is left unattributed rather than searched for
15
+ // indefinitely, because reading a deep stack is the expensive part of this check.
16
+ const ATTRIBUTION_FRAME_LIMIT = 30;
17
+ // This file's own directory, so its frames can be skipped when reading the stack.
18
+ const selfDirectory = dirname(fileURLToPath(import.meta.url));
19
+ // Which extension made the call, read from the stack. Same technique as
20
+ // `defineVendureExtension`'s declaring-file trap and for the same reason: our own
21
+ // `prepareStackTrace` is installed so that `--enable-source-maps` cannot rewrite the frames
22
+ // back to `.ts` paths the image does not ship.
23
+ function attribute(state, boundary) {
24
+ for (const path of stackFrameFiles(boundary, ATTRIBUTION_FRAME_LIMIT)) {
25
+ if (path === undefined || path.startsWith(selfDirectory))
26
+ continue;
27
+ const match = state.attributions.find((candidate) => path === candidate.directory || path.startsWith(candidate.directory + sep));
28
+ if (match)
29
+ return match.extension;
30
+ }
31
+ return undefined;
32
+ }
33
+ // The message ADR-0028 specifies, verbatim in shape:
34
+ //
35
+ // Extension acme.erp attempted outbound access to api.other-erp.example.
36
+ // Declared outbound hosts: erp.acme.com
37
+ function formatReport(host, extension) {
38
+ const declaredHosts = extension ? [...extension.outboundHosts] : [];
39
+ const subject = extension ? `Extension ${extension.id}` : "An extension";
40
+ const declared = declaredHosts.length > 0
41
+ ? `Declared outbound hosts: ${declaredHosts.join(", ")}`
42
+ : "It declares no outbound hosts.";
43
+ return {
44
+ host,
45
+ extensionId: extension?.id,
46
+ declaredHosts,
47
+ message: `Vendure Cloud: ${subject} attempted outbound access to ${host}.\n${declared}\n This is reported, not blocked. Add the host to the extension's outboundHosts to silence it.`,
48
+ };
49
+ }
50
+ function check(state, rawHost) {
51
+ if (rawHost === undefined || rawHost === "")
52
+ return;
53
+ const host = normalizeHost(rawHost);
54
+ // The fast path, and the reason an allowed call costs one Set lookup rather than a captured
55
+ // stack. Its cost is that an extension reaching a host another extension declared is not
56
+ // reported. That is accepted: this is a hygiene control, and one acceptance criterion on
57
+ // CLO-412 is that an allowed call carries no measurable per-request cost. Attributing every
58
+ // call would mean capturing a stack on every request to satisfy a control that a single
59
+ // line of code can bypass anyway.
60
+ if (state.declaredAnywhere.has(host))
61
+ return;
62
+ const extension = attribute(state, check);
63
+ // Nothing on the stack belongs to an extension, so there is no declaration this call is
64
+ // measured against. Vendure core's own traffic, the database driver's, and a library called
65
+ // from platform code all land here, and reporting them would drown the signal.
66
+ if (extension === undefined)
67
+ return;
68
+ // The extension declared nothing, so it opted out of the check. CLO-412 records the
69
+ // reasoning: reading "declared nothing" as "may reach nothing" would report against every
70
+ // extension in existence today, and a report that fires on everything is not read.
71
+ if (extension.outboundHosts.length === 0)
72
+ return;
73
+ const seen = `${extension.id}\u0000${host}`;
74
+ if (state.reported.has(seen))
75
+ return;
76
+ state.reported.add(seen);
77
+ state.report(formatReport(host, extension));
78
+ }
79
+ function hostFromUrl(value) {
80
+ try {
81
+ return new URL(value).hostname;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ // `fetch` takes a string, a URL, or a Request. Reading `.url` off the third rather than
88
+ // naming `Request`, because the DOM lib is not in this package's types and the shape is the
89
+ // only part that matters.
90
+ function hostFromFetchInput(input) {
91
+ if (typeof input === "string")
92
+ return hostFromUrl(input);
93
+ if (input instanceof URL)
94
+ return input.hostname;
95
+ if (typeof input === "object" && input !== null) {
96
+ const { url } = input;
97
+ if (typeof url === "string")
98
+ return hostFromUrl(url);
99
+ }
100
+ return undefined;
101
+ }
102
+ function hostFromRequestArguments(args, depth = 0) {
103
+ for (const argument of args) {
104
+ // `Socket.prototype.connect` is reached with Node's own normalised argument list, which is
105
+ // an array holding the options object and the callback, so the options are one level down
106
+ // rather than in the position a direct caller would put them.
107
+ //
108
+ // Bounded at the single level Node actually produces, because this runs on the caller's
109
+ // arguments before Node has validated them. Anything deeper is a caller passing something
110
+ // strange, and a self-referencing array would otherwise exhaust the stack inside a check
111
+ // whose whole job is to watch without getting in the way.
112
+ if (Array.isArray(argument)) {
113
+ if (depth >= 1)
114
+ continue;
115
+ const nested = hostFromRequestArguments(argument, depth + 1);
116
+ if (nested !== undefined)
117
+ return nested;
118
+ continue;
119
+ }
120
+ if (typeof argument === "string") {
121
+ const fromUrl = hostFromUrl(argument);
122
+ if (fromUrl !== undefined)
123
+ return fromUrl;
124
+ continue;
125
+ }
126
+ if (argument instanceof URL)
127
+ return argument.hostname;
128
+ if (typeof argument === "object" && argument !== null) {
129
+ const { hostname, host } = argument;
130
+ if (typeof hostname === "string")
131
+ return hostname;
132
+ // `host` may carry a port; the declaration is of hosts, so the port is dropped.
133
+ if (typeof host === "string")
134
+ return host.split(":")[0];
135
+ }
136
+ }
137
+ return undefined;
138
+ }
139
+ // Installed by the platform entrypoint, and by `vendure dev`, so the check behaves the same
140
+ // in both. Returns the function that undoes it, which is what the tests use and what an
141
+ // entrypoint would call on shutdown.
142
+ //
143
+ // Nothing is patched at all when no extension declares a host, so an application that has not
144
+ // adopted the field pays nothing and cannot be affected by a bug in here.
145
+ export function installOutboundHostCheck(options) {
146
+ const declaring = options.extensions.filter((extension) => extension.outboundHosts.length > 0);
147
+ if (declaring.length === 0)
148
+ return () => undefined;
149
+ const declaredAnywhere = new Set();
150
+ for (const extension of declaring) {
151
+ for (const host of extension.outboundHosts)
152
+ declaredAnywhere.add(normalizeHost(host));
153
+ }
154
+ const attributions = options.extensions
155
+ .flatMap((extension) => {
156
+ if (extension.declaredIn === undefined)
157
+ return [];
158
+ return [{ directory: dirname(extension.declaredIn), extension }];
159
+ })
160
+ .sort((left, right) => right.directory.length - left.directory.length);
161
+ const state = {
162
+ declaredAnywhere,
163
+ attributions,
164
+ report: options.report ?? ((report) => process.stderr.write(`${report.message}\n`)),
165
+ reported: new Set(),
166
+ };
167
+ const undo = [];
168
+ const originalFetch = globalThis.fetch;
169
+ if (typeof originalFetch === "function") {
170
+ globalThis.fetch = function patchedFetch(input, init) {
171
+ check(state, hostFromFetchInput(input));
172
+ return originalFetch.call(this, input, init);
173
+ };
174
+ undo.push(() => {
175
+ globalThis.fetch = originalFetch;
176
+ });
177
+ }
178
+ // Everything that is not `fetch` is caught one level lower, at the socket, because that is
179
+ // the only interception point that behaves the same under both module systems — which is an
180
+ // acceptance criterion on CLO-412 rather than a preference.
181
+ //
182
+ // Replacing `net.connect` and `http.request` on the module object does not work. An ES
183
+ // module's named import is a snapshot of the binding taken when the module was linked, so
184
+ // `import { connect } from "node:net"` keeps calling the original however the module object
185
+ // is rewritten afterwards, while a CommonJS caller doing `require("node:net").connect` sees
186
+ // the replacement. That is the same control behaving differently in the two module systems,
187
+ // and it silently covers only half of them.
188
+ //
189
+ // `net.Socket.prototype.connect` has no such split. `net.connect`, `net.createConnection`,
190
+ // `http.request`, `https.request` and the global `fetch` all end at a Socket, and the method
191
+ // is looked up on the prototype at call time whoever reached it.
192
+ const require = createRequire(import.meta.url);
193
+ const net = require("node:net");
194
+ const socketPrototype = net.Socket.prototype;
195
+ const originalConnect = socketPrototype.connect;
196
+ if (typeof originalConnect === "function") {
197
+ socketPrototype.connect = function patchedConnect(...args) {
198
+ check(state, hostFromRequestArguments(args));
199
+ return originalConnect.apply(this, args);
200
+ };
201
+ undo.push(() => {
202
+ socketPrototype.connect = originalConnect;
203
+ });
204
+ }
205
+ return () => {
206
+ for (const step of undo)
207
+ step();
208
+ undo.length = 0;
209
+ };
210
+ }
211
+ //# sourceMappingURL=outbound-hosts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-hosts.js","sourceRoot":"","sources":["../src/outbound-hosts.ts"],"names":[],"mappings":"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"}
package/dist/path.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function readPath(source: unknown, path: string): unknown;
2
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAQA,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/D"}
package/dist/path.js ADDED
@@ -0,0 +1,18 @@
1
+ // Walks a dot-separated path over an unknown value, returning undefined at anything that is
2
+ // not a traversable object rather than throwing.
3
+ //
4
+ // Every check over the platform-owned fields reads through this one function — the runtime
5
+ // backstop, the forward assertion and the attribution diff. They must agree on what "this
6
+ // field is set" means: a second implementation that treated a null option object as a miss,
7
+ // or that stopped at a prototype property, would let one check pass a config another
8
+ // rejects, and the boot error would name a field the customer's file does not set.
9
+ export function readPath(source, path) {
10
+ let current = source;
11
+ for (const segment of path.split(".")) {
12
+ if (typeof current !== "object" || current === null)
13
+ return undefined;
14
+ current = current[segment];
15
+ }
16
+ return current;
17
+ }
18
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,iDAAiD;AACjD,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,4FAA4F;AAC5F,qFAAqF;AACrF,mFAAmF;AACnF,MAAM,UAAU,QAAQ,CAAC,MAAe,EAAE,IAAY;IACpD,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,71 @@
1
+ export interface PlatformOwnedField {
2
+ path: string;
3
+ reason: string;
4
+ writtenBy?: {
5
+ plugin: string;
6
+ replacement: string;
7
+ };
8
+ }
9
+ declare const FIELD_SPECS: readonly [{
10
+ readonly path: "dbConnectionOptions";
11
+ readonly reason: "The database connection is supplied by Vendure Cloud, including credential rotation. Remove dbConnectionOptions from your config.";
12
+ }, {
13
+ readonly path: "logger";
14
+ readonly reason: "Logging is supplied by Vendure Cloud so output reaches the platform log pipeline from the server, the worker and migrations alike. To forward logs elsewhere, use a log drain rather than replacing the logger.";
15
+ }, {
16
+ readonly path: "apiOptions.port";
17
+ readonly reason: "The port is supplied by Vendure Cloud and must match what the load balancer routes to. Remove apiOptions.port.";
18
+ }, {
19
+ readonly path: "apiOptions.hostname";
20
+ readonly reason: "The hostname is supplied by Vendure Cloud. Remove apiOptions.hostname.";
21
+ }, {
22
+ readonly path: "authOptions.superadminCredentials";
23
+ readonly reason: "Vendure Cloud generates the superadmin credential for each environment and surfaces it once. Remove authOptions.superadminCredentials.";
24
+ }, {
25
+ readonly path: "authOptions.sessionCacheStrategy";
26
+ readonly reason: "The session cache is backed by platform-managed infrastructure. Remove authOptions.sessionCacheStrategy.";
27
+ }, {
28
+ readonly path: "authOptions.sessionCacheTTL";
29
+ readonly reason: "The session cache TTL is the dominant lever on platform cache cost, so it is a platform decision. Remove authOptions.sessionCacheTTL.";
30
+ }, {
31
+ readonly path: "systemOptions.cacheStrategy";
32
+ readonly reason: "The cache is supplied by Vendure Cloud. Remove systemOptions.cacheStrategy, and remove RedisCachePlugin from your plugins array.";
33
+ readonly writtenBy: {
34
+ readonly plugin: "RedisCachePlugin";
35
+ readonly replacement: "the platform cache strategy, which is injected automatically";
36
+ };
37
+ }, {
38
+ readonly path: "systemOptions.healthChecks";
39
+ readonly reason: "The health check is shallow by design: it must not touch the database, because a target group that marks every server unhealthy during a database blip causes ECS to replace all of them at once. Remove systemOptions.healthChecks.";
40
+ }, {
41
+ readonly path: "assetOptions.assetStorageStrategy";
42
+ readonly reason: "Asset storage is supplied by Vendure Cloud. Remove assetOptions.assetStorageStrategy, and use CloudAssetServerPlugin in place of AssetServerPlugin.";
43
+ readonly writtenBy: {
44
+ readonly plugin: "AssetServerPlugin";
45
+ readonly replacement: "CloudAssetServerPlugin";
46
+ };
47
+ }, {
48
+ readonly path: "jobQueueOptions.jobQueueStrategy";
49
+ readonly reason: "The job queue is supplied by Vendure Cloud. Remove jobQueueOptions.jobQueueStrategy, and remove BullMQJobQueuePlugin from your plugins array.";
50
+ readonly writtenBy: {
51
+ readonly plugin: "BullMQJobQueuePlugin";
52
+ readonly replacement: "the platform job queue strategy, which is injected automatically";
53
+ };
54
+ }, {
55
+ readonly path: "schedulerOptions.schedulerStrategy";
56
+ readonly reason: "Scheduled tasks are driven by Vendure Cloud, because a worker scaled to zero runs none. Remove schedulerOptions.schedulerStrategy, and remove DefaultSchedulerPlugin from your plugins array.";
57
+ readonly writtenBy: {
58
+ readonly plugin: "DefaultSchedulerPlugin";
59
+ readonly replacement: "the platform scheduler strategy, which is injected automatically";
60
+ };
61
+ }, {
62
+ readonly path: "schedulerOptions.runTasksInWorkerOnly";
63
+ readonly reason: "Vendure Cloud pins runTasksInWorkerOnly to false so the server ticks when the worker is asleep. Remove schedulerOptions.runTasksInWorkerOnly.";
64
+ }];
65
+ export declare const PLATFORM_OWNED_FIELDS: readonly PlatformOwnedField[];
66
+ export type PlatformOwnedFieldSpec = (typeof FIELD_SPECS)[number];
67
+ export type PlatformOwnedFieldPath = PlatformOwnedFieldSpec["path"];
68
+ export declare function findPlatformOwnedField(path: string): PlatformOwnedField | undefined;
69
+ export declare function formatPlatformFieldDetail(fields: readonly PlatformOwnedField[]): string;
70
+ export {};
71
+ //# sourceMappingURL=platform-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-fields.d.ts","sourceRoot":"","sources":["../src/platform-fields.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,kBAAkB;IAGjC,IAAI,EAAE,MAAM,CAAC;IAIb,MAAM,EAAE,MAAM,CAAC;IAIf,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAMD,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiFiC,CAAC;AAEnD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAAgB,CAAC;AAEhF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAEpE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEnF;AAKD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,CAEvF"}
@@ -0,0 +1,94 @@
1
+ // The canonical list of VendureConfig fields the platform owns, from ADR-0017's
2
+ // "Platform-owned" category plus the two additions ADR-0023 assigned to it and the
3
+ // `healthChecks` forcing CLO-355 delivers.
4
+ //
5
+ // This list is the single source for four things that must never disagree: the Cloud Config
6
+ // type's branded keys, the runtime backstop, the post-composition assertion, and ADR-0044's
7
+ // converse check. A field added to the type but not to this list is rejected at build time
8
+ // and accepted at boot, so a config assembled at runtime would set it and no check would
9
+ // object.
10
+ // Literal-typed on purpose: cloud-config.ts derives each branded field's explanation from
11
+ // these exact strings, so the build-time message and the runtime message cannot drift.
12
+ // The cost is that an entry without `writtenBy` genuinely lacks the property, so runtime
13
+ // callers iterate PLATFORM_OWNED_FIELDS below rather than this.
14
+ const FIELD_SPECS = [
15
+ {
16
+ path: "dbConnectionOptions",
17
+ reason: "The database connection is supplied by Vendure Cloud, including credential rotation. Remove dbConnectionOptions from your config.",
18
+ },
19
+ {
20
+ path: "logger",
21
+ reason: "Logging is supplied by Vendure Cloud so output reaches the platform log pipeline from the server, the worker and migrations alike. To forward logs elsewhere, use a log drain rather than replacing the logger.",
22
+ },
23
+ {
24
+ path: "apiOptions.port",
25
+ reason: "The port is supplied by Vendure Cloud and must match what the load balancer routes to. Remove apiOptions.port.",
26
+ },
27
+ {
28
+ path: "apiOptions.hostname",
29
+ reason: "The hostname is supplied by Vendure Cloud. Remove apiOptions.hostname.",
30
+ },
31
+ {
32
+ path: "authOptions.superadminCredentials",
33
+ reason: "Vendure Cloud generates the superadmin credential for each environment and surfaces it once. Remove authOptions.superadminCredentials.",
34
+ },
35
+ {
36
+ path: "authOptions.sessionCacheStrategy",
37
+ reason: "The session cache is backed by platform-managed infrastructure. Remove authOptions.sessionCacheStrategy.",
38
+ },
39
+ {
40
+ path: "authOptions.sessionCacheTTL",
41
+ reason: "The session cache TTL is the dominant lever on platform cache cost, so it is a platform decision. Remove authOptions.sessionCacheTTL.",
42
+ },
43
+ {
44
+ path: "systemOptions.cacheStrategy",
45
+ reason: "The cache is supplied by Vendure Cloud. Remove systemOptions.cacheStrategy, and remove RedisCachePlugin from your plugins array.",
46
+ writtenBy: {
47
+ plugin: "RedisCachePlugin",
48
+ replacement: "the platform cache strategy, which is injected automatically",
49
+ },
50
+ },
51
+ {
52
+ path: "systemOptions.healthChecks",
53
+ reason: "The health check is shallow by design: it must not touch the database, because a target group that marks every server unhealthy during a database blip causes ECS to replace all of them at once. Remove systemOptions.healthChecks.",
54
+ },
55
+ {
56
+ path: "assetOptions.assetStorageStrategy",
57
+ reason: "Asset storage is supplied by Vendure Cloud. Remove assetOptions.assetStorageStrategy, and use CloudAssetServerPlugin in place of AssetServerPlugin.",
58
+ writtenBy: {
59
+ plugin: "AssetServerPlugin",
60
+ replacement: "CloudAssetServerPlugin",
61
+ },
62
+ },
63
+ {
64
+ path: "jobQueueOptions.jobQueueStrategy",
65
+ reason: "The job queue is supplied by Vendure Cloud. Remove jobQueueOptions.jobQueueStrategy, and remove BullMQJobQueuePlugin from your plugins array.",
66
+ writtenBy: {
67
+ plugin: "BullMQJobQueuePlugin",
68
+ replacement: "the platform job queue strategy, which is injected automatically",
69
+ },
70
+ },
71
+ {
72
+ path: "schedulerOptions.schedulerStrategy",
73
+ reason: "Scheduled tasks are driven by Vendure Cloud, because a worker scaled to zero runs none. Remove schedulerOptions.schedulerStrategy, and remove DefaultSchedulerPlugin from your plugins array.",
74
+ writtenBy: {
75
+ plugin: "DefaultSchedulerPlugin",
76
+ replacement: "the platform scheduler strategy, which is injected automatically",
77
+ },
78
+ },
79
+ {
80
+ path: "schedulerOptions.runTasksInWorkerOnly",
81
+ reason: "Vendure Cloud pins runTasksInWorkerOnly to false so the server ticks when the worker is asleep. Remove schedulerOptions.runTasksInWorkerOnly.",
82
+ },
83
+ ];
84
+ export const PLATFORM_OWNED_FIELDS = FIELD_SPECS;
85
+ export function findPlatformOwnedField(path) {
86
+ return PLATFORM_OWNED_FIELDS.find((field) => field.path === path);
87
+ }
88
+ // The shared body of the two boot-time failures. Both name the offending fields the same
89
+ // way, and a customer meeting them one at a time during a migration should not find the
90
+ // two laid out differently.
91
+ export function formatPlatformFieldDetail(fields) {
92
+ return fields.map((field) => ` ${field.path}\n ${field.reason}`).join("\n\n");
93
+ }
94
+ //# sourceMappingURL=platform-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-fields.js","sourceRoot":"","sources":["../src/platform-fields.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,2CAA2C;AAC3C,EAAE;AACF,4FAA4F;AAC5F,4FAA4F;AAC5F,2FAA2F;AAC3F,yFAAyF;AACzF,UAAU;AAgBV,0FAA0F;AAC1F,uFAAuF;AACvF,yFAAyF;AACzF,gEAAgE;AAChE,MAAM,WAAW,GAAG;IAClB;QACE,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EACJ,mIAAmI;KACtI;IACD;QACE,IAAI,EAAE,QAAQ;QACd,MAAM,EACJ,iNAAiN;KACpN;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,MAAM,EACJ,gHAAgH;KACnH;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,wEAAwE;KACjF;IACD;QACE,IAAI,EAAE,mCAAmC;QACzC,MAAM,EACJ,wIAAwI;KAC3I;IACD;QACE,IAAI,EAAE,kCAAkC;QACxC,MAAM,EACJ,0GAA0G;KAC7G;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,MAAM,EACJ,uIAAuI;KAC1I;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,MAAM,EACJ,kIAAkI;QACpI,SAAS,EAAE;YACT,MAAM,EAAE,kBAAkB;YAC1B,WAAW,EAAE,8DAA8D;SAC5E;KACF;IACD;QACE,IAAI,EAAE,4BAA4B;QAClC,MAAM,EACJ,sOAAsO;KACzO;IACD;QACE,IAAI,EAAE,mCAAmC;QACzC,MAAM,EACJ,qJAAqJ;QACvJ,SAAS,EAAE;YACT,MAAM,EAAE,mBAAmB;YAC3B,WAAW,EAAE,wBAAwB;SACtC;KACF;IACD;QACE,IAAI,EAAE,kCAAkC;QACxC,MAAM,EACJ,+IAA+I;QACjJ,SAAS,EAAE;YACT,MAAM,EAAE,sBAAsB;YAC9B,WAAW,EAAE,kEAAkE;SAChF;KACF;IACD;QACE,IAAI,EAAE,oCAAoC;QAC1C,MAAM,EACJ,+LAA+L;QACjM,SAAS,EAAE;YACT,MAAM,EAAE,wBAAwB;YAChC,WAAW,EAAE,kEAAkE;SAChF;KACF;IACD;QACE,IAAI,EAAE,uCAAuC;QAC7C,MAAM,EACJ,+IAA+I;KAClJ;CAC+C,CAAC;AAEnD,MAAM,CAAC,MAAM,qBAAqB,GAAkC,WAAW,CAAC;AAKhF,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,yFAAyF;AACzF,wFAAwF;AACxF,4BAA4B;AAC5B,MAAM,UAAU,yBAAyB,CAAC,MAAqC;IAC7E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,27 @@
1
+ import "reflect-metadata";
2
+ import { CloudConfigError } from "./errors.js";
3
+ import { type PlatformOwnedField } from "./platform-fields.js";
4
+ export declare const PLUGIN_CONFIGURATION_METADATA_KEY = "configuration";
5
+ export type PluginConstructor = abstract new (...args: never[]) => unknown;
6
+ export type PluginLike = PluginConstructor | {
7
+ readonly module: PluginConstructor;
8
+ };
9
+ export interface PlatformFieldWrite {
10
+ plugin: string;
11
+ field: PlatformOwnedField;
12
+ extension?: string;
13
+ }
14
+ export type ExtensionOfPlugin = ReadonlyMap<PluginLike, {
15
+ readonly id: string;
16
+ }>;
17
+ export declare function pluginName(plugin: PluginLike): string;
18
+ export interface PendingPlatformFieldWrites {
19
+ readonly writes: readonly PlatformFieldWrite[];
20
+ }
21
+ export declare function recordPlatformFieldWrites(plugins: readonly PluginLike[], extensionOf?: ExtensionOfPlugin): PendingPlatformFieldWrites;
22
+ export declare class PlatformFieldSetByPluginError extends CloudConfigError {
23
+ readonly writes: readonly PlatformFieldWrite[];
24
+ constructor(writes: readonly PlatformFieldWrite[]);
25
+ }
26
+ export declare function assertNoCustomerPluginWroteAPlatformField(writes: readonly PlatformFieldWrite[], cloudPluginNames: readonly string[]): void;
27
+ //# sourceMappingURL=plugin-attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-attribution.d.ts","sourceRoot":"","sources":["../src/plugin-attribution.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAWtF,eAAO,MAAM,iCAAiC,kBAAkB,CAAC;AAIjE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;AAC3E,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAEpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,kBAAkB,CAAC;IAI1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,UAAU,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AASjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAIrD;AAsBD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAChD;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,WAAW,GAAE,iBAA6B,GACzC,0BAA0B,CA0C5B;AAYD,qBAAa,6BAA8B,SAAQ,gBAAgB;IACjE,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;gBAEnC,MAAM,EAAE,SAAS,kBAAkB,EAAE;CAiBlD;AAED,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAClC,IAAI,CAKN"}
@@ -0,0 +1,97 @@
1
+ import "reflect-metadata";
2
+ import { CloudConfigError } from "./errors.js";
3
+ import { readPath } from "./path.js";
4
+ import { PLATFORM_OWNED_FIELDS } from "./platform-fields.js";
5
+ // Core reads a plugin's configuration function out of this metadata key
6
+ // (`PLUGIN_METADATA.CONFIGURATION` in @vendure/core). It is restated rather than imported so
7
+ // this package needs no runtime dependency on core. That keeps it a thin interface package,
8
+ // and it means the package cannot itself load a second copy of `@vendure/core` — the failure
9
+ // ADR-0043 measured, where the application's `Logger` and the platform's are different
10
+ // classes and every log line goes to the one nobody reads.
11
+ //
12
+ // A test pins this string to core's real value, so a rename there fails our build rather
13
+ // than silently disabling attribution.
14
+ export const PLUGIN_CONFIGURATION_METADATA_KEY = "configuration";
15
+ function metadataTarget(plugin) {
16
+ if (typeof plugin === "object" && plugin !== null && "module" in plugin) {
17
+ return plugin.module;
18
+ }
19
+ return plugin;
20
+ }
21
+ export function pluginName(plugin) {
22
+ const target = metadataTarget(plugin);
23
+ const named = target.name;
24
+ return typeof named === "string" && named.length > 0 ? named : "an anonymous plugin";
25
+ }
26
+ function snapshotPlatformFields(config) {
27
+ return new Map(PLATFORM_OWNED_FIELDS.map((field) => [field.path, readPath(config, field.path)]));
28
+ }
29
+ export function recordPlatformFieldWrites(plugins, extensionOf = new Map()) {
30
+ const writes = [];
31
+ // Guards against wrapping one configuration function twice. A plugins array holding the
32
+ // same reference twice, or a class and a DynamicModule around that same class, resolves to
33
+ // one metadata target: without this, the second pass reads the first wrapper back as the
34
+ // "original" and wraps it again, so every execution of that plugin records its writes twice
35
+ // and the boot message reports writes the plugin did not make.
36
+ const wrappedTargets = new WeakSet();
37
+ for (const plugin of plugins) {
38
+ const target = metadataTarget(plugin);
39
+ if (wrappedTargets.has(target))
40
+ continue;
41
+ const original = Reflect.getMetadata(PLUGIN_CONFIGURATION_METADATA_KEY, target);
42
+ if (typeof original !== "function")
43
+ continue;
44
+ const name = pluginName(plugin);
45
+ // The extension is read at the array entry that gets wrapped. For a plugin appearing
46
+ // more than once, that is the first entry: core keeps the configuration function on the
47
+ // class, so one wrapper covers every execution and there is no finer attribution to be
48
+ // had.
49
+ const extension = extensionOf.get(plugin)?.id;
50
+ const configure = original;
51
+ const wrapped = async (config) => {
52
+ const before = snapshotPlatformFields(config);
53
+ const result = await configure(config);
54
+ const after = snapshotPlatformFields(result ?? config);
55
+ for (const field of PLATFORM_OWNED_FIELDS) {
56
+ if (!Object.is(before.get(field.path), after.get(field.path))) {
57
+ writes.push({ plugin: name, field, extension });
58
+ }
59
+ }
60
+ return result;
61
+ };
62
+ Reflect.defineMetadata(PLUGIN_CONFIGURATION_METADATA_KEY, wrapped, target);
63
+ wrappedTargets.add(target);
64
+ }
65
+ return { writes };
66
+ }
67
+ // ADR-0044's converse check. Cloud Plugins run last, so a customer plugin that wrote a
68
+ // platform-owned field has already had its value overwritten. The customer set something,
69
+ // saw no error, and is running with a different value than they wrote.
70
+ //
71
+ // The message names the plugin and the Cloud Plugin that replaces it, and the Vendure
72
+ // Extension the plugin came from where one declared it. Naming only the offending plugin
73
+ // leaves the customer knowing a line is wrong and not what to write instead, which turns a
74
+ // one-line edit into a support ticket. The extension is named for the same reason: a plugin
75
+ // declared inside one is not written in the config file at all, so its class name does not
76
+ // say which line to look at.
77
+ export class PlatformFieldSetByPluginError extends CloudConfigError {
78
+ writes;
79
+ constructor(writes) {
80
+ const detail = writes
81
+ .map(({ plugin, field, extension }) => {
82
+ const replacement = field.writtenBy?.replacement ?? "the platform equivalent, which is supplied for you";
83
+ const offender = extension === undefined ? plugin : `${plugin}, in the Vendure Extension "${extension}",`;
84
+ return ` ${offender} set ${field.path}, which Vendure Cloud supplies.\n Use ${replacement}.`;
85
+ })
86
+ .join("\n\n");
87
+ super(`Vendure Cloud: ${writes.length} plugin write${writes.length === 1 ? "" : "s"} to platform-owned configuration would be discarded. Refusing to boot.\n\n${detail}\n`);
88
+ this.writes = writes;
89
+ }
90
+ }
91
+ export function assertNoCustomerPluginWroteAPlatformField(writes, cloudPluginNames) {
92
+ const offending = writes.filter((write) => !cloudPluginNames.includes(write.plugin));
93
+ if (offending.length > 0) {
94
+ throw new PlatformFieldSetByPluginError(offending);
95
+ }
96
+ }
97
+ //# sourceMappingURL=plugin-attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-attribution.js","sourceRoot":"","sources":["../src/plugin-attribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { CloudConfigError } from "./errors.js";
2
+ import { type PlatformOwnedField } from "./platform-fields.js";
3
+ export declare function listPlatformOwnedFieldsSetIn(config: unknown): PlatformOwnedField[];
4
+ export declare class PlatformFieldSetByConfigError extends CloudConfigError {
5
+ readonly fields: readonly PlatformOwnedField[];
6
+ constructor(fields: readonly PlatformOwnedField[]);
7
+ }
8
+ //# sourceMappingURL=runtime-backstop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-backstop.d.ts","sourceRoot":"","sources":["../src/runtime-backstop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAQ9B,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAElF;AAED,qBAAa,6BAA8B,SAAQ,gBAAgB;IACjE,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;gBAEnC,MAAM,EAAE,SAAS,kBAAkB,EAAE;CASlD"}
@@ -0,0 +1,20 @@
1
+ import { CloudConfigError } from "./errors.js";
2
+ import { readPath } from "./path.js";
3
+ import { PLATFORM_OWNED_FIELDS, formatPlatformFieldDetail, } from "./platform-fields.js";
4
+ // The build-time guarantee holds for a config written as an object literal and is absent
5
+ // for one assembled programmatically, which no type can reach. ADR-0043 requires this
6
+ // check so that second case fails loudly at boot rather than silently at runtime.
7
+ // A key present but undefined is not a violation: that is what spreading an optional
8
+ // object produces, and the customer expressed no intent. Only a set value counts.
9
+ export function listPlatformOwnedFieldsSetIn(config) {
10
+ return PLATFORM_OWNED_FIELDS.filter((field) => readPath(config, field.path) !== undefined);
11
+ }
12
+ export class PlatformFieldSetByConfigError extends CloudConfigError {
13
+ fields;
14
+ constructor(fields) {
15
+ const detail = formatPlatformFieldDetail(fields);
16
+ super(`Vendure Cloud: this configuration sets ${fields.length} field${fields.length === 1 ? "" : "s"} that the platform supplies.\n\n${detail}\n`);
17
+ this.fields = fields;
18
+ }
19
+ }
20
+ //# sourceMappingURL=runtime-backstop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-backstop.js","sourceRoot":"","sources":["../src/runtime-backstop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,GAE1B,MAAM,sBAAsB,CAAC;AAE9B,yFAAyF;AACzF,sFAAsF;AACtF,kFAAkF;AAElF,qFAAqF;AACrF,kFAAkF;AAClF,MAAM,UAAU,4BAA4B,CAAC,MAAe;IAC1D,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC;AAC7F,CAAC;AAED,MAAM,OAAO,6BAA8B,SAAQ,gBAAgB;IACxD,MAAM,CAAgC;IAE/C,YAAY,MAAqC;QAC/C,MAAM,MAAM,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC;QACjD,KAAK,CACH,0CAA0C,MAAM,CAAC,MAAM,SACrD,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC7B,mCAAmC,MAAM,IAAI,CAC9C,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}