@empire-builder-kit/nx 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +25 -0
- package/executors.json +54 -0
- package/generators.json +35 -0
- package/migrations/standard-pre-v1-to-v1.md +11 -0
- package/package.json +66 -0
- package/src/blueprints/execution.d.ts +31 -0
- package/src/blueprints/execution.js +166 -0
- package/src/blueprints/execution.js.map +1 -0
- package/src/blueprints/index.d.ts +5 -0
- package/src/blueprints/index.js +9 -0
- package/src/blueprints/index.js.map +1 -0
- package/src/blueprints/inputs.d.ts +43 -0
- package/src/blueprints/inputs.js +103 -0
- package/src/blueprints/inputs.js.map +1 -0
- package/src/blueprints/manifest.d.ts +241 -0
- package/src/blueprints/manifest.js +369 -0
- package/src/blueprints/manifest.js.map +1 -0
- package/src/blueprints/resolver.d.ts +28 -0
- package/src/blueprints/resolver.js +214 -0
- package/src/blueprints/resolver.js.map +1 -0
- package/src/blueprints/transitions.d.ts +20 -0
- package/src/blueprints/transitions.js +61 -0
- package/src/blueprints/transitions.js.map +1 -0
- package/src/executors/deploy/deploy.d.ts +4 -0
- package/src/executors/deploy/deploy.js +6 -0
- package/src/executors/deploy/deploy.js.map +1 -0
- package/src/executors/dev/dev.d.ts +4 -0
- package/src/executors/dev/dev.js +6 -0
- package/src/executors/dev/dev.js.map +1 -0
- package/src/executors/dev-doctor/dev-doctor.d.ts +18 -0
- package/src/executors/dev-doctor/dev-doctor.js +145 -0
- package/src/executors/dev-doctor/dev-doctor.js.map +1 -0
- package/src/executors/dev-doctor/schema.json +17 -0
- package/src/executors/dev-setup/dev-setup.d.ts +11 -0
- package/src/executors/dev-setup/dev-setup.js +454 -0
- package/src/executors/dev-setup/dev-setup.js.map +1 -0
- package/src/executors/dev-setup/schema.json +16 -0
- package/src/executors/fleet/fleet.d.ts +51 -0
- package/src/executors/fleet/fleet.js +529 -0
- package/src/executors/fleet/fleet.js.map +1 -0
- package/src/executors/fleet/schema.json +25 -0
- package/src/executors/foundation-preflight/foundation-preflight.d.ts +15 -0
- package/src/executors/foundation-preflight/foundation-preflight.js +94 -0
- package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -0
- package/src/executors/foundation-preflight/schema.json +31 -0
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +13 -0
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +69 -0
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -0
- package/src/executors/foundation-remove-preflight/schema.json +17 -0
- package/src/executors/refresh/refresh.d.ts +4 -0
- package/src/executors/refresh/refresh.js +6 -0
- package/src/executors/refresh/refresh.js.map +1 -0
- package/src/executors/remove/remove.d.ts +4 -0
- package/src/executors/remove/remove.js +6 -0
- package/src/executors/remove/remove.js.map +1 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +28 -0
- package/src/executors/sst-lifecycle/run-sst-command.js +154 -0
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -0
- package/src/executors/sst-lifecycle/schema.json +45 -0
- package/src/executors/unlock/unlock.d.ts +4 -0
- package/src/executors/unlock/unlock.js +6 -0
- package/src/executors/unlock/unlock.js.map +1 -0
- package/src/foundation/aurora-logical-database.d.ts +76 -0
- package/src/foundation/aurora-logical-database.js +419 -0
- package/src/foundation/aurora-logical-database.js.map +1 -0
- package/src/foundation/discovery.d.ts +55 -0
- package/src/foundation/discovery.js +55 -0
- package/src/foundation/discovery.js.map +1 -0
- package/src/foundation/index.d.ts +6 -0
- package/src/foundation/index.js +10 -0
- package/src/foundation/index.js.map +1 -0
- package/src/foundation/local-database-contract.d.ts +30 -0
- package/src/foundation/local-database-contract.js +75 -0
- package/src/foundation/local-database-contract.js.map +1 -0
- package/src/foundation/preflight.d.ts +31 -0
- package/src/foundation/preflight.js +114 -0
- package/src/foundation/preflight.js.map +1 -0
- package/src/foundation/provision-database-bindings.d.ts +30 -0
- package/src/foundation/provision-database-bindings.js +113 -0
- package/src/foundation/provision-database-bindings.js.map +1 -0
- package/src/foundation/provision-logical-database.d.ts +26 -0
- package/src/foundation/provision-logical-database.js +162 -0
- package/src/foundation/provision-logical-database.js.map +1 -0
- package/src/foundation/requirements.d.ts +37 -0
- package/src/foundation/requirements.js +141 -0
- package/src/foundation/requirements.js.map +1 -0
- package/src/foundation/stages.d.ts +14 -0
- package/src/foundation/stages.js +51 -0
- package/src/foundation/stages.js.map +1 -0
- package/src/generators/function/function.d.ts +13 -0
- package/src/generators/function/function.js +71 -0
- package/src/generators/function/function.js.map +1 -0
- package/src/generators/function/schema.d.ts +5 -0
- package/src/generators/function/schema.json +20 -0
- package/src/generators/job/job.d.ts +4 -0
- package/src/generators/job/job.js +5 -0
- package/src/generators/job/job.js.map +1 -0
- package/src/generators/preset/generator.d.ts +5 -0
- package/src/generators/preset/generator.js +41 -0
- package/src/generators/preset/generator.js.map +1 -0
- package/src/generators/preset/schema.d.ts +4 -0
- package/src/generators/preset/schema.json +22 -0
- package/src/generators/preset/workspace-files.d.ts +18 -0
- package/src/generators/preset/workspace-files.js +895 -0
- package/src/generators/preset/workspace-files.js.map +1 -0
- package/src/generators/service/service.d.ts +4 -0
- package/src/generators/service/service.js +5 -0
- package/src/generators/service/service.js.map +1 -0
- package/src/generators/slice/schema.d.ts +7 -0
- package/src/generators/slice/schema.json +38 -0
- package/src/generators/slice/slice.d.ts +26 -0
- package/src/generators/slice/slice.js +1676 -0
- package/src/generators/slice/slice.js.map +1 -0
- package/src/generators/upgrade/schema.d.ts +8 -0
- package/src/generators/upgrade/schema.json +31 -0
- package/src/generators/upgrade/upgrade.d.ts +27 -0
- package/src/generators/upgrade/upgrade.js +666 -0
- package/src/generators/upgrade/upgrade.js.map +1 -0
- package/src/generators/workload/schema.d.ts +6 -0
- package/src/generators/workload/schema.json +21 -0
- package/src/generators/workload/workload.d.ts +16 -0
- package/src/generators/workload/workload.js +157 -0
- package/src/generators/workload/workload.js.map +1 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +9 -0
- package/src/index.js.map +1 -0
- package/src/local-dev/contracts.d.ts +31 -0
- package/src/local-dev/contracts.js +126 -0
- package/src/local-dev/contracts.js.map +1 -0
- package/src/local-dev/gateway-daemon.d.ts +4 -0
- package/src/local-dev/gateway-daemon.js +39 -0
- package/src/local-dev/gateway-daemon.js.map +1 -0
- package/src/local-dev/gateway.d.ts +60 -0
- package/src/local-dev/gateway.js +368 -0
- package/src/local-dev/gateway.js.map +1 -0
- package/src/local-dev/identity.d.ts +16 -0
- package/src/local-dev/identity.js +101 -0
- package/src/local-dev/identity.js.map +1 -0
- package/src/local-dev/index.d.ts +7 -0
- package/src/local-dev/index.js +11 -0
- package/src/local-dev/index.js.map +1 -0
- package/src/local-dev/portless.d.ts +15 -0
- package/src/local-dev/portless.js +115 -0
- package/src/local-dev/portless.js.map +1 -0
- package/src/local-dev/ports.d.ts +10 -0
- package/src/local-dev/ports.js +39 -0
- package/src/local-dev/ports.js.map +1 -0
- package/src/local-dev/proxy.d.ts +37 -0
- package/src/local-dev/proxy.js +271 -0
- package/src/local-dev/proxy.js.map +1 -0
- package/src/local-dev/tls.d.ts +24 -0
- package/src/local-dev/tls.js +294 -0
- package/src/local-dev/tls.js.map +1 -0
- package/src/ownership/composition.d.ts +16 -0
- package/src/ownership/composition.js +125 -0
- package/src/ownership/composition.js.map +1 -0
- package/src/ownership/foundation-record.d.ts +66 -0
- package/src/ownership/foundation-record.js +108 -0
- package/src/ownership/foundation-record.js.map +1 -0
- package/src/ownership/hash.d.ts +2 -0
- package/src/ownership/hash.js +10 -0
- package/src/ownership/hash.js.map +1 -0
- package/src/ownership/index.d.ts +6 -0
- package/src/ownership/index.js +10 -0
- package/src/ownership/index.js.map +1 -0
- package/src/ownership/json-ownership.d.ts +13 -0
- package/src/ownership/json-ownership.js +147 -0
- package/src/ownership/json-ownership.js.map +1 -0
- package/src/ownership/record.d.ts +186 -0
- package/src/ownership/record.js +336 -0
- package/src/ownership/record.js.map +1 -0
- package/src/ownership/workspace-record.d.ts +66 -0
- package/src/ownership/workspace-record.js +108 -0
- package/src/ownership/workspace-record.js.map +1 -0
- package/src/schemas/blueprint-input-schema-v1.json +23 -0
- package/src/schemas/blueprint-v1.json +170 -0
- package/src/schemas/foundation-ownership-v2.json +42 -0
- package/src/schemas/foundation-requirement-v1.json +32 -0
- package/src/schemas/generated-ownership-v2.json +117 -0
- package/src/schemas/workspace-ownership-v2.json +42 -0
- package/src/validation.d.ts +14 -0
- package/src/validation.js +14 -0
- package/src/validation.js.map +1 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WORKSPACE_PACKAGE_MANAGER_VERSION = exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = void 0;
|
|
4
|
+
exports.renderWorkspaceFiles = renderWorkspaceFiles;
|
|
5
|
+
exports.renderWorkspaceRootFilePlan = renderWorkspaceRootFilePlan;
|
|
6
|
+
exports.renderFoundationFilePlan = renderFoundationFilePlan;
|
|
7
|
+
const index_js_1 = require("../../ownership/index.js");
|
|
8
|
+
const TOOL_VERSIONS = {
|
|
9
|
+
eslint: '^9.8.0',
|
|
10
|
+
eslintJs: '^9.8.0',
|
|
11
|
+
globals: '^16.0.0',
|
|
12
|
+
nodeTypes: '25.5.0',
|
|
13
|
+
nx: '23.1.0',
|
|
14
|
+
pnpm: '10.30.1',
|
|
15
|
+
prettier: '^3.8.2',
|
|
16
|
+
sst: '4.11.0',
|
|
17
|
+
typescript: '~5.9.2',
|
|
18
|
+
typescriptEslint: '^8.40.0',
|
|
19
|
+
};
|
|
20
|
+
function json(value) {
|
|
21
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
22
|
+
}
|
|
23
|
+
const FOUNDATION_ROOT = 'packages/foundation/';
|
|
24
|
+
const FOUNDATION_OWNERSHIP_PATH = `${FOUNDATION_ROOT}.ebk/ownership.json`;
|
|
25
|
+
const FOUNDATION_TOOL_OWNED_PATHS = new Set(['sst-env.d.ts']);
|
|
26
|
+
const WORKSPACE_OWNERSHIP_PATH = '.ebk/ownership.json';
|
|
27
|
+
const WORKSPACE_SEEDED_PATHS = new Set([
|
|
28
|
+
'README.md',
|
|
29
|
+
'docs/architecture.md',
|
|
30
|
+
'package.json',
|
|
31
|
+
]);
|
|
32
|
+
exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
33
|
+
'/name',
|
|
34
|
+
'/$schema',
|
|
35
|
+
'/projectType',
|
|
36
|
+
'/tags',
|
|
37
|
+
'/targets/dev',
|
|
38
|
+
'/targets/dev-doctor',
|
|
39
|
+
'/targets/dev-setup',
|
|
40
|
+
'/targets/verify',
|
|
41
|
+
'/targets/workspace-health',
|
|
42
|
+
];
|
|
43
|
+
const FOUNDATION_SEEDED_PATHS = new Set([
|
|
44
|
+
'README.md',
|
|
45
|
+
'docs/costs.md',
|
|
46
|
+
'docs/runbooks.md',
|
|
47
|
+
'docs/security.md',
|
|
48
|
+
]);
|
|
49
|
+
function createFoundationOwnership(files, frameworkVersion) {
|
|
50
|
+
const paths = Object.keys(files)
|
|
51
|
+
.filter((path) => path.startsWith(FOUNDATION_ROOT) &&
|
|
52
|
+
path !== FOUNDATION_OWNERSHIP_PATH &&
|
|
53
|
+
!FOUNDATION_TOOL_OWNED_PATHS.has(path.slice(FOUNDATION_ROOT.length)))
|
|
54
|
+
.map((path) => {
|
|
55
|
+
const relativePath = path.slice(FOUNDATION_ROOT.length);
|
|
56
|
+
return {
|
|
57
|
+
mode: FOUNDATION_SEEDED_PATHS.has(relativePath)
|
|
58
|
+
? 'seeded'
|
|
59
|
+
: 'managed',
|
|
60
|
+
origin: 'framework',
|
|
61
|
+
path: relativePath,
|
|
62
|
+
sha256: (0, index_js_1.hashGeneratedContent)(files[path] ?? ''),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
paths.sort((left, right) => left.path.localeCompare(right.path));
|
|
66
|
+
return {
|
|
67
|
+
frameworkVersion,
|
|
68
|
+
kind: 'foundation',
|
|
69
|
+
paths,
|
|
70
|
+
schemaVersion: index_js_1.FOUNDATION_OWNERSHIP_SCHEMA_VERSION,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function createWorkspaceOwnership(files, frameworkVersion) {
|
|
74
|
+
const paths = Object.keys(files)
|
|
75
|
+
.filter((path) => !path.startsWith(FOUNDATION_ROOT) && path !== WORKSPACE_OWNERSHIP_PATH)
|
|
76
|
+
.map((path) => {
|
|
77
|
+
const ownership = {
|
|
78
|
+
...(path === 'project.json'
|
|
79
|
+
? {
|
|
80
|
+
managedJsonPointers: [...exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS],
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
mode: WORKSPACE_SEEDED_PATHS.has(path)
|
|
84
|
+
? 'seeded'
|
|
85
|
+
: 'managed',
|
|
86
|
+
origin: 'framework',
|
|
87
|
+
path,
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
...ownership,
|
|
91
|
+
sha256: (0, index_js_1.hashOwnedContent)(files[path] ?? '', ownership),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
paths.sort((left, right) => left.path.localeCompare(right.path));
|
|
95
|
+
return {
|
|
96
|
+
frameworkVersion,
|
|
97
|
+
kind: 'workspace',
|
|
98
|
+
paths,
|
|
99
|
+
schemaVersion: index_js_1.WORKSPACE_OWNERSHIP_SCHEMA_VERSION,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function renderWorkspaceFiles(options) {
|
|
103
|
+
const { frameworkVersion, name } = options;
|
|
104
|
+
const files = {
|
|
105
|
+
'.github/workflows/ci.yml': `name: Verify
|
|
106
|
+
|
|
107
|
+
on:
|
|
108
|
+
pull_request:
|
|
109
|
+
push:
|
|
110
|
+
branches: [main]
|
|
111
|
+
|
|
112
|
+
concurrency:
|
|
113
|
+
group: verify-\${{ github.ref }}
|
|
114
|
+
cancel-in-progress: true
|
|
115
|
+
|
|
116
|
+
permissions:
|
|
117
|
+
contents: read
|
|
118
|
+
|
|
119
|
+
jobs:
|
|
120
|
+
verify:
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/checkout@v6
|
|
124
|
+
- uses: pnpm/action-setup@v5
|
|
125
|
+
- uses: actions/setup-node@v6
|
|
126
|
+
with:
|
|
127
|
+
node-version: 22
|
|
128
|
+
cache: pnpm
|
|
129
|
+
- run: pnpm install --frozen-lockfile
|
|
130
|
+
- run: pnpm nx format:check
|
|
131
|
+
- run: pnpm nx run-many -t verify --parallel=3
|
|
132
|
+
`,
|
|
133
|
+
'.github/workflows/deploy.yml': `name: Deploy
|
|
134
|
+
|
|
135
|
+
on:
|
|
136
|
+
workflow_dispatch:
|
|
137
|
+
inputs:
|
|
138
|
+
stage:
|
|
139
|
+
description: Deployment stage
|
|
140
|
+
required: true
|
|
141
|
+
default: development
|
|
142
|
+
type: choice
|
|
143
|
+
options: [development, staging, production]
|
|
144
|
+
action:
|
|
145
|
+
description: Lifecycle action
|
|
146
|
+
required: true
|
|
147
|
+
default: deploy
|
|
148
|
+
type: choice
|
|
149
|
+
options: [deploy, refresh, unlock, remove]
|
|
150
|
+
|
|
151
|
+
concurrency:
|
|
152
|
+
group: deploy-\${{ inputs.stage }}
|
|
153
|
+
cancel-in-progress: false
|
|
154
|
+
|
|
155
|
+
permissions:
|
|
156
|
+
contents: read
|
|
157
|
+
id-token: write
|
|
158
|
+
|
|
159
|
+
jobs:
|
|
160
|
+
deploy:
|
|
161
|
+
if: github.ref == 'refs/heads/main'
|
|
162
|
+
environment: \${{ inputs.stage }}
|
|
163
|
+
env:
|
|
164
|
+
EBK_ALLOW_PRODUCTION_REMOVE: \${{ vars.EBK_ALLOW_PRODUCTION_REMOVE || 'false' }}
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
steps:
|
|
167
|
+
- uses: actions/checkout@v6
|
|
168
|
+
- uses: aws-actions/configure-aws-credentials@v6
|
|
169
|
+
with:
|
|
170
|
+
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
171
|
+
aws-region: \${{ vars.AWS_REGION || 'us-east-1' }}
|
|
172
|
+
- uses: pnpm/action-setup@v5
|
|
173
|
+
- uses: actions/setup-node@v6
|
|
174
|
+
with:
|
|
175
|
+
node-version: 22
|
|
176
|
+
cache: pnpm
|
|
177
|
+
- run: pnpm install --frozen-lockfile
|
|
178
|
+
- run: pnpm nx run-many -t \${{ inputs.action }} --stage=\${{ inputs.stage }} --parallel=1
|
|
179
|
+
`,
|
|
180
|
+
'.gitignore': `.nx/cache
|
|
181
|
+
.nx/workspace-data
|
|
182
|
+
.ebk/cache
|
|
183
|
+
.next
|
|
184
|
+
.open-next
|
|
185
|
+
dist
|
|
186
|
+
*.tsbuildinfo
|
|
187
|
+
node_modules
|
|
188
|
+
.sst
|
|
189
|
+
.env
|
|
190
|
+
.env.*
|
|
191
|
+
!.env.example
|
|
192
|
+
`,
|
|
193
|
+
'.prettierignore': `node_modules
|
|
194
|
+
dist
|
|
195
|
+
.nx
|
|
196
|
+
.next
|
|
197
|
+
.open-next
|
|
198
|
+
.sst
|
|
199
|
+
**/next-env.d.ts
|
|
200
|
+
pnpm-lock.yaml
|
|
201
|
+
`,
|
|
202
|
+
'.prettierrc.json': json({ singleQuote: true }),
|
|
203
|
+
'eslint.config.mjs': `import eslint from '@eslint/js';
|
|
204
|
+
import globals from 'globals';
|
|
205
|
+
import tseslint from 'typescript-eslint';
|
|
206
|
+
|
|
207
|
+
export default tseslint.config(
|
|
208
|
+
{ ignores: ['**/.next/**', '**/.open-next/**', '**/.sst/**', '**/dist/**', '**/node_modules/**'] },
|
|
209
|
+
eslint.configs.recommended,
|
|
210
|
+
...tseslint.configs.recommended,
|
|
211
|
+
{
|
|
212
|
+
languageOptions: { globals: globals.node },
|
|
213
|
+
rules: { '@typescript-eslint/no-explicit-any': 'off' },
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
`,
|
|
217
|
+
'README.md': `# ${name}
|
|
218
|
+
|
|
219
|
+
This workspace was created with Empire Builder Kit v1.
|
|
220
|
+
|
|
221
|
+
## Start here
|
|
222
|
+
|
|
223
|
+
1. Review \`docs/architecture.md\`.
|
|
224
|
+
2. Review the generated Foundation before deploying it.
|
|
225
|
+
3. Add product slices through the Empire Builder Kit Nx plugin.
|
|
226
|
+
|
|
227
|
+
For one-origin fleet development, first run \`pnpm nx run ${name}:dev-setup\`, inspect the
|
|
228
|
+
generated root certificate, then explicitly approve \`--trust --portless\`. Diagnose without
|
|
229
|
+
mutation through \`pnpm nx run ${name}:dev-doctor --stage=<personal-stage>\`. Start selected
|
|
230
|
+
slices with \`pnpm nx run ${name}:dev --slices=billing,accounts --stage=<personal-stage>\`.
|
|
231
|
+
|
|
232
|
+
Workspace creation only writes source files. It does not create or change cloud resources.
|
|
233
|
+
`,
|
|
234
|
+
'docs/architecture.md': `# Workspace architecture
|
|
235
|
+
|
|
236
|
+
This workspace uses three public Empire Builder Kit packages:
|
|
237
|
+
|
|
238
|
+
- \`@empire-builder-kit/create\` creates the workspace source tree.
|
|
239
|
+
- \`@empire-builder-kit/nx\` owns generators and infrastructure lifecycle executors.
|
|
240
|
+
- \`@empire-builder-kit/runtime\` owns application-facing runtime contracts.
|
|
241
|
+
|
|
242
|
+
The Standard Foundation lives at \`packages/foundation\`. Product slices live beside it under
|
|
243
|
+
\`packages/<slice-name>\` and consume versioned Foundation contracts instead of importing
|
|
244
|
+
Foundation implementation files.
|
|
245
|
+
|
|
246
|
+
Canonical consumer stages use the matching \`development\`, \`staging\`, or \`production\`
|
|
247
|
+
Foundation. Personal and preview consumer stages may use only the \`development\` Foundation.
|
|
248
|
+
`,
|
|
249
|
+
'nx.json': json({
|
|
250
|
+
$schema: './node_modules/nx/schemas/nx-schema.json',
|
|
251
|
+
defaultBase: 'main',
|
|
252
|
+
namedInputs: {
|
|
253
|
+
default: ['{projectRoot}/**/*', 'sharedGlobals'],
|
|
254
|
+
production: [
|
|
255
|
+
'default',
|
|
256
|
+
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
|
257
|
+
'!{projectRoot}/tsconfig.spec.json',
|
|
258
|
+
],
|
|
259
|
+
sharedGlobals: [],
|
|
260
|
+
},
|
|
261
|
+
targetDefaults: {
|
|
262
|
+
build: {
|
|
263
|
+
cache: true,
|
|
264
|
+
dependsOn: ['^build'],
|
|
265
|
+
inputs: ['production', '^production'],
|
|
266
|
+
},
|
|
267
|
+
lint: {
|
|
268
|
+
cache: true,
|
|
269
|
+
inputs: ['default', '^production'],
|
|
270
|
+
},
|
|
271
|
+
test: {
|
|
272
|
+
cache: true,
|
|
273
|
+
dependsOn: ['^build'],
|
|
274
|
+
inputs: ['default', '^production'],
|
|
275
|
+
},
|
|
276
|
+
typecheck: {
|
|
277
|
+
cache: true,
|
|
278
|
+
dependsOn: ['^build'],
|
|
279
|
+
inputs: ['default', '^production'],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
neverConnectToCloud: true,
|
|
283
|
+
}),
|
|
284
|
+
'package.json': json({
|
|
285
|
+
name,
|
|
286
|
+
version: '0.0.0',
|
|
287
|
+
private: true,
|
|
288
|
+
packageManager: `pnpm@${TOOL_VERSIONS.pnpm}`,
|
|
289
|
+
engines: {
|
|
290
|
+
node: '>=22.12.0',
|
|
291
|
+
pnpm: '>=10.0.0',
|
|
292
|
+
},
|
|
293
|
+
scripts: {
|
|
294
|
+
build: `nx run-many -t build --exclude=${name}`,
|
|
295
|
+
format: 'nx format:write',
|
|
296
|
+
'format:check': 'nx format:check',
|
|
297
|
+
graph: 'nx graph',
|
|
298
|
+
health: `nx run ${name}:workspace-health`,
|
|
299
|
+
lint: `nx run-many -t lint --exclude=${name}`,
|
|
300
|
+
test: `nx run-many -t test --exclude=${name}`,
|
|
301
|
+
typecheck: `nx run-many -t typecheck --exclude=${name}`,
|
|
302
|
+
verify: `nx run ${name}:workspace-health && nx run-many -t verify --exclude=${name}`,
|
|
303
|
+
dev: `nx run ${name}:dev`,
|
|
304
|
+
},
|
|
305
|
+
dependencies: {
|
|
306
|
+
'@empire-builder-kit/runtime': frameworkVersion,
|
|
307
|
+
},
|
|
308
|
+
devDependencies: {
|
|
309
|
+
'@eslint/js': TOOL_VERSIONS.eslintJs,
|
|
310
|
+
'@empire-builder-kit/nx': frameworkVersion,
|
|
311
|
+
'@types/node': TOOL_VERSIONS.nodeTypes,
|
|
312
|
+
eslint: TOOL_VERSIONS.eslint,
|
|
313
|
+
globals: TOOL_VERSIONS.globals,
|
|
314
|
+
nx: TOOL_VERSIONS.nx,
|
|
315
|
+
prettier: TOOL_VERSIONS.prettier,
|
|
316
|
+
sst: TOOL_VERSIONS.sst,
|
|
317
|
+
typescript: TOOL_VERSIONS.typescript,
|
|
318
|
+
'typescript-eslint': TOOL_VERSIONS.typescriptEslint,
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
'project.json': json({
|
|
322
|
+
name,
|
|
323
|
+
$schema: './node_modules/nx/schemas/project-schema.json',
|
|
324
|
+
projectType: 'application',
|
|
325
|
+
tags: ['scope:workspace', 'layer:governance'],
|
|
326
|
+
targets: {
|
|
327
|
+
dev: {
|
|
328
|
+
continuous: true,
|
|
329
|
+
executor: '@empire-builder-kit/nx:fleet',
|
|
330
|
+
options: {
|
|
331
|
+
stage: 'personal',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
'dev-doctor': {
|
|
335
|
+
executor: '@empire-builder-kit/nx:dev-doctor',
|
|
336
|
+
options: {},
|
|
337
|
+
},
|
|
338
|
+
'dev-setup': {
|
|
339
|
+
executor: '@empire-builder-kit/nx:dev-setup',
|
|
340
|
+
options: {},
|
|
341
|
+
},
|
|
342
|
+
verify: {
|
|
343
|
+
executor: 'nx:run-commands',
|
|
344
|
+
options: { command: 'node tools/workspace-health.mjs' },
|
|
345
|
+
},
|
|
346
|
+
'workspace-health': {
|
|
347
|
+
executor: 'nx:run-commands',
|
|
348
|
+
options: { command: 'node tools/workspace-health.mjs' },
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
}),
|
|
352
|
+
'packages/foundation/README.md': `# Standard Foundation
|
|
353
|
+
|
|
354
|
+
This project owns the workspace-wide AWS platform for each canonical \`development\`,
|
|
355
|
+
\`staging\`, or \`production\` stage. It is a separate SST state boundary from product slices.
|
|
356
|
+
Never deploy it at a personal or preview stage; those consumers map to \`development\`.
|
|
357
|
+
|
|
358
|
+
It provides the v1 network, routing, container compute, Postgres, identity, events,
|
|
359
|
+
observability, configuration, and deployment capabilities. Product slices consume its
|
|
360
|
+
versioned contract; they do not import files from \`infra/\`.
|
|
361
|
+
|
|
362
|
+
Workspace creation does not deploy this project. Review \`docs/costs.md\`,
|
|
363
|
+
\`docs/security.md\`, and \`docs/runbooks.md\` before the first deployment.
|
|
364
|
+
|
|
365
|
+
Set \`EBK_ROOT_DOMAIN\` before deploying when Route 53 in the target account owns a public
|
|
366
|
+
domain. Without it, the shared Router uses its functional provider URL.
|
|
367
|
+
`,
|
|
368
|
+
'packages/foundation/contracts/foundation.json': json({
|
|
369
|
+
contractVersion: '1.0.0',
|
|
370
|
+
capabilities: [
|
|
371
|
+
'network',
|
|
372
|
+
'routing',
|
|
373
|
+
'container-compute',
|
|
374
|
+
'postgres',
|
|
375
|
+
'identity',
|
|
376
|
+
'events',
|
|
377
|
+
'observability',
|
|
378
|
+
'configuration',
|
|
379
|
+
'deployment',
|
|
380
|
+
],
|
|
381
|
+
}),
|
|
382
|
+
'packages/foundation/contracts/costs.json': json({
|
|
383
|
+
schemaVersion: 1,
|
|
384
|
+
stages: {
|
|
385
|
+
development: {
|
|
386
|
+
aurora: 'scale-to-zero',
|
|
387
|
+
dataRetention: 'retained',
|
|
388
|
+
nat: 'ec2',
|
|
389
|
+
},
|
|
390
|
+
staging: {
|
|
391
|
+
aurora: 'scale-to-zero',
|
|
392
|
+
dataRetention: 'retained',
|
|
393
|
+
nat: 'ec2',
|
|
394
|
+
},
|
|
395
|
+
production: {
|
|
396
|
+
aurora: 'always-warm',
|
|
397
|
+
dataRetention: 'retained-and-protected',
|
|
398
|
+
nat: 'managed',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
usagePriced: [
|
|
402
|
+
'cloudwatch',
|
|
403
|
+
'cognito',
|
|
404
|
+
'ecs-workloads',
|
|
405
|
+
'eventbridge',
|
|
406
|
+
'sns',
|
|
407
|
+
'ssm',
|
|
408
|
+
],
|
|
409
|
+
}),
|
|
410
|
+
'packages/foundation/docs/costs.md': `# Foundation cost contract
|
|
411
|
+
|
|
412
|
+
The VPC NAT path and Aurora capacity are the main fixed-cost decisions.
|
|
413
|
+
The machine-readable stage inventory is \`../contracts/costs.json\`; keep this
|
|
414
|
+
seeded decision record aligned with organization-specific budgets and alerts.
|
|
415
|
+
|
|
416
|
+
- Development uses EC2 NAT, a development bastion, and Aurora scale-to-zero.
|
|
417
|
+
- Staging uses EC2 NAT and Aurora scale-to-zero by default.
|
|
418
|
+
- Production uses managed NAT and an always-warm Aurora baseline.
|
|
419
|
+
- ECS, EventBridge, Cognito, SNS, SSM, and CloudWatch are primarily usage-priced; logs,
|
|
420
|
+
metrics, alarms, and retained data still require active cost review.
|
|
421
|
+
|
|
422
|
+
Run \`foundation:remove\` only for disposable stages. Production is retained and protected by
|
|
423
|
+
the SST configuration.
|
|
424
|
+
`,
|
|
425
|
+
'packages/foundation/docs/runbooks.md': `# Foundation runbooks
|
|
426
|
+
|
|
427
|
+
## Deployment
|
|
428
|
+
|
|
429
|
+
Use Nx for every supported lifecycle action:
|
|
430
|
+
|
|
431
|
+
- \`pnpm nx run foundation:deploy --stage=development\`
|
|
432
|
+
- \`pnpm nx run foundation:refresh --stage=development\`
|
|
433
|
+
- \`pnpm nx run foundation:unlock --stage=development\`
|
|
434
|
+
- \`pnpm nx run foundation:remove --stage=development\`
|
|
435
|
+
|
|
436
|
+
Do not invoke SST lifecycle commands directly. Production deployment belongs in the trusted
|
|
437
|
+
CI path.
|
|
438
|
+
|
|
439
|
+
Foundation stages are canonical. Run a Product Slice with \`--stage=<personal-name>\` to map
|
|
440
|
+
that consumer to the development Foundation. The slice preflight reports both stages; it
|
|
441
|
+
does not deploy a personal Foundation. Full mapped personal-stage deploys fail closed.
|
|
442
|
+
|
|
443
|
+
## Database restore
|
|
444
|
+
|
|
445
|
+
Production Aurora retains 30 days of automated backups; non-production retains 7 days.
|
|
446
|
+
Restore into a new cluster, validate application migrations and data integrity, then cut over
|
|
447
|
+
consumers deliberately. Never expose the Foundation master credential to a product slice.
|
|
448
|
+
`,
|
|
449
|
+
'packages/foundation/docs/security.md': `# Foundation security contract
|
|
450
|
+
|
|
451
|
+
- Production resources are retained and protected.
|
|
452
|
+
- The shared Aurora master credential is Foundation-internal.
|
|
453
|
+
- Product slices receive scoped logical-database credentials through the provisioning contract.
|
|
454
|
+
- Cognito owns authentication; product slices own authorization policy and app clients.
|
|
455
|
+
- Lambda and server-side web workloads use resource linking. Containers use the documented
|
|
456
|
+
stage-scoped SSM/secret path and least-privilege IAM.
|
|
457
|
+
- Public domains are configured explicitly through \`EBK_ROOT_DOMAIN\`; generated source never
|
|
458
|
+
contains credentials or customer domain assumptions.
|
|
459
|
+
`,
|
|
460
|
+
'packages/foundation/ebk-globals.d.ts': `// SST and Pulumi inject these globals while evaluating sst.config.ts.
|
|
461
|
+
declare const sst: any;
|
|
462
|
+
declare const aws: any;
|
|
463
|
+
declare const $config: any;
|
|
464
|
+
declare const $app: {
|
|
465
|
+
name: string;
|
|
466
|
+
stage: string;
|
|
467
|
+
removal: 'remove' | 'retain' | 'retain-all';
|
|
468
|
+
protect: boolean;
|
|
469
|
+
};
|
|
470
|
+
`,
|
|
471
|
+
'packages/foundation/infra/foundation.ts': `import {
|
|
472
|
+
FOUNDATION_SSM_SCHEMA_VERSION,
|
|
473
|
+
STANDARD_FOUNDATION_CAPABILITIES,
|
|
474
|
+
foundationSsmPath,
|
|
475
|
+
type FoundationSsmKey,
|
|
476
|
+
} from '@empire-builder-kit/nx/foundation';
|
|
477
|
+
|
|
478
|
+
function publishFoundation(values: Record<FoundationSsmKey, any>) {
|
|
479
|
+
return Object.fromEntries(
|
|
480
|
+
Object.entries(values).map(([key, value]) => [
|
|
481
|
+
key,
|
|
482
|
+
new aws.ssm.Parameter('FoundationSsm_' + key, {
|
|
483
|
+
name: foundationSsmPath($app.stage, key as FoundationSsmKey),
|
|
484
|
+
type: 'String',
|
|
485
|
+
value,
|
|
486
|
+
}),
|
|
487
|
+
]),
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function foundation() {
|
|
492
|
+
const isProduction = $app.stage === 'production';
|
|
493
|
+
const isStaging = $app.stage === 'staging';
|
|
494
|
+
const rootDomain = process.env.EBK_ROOT_DOMAIN?.trim();
|
|
495
|
+
|
|
496
|
+
const vpc = new sst.aws.Vpc('FoundationVpc', {
|
|
497
|
+
az: 2,
|
|
498
|
+
bastion: $app.stage === 'development',
|
|
499
|
+
nat: isProduction ? 'managed' : 'ec2',
|
|
500
|
+
});
|
|
501
|
+
const cluster = new sst.aws.Cluster('FoundationCluster', { vpc });
|
|
502
|
+
const router = new sst.aws.Router('FoundationRouter', {
|
|
503
|
+
...(rootDomain
|
|
504
|
+
? {
|
|
505
|
+
domain: {
|
|
506
|
+
name: rootDomain,
|
|
507
|
+
aliases: ['*.' + rootDomain],
|
|
508
|
+
dns: sst.aws.dns(),
|
|
509
|
+
},
|
|
510
|
+
}
|
|
511
|
+
: {}),
|
|
512
|
+
});
|
|
513
|
+
const database = new sst.aws.Aurora('FoundationDatabase', {
|
|
514
|
+
dataApi: true,
|
|
515
|
+
engine: 'postgres',
|
|
516
|
+
scaling: isProduction
|
|
517
|
+
? { min: '2 ACU', max: '16 ACU' }
|
|
518
|
+
: isStaging
|
|
519
|
+
? { min: '0 ACU', max: '4 ACU', pauseAfter: '5 minutes' }
|
|
520
|
+
: { min: '0 ACU', max: '2 ACU', pauseAfter: '5 minutes' },
|
|
521
|
+
transform: {
|
|
522
|
+
cluster: {
|
|
523
|
+
backupRetentionPeriod: isProduction ? 30 : 7,
|
|
524
|
+
deletionProtection: isProduction,
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
vpc,
|
|
528
|
+
});
|
|
529
|
+
const userPool = new sst.aws.CognitoUserPool('FoundationIdentity', {
|
|
530
|
+
usernames: ['email'],
|
|
531
|
+
});
|
|
532
|
+
const eventBus = new sst.aws.Bus('FoundationEventBus');
|
|
533
|
+
const alertTopic = new sst.aws.SnsTopic('FoundationAlerts');
|
|
534
|
+
const dashboard = new aws.cloudwatch.Dashboard('FoundationDashboard', {
|
|
535
|
+
dashboardName: 'ebk-' + $app.stage + '-foundation',
|
|
536
|
+
dashboardBody: JSON.stringify({
|
|
537
|
+
widgets: [
|
|
538
|
+
{
|
|
539
|
+
type: 'text',
|
|
540
|
+
x: 0,
|
|
541
|
+
y: 0,
|
|
542
|
+
width: 24,
|
|
543
|
+
height: 3,
|
|
544
|
+
properties: {
|
|
545
|
+
markdown: '# Empire Builder Kit Foundation\\nStage: ' + $app.stage,
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
}),
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (new Set(['development', 'staging', 'production']).has($app.stage)) {
|
|
553
|
+
new aws.cloudwatch.EventArchive('FoundationEventArchive', {
|
|
554
|
+
eventSourceArn: eventBus.arn,
|
|
555
|
+
retentionDays: 30,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
publishFoundation({
|
|
560
|
+
alertTopicArn: alertTopic.arn,
|
|
561
|
+
capabilities: STANDARD_FOUNDATION_CAPABILITIES.join(','),
|
|
562
|
+
clusterId: cluster.id,
|
|
563
|
+
contractVersion: '1.0.0',
|
|
564
|
+
databaseClusterArn: database.clusterArn,
|
|
565
|
+
databaseClusterId: database.id,
|
|
566
|
+
databaseMasterSecretArn: database.secretArn,
|
|
567
|
+
eventBusArn: eventBus.arn,
|
|
568
|
+
eventBusName: eventBus.name,
|
|
569
|
+
hasPublicDomain: rootDomain ? 'true' : 'false',
|
|
570
|
+
identityUserPoolArn: userPool.arn,
|
|
571
|
+
identityUserPoolId: userPool.id,
|
|
572
|
+
rootDomain: rootDomain ?? 'none',
|
|
573
|
+
routerDistributionId: router.distributionID,
|
|
574
|
+
routerUrl: router.url,
|
|
575
|
+
schemaVersion: String(FOUNDATION_SSM_SCHEMA_VERSION),
|
|
576
|
+
vpcId: vpc.id,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
alertTopicArn: alertTopic.arn,
|
|
581
|
+
capabilities: STANDARD_FOUNDATION_CAPABILITIES,
|
|
582
|
+
clusterId: cluster.id,
|
|
583
|
+
contractVersion: '1.0.0',
|
|
584
|
+
dashboardName: dashboard.dashboardName,
|
|
585
|
+
databaseClusterId: database.id,
|
|
586
|
+
eventBusName: eventBus.name,
|
|
587
|
+
identityUserPoolId: userPool.id,
|
|
588
|
+
name: 'foundation',
|
|
589
|
+
rootDomain,
|
|
590
|
+
routerUrl: router.url,
|
|
591
|
+
vpcId: vpc.id,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
`,
|
|
595
|
+
'packages/foundation/infra/index.ts': `export { foundation as app } from './foundation.js';
|
|
596
|
+
`,
|
|
597
|
+
'packages/foundation/package.json': json({
|
|
598
|
+
name: `@${name}/foundation`,
|
|
599
|
+
version: '0.0.0',
|
|
600
|
+
private: true,
|
|
601
|
+
}),
|
|
602
|
+
'packages/foundation/project.json': json({
|
|
603
|
+
name: 'foundation',
|
|
604
|
+
$schema: '../../node_modules/nx/schemas/project-schema.json',
|
|
605
|
+
projectType: 'application',
|
|
606
|
+
sourceRoot: 'packages/foundation/infra',
|
|
607
|
+
tags: [
|
|
608
|
+
'scope:foundation',
|
|
609
|
+
'type:sst',
|
|
610
|
+
'layer:platform',
|
|
611
|
+
'visibility:internal',
|
|
612
|
+
],
|
|
613
|
+
targets: {
|
|
614
|
+
deploy: {
|
|
615
|
+
executor: '@empire-builder-kit/nx:deploy',
|
|
616
|
+
dependsOn: ['verify'],
|
|
617
|
+
options: {
|
|
618
|
+
cwd: 'packages/foundation',
|
|
619
|
+
stage: 'development',
|
|
620
|
+
stageMode: 'foundation',
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
dev: {
|
|
624
|
+
executor: '@empire-builder-kit/nx:dev',
|
|
625
|
+
dependsOn: ['verify'],
|
|
626
|
+
options: {
|
|
627
|
+
cwd: 'packages/foundation',
|
|
628
|
+
stage: 'development',
|
|
629
|
+
stageMode: 'foundation',
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
lint: {
|
|
633
|
+
executor: 'nx:run-commands',
|
|
634
|
+
options: {
|
|
635
|
+
command: 'eslint .',
|
|
636
|
+
cwd: 'packages/foundation',
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
refresh: {
|
|
640
|
+
executor: '@empire-builder-kit/nx:refresh',
|
|
641
|
+
options: {
|
|
642
|
+
cwd: 'packages/foundation',
|
|
643
|
+
stage: 'development',
|
|
644
|
+
stageMode: 'foundation',
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
remove: {
|
|
648
|
+
executor: '@empire-builder-kit/nx:remove',
|
|
649
|
+
dependsOn: [{ target: 'remove-preflight', params: 'forward' }],
|
|
650
|
+
options: {
|
|
651
|
+
cwd: 'packages/foundation',
|
|
652
|
+
stage: 'development',
|
|
653
|
+
stageMode: 'foundation',
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
'remove-preflight': {
|
|
657
|
+
executor: '@empire-builder-kit/nx:foundation-remove-preflight',
|
|
658
|
+
options: { stage: 'development' },
|
|
659
|
+
},
|
|
660
|
+
typecheck: {
|
|
661
|
+
executor: 'nx:run-commands',
|
|
662
|
+
options: {
|
|
663
|
+
command: 'tsc --noEmit -p tsconfig.json',
|
|
664
|
+
cwd: 'packages/foundation',
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
unlock: {
|
|
668
|
+
executor: '@empire-builder-kit/nx:unlock',
|
|
669
|
+
options: {
|
|
670
|
+
cwd: 'packages/foundation',
|
|
671
|
+
stage: 'development',
|
|
672
|
+
stageMode: 'foundation',
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
verify: {
|
|
676
|
+
executor: 'nx:run-commands',
|
|
677
|
+
dependsOn: ['lint', 'typecheck'],
|
|
678
|
+
options: {
|
|
679
|
+
command: 'node scripts/verify-foundation.mjs',
|
|
680
|
+
cwd: 'packages/foundation',
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
}),
|
|
685
|
+
'packages/foundation/scripts/verify-foundation.mjs': `import { readFileSync } from 'node:fs';
|
|
686
|
+
|
|
687
|
+
const descriptor = JSON.parse(
|
|
688
|
+
readFileSync(new URL('../contracts/foundation.json', import.meta.url), 'utf8'),
|
|
689
|
+
);
|
|
690
|
+
const costs = JSON.parse(
|
|
691
|
+
readFileSync(new URL('../contracts/costs.json', import.meta.url), 'utf8'),
|
|
692
|
+
);
|
|
693
|
+
const expectedCapabilities = [
|
|
694
|
+
'network',
|
|
695
|
+
'routing',
|
|
696
|
+
'container-compute',
|
|
697
|
+
'postgres',
|
|
698
|
+
'identity',
|
|
699
|
+
'events',
|
|
700
|
+
'observability',
|
|
701
|
+
'configuration',
|
|
702
|
+
'deployment',
|
|
703
|
+
];
|
|
704
|
+
|
|
705
|
+
if (descriptor.contractVersion !== '1.0.0') {
|
|
706
|
+
throw new Error('Foundation contractVersion must be 1.0.0.');
|
|
707
|
+
}
|
|
708
|
+
if (JSON.stringify(descriptor.capabilities) !== JSON.stringify(expectedCapabilities)) {
|
|
709
|
+
throw new Error('Foundation capability descriptor does not match the v1 standard.');
|
|
710
|
+
}
|
|
711
|
+
if (
|
|
712
|
+
costs.schemaVersion !== 1 ||
|
|
713
|
+
!costs.stages?.development ||
|
|
714
|
+
!costs.stages?.staging ||
|
|
715
|
+
costs.stages?.production?.dataRetention !== 'retained-and-protected'
|
|
716
|
+
) {
|
|
717
|
+
throw new Error('Foundation cost manifest does not match the v1 stage contract.');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
process.stdout.write('Foundation source contract verified.\\n');
|
|
721
|
+
`,
|
|
722
|
+
'packages/foundation/sst-env.d.ts': `// Bootstrap placeholder. SST overwrites this file after install, dev, or deploy.
|
|
723
|
+
export {};
|
|
724
|
+
`,
|
|
725
|
+
'packages/foundation/sst.config.ts': `// SST/Pulumi ambient globals are declared in ebk-globals.d.ts.
|
|
726
|
+
export default $config({
|
|
727
|
+
app(input: { stage?: string } | undefined) {
|
|
728
|
+
const stage = input?.stage ?? 'development';
|
|
729
|
+
if (!['development', 'staging', 'production'].includes(stage)) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
'Foundation stage ' + stage + ' is not canonical. Use development, staging, or production.',
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const production = stage === 'production';
|
|
735
|
+
return {
|
|
736
|
+
name: 'foundation',
|
|
737
|
+
removal: production ? 'retain' : 'remove',
|
|
738
|
+
protect: production,
|
|
739
|
+
home: 'aws',
|
|
740
|
+
providers: {
|
|
741
|
+
aws: {
|
|
742
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
};
|
|
746
|
+
},
|
|
747
|
+
async run() {
|
|
748
|
+
const { app } = await import('./infra/index.js');
|
|
749
|
+
return app();
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
`,
|
|
753
|
+
'packages/foundation/tsconfig.json': json({
|
|
754
|
+
extends: '../../tsconfig.base.json',
|
|
755
|
+
compilerOptions: { types: ['node'] },
|
|
756
|
+
include: [
|
|
757
|
+
'infra/**/*.ts',
|
|
758
|
+
'sst.config.ts',
|
|
759
|
+
'sst-env.d.ts',
|
|
760
|
+
'ebk-globals.d.ts',
|
|
761
|
+
],
|
|
762
|
+
exclude: ['.sst', 'dist', 'node_modules'],
|
|
763
|
+
}),
|
|
764
|
+
'pnpm-workspace.yaml': `packages:
|
|
765
|
+
- 'packages/*'
|
|
766
|
+
- 'packages/*/apps/*'
|
|
767
|
+
- 'packages/*/jobs/*'
|
|
768
|
+
- 'packages/*/libs/*'
|
|
769
|
+
- 'packages/*/services/*'
|
|
770
|
+
`,
|
|
771
|
+
'tsconfig.base.json': json({
|
|
772
|
+
compilerOptions: {
|
|
773
|
+
composite: true,
|
|
774
|
+
declarationMap: true,
|
|
775
|
+
forceConsistentCasingInFileNames: true,
|
|
776
|
+
isolatedModules: true,
|
|
777
|
+
lib: ['es2022'],
|
|
778
|
+
module: 'nodenext',
|
|
779
|
+
moduleResolution: 'nodenext',
|
|
780
|
+
noEmitOnError: true,
|
|
781
|
+
noFallthroughCasesInSwitch: true,
|
|
782
|
+
noImplicitOverride: true,
|
|
783
|
+
noImplicitReturns: true,
|
|
784
|
+
noUnusedLocals: true,
|
|
785
|
+
skipLibCheck: true,
|
|
786
|
+
strict: true,
|
|
787
|
+
target: 'es2022',
|
|
788
|
+
},
|
|
789
|
+
}),
|
|
790
|
+
'tsconfig.json': json({
|
|
791
|
+
extends: './tsconfig.base.json',
|
|
792
|
+
files: [],
|
|
793
|
+
references: [],
|
|
794
|
+
}),
|
|
795
|
+
'tools/workspace-health.mjs': `import { createHash } from 'node:crypto';
|
|
796
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
797
|
+
import { join } from 'node:path';
|
|
798
|
+
|
|
799
|
+
const parseJson = (contents, label) => {
|
|
800
|
+
try {
|
|
801
|
+
return JSON.parse(contents);
|
|
802
|
+
} catch (error) {
|
|
803
|
+
throw new Error(label + ' is not valid JSON: ' + (error instanceof Error ? error.message : String(error)));
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
const readJson = (path) => parseJson(readFileSync(path, 'utf8'), path);
|
|
807
|
+
const sha256 = (contents) => createHash('sha256').update(contents, 'utf8').digest('hex');
|
|
808
|
+
const stableValue = (value) => {
|
|
809
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
810
|
+
if (!value || typeof value !== 'object') return value;
|
|
811
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
|
|
812
|
+
};
|
|
813
|
+
const pointerValue = (document, pointer) => {
|
|
814
|
+
let current = document;
|
|
815
|
+
for (const segment of pointer.slice(1).split('/').map((part) => part.replaceAll('~1', '/').replaceAll('~0', '~'))) {
|
|
816
|
+
if (!current || typeof current !== 'object' || !Object.hasOwn(current, segment)) {
|
|
817
|
+
return { exists: false };
|
|
818
|
+
}
|
|
819
|
+
current = current[segment];
|
|
820
|
+
}
|
|
821
|
+
return { exists: true, value: current };
|
|
822
|
+
};
|
|
823
|
+
const ownedSha256 = (contents, entry) => {
|
|
824
|
+
if (!entry.managedJsonPointers) return sha256(contents);
|
|
825
|
+
const document = parseJson(contents, 'Managed JSON file ' + entry.path);
|
|
826
|
+
const projection = [...entry.managedJsonPointers].sort().map((pointer) => {
|
|
827
|
+
const resolved = pointerValue(document, pointer);
|
|
828
|
+
return { exists: resolved.exists, pointer, ...(resolved.exists ? { value: resolved.value } : {}) };
|
|
829
|
+
});
|
|
830
|
+
return sha256(JSON.stringify(stableValue(projection)));
|
|
831
|
+
};
|
|
832
|
+
const validateOwnership = (root, record, label) => {
|
|
833
|
+
for (const entry of record.paths ?? []) {
|
|
834
|
+
if (entry.mode !== 'managed') continue;
|
|
835
|
+
const path = join(root, entry.path);
|
|
836
|
+
if (!existsSync(path)) throw new Error(label + ' managed path is missing: ' + entry.path);
|
|
837
|
+
if (ownedSha256(readFileSync(path, 'utf8'), entry) !== entry.sha256) {
|
|
838
|
+
throw new Error(label + ' managed path has drifted: ' + entry.path);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const workspace = readJson('.ebk/ownership.json');
|
|
844
|
+
const foundation = readJson('packages/foundation/.ebk/ownership.json');
|
|
845
|
+
if (
|
|
846
|
+
workspace.schemaVersion !== ${index_js_1.WORKSPACE_OWNERSHIP_SCHEMA_VERSION} ||
|
|
847
|
+
workspace.kind !== 'workspace' ||
|
|
848
|
+
foundation.schemaVersion !== ${index_js_1.FOUNDATION_OWNERSHIP_SCHEMA_VERSION} ||
|
|
849
|
+
foundation.kind !== 'foundation'
|
|
850
|
+
) {
|
|
851
|
+
throw new Error('EBK workspace or Foundation ownership metadata is incompatible.');
|
|
852
|
+
}
|
|
853
|
+
validateOwnership('.', workspace, 'Workspace');
|
|
854
|
+
validateOwnership('packages/foundation', foundation, 'Foundation');
|
|
855
|
+
|
|
856
|
+
for (const name of readdirSync('packages')) {
|
|
857
|
+
if (name === 'foundation') continue;
|
|
858
|
+
const ownershipPath = join('packages', name, '.ebk/ownership.json');
|
|
859
|
+
if (!existsSync(ownershipPath)) continue;
|
|
860
|
+
const ownership = readJson(ownershipPath);
|
|
861
|
+
if (ownership.schemaVersion !== ${index_js_1.GENERATED_OWNERSHIP_SCHEMA_VERSION}) {
|
|
862
|
+
throw new Error('Product Slice ' + name + ' ownership metadata is incompatible.');
|
|
863
|
+
}
|
|
864
|
+
validateOwnership(join('packages', name), ownership, 'Product Slice ' + name);
|
|
865
|
+
}
|
|
866
|
+
process.stdout.write('Empire Builder Kit workspace source health is green.\\n');
|
|
867
|
+
`,
|
|
868
|
+
};
|
|
869
|
+
files[FOUNDATION_OWNERSHIP_PATH] = json(createFoundationOwnership(files, frameworkVersion));
|
|
870
|
+
files[WORKSPACE_OWNERSHIP_PATH] = json(createWorkspaceOwnership(files, frameworkVersion));
|
|
871
|
+
return files;
|
|
872
|
+
}
|
|
873
|
+
function renderWorkspaceRootFilePlan(options) {
|
|
874
|
+
const workspaceFiles = renderWorkspaceFiles(options);
|
|
875
|
+
const files = Object.fromEntries(Object.entries(workspaceFiles).filter(([path]) => !path.startsWith(FOUNDATION_ROOT)));
|
|
876
|
+
return {
|
|
877
|
+
files,
|
|
878
|
+
ownership: JSON.parse(workspaceFiles[WORKSPACE_OWNERSHIP_PATH]),
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function renderFoundationFilePlan(options) {
|
|
882
|
+
const workspaceFiles = renderWorkspaceFiles(options);
|
|
883
|
+
const files = Object.fromEntries(Object.entries(workspaceFiles)
|
|
884
|
+
.filter(([path]) => path.startsWith(FOUNDATION_ROOT))
|
|
885
|
+
.map(([path, contents]) => [
|
|
886
|
+
path.slice(FOUNDATION_ROOT.length),
|
|
887
|
+
contents,
|
|
888
|
+
]));
|
|
889
|
+
return {
|
|
890
|
+
files,
|
|
891
|
+
ownership: JSON.parse(workspaceFiles[FOUNDATION_OWNERSHIP_PATH]),
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
exports.WORKSPACE_PACKAGE_MANAGER_VERSION = TOOL_VERSIONS.pnpm;
|
|
895
|
+
//# sourceMappingURL=workspace-files.js.map
|