@bleedingdev/modern-js-app-tools-extensions 3.8.3-ultramodern.2
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/README.md +5 -0
- package/dist/cjs/cloudflare/artifacts.js +127 -0
- package/dist/cjs/cloudflare/constants.js +141 -0
- package/dist/cjs/cloudflare/delivery-unit.js +161 -0
- package/dist/cjs/cloudflare/i18n-worker.js +135 -0
- package/dist/cjs/cloudflare/index.js +156 -0
- package/dist/cjs/cloudflare/security-policies.js +205 -0
- package/dist/cjs/cloudflare/types.js +18 -0
- package/dist/cjs/cloudflare/utils.js +66 -0
- package/dist/cjs/cloudflare/worker-manifest.js +239 -0
- package/dist/cjs/cloudflare/wrangler-config.js +205 -0
- package/dist/cjs/cloudflare-builder.js +394 -0
- package/dist/cjs/cloudflare-output-contract.js +108 -0
- package/dist/cjs/cloudflare-output-plan.js +93 -0
- package/dist/cjs/cloudflare-output-verifier/identity.js +92 -0
- package/dist/cjs/cloudflare-output-verifier/index.js +188 -0
- package/dist/cjs/cloudflare-output-verifier/issues.js +54 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +89 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +151 -0
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +378 -0
- package/dist/cjs/config.js +18 -0
- package/dist/cjs/css-extract-runtime-plugin.js +74 -0
- package/dist/cjs/index.js +125 -0
- package/dist/cjs/read-template.js +56 -0
- package/dist/cjs/release-envelope/canonical.js +68 -0
- package/dist/cjs/release-envelope/framework-output.js +649 -0
- package/dist/cjs/release-envelope/index.js +399 -0
- package/dist/cjs/release-envelope/plugin.js +102 -0
- package/dist/cjs/release-envelope/types.js +51 -0
- package/dist/cjs/release-identity.js +110 -0
- package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/cjs/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/cjs/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/esm/cloudflare/artifacts.mjs +64 -0
- package/dist/esm/cloudflare/constants.mjs +34 -0
- package/dist/esm/cloudflare/delivery-unit.mjs +110 -0
- package/dist/esm/cloudflare/i18n-worker.mjs +97 -0
- package/dist/esm/cloudflare/index.mjs +108 -0
- package/dist/esm/cloudflare/security-policies.mjs +167 -0
- package/dist/esm/cloudflare/types.mjs +0 -0
- package/dist/esm/cloudflare/utils.mjs +12 -0
- package/dist/esm/cloudflare/worker-manifest.mjs +179 -0
- package/dist/esm/cloudflare/wrangler-config.mjs +151 -0
- package/dist/esm/cloudflare-builder.mjs +327 -0
- package/dist/esm/cloudflare-output-contract.mjs +31 -0
- package/dist/esm/cloudflare-output-plan.mjs +45 -0
- package/dist/esm/cloudflare-output-verifier/identity.mjs +54 -0
- package/dist/esm/cloudflare-output-verifier/index.mjs +133 -0
- package/dist/esm/cloudflare-output-verifier/issues.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +51 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +99 -0
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +313 -0
- package/dist/esm/config.mjs +0 -0
- package/dist/esm/css-extract-runtime-plugin.mjs +32 -0
- package/dist/esm/index.mjs +10 -0
- package/dist/esm/read-template.mjs +5 -0
- package/dist/esm/release-envelope/canonical.mjs +21 -0
- package/dist/esm/release-envelope/framework-output.mjs +573 -0
- package/dist/esm/release-envelope/index.mjs +335 -0
- package/dist/esm/release-envelope/plugin.mjs +54 -0
- package/dist/esm/release-envelope/types.mjs +7 -0
- package/dist/esm/release-identity.mjs +66 -0
- package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/esm/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/esm/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/esm-node/cloudflare/artifacts.mjs +65 -0
- package/dist/esm-node/cloudflare/constants.mjs +35 -0
- package/dist/esm-node/cloudflare/delivery-unit.mjs +111 -0
- package/dist/esm-node/cloudflare/i18n-worker.mjs +98 -0
- package/dist/esm-node/cloudflare/index.mjs +109 -0
- package/dist/esm-node/cloudflare/security-policies.mjs +168 -0
- package/dist/esm-node/cloudflare/types.mjs +1 -0
- package/dist/esm-node/cloudflare/utils.mjs +13 -0
- package/dist/esm-node/cloudflare/worker-manifest.mjs +180 -0
- package/dist/esm-node/cloudflare/wrangler-config.mjs +152 -0
- package/dist/esm-node/cloudflare-builder.mjs +328 -0
- package/dist/esm-node/cloudflare-output-contract.mjs +32 -0
- package/dist/esm-node/cloudflare-output-plan.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +55 -0
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +134 -0
- package/dist/esm-node/cloudflare-output-verifier/issues.mjs +11 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +52 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +100 -0
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +314 -0
- package/dist/esm-node/config.mjs +1 -0
- package/dist/esm-node/css-extract-runtime-plugin.mjs +33 -0
- package/dist/esm-node/index.mjs +11 -0
- package/dist/esm-node/read-template.mjs +9 -0
- package/dist/esm-node/release-envelope/canonical.mjs +22 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +574 -0
- package/dist/esm-node/release-envelope/index.mjs +336 -0
- package/dist/esm-node/release-envelope/plugin.mjs +55 -0
- package/dist/esm-node/release-envelope/types.mjs +8 -0
- package/dist/esm-node/release-identity.mjs +67 -0
- package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/esm-node/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/esm-node/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/types/cloudflare/artifacts.d.ts +15 -0
- package/dist/types/cloudflare/constants.d.ts +24 -0
- package/dist/types/cloudflare/delivery-unit.d.ts +23 -0
- package/dist/types/cloudflare/i18n-worker.d.ts +22 -0
- package/dist/types/cloudflare/index.d.ts +2 -0
- package/dist/types/cloudflare/security-policies.d.ts +52 -0
- package/dist/types/cloudflare/types.d.ts +28 -0
- package/dist/types/cloudflare/utils.d.ts +6 -0
- package/dist/types/cloudflare/worker-manifest.d.ts +149 -0
- package/dist/types/cloudflare/wrangler-config.d.ts +51 -0
- package/dist/types/cloudflare-builder.d.ts +59 -0
- package/dist/types/cloudflare-output-contract.d.ts +14 -0
- package/dist/types/cloudflare-output-plan.d.ts +34 -0
- package/dist/types/cloudflare-output-verifier/identity.d.ts +8 -0
- package/dist/types/cloudflare-output-verifier/index.d.ts +18 -0
- package/dist/types/cloudflare-output-verifier/issues.d.ts +14 -0
- package/dist/types/cloudflare-output-verifier/manifest.d.ts +2 -0
- package/dist/types/cloudflare-output-verifier/mutation-policy.d.ts +14 -0
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +20 -0
- package/dist/types/config.d.ts +175 -0
- package/dist/types/css-extract-runtime-plugin.d.ts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/read-template.d.ts +2 -0
- package/dist/types/release-envelope/canonical.d.ts +5 -0
- package/dist/types/release-envelope/framework-output.d.ts +32 -0
- package/dist/types/release-envelope/index.d.ts +6 -0
- package/dist/types/release-envelope/plugin.d.ts +30 -0
- package/dist/types/release-envelope/types.d.ts +62 -0
- package/dist/types/release-identity.d.ts +16 -0
- package/dist/types/templates/cloudflare-entry.001-bootstrap-security.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.002-assets-routes.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.003-i18n-locales.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.004-rendering-css.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.005-worker-dispatch.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.006-fetch-handler.d.mts +4 -0
- package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +5 -0
- package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +48 -0
- package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +13 -0
- package/dist/types/templates/cloudflare-worker-path.d.mts +21 -0
- package/package.json +131 -0
- package/src/cloudflare/artifacts.ts +165 -0
- package/src/cloudflare/constants.ts +50 -0
- package/src/cloudflare/delivery-unit.ts +195 -0
- package/src/cloudflare/i18n-worker.ts +197 -0
- package/src/cloudflare/index.ts +259 -0
- package/src/cloudflare/security-policies.ts +194 -0
- package/src/cloudflare/types.ts +33 -0
- package/src/cloudflare/utils.ts +40 -0
- package/src/cloudflare/worker-manifest.ts +313 -0
- package/src/cloudflare/wrangler-config.ts +338 -0
- package/src/cloudflare-builder.ts +653 -0
- package/src/cloudflare-output-contract.ts +30 -0
- package/src/cloudflare-output-plan.ts +108 -0
- package/src/cloudflare-output-verifier/identity.ts +87 -0
- package/src/cloudflare-output-verifier/index.ts +277 -0
- package/src/cloudflare-output-verifier/issues.ts +54 -0
- package/src/cloudflare-output-verifier/manifest.ts +76 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +159 -0
- package/src/cloudflare-output-verifier/worker-bundles.ts +664 -0
- package/src/config.ts +196 -0
- package/src/css-extract-runtime-plugin.ts +70 -0
- package/src/index.ts +10 -0
- package/src/read-template.ts +8 -0
- package/src/release-envelope/canonical.ts +58 -0
- package/src/release-envelope/framework-output.ts +1121 -0
- package/src/release-envelope/index.ts +656 -0
- package/src/release-envelope/plugin.ts +125 -0
- package/src/release-envelope/types.ts +83 -0
- package/src/release-identity.ts +134 -0
- package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/src/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/src/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/src/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/src/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/src/templates/cloudflare-worker-path.mjs +98 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
emitFrameworkMicroVerticalReleaseEnvelope,
|
|
4
|
+
emitNodeStagedReleaseEnvelope,
|
|
5
|
+
verifyBuildOutputReleaseEnvelope,
|
|
6
|
+
verifyNodeReleaseEnvelopeStaging,
|
|
7
|
+
} from './framework-output';
|
|
8
|
+
|
|
9
|
+
type ReleaseEnvelopeTarget = 'cloudflare' | 'node' | string;
|
|
10
|
+
|
|
11
|
+
export interface ReleaseEnvelopeConfig {
|
|
12
|
+
deploy?: { target?: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ReleaseEnvelopeAppContext {
|
|
16
|
+
apiOnly: boolean;
|
|
17
|
+
appDirectory: string;
|
|
18
|
+
distDirectory: string;
|
|
19
|
+
metaName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ReleaseEnvelopePluginApi<
|
|
23
|
+
Config extends ReleaseEnvelopeConfig = ReleaseEnvelopeConfig,
|
|
24
|
+
> {
|
|
25
|
+
getAppContext(): ReleaseEnvelopeAppContext;
|
|
26
|
+
getNormalizedConfig(): Config;
|
|
27
|
+
onAfterBuild(handler: () => Promise<void>): void;
|
|
28
|
+
onBeforeDeploy(handler: () => Promise<void>): void;
|
|
29
|
+
onAfterDeploy(handler: () => Promise<void>): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ReleaseEnvelopePlugin<
|
|
33
|
+
Config extends ReleaseEnvelopeConfig = ReleaseEnvelopeConfig,
|
|
34
|
+
> {
|
|
35
|
+
name: string;
|
|
36
|
+
pre: string[];
|
|
37
|
+
post: string[];
|
|
38
|
+
setup(api: ReleaseEnvelopePluginApi<Config>): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ResolveDeployTarget<
|
|
42
|
+
Config extends ReleaseEnvelopeConfig = ReleaseEnvelopeConfig,
|
|
43
|
+
> = (config: Config) => ReleaseEnvelopeTarget;
|
|
44
|
+
|
|
45
|
+
const resolveActiveDeployTarget = <Config extends ReleaseEnvelopeConfig>(
|
|
46
|
+
api: ReleaseEnvelopePluginApi<Config>,
|
|
47
|
+
resolveDeployTarget: ResolveDeployTarget<Config>,
|
|
48
|
+
) => {
|
|
49
|
+
const { metaName } = api.getAppContext();
|
|
50
|
+
const config = api.getNormalizedConfig();
|
|
51
|
+
if (
|
|
52
|
+
metaName !== 'modern-js' &&
|
|
53
|
+
!config.deploy?.target &&
|
|
54
|
+
!process.env.MODERNJS_DEPLOY
|
|
55
|
+
) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return resolveDeployTarget(config);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const createUltramodernReleaseEnvelopePlugin = <
|
|
62
|
+
Config extends ReleaseEnvelopeConfig,
|
|
63
|
+
>({
|
|
64
|
+
resolveDeployTarget,
|
|
65
|
+
}: {
|
|
66
|
+
resolveDeployTarget: ResolveDeployTarget<Config>;
|
|
67
|
+
}): ReleaseEnvelopePlugin<Config> => {
|
|
68
|
+
return {
|
|
69
|
+
name: '@modern-js/ultramodern-release-envelope',
|
|
70
|
+
pre: ['@modern-js/backend-federation-build', '@modern-js/plugin-bff'],
|
|
71
|
+
post: ['@modern-js/plugin-deploy'],
|
|
72
|
+
setup(api) {
|
|
73
|
+
const emitBuildEnvelope = async (target: 'node' | 'cloudflare') => {
|
|
74
|
+
const { apiOnly, distDirectory } = api.getAppContext();
|
|
75
|
+
await emitFrameworkMicroVerticalReleaseEnvelope({
|
|
76
|
+
apiOnly,
|
|
77
|
+
distDirectory,
|
|
78
|
+
target,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
api.onAfterBuild(async () => {
|
|
83
|
+
const configuredTarget = resolveDeployTarget(api.getNormalizedConfig());
|
|
84
|
+
if (configuredTarget !== 'node' && configuredTarget !== 'cloudflare') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await emitBuildEnvelope(configuredTarget);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
api.onBeforeDeploy(async () => {
|
|
91
|
+
const configuredTarget = resolveActiveDeployTarget(
|
|
92
|
+
api,
|
|
93
|
+
resolveDeployTarget,
|
|
94
|
+
);
|
|
95
|
+
if (configuredTarget !== 'node') {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await emitBuildEnvelope(configuredTarget);
|
|
99
|
+
const { distDirectory } = api.getAppContext();
|
|
100
|
+
await verifyBuildOutputReleaseEnvelope(distDirectory, configuredTarget);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
api.onAfterDeploy(async () => {
|
|
104
|
+
const configuredTarget = resolveActiveDeployTarget(
|
|
105
|
+
api,
|
|
106
|
+
resolveDeployTarget,
|
|
107
|
+
);
|
|
108
|
+
if (configuredTarget !== 'node') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const { appDirectory, distDirectory } = api.getAppContext();
|
|
112
|
+
const outputDirectory = path.join(appDirectory, '.output');
|
|
113
|
+
const releaseEnvelope = await emitNodeStagedReleaseEnvelope({
|
|
114
|
+
distDirectory,
|
|
115
|
+
outputDirectory,
|
|
116
|
+
});
|
|
117
|
+
if (releaseEnvelope) {
|
|
118
|
+
await verifyNodeReleaseEnvelopeStaging({
|
|
119
|
+
outputDirectory,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION = 3 as const;
|
|
2
|
+
|
|
3
|
+
export const MICROVERTICAL_RELEASE_ENVELOPE_KIND =
|
|
4
|
+
'ultramodern-target-microvertical-release-envelope' as const;
|
|
5
|
+
|
|
6
|
+
export const MICROVERTICAL_RELEASE_TARGETS = ['node', 'cloudflare'] as const;
|
|
7
|
+
|
|
8
|
+
export type MicroVerticalReleaseTarget =
|
|
9
|
+
(typeof MICROVERTICAL_RELEASE_TARGETS)[number];
|
|
10
|
+
|
|
11
|
+
export type MicroVerticalReleaseIdentity = {
|
|
12
|
+
unitId: string;
|
|
13
|
+
buildMarker: string;
|
|
14
|
+
sourceRevision: string;
|
|
15
|
+
releaseVersion: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type MicroVerticalReleaseArtifactInput = {
|
|
19
|
+
logicalPath: string;
|
|
20
|
+
runtime: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type MicroVerticalReleaseFileArtifact =
|
|
24
|
+
MicroVerticalReleaseArtifactInput & {
|
|
25
|
+
kind: 'file';
|
|
26
|
+
byteLength: number;
|
|
27
|
+
sha256: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type MicroVerticalReleaseSymbolicLinkArtifact =
|
|
31
|
+
MicroVerticalReleaseArtifactInput & {
|
|
32
|
+
kind: 'symbolic-link';
|
|
33
|
+
linkTarget: string;
|
|
34
|
+
targetKind: 'directory' | 'file';
|
|
35
|
+
targetLogicalPath: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type MicroVerticalReleaseArtifact =
|
|
39
|
+
| MicroVerticalReleaseFileArtifact
|
|
40
|
+
| MicroVerticalReleaseSymbolicLinkArtifact;
|
|
41
|
+
|
|
42
|
+
export type MicroVerticalReleaseArtifactInputs = {
|
|
43
|
+
artifacts: MicroVerticalReleaseArtifactInput[];
|
|
44
|
+
surfaces: MicroVerticalReleaseSurfaces;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type MicroVerticalReleaseSurfaces = {
|
|
48
|
+
uiClient: string[];
|
|
49
|
+
ssr: string[];
|
|
50
|
+
apiBackend: string[];
|
|
51
|
+
backendFederation: {
|
|
52
|
+
manifest: string;
|
|
53
|
+
container: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type MicroVerticalReleaseEnvelopePayload = {
|
|
58
|
+
schemaVersion: typeof MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION;
|
|
59
|
+
kind: typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND;
|
|
60
|
+
target: MicroVerticalReleaseTarget;
|
|
61
|
+
identity: MicroVerticalReleaseIdentity;
|
|
62
|
+
artifacts: MicroVerticalReleaseArtifact[];
|
|
63
|
+
surfaces: MicroVerticalReleaseSurfaces;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type MicroVerticalReleaseEnvelope =
|
|
67
|
+
MicroVerticalReleaseEnvelopePayload & {
|
|
68
|
+
envelopeDigest: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type CreateMicroVerticalReleaseEnvelopeInput = {
|
|
72
|
+
artifactRoot: string;
|
|
73
|
+
target: MicroVerticalReleaseTarget;
|
|
74
|
+
identity: MicroVerticalReleaseIdentity;
|
|
75
|
+
artifacts: MicroVerticalReleaseArtifactInput[];
|
|
76
|
+
surfaces: MicroVerticalReleaseSurfaces;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type VerifyMicroVerticalReleaseEnvelopeOptions = {
|
|
80
|
+
artifactRoot: string;
|
|
81
|
+
logicalPathForArtifact?: (artifact: MicroVerticalReleaseArtifact) => string;
|
|
82
|
+
expectedTarget?: MicroVerticalReleaseTarget;
|
|
83
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
const BUILD_MARKER_NAMESPACE =
|
|
5
|
+
'ultramodern-delivery-unit-release-build-marker:v1';
|
|
6
|
+
const PROMOTABLE_SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
7
|
+
|
|
8
|
+
export type UltramodernReleaseIdentity = {
|
|
9
|
+
buildMarker: string;
|
|
10
|
+
sourceRevision: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const gitOutput = (
|
|
14
|
+
workspaceRoot: string,
|
|
15
|
+
args: string[],
|
|
16
|
+
): string | undefined => {
|
|
17
|
+
try {
|
|
18
|
+
return execFileSync('git', ['-C', workspaceRoot, ...args], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
21
|
+
}).trim();
|
|
22
|
+
} catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const createUltramodernReleaseBuildMarker = ({
|
|
28
|
+
generationBuildMarker,
|
|
29
|
+
sourceRevision,
|
|
30
|
+
unitId,
|
|
31
|
+
}: {
|
|
32
|
+
generationBuildMarker: string;
|
|
33
|
+
sourceRevision: string;
|
|
34
|
+
unitId: string;
|
|
35
|
+
}): string =>
|
|
36
|
+
createHash('sha256')
|
|
37
|
+
.update(
|
|
38
|
+
`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`,
|
|
39
|
+
)
|
|
40
|
+
.digest('hex')
|
|
41
|
+
.slice(0, 16);
|
|
42
|
+
|
|
43
|
+
export const resolveUltramodernSourceRevision = (
|
|
44
|
+
workspaceRoot = process.cwd(),
|
|
45
|
+
configuredSourceRevision?: string,
|
|
46
|
+
): string => {
|
|
47
|
+
const configuredArgumentValue = configuredSourceRevision?.trim();
|
|
48
|
+
const configuredEnvironmentValue =
|
|
49
|
+
process.env.ULTRAMODERN_SOURCE_REVISION?.trim();
|
|
50
|
+
const configuredArgument =
|
|
51
|
+
configuredArgumentValue && configuredArgumentValue !== 'workspace'
|
|
52
|
+
? configuredArgumentValue
|
|
53
|
+
: undefined;
|
|
54
|
+
const configuredEnvironment =
|
|
55
|
+
configuredEnvironmentValue && configuredEnvironmentValue !== 'workspace'
|
|
56
|
+
? configuredEnvironmentValue
|
|
57
|
+
: undefined;
|
|
58
|
+
if (
|
|
59
|
+
configuredArgument &&
|
|
60
|
+
configuredEnvironment &&
|
|
61
|
+
configuredArgument !== configuredEnvironment
|
|
62
|
+
) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Configured source revision ${configuredArgument} does not match environment source revision ${configuredEnvironment}.`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const configured = configuredArgument ?? configuredEnvironment;
|
|
68
|
+
const gitRoot = gitOutput(workspaceRoot, ['rev-parse', '--show-toplevel']);
|
|
69
|
+
if (!gitRoot) {
|
|
70
|
+
// Source archives and synthetic non-Git proofs can provide an externally
|
|
71
|
+
// authenticated immutable revision. Git worktrees are handled below so
|
|
72
|
+
// this override can never conceal dirty or mismatched checkout bytes.
|
|
73
|
+
if (!configured) {
|
|
74
|
+
return 'workspace';
|
|
75
|
+
}
|
|
76
|
+
if (!PROMOTABLE_SOURCE_REVISION_PATTERN.test(configured)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Configured source revision ${configured} must be an exact lowercase 40- or 64-character Git object ID.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return configured;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const revision = gitOutput(gitRoot, ['rev-parse', 'HEAD']);
|
|
85
|
+
const status = gitOutput(gitRoot, [
|
|
86
|
+
'status',
|
|
87
|
+
'--porcelain=v1',
|
|
88
|
+
'--untracked-files=all',
|
|
89
|
+
]);
|
|
90
|
+
if (!revision || status === undefined || status.length > 0) {
|
|
91
|
+
// Development remains usable, but the release-envelope gate rejects this
|
|
92
|
+
// non-promotable identity. Never label dirty bytes with the clean HEAD.
|
|
93
|
+
return 'workspace';
|
|
94
|
+
}
|
|
95
|
+
if (!PROMOTABLE_SOURCE_REVISION_PATTERN.test(revision)) {
|
|
96
|
+
return 'workspace';
|
|
97
|
+
}
|
|
98
|
+
if (configured && configured !== revision) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Configured source revision ${configured} does not match clean Git HEAD ${revision}.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return revision;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const resolveUltramodernReleaseIdentity = ({
|
|
108
|
+
generationBuildMarker,
|
|
109
|
+
sourceRevision: configuredSourceRevision,
|
|
110
|
+
unitId,
|
|
111
|
+
workspaceRoot,
|
|
112
|
+
}: {
|
|
113
|
+
generationBuildMarker: string;
|
|
114
|
+
sourceRevision?: string;
|
|
115
|
+
unitId: string;
|
|
116
|
+
workspaceRoot?: string;
|
|
117
|
+
}): UltramodernReleaseIdentity => {
|
|
118
|
+
const explicitSourceRevision = configuredSourceRevision?.trim();
|
|
119
|
+
const sourceRevision = resolveUltramodernSourceRevision(
|
|
120
|
+
workspaceRoot,
|
|
121
|
+
explicitSourceRevision,
|
|
122
|
+
);
|
|
123
|
+
return {
|
|
124
|
+
buildMarker:
|
|
125
|
+
sourceRevision === 'workspace'
|
|
126
|
+
? generationBuildMarker
|
|
127
|
+
: createUltramodernReleaseBuildMarker({
|
|
128
|
+
generationBuildMarker,
|
|
129
|
+
sourceRevision,
|
|
130
|
+
unitId,
|
|
131
|
+
}),
|
|
132
|
+
sourceRevision,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const ASSETS_BINDING = 'ASSETS';
|
|
2
|
+
const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
3
|
+
export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
|
|
4
|
+
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
5
|
+
const workerModulePromises = new Map();
|
|
6
|
+
const effectBffDispatcherPromises = new Map();
|
|
7
|
+
const remoteJsonPromises = new Map();
|
|
8
|
+
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
9
|
+
const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
|
|
10
|
+
const APP_CORS_ALLOWED_ORIGINS = (CORS_POLICY.allowedOrigins || []).map(
|
|
11
|
+
origin => String(origin).toLowerCase(),
|
|
12
|
+
);
|
|
13
|
+
const APP_CORS_ALLOWED_METHODS = (
|
|
14
|
+
CORS_POLICY.allowedMethods?.length
|
|
15
|
+
? CORS_POLICY.allowedMethods
|
|
16
|
+
: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
|
|
17
|
+
).join(', ');
|
|
18
|
+
const APP_CORS_ALLOWED_HEADERS = (
|
|
19
|
+
CORS_POLICY.allowedHeaders?.length ? CORS_POLICY.allowedHeaders : ['*']
|
|
20
|
+
).join(', ');
|
|
21
|
+
const ASSET_CORS_HEADERS = {
|
|
22
|
+
'access-control-allow-headers': '*',
|
|
23
|
+
'access-control-allow-methods': 'GET, HEAD, OPTIONS',
|
|
24
|
+
'access-control-allow-origin': '*',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
globalThis.__dirname ??= '/';
|
|
28
|
+
globalThis.__filename ??= '/index.js';
|
|
29
|
+
|
|
30
|
+
function getAllowedAppCorsOrigin(request) {
|
|
31
|
+
if (APP_CORS_ALLOWED_ORIGINS.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const origin = request.headers.get('origin');
|
|
36
|
+
|
|
37
|
+
if (!origin) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (APP_CORS_ALLOWED_ORIGINS.includes('*')) {
|
|
42
|
+
return '*';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return APP_CORS_ALLOWED_ORIGINS.includes(origin.toLowerCase())
|
|
46
|
+
? origin
|
|
47
|
+
: null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function appendVaryOrigin(headers) {
|
|
51
|
+
const vary = headers.get('vary');
|
|
52
|
+
|
|
53
|
+
if (!vary) {
|
|
54
|
+
headers.set('vary', 'origin');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const varyValues = vary.split(',').map(value => value.trim().toLowerCase());
|
|
59
|
+
|
|
60
|
+
if (!varyValues.includes('origin')) {
|
|
61
|
+
headers.set('vary', `${vary}, origin`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function withAppCorsHeaders(response, request) {
|
|
66
|
+
const allowedOrigin = getAllowedAppCorsOrigin(request);
|
|
67
|
+
|
|
68
|
+
if (!allowedOrigin) {
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const headers = new Headers(response.headers);
|
|
73
|
+
|
|
74
|
+
if (!headers.has('access-control-allow-origin')) {
|
|
75
|
+
headers.set('access-control-allow-origin', allowedOrigin);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (allowedOrigin !== '*') {
|
|
79
|
+
appendVaryOrigin(headers);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Response(response.body, {
|
|
83
|
+
headers,
|
|
84
|
+
status: response.status,
|
|
85
|
+
statusText: response.statusText,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function withAssetCorsHeaders(response) {
|
|
90
|
+
if (!ASSET_CORS_ENABLED) {
|
|
91
|
+
return response;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const headers = new Headers(response.headers);
|
|
95
|
+
|
|
96
|
+
for (const [name, value] of Object.entries(ASSET_CORS_HEADERS)) {
|
|
97
|
+
if (!headers.has(name)) {
|
|
98
|
+
headers.set(name, value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return new Response(response.body, {
|
|
103
|
+
headers,
|
|
104
|
+
status: response.status,
|
|
105
|
+
statusText: response.statusText,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function setHeaderIfEnabled(headers, name, value) {
|
|
110
|
+
if (value === false || typeof value !== 'string' || value.trim() === '') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!headers.has(name)) {
|
|
115
|
+
headers.set(name, value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function renderContentSecurityPolicy(directives) {
|
|
120
|
+
return Object.entries(directives || {})
|
|
121
|
+
.filter(([, values]) => Array.isArray(values) && values.length > 0)
|
|
122
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
123
|
+
.map(([name, values]) => `${name} ${values.join(' ')}`)
|
|
124
|
+
.join('; ');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isHtmlResponse(response) {
|
|
128
|
+
return (response.headers.get('content-type') || '').includes('text/html');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function matchesPreviewHostname(hostname, pattern) {
|
|
132
|
+
const normalizedHostname = hostname.toLowerCase();
|
|
133
|
+
const normalizedPattern = String(pattern || '').toLowerCase();
|
|
134
|
+
|
|
135
|
+
if (!normalizedPattern) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (normalizedPattern.startsWith('*.')) {
|
|
140
|
+
return normalizedHostname.endsWith(normalizedPattern.slice(1));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return normalizedHostname === normalizedPattern;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function shouldNoindex(request, noindex) {
|
|
147
|
+
if (!noindex || noindex === false) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { hostname } = new URL(request.url);
|
|
152
|
+
const normalizedHostname = hostname.toLowerCase();
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
noindex.localhost !== false &&
|
|
156
|
+
(normalizedHostname === 'localhost' ||
|
|
157
|
+
normalizedHostname === '127.0.0.1' ||
|
|
158
|
+
normalizedHostname === '[::1]')
|
|
159
|
+
) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (
|
|
164
|
+
noindex.workersDev !== false &&
|
|
165
|
+
normalizedHostname.endsWith('.workers.dev')
|
|
166
|
+
) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return (noindex.previewHostnames || []).some(pattern =>
|
|
171
|
+
matchesPreviewHostname(normalizedHostname, pattern),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function withCloudflareSecurityHeaders(response, request) {
|
|
176
|
+
const security = MODERN_WORKER_MANIFEST.security;
|
|
177
|
+
|
|
178
|
+
if (!security || security.enabled === false) {
|
|
179
|
+
return response;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const headers = new Headers(response.headers);
|
|
183
|
+
const configuredHeaders = security.headers || {};
|
|
184
|
+
|
|
185
|
+
setHeaderIfEnabled(
|
|
186
|
+
headers,
|
|
187
|
+
'referrer-policy',
|
|
188
|
+
configuredHeaders.referrerPolicy,
|
|
189
|
+
);
|
|
190
|
+
setHeaderIfEnabled(
|
|
191
|
+
headers,
|
|
192
|
+
'x-content-type-options',
|
|
193
|
+
configuredHeaders.contentTypeOptions,
|
|
194
|
+
);
|
|
195
|
+
setHeaderIfEnabled(
|
|
196
|
+
headers,
|
|
197
|
+
'permissions-policy',
|
|
198
|
+
configuredHeaders.permissionsPolicy,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const csp = security.contentSecurityPolicy;
|
|
202
|
+
const cspHeader =
|
|
203
|
+
csp?.mode === 'enforce'
|
|
204
|
+
? 'content-security-policy'
|
|
205
|
+
: 'content-security-policy-report-only';
|
|
206
|
+
const cspValue = renderContentSecurityPolicy(csp?.directives);
|
|
207
|
+
|
|
208
|
+
if (
|
|
209
|
+
isHtmlResponse(response) &&
|
|
210
|
+
csp?.mode !== 'off' &&
|
|
211
|
+
cspValue &&
|
|
212
|
+
!headers.has(cspHeader)
|
|
213
|
+
) {
|
|
214
|
+
headers.set(cspHeader, cspValue);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (shouldNoindex(request, security.noindex)) {
|
|
218
|
+
headers.set('x-robots-tag', 'noindex, nofollow');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return new Response(response.body, {
|
|
222
|
+
headers,
|
|
223
|
+
status: response.status,
|
|
224
|
+
statusText: response.statusText,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function createRenderableRequest(request) {
|
|
229
|
+
if (request.method !== 'HEAD') {
|
|
230
|
+
return request;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new Request(request, { method: 'GET' });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function finalizeResponseForRequest(response, request) {
|
|
237
|
+
const securedResponse = withCloudflareSecurityHeaders(response, request);
|
|
238
|
+
|
|
239
|
+
if (request.method !== 'HEAD') {
|
|
240
|
+
return securedResponse;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const headers = new Headers(securedResponse.headers);
|
|
244
|
+
headers.delete('content-length');
|
|
245
|
+
|
|
246
|
+
return new Response(null, {
|
|
247
|
+
headers,
|
|
248
|
+
status: securedResponse.status,
|
|
249
|
+
statusText: securedResponse.statusText,
|
|
250
|
+
});
|
|
251
|
+
}
|