@bleedingdev/modern-js-surface-resolution 0.0.0 → 3.9.0-ultramodern.5
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/LICENSE +21 -0
- package/dist/cjs/surface-resolution/env-static-provider.js +318 -0
- package/dist/cjs/surface-resolution/index.js +69 -0
- package/dist/cjs/surface-resolution/surface-ref.js +132 -0
- package/dist/cjs/surface-resolution/types.js +18 -0
- package/dist/cjs/surface-resolution/validation.js +202 -0
- package/dist/esm/surface-resolution/env-static-provider.mjs +274 -0
- package/dist/esm/surface-resolution/index.mjs +3 -0
- package/dist/esm/surface-resolution/surface-ref.mjs +88 -0
- package/dist/esm/surface-resolution/types.mjs +0 -0
- package/dist/esm/surface-resolution/validation.mjs +155 -0
- package/dist/esm-node/surface-resolution/env-static-provider.mjs +275 -0
- package/dist/esm-node/surface-resolution/index.mjs +4 -0
- package/dist/esm-node/surface-resolution/surface-ref.mjs +89 -0
- package/dist/esm-node/surface-resolution/types.mjs +1 -0
- package/dist/esm-node/surface-resolution/validation.mjs +156 -0
- package/dist/types/surface-resolution/env-static-provider.d.ts +107 -0
- package/dist/types/surface-resolution/index.d.ts +4 -0
- package/dist/types/surface-resolution/surface-ref.d.ts +72 -0
- package/dist/types/surface-resolution/types.d.ts +102 -0
- package/dist/types/surface-resolution/validation.d.ts +53 -0
- package/package.json +40 -4
- package/src/surface-resolution/env-static-provider.ts +596 -0
- package/src/surface-resolution/index.ts +43 -0
- package/src/surface-resolution/surface-ref.ts +145 -0
- package/src/surface-resolution/types.ts +136 -0
- package/src/surface-resolution/validation.ts +337 -0
- package/README.md +0 -3
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env/static surface-resolution provider (MV-G25d) — the baseline provider of
|
|
3
|
+
* RESOLUTION-0001 §2.2: assembles a complete {@link ResolvedDeliveryUnit} from
|
|
4
|
+
* environment-configured values and static per-unit configuration. Works
|
|
5
|
+
* offline, no external service.
|
|
6
|
+
*
|
|
7
|
+
* It unifies the two previously independent resolutions into ONE record:
|
|
8
|
+
*
|
|
9
|
+
* - browser MF manifest: the generated `createRemoteManifestUrl` chain
|
|
10
|
+
* (`packages/toolkit/create/src/ultramodern-workspace/module-federation/remote-refs.ts`)
|
|
11
|
+
* — `VERTICAL_<SEG>_MF_MANIFEST`, else `ULTRAMODERN_PUBLIC_URL_<SEG>` +
|
|
12
|
+
* `/mf-manifest.json`, else the Cloudflare workers.dev overlay, else the
|
|
13
|
+
* localhost dev fallback.
|
|
14
|
+
* - node MF manifest: the backend manifest env convention
|
|
15
|
+
* (`VERTICAL_<SEG>_BACKEND_MF_MANIFEST`, see plugin-bff manifest reference
|
|
16
|
+
* resolution) with the generated localhost fallback.
|
|
17
|
+
*
|
|
18
|
+
* Failure semantics: partial is an error. If any declared platform location
|
|
19
|
+
* for any surface cannot be assembled, the provider returns one typed
|
|
20
|
+
* {@link DiscoveryError} and no record.
|
|
21
|
+
*
|
|
22
|
+
* Fail-closed environment semantics: localhost dev fallbacks (the `port`
|
|
23
|
+
* chains) apply ONLY when the requested {@link EnvironmentId} is a designated
|
|
24
|
+
* local environment ({@link EnvStaticProviderOptions.localEnvironments},
|
|
25
|
+
* default `['development', 'local']`). Any other environment with missing
|
|
26
|
+
* explicit configuration yields a typed `provider-unavailable` error.
|
|
27
|
+
*
|
|
28
|
+
* External majors (ADR-0020): a versioned SurfaceRef (`…@vN`) is served only
|
|
29
|
+
* from a major-specific materialization declared in
|
|
30
|
+
* {@link EnvStaticUnitConfig.majors}; each configured major resolves through
|
|
31
|
+
* the same env chains under its own env segment (default
|
|
32
|
+
* `${envSegment}_V${major}`) and never inherits the unversioned localhost
|
|
33
|
+
* port or worker name. A requested major with no configured materialization
|
|
34
|
+
* is the typed `major-not-published` error — unversioned locations are never
|
|
35
|
+
* served for a versioned request. Served records stamp `servedMajor` on every
|
|
36
|
+
* surface.
|
|
37
|
+
*
|
|
38
|
+
* Identity honesty (ADR-0019): this provider stamps STATIC identity
|
|
39
|
+
* (`buildMarker` / `sourceRevision` / `baselineCohortId`) onto independently
|
|
40
|
+
* resolved URLs; it never fetches the artifacts to verify them. Under the
|
|
41
|
+
* default `identityVerification: 'static-trust'`, the compatibility verdict is
|
|
42
|
+
* `{ status: 'compatible', reason: 'static-identity-unverified' }` — an
|
|
43
|
+
* explicit machine-readable marker that identity was asserted, not verified.
|
|
44
|
+
* Runtime identity validation (ADR-0019, `matchDeliveryUnitIdentity`) remains
|
|
45
|
+
* the enforcement point.
|
|
46
|
+
*/
|
|
47
|
+
import { formatSurfaceRef, type ParsedSurfaceRef } from './surface-ref';
|
|
48
|
+
import type {
|
|
49
|
+
DiscoveryResult,
|
|
50
|
+
EnvironmentId,
|
|
51
|
+
ResolvedDeliveryUnit,
|
|
52
|
+
ResolvedSurface,
|
|
53
|
+
ResolvedSurfaceKind,
|
|
54
|
+
ResolvedSurfaceLocation,
|
|
55
|
+
SurfaceResolutionProvider,
|
|
56
|
+
} from './types';
|
|
57
|
+
import {
|
|
58
|
+
createDiscoveryError,
|
|
59
|
+
validateResolvedDeliveryUnit,
|
|
60
|
+
} from './validation';
|
|
61
|
+
|
|
62
|
+
export type EnvRecord = Record<string, string | undefined>;
|
|
63
|
+
|
|
64
|
+
/** Which platforms a surface publishes, plus their static (non-env) inputs. */
|
|
65
|
+
export type EnvStaticSurfacePlatforms = {
|
|
66
|
+
/** Publish a `browser-mf-manifest` location (env/public/cloudflare/localhost chain). */
|
|
67
|
+
browserMfManifest?: boolean;
|
|
68
|
+
/** Publish a `node-mf-manifest` location (backend manifest env / localhost chain). */
|
|
69
|
+
nodeMfManifest?: boolean;
|
|
70
|
+
/** Publish an `http-api` location; the base URL follows the public-URL chain. */
|
|
71
|
+
httpApi?: { prefix: string };
|
|
72
|
+
/** Publish a `cloudflare-service-binding` location (statically provisioned). */
|
|
73
|
+
cloudflareServiceBinding?: {
|
|
74
|
+
serviceBinding: string;
|
|
75
|
+
dispatchNamespace?: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type EnvStaticSurfaceConfig = {
|
|
80
|
+
surfaceId: string;
|
|
81
|
+
kind: ResolvedSurfaceKind;
|
|
82
|
+
platforms: EnvStaticSurfacePlatforms;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Static configuration for one delivery unit. Identity comes from the build
|
|
87
|
+
* artifact (never from the environment); the environment only overrides
|
|
88
|
+
* addresses through the documented env-var chains.
|
|
89
|
+
*/
|
|
90
|
+
export type EnvStaticUnitConfig = {
|
|
91
|
+
unitId: string;
|
|
92
|
+
buildMarker: string;
|
|
93
|
+
sourceRevision: string;
|
|
94
|
+
baselineCohortId: string;
|
|
95
|
+
/**
|
|
96
|
+
* Env-var segment, e.g. `CHECKOUT` in `VERTICAL_CHECKOUT_MF_MANIFEST` /
|
|
97
|
+
* `ULTRAMODERN_PUBLIC_URL_CHECKOUT` (`toEnvSegment(domain ?? id)`).
|
|
98
|
+
*/
|
|
99
|
+
envSegment: string;
|
|
100
|
+
/** Module-federation container name (used to strip `mfName@` env prefixes). */
|
|
101
|
+
mfName: string;
|
|
102
|
+
/** Localhost dev fallback port. Without it, env values are mandatory. */
|
|
103
|
+
port?: number;
|
|
104
|
+
/** Cloudflare worker name for the workers.dev fallback. */
|
|
105
|
+
workerName?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Externally published majors (ADR-0020), each with its own materialization.
|
|
108
|
+
* A requested `@vN` resolves ONLY through the matching entry; a major that
|
|
109
|
+
* is not configured here is the typed `major-not-published` error.
|
|
110
|
+
*/
|
|
111
|
+
majors?: EnvStaticMajorConfig[];
|
|
112
|
+
surfaces: EnvStaticSurfaceConfig[];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Per-major materialization (ADR-0020): where the externally published major
|
|
117
|
+
* `vN` of a unit lives. Address inputs are deliberately NOT inherited from the
|
|
118
|
+
* unversioned unit — a versioned request must never be answered with
|
|
119
|
+
* unversioned locations — so each major carries its own env segment (default
|
|
120
|
+
* `${unit.envSegment}_V${major}`) and, optionally, its own localhost port /
|
|
121
|
+
* worker name.
|
|
122
|
+
*/
|
|
123
|
+
export type EnvStaticMajorConfig = {
|
|
124
|
+
major: number;
|
|
125
|
+
/** Env-var segment for this major. Default: `${unit.envSegment}_V${major}`. */
|
|
126
|
+
envSegment?: string;
|
|
127
|
+
/** Localhost dev fallback port for this major (local environments only). */
|
|
128
|
+
port?: number;
|
|
129
|
+
/** Cloudflare worker name for this major's workers.dev fallback. */
|
|
130
|
+
workerName?: string;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* How the provider's stamped identity is to be interpreted. `static-trust`
|
|
135
|
+
* (the only mode, and the default) means identity is asserted from static
|
|
136
|
+
* config, not verified against the artifacts; the compatibility verdict then
|
|
137
|
+
* carries `reason: 'static-identity-unverified'`.
|
|
138
|
+
*/
|
|
139
|
+
export type EnvStaticIdentityVerification = 'static-trust';
|
|
140
|
+
|
|
141
|
+
/** Environments in which localhost dev fallbacks are allowed by default. */
|
|
142
|
+
export const DEFAULT_LOCAL_ENVIRONMENTS: readonly string[] = [
|
|
143
|
+
'development',
|
|
144
|
+
'local',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
export type EnvStaticProviderOptions = {
|
|
148
|
+
units: EnvStaticUnitConfig[];
|
|
149
|
+
/**
|
|
150
|
+
* Environment record to read from. Universal module: `process.env` is never
|
|
151
|
+
* read implicitly; pass it explicitly in Node hosts.
|
|
152
|
+
*/
|
|
153
|
+
env?: EnvRecord;
|
|
154
|
+
/**
|
|
155
|
+
* Environments in which localhost dev fallbacks apply. Any environment NOT
|
|
156
|
+
* listed here fails closed: missing explicit configuration is the typed
|
|
157
|
+
* `provider-unavailable` error, never a localhost URL.
|
|
158
|
+
* Default: `['development', 'local']`.
|
|
159
|
+
*/
|
|
160
|
+
localEnvironments?: EnvironmentId[];
|
|
161
|
+
/**
|
|
162
|
+
* Identity-honesty mode (ADR-0019). Default `'static-trust'`: the verdict
|
|
163
|
+
* is marked `static-identity-unverified` because this provider asserts
|
|
164
|
+
* identity from static config without verifying the resolved artifacts.
|
|
165
|
+
*/
|
|
166
|
+
identityVerification?: EnvStaticIdentityVerification;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const ENV_STATIC_PROVIDER_NAME = 'env-static';
|
|
170
|
+
|
|
171
|
+
function trimTrailingSlashes(value: string): string {
|
|
172
|
+
return value.replace(/\/+$/u, '');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function envValue(env: EnvRecord, name: string): string | undefined {
|
|
176
|
+
const value = env[name]?.trim();
|
|
177
|
+
return value !== undefined && value.length > 0 ? value : undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Strip an optional `mfName@` remote-ref prefix from a configured manifest env value. */
|
|
181
|
+
function stripMfNamePrefix(value: string, mfName: string): string {
|
|
182
|
+
return value.startsWith(`${mfName}@`)
|
|
183
|
+
? value.slice(mfName.length + 1)
|
|
184
|
+
: value;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
type LocationOutcome =
|
|
188
|
+
| { ok: true; location: ResolvedSurfaceLocation }
|
|
189
|
+
| { ok: false; reason: string; details?: Record<string, unknown> };
|
|
190
|
+
|
|
191
|
+
type EnvContext = {
|
|
192
|
+
env: EnvRecord;
|
|
193
|
+
cloudflareDeployEnabled: boolean;
|
|
194
|
+
workersDevSubdomain: string | undefined;
|
|
195
|
+
requireCloudflarePublicUrls: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Whether localhost dev fallbacks may be used. True only in designated
|
|
198
|
+
* local environments; everywhere else missing config fails closed.
|
|
199
|
+
*/
|
|
200
|
+
allowLocalFallback: boolean;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
function createEnvContext(
|
|
204
|
+
env: EnvRecord,
|
|
205
|
+
allowLocalFallback: boolean,
|
|
206
|
+
): EnvContext {
|
|
207
|
+
return {
|
|
208
|
+
env,
|
|
209
|
+
allowLocalFallback,
|
|
210
|
+
cloudflareDeployEnabled: env.MODERNJS_DEPLOY === 'cloudflare',
|
|
211
|
+
workersDevSubdomain: envValue(
|
|
212
|
+
env,
|
|
213
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
214
|
+
),
|
|
215
|
+
requireCloudflarePublicUrls:
|
|
216
|
+
env.ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS === 'true',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Base-URL chain shared by the browser manifest and http-api locations:
|
|
222
|
+
* public URL env, Cloudflare workers.dev overlay, localhost dev fallback.
|
|
223
|
+
*/
|
|
224
|
+
function resolveBaseUrl(
|
|
225
|
+
context: EnvContext,
|
|
226
|
+
unit: EnvStaticUnitConfig,
|
|
227
|
+
): LocationOutcomeBase {
|
|
228
|
+
const publicUrlEnv = `ULTRAMODERN_PUBLIC_URL_${unit.envSegment}`;
|
|
229
|
+
const configuredPublicUrl = envValue(context.env, publicUrlEnv);
|
|
230
|
+
if (configuredPublicUrl !== undefined) {
|
|
231
|
+
return { ok: true, baseUrl: trimTrailingSlashes(configuredPublicUrl) };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
context.cloudflareDeployEnabled &&
|
|
236
|
+
context.workersDevSubdomain !== undefined &&
|
|
237
|
+
unit.workerName !== undefined
|
|
238
|
+
) {
|
|
239
|
+
return {
|
|
240
|
+
ok: true,
|
|
241
|
+
baseUrl: `https://${unit.workerName}.${context.workersDevSubdomain}.workers.dev`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (context.cloudflareDeployEnabled && context.requireCloudflarePublicUrls) {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
reason: `Cloudflare deploy requires ${publicUrlEnv} (or a configured manifest env / ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN with a worker name)`,
|
|
249
|
+
details: { publicUrlEnv },
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (unit.port !== undefined && context.allowLocalFallback) {
|
|
254
|
+
return { ok: true, baseUrl: `http://localhost:${unit.port}` };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (unit.port !== undefined) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
reason: `No base URL available: set ${publicUrlEnv} (localhost fallback is disabled outside designated local environments)`,
|
|
261
|
+
details: { publicUrlEnv },
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
reason: `No base URL available: set ${publicUrlEnv} or configure a localhost port`,
|
|
268
|
+
details: { publicUrlEnv },
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
type LocationOutcomeBase =
|
|
273
|
+
| { ok: true; baseUrl: string }
|
|
274
|
+
| { ok: false; reason: string; details?: Record<string, unknown> };
|
|
275
|
+
|
|
276
|
+
function resolveBrowserMfManifest(
|
|
277
|
+
context: EnvContext,
|
|
278
|
+
unit: EnvStaticUnitConfig,
|
|
279
|
+
): LocationOutcome {
|
|
280
|
+
const manifestEnv = `VERTICAL_${unit.envSegment}_MF_MANIFEST`;
|
|
281
|
+
const configuredManifest = envValue(context.env, manifestEnv);
|
|
282
|
+
if (configuredManifest !== undefined) {
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
location: {
|
|
286
|
+
platform: 'browser-mf-manifest',
|
|
287
|
+
manifestUrl: stripMfNamePrefix(configuredManifest, unit.mfName),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const base = resolveBaseUrl(context, unit);
|
|
293
|
+
if (!base.ok) {
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
reason: `browser-mf-manifest unavailable (${manifestEnv} unset; ${base.reason})`,
|
|
297
|
+
details: { manifestEnv, ...base.details },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
location: {
|
|
304
|
+
platform: 'browser-mf-manifest',
|
|
305
|
+
manifestUrl: `${base.baseUrl}/mf-manifest.json`,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function resolveNodeMfManifest(
|
|
311
|
+
context: EnvContext,
|
|
312
|
+
unit: EnvStaticUnitConfig,
|
|
313
|
+
): LocationOutcome {
|
|
314
|
+
const manifestEnv = `VERTICAL_${unit.envSegment}_BACKEND_MF_MANIFEST`;
|
|
315
|
+
const configuredManifest = envValue(context.env, manifestEnv);
|
|
316
|
+
if (configuredManifest !== undefined) {
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
location: {
|
|
320
|
+
platform: 'node-mf-manifest',
|
|
321
|
+
manifestRef: configuredManifest,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (unit.port !== undefined && context.allowLocalFallback) {
|
|
327
|
+
return {
|
|
328
|
+
ok: true,
|
|
329
|
+
location: {
|
|
330
|
+
platform: 'node-mf-manifest',
|
|
331
|
+
manifestRef: `http://localhost:${unit.port}/backend-mf-manifest.json`,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (unit.port !== undefined) {
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
reason: `node-mf-manifest unavailable: set ${manifestEnv} (localhost fallback is disabled outside designated local environments)`,
|
|
340
|
+
details: { manifestEnv },
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
ok: false,
|
|
346
|
+
reason: `node-mf-manifest unavailable: set ${manifestEnv} or configure a localhost port`,
|
|
347
|
+
details: { manifestEnv },
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function resolveHttpApi(
|
|
352
|
+
context: EnvContext,
|
|
353
|
+
unit: EnvStaticUnitConfig,
|
|
354
|
+
config: { prefix: string },
|
|
355
|
+
): LocationOutcome {
|
|
356
|
+
const base = resolveBaseUrl(context, unit);
|
|
357
|
+
if (!base.ok) {
|
|
358
|
+
return {
|
|
359
|
+
ok: false,
|
|
360
|
+
reason: `http-api unavailable (${base.reason})`,
|
|
361
|
+
details: base.details,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
ok: true,
|
|
366
|
+
location: {
|
|
367
|
+
platform: 'http-api',
|
|
368
|
+
baseUrl: base.baseUrl,
|
|
369
|
+
prefix: config.prefix,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function resolveCloudflareServiceBinding(config: {
|
|
375
|
+
serviceBinding: string;
|
|
376
|
+
dispatchNamespace?: string;
|
|
377
|
+
}): LocationOutcome {
|
|
378
|
+
if (config.serviceBinding.length === 0) {
|
|
379
|
+
return {
|
|
380
|
+
ok: false,
|
|
381
|
+
reason: 'cloudflare-service-binding unavailable: empty serviceBinding',
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
ok: true,
|
|
386
|
+
location: {
|
|
387
|
+
platform: 'cloudflare-service-binding',
|
|
388
|
+
serviceBinding: config.serviceBinding,
|
|
389
|
+
...(config.dispatchNamespace === undefined
|
|
390
|
+
? {}
|
|
391
|
+
: { dispatchNamespace: config.dispatchNamespace }),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function resolveSurfaceLocations(
|
|
397
|
+
context: EnvContext,
|
|
398
|
+
unit: EnvStaticUnitConfig,
|
|
399
|
+
surface: EnvStaticSurfaceConfig,
|
|
400
|
+
):
|
|
401
|
+
| { ok: true; surface: ResolvedSurface }
|
|
402
|
+
| { ok: false; reason: string; details?: Record<string, unknown> } {
|
|
403
|
+
const outcomes: LocationOutcome[] = [];
|
|
404
|
+
if (surface.platforms.browserMfManifest) {
|
|
405
|
+
outcomes.push(resolveBrowserMfManifest(context, unit));
|
|
406
|
+
}
|
|
407
|
+
if (surface.platforms.nodeMfManifest) {
|
|
408
|
+
outcomes.push(resolveNodeMfManifest(context, unit));
|
|
409
|
+
}
|
|
410
|
+
if (surface.platforms.httpApi !== undefined) {
|
|
411
|
+
outcomes.push(resolveHttpApi(context, unit, surface.platforms.httpApi));
|
|
412
|
+
}
|
|
413
|
+
if (surface.platforms.cloudflareServiceBinding !== undefined) {
|
|
414
|
+
outcomes.push(
|
|
415
|
+
resolveCloudflareServiceBinding(
|
|
416
|
+
surface.platforms.cloudflareServiceBinding,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const failure = outcomes.find(outcome => !outcome.ok);
|
|
422
|
+
if (failure !== undefined && !failure.ok) {
|
|
423
|
+
return {
|
|
424
|
+
ok: false,
|
|
425
|
+
reason: `surface ${surface.surfaceId}: ${failure.reason}`,
|
|
426
|
+
details: failure.details,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const locations = outcomes.flatMap(outcome =>
|
|
431
|
+
outcome.ok ? [outcome.location] : [],
|
|
432
|
+
);
|
|
433
|
+
if (locations.length === 0) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
reason: `surface ${surface.surfaceId} declares no platform locations`,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
surface: { surfaceId: surface.surfaceId, kind: surface.kind, locations },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Create the baseline env/static provider. Resolution is all-or-nothing per
|
|
448
|
+
* RESOLUTION-0001: every declared platform location for every surface of the
|
|
449
|
+
* unit assembles against the unit's single static identity, or the whole
|
|
450
|
+
* resolution fails with one typed error.
|
|
451
|
+
*/
|
|
452
|
+
export function createEnvStaticSurfaceResolutionProvider(
|
|
453
|
+
options: EnvStaticProviderOptions,
|
|
454
|
+
): SurfaceResolutionProvider {
|
|
455
|
+
const env = options.env ?? {};
|
|
456
|
+
const localEnvironments = new Set(
|
|
457
|
+
options.localEnvironments ?? DEFAULT_LOCAL_ENVIRONMENTS,
|
|
458
|
+
);
|
|
459
|
+
// 'static-trust' is currently the only mode; keeping it explicit makes the
|
|
460
|
+
// identity-honesty marker below auditable at the call site.
|
|
461
|
+
const identityVerification: EnvStaticIdentityVerification =
|
|
462
|
+
options.identityVerification ?? 'static-trust';
|
|
463
|
+
const unitsById = new Map(options.units.map(unit => [unit.unitId, unit]));
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
name: ENV_STATIC_PROVIDER_NAME,
|
|
467
|
+
resolve(
|
|
468
|
+
ref: ParsedSurfaceRef,
|
|
469
|
+
environment: EnvironmentId,
|
|
470
|
+
): DiscoveryResult {
|
|
471
|
+
const refString = formatSurfaceRef(ref);
|
|
472
|
+
const unit = unitsById.get(ref.unitId);
|
|
473
|
+
if (unit === undefined) {
|
|
474
|
+
return {
|
|
475
|
+
ok: false,
|
|
476
|
+
error: createDiscoveryError(
|
|
477
|
+
'unknown-unit',
|
|
478
|
+
refString,
|
|
479
|
+
`No statically configured delivery unit ${ref.unitId}.`,
|
|
480
|
+
{ environment, knownUnits: [...unitsById.keys()] },
|
|
481
|
+
),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!unit.surfaces.some(surface => surface.surfaceId === ref.surfaceId)) {
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
error: createDiscoveryError(
|
|
489
|
+
'unknown-surface',
|
|
490
|
+
refString,
|
|
491
|
+
`Unit ${unit.unitId} does not declare surface ${ref.surfaceId}.`,
|
|
492
|
+
{
|
|
493
|
+
environment,
|
|
494
|
+
availableSurfaces: unit.surfaces.map(
|
|
495
|
+
surface => surface.surfaceId,
|
|
496
|
+
),
|
|
497
|
+
},
|
|
498
|
+
),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// A versioned request is served ONLY from the matching major-specific
|
|
503
|
+
// materialization; unversioned locations are never substituted.
|
|
504
|
+
let effectiveUnit = unit;
|
|
505
|
+
if (ref.major !== undefined) {
|
|
506
|
+
const majorConfig = (unit.majors ?? []).find(
|
|
507
|
+
candidate => candidate.major === ref.major,
|
|
508
|
+
);
|
|
509
|
+
if (majorConfig === undefined) {
|
|
510
|
+
return {
|
|
511
|
+
ok: false,
|
|
512
|
+
error: createDiscoveryError(
|
|
513
|
+
'major-not-published',
|
|
514
|
+
refString,
|
|
515
|
+
`Unit ${unit.unitId} has no materialization for external major v${ref.major}.`,
|
|
516
|
+
{
|
|
517
|
+
environment,
|
|
518
|
+
publishedMajors: (unit.majors ?? []).map(
|
|
519
|
+
candidate => candidate.major,
|
|
520
|
+
),
|
|
521
|
+
},
|
|
522
|
+
),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
effectiveUnit = {
|
|
526
|
+
...unit,
|
|
527
|
+
envSegment:
|
|
528
|
+
majorConfig.envSegment ??
|
|
529
|
+
`${unit.envSegment}_V${majorConfig.major}`,
|
|
530
|
+
// Deliberately NOT inherited from the unversioned unit (fail closed).
|
|
531
|
+
port: majorConfig.port,
|
|
532
|
+
workerName: majorConfig.workerName,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const context = createEnvContext(env, localEnvironments.has(environment));
|
|
537
|
+
const surfaces: ResolvedSurface[] = [];
|
|
538
|
+
for (const surfaceConfig of unit.surfaces) {
|
|
539
|
+
const resolved = resolveSurfaceLocations(
|
|
540
|
+
context,
|
|
541
|
+
effectiveUnit,
|
|
542
|
+
surfaceConfig,
|
|
543
|
+
);
|
|
544
|
+
if (!resolved.ok) {
|
|
545
|
+
// Partial is an error: one unassemblable location fails the record.
|
|
546
|
+
return {
|
|
547
|
+
ok: false,
|
|
548
|
+
error: createDiscoveryError(
|
|
549
|
+
'provider-unavailable',
|
|
550
|
+
refString,
|
|
551
|
+
`env-static provider cannot assemble a complete record for ${unit.unitId}: ${resolved.reason}.`,
|
|
552
|
+
{ environment, ...resolved.details },
|
|
553
|
+
),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
surfaces.push(
|
|
557
|
+
ref.major === undefined
|
|
558
|
+
? resolved.surface
|
|
559
|
+
: { ...resolved.surface, servedMajor: ref.major },
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const record: ResolvedDeliveryUnit = {
|
|
564
|
+
unitId: unit.unitId,
|
|
565
|
+
buildMarker: unit.buildMarker,
|
|
566
|
+
sourceRevision: unit.sourceRevision,
|
|
567
|
+
baselineCohortId: unit.baselineCohortId,
|
|
568
|
+
surfaces,
|
|
569
|
+
compatibility: {
|
|
570
|
+
status: 'compatible',
|
|
571
|
+
baselineCohortId: unit.baselineCohortId,
|
|
572
|
+
// Identity honesty (ADR-0019): under static-trust the verdict marks
|
|
573
|
+
// that identity was asserted from static config, not verified.
|
|
574
|
+
...(identityVerification === 'static-trust'
|
|
575
|
+
? { reason: 'static-identity-unverified' }
|
|
576
|
+
: {}),
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const validation = validateResolvedDeliveryUnit(record);
|
|
581
|
+
if (!validation.ok) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
error: createDiscoveryError(
|
|
585
|
+
'provider-unavailable',
|
|
586
|
+
refString,
|
|
587
|
+
`env-static provider assembled an invalid record for ${unit.unitId}.`,
|
|
588
|
+
{ environment, issues: validation.issues },
|
|
589
|
+
),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return { ok: true, unit: record };
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createEnvStaticSurfaceResolutionProvider,
|
|
3
|
+
DEFAULT_LOCAL_ENVIRONMENTS,
|
|
4
|
+
ENV_STATIC_PROVIDER_NAME,
|
|
5
|
+
type EnvRecord,
|
|
6
|
+
type EnvStaticIdentityVerification,
|
|
7
|
+
type EnvStaticMajorConfig,
|
|
8
|
+
type EnvStaticProviderOptions,
|
|
9
|
+
type EnvStaticSurfaceConfig,
|
|
10
|
+
type EnvStaticSurfacePlatforms,
|
|
11
|
+
type EnvStaticUnitConfig,
|
|
12
|
+
} from './env-static-provider';
|
|
13
|
+
export {
|
|
14
|
+
formatSurfaceRef,
|
|
15
|
+
type ParsedSurfaceRef,
|
|
16
|
+
parseSurfaceRef,
|
|
17
|
+
type SurfaceRefParseError,
|
|
18
|
+
type SurfaceRefParseResult,
|
|
19
|
+
validateSurfaceRef,
|
|
20
|
+
} from './surface-ref';
|
|
21
|
+
export type {
|
|
22
|
+
CompatibilityStatus,
|
|
23
|
+
CompatibilityVerdict,
|
|
24
|
+
DiscoveryError,
|
|
25
|
+
DiscoveryErrorCode,
|
|
26
|
+
DiscoveryResult,
|
|
27
|
+
EnvironmentId,
|
|
28
|
+
ResolvedDeliveryUnit,
|
|
29
|
+
ResolvedSurface,
|
|
30
|
+
ResolvedSurfaceKind,
|
|
31
|
+
ResolvedSurfaceLocation,
|
|
32
|
+
ResolvedSurfaceLocationPlatform,
|
|
33
|
+
SurfaceResolutionProvider,
|
|
34
|
+
} from './types';
|
|
35
|
+
export {
|
|
36
|
+
createDiscoveryError,
|
|
37
|
+
type ExpectedDeliveryUnitIdentity,
|
|
38
|
+
matchDeliveryUnitIdentity,
|
|
39
|
+
type ResolvedDeliveryUnitIssue,
|
|
40
|
+
type ResolvedDeliveryUnitValidationResult,
|
|
41
|
+
selectResolvedSurface,
|
|
42
|
+
validateResolvedDeliveryUnit,
|
|
43
|
+
} from './validation';
|