@aws-blocks/core 0.1.7 → 0.1.10
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/dist/db-naming.d.ts +17 -5
- package/dist/db-naming.d.ts.map +1 -1
- package/dist/db-naming.js +18 -6
- package/dist/db-naming.test.js +44 -3
- package/dist/hosting.d.ts +49 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +47 -8
- package/dist/hosting.test.js +60 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +4 -2
- package/dist/scripts/ensure-secrets.d.ts +5 -2
- package/dist/scripts/ensure-secrets.d.ts.map +1 -1
- package/dist/scripts/ensure-secrets.js +14 -6
- package/dist/scripts/external-migrations-step.d.ts.map +1 -1
- package/dist/scripts/external-migrations-step.js +5 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +1 -1
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +10 -1
- package/dist/scripts/stack-id.d.ts +25 -0
- package/dist/scripts/stack-id.d.ts.map +1 -1
- package/dist/scripts/stack-id.js +25 -0
- package/dist/scripts/stack-id.test.js +51 -1
- package/dist/telemetry/client.d.ts +3 -1
- package/dist/telemetry/client.d.ts.map +1 -1
- package/dist/telemetry/client.js +20 -24
- package/dist/telemetry/telemetry-send-worker.d.ts +2 -0
- package/dist/telemetry/telemetry-send-worker.d.ts.map +1 -0
- package/dist/telemetry/telemetry-send-worker.js +58 -0
- package/dist/telemetry/telemetry.test.js +77 -1
- package/dist/telemetry/trackCommand.d.ts +1 -1
- package/dist/telemetry/trackCommand.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/db-naming.test.ts +50 -5
- package/src/db-naming.ts +18 -6
- package/src/hosting.test.ts +79 -0
- package/src/hosting.ts +105 -12
- package/src/scripts/deploy.ts +4 -2
- package/src/scripts/ensure-secrets.ts +17 -6
- package/src/scripts/external-migrations-step.ts +5 -1
- package/src/scripts/index.ts +1 -1
- package/src/scripts/sandbox.ts +10 -1
- package/src/scripts/stack-id.test.ts +61 -1
- package/src/scripts/stack-id.ts +26 -0
- package/src/telemetry/client.ts +22 -30
- package/src/telemetry/telemetry-send-worker.ts +60 -0
- package/src/telemetry/telemetry.test.ts +91 -1
- package/src/telemetry/trackCommand.ts +3 -3
- package/src/version.ts +1 -1
package/dist/db-naming.d.ts
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
* database connection string, and for the project ref derived from a Postgres
|
|
4
4
|
* connection string.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The connection-string parameter name is **stack-scoped** (embeds the
|
|
7
|
+
* deployment's stack name), so two Blocks apps in the same account + region +
|
|
8
|
+
* stage get distinct names and cannot overwrite each other's credentials.
|
|
9
|
+
*
|
|
10
|
+
* Two call sites compute the name: the pre-deploy writer (`ensure-secrets`) and
|
|
11
|
+
* the `db pull` generated wiring at synth. Both pass the result of
|
|
12
|
+
* `getStackName({ sandbox, projectRoot })` into this function, so they produce
|
|
13
|
+
* the same name by construction. The runtime Lambda does not call this function;
|
|
14
|
+
* it reads the name recorded at synth.
|
|
9
15
|
*/
|
|
10
16
|
/**
|
|
11
17
|
* Extract a stable identifier from a Postgres connection string.
|
|
@@ -16,6 +22,12 @@
|
|
|
16
22
|
* to a sanitized hostname for non-Supabase hosts.
|
|
17
23
|
*/
|
|
18
24
|
export declare function extractDbRef(connectionString: string): string;
|
|
19
|
-
/**
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* SSM SecureString parameter name for a deployment's external database
|
|
27
|
+
* connection string.
|
|
28
|
+
*
|
|
29
|
+
* Pure string transform: `/<stackName>-db-url`. The caller is responsible for
|
|
30
|
+
* computing the stack name via `getStackName({ sandbox, projectRoot })`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function dbConnectionParameterName(stackName: string): string;
|
|
21
33
|
//# sourceMappingURL=db-naming.d.ts.map
|
package/dist/db-naming.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-naming.d.ts","sourceRoot":"","sources":["../src/db-naming.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"db-naming.d.ts","sourceRoot":"","sources":["../src/db-naming.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAc7D;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
package/dist/db-naming.js
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* database connection string, and for the project ref derived from a Postgres
|
|
6
6
|
* connection string.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The connection-string parameter name is **stack-scoped** (embeds the
|
|
9
|
+
* deployment's stack name), so two Blocks apps in the same account + region +
|
|
10
|
+
* stage get distinct names and cannot overwrite each other's credentials.
|
|
11
|
+
*
|
|
12
|
+
* Two call sites compute the name: the pre-deploy writer (`ensure-secrets`) and
|
|
13
|
+
* the `db pull` generated wiring at synth. Both pass the result of
|
|
14
|
+
* `getStackName({ sandbox, projectRoot })` into this function, so they produce
|
|
15
|
+
* the same name by construction. The runtime Lambda does not call this function;
|
|
16
|
+
* it reads the name recorded at synth.
|
|
11
17
|
*/
|
|
12
18
|
/**
|
|
13
19
|
* Extract a stable identifier from a Postgres connection string.
|
|
@@ -32,7 +38,13 @@ export function extractDbRef(connectionString) {
|
|
|
32
38
|
return host[1].replace(/\./g, '-');
|
|
33
39
|
throw new Error('Cannot extract database identifier from connection string.');
|
|
34
40
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* SSM SecureString parameter name for a deployment's external database
|
|
43
|
+
* connection string.
|
|
44
|
+
*
|
|
45
|
+
* Pure string transform: `/<stackName>-db-url`. The caller is responsible for
|
|
46
|
+
* computing the stack name via `getStackName({ sandbox, projectRoot })`.
|
|
47
|
+
*/
|
|
48
|
+
export function dbConnectionParameterName(stackName) {
|
|
49
|
+
return `/${stackName}-db-url`;
|
|
38
50
|
}
|
package/dist/db-naming.test.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import { test, describe } from 'node:test';
|
|
3
|
+
import { test, describe, afterEach } from 'node:test';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
5
8
|
import { extractDbRef, dbConnectionParameterName } from './db-naming.js';
|
|
9
|
+
import { getStackName } from './scripts/stack-id.js';
|
|
6
10
|
describe('extractDbRef', () => {
|
|
7
11
|
test('pooler form (postgres.{ref}@) yields ref', () => {
|
|
8
12
|
assert.strictEqual(extractDbRef('postgresql://postgres.abcdef:pw@aws-0-us-east-1.pooler.supabase.com:6543/postgres'), 'abcdef');
|
|
@@ -23,7 +27,44 @@ describe('extractDbRef', () => {
|
|
|
23
27
|
});
|
|
24
28
|
});
|
|
25
29
|
describe('dbConnectionParameterName', () => {
|
|
26
|
-
test('
|
|
27
|
-
assert.strictEqual(dbConnectionParameterName('
|
|
30
|
+
test('formats stack name into parameter path', () => {
|
|
31
|
+
assert.strictEqual(dbConnectionParameterName('my-app-k7x2mf-prod'), '/my-app-k7x2mf-prod-db-url');
|
|
32
|
+
});
|
|
33
|
+
test('two distinct stack names produce distinct parameter names', () => {
|
|
34
|
+
assert.notStrictEqual(dbConnectionParameterName('app-a-111111-prod'), dbConnectionParameterName('app-b-222222-prod'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('cross-site invariant: write name == read name', () => {
|
|
38
|
+
let tmpDir;
|
|
39
|
+
let originalCwd;
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
if (originalCwd)
|
|
42
|
+
process.chdir(originalCwd);
|
|
43
|
+
if (tmpDir)
|
|
44
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
function setupProject(stackId, sandboxId) {
|
|
47
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'cross-site-'));
|
|
48
|
+
mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
|
|
49
|
+
writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId }));
|
|
50
|
+
mkdirSync(join(tmpDir, '.blocks-sandbox'), { recursive: true });
|
|
51
|
+
writeFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), sandboxId);
|
|
52
|
+
return tmpDir;
|
|
53
|
+
}
|
|
54
|
+
test('write-side name == read-side name (sandbox)', () => {
|
|
55
|
+
const root = setupProject('my-app-k7x2mf', 'alice-0d7e1c');
|
|
56
|
+
const writeName = dbConnectionParameterName(getStackName({ sandbox: true, projectRoot: root }));
|
|
57
|
+
originalCwd = process.cwd();
|
|
58
|
+
process.chdir(root);
|
|
59
|
+
const readName = dbConnectionParameterName(getStackName({ sandbox: true }));
|
|
60
|
+
assert.strictEqual(writeName, readName);
|
|
61
|
+
});
|
|
62
|
+
test('write-side name == read-side name (production)', () => {
|
|
63
|
+
const root = setupProject('my-app-k7x2mf', 'alice-0d7e1c');
|
|
64
|
+
const writeName = dbConnectionParameterName(getStackName({ sandbox: false, projectRoot: root }));
|
|
65
|
+
originalCwd = process.cwd();
|
|
66
|
+
process.chdir(root);
|
|
67
|
+
const readName = dbConnectionParameterName(getStackName({ sandbox: false }));
|
|
68
|
+
assert.strictEqual(writeName, readName);
|
|
28
69
|
});
|
|
29
70
|
});
|
package/dist/hosting.d.ts
CHANGED
|
@@ -105,6 +105,26 @@ export interface HostingProps {
|
|
|
105
105
|
buildOutputDir?: string;
|
|
106
106
|
/** Supply a custom adapter when using an unsupported framework. */
|
|
107
107
|
customAdapter?: FrameworkAdapterFn;
|
|
108
|
+
/**
|
|
109
|
+
* URL prefix the whole site is served under (Next.js `basePath`, Astro
|
|
110
|
+
* `base`, Nuxt `app.baseURL`). When set, CloudFront behaviors are prefixed
|
|
111
|
+
* with it and the bare root issues a 308 redirect to `/<basePath>/`.
|
|
112
|
+
*
|
|
113
|
+
* Declaring it here is the recommended source of truth: the value is
|
|
114
|
+
* caller-provided rather than reverse-engineered from build output, so it
|
|
115
|
+
* can't drift with framework/bundler internals. When omitted, the adapter
|
|
116
|
+
* falls back to detecting the framework's own base-path config from the
|
|
117
|
+
* build output.
|
|
118
|
+
*
|
|
119
|
+
* Format: leading slash, no trailing slash (e.g. `'/app'`). A trailing
|
|
120
|
+
* slash or bare `'/'` is normalized/ignored.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* new Hosting(stack, 'Web', { root, framework: 'nuxt', basePath: '/app' });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
basePath?: string;
|
|
108
128
|
/**
|
|
109
129
|
* The Blocks backend stack (or any object with `apiUrl`).
|
|
110
130
|
*
|
|
@@ -148,6 +168,35 @@ export interface HostingProps {
|
|
|
148
168
|
type: 'whitelist' | 'blacklist';
|
|
149
169
|
countries: string[];
|
|
150
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Overrides for the adjustable AWS Service Quotas the CloudFront
|
|
173
|
+
* distribution draws on. Each field maps to a named AWS quota you can
|
|
174
|
+
* request an increase on:
|
|
175
|
+
*
|
|
176
|
+
* - `cacheBehaviors` — "Cache behaviors per distribution" (default 25).
|
|
177
|
+
* Consumed by routed paths, prerendered pages, per-pattern header
|
|
178
|
+
* rules, assetPrefix, and the error-page behavior.
|
|
179
|
+
* - `edgeFunctions` — Lambda@Edge associations per distribution
|
|
180
|
+
* (default 25). Consumed by `runtime: 'edge'` routes.
|
|
181
|
+
* - `headerPolicies` — "Response headers policies per AWS account"
|
|
182
|
+
* (default 20, account-wide).
|
|
183
|
+
*
|
|
184
|
+
* Omitted fields use the AWS default. Set a field ONLY to match a quota
|
|
185
|
+
* increase AWS has actually granted — synth cannot verify your real quota,
|
|
186
|
+
* so an over-set value does not raise the AWS ceiling; it just moves the
|
|
187
|
+
* failure from a clear synth error to an opaque CloudFormation rollback.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* // After AWS grants "Cache behaviors per distribution" = 50:
|
|
192
|
+
* new Hosting(stack, 'Web', { root, quotas: { cacheBehaviors: 50 } });
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
quotas?: {
|
|
196
|
+
cacheBehaviors?: number;
|
|
197
|
+
edgeFunctions?: number;
|
|
198
|
+
headerPolicies?: number;
|
|
199
|
+
};
|
|
151
200
|
/**
|
|
152
201
|
* Build cache configuration. When enabled, provisions an S3 bucket for
|
|
153
202
|
* framework build caches (e.g. Next.js .next/cache) and exports the bucket
|
package/dist/hosting.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AASnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"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"}
|
package/dist/hosting.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync } from 'node:child_process';
|
|
|
9
9
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { HostingConstruct, generateBuildId, } from '@aws-blocks/hosting/constructs';
|
|
12
|
-
import { detectFramework, getAdapter, } from '@aws-blocks/hosting/adapters';
|
|
12
|
+
import { detectFramework, getAdapter, normalizeBasePath, } from '@aws-blocks/hosting/adapters';
|
|
13
13
|
import { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
|
|
14
14
|
import { registerConfig } from './cdk/config-registry.js';
|
|
15
15
|
import { getRegisteredRoutes } from './raw-route.js';
|
|
@@ -123,6 +123,23 @@ export class Hosting extends Construct {
|
|
|
123
123
|
if (!manifest.buildId) {
|
|
124
124
|
manifest.buildId = generateBuildId();
|
|
125
125
|
}
|
|
126
|
+
// ── 4b'. basePath: prop is the source of truth ───────────────
|
|
127
|
+
// A caller-declared `basePath` overrides whatever the adapter
|
|
128
|
+
// detected from build output. This is the robust path: the value
|
|
129
|
+
// is provided rather than reverse-engineered from framework/bundler
|
|
130
|
+
// internals (which drift across versions). When the prop is omitted,
|
|
131
|
+
// the adapter's detected `manifest.basePath` (if any) stands.
|
|
132
|
+
if (props.basePath !== undefined) {
|
|
133
|
+
const normalized = normalizeBasePath(props.basePath);
|
|
134
|
+
if (normalized) {
|
|
135
|
+
manifest.basePath = normalized;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Explicit '/' (or empty) means "no base path" — clear any value
|
|
139
|
+
// the adapter may have detected so the prop genuinely wins.
|
|
140
|
+
delete manifest.basePath;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
126
143
|
// ── 4c. Prevent duplicate error pages ────────────────────────
|
|
127
144
|
// The adapter may auto-detect error pages (e.g. SPA adapter finds
|
|
128
145
|
// 404.html in build output and sets manifest.errorPages). When the
|
|
@@ -185,11 +202,12 @@ export class Hosting extends Construct {
|
|
|
185
202
|
storage: props.retainOnDelete != null
|
|
186
203
|
? { retainOnDelete: props.retainOnDelete }
|
|
187
204
|
: undefined,
|
|
188
|
-
cdn: (props.contentSecurityPolicy || props.priceClass || props.geoRestriction)
|
|
205
|
+
cdn: (props.contentSecurityPolicy || props.priceClass || props.geoRestriction || props.quotas)
|
|
189
206
|
? {
|
|
190
207
|
contentSecurityPolicy: props.contentSecurityPolicy,
|
|
191
208
|
priceClass: props.priceClass,
|
|
192
209
|
geoRestriction: props.geoRestriction,
|
|
210
|
+
quotas: props.quotas,
|
|
193
211
|
}
|
|
194
212
|
: undefined,
|
|
195
213
|
logging: props.logging,
|
|
@@ -204,8 +222,15 @@ export class Hosting extends Construct {
|
|
|
204
222
|
this.addApiBehaviors(hosting, props.api.apiUrl);
|
|
205
223
|
}
|
|
206
224
|
// ── 7a. Inject Blocks env vars into compute functions ───────────
|
|
207
|
-
|
|
225
|
+
// Lambda@Edge functions (edge-runtime routes) do NOT support environment
|
|
226
|
+
// variables — they surface in computeFunctions as EdgeFunction/IVersion
|
|
227
|
+
// without an `addEnvironment` method. Skip any function that can't take
|
|
228
|
+
// env vars instead of crashing (`fn.addEnvironment is not a function`).
|
|
229
|
+
const canAddEnv = (fn) => typeof fn?.addEnvironment === 'function';
|
|
230
|
+
const primaryFunction = [...hosting.computeFunctions.values()].find(canAddEnv);
|
|
208
231
|
for (const [, fn] of hosting.computeFunctions) {
|
|
232
|
+
if (!canAddEnv(fn))
|
|
233
|
+
continue; // Lambda@Edge: no env var support
|
|
209
234
|
if (props.api) {
|
|
210
235
|
fn.addEnvironment('BLOCKS_API_URL', props.api.apiUrl);
|
|
211
236
|
}
|
|
@@ -227,11 +252,25 @@ export class Hosting extends Construct {
|
|
|
227
252
|
distributionPaths: ['/.blocks-sandbox/*'],
|
|
228
253
|
cacheControl: [s3deploy.CacheControl.fromString('public, max-age=60, must-revalidate')],
|
|
229
254
|
});
|
|
230
|
-
// Ensure config deployment runs
|
|
231
|
-
// asset
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
255
|
+
// Ensure the config deployment runs AFTER the hosting construct's
|
|
256
|
+
// asset deployments. Those deployments upload the whole static dir —
|
|
257
|
+
// which includes the *placeholder* `.blocks-sandbox/config.json`
|
|
258
|
+
// (`{_placeholder:true}`) written during synth — to the same
|
|
259
|
+
// `builds/<id>/.blocks-sandbox/config.json` key this deployment writes
|
|
260
|
+
// the resolved config to. Without an ordering dependency the
|
|
261
|
+
// placeholder can land last and clobber the real config.
|
|
262
|
+
//
|
|
263
|
+
// We depend on EVERY BucketDeployment under the hosting construct
|
|
264
|
+
// rather than a single hard-coded child id: the real children are
|
|
265
|
+
// `AssetDeploymentImmutable` / `AssetDeploymentHtml` / `...Mutable`
|
|
266
|
+
// (and vary by deploy shape), so the previous
|
|
267
|
+
// `tryFindChild('AssetDeployment')` never matched and the dependency
|
|
268
|
+
// was silently never wired.
|
|
269
|
+
const assetDeployments = hosting.node
|
|
270
|
+
.findAll()
|
|
271
|
+
.filter((c) => c instanceof s3deploy.BucketDeployment);
|
|
272
|
+
for (const dep of assetDeployments) {
|
|
273
|
+
configDeployment.node.addDependency(dep);
|
|
235
274
|
}
|
|
236
275
|
}
|
|
237
276
|
// ── 9. Register public origin + CORS hosting origin into S3 config ──
|
package/dist/hosting.test.js
CHANGED
|
@@ -1074,4 +1074,64 @@ describe('Hosting', () => {
|
|
|
1074
1074
|
template.resourceCountIs('AWS::CloudFront::Distribution', 1);
|
|
1075
1075
|
});
|
|
1076
1076
|
});
|
|
1077
|
+
// ── basePath prop (caller-declared source of truth) ─────────
|
|
1078
|
+
// Under KVS edge routing, basePath is no longer expressed as a per-behavior
|
|
1079
|
+
// PathPattern prefix — it lives in the KVS route table's `meta.bp`, which the
|
|
1080
|
+
// edge router uses for the canonical 308 + static strip. So these tests read
|
|
1081
|
+
// the basePath out of the RouteStoreKeys custom resource's Entries.
|
|
1082
|
+
describe('basePath prop', () => {
|
|
1083
|
+
const metaBasePath = (root, basePath) => {
|
|
1084
|
+
const app = new App();
|
|
1085
|
+
const stack = new Stack(app, 'BasePathStack', {
|
|
1086
|
+
env: { account: '123456789012', region: 'us-east-1' },
|
|
1087
|
+
});
|
|
1088
|
+
new Hosting(stack, 'Web', {
|
|
1089
|
+
root,
|
|
1090
|
+
framework: 'spa',
|
|
1091
|
+
buildOutputDir: 'dist',
|
|
1092
|
+
...(basePath !== undefined ? { basePath } : {}),
|
|
1093
|
+
});
|
|
1094
|
+
const tpl = Template.fromStack(stack).toJSON();
|
|
1095
|
+
const kvKeys = Object.entries(tpl.Resources).find(([id, r]) => r.Type === 'AWS::CloudFormation::CustomResource' &&
|
|
1096
|
+
/RouteStoreKeys/.test(id));
|
|
1097
|
+
assert.ok(kvKeys, 'expected a RouteStoreKeys custom resource');
|
|
1098
|
+
const entries = JSON.parse(kvKeys[1].Properties.Entries);
|
|
1099
|
+
const meta = JSON.parse(entries.meta);
|
|
1100
|
+
return meta.bp;
|
|
1101
|
+
};
|
|
1102
|
+
it('records basePath in the KVS route table when set (SPA, no framework base)', () => {
|
|
1103
|
+
createSpaBuildOutput(tmpDir);
|
|
1104
|
+
assert.strictEqual(metaBasePath(tmpDir, '/app'), '/app');
|
|
1105
|
+
});
|
|
1106
|
+
it('normalizes a trailing slash (/app/ → /app)', () => {
|
|
1107
|
+
createSpaBuildOutput(tmpDir);
|
|
1108
|
+
assert.strictEqual(metaBasePath(tmpDir, '/app/'), '/app');
|
|
1109
|
+
});
|
|
1110
|
+
it('treats "/" as no base path', () => {
|
|
1111
|
+
createSpaBuildOutput(tmpDir);
|
|
1112
|
+
assert.strictEqual(metaBasePath(tmpDir, '/'), '');
|
|
1113
|
+
});
|
|
1114
|
+
});
|
|
1115
|
+
// ── P0.4: config.json ordering dependency ────────────────────
|
|
1116
|
+
describe('config.json deploy ordering (P0.4)', () => {
|
|
1117
|
+
it('BlocksConfigDeployment depends on the asset deployments', () => {
|
|
1118
|
+
// The asset deployments upload the whole static dir — including the
|
|
1119
|
+
// placeholder `.blocks-sandbox/config.json` — to the same key the
|
|
1120
|
+
// resolved config writes to. Without an ordering dependency the
|
|
1121
|
+
// placeholder can clobber the real config. The previous
|
|
1122
|
+
// `tryFindChild('AssetDeployment')` never matched the real child ids
|
|
1123
|
+
// (AssetDeploymentImmutable/Html/Mutable), so the dep was never wired.
|
|
1124
|
+
createSpaBuildOutput(tmpDir);
|
|
1125
|
+
const app = new App();
|
|
1126
|
+
const stack = new Stack(app, 'ConfigOrderStack');
|
|
1127
|
+
new Hosting(stack, 'Hosting', { root: tmpDir, api: MOCK_API });
|
|
1128
|
+
const tpl = Template.fromStack(stack).toJSON();
|
|
1129
|
+
const configId = Object.keys(tpl.Resources).find((id) => /BlocksConfigDeployment/.test(id) && /CustomResource/.test(id));
|
|
1130
|
+
assert.ok(configId, 'expected a BlocksConfigDeployment custom resource');
|
|
1131
|
+
const dependsOn = tpl.Resources[configId].DependsOn ?? [];
|
|
1132
|
+
const assetDeps = dependsOn.filter((d) => /AssetDeployment/.test(d));
|
|
1133
|
+
assert.ok(assetDeps.length >= 1, `BlocksConfigDeployment must DependsOn the asset deployment(s); ` +
|
|
1134
|
+
`found DependsOn=${JSON.stringify(dependsOn)}`);
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1077
1137
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/scripts/deploy.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/scripts/deploy.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,iBA0FlD"}
|
package/dist/scripts/deploy.js
CHANGED
|
@@ -15,8 +15,10 @@ export async function deploy(options) {
|
|
|
15
15
|
// Load production environment (from .env.production or CI env vars)
|
|
16
16
|
loadProductionEnv();
|
|
17
17
|
process.env.BLOCKS_STAGE = 'production';
|
|
18
|
-
// Provision secrets for production
|
|
19
|
-
|
|
18
|
+
// Provision secrets for production. projectRoot must match the root cdk
|
|
19
|
+
// synth uses (passed as --context below) so the written parameter name
|
|
20
|
+
// equals the one the app resolves at synth.
|
|
21
|
+
const secrets = await ensureSecrets('production', options.projectRoot);
|
|
20
22
|
if (secrets.created.length > 0 || secrets.updated.length > 0) {
|
|
21
23
|
console.log(`🔐 Secrets provisioned: ${[...secrets.created, ...secrets.updated].join(', ')}`);
|
|
22
24
|
}
|
|
@@ -8,9 +8,12 @@ export declare function findConnectionString(): {
|
|
|
8
8
|
value: string;
|
|
9
9
|
} | null;
|
|
10
10
|
/**
|
|
11
|
-
* Ensure the connection string is stored in SSM
|
|
11
|
+
* Ensure the connection string is stored in SSM under this app's stack-scoped
|
|
12
|
+
* parameter name. `projectRoot` locates the committed `.blocks/config.json`
|
|
13
|
+
* that defines the stack name; it must match the root used at synth (the deploy
|
|
14
|
+
* commands pass it explicitly) so the written name equals the name the app reads.
|
|
12
15
|
*/
|
|
13
|
-
export declare function ensureSecrets(stage?:
|
|
16
|
+
export declare function ensureSecrets(stage?: string, projectRoot?: string): Promise<EnsureSecretsResult>;
|
|
14
17
|
/**
|
|
15
18
|
* Load environment for production deployment.
|
|
16
19
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-secrets.d.ts","sourceRoot":"","sources":["../../src/scripts/ensure-secrets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ensure-secrets.d.ts","sourceRoot":"","sources":["../../src/scripts/ensure-secrets.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,oBAAoB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO7E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAsC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAelD"}
|
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Pre-deploy secret provisioning.
|
|
5
5
|
*
|
|
6
|
-
* Writes the connection string to an SSM SecureString parameter.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Writes the connection string to an SSM SecureString parameter. The parameter
|
|
7
|
+
* name is stack-scoped (`/<stackName>-db-url` via `dbConnectionParameterName`),
|
|
8
|
+
* so two Blocks apps in the same account/region/stage never collide. The synth
|
|
9
|
+
* step names the parameter with the same function and the same inputs
|
|
10
|
+
* (`projectRoot` + stage), so the value written here is read back under the
|
|
11
|
+
* identical name — which is why this must be given the same `projectRoot` the
|
|
12
|
+
* deploy command passes to synth.
|
|
9
13
|
*
|
|
10
14
|
* On first deploy: creates the parameter.
|
|
11
15
|
* On subsequent deploys: updates if value changed, no-op otherwise.
|
|
12
16
|
*/
|
|
13
17
|
import { existsSync, readFileSync } from 'node:fs';
|
|
14
18
|
import { dbConnectionParameterName } from '../db-naming.js';
|
|
19
|
+
import { getStackName } from './stack-id.js';
|
|
15
20
|
const CONNECTION_STRING_PATTERN = /_(DB_URL|CONNECTION_STRING)$/;
|
|
16
21
|
export function findConnectionString() {
|
|
17
22
|
for (const [name, value] of Object.entries(process.env)) {
|
|
@@ -22,9 +27,12 @@ export function findConnectionString() {
|
|
|
22
27
|
return null;
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
25
|
-
* Ensure the connection string is stored in SSM
|
|
30
|
+
* Ensure the connection string is stored in SSM under this app's stack-scoped
|
|
31
|
+
* parameter name. `projectRoot` locates the committed `.blocks/config.json`
|
|
32
|
+
* that defines the stack name; it must match the root used at synth (the deploy
|
|
33
|
+
* commands pass it explicitly) so the written name equals the name the app reads.
|
|
26
34
|
*/
|
|
27
|
-
export async function ensureSecrets(stage) {
|
|
35
|
+
export async function ensureSecrets(stage, projectRoot) {
|
|
28
36
|
const result = { created: [], updated: [], unchanged: [] };
|
|
29
37
|
const conn = findConnectionString();
|
|
30
38
|
if (!conn)
|
|
@@ -32,7 +40,7 @@ export async function ensureSecrets(stage) {
|
|
|
32
40
|
const resolvedStage = stage ?? process.env.BLOCKS_STAGE ?? 'sandbox';
|
|
33
41
|
const { SSMClient, GetParameterCommand, PutParameterCommand } = await import('@aws-sdk/client-ssm');
|
|
34
42
|
const client = new SSMClient();
|
|
35
|
-
const parameterName = dbConnectionParameterName(resolvedStage);
|
|
43
|
+
const parameterName = dbConnectionParameterName(getStackName({ sandbox: resolvedStage !== 'production', projectRoot }));
|
|
36
44
|
let isNew = false;
|
|
37
45
|
try {
|
|
38
46
|
const current = await client.send(new GetParameterCommand({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-migrations-step.d.ts","sourceRoot":"","sources":["../../src/scripts/external-migrations-step.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"external-migrations-step.d.ts","sourceRoot":"","sources":["../../src/scripts/external-migrations-step.ts"],"names":[],"mappings":"AAoCA;;;;;GAKG;AACH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAI5G;AAcD,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,SAAS,GAAG,YAAY,CAAC;IAChC,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,GAAG,OAAO,CAErF;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBpG;AAoBD,qFAAqF;AACrF,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAe3E;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAExF;AA0ED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnH"}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
26
26
|
import { findConnectionString } from './ensure-secrets.js';
|
|
27
27
|
import { extractDbRef, dbConnectionParameterName } from '../db-naming.js';
|
|
28
|
+
import { getStackName } from './stack-id.js';
|
|
28
29
|
import { runSync } from './run-command.js';
|
|
29
30
|
const DEFAULT_MIGRATIONS_DIR = './migrations';
|
|
30
31
|
/** Default output dir for db-pull generated files (database.types.ts / database.meta.ts). */
|
|
@@ -150,7 +151,10 @@ async function productionRefs(devRef) {
|
|
|
150
151
|
}
|
|
151
152
|
try {
|
|
152
153
|
const { SSMClient, GetParameterCommand } = await import('@aws-sdk/client-ssm');
|
|
153
|
-
const res = await new SSMClient().send(new GetParameterCommand({
|
|
154
|
+
const res = await new SSMClient().send(new GetParameterCommand({
|
|
155
|
+
Name: dbConnectionParameterName(getStackName({ sandbox: false })),
|
|
156
|
+
WithDecryption: true,
|
|
157
|
+
}));
|
|
154
158
|
const v = res.Parameter?.Value;
|
|
155
159
|
const r = v ? safeRef(v) : null;
|
|
156
160
|
if (r)
|
package/dist/scripts/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export { openConsole, type ConsoleOptions } from './console.js';
|
|
|
9
9
|
export { ensureSecrets, loadProductionEnv, loadEnvFile } from './ensure-secrets.js';
|
|
10
10
|
export { trackCommand, buildAndSendEvent, classifyError, type CommandName, type CommandState, type BuildAndSendEventOptions, } from '../telemetry/index.js';
|
|
11
11
|
export { telemetry, type TelemetryOptions } from './telemetry.js';
|
|
12
|
-
export { getStackId, getSandboxId } from './stack-id.js';
|
|
12
|
+
export { getStackId, getSandboxId, getStackName } from './stack-id.js';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scripts/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scripts/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/scripts/index.js
CHANGED
|
@@ -11,4 +11,4 @@ export { openConsole } from './console.js';
|
|
|
11
11
|
export { ensureSecrets, loadProductionEnv, loadEnvFile } from './ensure-secrets.js';
|
|
12
12
|
export { trackCommand, buildAndSendEvent, classifyError, } from '../telemetry/index.js';
|
|
13
13
|
export { telemetry } from './telemetry.js';
|
|
14
|
-
export { getStackId, getSandboxId } from './stack-id.js';
|
|
14
|
+
export { getStackId, getSandboxId, getStackName } from './stack-id.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+BAkMzD;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,iBAwCvD"}
|
package/dist/scripts/sandbox.js
CHANGED
|
@@ -41,7 +41,9 @@ export async function startSandbox(options) {
|
|
|
41
41
|
process.env.BLOCKS_STAGE = 'sandbox';
|
|
42
42
|
// Provision connection string to SSM SecureString.
|
|
43
43
|
// On first deploy, creates the parameter. On subsequent deploys, updates if changed.
|
|
44
|
-
|
|
44
|
+
// projectRoot is process.cwd() — the same value passed to cdk as --context
|
|
45
|
+
// projectRoot below — so the written name matches the name resolved at synth.
|
|
46
|
+
const secrets = await ensureSecrets('sandbox', process.cwd());
|
|
45
47
|
if (secrets.created.length > 0) {
|
|
46
48
|
console.log(`🔐 Created secrets: ${secrets.created.join(', ')}`);
|
|
47
49
|
}
|
|
@@ -59,6 +61,13 @@ export async function startSandbox(options) {
|
|
|
59
61
|
try {
|
|
60
62
|
runSync("npm", [
|
|
61
63
|
"exec", "cdk", "--", "deploy",
|
|
64
|
+
// `--all`: an app that uses Lambda@Edge (e.g. a Next.js route with
|
|
65
|
+
// `export const runtime = 'edge'`) synthesizes a SECOND stack
|
|
66
|
+
// (`edge-lambda-stack-*`, region us-east-1) in addition to the main
|
|
67
|
+
// hosting stack. Without `--all`, CDK refuses with "specify which
|
|
68
|
+
// stacks to use". Deploying every stack in a sandbox app is the
|
|
69
|
+
// intended behavior, so select them all.
|
|
70
|
+
"--all",
|
|
62
71
|
"--require-approval", "never",
|
|
63
72
|
"--outputs-file", `${outDir}/outputs.json`,
|
|
64
73
|
"--context", `projectRoot=${process.cwd()}`,
|
|
@@ -7,6 +7,31 @@ export declare function getStackId(projectRoot?: string): string;
|
|
|
7
7
|
* Get or create a per-machine sandbox identifier.
|
|
8
8
|
* Stored in `.blocks-sandbox/sandbox-id.txt` (gitignored).
|
|
9
9
|
* Format: `<username(8)>-<random(6)>` — identifies the developer's sandbox.
|
|
10
|
+
*
|
|
11
|
+
* Get-or-create (lazy init): returns the existing id, or generates and persists
|
|
12
|
+
* one on first call. The file is the shared sync point — once written, every
|
|
13
|
+
* later caller and every process reads the same id, so the secret writer
|
|
14
|
+
* (`ensureSecrets`) and synth derive identical names.
|
|
10
15
|
*/
|
|
11
16
|
export declare function getSandboxId(projectRoot?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The full CloudFormation stack name for a deployment.
|
|
19
|
+
*
|
|
20
|
+
* Single source of truth for the stack-name scheme (D-012): production is
|
|
21
|
+
* `<stackId>-prod`; a sandbox is `<stackId>-<sandboxId>`. The CDK templates name
|
|
22
|
+
* the stack with this function, and the external-DB connection-string parameter
|
|
23
|
+
* name (`dbConnectionParameterName`) is derived from it — so a deployed stack and
|
|
24
|
+
* the parameter holding its database credentials can never use divergent names.
|
|
25
|
+
*
|
|
26
|
+
* This function reads committed config (`.blocks/config.json`, throws if absent
|
|
27
|
+
* — D-012) and resolves the sandbox id via {@link getSandboxId} (get-or-create):
|
|
28
|
+
* the first caller materializes `.blocks-sandbox/sandbox-id.txt`, every later
|
|
29
|
+
* caller reads the same value. Because that file persists and is shared across
|
|
30
|
+
* processes, the secret writer (`ensureSecrets`) and synth resolve identical
|
|
31
|
+
* names. Production does not use the sandbox id.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getStackName(opts: {
|
|
34
|
+
sandbox: boolean;
|
|
35
|
+
projectRoot?: string;
|
|
36
|
+
}): string;
|
|
12
37
|
//# sourceMappingURL=stack-id.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-id.d.ts","sourceRoot":"","sources":["../../src/scripts/stack-id.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAavD;AAED
|
|
1
|
+
{"version":3,"file":"stack-id.d.ts","sourceRoot":"","sources":["../../src/scripts/stack-id.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAavD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGrF"}
|
package/dist/scripts/stack-id.js
CHANGED
|
@@ -29,6 +29,11 @@ export function getStackId(projectRoot) {
|
|
|
29
29
|
* Get or create a per-machine sandbox identifier.
|
|
30
30
|
* Stored in `.blocks-sandbox/sandbox-id.txt` (gitignored).
|
|
31
31
|
* Format: `<username(8)>-<random(6)>` — identifies the developer's sandbox.
|
|
32
|
+
*
|
|
33
|
+
* Get-or-create (lazy init): returns the existing id, or generates and persists
|
|
34
|
+
* one on first call. The file is the shared sync point — once written, every
|
|
35
|
+
* later caller and every process reads the same id, so the secret writer
|
|
36
|
+
* (`ensureSecrets`) and synth derive identical names.
|
|
32
37
|
*/
|
|
33
38
|
export function getSandboxId(projectRoot) {
|
|
34
39
|
const root = projectRoot || process.cwd();
|
|
@@ -44,6 +49,26 @@ export function getSandboxId(projectRoot) {
|
|
|
44
49
|
writeFileSync(filePath, id);
|
|
45
50
|
return id;
|
|
46
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The full CloudFormation stack name for a deployment.
|
|
54
|
+
*
|
|
55
|
+
* Single source of truth for the stack-name scheme (D-012): production is
|
|
56
|
+
* `<stackId>-prod`; a sandbox is `<stackId>-<sandboxId>`. The CDK templates name
|
|
57
|
+
* the stack with this function, and the external-DB connection-string parameter
|
|
58
|
+
* name (`dbConnectionParameterName`) is derived from it — so a deployed stack and
|
|
59
|
+
* the parameter holding its database credentials can never use divergent names.
|
|
60
|
+
*
|
|
61
|
+
* This function reads committed config (`.blocks/config.json`, throws if absent
|
|
62
|
+
* — D-012) and resolves the sandbox id via {@link getSandboxId} (get-or-create):
|
|
63
|
+
* the first caller materializes `.blocks-sandbox/sandbox-id.txt`, every later
|
|
64
|
+
* caller reads the same value. Because that file persists and is shared across
|
|
65
|
+
* processes, the secret writer (`ensureSecrets`) and synth resolve identical
|
|
66
|
+
* names. Production does not use the sandbox id.
|
|
67
|
+
*/
|
|
68
|
+
export function getStackName(opts) {
|
|
69
|
+
const base = getStackId(opts.projectRoot);
|
|
70
|
+
return opts.sandbox ? `${base}-${getSandboxId(opts.projectRoot)}` : `${base}-prod`;
|
|
71
|
+
}
|
|
47
72
|
function getUsername() {
|
|
48
73
|
try {
|
|
49
74
|
return execSync('git config user.name', { encoding: 'utf-8' }).trim();
|