@empire-builder-kit/nx 1.0.0-rc.30 → 1.0.0-rc.32
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/executors.json +8 -3
- package/package.json +14 -7
- package/src/aws/child-environment.d.ts +10 -0
- package/src/aws/child-environment.js +77 -0
- package/src/aws/child-environment.js.map +1 -0
- package/src/aws/identity.d.ts +13 -0
- package/src/aws/identity.js +41 -0
- package/src/aws/identity.js.map +1 -0
- package/src/aws-authority/index.d.ts +1 -0
- package/src/aws-authority/index.js +5 -0
- package/src/aws-authority/index.js.map +1 -0
- package/src/aws-authority/stage-authority.d.ts +240 -0
- package/src/aws-authority/stage-authority.js +2968 -0
- package/src/aws-authority/stage-authority.js.map +1 -0
- package/src/cli/main.d.ts +2 -0
- package/src/cli/main.js +11 -0
- package/src/cli/main.js.map +1 -1
- package/src/developer-agent/generator-planning.js +4 -4
- package/src/developer-agent/generator-planning.js.map +1 -1
- package/src/developer-agent/operation-registry.js +14 -6
- package/src/developer-agent/operation-registry.js.map +1 -1
- package/src/developer-agent/workspace-inspection.js +11 -2
- package/src/developer-agent/workspace-inspection.js.map +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +1 -1
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/foundation-preflight/foundation-preflight.d.ts +0 -1
- package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
- package/src/executors/foundation-preflight/schema.json +0 -5
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +12 -3
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -32
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
- package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +64 -0
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js +221 -0
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js.map +1 -0
- package/src/executors/foundation-profile-preflight/schema.json +1 -6
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +44 -21
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -1
- package/src/executors/foundation-remove-preflight/schema.json +6 -1
- package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/github-bootstrap/schema.json +7 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +27 -369
- package/src/executors/infrastructure-plan/infrastructure-plan.js +237 -5470
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
- package/src/executors/infrastructure-plan/schema.json +8 -64
- package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
- package/src/executors/local-deploy/federated-credential-transport.js +252 -0
- package/src/executors/local-deploy/federated-credential-transport.js.map +1 -0
- package/src/executors/local-deploy/local-approval.d.ts +4 -9
- package/src/executors/local-deploy/local-approval.js +3 -7
- package/src/executors/local-deploy/local-approval.js.map +1 -1
- package/src/executors/local-deploy/local-deploy.d.ts +60 -43
- package/src/executors/local-deploy/local-deploy.js +955 -804
- package/src/executors/local-deploy/local-deploy.js.map +1 -1
- package/src/executors/local-deploy/schema.json +3 -20
- package/src/executors/refresh/refresh.js +2 -2
- package/src/executors/refresh/refresh.js.map +1 -1
- package/src/executors/remove/remove.js +2 -2
- package/src/executors/remove/remove.js.map +1 -1
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +27 -1
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +907 -22
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
- package/src/executors/router-waf-rehearsal/schema.json +17 -2
- package/src/executors/secret-initialize/schema.json +42 -0
- package/src/executors/secret-initialize/secret-initialize.d.ts +32 -0
- package/src/executors/secret-initialize/secret-initialize.js +243 -0
- package/src/executors/secret-initialize/secret-initialize.js.map +1 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +4 -1
- package/src/executors/secret-metadata/secret-metadata.js +76 -29
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
- package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
- package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
- package/src/executors/sst-lifecycle/generated-secret-retirement.js.map +1 -0
- package/src/executors/sst-lifecycle/governed-native-operation.d.ts +46 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js +153 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js.map +1 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +27 -7
- package/src/executors/sst-lifecycle/run-sst-command.js +474 -89
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +0 -5
- package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
- package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
- package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -1
- package/src/executors/unlock/unlock.js +2 -2
- package/src/executors/unlock/unlock.js.map +1 -1
- package/src/foundation/provision-logical-database.js +2 -0
- package/src/foundation/provision-logical-database.js.map +1 -1
- package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
- package/src/foundation/router-waf-rate-rehearsal.js +210 -0
- package/src/foundation/router-waf-rate-rehearsal.js.map +1 -0
- package/src/foundation/router-waf.d.ts +59 -9
- package/src/foundation/router-waf.js +427 -32
- package/src/foundation/router-waf.js.map +1 -1
- package/src/foundation/stages.d.ts +1 -0
- package/src/foundation/stages.js +11 -0
- package/src/foundation/stages.js.map +1 -1
- package/src/generators/formatting.d.ts +8 -0
- package/src/generators/formatting.js +118 -0
- package/src/generators/formatting.js.map +1 -0
- package/src/generators/function/function.js +37 -17
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/post-generation-install.js +2 -2
- package/src/generators/post-generation-install.js.map +1 -1
- package/src/generators/preset/generator.js +28 -8
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/schema.d.ts +3 -0
- package/src/generators/preset/schema.json +5 -5
- package/src/generators/preset/workspace-files-developer-agent.js +15 -7
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
- package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
- package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
- package/src/generators/preset/workspace-files-ecr-inspector.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
- package/src/generators/preset/workspace-files-performance-cost.js +8 -8
- package/src/generators/preset/workspace-files-performance-cost.js.map +1 -1
- package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
- package/src/generators/preset/workspace-files-performance-load.js +29 -24
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
- package/src/generators/preset/workspace-files-stage-authority.d.ts +6 -0
- package/src/generators/preset/workspace-files-stage-authority.js +479 -0
- package/src/generators/preset/workspace-files-stage-authority.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-deploy.d.ts +40 -0
- package/src/generators/preset/workspace-files-thin-deploy.js +1500 -0
- package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-docs.d.ts +5 -0
- package/src/generators/preset/workspace-files-thin-docs.js +240 -0
- package/src/generators/preset/workspace-files-thin-docs.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
- package/src/generators/preset/workspace-files-thin-release.js +26 -0
- package/src/generators/preset/workspace-files-thin-release.js.map +1 -0
- package/src/generators/preset/workspace-files.d.ts +2 -1
- package/src/generators/preset/workspace-files.js +448 -680
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +149 -19
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/repository-policy/schema.d.ts +2 -0
- package/src/generators/repository-policy/schema.json +11 -1
- package/src/generators/repository-policy/tree-policy.d.ts +3 -0
- package/src/generators/repository-policy/tree-policy.js +25 -0
- package/src/generators/repository-policy/tree-policy.js.map +1 -0
- package/src/generators/slice/built-in-plan-api-files.js +1 -0
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-async-work-files.js +41 -0
- package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +6 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +89 -30
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +19 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +21 -3
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +20 -1
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +3 -5
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-frontend-files.js +5 -1
- package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +199 -188
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-metadata-files.js +13 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +307 -0
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-operations-files.js +65 -51
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-performance-files.js +97 -19
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-project-files.js +9 -68
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.js +87 -76
- package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +94 -47
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
- package/src/generators/slice/built-in-plan-thin-release-files.js +129 -0
- package/src/generators/slice/built-in-plan-thin-release-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-verification-files.js +6 -2
- package/src/generators/slice/built-in-plan-verification-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +4 -2
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js +26 -4
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
- package/src/generators/slice/built-in-plan.d.ts +1 -0
- package/src/generators/slice/built-in-plan.js +28 -11
- package/src/generators/slice/built-in-plan.js.map +1 -1
- package/src/generators/slice/slice.js +30 -32
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/upgrade.d.ts +1 -1
- package/src/generators/upgrade/upgrade.js +200 -48
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/performance-registration.d.ts +1 -0
- package/src/generators/workload/performance-registration.js +9 -11
- package/src/generators/workload/performance-registration.js.map +1 -1
- package/src/generators/workload/service-performance-files.d.ts +1 -0
- package/src/generators/workload/service-performance-files.js +27 -7
- package/src/generators/workload/service-performance-files.js.map +1 -1
- package/src/generators/workload/workload.d.ts +1 -1
- package/src/generators/workload/workload.js +55 -28
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +28 -7
- package/src/git-policy/github-bootstrap.js +205 -163
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/local-dev/private-state.d.ts +3 -0
- package/src/local-dev/private-state.js +17 -5
- package/src/local-dev/private-state.js.map +1 -1
- package/src/local-dev/tls.js +5 -1
- package/src/local-dev/tls.js.map +1 -1
- package/src/performance/cost-collection.d.ts +1 -1
- package/src/performance/cost-collection.js +4 -2
- package/src/performance/cost-collection.js.map +1 -1
- package/src/performance/cost-controls.d.ts +3 -1
- package/src/performance/cost-controls.js +9 -4
- package/src/performance/cost-controls.js.map +1 -1
- package/src/performance/evidence.d.ts +1 -1
- package/src/performance/evidence.js +5 -3
- package/src/performance/evidence.js.map +1 -1
- package/src/performance/load-safety.js +5 -3
- package/src/performance/load-safety.js.map +1 -1
- package/src/performance/model.d.ts +2 -2
- package/src/performance/model.js +4 -3
- package/src/performance/model.js.map +1 -1
- package/src/release/index.d.ts +0 -2
- package/src/release/index.js +0 -2
- package/src/release/index.js.map +1 -1
- package/src/release/source-authority.d.ts +4 -5
- package/src/release/source-authority.js +13 -123
- package/src/release/source-authority.js.map +1 -1
- package/src/repository-policy/aurora-preflight.d.ts +1 -1
- package/src/repository-policy/aurora-preflight.js +4 -1
- package/src/repository-policy/aurora-preflight.js.map +1 -1
- package/src/repository-policy/policy.d.ts +21 -2
- package/src/repository-policy/policy.js +290 -25
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/performance-policy-v1.json +3 -1
- package/src/schemas/repository-policy-v1.json +46 -2
- package/src/secrets/adapters.d.ts +7 -2
- package/src/secrets/adapters.js +34 -3
- package/src/secrets/adapters.js.map +1 -1
- package/src/secrets/aws-control-plane.d.ts +10 -1
- package/src/secrets/aws-control-plane.js +67 -0
- package/src/secrets/aws-control-plane.js.map +1 -1
- package/src/secrets/generated-aws-control-plane.d.ts +23 -0
- package/src/secrets/generated-aws-control-plane.js +259 -0
- package/src/secrets/generated-aws-control-plane.js.map +1 -0
- package/src/secrets/metadata.js +38 -10
- package/src/secrets/metadata.js.map +1 -1
- package/src/secrets/model.d.ts +8 -1
- package/src/secrets/model.js +24 -3
- package/src/secrets/model.js.map +1 -1
- package/src/secrets/operator.d.ts +89 -3
- package/src/secrets/operator.js +422 -9
- package/src/secrets/operator.js.map +1 -1
- package/src/secrets/registry.d.ts +2 -1
- package/src/secrets/registry.js +18 -1
- package/src/secrets/registry.js.map +1 -1
- package/src/secrets/workload-access.js +10 -2
- package/src/secrets/workload-access.js.map +1 -1
- package/src/supply-chain/action-pins.js +61 -7
- package/src/supply-chain/action-pins.js.map +1 -1
- package/src/supply-chain/build-output-bounds.d.ts +2 -0
- package/src/supply-chain/build-output-bounds.js +12 -0
- package/src/supply-chain/build-output-bounds.js.map +1 -0
- package/src/supply-chain/build-output.js +2 -4
- package/src/supply-chain/build-output.js.map +1 -1
- package/src/supply-chain/ecr-scanning.d.ts +5 -3
- package/src/supply-chain/ecr-scanning.js +11 -7
- package/src/supply-chain/ecr-scanning.js.map +1 -1
- package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
- package/src/supply-chain/ecs-deployed-image.js +6 -4
- package/src/supply-chain/ecs-deployed-image.js.map +1 -1
- package/src/supply-chain/generated-container-release-command.js +849 -44
- package/src/supply-chain/generated-container-release-command.js.map +1 -1
- package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
- package/src/supply-chain/generated-ecr-collector-command.js +188 -91
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
- package/src/supply-chain/generated-files.js +4 -3
- package/src/supply-chain/generated-files.js.map +1 -1
- package/src/supply-chain/index.d.ts +1 -1
- package/src/supply-chain/index.js +2 -1
- package/src/supply-chain/index.js.map +1 -1
- package/src/workload-roles/index.d.ts +10 -17
- package/src/workload-roles/index.js +5 -18
- package/src/workload-roles/index.js.map +1 -1
- package/src/workspace-deployment/identity.d.ts +3 -0
- package/src/workspace-deployment/identity.js +22 -0
- package/src/workspace-deployment/identity.js.map +1 -0
- package/src/workspace-deployment/index.d.ts +3 -0
- package/src/workspace-deployment/index.js +7 -0
- package/src/workspace-deployment/index.js.map +1 -0
- package/src/workspace-deployment/migration-evidence.d.ts +29 -0
- package/src/workspace-deployment/migration-evidence.js +83 -0
- package/src/workspace-deployment/migration-evidence.js.map +1 -0
- package/src/workspace-deployment/migration.d.ts +27 -0
- package/src/workspace-deployment/migration.js +109 -0
- package/src/workspace-deployment/migration.js.map +1 -0
- package/src/workspace-deployment/ownership.d.ts +81 -0
- package/src/workspace-deployment/ownership.js +381 -0
- package/src/workspace-deployment/ownership.js.map +1 -0
|
@@ -1,911 +1,1062 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
3
|
+
exports.localWorkingTreeDigest = localWorkingTreeDigest;
|
|
4
|
+
exports.localDeploymentProjectOrder = localDeploymentProjectOrder;
|
|
5
5
|
exports.runLocalDeploy = runLocalDeploy;
|
|
6
6
|
const node_crypto_1 = require("node:crypto");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
|
-
const node_os_1 = require("node:os");
|
|
10
9
|
const node_path_1 = require("node:path");
|
|
11
|
-
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
12
|
-
const client_ssm_1 = require("@aws-sdk/client-ssm");
|
|
13
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
14
10
|
const devkit_1 = require("@nx/devkit");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
11
|
+
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
12
|
+
const index_js_1 = require("../../aws-authority/index.js");
|
|
13
|
+
const child_environment_js_1 = require("../../aws/child-environment.js");
|
|
14
|
+
const identity_js_1 = require("../../aws/identity.js");
|
|
17
15
|
const index_js_2 = require("../../repository-policy/index.js");
|
|
18
|
-
const
|
|
16
|
+
const production_database_safeguard_js_1 = require("../foundation-profile-preflight/production-database-safeguard.js");
|
|
19
17
|
const private_state_js_1 = require("../../local-dev/private-state.js");
|
|
20
|
-
const
|
|
21
|
-
const sst_environment_source_js_1 = require("../sst-lifecycle/sst-environment-source.js");
|
|
18
|
+
const federated_credential_transport_js_1 = require("./federated-credential-transport.js");
|
|
22
19
|
const local_approval_js_1 = require("./local-approval.js");
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const PROTECTED_PLAN_KEYS = [
|
|
41
|
-
'EBK_LOCAL_MUTATION_CAPABILITY_NONCE',
|
|
42
|
-
'EBK_LOCAL_MUTATION_CAPABILITY_PATH',
|
|
43
|
-
'EBK_LOCAL_PLAN_APPROVER_ARN',
|
|
44
|
-
'EBK_LOCAL_PLAN_MATERIAL_DIGEST',
|
|
45
|
-
'EBK_LOCAL_PLAN_SUMMARY_DIGEST',
|
|
46
|
-
'AWS_PLAN_GENERATED_OBJECT_ARNS_JSON',
|
|
47
|
-
'AWS_PLAN_GENERATED_PARAMETER_ARNS_JSON',
|
|
48
|
-
'AWS_PLAN_GENERATED_SECRET_ARNS_JSON',
|
|
49
|
-
'AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON',
|
|
50
|
-
'AWS_PLAN_STATE_KMS_KEY_ARN',
|
|
51
|
-
'EBK_RELEASE_SOURCE_COMMIT',
|
|
52
|
-
];
|
|
53
|
-
function projectEvidencePath(context, project, name) {
|
|
54
|
-
const configured = context.projectsConfigurations?.projects[project]?.root;
|
|
55
|
-
if (!configured) {
|
|
56
|
-
throw new Error(`Local deployment references unknown project ${project}.`);
|
|
57
|
-
}
|
|
58
|
-
return (0, node_path_1.join)((0, node_path_1.isAbsolute)(configured) ? configured : (0, node_path_1.join)(context.root, configured), '.ebk', 'evidence', name);
|
|
59
|
-
}
|
|
60
|
-
function writeLocalMutationCapability(input) {
|
|
61
|
-
const nonce = (0, node_crypto_1.randomBytes)(32).toString('hex');
|
|
62
|
-
const directory = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'ebk-local-mutation-'));
|
|
63
|
-
(0, private_state_js_1.ensurePrivateDirectory)(directory);
|
|
64
|
-
const path = (0, node_path_1.join)(directory, 'capability.json');
|
|
65
|
-
const planContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan.json'), 'utf8');
|
|
66
|
-
const authorizationContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan-authorization.json'), 'utf8');
|
|
67
|
-
const capability = (0, local_capability_js_1.createLocalMutationCapability)({
|
|
68
|
-
actorArn: input.actorArn,
|
|
69
|
-
authorizationContents,
|
|
70
|
-
deploymentProfile: input.deploymentProfile,
|
|
71
|
-
deploymentSourceCommit: input.deploymentSourceCommit,
|
|
72
|
-
deploymentSourceTree: input.deploymentSourceTree,
|
|
73
|
-
expectedRoleArn: input.expectedRoleArn,
|
|
74
|
-
issuedAt: input.issuedAt,
|
|
75
|
-
nonce,
|
|
76
|
-
plan: input.plan,
|
|
77
|
-
planContents,
|
|
78
|
-
project: input.project,
|
|
79
|
-
releaseSourceTree: input.releaseSourceTree,
|
|
80
|
-
});
|
|
81
|
-
(0, private_state_js_1.atomicWritePrivateFile)(path, `${JSON.stringify(capability, null, 2)}\n`);
|
|
82
|
-
return { directory, nonce, path };
|
|
83
|
-
}
|
|
84
|
-
function digest(value) {
|
|
85
|
-
return (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
|
|
86
|
-
}
|
|
87
|
-
function ssmParameterAuthority(value, expectedName) {
|
|
88
|
-
const parameter = record(value, expectedName);
|
|
89
|
-
if (parameter['Name'] !== expectedName ||
|
|
90
|
-
parameter['Type'] !== 'SecureString' ||
|
|
91
|
-
typeof parameter['Version'] !== 'number' ||
|
|
92
|
-
!Number.isSafeInteger(parameter['Version']) ||
|
|
93
|
-
parameter['Version'] < 1) {
|
|
94
|
-
throw new Error(`Local SST backend parameter ${expectedName} is missing or malformed.`);
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
name: expectedName,
|
|
98
|
-
type: parameter['Type'],
|
|
99
|
-
version: parameter['Version'],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/** @internal */
|
|
103
|
-
async function prepareLocalSstBackend(input, dependencies = {}) {
|
|
104
|
-
if (input.applications.length === 0 ||
|
|
105
|
-
new Set(input.applications).size !== input.applications.length ||
|
|
106
|
-
input.applications.some((application) => !/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(application))) {
|
|
107
|
-
throw new Error('Local SST backend requires a unique exact application inventory.');
|
|
108
|
-
}
|
|
109
|
-
const client = new client_ssm_1.SSMClient({
|
|
110
|
-
region: input.region,
|
|
111
|
-
...(input.profile
|
|
112
|
-
? { credentials: (0, credential_providers_1.fromIni)({ profile: input.profile }) }
|
|
113
|
-
: {}),
|
|
114
|
-
});
|
|
115
|
-
const send = dependencies.send ??
|
|
116
|
-
(async (command) => client.send(command));
|
|
117
|
-
const bootstrapResponse = await send(new client_ssm_1.GetParameterCommand({ Name: '/sst/bootstrap' }));
|
|
118
|
-
const bootstrapParameter = bootstrapResponse.Parameter;
|
|
119
|
-
let bootstrap;
|
|
120
|
-
try {
|
|
121
|
-
bootstrap = record(JSON.parse(bootstrapParameter?.Value ?? ''), 'Local SST bootstrap value');
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
|
|
125
|
-
}
|
|
126
|
-
const exactBootstrapKeys = [
|
|
127
|
-
'appsyncHttp',
|
|
128
|
-
'appsyncRealtime',
|
|
129
|
-
'asset',
|
|
130
|
-
'assetEcrRegistryId',
|
|
131
|
-
'assetEcrUrl',
|
|
132
|
-
'state',
|
|
133
|
-
'version',
|
|
134
|
-
];
|
|
135
|
-
const state = bootstrap['state'];
|
|
136
|
-
if (bootstrapParameter?.Name !== '/sst/bootstrap' ||
|
|
137
|
-
bootstrapParameter.Type !== 'String' ||
|
|
138
|
-
typeof bootstrapParameter.Version !== 'number' ||
|
|
139
|
-
!Number.isSafeInteger(bootstrapParameter.Version) ||
|
|
140
|
-
bootstrapParameter.Version < 1 ||
|
|
141
|
-
Object.keys(bootstrap).sort().join(',') !== exactBootstrapKeys.join(',') ||
|
|
142
|
-
bootstrap['version'] !== 5 ||
|
|
143
|
-
typeof state !== 'string' ||
|
|
144
|
-
!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(state) ||
|
|
145
|
-
typeof bootstrap['asset'] !== 'string' ||
|
|
146
|
-
!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bootstrap['asset']) ||
|
|
147
|
-
bootstrap['assetEcrRegistryId'] !== input.account ||
|
|
148
|
-
bootstrap['assetEcrUrl'] !==
|
|
149
|
-
`${input.account}.dkr.ecr.${input.region}.amazonaws.com/sst-asset` ||
|
|
150
|
-
typeof bootstrap['appsyncHttp'] !== 'string' ||
|
|
151
|
-
typeof bootstrap['appsyncRealtime'] !== 'string') {
|
|
152
|
-
throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
|
|
153
|
-
}
|
|
154
|
-
const parameterAuthority = new Map();
|
|
155
|
-
const missingApplications = [];
|
|
156
|
-
const createdApplications = [];
|
|
157
|
-
for (const application of input.applications) {
|
|
158
|
-
const name = `/sst/passphrase/${application}/${input.stage}`;
|
|
159
|
-
try {
|
|
160
|
-
const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
|
|
161
|
-
parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
if (!(error instanceof Error) || error.name !== 'ParameterNotFound') {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
missingApplications.push(application);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
for (const application of missingApplications) {
|
|
171
|
-
const name = `/sst/passphrase/${application}/${input.stage}`;
|
|
172
|
-
await send(new client_ssm_1.PutParameterCommand({
|
|
173
|
-
Description: `SST state passphrase for ${application}/${input.stage}`,
|
|
174
|
-
Name: name,
|
|
175
|
-
Overwrite: false,
|
|
176
|
-
Type: 'SecureString',
|
|
177
|
-
Value: dependencies.passphrase?.() ?? (0, node_crypto_1.randomBytes)(32).toString('base64'),
|
|
178
|
-
}));
|
|
179
|
-
createdApplications.push(application);
|
|
180
|
-
const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
|
|
181
|
-
parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
|
|
182
|
-
}
|
|
183
|
-
const parameters = input.applications.map((application) => {
|
|
184
|
-
const authority = parameterAuthority.get(application);
|
|
185
|
-
if (!authority) {
|
|
186
|
-
throw new Error(`Local SST backend parameter authority is absent for ${application}.`);
|
|
187
|
-
}
|
|
188
|
-
return authority;
|
|
189
|
-
});
|
|
190
|
-
return {
|
|
191
|
-
authorityDigest: digest(JSON.stringify({
|
|
192
|
-
applications: [...input.applications],
|
|
193
|
-
bootstrap: {
|
|
194
|
-
name: bootstrapParameter.Name,
|
|
195
|
-
type: bootstrapParameter.Type,
|
|
196
|
-
version: bootstrapParameter.Version,
|
|
197
|
-
},
|
|
198
|
-
parameters,
|
|
199
|
-
region: input.region,
|
|
200
|
-
stage: input.stage,
|
|
201
|
-
state,
|
|
202
|
-
})),
|
|
203
|
-
createdApplications,
|
|
204
|
-
stateBucketArn: `arn:aws:s3:::${state}`,
|
|
205
|
-
};
|
|
20
|
+
const container_release_js_1 = require("../../supply-chain/container-release.js");
|
|
21
|
+
const run_sst_command_js_1 = require("../sst-lifecycle/run-sst-command.js");
|
|
22
|
+
const index_js_3 = require("../../workspace-deployment/index.js");
|
|
23
|
+
const SHA = /^[a-f0-9]{40,64}$/u;
|
|
24
|
+
const ROLE = /^arn:(aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}$/u;
|
|
25
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES = 64 * 1024;
|
|
26
|
+
const MAX_LOCAL_RELEASE_ARTIFACTS_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_FILES = 32;
|
|
28
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_BYTES = MAX_LOCAL_RELEASE_ARTIFACTS_BYTES +
|
|
29
|
+
(MAX_LOCAL_RELEASE_CANDIDATE_FILES - 1) *
|
|
30
|
+
MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
|
|
31
|
+
function partitionForRegion(region) {
|
|
32
|
+
if (region.startsWith('cn-'))
|
|
33
|
+
return 'aws-cn';
|
|
34
|
+
if (region.startsWith('us-gov-'))
|
|
35
|
+
return 'aws-us-gov';
|
|
36
|
+
return 'aws';
|
|
206
37
|
}
|
|
207
38
|
function git(root, args, run) {
|
|
208
39
|
const result = run('git', args, {
|
|
209
40
|
cwd: root,
|
|
210
41
|
encoding: 'utf8',
|
|
211
42
|
env: process.env,
|
|
212
|
-
maxBuffer: 1024 * 1024,
|
|
43
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
213
44
|
shell: false,
|
|
214
45
|
});
|
|
215
46
|
if (result.status !== 0) {
|
|
216
47
|
throw new Error(`Local deployment Git preflight failed: ${String(result.stderr).trim()}`);
|
|
217
48
|
}
|
|
218
|
-
return String(result.stdout)
|
|
49
|
+
return String(result.stdout);
|
|
219
50
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
encoding: 'utf8',
|
|
224
|
-
env: process.env,
|
|
225
|
-
maxBuffer: 1024 * 1024,
|
|
226
|
-
shell: false,
|
|
227
|
-
});
|
|
228
|
-
if (result.status !== 0 || String(result.stderr) !== '') {
|
|
229
|
-
throw new Error(`Local deployment Git reconciliation failed: ${String(result.stderr).trim()}`);
|
|
51
|
+
function confinedWorkspacePath(root, candidate) {
|
|
52
|
+
if (!candidate || (0, node_path_1.isAbsolute)(candidate) || candidate.includes('\0')) {
|
|
53
|
+
throw new Error('Working-tree inventory contains an invalid path.');
|
|
230
54
|
}
|
|
231
|
-
|
|
55
|
+
const absolute = (0, node_path_1.join)(root, candidate);
|
|
56
|
+
const confined = (0, node_path_1.relative)(root, absolute);
|
|
57
|
+
if (confined === '..' || confined.startsWith('../') || (0, node_path_1.isAbsolute)(confined)) {
|
|
58
|
+
throw new Error('Working-tree inventory escapes the workspace.');
|
|
59
|
+
}
|
|
60
|
+
return absolute;
|
|
232
61
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
62
|
+
/** Bind a development deployment to the exact tracked diff and untracked
|
|
63
|
+
* source bytes without requiring a commit. Ignored caches and build output are
|
|
64
|
+
* intentionally outside this identity. */
|
|
65
|
+
function localWorkingTreeDigest(root, run = node_child_process_1.spawnSync) {
|
|
66
|
+
const commit = git(root, ['rev-parse', 'HEAD'], run).trim();
|
|
67
|
+
if (!SHA.test(commit)) {
|
|
68
|
+
throw new Error('Local deployment could not resolve one exact Git HEAD.');
|
|
69
|
+
}
|
|
70
|
+
const status = git(root, ['status', '--porcelain=v1', '--untracked-files=all'], run);
|
|
71
|
+
const trackedDiff = git(root, ['diff', '--binary', 'HEAD'], run);
|
|
72
|
+
const untracked = git(root, ['ls-files', '--others', '--exclude-standard', '-z'], run)
|
|
73
|
+
.split('\0')
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.sort();
|
|
76
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
77
|
+
hash.update('ebk-working-tree-v1\0');
|
|
78
|
+
hash.update(commit);
|
|
79
|
+
hash.update('\0');
|
|
80
|
+
hash.update(status);
|
|
81
|
+
hash.update('\0');
|
|
82
|
+
hash.update(trackedDiff);
|
|
83
|
+
hash.update('\0');
|
|
84
|
+
for (const path of untracked) {
|
|
85
|
+
const absolute = confinedWorkspacePath(root, path);
|
|
86
|
+
const stat = (0, node_fs_1.lstatSync)(absolute);
|
|
87
|
+
hash.update(path);
|
|
88
|
+
hash.update('\0');
|
|
89
|
+
if (stat.isSymbolicLink()) {
|
|
90
|
+
hash.update('symlink\0');
|
|
91
|
+
hash.update((0, node_fs_1.readlinkSync)(absolute));
|
|
239
92
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
function nulSeparatedPaths(value) {
|
|
249
|
-
return value.split('\0').filter((entry) => entry !== '');
|
|
250
|
-
}
|
|
251
|
-
function reconcileToolOwnedSource(root, snapshots, run) {
|
|
252
|
-
for (const snapshot of snapshots) {
|
|
253
|
-
(0, sst_environment_source_js_1.reconcileSstEnvironmentSourceTree)(snapshot);
|
|
93
|
+
else if (stat.isFile()) {
|
|
94
|
+
hash.update('file\0');
|
|
95
|
+
hash.update((0, node_fs_1.readFileSync)(absolute));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
throw new Error(`Untracked deployment source ${path} is not a regular file or symbolic link.`);
|
|
99
|
+
}
|
|
100
|
+
hash.update('\0');
|
|
254
101
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
|
|
102
|
+
return { clean: status === '', commit, digest: hash.digest('hex') };
|
|
103
|
+
}
|
|
104
|
+
function assertDeploymentSourceUnchanged(root, expected, run) {
|
|
105
|
+
const current = localWorkingTreeDigest(root, run);
|
|
106
|
+
if (current.clean !== expected.clean ||
|
|
107
|
+
current.commit !== expected.commit ||
|
|
108
|
+
current.digest !== expected.workingTreeDigest) {
|
|
109
|
+
throw new Error('Deployment source changed after lifecycle authority was established; downstream execution is blocked.');
|
|
261
110
|
}
|
|
262
111
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
112
|
+
function projectDescriptors(context) {
|
|
113
|
+
return Object.entries(context.projectsConfigurations?.projects ?? {}).map(([name, configuration]) => ({
|
|
265
114
|
name,
|
|
266
|
-
|
|
267
|
-
|
|
115
|
+
root: configuration.root,
|
|
116
|
+
tags: configuration.tags ?? [],
|
|
117
|
+
targets: configuration.targets ?? {},
|
|
268
118
|
}));
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
119
|
+
}
|
|
120
|
+
function supportsTarget(project, target) {
|
|
121
|
+
return Object.prototype.hasOwnProperty.call(project.targets, target);
|
|
122
|
+
}
|
|
123
|
+
const LOCAL_CONTAINER_TARGETS = Object.freeze([
|
|
124
|
+
'release-candidate',
|
|
125
|
+
'container-release-amd64',
|
|
126
|
+
'container-release-arm64',
|
|
127
|
+
'container-release-assemble',
|
|
128
|
+
]);
|
|
129
|
+
function requiresLocalContainerPreparation(profile, stage) {
|
|
130
|
+
return ((profile === 'starter' && stage === 'production') ||
|
|
131
|
+
(profile === 'production-isolated' && stage === 'staging'));
|
|
132
|
+
}
|
|
133
|
+
function confinedEvidenceSegments(root, candidate) {
|
|
134
|
+
const confined = (0, node_path_1.relative)(root, candidate);
|
|
135
|
+
if (confined === '' ||
|
|
136
|
+
confined === '..' ||
|
|
137
|
+
confined.startsWith(`..${node_path_1.sep}`) ||
|
|
138
|
+
(0, node_path_1.isAbsolute)(confined)) {
|
|
139
|
+
throw new Error('Local container evidence path escapes the workspace.');
|
|
275
140
|
}
|
|
276
|
-
|
|
277
|
-
|
|
141
|
+
const segments = confined.split(node_path_1.sep);
|
|
142
|
+
if (segments.some((segment) => segment === '' ||
|
|
143
|
+
segment === '.' ||
|
|
144
|
+
segment === '..' ||
|
|
145
|
+
segment.includes('\0'))) {
|
|
146
|
+
throw new Error('Local container evidence path is invalid.');
|
|
278
147
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
})
|
|
148
|
+
return segments;
|
|
149
|
+
}
|
|
150
|
+
function assertSafeEvidenceTree(path) {
|
|
151
|
+
let entries = 0;
|
|
152
|
+
const visit = (directory) => {
|
|
153
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
|
|
154
|
+
entries += 1;
|
|
155
|
+
if (entries > 4096) {
|
|
156
|
+
throw new Error('Local container evidence tree is unexpectedly large.');
|
|
157
|
+
}
|
|
158
|
+
const child = (0, node_path_1.join)(directory, entry.name);
|
|
159
|
+
const identity = (0, node_fs_1.lstatSync)(child);
|
|
160
|
+
if (identity.isSymbolicLink()) {
|
|
161
|
+
throw new Error('Local container evidence cleanup refuses symbolic links.');
|
|
162
|
+
}
|
|
163
|
+
if (identity.isDirectory()) {
|
|
164
|
+
visit(child);
|
|
165
|
+
}
|
|
166
|
+
else if (!identity.isFile()) {
|
|
167
|
+
throw new Error('Local container evidence cleanup accepts only regular files and directories.');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
visit(path);
|
|
172
|
+
}
|
|
173
|
+
function removeExactLocalEvidenceDirectory(root, path) {
|
|
174
|
+
const segments = confinedEvidenceSegments(root, path);
|
|
175
|
+
let current = root;
|
|
176
|
+
const rootIdentity = (0, node_fs_1.lstatSync)(current);
|
|
177
|
+
if (!rootIdentity.isDirectory() || rootIdentity.isSymbolicLink()) {
|
|
178
|
+
throw new Error('Local deployment workspace root is absent or unsafe.');
|
|
285
179
|
}
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
180
|
+
for (const segment of segments) {
|
|
181
|
+
current = (0, node_path_1.join)(current, segment);
|
|
182
|
+
if (!(0, node_fs_1.existsSync)(current))
|
|
183
|
+
return;
|
|
184
|
+
const identity = (0, node_fs_1.lstatSync)(current);
|
|
185
|
+
if (!identity.isDirectory() || identity.isSymbolicLink()) {
|
|
186
|
+
throw new Error('Local container evidence cleanup encountered an unsafe path.');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
assertSafeEvidenceTree(current);
|
|
190
|
+
(0, node_fs_1.rmSync)(current, { recursive: true });
|
|
291
191
|
}
|
|
292
|
-
function
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
shell: false,
|
|
305
|
-
stdio: 'inherit',
|
|
306
|
-
});
|
|
307
|
-
if (result.status !== 0) {
|
|
308
|
-
throw new Error(`Local ${target} failed for ${project}.`);
|
|
192
|
+
function ensurePrivateEvidenceDirectory(root, path) {
|
|
193
|
+
const segments = confinedEvidenceSegments(root, path);
|
|
194
|
+
let current = root;
|
|
195
|
+
for (const segment of segments) {
|
|
196
|
+
current = (0, node_path_1.join)(current, segment);
|
|
197
|
+
if ((0, node_fs_1.existsSync)(current)) {
|
|
198
|
+
const identity = (0, node_fs_1.lstatSync)(current);
|
|
199
|
+
if (!identity.isDirectory() || identity.isSymbolicLink()) {
|
|
200
|
+
throw new Error('Local container evidence directory encountered an unsafe path.');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
(0, private_state_js_1.ensurePrivateDirectory)(current);
|
|
309
204
|
}
|
|
310
205
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
206
|
+
function resetLocalContainerWorkingEvidence(input) {
|
|
207
|
+
const projectRoot = confinedWorkspacePath(input.root, input.project.root);
|
|
208
|
+
for (const path of [
|
|
209
|
+
(0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate'),
|
|
210
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name),
|
|
211
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', 'partials', input.stage, input.project.name),
|
|
212
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name),
|
|
213
|
+
]) {
|
|
214
|
+
removeExactLocalEvidenceDirectory(input.root, path);
|
|
315
215
|
}
|
|
316
|
-
|
|
317
|
-
|
|
216
|
+
}
|
|
217
|
+
/** Materialize the same bounded workspace-level candidate layout restored by
|
|
218
|
+
* protected GitHub deployment. The generated container helper and SST image
|
|
219
|
+
* resolver both consume this exact path. */
|
|
220
|
+
function materializeLocalReleaseCandidate(input) {
|
|
221
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(input.project.name)) {
|
|
222
|
+
throw new Error(`Local container release project ${input.project.name} has an unsafe evidence identity.`);
|
|
318
223
|
}
|
|
319
|
-
|
|
224
|
+
const projectRoot = confinedWorkspacePath(input.root, input.project.root);
|
|
225
|
+
const source = (0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate');
|
|
226
|
+
const sourceIdentity = (0, node_fs_1.lstatSync)(source);
|
|
227
|
+
if (!sourceIdentity.isDirectory() || sourceIdentity.isSymbolicLink()) {
|
|
228
|
+
throw new Error(`Local release candidate for ${input.project.name} is absent or unsafe.`);
|
|
229
|
+
}
|
|
230
|
+
const entries = (0, node_fs_1.readdirSync)(source, { withFileTypes: true }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
231
|
+
if (entries.length === 0 ||
|
|
232
|
+
entries.length > MAX_LOCAL_RELEASE_CANDIDATE_FILES ||
|
|
233
|
+
entries.some((entry) => !entry.isFile() ||
|
|
234
|
+
entry.isSymbolicLink() ||
|
|
235
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.json$/u.test(entry.name))) {
|
|
236
|
+
throw new Error(`Local release candidate for ${input.project.name} has an invalid file inventory.`);
|
|
237
|
+
}
|
|
238
|
+
let totalBytes = 0;
|
|
239
|
+
const files = entries.map(({ name }) => {
|
|
240
|
+
const path = (0, node_path_1.join)(source, name);
|
|
241
|
+
const identity = (0, node_fs_1.lstatSync)(path);
|
|
242
|
+
const maximumBytes = name === 'release-artifacts.json'
|
|
243
|
+
? MAX_LOCAL_RELEASE_ARTIFACTS_BYTES
|
|
244
|
+
: MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
|
|
245
|
+
if (!identity.isFile() ||
|
|
246
|
+
identity.isSymbolicLink() ||
|
|
247
|
+
identity.size <= 0 ||
|
|
248
|
+
identity.size > maximumBytes) {
|
|
249
|
+
throw new Error(`Local release candidate file ${input.project.name}/${name} is absent or unsafe.`);
|
|
250
|
+
}
|
|
251
|
+
totalBytes += identity.size;
|
|
252
|
+
return { name, path };
|
|
253
|
+
});
|
|
254
|
+
if (totalBytes > MAX_LOCAL_RELEASE_CANDIDATE_BYTES) {
|
|
255
|
+
throw new Error(`Local release candidate for ${input.project.name} exceeds the bounded inventory size.`);
|
|
256
|
+
}
|
|
257
|
+
const candidateFiles = files.map(({ name, path }) => ({
|
|
258
|
+
contents: (0, node_fs_1.readFileSync)(path),
|
|
259
|
+
name,
|
|
260
|
+
}));
|
|
261
|
+
const candidateFile = candidateFiles.find(({ name }) => name === 'candidate.json');
|
|
262
|
+
let candidate;
|
|
320
263
|
try {
|
|
321
|
-
|
|
264
|
+
candidate = JSON.parse(candidateFile?.contents.toString('utf8') ?? '');
|
|
322
265
|
}
|
|
323
|
-
catch
|
|
324
|
-
|
|
266
|
+
catch {
|
|
267
|
+
throw new Error(`Local release candidate for ${input.project.name} is invalid JSON.`);
|
|
325
268
|
}
|
|
326
|
-
if (
|
|
327
|
-
|
|
269
|
+
if (candidate.kind !== 'ebk-release-candidate' ||
|
|
270
|
+
candidate.schemaVersion !== 1 ||
|
|
271
|
+
candidate.slice !== input.project.name ||
|
|
272
|
+
candidate.sourceCommit !== input.sourceCommit) {
|
|
273
|
+
throw new Error(`Local release candidate for ${input.project.name} differs from the exact deployment source.`);
|
|
328
274
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
275
|
+
const destination = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name);
|
|
276
|
+
removeExactLocalEvidenceDirectory(input.root, destination);
|
|
277
|
+
ensurePrivateEvidenceDirectory(input.root, destination);
|
|
278
|
+
for (const { contents, name } of candidateFiles) {
|
|
279
|
+
if (name === 'release-artifacts.json') {
|
|
280
|
+
(0, private_state_js_1.atomicWritePrivateFileWithMaximumBytes)((0, node_path_1.join)(destination, name), contents, MAX_LOCAL_RELEASE_ARTIFACTS_BYTES);
|
|
335
281
|
}
|
|
336
|
-
|
|
337
|
-
|
|
282
|
+
else {
|
|
283
|
+
(0, private_state_js_1.atomicWritePrivateFile)((0, node_path_1.join)(destination, name), contents);
|
|
338
284
|
}
|
|
339
|
-
if (!accepted)
|
|
340
|
-
throw executionError;
|
|
341
285
|
}
|
|
342
286
|
}
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
287
|
+
function attestLocalContainerBinding(input) {
|
|
288
|
+
const path = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name, 'binding.json');
|
|
289
|
+
const identity = (0, node_fs_1.lstatSync)(path);
|
|
290
|
+
if (!identity.isFile() ||
|
|
291
|
+
identity.isSymbolicLink() ||
|
|
292
|
+
identity.size <= 0 ||
|
|
293
|
+
identity.size > 64 * 1024 * 1024) {
|
|
294
|
+
throw new Error(`Local protected container binding for ${input.project.name} is absent or unsafe.`);
|
|
295
|
+
}
|
|
296
|
+
const binding = (0, container_release_js_1.validateContainerReleaseBinding)(JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8')));
|
|
297
|
+
if (binding.accountId !== input.account ||
|
|
298
|
+
binding.region !== input.region ||
|
|
299
|
+
binding.slice !== input.project.name ||
|
|
300
|
+
binding.sourceCommit !== input.sourceCommit ||
|
|
301
|
+
binding.stage !== input.stage) {
|
|
302
|
+
throw new Error(`Local protected container binding for ${input.project.name} differs from the exact deployment authority.`);
|
|
346
303
|
}
|
|
347
|
-
return value;
|
|
348
304
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
before.size > BigInt(MAX_PLAN_BYTES)) {
|
|
367
|
-
throw new Error(`Local deployment plan for ${project} is not one bounded regular file.`);
|
|
368
|
-
}
|
|
369
|
-
const bytes = (0, node_fs_1.readFileSync)(path);
|
|
370
|
-
const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
|
|
371
|
-
if (before.dev !== after.dev ||
|
|
372
|
-
before.ino !== after.ino ||
|
|
373
|
-
before.mode !== after.mode ||
|
|
374
|
-
before.nlink !== after.nlink ||
|
|
375
|
-
before.size !== after.size ||
|
|
376
|
-
before.mtimeNs !== after.mtimeNs ||
|
|
377
|
-
before.ctimeNs !== after.ctimeNs ||
|
|
378
|
-
BigInt(bytes.byteLength) !== after.size) {
|
|
379
|
-
throw new Error(`Local deployment plan for ${project} changed while it was read.`);
|
|
380
|
-
}
|
|
381
|
-
let parsed;
|
|
305
|
+
/** Read only the bounded public outputs SST writes after a successful native
|
|
306
|
+
* deploy. This is post-operation smoke input, never preview or mutation
|
|
307
|
+
* authority. Stage and project identity prevent a stale output file from
|
|
308
|
+
* selecting another deployment. */
|
|
309
|
+
function readDeployedSmokeTarget(workspaceRoot, project, stage) {
|
|
310
|
+
const projectRoot = confinedWorkspacePath(workspaceRoot, project.root);
|
|
311
|
+
const outputPath = (0, node_path_1.join)(projectRoot, '.sst', 'outputs.json');
|
|
312
|
+
const identity = (0, node_fs_1.lstatSync)(outputPath);
|
|
313
|
+
if (!identity.isFile() ||
|
|
314
|
+
identity.isSymbolicLink() ||
|
|
315
|
+
identity.size < 2 ||
|
|
316
|
+
identity.size > 64 * 1024) {
|
|
317
|
+
throw new Error(`Native SST outputs for ${project.name} are absent or unsafe.`);
|
|
318
|
+
}
|
|
319
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)(outputPath, 'utf8'));
|
|
320
|
+
let api;
|
|
321
|
+
let web;
|
|
382
322
|
try {
|
|
383
|
-
|
|
323
|
+
api = new URL(String(value.apiUrl ?? ''));
|
|
324
|
+
web = new URL(String(value.webUrl ?? ''));
|
|
384
325
|
}
|
|
385
326
|
catch {
|
|
386
|
-
throw new Error(`
|
|
387
|
-
}
|
|
388
|
-
const plan = parsed;
|
|
389
|
-
if (plan.kind !== index_js_1.INFRASTRUCTURE_PLAN_KIND ||
|
|
390
|
-
plan.schemaVersion !== index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION ||
|
|
391
|
-
plan.action !== action ||
|
|
392
|
-
!SHA.test(plan.sourceCommit) ||
|
|
393
|
-
plan.target?.accountFingerprint !== digest(account) ||
|
|
394
|
-
plan.target?.region !== region ||
|
|
395
|
-
plan.target?.stage !== stage ||
|
|
396
|
-
!Array.isArray(plan.projects) ||
|
|
397
|
-
plan.projects.length !== 1 ||
|
|
398
|
-
plan.projects[0] !== project ||
|
|
399
|
-
!SHA256.test(plan.materialDigest) ||
|
|
400
|
-
(0, index_js_1.computeMaterialPlanDigest)(plan) !== plan.materialDigest) {
|
|
401
|
-
throw new Error(`Local deployment plan for ${project} is not the exact current deployment authority.`);
|
|
402
|
-
}
|
|
403
|
-
if (plan.sourceCommit !== deploymentSourceCommit) {
|
|
404
|
-
try {
|
|
405
|
-
(0, source_authority_js_1.validateReviewedReleaseCheckout)({
|
|
406
|
-
deploymentSourceCommit,
|
|
407
|
-
releaseSourceCommit: plan.sourceCommit,
|
|
408
|
-
repositoryRoot: context.root,
|
|
409
|
-
reviewedSourceCommit: deploymentSourceCommit,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
throw new Error(`Local deployment plan for ${project} is not bound through one exact approval-only source transition.`);
|
|
414
|
-
}
|
|
327
|
+
throw new Error(`Native SST outputs for ${project.name} contain invalid smoke URLs.`);
|
|
415
328
|
}
|
|
416
|
-
|
|
329
|
+
const credentialFreeHttps = (url) => url.protocol === 'https:' &&
|
|
330
|
+
!url.username &&
|
|
331
|
+
!url.password &&
|
|
332
|
+
!url.search &&
|
|
333
|
+
!url.hash;
|
|
334
|
+
if (value.name !== project.name ||
|
|
335
|
+
value.stage !== stage ||
|
|
336
|
+
!credentialFreeHttps(api) ||
|
|
337
|
+
!credentialFreeHttps(web) ||
|
|
338
|
+
api.pathname !== '/' ||
|
|
339
|
+
web.pathname.replace(/\/$/u, '') !== `/${project.name}` ||
|
|
340
|
+
!/^[a-z0-9]{1,128}$/u.test(String(value.authClientId ?? '')) ||
|
|
341
|
+
!/^[A-Za-z0-9_-]{1,128}_[A-Za-z0-9]{1,128}$/u.test(String(value.authUserPoolId ?? ''))) {
|
|
342
|
+
throw new Error(`Native SST outputs for ${project.name} do not match the deployed stage and smoke target.`);
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
apiUrl: api.href,
|
|
346
|
+
clientId: String(value.authClientId),
|
|
347
|
+
userPoolId: String(value.authUserPoolId),
|
|
348
|
+
webUrl: web.href,
|
|
349
|
+
};
|
|
417
350
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
before.nlink !== 1n ||
|
|
424
|
-
before.size > BigInt(MAX_PLAN_BYTES)) {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
const bytes = (0, node_fs_1.readFileSync)(path);
|
|
428
|
-
const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
|
|
429
|
-
if (before.dev !== after.dev ||
|
|
430
|
-
before.ino !== after.ino ||
|
|
431
|
-
before.mode !== after.mode ||
|
|
432
|
-
before.nlink !== after.nlink ||
|
|
433
|
-
before.size !== after.size ||
|
|
434
|
-
before.mtimeNs !== after.mtimeNs ||
|
|
435
|
-
before.ctimeNs !== after.ctimeNs ||
|
|
436
|
-
BigInt(bytes.byteLength) !== after.size) {
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
const authorization = record(JSON.parse(bytes.toString('utf8')), `${project} authorization`);
|
|
440
|
-
return (authorization.kind === 'ebk-infrastructure-authorization' &&
|
|
441
|
-
authorization.schemaVersion === index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION &&
|
|
442
|
-
authorization.action === 'deploy' &&
|
|
443
|
-
authorization.approval === undefined &&
|
|
444
|
-
authorization.localGovernance === undefined &&
|
|
445
|
-
authorization.governanceEvidenceDigests === undefined &&
|
|
446
|
-
authorization.plannedMaterialDigest === plan.materialDigest &&
|
|
447
|
-
authorization.recomputedMaterialDigest === plan.materialDigest &&
|
|
448
|
-
Array.isArray(authorization.sourceChangesSincePlan) &&
|
|
449
|
-
authorization.sourceChangesSincePlan.length === 0 &&
|
|
450
|
-
authorization.validation.allowed === false &&
|
|
451
|
-
authorization.validation.outcome === 'plan-risk-blocked' &&
|
|
452
|
-
authorization.validation.findings.length === 1 &&
|
|
453
|
-
authorization.validation.findings[0]?.code === 'approval-missing');
|
|
351
|
+
/** Determine the one native deployment order. Foundation is always first and
|
|
352
|
+
* production Slice admission remains a committed policy decision. */
|
|
353
|
+
function localDeploymentProjectOrder(policy, context, stage) {
|
|
354
|
+
if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, stage)) {
|
|
355
|
+
throw new Error(`Deployment stage ${stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
|
|
454
356
|
}
|
|
455
|
-
|
|
456
|
-
|
|
357
|
+
const projects = projectDescriptors(context);
|
|
358
|
+
const foundations = projects.filter((project) => project.tags.includes('scope:foundation') &&
|
|
359
|
+
supportsTarget(project, 'deploy'));
|
|
360
|
+
if (foundations.length !== 1) {
|
|
361
|
+
throw new Error(`Local deployment requires exactly one deployable Foundation; found ${foundations.length}.`);
|
|
457
362
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
Object.keys(input.plans).length !== input.orderedProjects.length) {
|
|
469
|
-
throw new Error('Local deployment plan summary input is invalid.');
|
|
470
|
-
}
|
|
471
|
-
const plans = input.orderedProjects.map((project) => {
|
|
472
|
-
const plan = input.plans[project];
|
|
473
|
-
if (!plan ||
|
|
474
|
-
plan.action !== action ||
|
|
475
|
-
plan.projects.length !== 1 ||
|
|
476
|
-
plan.projects[0] !== project ||
|
|
477
|
-
!SHA256.test(plan.materialDigest)) {
|
|
478
|
-
throw new Error(`Local deployment plan summary omitted exact project ${project}.`);
|
|
363
|
+
const slices = projects
|
|
364
|
+
.filter((project) => project.tags.includes('type:slice') &&
|
|
365
|
+
supportsTarget(project, 'deploy'))
|
|
366
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
367
|
+
if (stage === 'production') {
|
|
368
|
+
const missing = slices
|
|
369
|
+
.filter(({ name }) => policy.slices[name] === undefined)
|
|
370
|
+
.map(({ name }) => name);
|
|
371
|
+
if (missing.length > 0) {
|
|
372
|
+
throw new Error(`Registered Product Slices lack an explicit production policy: ${missing.join(', ')}.`);
|
|
479
373
|
}
|
|
480
|
-
|
|
374
|
+
}
|
|
375
|
+
const selectedSlices = stage === 'production'
|
|
376
|
+
? slices.filter(({ name }) => {
|
|
377
|
+
const status = policy.slices[name]?.productionStatus;
|
|
378
|
+
return status === 'pilot' || status === 'ready';
|
|
379
|
+
})
|
|
380
|
+
: slices;
|
|
381
|
+
const selected = [
|
|
382
|
+
{ kind: 'foundation', name: foundations[0]?.name ?? '' },
|
|
383
|
+
...selectedSlices.map(({ name }) => ({ kind: 'slice', name })),
|
|
384
|
+
];
|
|
385
|
+
const decision = (0, index_js_2.evaluateRepositoryPolicyRequest)(policy, {
|
|
386
|
+
action: 'deploy',
|
|
387
|
+
projects: selected,
|
|
388
|
+
stage,
|
|
481
389
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
phase: input.phase,
|
|
487
|
-
plans,
|
|
488
|
-
region: input.region,
|
|
489
|
-
sourceCommit: input.sourceCommit,
|
|
490
|
-
stage: input.stage,
|
|
491
|
-
}));
|
|
492
|
-
}
|
|
493
|
-
function retainEvidence(root, evidence) {
|
|
494
|
-
const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'local-deployment');
|
|
495
|
-
(0, node_fs_1.mkdirSync)(directory, { recursive: true, mode: 0o700 });
|
|
496
|
-
(0, node_fs_1.chmodSync)(directory, 0o700);
|
|
497
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, 'latest.json'), `${JSON.stringify(evidence, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
390
|
+
if (!decision.allowed) {
|
|
391
|
+
throw new Error(`Repository Operating Policy rejected local deployment:\n${decision.reasons.map((reason) => `- ${reason}`).join('\n')}`);
|
|
392
|
+
}
|
|
393
|
+
return selected.map(({ name }) => name);
|
|
498
394
|
}
|
|
499
|
-
|
|
500
|
-
[
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
]
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
property.kind !== 'update' ||
|
|
518
|
-
property.sensitive ||
|
|
519
|
-
observed.has(property.path)) {
|
|
520
|
-
return false;
|
|
395
|
+
function frameworkVersion(root) {
|
|
396
|
+
const candidates = [
|
|
397
|
+
(0, node_path_1.join)(root, 'node_modules', '@empire-builder-kit', 'nx', 'package.json'),
|
|
398
|
+
(0, node_path_1.join)(root, 'package.json'),
|
|
399
|
+
];
|
|
400
|
+
for (const path of candidates) {
|
|
401
|
+
try {
|
|
402
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
403
|
+
if (value.name === '@empire-builder-kit/nx' && value.version) {
|
|
404
|
+
return value.version;
|
|
405
|
+
}
|
|
406
|
+
const dependency = value.devDependencies?.['@empire-builder-kit/nx'] ??
|
|
407
|
+
value.dependencies?.['@empire-builder-kit/nx'];
|
|
408
|
+
if (dependency)
|
|
409
|
+
return dependency;
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// Try the next deterministic package identity.
|
|
521
413
|
}
|
|
522
|
-
observed.add(property.path);
|
|
523
414
|
}
|
|
524
|
-
return
|
|
415
|
+
return 'unknown';
|
|
525
416
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (plan.diff.resources.length === 0) {
|
|
534
|
-
return plan.driftStatus === 'no-drift';
|
|
535
|
-
}
|
|
536
|
-
if (plan.driftStatus !== 'drift-detected' ||
|
|
537
|
-
!plan.diff.resources.every(isFrameworkTagProviderObservation) ||
|
|
538
|
-
plan.requiredApprovals.some((approval) => ![
|
|
539
|
-
'deployment-review',
|
|
540
|
-
'drift-decision-and-state-recovery',
|
|
541
|
-
'point-of-no-return',
|
|
542
|
-
].includes(approval))) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
const observedResourceIds = new Set(plan.diff.resources.map(({ logicalId }) => logicalId));
|
|
546
|
-
return plan.resourceFindings.every((finding) => (finding.resourceId === undefined &&
|
|
547
|
-
finding.class === 'stateful-or-destructive' &&
|
|
548
|
-
finding.code === 'protected-state-refresh' &&
|
|
549
|
-
finding.disposition === 'block') ||
|
|
550
|
-
(finding.resourceId !== undefined &&
|
|
551
|
-
observedResourceIds.has(finding.resourceId) &&
|
|
552
|
-
finding.class === 'in-place-reviewed' &&
|
|
553
|
-
finding.code === 'in-place-update' &&
|
|
554
|
-
finding.disposition === 'review'));
|
|
417
|
+
function prepareReceiptDestination(root) {
|
|
418
|
+
const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'deployment');
|
|
419
|
+
ensurePrivateEvidenceDirectory(root, directory);
|
|
420
|
+
const path = (0, node_path_1.join)(directory, 'latest.json');
|
|
421
|
+
if ((0, node_fs_1.existsSync)(path))
|
|
422
|
+
(0, private_state_js_1.assertPrivatePath)(path, 'file');
|
|
423
|
+
return path;
|
|
555
424
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
plan.resourceFindings.length === 0);
|
|
568
|
-
}
|
|
569
|
-
if (plan.aggregateRisk !== 'in-place-reviewed' ||
|
|
570
|
-
(plan.driftStatus !== 'no-drift' &&
|
|
571
|
-
plan.driftStatus !== 'drift-detected') ||
|
|
572
|
-
plan.requiredApprovals.length !== 1 ||
|
|
573
|
-
plan.requiredApprovals[0] !== 'deployment-review') {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
const providerReads = new Map();
|
|
577
|
-
for (const resource of plan.diff.resources) {
|
|
578
|
-
const retainedResourceType = providerReads.get(resource.logicalId);
|
|
579
|
-
if (resource.operation !== 'read' ||
|
|
580
|
-
resource.providerOperation !== 'read' ||
|
|
581
|
-
resource.replacement ||
|
|
582
|
-
resource.changedProperties.length > 0 ||
|
|
583
|
-
(retainedResourceType !== undefined &&
|
|
584
|
-
retainedResourceType !== resource.resourceType)) {
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
providerReads.set(resource.logicalId, resource.resourceType);
|
|
588
|
-
}
|
|
589
|
-
if (plan.resourceFindings.length !== providerReads.size) {
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
const providerReadFindings = new Set();
|
|
593
|
-
for (const finding of plan.resourceFindings) {
|
|
594
|
-
if (finding.resourceId === undefined ||
|
|
595
|
-
!providerReads.has(finding.resourceId) ||
|
|
596
|
-
finding.class !== 'in-place-reviewed' ||
|
|
597
|
-
finding.code !== 'provider-read' ||
|
|
598
|
-
finding.disposition !== 'review' ||
|
|
599
|
-
providerReadFindings.has(finding.resourceId)) {
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
providerReadFindings.add(finding.resourceId);
|
|
425
|
+
function writeReceipt(root, receipt) {
|
|
426
|
+
(0, private_state_js_1.atomicWritePrivateFile)(prepareReceiptDestination(root), `${JSON.stringify(receipt, null, 2)}\n`);
|
|
427
|
+
}
|
|
428
|
+
function invalidatePriorReceipt(root) {
|
|
429
|
+
const path = prepareReceiptDestination(root);
|
|
430
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
431
|
+
return;
|
|
432
|
+
(0, private_state_js_1.assertPrivatePath)(path, 'file');
|
|
433
|
+
(0, node_fs_1.rmSync)(path, { force: true });
|
|
434
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
435
|
+
throw new Error('Prior deployment receipt invalidation failed.');
|
|
603
436
|
}
|
|
604
|
-
return providerReadFindings.size === providerReads.size;
|
|
605
437
|
}
|
|
606
|
-
function
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
438
|
+
async function runNxTarget(input) {
|
|
439
|
+
const forwarded = [
|
|
440
|
+
`--stage=${input.stage}`,
|
|
441
|
+
`--expectedRoleArn=${input.expectedRoleArn}`,
|
|
442
|
+
`--region=${input.region}`,
|
|
443
|
+
...(input.profile ? [`--profile=${input.profile}`] : []),
|
|
444
|
+
];
|
|
445
|
+
const args = [
|
|
446
|
+
(0, node_path_1.join)(input.context.root, 'node_modules', 'nx', 'dist', 'bin', 'nx.js'),
|
|
447
|
+
'run',
|
|
448
|
+
`${input.project}:${input.target}`,
|
|
449
|
+
'--',
|
|
450
|
+
...forwarded,
|
|
451
|
+
];
|
|
452
|
+
const result = input.runSync
|
|
453
|
+
? input.runSync(process.execPath, args, {
|
|
454
|
+
cwd: input.context.root,
|
|
455
|
+
env: input.environment,
|
|
456
|
+
shell: false,
|
|
457
|
+
stdio: 'inherit',
|
|
458
|
+
})
|
|
459
|
+
: await (0, run_sst_command_js_1.runGovernedChildProcess)({
|
|
460
|
+
args,
|
|
461
|
+
cwd: input.context.root,
|
|
462
|
+
environment: input.environment,
|
|
463
|
+
executable: process.execPath,
|
|
464
|
+
...(input.spawnChild ? { spawnChild: input.spawnChild } : {}),
|
|
465
|
+
});
|
|
466
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
467
|
+
throw new Error(result.error
|
|
468
|
+
? `${input.target} failed for ${input.project}: ${result.error.message}`
|
|
469
|
+
: result.signal
|
|
470
|
+
? `${input.target} failed for ${input.project} with signal ${result.signal}.`
|
|
471
|
+
: `${input.target} failed for ${input.project} with status ${result.status ?? 'unknown'}.`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function productionConfirmation(account, region, commit) {
|
|
475
|
+
return `deploy:production:${account}:${region}:${commit}`;
|
|
476
|
+
}
|
|
477
|
+
async function defaultIdentifyGithubCaller(region) {
|
|
478
|
+
return new client_sts_1.STSClient({
|
|
479
|
+
endpoint: (0, identity_js_1.canonicalStsEndpoint)(region),
|
|
480
|
+
region,
|
|
481
|
+
}).send(new client_sts_1.GetCallerIdentityCommand({}));
|
|
482
|
+
}
|
|
483
|
+
function assertGithubDeploymentAuthority(input) {
|
|
484
|
+
const repository = process.env.GITHUB_REPOSITORY?.trim() ?? '';
|
|
485
|
+
const ref = process.env.GITHUB_REF?.trim() ?? '';
|
|
486
|
+
const eventName = process.env.GITHUB_EVENT_NAME?.trim() ?? '';
|
|
487
|
+
const callerWorkflow = process.env.GITHUB_WORKFLOW_REF?.trim() ?? '';
|
|
488
|
+
const calledWorkflow = process.env.EBK_GITHUB_JOB_WORKFLOW_REF?.trim() ?? '';
|
|
489
|
+
const expectedBranch = input.stage === 'production' ? 'main' : 'develop';
|
|
490
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
491
|
+
const expectedCalledWorkflow = `${repository}/.github/workflows/deploy.yml@${expectedRef}`;
|
|
492
|
+
const expectedCallerWorkflow = eventName === 'push'
|
|
493
|
+
? `${repository}/.github/workflows/ci.yml@${expectedRef}`
|
|
494
|
+
: eventName === 'workflow_dispatch'
|
|
495
|
+
? expectedCalledWorkflow
|
|
496
|
+
: undefined;
|
|
497
|
+
const allowedEvent = eventName === 'workflow_dispatch' ||
|
|
498
|
+
(eventName === 'push' && input.policy.deploymentProfile !== 'starter');
|
|
499
|
+
if (process.env.GITHUB_ACTIONS !== 'true' ||
|
|
500
|
+
!repository ||
|
|
501
|
+
!allowedEvent ||
|
|
502
|
+
process.env.GITHUB_SHA !== input.commit ||
|
|
503
|
+
ref !== expectedRef ||
|
|
504
|
+
callerWorkflow !== expectedCallerWorkflow ||
|
|
505
|
+
calledWorkflow !== expectedCalledWorkflow) {
|
|
506
|
+
throw new Error('Protected deployment requires the exact generated GitHub caller and reusable deploy workflows, branch, and immutable source commit.');
|
|
507
|
+
}
|
|
508
|
+
if (input.policy.deploymentProfile === 'starter' &&
|
|
509
|
+
input.stage === 'staging') {
|
|
510
|
+
throw new Error('The starter deployment profile has no staging stage.');
|
|
511
|
+
}
|
|
512
|
+
const clean = git(input.root, ['status', '--porcelain=v1', '--untracked-files=all'], input.run);
|
|
513
|
+
if (clean !== '') {
|
|
514
|
+
throw new Error('Protected GitHub deployment requires a clean checkout.');
|
|
515
|
+
}
|
|
618
516
|
}
|
|
619
517
|
async function runLocalDeploy(options, context, dependencies = {}) {
|
|
620
518
|
const run = dependencies.run ?? node_child_process_1.spawnSync;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const expectedConfirmation = (0, infrastructure_plan_js_1.localProductionConfirmation)({
|
|
652
|
-
action,
|
|
653
|
-
accountId: expectedAccount,
|
|
654
|
-
region,
|
|
655
|
-
sourceCommit,
|
|
656
|
-
});
|
|
657
|
-
if (options.stage === 'production' &&
|
|
658
|
-
options.confirmation !== expectedConfirmation) {
|
|
659
|
-
throw new Error(`Local production ${action} requires --confirmation=${expectedConfirmation}.`);
|
|
660
|
-
}
|
|
661
|
-
const caller = await (dependencies.caller ??
|
|
662
|
-
(async (targetRegion) => new client_sts_1.STSClient({
|
|
663
|
-
region: targetRegion,
|
|
664
|
-
...(options.profile
|
|
665
|
-
? { credentials: (0, credential_providers_1.fromIni)({ profile: options.profile }) }
|
|
666
|
-
: {}),
|
|
667
|
-
}).send(new client_sts_1.GetCallerIdentityCommand({}))))(region);
|
|
668
|
-
const account = caller.Account?.trim() ?? '';
|
|
669
|
-
const arn = caller.Arn?.trim() ?? '';
|
|
670
|
-
if (account !== expectedAccount ||
|
|
671
|
-
!(0, infrastructure_plan_js_1.matchesExpectedAssumedRoleArn)(expectedAccount, arn, options.expectedRoleArn)) {
|
|
672
|
-
throw new Error('Local AWS caller does not match the committed account and exact temporary assumed role.');
|
|
673
|
-
}
|
|
674
|
-
const orderedProjects = projectInventory(context, options.stage, phase, action);
|
|
675
|
-
const sourceSnapshots = toolOwnedSourceSnapshots(context, orderedProjects);
|
|
676
|
-
const startedAt = (dependencies.now ?? (() => new Date()))().toISOString();
|
|
677
|
-
let backend;
|
|
678
|
-
let result = 'failed';
|
|
679
|
-
let planDigests = {};
|
|
680
|
-
let planSummaryDigest = null;
|
|
681
|
-
let deployNoOpProjects = [];
|
|
682
|
-
const mutatedProjects = [];
|
|
683
|
-
let operatorAcknowledgementDigest = null;
|
|
684
|
-
let replanRequiredProject = null;
|
|
685
|
-
let refreshNoOpProjects = [];
|
|
686
|
-
try {
|
|
687
|
-
backend = await (dependencies.backend ?? prepareLocalSstBackend)({
|
|
688
|
-
account: expectedAccount,
|
|
689
|
-
applications: orderedProjects,
|
|
690
|
-
...(options.profile ? { profile: options.profile } : {}),
|
|
519
|
+
const now = dependencies.now ?? (() => new Date());
|
|
520
|
+
const startedAt = now().toISOString();
|
|
521
|
+
const children = [];
|
|
522
|
+
const migrations = {};
|
|
523
|
+
const smoke = {};
|
|
524
|
+
let account = '';
|
|
525
|
+
let actor = '';
|
|
526
|
+
let policy;
|
|
527
|
+
let region = '';
|
|
528
|
+
let source = { clean: false, commit: '', workingTreeDigest: '' };
|
|
529
|
+
let transport;
|
|
530
|
+
let productionDatabaseBefore;
|
|
531
|
+
let productionDatabaseAfterFoundation;
|
|
532
|
+
let outcome = 'failed';
|
|
533
|
+
let receiptDestinationPrepared = false;
|
|
534
|
+
const githubAuthority = process.env.GITHUB_ACTIONS === 'true';
|
|
535
|
+
const retain = () => {
|
|
536
|
+
if (!policy || !receiptDestinationPrepared)
|
|
537
|
+
return;
|
|
538
|
+
writeReceipt(context.root, {
|
|
539
|
+
account,
|
|
540
|
+
actor,
|
|
541
|
+
authority: githubAuthority ? 'github' : 'local',
|
|
542
|
+
children,
|
|
543
|
+
completedAt: now().toISOString(),
|
|
544
|
+
frameworkVersion: frameworkVersion(context.root),
|
|
545
|
+
kind: 'ebk-deployment-receipt',
|
|
546
|
+
migrations,
|
|
547
|
+
outcome,
|
|
548
|
+
profile: policy.deploymentProfile,
|
|
691
549
|
region,
|
|
550
|
+
schemaVersion: 1,
|
|
551
|
+
smoke,
|
|
552
|
+
source,
|
|
692
553
|
stage: options.stage,
|
|
554
|
+
startedAt,
|
|
693
555
|
});
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
// Local deployment's reviewed clean Git commit is the one stage-wide
|
|
703
|
-
// workload-role candidate shared by Foundation and every Slice. Never
|
|
704
|
-
// accept an ambient candidate identity from the caller.
|
|
705
|
-
EBK_ACTION: action,
|
|
706
|
-
EBK_CANDIDATE_IDENTITY: sourceCommit,
|
|
707
|
-
EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
|
|
708
|
-
EBK_LOCAL_SOURCE_TREE: sourceTree,
|
|
709
|
-
EBK_SOURCE_COMMIT: sourceCommit,
|
|
710
|
-
EBK_STAGE: options.stage,
|
|
711
|
-
ORDERED_PROJECTS: orderedProjects.join(','),
|
|
712
|
-
};
|
|
713
|
-
for (const key of PROTECTED_PLAN_KEYS)
|
|
714
|
-
delete environment[key];
|
|
715
|
-
if (options.profile) {
|
|
716
|
-
for (const key of AMBIENT_CREDENTIAL_KEYS)
|
|
717
|
-
delete environment[key];
|
|
556
|
+
};
|
|
557
|
+
try {
|
|
558
|
+
invalidatePriorReceipt(context.root);
|
|
559
|
+
receiptDestinationPrepared = true;
|
|
560
|
+
(0, run_sst_command_js_1.assertGovernedAwsLifecyclePlatform)(dependencies.platform);
|
|
561
|
+
policy = (0, index_js_2.readRepositoryOperatingPolicy)(context.root);
|
|
562
|
+
if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, options.stage)) {
|
|
563
|
+
throw new Error(`Deployment stage ${options.stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
|
|
718
564
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
for (const project of orderedProjects) {
|
|
726
|
-
runNxWithToolOwnedSourceReconciliation(context, project, 'infrastructure-plan', [`--action=${action}`, ...common], environment, sourceSnapshots, run, action === 'deploy'
|
|
727
|
-
? () => {
|
|
728
|
-
const plan = retainedPlan(context, project, sourceCommit, expectedAccount, region, options.stage, action);
|
|
729
|
-
const accepted = isFreshInvocationPlan(plan, startedAt, (dependencies.now ?? (() => new Date()))()) &&
|
|
730
|
-
isSemanticallyConvergedDeploy(plan) &&
|
|
731
|
-
hasExactPendingLocalAuthorizationRefusal(context, project, plan);
|
|
732
|
-
if (accepted) {
|
|
733
|
-
devkit_1.logger.info(`Local infrastructure-plan for ${project} returned a conservative non-success after emitting one fresh provider-read-only semantic convergence record.`);
|
|
734
|
-
}
|
|
735
|
-
return accepted;
|
|
736
|
-
}
|
|
737
|
-
: undefined);
|
|
565
|
+
if (!githubAuthority &&
|
|
566
|
+
(0, index_js_2.requiresGithubDeployment)(policy.deploymentProfile, options.stage)) {
|
|
567
|
+
throw new Error(`The ${policy.deploymentProfile} profile requires GitHub deployment authority for ${options.stage}.`);
|
|
568
|
+
}
|
|
569
|
+
if (!ROLE.test(options.expectedRoleArn)) {
|
|
570
|
+
throw new Error('Local deployment requires one exact IAM role ARN.');
|
|
738
571
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
project,
|
|
745
|
-
plans[project].materialDigest,
|
|
746
|
-
]));
|
|
747
|
-
planSummaryDigest = localPlanSummaryDigest({
|
|
748
|
-
account: expectedAccount,
|
|
749
|
-
action,
|
|
750
|
-
orderedProjects,
|
|
751
|
-
phase,
|
|
752
|
-
plans,
|
|
572
|
+
region = (0, index_js_2.configuredDeploymentRegion)(policy.deploymentRegion);
|
|
573
|
+
account = (0, index_js_2.configuredDeploymentAccountForStage)(policy.deploymentProfile, policy.deploymentAccounts, options.stage);
|
|
574
|
+
const expectedRoleArn = (0, index_js_1.createStageAuthorityIdentity)({
|
|
575
|
+
accountId: account,
|
|
576
|
+
partition: partitionForRegion(region),
|
|
753
577
|
region,
|
|
754
|
-
sourceCommit,
|
|
755
578
|
stage: options.stage,
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
579
|
+
}).deploymentRoleArn;
|
|
580
|
+
if (options.expectedRoleArn !== expectedRoleArn) {
|
|
581
|
+
throw new Error(`Deployment role must equal the committed ${options.stage} stage authority role ${expectedRoleArn}.`);
|
|
582
|
+
}
|
|
583
|
+
const identity = localWorkingTreeDigest(context.root, run);
|
|
584
|
+
source = {
|
|
585
|
+
clean: identity.clean,
|
|
586
|
+
commit: identity.commit,
|
|
587
|
+
workingTreeDigest: identity.digest,
|
|
761
588
|
};
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
589
|
+
if ((githubAuthority || options.stage !== 'development') && !source.clean) {
|
|
590
|
+
throw new Error(`Local ${options.stage} deployment requires a completely clean source tree.`);
|
|
591
|
+
}
|
|
592
|
+
if (githubAuthority) {
|
|
593
|
+
if (options.profile) {
|
|
594
|
+
throw new Error('Protected GitHub deployment must use ambient OIDC credentials, not a local AWS profile.');
|
|
766
595
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
refreshNoOpProjects =
|
|
773
|
-
action === 'refresh'
|
|
774
|
-
? orderedProjects.filter((project) => isSemanticallyConvergedRefresh(plans[project]))
|
|
775
|
-
: [];
|
|
776
|
-
const mutationNoOpProjectSet = new Set([
|
|
777
|
-
...deployNoOpProjects,
|
|
778
|
-
...refreshNoOpProjects,
|
|
779
|
-
]);
|
|
780
|
-
const materialProjectIndex = orderedProjects.findIndex((project) => !mutationNoOpProjectSet.has(project));
|
|
781
|
-
if (materialProjectIndex >= 0) {
|
|
782
|
-
const project = orderedProjects[materialProjectIndex];
|
|
783
|
-
const plan = plans[project];
|
|
784
|
-
const challenge = `approve:${action}:${options.stage}:${project}:${plan.materialDigest}`;
|
|
785
|
-
devkit_1.logger.info(`[ebk-local-deployment-review]\n${JSON.stringify({
|
|
786
|
-
action,
|
|
787
|
-
challenge,
|
|
788
|
-
firstMaterialProject: project,
|
|
789
|
-
orderedProjects,
|
|
790
|
-
plan: {
|
|
791
|
-
aggregateRisk: plan.aggregateRisk,
|
|
792
|
-
diff: plan.diff,
|
|
793
|
-
materialDigest: plan.materialDigest,
|
|
794
|
-
requiredApprovals: plan.requiredApprovals,
|
|
795
|
-
resourceFindings: plan.resourceFindings,
|
|
796
|
-
stateDigest: plan.state.digest,
|
|
797
|
-
},
|
|
798
|
-
planSummaryDigest,
|
|
799
|
-
sourceCommit,
|
|
596
|
+
assertGithubDeploymentAuthority({
|
|
597
|
+
commit: source.commit,
|
|
598
|
+
policy,
|
|
599
|
+
root: context.root,
|
|
600
|
+
run,
|
|
800
601
|
stage: options.stage,
|
|
801
|
-
}
|
|
602
|
+
});
|
|
603
|
+
const caller = await (dependencies.identifyGithubCaller ?? defaultIdentifyGithubCaller)(region);
|
|
604
|
+
if (caller.Account?.trim() !== account ||
|
|
605
|
+
!(0, identity_js_1.matchesExpectedAssumedRoleArn)(account, caller.Arn?.trim() ?? '', expectedRoleArn)) {
|
|
606
|
+
throw new Error('Protected GitHub deployment caller does not match the committed account and exact assumed role.');
|
|
607
|
+
}
|
|
608
|
+
actor = process.env.GITHUB_ACTOR?.trim() ?? '';
|
|
609
|
+
if (!actor) {
|
|
610
|
+
throw new Error('Protected GitHub deployment actor is absent.');
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else if (options.stage === 'staging') {
|
|
614
|
+
const challenge = `approve:deploy:staging:${source.commit}`;
|
|
802
615
|
let acknowledged = false;
|
|
803
616
|
try {
|
|
804
617
|
acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
|
|
805
|
-
action,
|
|
618
|
+
action: 'deploy',
|
|
806
619
|
challenge,
|
|
807
|
-
|
|
808
|
-
project,
|
|
620
|
+
sourceCommit: source.commit,
|
|
809
621
|
stage: options.stage,
|
|
810
622
|
});
|
|
811
623
|
}
|
|
812
624
|
catch (error) {
|
|
813
625
|
if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
|
|
814
|
-
|
|
626
|
+
outcome = 'review-required';
|
|
815
627
|
}
|
|
816
628
|
throw error;
|
|
817
629
|
}
|
|
818
630
|
if (!acknowledged) {
|
|
819
|
-
|
|
820
|
-
throw new Error(
|
|
631
|
+
outcome = 'review-required';
|
|
632
|
+
throw new Error('Local staging deployment acknowledgement was declined.');
|
|
821
633
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
634
|
+
}
|
|
635
|
+
if (!githubAuthority && options.stage === 'production') {
|
|
636
|
+
const expected = productionConfirmation(account, region, source.commit);
|
|
637
|
+
let acknowledged = false;
|
|
638
|
+
try {
|
|
639
|
+
acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
|
|
640
|
+
action: 'deploy',
|
|
641
|
+
challenge: expected,
|
|
642
|
+
sourceCommit: source.commit,
|
|
643
|
+
stage: options.stage,
|
|
644
|
+
});
|
|
832
645
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
646
|
+
catch (error) {
|
|
647
|
+
if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
|
|
648
|
+
outcome = 'review-required';
|
|
649
|
+
}
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
if (!acknowledged) {
|
|
653
|
+
outcome = 'review-required';
|
|
654
|
+
throw new Error('Local production deployment acknowledgement was declined.');
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const orderedProjects = localDeploymentProjectOrder(policy, context, options.stage);
|
|
658
|
+
const descriptors = projectDescriptors(context);
|
|
659
|
+
const localProtectedContainerStage = !githubAuthority &&
|
|
660
|
+
requiresLocalContainerPreparation(policy.deploymentProfile, options.stage)
|
|
661
|
+
? options.stage
|
|
662
|
+
: undefined;
|
|
663
|
+
const localProtectedContainerProjects = localProtectedContainerStage
|
|
664
|
+
? orderedProjects
|
|
665
|
+
.map((project) => descriptors.find((candidate) => candidate.name === project))
|
|
666
|
+
.filter((project) => project?.tags.includes('type:slice') === true)
|
|
667
|
+
: [];
|
|
668
|
+
for (const project of localProtectedContainerProjects) {
|
|
669
|
+
const missingTargets = LOCAL_CONTAINER_TARGETS.filter((target) => !supportsTarget(project, target));
|
|
670
|
+
if (missingTargets.length > 0) {
|
|
671
|
+
throw new Error(`Local ${policy.deploymentProfile} ${options.stage} requires the complete immutable OCI target set for ${project.name}: ${missingTargets.join(', ')}.`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!githubAuthority) {
|
|
675
|
+
transport = await (dependencies.createTransport ?? federated_credential_transport_js_1.createLocalFederatedCredentialTransport)({
|
|
676
|
+
account,
|
|
677
|
+
expectedRoleArn,
|
|
678
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
679
|
+
region,
|
|
680
|
+
});
|
|
681
|
+
const firstIdentity = await transport.refresh();
|
|
682
|
+
actor = firstIdentity.actorArn;
|
|
683
|
+
}
|
|
684
|
+
const ownershipEnvironment = transport
|
|
685
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
686
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
687
|
+
preserveVerifiedGithubCredentials: true,
|
|
688
|
+
});
|
|
689
|
+
await (dependencies.ownership ?? index_js_3.assertWorkspaceStageOwnershipForWorkspace)(context.root, options.stage, {
|
|
690
|
+
...(ownershipEnvironment.AWS_CONFIG_FILE
|
|
691
|
+
? { configFilepath: ownershipEnvironment.AWS_CONFIG_FILE }
|
|
692
|
+
: {}),
|
|
693
|
+
...(ownershipEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
694
|
+
? {
|
|
695
|
+
credentialsFilepath: ownershipEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
696
|
+
}
|
|
697
|
+
: {}),
|
|
698
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
699
|
+
});
|
|
700
|
+
if (options.stage === 'production') {
|
|
701
|
+
if (transport)
|
|
702
|
+
await transport.refresh();
|
|
703
|
+
const boundaryEnvironment = transport
|
|
704
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
705
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
706
|
+
preserveVerifiedGithubCredentials: true,
|
|
707
|
+
});
|
|
708
|
+
productionDatabaseBefore = await (dependencies.attestProductionDatabase ??
|
|
709
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
710
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
711
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
712
|
+
: {}),
|
|
713
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
714
|
+
? {
|
|
715
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
716
|
+
}
|
|
717
|
+
: {}),
|
|
718
|
+
expectedAccountId: account,
|
|
719
|
+
expectedRegion: region,
|
|
720
|
+
expectedRoleArn,
|
|
721
|
+
now: now(),
|
|
722
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
723
|
+
requireExisting: false,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
const localContainerRunId = localProtectedContainerProjects.length === 0
|
|
727
|
+
? ''
|
|
728
|
+
: ((0, node_crypto_1.randomBytes)(8).readBigUInt64BE() + 1n).toString();
|
|
729
|
+
for (const project of localProtectedContainerProjects) {
|
|
730
|
+
resetLocalContainerWorkingEvidence({
|
|
842
731
|
project,
|
|
843
|
-
|
|
732
|
+
root: context.root,
|
|
733
|
+
stage: localProtectedContainerStage,
|
|
844
734
|
});
|
|
735
|
+
for (const target of LOCAL_CONTAINER_TARGETS) {
|
|
736
|
+
if (transport)
|
|
737
|
+
await transport.refresh();
|
|
738
|
+
const environment = {
|
|
739
|
+
...(transport
|
|
740
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
741
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env)),
|
|
742
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
743
|
+
AWS_REGION: region,
|
|
744
|
+
EBK_LOCAL_CONTAINER_RUN_ID: localContainerRunId,
|
|
745
|
+
EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
|
|
746
|
+
EBK_DEPLOYMENT_PROFILE: policy.deploymentProfile,
|
|
747
|
+
EBK_NX_STAGE_LIFECYCLE: 'local',
|
|
748
|
+
EBK_RELEASE_SOURCE_COMMIT: source.commit,
|
|
749
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
750
|
+
EBK_STAGE: options.stage,
|
|
751
|
+
EBK_WORKING_TREE_DIRTY: 'false',
|
|
752
|
+
};
|
|
753
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
754
|
+
const childStartedAt = now().toISOString();
|
|
755
|
+
try {
|
|
756
|
+
await runNxTarget({
|
|
757
|
+
context,
|
|
758
|
+
environment,
|
|
759
|
+
expectedRoleArn,
|
|
760
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
761
|
+
project: project.name,
|
|
762
|
+
region,
|
|
763
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
764
|
+
...(dependencies.spawnChild
|
|
765
|
+
? { spawnChild: dependencies.spawnChild }
|
|
766
|
+
: {}),
|
|
767
|
+
stage: options.stage,
|
|
768
|
+
target,
|
|
769
|
+
});
|
|
770
|
+
if (target === 'release-candidate') {
|
|
771
|
+
materializeLocalReleaseCandidate({
|
|
772
|
+
project,
|
|
773
|
+
root: context.root,
|
|
774
|
+
sourceCommit: source.commit,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
if (target === 'container-release-assemble') {
|
|
778
|
+
(dependencies.attestContainerBinding ?? attestLocalContainerBinding)({
|
|
779
|
+
account,
|
|
780
|
+
project,
|
|
781
|
+
region,
|
|
782
|
+
root: context.root,
|
|
783
|
+
sourceCommit: source.commit,
|
|
784
|
+
stage: options.stage,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
children.push({
|
|
788
|
+
completedAt: now().toISOString(),
|
|
789
|
+
outcome: 'succeeded',
|
|
790
|
+
project: project.name,
|
|
791
|
+
startedAt: childStartedAt,
|
|
792
|
+
target,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
children.push({
|
|
797
|
+
completedAt: now().toISOString(),
|
|
798
|
+
outcome: 'failed',
|
|
799
|
+
project: project.name,
|
|
800
|
+
startedAt: childStartedAt,
|
|
801
|
+
target,
|
|
802
|
+
});
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
for (const project of orderedProjects) {
|
|
808
|
+
const descriptor = descriptors.find((candidate) => candidate.name === project);
|
|
809
|
+
if (!descriptor) {
|
|
810
|
+
throw new Error(`Local deployment project ${project} disappeared.`);
|
|
811
|
+
}
|
|
812
|
+
if (transport)
|
|
813
|
+
await transport.refresh();
|
|
814
|
+
const environment = {
|
|
815
|
+
...(transport
|
|
816
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
817
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
818
|
+
preserveVerifiedGithubCredentials: true,
|
|
819
|
+
})),
|
|
820
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
821
|
+
AWS_REGION: region,
|
|
822
|
+
EBK_LOCAL_SOURCE_TREE: source.workingTreeDigest,
|
|
823
|
+
EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
|
|
824
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
825
|
+
EBK_WORKING_TREE_DIRTY: source.clean ? 'false' : 'true',
|
|
826
|
+
};
|
|
827
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
828
|
+
const childStartedAt = now().toISOString();
|
|
845
829
|
try {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
830
|
+
await runNxTarget({
|
|
831
|
+
context,
|
|
832
|
+
environment,
|
|
833
|
+
expectedRoleArn,
|
|
834
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
835
|
+
project,
|
|
836
|
+
region,
|
|
837
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
838
|
+
...(dependencies.spawnChild
|
|
839
|
+
? { spawnChild: dependencies.spawnChild }
|
|
840
|
+
: {}),
|
|
841
|
+
stage: options.stage,
|
|
842
|
+
target: 'deploy',
|
|
843
|
+
});
|
|
844
|
+
children.push({
|
|
845
|
+
completedAt: now().toISOString(),
|
|
846
|
+
outcome: 'succeeded',
|
|
847
|
+
project,
|
|
848
|
+
startedAt: childStartedAt,
|
|
849
|
+
target: 'deploy',
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
children.push({
|
|
854
|
+
completedAt: now().toISOString(),
|
|
855
|
+
outcome: 'failed',
|
|
856
|
+
project,
|
|
857
|
+
startedAt: childStartedAt,
|
|
858
|
+
target: 'deploy',
|
|
859
|
+
});
|
|
860
|
+
throw error;
|
|
856
861
|
}
|
|
857
|
-
|
|
858
|
-
(
|
|
862
|
+
if (options.stage === 'production' &&
|
|
863
|
+
descriptor.tags.includes('scope:foundation')) {
|
|
864
|
+
if (transport)
|
|
865
|
+
await transport.refresh();
|
|
866
|
+
const boundaryEnvironment = transport
|
|
867
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
868
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
869
|
+
preserveVerifiedGithubCredentials: true,
|
|
870
|
+
});
|
|
871
|
+
const postflightStartedAt = now().toISOString();
|
|
872
|
+
const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
|
|
873
|
+
try {
|
|
874
|
+
productionDatabaseAfterFoundation = await (dependencies.attestProductionDatabase ??
|
|
875
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
876
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
877
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
878
|
+
: {}),
|
|
879
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
880
|
+
? {
|
|
881
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
882
|
+
}
|
|
883
|
+
: {}),
|
|
884
|
+
expectedAccountId: account,
|
|
885
|
+
expectedCapacity: {
|
|
886
|
+
max: Number.parseFloat(profile.aurora.max),
|
|
887
|
+
min: Number.parseFloat(profile.aurora.min),
|
|
888
|
+
},
|
|
889
|
+
expectedRegion: region,
|
|
890
|
+
expectedRoleArn,
|
|
891
|
+
now: now(),
|
|
892
|
+
priorDiscovery: productionDatabaseBefore,
|
|
893
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
894
|
+
requireExisting: true,
|
|
895
|
+
});
|
|
896
|
+
children.push({
|
|
897
|
+
completedAt: now().toISOString(),
|
|
898
|
+
outcome: 'succeeded',
|
|
899
|
+
project,
|
|
900
|
+
startedAt: postflightStartedAt,
|
|
901
|
+
target: 'production-database-postflight',
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
children.push({
|
|
906
|
+
completedAt: now().toISOString(),
|
|
907
|
+
outcome: 'failed',
|
|
908
|
+
project,
|
|
909
|
+
startedAt: postflightStartedAt,
|
|
910
|
+
target: 'production-database-postflight',
|
|
911
|
+
});
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
migrations[project] = supportsTarget(descriptor, 'db-migrate')
|
|
916
|
+
? 'completed-by-deploy-target'
|
|
917
|
+
: 'not-applicable';
|
|
918
|
+
if (supportsTarget(descriptor, 'smoke-deployed')) {
|
|
919
|
+
if (transport)
|
|
920
|
+
await transport.refresh();
|
|
921
|
+
const smokeTarget = readDeployedSmokeTarget(context.root, descriptor, options.stage);
|
|
922
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
923
|
+
const smokeStartedAt = now().toISOString();
|
|
924
|
+
try {
|
|
925
|
+
await runNxTarget({
|
|
926
|
+
context,
|
|
927
|
+
environment: {
|
|
928
|
+
...(transport
|
|
929
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
930
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
931
|
+
preserveVerifiedGithubCredentials: true,
|
|
932
|
+
})),
|
|
933
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
934
|
+
AWS_REGION: region,
|
|
935
|
+
EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
|
|
936
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
937
|
+
EBK_SMOKE_API_URL: smokeTarget.apiUrl,
|
|
938
|
+
EBK_SMOKE_AUTHORITY_VERSION: 'cognito-admin-cloudwatch-observe-v1',
|
|
939
|
+
EBK_SMOKE_CLIENT_ID: smokeTarget.clientId,
|
|
940
|
+
EBK_SMOKE_URL: smokeTarget.webUrl,
|
|
941
|
+
EBK_SMOKE_USER_POOL_ID: smokeTarget.userPoolId,
|
|
942
|
+
EBK_STAGE: options.stage,
|
|
943
|
+
},
|
|
944
|
+
expectedRoleArn,
|
|
945
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
946
|
+
project,
|
|
947
|
+
region,
|
|
948
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
949
|
+
...(dependencies.spawnChild
|
|
950
|
+
? { spawnChild: dependencies.spawnChild }
|
|
951
|
+
: {}),
|
|
952
|
+
stage: options.stage,
|
|
953
|
+
target: 'smoke-deployed',
|
|
954
|
+
});
|
|
955
|
+
children.push({
|
|
956
|
+
completedAt: now().toISOString(),
|
|
957
|
+
outcome: 'succeeded',
|
|
958
|
+
project,
|
|
959
|
+
startedAt: smokeStartedAt,
|
|
960
|
+
target: 'smoke-deployed',
|
|
961
|
+
});
|
|
962
|
+
smoke[project] = 'passed';
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
children.push({
|
|
966
|
+
completedAt: now().toISOString(),
|
|
967
|
+
outcome: 'failed',
|
|
968
|
+
project,
|
|
969
|
+
startedAt: smokeStartedAt,
|
|
970
|
+
target: 'smoke-deployed',
|
|
971
|
+
});
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
859
974
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
975
|
+
else {
|
|
976
|
+
smoke[project] = 'not-applicable';
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (options.stage === 'production') {
|
|
980
|
+
if (!productionDatabaseAfterFoundation) {
|
|
981
|
+
throw new Error('Production Foundation database postflight was not completed before final deployment acceptance.');
|
|
982
|
+
}
|
|
983
|
+
if (transport)
|
|
984
|
+
await transport.refresh();
|
|
985
|
+
const boundaryEnvironment = transport
|
|
986
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
987
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
988
|
+
preserveVerifiedGithubCredentials: true,
|
|
989
|
+
});
|
|
990
|
+
const finalPostflightStartedAt = now().toISOString();
|
|
991
|
+
const foundationProject = orderedProjects[0] ?? 'foundation';
|
|
992
|
+
const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
|
|
993
|
+
try {
|
|
994
|
+
await (dependencies.attestProductionDatabase ??
|
|
995
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
996
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
997
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
998
|
+
: {}),
|
|
999
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
1000
|
+
? {
|
|
1001
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
1002
|
+
}
|
|
1003
|
+
: {}),
|
|
1004
|
+
expectedAccountId: account,
|
|
1005
|
+
expectedCapacity: {
|
|
1006
|
+
max: Number.parseFloat(profile.aurora.max),
|
|
1007
|
+
min: Number.parseFloat(profile.aurora.min),
|
|
1008
|
+
},
|
|
1009
|
+
expectedRegion: region,
|
|
1010
|
+
expectedRoleArn,
|
|
1011
|
+
now: now(),
|
|
1012
|
+
priorDiscovery: productionDatabaseAfterFoundation,
|
|
1013
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
1014
|
+
requireExisting: true,
|
|
1015
|
+
});
|
|
1016
|
+
children.push({
|
|
1017
|
+
completedAt: now().toISOString(),
|
|
1018
|
+
outcome: 'succeeded',
|
|
1019
|
+
project: foundationProject,
|
|
1020
|
+
startedAt: finalPostflightStartedAt,
|
|
1021
|
+
target: 'production-database-final-postflight',
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
catch (error) {
|
|
1025
|
+
children.push({
|
|
1026
|
+
completedAt: now().toISOString(),
|
|
1027
|
+
outcome: 'failed',
|
|
1028
|
+
project: foundationProject,
|
|
1029
|
+
startedAt: finalPostflightStartedAt,
|
|
1030
|
+
target: 'production-database-final-postflight',
|
|
1031
|
+
});
|
|
1032
|
+
throw error;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
1036
|
+
if (transport) {
|
|
1037
|
+
transport.cleanup();
|
|
1038
|
+
transport = undefined;
|
|
866
1039
|
}
|
|
867
|
-
|
|
1040
|
+
outcome = 'succeeded';
|
|
1041
|
+
retain();
|
|
1042
|
+
devkit_1.logger.info(`${githubAuthority ? 'Protected GitHub' : 'Local'} ${options.stage} deployment completed through native SST for ${children.filter(({ target }) => target === 'deploy').length} project(s).`);
|
|
868
1043
|
return { success: true };
|
|
869
1044
|
}
|
|
870
|
-
finally {
|
|
871
|
-
retainEvidence(context.root, {
|
|
872
|
-
account: expectedAccount,
|
|
873
|
-
action,
|
|
874
|
-
actorArnDigest: digest(arn),
|
|
875
|
-
backendAuthorityDigest: backend?.authorityDigest ?? null,
|
|
876
|
-
backendCreatedApplications: backend
|
|
877
|
-
? [...backend.createdApplications]
|
|
878
|
-
: [],
|
|
879
|
-
completedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
|
|
880
|
-
deployNoOpProjects,
|
|
881
|
-
kind: 'ebk-local-deployment-evidence',
|
|
882
|
-
mutatedProjects,
|
|
883
|
-
orderedProjects,
|
|
884
|
-
operatorAcknowledgementDigest,
|
|
885
|
-
planDigests,
|
|
886
|
-
planSummaryDigest,
|
|
887
|
-
phase,
|
|
888
|
-
profile: policy.deploymentProfile,
|
|
889
|
-
replanRequiredProject,
|
|
890
|
-
refreshNoOpProjects,
|
|
891
|
-
region,
|
|
892
|
-
result,
|
|
893
|
-
schemaVersion: 1,
|
|
894
|
-
sourceCommit,
|
|
895
|
-
sourceDigest: digest(`${sourceCommit}:${sourceTree}`),
|
|
896
|
-
stage: options.stage,
|
|
897
|
-
startedAt,
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const executor = async (options, context) => {
|
|
902
|
-
try {
|
|
903
|
-
return await runLocalDeploy(options, context);
|
|
904
|
-
}
|
|
905
1045
|
catch (error) {
|
|
906
1046
|
devkit_1.logger.error(error instanceof Error ? error.message : String(error));
|
|
1047
|
+
if (transport) {
|
|
1048
|
+
try {
|
|
1049
|
+
transport.cleanup();
|
|
1050
|
+
transport = undefined;
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
devkit_1.logger.error('Local federated credential transport cleanup failed; deployment acceptance is blocked.');
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
retain();
|
|
907
1057
|
return { success: false };
|
|
908
1058
|
}
|
|
909
|
-
}
|
|
1059
|
+
}
|
|
1060
|
+
const executor = (options, context) => runLocalDeploy(options, context);
|
|
910
1061
|
exports.default = executor;
|
|
911
1062
|
//# sourceMappingURL=local-deploy.js.map
|