@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Romain Geffrault
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @craft-ts/deploy-alchemy
|
|
2
|
+
|
|
3
|
+
Optional Alchemy provider for CraftTS deployments.
|
|
4
|
+
|
|
5
|
+
> **Experimental — never run against a live account.** The presets, the plan,
|
|
6
|
+
> the credential checks and the refusal paths are covered by tests through a
|
|
7
|
+
> runtime port. The adapter that calls Alchemy itself has never run against a
|
|
8
|
+
> real Cloudflare or AWS account from the CraftTS repository: treat your first
|
|
9
|
+
> deployment as its validation, and run `deploy preview` first.
|
|
10
|
+
|
|
11
|
+
It consumes the manifest produced by `@craft-ts/deploy` and turns it into
|
|
12
|
+
Cloudflare or AWS resources. It never rebuilds routes, contracts or layers, and
|
|
13
|
+
it is never a dependency of a CraftTS application bundle.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install --save-dev @craft-ts/deploy-alchemy alchemy
|
|
17
|
+
npx craft-ts deploy preview --provider alchemy --stage staging
|
|
18
|
+
npx craft-ts deploy --provider alchemy --stage staging --yes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`preview` opens Alchemy in its read phase: it lists what would be created,
|
|
22
|
+
updated or deleted and provably mutates nothing. `deploy` runs the same checks
|
|
23
|
+
and the same preview, then refuses to apply until `--yes` approves the plan.
|
|
24
|
+
|
|
25
|
+
| Manifest | Resources |
|
|
26
|
+
| ------------------------ | -------------------------------------------------- |
|
|
27
|
+
| `static` on `cloudflare` | `StaticSite` |
|
|
28
|
+
| `worker` on `cloudflare` | the bound `KVNamespace`/`R2Bucket`/… then `Worker` |
|
|
29
|
+
| `static` on `aws` | `Bucket` behind a `CloudFrontDistribution` |
|
|
30
|
+
| `lambda` on `aws` | `Function` plus its `FunctionUrl` |
|
|
31
|
+
| `node` on `aws` | Fargate `Cluster` and `Service` |
|
|
32
|
+
|
|
33
|
+
Credentials are read from the environment and never written anywhere:
|
|
34
|
+
`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`, or `AWS_ACCESS_KEY_ID` and
|
|
35
|
+
`AWS_REGION`, plus `ALCHEMY_PASSWORD` for the state.
|
|
36
|
+
|
|
37
|
+
See the [Alchemy provider guide](../../apps/docs/guide/deployment/alchemy.md)
|
|
38
|
+
for the state, the stages, the outputs and the rollback procedure.
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@craft-ts/deploy-alchemy",
|
|
3
|
+
"version": "0.7.0-beta.15",
|
|
4
|
+
"description": "Optional Alchemy provider for CraftTS deployments: Cloudflare and AWS presets driven by the deployment manifest",
|
|
5
|
+
"author": "Romain Geffrault",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://craft-ts.github.io/craft/",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/craft-ts/craft-ts/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/craft-ts/craft-ts.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"craft-ts",
|
|
17
|
+
"deployment",
|
|
18
|
+
"alchemy",
|
|
19
|
+
"cloudflare",
|
|
20
|
+
"aws",
|
|
21
|
+
"infrastructure",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.19.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./src/index.js",
|
|
33
|
+
"types": "./src/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
"./package.json": "./package.json",
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./src/index.d.ts",
|
|
38
|
+
"default": "./src/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@craft-ts/deploy": "^0.7.0-beta.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"alchemy": ">=0.70.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"alchemy": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"module": "./src/index.js"
|
|
53
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CraftDeploymentProvider } from '@craft-ts/deploy';
|
|
2
|
+
export { createAlchemyDeploymentProvider } from './lib/provider.js';
|
|
3
|
+
export type { AlchemyProviderOptions } from './lib/provider.js';
|
|
4
|
+
/**
|
|
5
|
+
* The factory the CraftTS CLI looks for when it resolves
|
|
6
|
+
* `@craft-ts/deploy-alchemy`. Keeping the name generic is what lets the CLI
|
|
7
|
+
* load any provider package without knowing it exists.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createCraftDeploymentProvider(options?: Readonly<Record<string, unknown>>): CraftDeploymentProvider;
|
|
10
|
+
export { alchemyCredentialNames, checkAlchemyCredentials, } from './lib/credentials.js';
|
|
11
|
+
export type { AlchemyEnvironment } from './lib/credentials.js';
|
|
12
|
+
export { ALCHEMY_PRESETS, planAlchemyDeployment } from './lib/plan.js';
|
|
13
|
+
export type { AlchemyPlanInput, AlchemyPlanResult } from './lib/plan.js';
|
|
14
|
+
export { awsPreset } from './lib/presets/aws.js';
|
|
15
|
+
export { cloudflarePreset } from './lib/presets/cloudflare.js';
|
|
16
|
+
export type { AlchemyPreset, AlchemyPresetResult, } from './lib/presets/preset.js';
|
|
17
|
+
export { alchemyResourceName } from './lib/naming.js';
|
|
18
|
+
export { ALCHEMY_RESOURCE_EXPORTS, loadAlchemyRuntime, } from './lib/alchemy-runtime.js';
|
|
19
|
+
export type { AlchemyOpenOptions, AlchemyPhase, AlchemyResourceRequest, AlchemyResourceState, AlchemyRuntime, AlchemyRuntimeLoader, AlchemyScope, } from './lib/runtime.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/deploy-alchemy/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAMhE,OAAO,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AACpE,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC9C,uBAAuB,CAEzB;AAED,OAAO,EACL,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACvE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,aAAa,EACb,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,YAAY,GACb,MAAM,kBAAkB,CAAC"}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAlchemyDeploymentProvider, } from './lib/provider.js';
|
|
2
|
+
export { createAlchemyDeploymentProvider } from './lib/provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* The factory the CraftTS CLI looks for when it resolves
|
|
5
|
+
* `@craft-ts/deploy-alchemy`. Keeping the name generic is what lets the CLI
|
|
6
|
+
* load any provider package without knowing it exists.
|
|
7
|
+
*/
|
|
8
|
+
export function createCraftDeploymentProvider(options = {}) {
|
|
9
|
+
return createAlchemyDeploymentProvider(options);
|
|
10
|
+
}
|
|
11
|
+
export { alchemyCredentialNames, checkAlchemyCredentials, } from './lib/credentials.js';
|
|
12
|
+
export { ALCHEMY_PRESETS, planAlchemyDeployment } from './lib/plan.js';
|
|
13
|
+
export { awsPreset } from './lib/presets/aws.js';
|
|
14
|
+
export { cloudflarePreset } from './lib/presets/cloudflare.js';
|
|
15
|
+
export { alchemyResourceName } from './lib/naming.js';
|
|
16
|
+
export { ALCHEMY_RESOURCE_EXPORTS, loadAlchemyRuntime, } from './lib/alchemy-runtime.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/deploy-alchemy/src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,+BAA+B,GAEhC,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAGpE;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,UAA6C,EAAE;IAE/C,OAAO,+BAA+B,CAAC,OAAiC,CAAC,CAAC;AAC5E,CAAC;AAED,OAAO,EACL,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAM/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC","sourcesContent":["import type { CraftDeploymentProvider } from '@craft-ts/deploy';\nimport {\n createAlchemyDeploymentProvider,\n type AlchemyProviderOptions,\n} from './lib/provider.js';\n\nexport { createAlchemyDeploymentProvider } from './lib/provider.js';\nexport type { AlchemyProviderOptions } from './lib/provider.js';\n\n/**\n * The factory the CraftTS CLI looks for when it resolves\n * `@craft-ts/deploy-alchemy`. Keeping the name generic is what lets the CLI\n * load any provider package without knowing it exists.\n */\nexport function createCraftDeploymentProvider(\n options: Readonly<Record<string, unknown>> = {},\n): CraftDeploymentProvider {\n return createAlchemyDeploymentProvider(options as AlchemyProviderOptions);\n}\n\nexport {\n alchemyCredentialNames,\n checkAlchemyCredentials,\n} from './lib/credentials.js';\nexport type { AlchemyEnvironment } from './lib/credentials.js';\n\nexport { ALCHEMY_PRESETS, planAlchemyDeployment } from './lib/plan.js';\nexport type { AlchemyPlanInput, AlchemyPlanResult } from './lib/plan.js';\n\nexport { awsPreset } from './lib/presets/aws.js';\nexport { cloudflarePreset } from './lib/presets/cloudflare.js';\nexport type {\n AlchemyPreset,\n AlchemyPresetResult,\n} from './lib/presets/preset.js';\n\nexport { alchemyResourceName } from './lib/naming.js';\n\nexport {\n ALCHEMY_RESOURCE_EXPORTS,\n loadAlchemyRuntime,\n} from './lib/alchemy-runtime.js';\n\nexport type {\n AlchemyOpenOptions,\n AlchemyPhase,\n AlchemyResourceRequest,\n AlchemyResourceState,\n AlchemyRuntime,\n AlchemyRuntimeLoader,\n AlchemyScope,\n} from './lib/runtime.js';\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AlchemyRuntime } from './runtime.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter over the real Alchemy package.
|
|
4
|
+
*
|
|
5
|
+
* Alchemy is an optional peer dependency, so it is imported lazily and only
|
|
6
|
+
* when a preview or a deployment actually runs. Everything version-specific is
|
|
7
|
+
* concentrated here: the presets and the planner never see an Alchemy type.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Where each planned resource type comes from.
|
|
11
|
+
*
|
|
12
|
+
* Alchemy exports its resources per platform module, so a plan type maps to a
|
|
13
|
+
* module plus an export. This table is the single place to adjust when Alchemy
|
|
14
|
+
* renames or moves a resource.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ALCHEMY_RESOURCE_EXPORTS: Readonly<Record<string, Readonly<{
|
|
17
|
+
module: string;
|
|
18
|
+
export: string;
|
|
19
|
+
}>>>;
|
|
20
|
+
/** Imports the optional peer dependency, or explains why it cannot. */
|
|
21
|
+
export declare function loadAlchemyRuntime(): Promise<AlchemyRuntime>;
|
|
22
|
+
//# sourceMappingURL=alchemy-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alchemy-runtime.d.ts","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/alchemy-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,cAAc,EAEf,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAC7C,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CA8B5D,CAAC;AAeH,uEAAuE;AACvE,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,cAAc,CAAC,CAQlE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Adapter over the real Alchemy package.
|
|
11
|
+
*
|
|
12
|
+
* Alchemy is an optional peer dependency, so it is imported lazily and only
|
|
13
|
+
* when a preview or a deployment actually runs. Everything version-specific is
|
|
14
|
+
* concentrated here: the presets and the planner never see an Alchemy type.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Where each planned resource type comes from.
|
|
18
|
+
*
|
|
19
|
+
* Alchemy exports its resources per platform module, so a plan type maps to a
|
|
20
|
+
* module plus an export. This table is the single place to adjust when Alchemy
|
|
21
|
+
* renames or moves a resource.
|
|
22
|
+
*/
|
|
23
|
+
export const ALCHEMY_RESOURCE_EXPORTS = Object.freeze({
|
|
24
|
+
'cloudflare:StaticSite': {
|
|
25
|
+
module: 'alchemy/cloudflare',
|
|
26
|
+
export: 'StaticSite',
|
|
27
|
+
},
|
|
28
|
+
'cloudflare:Worker': { module: 'alchemy/cloudflare', export: 'Worker' },
|
|
29
|
+
'cloudflare:KVNamespace': {
|
|
30
|
+
module: 'alchemy/cloudflare',
|
|
31
|
+
export: 'KVNamespace',
|
|
32
|
+
},
|
|
33
|
+
'cloudflare:R2Bucket': { module: 'alchemy/cloudflare', export: 'R2Bucket' },
|
|
34
|
+
'cloudflare:D1Database': {
|
|
35
|
+
module: 'alchemy/cloudflare',
|
|
36
|
+
export: 'D1Database',
|
|
37
|
+
},
|
|
38
|
+
'cloudflare:Queue': { module: 'alchemy/cloudflare', export: 'Queue' },
|
|
39
|
+
'cloudflare:DurableObjectNamespace': {
|
|
40
|
+
module: 'alchemy/cloudflare',
|
|
41
|
+
export: 'DurableObjectNamespace',
|
|
42
|
+
},
|
|
43
|
+
'aws:LambdaFunction': { module: 'alchemy/aws', export: 'Function' },
|
|
44
|
+
'aws:LambdaFunctionUrl': { module: 'alchemy/aws', export: 'FunctionUrl' },
|
|
45
|
+
'aws:Bucket': { module: 'alchemy/aws', export: 'Bucket' },
|
|
46
|
+
'aws:CloudFrontDistribution': {
|
|
47
|
+
module: 'alchemy/aws',
|
|
48
|
+
export: 'Distribution',
|
|
49
|
+
},
|
|
50
|
+
'aws:EcsCluster': { module: 'alchemy/aws', export: 'Cluster' },
|
|
51
|
+
'aws:EcsService': { module: 'alchemy/aws', export: 'Service' },
|
|
52
|
+
});
|
|
53
|
+
/** Imports the optional peer dependency, or explains why it cannot. */
|
|
54
|
+
export async function loadAlchemyRuntime() {
|
|
55
|
+
const alchemy = await importAlchemy();
|
|
56
|
+
const version = await readAlchemyVersion();
|
|
57
|
+
return {
|
|
58
|
+
version,
|
|
59
|
+
open: async (options) => await openScope(alchemy, options),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Imports a module whose specifier is only known at run time.
|
|
64
|
+
*
|
|
65
|
+
* The indirection is deliberate: `alchemy` is an optional peer dependency, so
|
|
66
|
+
* a literal `import('alchemy')` would fail to type-check in every project that
|
|
67
|
+
* has not installed it, and an ambient declaration would shadow the real types
|
|
68
|
+
* in the projects that have.
|
|
69
|
+
*/
|
|
70
|
+
async function importOptional(specifier) {
|
|
71
|
+
return await import(__rewriteRelativeImportExtension(specifier));
|
|
72
|
+
}
|
|
73
|
+
async function importAlchemy() {
|
|
74
|
+
try {
|
|
75
|
+
return (await importOptional('alchemy'));
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw new Error(`\`alchemy\` is not installed in this project (${error instanceof Error ? error.message : String(error)}). Run \`npm install --save-dev alchemy\`.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function readAlchemyVersion() {
|
|
82
|
+
try {
|
|
83
|
+
const manifest = (await importOptional('alchemy/package.json'));
|
|
84
|
+
return manifest.default?.version ?? manifest.version ?? 'unknown';
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function openScope(alchemy, options) {
|
|
91
|
+
const scope = await alchemy.default(options.app, {
|
|
92
|
+
stage: options.stage,
|
|
93
|
+
// Alchemy's read phase resolves the recorded state without creating or
|
|
94
|
+
// updating anything, which is exactly what a preview may do.
|
|
95
|
+
phase: options.phase,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
read: async () => readRecordedState(scope),
|
|
99
|
+
apply: async (resource) => await applyResource(resource),
|
|
100
|
+
finalize: async () => {
|
|
101
|
+
await scope.finalize();
|
|
102
|
+
},
|
|
103
|
+
dispose: async () => {
|
|
104
|
+
// A read scope has nothing to commit; disposing it must not delete the
|
|
105
|
+
// resources it just listed, so `destroy` is deliberately not called.
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function readRecordedState(scope) {
|
|
110
|
+
const state = scope['state'];
|
|
111
|
+
if (!Array.isArray(state))
|
|
112
|
+
return [];
|
|
113
|
+
return state
|
|
114
|
+
.filter((entry) => typeof entry === 'object' && entry !== null)
|
|
115
|
+
.map((entry) => ({
|
|
116
|
+
type: String(entry['type'] ?? ''),
|
|
117
|
+
name: String(entry['id'] ?? entry['name'] ?? ''),
|
|
118
|
+
outputs: stringRecord(entry['output'] ?? entry['outputs']),
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async function applyResource(resource) {
|
|
122
|
+
const target = ALCHEMY_RESOURCE_EXPORTS[resource.type];
|
|
123
|
+
if (!target) {
|
|
124
|
+
throw new Error(`No Alchemy resource is mapped to \`${resource.type}\`. Add it to ALCHEMY_RESOURCE_EXPORTS.`);
|
|
125
|
+
}
|
|
126
|
+
const module = (await importOptional(target.module));
|
|
127
|
+
const factory = module[target.export];
|
|
128
|
+
if (typeof factory !== 'function') {
|
|
129
|
+
throw new Error(`\`${target.module}\` exports no \`${target.export}\`; the installed Alchemy does not match ALCHEMY_RESOURCE_EXPORTS.`);
|
|
130
|
+
}
|
|
131
|
+
const created = (await factory(resource.name, { ...resource.properties }));
|
|
132
|
+
return {
|
|
133
|
+
type: resource.type,
|
|
134
|
+
name: resource.name,
|
|
135
|
+
outputs: stringRecord(created),
|
|
136
|
+
properties: resource.properties,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function stringRecord(value) {
|
|
140
|
+
if (typeof value !== 'object' || value === null)
|
|
141
|
+
return {};
|
|
142
|
+
const record = {};
|
|
143
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
144
|
+
if (typeof entry === 'string' || typeof entry === 'number') {
|
|
145
|
+
record[key] = String(entry);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return record;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=alchemy-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alchemy-runtime.js","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/alchemy-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AlchemyOpenOptions,\n AlchemyResourceRequest,\n AlchemyResourceState,\n AlchemyRuntime,\n AlchemyScope,\n} from './runtime.js';\n\n/**\n * Adapter over the real Alchemy package.\n *\n * Alchemy is an optional peer dependency, so it is imported lazily and only\n * when a preview or a deployment actually runs. Everything version-specific is\n * concentrated here: the presets and the planner never see an Alchemy type.\n */\n\n/**\n * Where each planned resource type comes from.\n *\n * Alchemy exports its resources per platform module, so a plan type maps to a\n * module plus an export. This table is the single place to adjust when Alchemy\n * renames or moves a resource.\n */\nexport const ALCHEMY_RESOURCE_EXPORTS: Readonly<\n Record<string, Readonly<{ module: string; export: string }>>\n> = Object.freeze({\n 'cloudflare:StaticSite': {\n module: 'alchemy/cloudflare',\n export: 'StaticSite',\n },\n 'cloudflare:Worker': { module: 'alchemy/cloudflare', export: 'Worker' },\n 'cloudflare:KVNamespace': {\n module: 'alchemy/cloudflare',\n export: 'KVNamespace',\n },\n 'cloudflare:R2Bucket': { module: 'alchemy/cloudflare', export: 'R2Bucket' },\n 'cloudflare:D1Database': {\n module: 'alchemy/cloudflare',\n export: 'D1Database',\n },\n 'cloudflare:Queue': { module: 'alchemy/cloudflare', export: 'Queue' },\n 'cloudflare:DurableObjectNamespace': {\n module: 'alchemy/cloudflare',\n export: 'DurableObjectNamespace',\n },\n 'aws:LambdaFunction': { module: 'alchemy/aws', export: 'Function' },\n 'aws:LambdaFunctionUrl': { module: 'alchemy/aws', export: 'FunctionUrl' },\n 'aws:Bucket': { module: 'alchemy/aws', export: 'Bucket' },\n 'aws:CloudFrontDistribution': {\n module: 'alchemy/aws',\n export: 'Distribution',\n },\n 'aws:EcsCluster': { module: 'alchemy/aws', export: 'Cluster' },\n 'aws:EcsService': { module: 'alchemy/aws', export: 'Service' },\n});\n\ntype AlchemyScopeHandle = {\n finalize(): Promise<void>;\n destroy?(): Promise<void>;\n [key: string]: unknown;\n};\n\ntype AlchemyModule = {\n default: (\n application: string,\n options: Record<string, unknown>,\n ) => Promise<AlchemyScopeHandle>;\n};\n\n/** Imports the optional peer dependency, or explains why it cannot. */\nexport async function loadAlchemyRuntime(): Promise<AlchemyRuntime> {\n const alchemy = await importAlchemy();\n const version = await readAlchemyVersion();\n\n return {\n version,\n open: async (options) => await openScope(alchemy, options),\n };\n}\n\n/**\n * Imports a module whose specifier is only known at run time.\n *\n * The indirection is deliberate: `alchemy` is an optional peer dependency, so\n * a literal `import('alchemy')` would fail to type-check in every project that\n * has not installed it, and an ambient declaration would shadow the real types\n * in the projects that have.\n */\nasync function importOptional(specifier: string): Promise<unknown> {\n return await import(specifier);\n}\n\nasync function importAlchemy(): Promise<AlchemyModule> {\n try {\n return (await importOptional('alchemy')) as AlchemyModule;\n } catch (error) {\n throw new Error(\n `\\`alchemy\\` is not installed in this project (${\n error instanceof Error ? error.message : String(error)\n }). Run \\`npm install --save-dev alchemy\\`.`,\n );\n }\n}\n\nasync function readAlchemyVersion(): Promise<string> {\n try {\n const manifest = (await importOptional('alchemy/package.json')) as {\n default?: { version?: string };\n version?: string;\n };\n return manifest.default?.version ?? manifest.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nasync function openScope(\n alchemy: AlchemyModule,\n options: AlchemyOpenOptions,\n): Promise<AlchemyScope> {\n const scope = await alchemy.default(options.app, {\n stage: options.stage,\n // Alchemy's read phase resolves the recorded state without creating or\n // updating anything, which is exactly what a preview may do.\n phase: options.phase,\n });\n\n return {\n read: async () => readRecordedState(scope),\n apply: async (resource) => await applyResource(resource),\n finalize: async () => {\n await scope.finalize();\n },\n dispose: async () => {\n // A read scope has nothing to commit; disposing it must not delete the\n // resources it just listed, so `destroy` is deliberately not called.\n },\n };\n}\n\nfunction readRecordedState(\n scope: AlchemyScopeHandle,\n): readonly AlchemyResourceState[] {\n const state = scope['state'];\n if (!Array.isArray(state)) return [];\n return state\n .filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === 'object' && entry !== null,\n )\n .map((entry) => ({\n type: String(entry['type'] ?? ''),\n name: String(entry['id'] ?? entry['name'] ?? ''),\n outputs: stringRecord(entry['output'] ?? entry['outputs']),\n }));\n}\n\nasync function applyResource(\n resource: AlchemyResourceRequest,\n): Promise<AlchemyResourceState> {\n const target = ALCHEMY_RESOURCE_EXPORTS[resource.type];\n if (!target) {\n throw new Error(\n `No Alchemy resource is mapped to \\`${resource.type}\\`. Add it to ALCHEMY_RESOURCE_EXPORTS.`,\n );\n }\n\n const module = (await importOptional(target.module)) as Record<\n string,\n unknown\n >;\n const factory = module[target.export];\n if (typeof factory !== 'function') {\n throw new Error(\n `\\`${target.module}\\` exports no \\`${target.export}\\`; the installed Alchemy does not match ALCHEMY_RESOURCE_EXPORTS.`,\n );\n }\n\n const created = (await (\n factory as (\n name: string,\n properties: Record<string, unknown>,\n ) => Promise<unknown>\n )(resource.name, { ...resource.properties })) as Record<string, unknown>;\n\n return {\n type: resource.type,\n name: resource.name,\n outputs: stringRecord(created),\n properties: resource.properties,\n };\n}\n\nfunction stringRecord(value: unknown): Readonly<Record<string, string>> {\n if (typeof value !== 'object' || value === null) return {};\n const record: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry === 'string' || typeof entry === 'number') {\n record[key] = String(entry);\n }\n }\n return record;\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CraftDeploymentDiagnostic, CraftDeploymentPlatform } from '@craft-ts/deploy';
|
|
2
|
+
export type AlchemyEnvironment = Readonly<Record<string, string | undefined>>;
|
|
3
|
+
export declare function checkAlchemyCredentials(platform: CraftDeploymentPlatform, environment: AlchemyEnvironment): readonly CraftDeploymentDiagnostic[];
|
|
4
|
+
/** Credential names a platform reads, for documentation and error messages. */
|
|
5
|
+
export declare function alchemyCredentialNames(platform: CraftDeploymentPlatform): readonly string[];
|
|
6
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AA2C9E,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,uBAAuB,EACjC,WAAW,EAAE,kBAAkB,GAC9B,SAAS,yBAAyB,EAAE,CA2CtC;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,uBAAuB,GAChC,SAAS,MAAM,EAAE,CAInB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials each platform needs. The tooling never reads a value beyond
|
|
3
|
+
* checking that it is set, and never writes one anywhere.
|
|
4
|
+
*/
|
|
5
|
+
const RULES = {
|
|
6
|
+
cloudflare: [
|
|
7
|
+
{
|
|
8
|
+
anyOf: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_API_KEY'],
|
|
9
|
+
purpose: 'authenticate against the Cloudflare API',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
anyOf: ['CLOUDFLARE_ACCOUNT_ID'],
|
|
13
|
+
purpose: 'select the Cloudflare account the resources belong to',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
aws: [
|
|
17
|
+
{
|
|
18
|
+
anyOf: ['AWS_ACCESS_KEY_ID', 'AWS_PROFILE', 'AWS_ROLE_ARN'],
|
|
19
|
+
purpose: 'authenticate against the AWS API',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
anyOf: ['AWS_REGION', 'AWS_DEFAULT_REGION'],
|
|
23
|
+
purpose: 'select the AWS region the resources belong to',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Alchemy encrypts the secrets it records in its state with this passphrase.
|
|
29
|
+
* Deploying without it would write a state nobody can read back.
|
|
30
|
+
*/
|
|
31
|
+
const STATE_SECRET = 'ALCHEMY_PASSWORD';
|
|
32
|
+
export function checkAlchemyCredentials(platform, environment) {
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
const isSet = (name) => (environment[name] ?? '').trim().length > 0;
|
|
35
|
+
for (const rule of RULES[platform] ?? []) {
|
|
36
|
+
if (rule.anyOf.some(isSet))
|
|
37
|
+
continue;
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
code: 'CRAFT_DEPLOY_PROVIDER_CREDENTIALS_MISSING',
|
|
40
|
+
severity: 'error',
|
|
41
|
+
provider: 'alchemy',
|
|
42
|
+
platform,
|
|
43
|
+
message: `None of ${rule.anyOf.join(', ')} is set, and Alchemy needs one to ${rule.purpose}.`,
|
|
44
|
+
fix: `Export ${rule.anyOf[0]} in the shell or the CI secret store; never write it in the manifest.`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if ((RULES[platform] ?? []).length > 0 &&
|
|
48
|
+
!isSet(STATE_SECRET) &&
|
|
49
|
+
!isSet('ALCHEMY_STATE_TOKEN')) {
|
|
50
|
+
diagnostics.push({
|
|
51
|
+
code: 'CRAFT_DEPLOY_PROVIDER_STATE_UNAVAILABLE',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
provider: 'alchemy',
|
|
54
|
+
platform,
|
|
55
|
+
message: `${STATE_SECRET} is not set, so Alchemy cannot encrypt or read back the secrets it records in its state.`,
|
|
56
|
+
fix: `Export ${STATE_SECRET} for the stage you deploy, and keep the same value across deployments of that stage.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (RULES[platform] === undefined || (RULES[platform] ?? []).length === 0) {
|
|
60
|
+
diagnostics.push({
|
|
61
|
+
code: 'CRAFT_DEPLOY_PROVIDER_PLATFORM_UNSUPPORTED',
|
|
62
|
+
severity: 'error',
|
|
63
|
+
provider: 'alchemy',
|
|
64
|
+
platform,
|
|
65
|
+
message: `Alchemy has no credential profile for \`${platform}\`.`,
|
|
66
|
+
fix: 'Deploy to `cloudflare` or `aws`, or use another provider.',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return diagnostics;
|
|
70
|
+
}
|
|
71
|
+
/** Credential names a platform reads, for documentation and error messages. */
|
|
72
|
+
export function alchemyCredentialNames(platform) {
|
|
73
|
+
const rules = RULES[platform] ?? [];
|
|
74
|
+
if (rules.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
return [...rules.flatMap((rule) => rule.anyOf), STATE_SECRET];
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/credentials.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,KAAK,GAEP;IACF,UAAU,EAAE;QACV;YACE,KAAK,EAAE,CAAC,sBAAsB,EAAE,oBAAoB,CAAC;YACrD,OAAO,EAAE,yCAAyC;SACnD;QACD;YACE,KAAK,EAAE,CAAC,uBAAuB,CAAC;YAChC,OAAO,EAAE,uDAAuD;SACjE;KACF;IACD,GAAG,EAAE;QACH;YACE,KAAK,EAAE,CAAC,mBAAmB,EAAE,aAAa,EAAE,cAAc,CAAC;YAC3D,OAAO,EAAE,kCAAkC;SAC5C;QACD;YACE,KAAK,EAAE,CAAC,YAAY,EAAE,oBAAoB,CAAC;YAC3C,OAAO,EAAE,+CAA+C;SACzD;KACF;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAExC,MAAM,UAAU,uBAAuB,CACrC,QAAiC,EACjC,WAA+B;IAE/B,MAAM,WAAW,GAAgC,EAAE,CAAC;IACpD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAE5E,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACrC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,2CAA2C;YACjD,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,SAAS;YACnB,QAAQ;YACR,OAAO,EAAE,WAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC,IAAI,CAAC,OAAO,GAAG;YAC7F,GAAG,EAAE,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,uEAAuE;SACpG,CAAC,CAAC;IACL,CAAC;IAED,IACE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;QAClC,CAAC,KAAK,CAAC,YAAY,CAAC;QACpB,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAC7B,CAAC;QACD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,yCAAyC;YAC/C,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,SAAS;YACnB,QAAQ;YACR,OAAO,EAAE,GAAG,YAAY,0FAA0F;YAClH,GAAG,EAAE,UAAU,YAAY,sFAAsF;SAClH,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,4CAA4C;YAClD,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,SAAS;YACnB,QAAQ;YACR,OAAO,EAAE,2CAA2C,QAAQ,KAAK;YACjE,GAAG,EAAE,2DAA2D;SACjE,CAAC,CAAC;IACL,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB,CACpC,QAAiC;IAEjC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;AAChE,CAAC","sourcesContent":["import type {\n CraftDeploymentDiagnostic,\n CraftDeploymentPlatform,\n} from '@craft-ts/deploy';\n\nexport type AlchemyEnvironment = Readonly<Record<string, string | undefined>>;\n\ntype CredentialRule = Readonly<{\n /** Any one of these names satisfies the rule. */\n anyOf: readonly string[];\n purpose: string;\n}>;\n\n/**\n * Credentials each platform needs. The tooling never reads a value beyond\n * checking that it is set, and never writes one anywhere.\n */\nconst RULES: Partial<\n Record<CraftDeploymentPlatform, readonly CredentialRule[]>\n> = {\n cloudflare: [\n {\n anyOf: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_API_KEY'],\n purpose: 'authenticate against the Cloudflare API',\n },\n {\n anyOf: ['CLOUDFLARE_ACCOUNT_ID'],\n purpose: 'select the Cloudflare account the resources belong to',\n },\n ],\n aws: [\n {\n anyOf: ['AWS_ACCESS_KEY_ID', 'AWS_PROFILE', 'AWS_ROLE_ARN'],\n purpose: 'authenticate against the AWS API',\n },\n {\n anyOf: ['AWS_REGION', 'AWS_DEFAULT_REGION'],\n purpose: 'select the AWS region the resources belong to',\n },\n ],\n};\n\n/**\n * Alchemy encrypts the secrets it records in its state with this passphrase.\n * Deploying without it would write a state nobody can read back.\n */\nconst STATE_SECRET = 'ALCHEMY_PASSWORD';\n\nexport function checkAlchemyCredentials(\n platform: CraftDeploymentPlatform,\n environment: AlchemyEnvironment,\n): readonly CraftDeploymentDiagnostic[] {\n const diagnostics: CraftDeploymentDiagnostic[] = [];\n const isSet = (name: string) => (environment[name] ?? '').trim().length > 0;\n\n for (const rule of RULES[platform] ?? []) {\n if (rule.anyOf.some(isSet)) continue;\n diagnostics.push({\n code: 'CRAFT_DEPLOY_PROVIDER_CREDENTIALS_MISSING',\n severity: 'error',\n provider: 'alchemy',\n platform,\n message: `None of ${rule.anyOf.join(', ')} is set, and Alchemy needs one to ${rule.purpose}.`,\n fix: `Export ${rule.anyOf[0]} in the shell or the CI secret store; never write it in the manifest.`,\n });\n }\n\n if (\n (RULES[platform] ?? []).length > 0 &&\n !isSet(STATE_SECRET) &&\n !isSet('ALCHEMY_STATE_TOKEN')\n ) {\n diagnostics.push({\n code: 'CRAFT_DEPLOY_PROVIDER_STATE_UNAVAILABLE',\n severity: 'error',\n provider: 'alchemy',\n platform,\n message: `${STATE_SECRET} is not set, so Alchemy cannot encrypt or read back the secrets it records in its state.`,\n fix: `Export ${STATE_SECRET} for the stage you deploy, and keep the same value across deployments of that stage.`,\n });\n }\n\n if (RULES[platform] === undefined || (RULES[platform] ?? []).length === 0) {\n diagnostics.push({\n code: 'CRAFT_DEPLOY_PROVIDER_PLATFORM_UNSUPPORTED',\n severity: 'error',\n provider: 'alchemy',\n platform,\n message: `Alchemy has no credential profile for \\`${platform}\\`.`,\n fix: 'Deploy to `cloudflare` or `aws`, or use another provider.',\n });\n }\n\n return diagnostics;\n}\n\n/** Credential names a platform reads, for documentation and error messages. */\nexport function alchemyCredentialNames(\n platform: CraftDeploymentPlatform,\n): readonly string[] {\n const rules = RULES[platform] ?? [];\n if (rules.length === 0) return [];\n return [...rules.flatMap((rule) => rule.anyOf), STATE_SECRET];\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource names carry the application and the stage.
|
|
3
|
+
*
|
|
4
|
+
* Two stages of the same application share one Alchemy account, so a name that
|
|
5
|
+
* omits the stage makes a preview deployment overwrite production.
|
|
6
|
+
*/
|
|
7
|
+
export declare function alchemyResourceName(application: string, stage: string, suffix: string): string;
|
|
8
|
+
//# sourceMappingURL=naming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/naming.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,MAAM,CAUR"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource names carry the application and the stage.
|
|
3
|
+
*
|
|
4
|
+
* Two stages of the same application share one Alchemy account, so a name that
|
|
5
|
+
* omits the stage makes a preview deployment overwrite production.
|
|
6
|
+
*/
|
|
7
|
+
export function alchemyResourceName(application, stage, suffix) {
|
|
8
|
+
return [application, stage, suffix]
|
|
9
|
+
.map((part) => part
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
12
|
+
.replace(/^-+|-+$/g, ''))
|
|
13
|
+
.filter((part) => part.length > 0)
|
|
14
|
+
.join('-');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/naming.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,KAAa,EACb,MAAc;IAEd,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC;SAChC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,IAAI;SACD,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAC3B;SACA,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;SACjC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC","sourcesContent":["/**\n * Resource names carry the application and the stage.\n *\n * Two stages of the same application share one Alchemy account, so a name that\n * omits the stage makes a preview deployment overwrite production.\n */\nexport function alchemyResourceName(\n application: string,\n stage: string,\n suffix: string,\n): string {\n return [application, stage, suffix]\n .map((part) =>\n part\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^-+|-+$/g, ''),\n )\n .filter((part) => part.length > 0)\n .join('-');\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CraftDeploymentDiagnostic, CraftDeploymentPlan, CraftDeploymentRequest } from '@craft-ts/deploy';
|
|
2
|
+
import type { AlchemyPreset } from './presets/preset.js';
|
|
3
|
+
import type { AlchemyResourceRequest, AlchemyResourceState } from './runtime.js';
|
|
4
|
+
export declare const ALCHEMY_PRESETS: Readonly<Record<string, AlchemyPreset>>;
|
|
5
|
+
export type AlchemyPlanInput = Readonly<{
|
|
6
|
+
request: CraftDeploymentRequest;
|
|
7
|
+
/** What Alchemy already records for this application and stage. */
|
|
8
|
+
existing: readonly AlchemyResourceState[];
|
|
9
|
+
}>;
|
|
10
|
+
export type AlchemyPlanResult = Readonly<{
|
|
11
|
+
plan: CraftDeploymentPlan;
|
|
12
|
+
/** Resources to apply, in declaration order, when the plan is approved. */
|
|
13
|
+
resources: readonly AlchemyResourceRequest[];
|
|
14
|
+
diagnostics: readonly CraftDeploymentDiagnostic[];
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Turns a manifest and the recorded state into the list of actions a
|
|
18
|
+
* deployment would take.
|
|
19
|
+
*
|
|
20
|
+
* The function is pure: it reads no environment, opens no connection and
|
|
21
|
+
* touches no infrastructure, which is what makes a preview trustworthy.
|
|
22
|
+
*/
|
|
23
|
+
export declare function planAlchemyDeployment(input: AlchemyPlanInput): AlchemyPlanResult;
|
|
24
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../../../libs/deploy-alchemy/src/lib/plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EAEnB,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,cAAc,CAAC;AAEtB,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAIhE,CAAC;AAEL,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,sBAAsB,CAAC;IAChC,mEAAmE;IACnE,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC3C,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,2EAA2E;IAC3E,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC7C,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACnD,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,gBAAgB,GACtB,iBAAiB,CA2DnB"}
|