@craft-ts/deploy-alchemy 0.7.0-beta.15
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 +38 -0
- package/package.json +53 -0
- package/src/index.d.ts +20 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +17 -0
- package/src/index.js.map +1 -0
- package/src/lib/alchemy-runtime.d.ts +22 -0
- package/src/lib/alchemy-runtime.d.ts.map +1 -0
- package/src/lib/alchemy-runtime.js +150 -0
- package/src/lib/alchemy-runtime.js.map +1 -0
- package/src/lib/credentials.d.ts +6 -0
- package/src/lib/credentials.d.ts.map +1 -0
- package/src/lib/credentials.js +78 -0
- package/src/lib/credentials.js.map +1 -0
- package/src/lib/naming.d.ts +8 -0
- package/src/lib/naming.d.ts.map +1 -0
- package/src/lib/naming.js +16 -0
- package/src/lib/naming.js.map +1 -0
- package/src/lib/plan.d.ts +24 -0
- package/src/lib/plan.d.ts.map +1 -0
- package/src/lib/plan.js +116 -0
- package/src/lib/plan.js.map +1 -0
- package/src/lib/presets/aws.d.ts +13 -0
- package/src/lib/presets/aws.d.ts.map +1 -0
- package/src/lib/presets/aws.js +133 -0
- package/src/lib/presets/aws.js.map +1 -0
- package/src/lib/presets/cloudflare.d.ts +11 -0
- package/src/lib/presets/cloudflare.d.ts.map +1 -0
- package/src/lib/presets/cloudflare.js +121 -0
- package/src/lib/presets/cloudflare.js.map +1 -0
- package/src/lib/presets/preset.d.ts +13 -0
- package/src/lib/presets/preset.d.ts.map +1 -0
- package/src/lib/presets/preset.js +16 -0
- package/src/lib/presets/preset.js.map +1 -0
- package/src/lib/provider.d.ts +22 -0
- package/src/lib/provider.d.ts.map +1 -0
- package/src/lib/provider.js +146 -0
- package/src/lib/provider.js.map +1 -0
- package/src/lib/runtime.d.ts +51 -0
- package/src/lib/runtime.d.ts.map +1 -0
- package/src/lib/runtime.js +9 -0
- package/src/lib/runtime.js.map +1 -0
package/src/lib/plan.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { awsPreset } from './presets/aws.js';
|
|
2
|
+
import { cloudflarePreset } from './presets/cloudflare.js';
|
|
3
|
+
export const ALCHEMY_PRESETS = Object.freeze({
|
|
4
|
+
cloudflare: cloudflarePreset,
|
|
5
|
+
aws: awsPreset,
|
|
6
|
+
});
|
|
7
|
+
/**
|
|
8
|
+
* Turns a manifest and the recorded state into the list of actions a
|
|
9
|
+
* deployment would take.
|
|
10
|
+
*
|
|
11
|
+
* The function is pure: it reads no environment, opens no connection and
|
|
12
|
+
* touches no infrastructure, which is what makes a preview trustworthy.
|
|
13
|
+
*/
|
|
14
|
+
export function planAlchemyDeployment(input) {
|
|
15
|
+
const { request, existing } = input;
|
|
16
|
+
const preset = ALCHEMY_PRESETS[request.manifest.platform];
|
|
17
|
+
if (!preset) {
|
|
18
|
+
return {
|
|
19
|
+
plan: {
|
|
20
|
+
provider: 'alchemy',
|
|
21
|
+
stage: request.stage,
|
|
22
|
+
resources: [],
|
|
23
|
+
notes: [],
|
|
24
|
+
},
|
|
25
|
+
resources: [],
|
|
26
|
+
diagnostics: [
|
|
27
|
+
{
|
|
28
|
+
code: 'CRAFT_DEPLOY_PROVIDER_PLATFORM_UNSUPPORTED',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
provider: 'alchemy',
|
|
31
|
+
platform: request.manifest.platform,
|
|
32
|
+
message: `Alchemy has no preset for \`${request.manifest.platform}\`.`,
|
|
33
|
+
fix: `Use one of ${Object.keys(ALCHEMY_PRESETS).join(', ')}, or another provider.`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const { resources, notes, diagnostics } = preset(request);
|
|
39
|
+
const declared = new Set(resources.map(identify));
|
|
40
|
+
const planned = resources.map((resource) => ({
|
|
41
|
+
type: resource.type,
|
|
42
|
+
name: resource.name,
|
|
43
|
+
action: actionFor(resource, existing),
|
|
44
|
+
details: describe(resource.properties),
|
|
45
|
+
}));
|
|
46
|
+
// A resource Alchemy still records but the manifest no longer declares is
|
|
47
|
+
// deleted on finalize; a preview that hid it would understate the change.
|
|
48
|
+
for (const state of existing) {
|
|
49
|
+
if (declared.has(identify(state)))
|
|
50
|
+
continue;
|
|
51
|
+
planned.push({
|
|
52
|
+
type: state.type,
|
|
53
|
+
name: state.name,
|
|
54
|
+
action: 'delete',
|
|
55
|
+
details: describe(state.outputs),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
plan: {
|
|
60
|
+
provider: 'alchemy',
|
|
61
|
+
stage: request.stage,
|
|
62
|
+
resources: planned,
|
|
63
|
+
notes,
|
|
64
|
+
},
|
|
65
|
+
resources,
|
|
66
|
+
diagnostics,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function identify(resource) {
|
|
70
|
+
return `${resource.type}#${resource.name}`;
|
|
71
|
+
}
|
|
72
|
+
function actionFor(resource, existing) {
|
|
73
|
+
const recorded = existing.find((state) => identify(state) === identify(resource));
|
|
74
|
+
if (!recorded)
|
|
75
|
+
return 'create';
|
|
76
|
+
if (!recorded.properties)
|
|
77
|
+
return 'update';
|
|
78
|
+
return sameProperties(recorded.properties, resource.properties)
|
|
79
|
+
? 'unchanged'
|
|
80
|
+
: 'update';
|
|
81
|
+
}
|
|
82
|
+
function sameProperties(recorded, declared) {
|
|
83
|
+
return stableJson(recorded) === stableJson(declared);
|
|
84
|
+
}
|
|
85
|
+
function stableJson(value) {
|
|
86
|
+
return JSON.stringify(sortValue(value));
|
|
87
|
+
}
|
|
88
|
+
function sortValue(value) {
|
|
89
|
+
if (Array.isArray(value))
|
|
90
|
+
return value.map(sortValue);
|
|
91
|
+
if (typeof value === 'object' && value !== null) {
|
|
92
|
+
const record = value;
|
|
93
|
+
const sorted = {};
|
|
94
|
+
for (const key of Object.keys(record).sort()) {
|
|
95
|
+
if (record[key] !== undefined)
|
|
96
|
+
sorted[key] = sortValue(record[key]);
|
|
97
|
+
}
|
|
98
|
+
return sorted;
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
/** Renders properties as the flat, secret-free facts a preview shows. */
|
|
103
|
+
function describe(properties) {
|
|
104
|
+
const details = {};
|
|
105
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
106
|
+
if (value === undefined || value === null)
|
|
107
|
+
continue;
|
|
108
|
+
details[key] = Array.isArray(value)
|
|
109
|
+
? value.join(', ')
|
|
110
|
+
: typeof value === 'object'
|
|
111
|
+
? JSON.stringify(value)
|
|
112
|
+
: String(value);
|
|
113
|
+
}
|
|
114
|
+
return details;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/plan.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CraftDeploymentDiagnostic,\n CraftDeploymentPlan,\n CraftDeploymentPlannedResource,\n CraftDeploymentRequest,\n} from '@craft-ts/deploy';\nimport { awsPreset } from './presets/aws.js';\nimport { cloudflarePreset } from './presets/cloudflare.js';\nimport type { AlchemyPreset } from './presets/preset.js';\nimport type {\n AlchemyResourceRequest,\n AlchemyResourceState,\n} from './runtime.js';\n\nexport const ALCHEMY_PRESETS: Readonly<Record<string, AlchemyPreset>> =\n Object.freeze({\n cloudflare: cloudflarePreset,\n aws: awsPreset,\n });\n\nexport type AlchemyPlanInput = Readonly<{\n request: CraftDeploymentRequest;\n /** What Alchemy already records for this application and stage. */\n existing: readonly AlchemyResourceState[];\n}>;\n\nexport type AlchemyPlanResult = Readonly<{\n plan: CraftDeploymentPlan;\n /** Resources to apply, in declaration order, when the plan is approved. */\n resources: readonly AlchemyResourceRequest[];\n diagnostics: readonly CraftDeploymentDiagnostic[];\n}>;\n\n/**\n * Turns a manifest and the recorded state into the list of actions a\n * deployment would take.\n *\n * The function is pure: it reads no environment, opens no connection and\n * touches no infrastructure, which is what makes a preview trustworthy.\n */\nexport function planAlchemyDeployment(\n input: AlchemyPlanInput,\n): AlchemyPlanResult {\n const { request, existing } = input;\n const preset = ALCHEMY_PRESETS[request.manifest.platform];\n\n if (!preset) {\n return {\n plan: {\n provider: 'alchemy',\n stage: request.stage,\n resources: [],\n notes: [],\n },\n resources: [],\n diagnostics: [\n {\n code: 'CRAFT_DEPLOY_PROVIDER_PLATFORM_UNSUPPORTED',\n severity: 'error',\n provider: 'alchemy',\n platform: request.manifest.platform,\n message: `Alchemy has no preset for \\`${request.manifest.platform}\\`.`,\n fix: `Use one of ${Object.keys(ALCHEMY_PRESETS).join(', ')}, or another provider.`,\n },\n ],\n };\n }\n\n const { resources, notes, diagnostics } = preset(request);\n const declared = new Set(resources.map(identify));\n const planned: CraftDeploymentPlannedResource[] = resources.map(\n (resource) => ({\n type: resource.type,\n name: resource.name,\n action: actionFor(resource, existing),\n details: describe(resource.properties),\n }),\n );\n\n // A resource Alchemy still records but the manifest no longer declares is\n // deleted on finalize; a preview that hid it would understate the change.\n for (const state of existing) {\n if (declared.has(identify(state))) continue;\n planned.push({\n type: state.type,\n name: state.name,\n action: 'delete',\n details: describe(state.outputs),\n });\n }\n\n return {\n plan: {\n provider: 'alchemy',\n stage: request.stage,\n resources: planned,\n notes,\n },\n resources,\n diagnostics,\n };\n}\n\nfunction identify(resource: { type: string; name: string }): string {\n return `${resource.type}#${resource.name}`;\n}\n\nfunction actionFor(\n resource: AlchemyResourceRequest,\n existing: readonly AlchemyResourceState[],\n): CraftDeploymentPlannedResource['action'] {\n const recorded = existing.find(\n (state) => identify(state) === identify(resource),\n );\n if (!recorded) return 'create';\n if (!recorded.properties) return 'update';\n return sameProperties(recorded.properties, resource.properties)\n ? 'unchanged'\n : 'update';\n}\n\nfunction sameProperties(\n recorded: Readonly<Record<string, unknown>>,\n declared: Readonly<Record<string, unknown>>,\n): boolean {\n return stableJson(recorded) === stableJson(declared);\n}\n\nfunction stableJson(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortValue);\n if (typeof value === 'object' && value !== null) {\n const record = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(record).sort()) {\n if (record[key] !== undefined) sorted[key] = sortValue(record[key]);\n }\n return sorted;\n }\n return value;\n}\n\n/** Renders properties as the flat, secret-free facts a preview shows. */\nfunction describe(\n properties: Readonly<Record<string, unknown>>,\n): Readonly<Record<string, string>> {\n const details: Record<string, string> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (value === undefined || value === null) continue;\n details[key] = Array.isArray(value)\n ? value.join(', ')\n : typeof value === 'object'\n ? JSON.stringify(value)\n : String(value);\n }\n return details;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CraftDeploymentRequest } from '@craft-ts/deploy';
|
|
2
|
+
import { type AlchemyPresetResult } from './preset.js';
|
|
3
|
+
/**
|
|
4
|
+
* AWS preset.
|
|
5
|
+
*
|
|
6
|
+
* `lambda` is the first-class shape: one function per deployment unit, exposed
|
|
7
|
+
* through a Function URL so a server-function keeps the same protocol it has
|
|
8
|
+
* locally. `static` becomes a bucket behind a distribution, and `node` falls
|
|
9
|
+
* back to a Fargate service because nothing else on AWS runs a long-lived Node
|
|
10
|
+
* server without more infrastructure than a manifest describes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function awsPreset(request: CraftDeploymentRequest): AlchemyPresetResult;
|
|
13
|
+
//# sourceMappingURL=aws.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws.d.ts","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/aws.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAG/D,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,sBAAsB,GAC9B,mBAAmB,CAyIrB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { alchemyResourceName } from '../naming.js';
|
|
2
|
+
import { environmentNames, unsupported, } from './preset.js';
|
|
3
|
+
/**
|
|
4
|
+
* AWS preset.
|
|
5
|
+
*
|
|
6
|
+
* `lambda` is the first-class shape: one function per deployment unit, exposed
|
|
7
|
+
* through a Function URL so a server-function keeps the same protocol it has
|
|
8
|
+
* locally. `static` becomes a bucket behind a distribution, and `node` falls
|
|
9
|
+
* back to a Fargate service because nothing else on AWS runs a long-lived Node
|
|
10
|
+
* server without more infrastructure than a manifest describes.
|
|
11
|
+
*/
|
|
12
|
+
export function awsPreset(request) {
|
|
13
|
+
const manifest = request.manifest;
|
|
14
|
+
const name = (suffix) => alchemyResourceName(manifest.name, request.stage, suffix);
|
|
15
|
+
if (manifest.runtime === 'lambda') {
|
|
16
|
+
const functionName = name('function');
|
|
17
|
+
const resources = [
|
|
18
|
+
{
|
|
19
|
+
type: 'aws:LambdaFunction',
|
|
20
|
+
name: functionName,
|
|
21
|
+
properties: {
|
|
22
|
+
entry: manifest.lambda.entry,
|
|
23
|
+
environment: environmentNames(request),
|
|
24
|
+
permissions: manifest.lambda.permissions.join(', '),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'aws:LambdaFunctionUrl',
|
|
29
|
+
name: name('function-url'),
|
|
30
|
+
properties: { function: functionName, authType: 'NONE' },
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
const notes = [
|
|
34
|
+
'The Function URL keeps the `{ id, input, context }` protocol, so a server-function behaves as it does locally.',
|
|
35
|
+
...(manifest.functions && manifest.functions.ids.length > 0
|
|
36
|
+
? [
|
|
37
|
+
`${manifest.functions.ids.length} server-function(s) share this deployment unit.`,
|
|
38
|
+
]
|
|
39
|
+
: []),
|
|
40
|
+
...(manifest.lambda.permissions.length === 0
|
|
41
|
+
? [
|
|
42
|
+
'No permission is declared: the function will only reach what its default execution role allows.',
|
|
43
|
+
]
|
|
44
|
+
: []),
|
|
45
|
+
];
|
|
46
|
+
return { resources, notes, diagnostics: [] };
|
|
47
|
+
}
|
|
48
|
+
if (manifest.runtime === 'static') {
|
|
49
|
+
const publicDir = manifest.artifact.publicDir;
|
|
50
|
+
if (!publicDir) {
|
|
51
|
+
return {
|
|
52
|
+
resources: [],
|
|
53
|
+
notes: [],
|
|
54
|
+
diagnostics: [
|
|
55
|
+
unsupported('The static manifest declares no public directory to upload.', 'Declare `client.outDir`, or `artifact.publicDir`.', request),
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const bucketName = name('assets');
|
|
60
|
+
const spa = manifest.static.mode === 'spa';
|
|
61
|
+
return {
|
|
62
|
+
resources: [
|
|
63
|
+
{
|
|
64
|
+
type: 'aws:Bucket',
|
|
65
|
+
name: bucketName,
|
|
66
|
+
properties: { directory: publicDir, public: false },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'aws:CloudFrontDistribution',
|
|
70
|
+
name: name('cdn'),
|
|
71
|
+
properties: {
|
|
72
|
+
origin: bucketName,
|
|
73
|
+
defaultRootObject: 'index.html',
|
|
74
|
+
...(spa
|
|
75
|
+
? { notFoundResponse: manifest.static.fallback }
|
|
76
|
+
: { prerenderedRoutes: manifest.static.routes.length }),
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
notes: [
|
|
81
|
+
spa
|
|
82
|
+
? `Unknown paths are rewritten to \`${manifest.static.fallback}\` by the distribution.`
|
|
83
|
+
: `${manifest.static.routes.length} pre-rendered document(s) are served directly by the distribution.`,
|
|
84
|
+
],
|
|
85
|
+
diagnostics: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (manifest.runtime === 'node') {
|
|
89
|
+
if (!manifest.artifact.start) {
|
|
90
|
+
return {
|
|
91
|
+
resources: [],
|
|
92
|
+
notes: [],
|
|
93
|
+
diagnostics: [
|
|
94
|
+
unsupported('A Fargate service needs a start command and the manifest declares none.', 'Declare `server.start`, or `artifact.start`.', request),
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const clusterName = name('cluster');
|
|
99
|
+
return {
|
|
100
|
+
resources: [
|
|
101
|
+
{
|
|
102
|
+
type: 'aws:EcsCluster',
|
|
103
|
+
name: clusterName,
|
|
104
|
+
properties: {},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'aws:EcsService',
|
|
108
|
+
name: name('service'),
|
|
109
|
+
properties: {
|
|
110
|
+
cluster: clusterName,
|
|
111
|
+
start: manifest.artifact.start,
|
|
112
|
+
healthPath: manifest.server.healthPath,
|
|
113
|
+
readyPath: manifest.server.readyPath,
|
|
114
|
+
environment: environmentNames(request),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
notes: [
|
|
119
|
+
'The Fargate fallback runs the artefact as a container: the image build stays outside CraftTS.',
|
|
120
|
+
`Readiness is taken from \`${manifest.server.readyPath}\`, so a rollout waits for the application, not for the container.`,
|
|
121
|
+
],
|
|
122
|
+
diagnostics: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
resources: [],
|
|
127
|
+
notes: [],
|
|
128
|
+
diagnostics: [
|
|
129
|
+
unsupported(`AWS cannot execute the \`${manifest.runtime}\` runtime.`, 'Use `lambda`, `static` or `node` on AWS.', request),
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=aws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/aws.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CraftDeploymentRequest } from '@craft-ts/deploy';\nimport { alchemyResourceName } from '../naming.js';\nimport type { AlchemyResourceRequest } from '../runtime.js';\nimport {\n environmentNames,\n unsupported,\n type AlchemyPresetResult,\n} from './preset.js';\n\n/**\n * AWS preset.\n *\n * `lambda` is the first-class shape: one function per deployment unit, exposed\n * through a Function URL so a server-function keeps the same protocol it has\n * locally. `static` becomes a bucket behind a distribution, and `node` falls\n * back to a Fargate service because nothing else on AWS runs a long-lived Node\n * server without more infrastructure than a manifest describes.\n */\nexport function awsPreset(\n request: CraftDeploymentRequest,\n): AlchemyPresetResult {\n const manifest = request.manifest;\n const name = (suffix: string) =>\n alchemyResourceName(manifest.name, request.stage, suffix);\n\n if (manifest.runtime === 'lambda') {\n const functionName = name('function');\n const resources: AlchemyResourceRequest[] = [\n {\n type: 'aws:LambdaFunction',\n name: functionName,\n properties: {\n entry: manifest.lambda.entry,\n environment: environmentNames(request),\n permissions: manifest.lambda.permissions.join(', '),\n },\n },\n {\n type: 'aws:LambdaFunctionUrl',\n name: name('function-url'),\n properties: { function: functionName, authType: 'NONE' },\n },\n ];\n const notes = [\n 'The Function URL keeps the `{ id, input, context }` protocol, so a server-function behaves as it does locally.',\n ...(manifest.functions && manifest.functions.ids.length > 0\n ? [\n `${manifest.functions.ids.length} server-function(s) share this deployment unit.`,\n ]\n : []),\n ...(manifest.lambda.permissions.length === 0\n ? [\n 'No permission is declared: the function will only reach what its default execution role allows.',\n ]\n : []),\n ];\n return { resources, notes, diagnostics: [] };\n }\n\n if (manifest.runtime === 'static') {\n const publicDir = manifest.artifact.publicDir;\n if (!publicDir) {\n return {\n resources: [],\n notes: [],\n diagnostics: [\n unsupported(\n 'The static manifest declares no public directory to upload.',\n 'Declare `client.outDir`, or `artifact.publicDir`.',\n request,\n ),\n ],\n };\n }\n const bucketName = name('assets');\n const spa = manifest.static.mode === 'spa';\n return {\n resources: [\n {\n type: 'aws:Bucket',\n name: bucketName,\n properties: { directory: publicDir, public: false },\n },\n {\n type: 'aws:CloudFrontDistribution',\n name: name('cdn'),\n properties: {\n origin: bucketName,\n defaultRootObject: 'index.html',\n ...(spa\n ? { notFoundResponse: manifest.static.fallback }\n : { prerenderedRoutes: manifest.static.routes.length }),\n },\n },\n ],\n notes: [\n spa\n ? `Unknown paths are rewritten to \\`${manifest.static.fallback}\\` by the distribution.`\n : `${manifest.static.routes.length} pre-rendered document(s) are served directly by the distribution.`,\n ],\n diagnostics: [],\n };\n }\n\n if (manifest.runtime === 'node') {\n if (!manifest.artifact.start) {\n return {\n resources: [],\n notes: [],\n diagnostics: [\n unsupported(\n 'A Fargate service needs a start command and the manifest declares none.',\n 'Declare `server.start`, or `artifact.start`.',\n request,\n ),\n ],\n };\n }\n const clusterName = name('cluster');\n return {\n resources: [\n {\n type: 'aws:EcsCluster',\n name: clusterName,\n properties: {},\n },\n {\n type: 'aws:EcsService',\n name: name('service'),\n properties: {\n cluster: clusterName,\n start: manifest.artifact.start,\n healthPath: manifest.server.healthPath,\n readyPath: manifest.server.readyPath,\n environment: environmentNames(request),\n },\n },\n ],\n notes: [\n 'The Fargate fallback runs the artefact as a container: the image build stays outside CraftTS.',\n `Readiness is taken from \\`${manifest.server.readyPath}\\`, so a rollout waits for the application, not for the container.`,\n ],\n diagnostics: [],\n };\n }\n\n return {\n resources: [],\n notes: [],\n diagnostics: [\n unsupported(\n `AWS cannot execute the \\`${manifest.runtime}\\` runtime.`,\n 'Use `lambda`, `static` or `node` on AWS.',\n request,\n ),\n ],\n };\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CraftDeploymentRequest } from '@craft-ts/deploy';
|
|
2
|
+
import { type AlchemyPresetResult } from './preset.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare preset.
|
|
5
|
+
*
|
|
6
|
+
* A `static` manifest becomes a StaticSite, a `worker` manifest becomes a
|
|
7
|
+
* Worker plus the resources its bindings name. Nothing else is invented: the
|
|
8
|
+
* preset reads the manifest and stops where the manifest stops.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cloudflarePreset(request: CraftDeploymentRequest): AlchemyPresetResult;
|
|
11
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/cloudflare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAcrB;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,sBAAsB,GAC9B,mBAAmB,CAiHrB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { alchemyResourceName } from '../naming.js';
|
|
2
|
+
import { environmentNames, unsupported, } from './preset.js';
|
|
3
|
+
/** Binding kinds Alchemy provisions, by the `type` the manifest declares. */
|
|
4
|
+
const BINDING_RESOURCES = {
|
|
5
|
+
kv: 'cloudflare:KVNamespace',
|
|
6
|
+
kv_namespace: 'cloudflare:KVNamespace',
|
|
7
|
+
r2: 'cloudflare:R2Bucket',
|
|
8
|
+
r2_bucket: 'cloudflare:R2Bucket',
|
|
9
|
+
d1: 'cloudflare:D1Database',
|
|
10
|
+
d1_database: 'cloudflare:D1Database',
|
|
11
|
+
queue: 'cloudflare:Queue',
|
|
12
|
+
durable_object: 'cloudflare:DurableObjectNamespace',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Cloudflare preset.
|
|
16
|
+
*
|
|
17
|
+
* A `static` manifest becomes a StaticSite, a `worker` manifest becomes a
|
|
18
|
+
* Worker plus the resources its bindings name. Nothing else is invented: the
|
|
19
|
+
* preset reads the manifest and stops where the manifest stops.
|
|
20
|
+
*/
|
|
21
|
+
export function cloudflarePreset(request) {
|
|
22
|
+
const manifest = request.manifest;
|
|
23
|
+
const name = (suffix) => alchemyResourceName(manifest.name, request.stage, suffix);
|
|
24
|
+
if (manifest.runtime === 'static') {
|
|
25
|
+
const publicDir = manifest.artifact.publicDir;
|
|
26
|
+
if (!publicDir) {
|
|
27
|
+
return {
|
|
28
|
+
resources: [],
|
|
29
|
+
notes: [],
|
|
30
|
+
diagnostics: [
|
|
31
|
+
unsupported('The static manifest declares no public directory to upload.', 'Declare `client.outDir`, or `artifact.publicDir`.', request),
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const spa = manifest.static.mode === 'spa';
|
|
36
|
+
return {
|
|
37
|
+
resources: [
|
|
38
|
+
{
|
|
39
|
+
type: 'cloudflare:StaticSite',
|
|
40
|
+
name: name('site'),
|
|
41
|
+
properties: {
|
|
42
|
+
directory: publicDir,
|
|
43
|
+
spa,
|
|
44
|
+
...(spa
|
|
45
|
+
? { notFoundPage: manifest.static.fallback }
|
|
46
|
+
: { prerenderedRoutes: manifest.static.routes.length }),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
notes: spa
|
|
51
|
+
? [`Unknown paths are answered with \`${manifest.static.fallback}\`.`]
|
|
52
|
+
: [
|
|
53
|
+
`${manifest.static.routes.length} pre-rendered route(s) are uploaded as documents.`,
|
|
54
|
+
...(manifest.static.serverRoutes.length > 0
|
|
55
|
+
? [
|
|
56
|
+
`${manifest.static.serverRoutes.length} route(s) still need a server runtime and are not covered by this deployment.`,
|
|
57
|
+
]
|
|
58
|
+
: []),
|
|
59
|
+
],
|
|
60
|
+
diagnostics: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (manifest.runtime !== 'worker') {
|
|
64
|
+
return {
|
|
65
|
+
resources: [],
|
|
66
|
+
notes: [],
|
|
67
|
+
diagnostics: [
|
|
68
|
+
unsupported(`Cloudflare cannot execute the \`${manifest.runtime}\` runtime.`, 'Use the `worker` runtime on Cloudflare, or deploy this manifest to another platform.', request),
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const resources = [];
|
|
73
|
+
const notes = [];
|
|
74
|
+
const diagnostics = [];
|
|
75
|
+
for (const binding of manifest.worker.bindings) {
|
|
76
|
+
const resource = bindingResource(binding, name);
|
|
77
|
+
if (resource === 'secret') {
|
|
78
|
+
notes.push(`The binding \`${binding.name}\` is a secret: its value must already exist in the Alchemy state or the environment; the plan never carries it.`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (resource === null) {
|
|
82
|
+
diagnostics.push(unsupported(`Alchemy has no Cloudflare resource for the binding type \`${binding.type}\` declared by \`${binding.name}\`.`, `Use one of ${Object.keys(BINDING_RESOURCES).join(', ')} or \`secret\`, or create the resource outside CraftTS.`, request));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
resources.push(resource);
|
|
86
|
+
}
|
|
87
|
+
const assets = manifest.artifact.publicDir;
|
|
88
|
+
resources.push({
|
|
89
|
+
type: 'cloudflare:Worker',
|
|
90
|
+
name: name('worker'),
|
|
91
|
+
properties: {
|
|
92
|
+
entrypoint: manifest.worker.entry,
|
|
93
|
+
...(assets ? { assets } : {}),
|
|
94
|
+
bindings: manifest.worker.bindings.map((binding) => binding.name),
|
|
95
|
+
environment: environmentNames(request),
|
|
96
|
+
...(manifest.functions
|
|
97
|
+
? { serverFunctionsBasePath: manifest.functions.basePath }
|
|
98
|
+
: {}),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (assets) {
|
|
102
|
+
notes.push(`Static assets are served by the Worker from \`${assets}\`.`);
|
|
103
|
+
}
|
|
104
|
+
if (manifest.functions && manifest.functions.ids.length > 0) {
|
|
105
|
+
notes.push(`${manifest.functions.ids.length} server-function(s) are exposed under \`${manifest.functions.basePath}\`.`);
|
|
106
|
+
}
|
|
107
|
+
return { resources, notes, diagnostics };
|
|
108
|
+
}
|
|
109
|
+
function bindingResource(binding, name) {
|
|
110
|
+
if (binding.type === 'secret')
|
|
111
|
+
return 'secret';
|
|
112
|
+
const type = BINDING_RESOURCES[binding.type.toLowerCase()];
|
|
113
|
+
if (!type)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
type,
|
|
117
|
+
name: name(binding.name),
|
|
118
|
+
properties: { binding: binding.name },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/cloudflare.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CraftDeploymentBinding,\n CraftDeploymentDiagnostic,\n CraftDeploymentRequest,\n} from '@craft-ts/deploy';\nimport { alchemyResourceName } from '../naming.js';\nimport type { AlchemyResourceRequest } from '../runtime.js';\nimport {\n environmentNames,\n unsupported,\n type AlchemyPresetResult,\n} from './preset.js';\n\n/** Binding kinds Alchemy provisions, by the `type` the manifest declares. */\nconst BINDING_RESOURCES: Readonly<Record<string, string>> = {\n kv: 'cloudflare:KVNamespace',\n kv_namespace: 'cloudflare:KVNamespace',\n r2: 'cloudflare:R2Bucket',\n r2_bucket: 'cloudflare:R2Bucket',\n d1: 'cloudflare:D1Database',\n d1_database: 'cloudflare:D1Database',\n queue: 'cloudflare:Queue',\n durable_object: 'cloudflare:DurableObjectNamespace',\n};\n\n/**\n * Cloudflare preset.\n *\n * A `static` manifest becomes a StaticSite, a `worker` manifest becomes a\n * Worker plus the resources its bindings name. Nothing else is invented: the\n * preset reads the manifest and stops where the manifest stops.\n */\nexport function cloudflarePreset(\n request: CraftDeploymentRequest,\n): AlchemyPresetResult {\n const manifest = request.manifest;\n const name = (suffix: string) =>\n alchemyResourceName(manifest.name, request.stage, suffix);\n\n if (manifest.runtime === 'static') {\n const publicDir = manifest.artifact.publicDir;\n if (!publicDir) {\n return {\n resources: [],\n notes: [],\n diagnostics: [\n unsupported(\n 'The static manifest declares no public directory to upload.',\n 'Declare `client.outDir`, or `artifact.publicDir`.',\n request,\n ),\n ],\n };\n }\n const spa = manifest.static.mode === 'spa';\n return {\n resources: [\n {\n type: 'cloudflare:StaticSite',\n name: name('site'),\n properties: {\n directory: publicDir,\n spa,\n ...(spa\n ? { notFoundPage: manifest.static.fallback }\n : { prerenderedRoutes: manifest.static.routes.length }),\n },\n },\n ],\n notes: spa\n ? [`Unknown paths are answered with \\`${manifest.static.fallback}\\`.`]\n : [\n `${manifest.static.routes.length} pre-rendered route(s) are uploaded as documents.`,\n ...(manifest.static.serverRoutes.length > 0\n ? [\n `${manifest.static.serverRoutes.length} route(s) still need a server runtime and are not covered by this deployment.`,\n ]\n : []),\n ],\n diagnostics: [],\n };\n }\n\n if (manifest.runtime !== 'worker') {\n return {\n resources: [],\n notes: [],\n diagnostics: [\n unsupported(\n `Cloudflare cannot execute the \\`${manifest.runtime}\\` runtime.`,\n 'Use the `worker` runtime on Cloudflare, or deploy this manifest to another platform.',\n request,\n ),\n ],\n };\n }\n\n const resources: AlchemyResourceRequest[] = [];\n const notes: string[] = [];\n const diagnostics: CraftDeploymentDiagnostic[] = [];\n\n for (const binding of manifest.worker.bindings) {\n const resource = bindingResource(binding, name);\n if (resource === 'secret') {\n notes.push(\n `The binding \\`${binding.name}\\` is a secret: its value must already exist in the Alchemy state or the environment; the plan never carries it.`,\n );\n continue;\n }\n if (resource === null) {\n diagnostics.push(\n unsupported(\n `Alchemy has no Cloudflare resource for the binding type \\`${binding.type}\\` declared by \\`${binding.name}\\`.`,\n `Use one of ${Object.keys(BINDING_RESOURCES).join(', ')} or \\`secret\\`, or create the resource outside CraftTS.`,\n request,\n ),\n );\n continue;\n }\n resources.push(resource);\n }\n\n const assets = manifest.artifact.publicDir;\n resources.push({\n type: 'cloudflare:Worker',\n name: name('worker'),\n properties: {\n entrypoint: manifest.worker.entry,\n ...(assets ? { assets } : {}),\n bindings: manifest.worker.bindings.map((binding) => binding.name),\n environment: environmentNames(request),\n ...(manifest.functions\n ? { serverFunctionsBasePath: manifest.functions.basePath }\n : {}),\n },\n });\n\n if (assets) {\n notes.push(`Static assets are served by the Worker from \\`${assets}\\`.`);\n }\n if (manifest.functions && manifest.functions.ids.length > 0) {\n notes.push(\n `${manifest.functions.ids.length} server-function(s) are exposed under \\`${manifest.functions.basePath}\\`.`,\n );\n }\n\n return { resources, notes, diagnostics };\n}\n\nfunction bindingResource(\n binding: CraftDeploymentBinding,\n name: (suffix: string) => string,\n): AlchemyResourceRequest | 'secret' | null {\n if (binding.type === 'secret') return 'secret';\n const type = BINDING_RESOURCES[binding.type.toLowerCase()];\n if (!type) return null;\n return {\n type,\n name: name(binding.name),\n properties: { binding: binding.name },\n };\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CraftDeploymentDiagnostic, CraftDeploymentRequest } from '@craft-ts/deploy';
|
|
2
|
+
import type { AlchemyResourceRequest } from '../runtime.js';
|
|
3
|
+
export type AlchemyPresetResult = Readonly<{
|
|
4
|
+
resources: readonly AlchemyResourceRequest[];
|
|
5
|
+
/** Facts an operator should know before approving the plan. */
|
|
6
|
+
notes: readonly string[];
|
|
7
|
+
diagnostics: readonly CraftDeploymentDiagnostic[];
|
|
8
|
+
}>;
|
|
9
|
+
export type AlchemyPreset = (request: CraftDeploymentRequest) => AlchemyPresetResult;
|
|
10
|
+
/** Names only: a manifest never carries a value, and neither does a plan. */
|
|
11
|
+
export declare function environmentNames(request: CraftDeploymentRequest): string;
|
|
12
|
+
export declare function unsupported(message: string, fix: string, request: CraftDeploymentRequest): CraftDeploymentDiagnostic;
|
|
13
|
+
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE5D,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC7C,+DAA+D;IAC/D,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACnD,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,sBAAsB,KAC5B,mBAAmB,CAAC;AAEzB,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAExE;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,sBAAsB,GAC9B,yBAAyB,CAU3B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Names only: a manifest never carries a value, and neither does a plan. */
|
|
2
|
+
export function environmentNames(request) {
|
|
3
|
+
return request.manifest.env.map((variable) => variable.name).join(', ');
|
|
4
|
+
}
|
|
5
|
+
export function unsupported(message, fix, request) {
|
|
6
|
+
return {
|
|
7
|
+
code: 'CRAFT_DEPLOY_PROVIDER_UNSUPPORTED_RESOURCE',
|
|
8
|
+
severity: 'error',
|
|
9
|
+
provider: 'alchemy',
|
|
10
|
+
runtime: request.manifest.runtime,
|
|
11
|
+
platform: request.manifest.platform,
|
|
12
|
+
message,
|
|
13
|
+
fix,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=preset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../../../../../../libs/deploy-alchemy/src/lib/presets/preset.ts"],"names":[],"mappings":"AAiBA,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,OAA+B;IAC9D,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAe,EACf,GAAW,EACX,OAA+B;IAE/B,OAAO;QACL,IAAI,EAAE,4CAA4C;QAClD,QAAQ,EAAE,OAAO;QACjB,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO;QACjC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ;QACnC,OAAO;QACP,GAAG;KACJ,CAAC;AACJ,CAAC","sourcesContent":["import type {\n CraftDeploymentDiagnostic,\n CraftDeploymentRequest,\n} from '@craft-ts/deploy';\nimport type { AlchemyResourceRequest } from '../runtime.js';\n\nexport type AlchemyPresetResult = Readonly<{\n resources: readonly AlchemyResourceRequest[];\n /** Facts an operator should know before approving the plan. */\n notes: readonly string[];\n diagnostics: readonly CraftDeploymentDiagnostic[];\n}>;\n\nexport type AlchemyPreset = (\n request: CraftDeploymentRequest,\n) => AlchemyPresetResult;\n\n/** Names only: a manifest never carries a value, and neither does a plan. */\nexport function environmentNames(request: CraftDeploymentRequest): string {\n return request.manifest.env.map((variable) => variable.name).join(', ');\n}\n\nexport function unsupported(\n message: string,\n fix: string,\n request: CraftDeploymentRequest,\n): CraftDeploymentDiagnostic {\n return {\n code: 'CRAFT_DEPLOY_PROVIDER_UNSUPPORTED_RESOURCE',\n severity: 'error',\n provider: 'alchemy',\n runtime: request.manifest.runtime,\n platform: request.manifest.platform,\n message,\n fix,\n };\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CraftDeploymentProvider } from '@craft-ts/deploy';
|
|
2
|
+
import { type AlchemyEnvironment } from './credentials.js';
|
|
3
|
+
import type { AlchemyRuntimeLoader } from './runtime.js';
|
|
4
|
+
export type AlchemyProviderOptions = Readonly<{
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the Alchemy runtime. Defaults to importing the optional peer
|
|
7
|
+
* dependency; tests and dry runs replace it.
|
|
8
|
+
*/
|
|
9
|
+
runtime?: AlchemyRuntimeLoader;
|
|
10
|
+
/** Environment the credentials are read from. Defaults to `process.env`. */
|
|
11
|
+
environment?: AlchemyEnvironment;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* The Alchemy provider.
|
|
15
|
+
*
|
|
16
|
+
* It consumes the manifest CraftTS produced and never rebuilds routes,
|
|
17
|
+
* contracts or layers. Everything platform-specific lives in a preset, and
|
|
18
|
+
* every mutation goes through the runtime port, so `preview` provably touches
|
|
19
|
+
* nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createAlchemyDeploymentProvider(options?: AlchemyProviderOptions): CraftDeploymentProvider;
|
|
22
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,uBAAuB,EAG7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC,CAAC,CAAC;AAIH;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sBAA2B,GACnC,uBAAuB,CAmEzB"}
|