@smoothbricks/cli 0.10.7 → 0.10.9
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/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -1
- package/dist/github-ci/index.d.ts +44 -4
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +220 -34
- package/dist/monorepo/ci-workflow.js +16 -6
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +19 -1
- package/dist/monorepo/pr-preview-cleanup-workflow.d.ts +5 -0
- package/dist/monorepo/pr-preview-cleanup-workflow.d.ts.map +1 -0
- package/dist/monorepo/pr-preview-cleanup-workflow.js +38 -0
- package/dist/monorepo/publish-workflow.js +3 -3
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +85 -5
- package/dist/playwright/index.d.ts +22 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +44 -0
- package/dist/release/bootstrap-npm-packages.d.ts +3 -0
- package/dist/release/bootstrap-npm-packages.d.ts.map +1 -1
- package/dist/release/bootstrap-npm-packages.js +21 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +31 -6
- package/dist/wrangler/cloudflare.d.ts +87 -0
- package/dist/wrangler/cloudflare.d.ts.map +1 -0
- package/dist/wrangler/cloudflare.js +238 -0
- package/dist/wrangler/deploy-environment.d.ts +48 -0
- package/dist/wrangler/deploy-environment.d.ts.map +1 -0
- package/dist/wrangler/deploy-environment.js +383 -0
- package/dist/wrangler/environment.d.ts +58 -0
- package/dist/wrangler/environment.d.ts.map +1 -0
- package/dist/wrangler/environment.js +297 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +3 -4
- package/package.json +9 -2
- package/src/cli.ts +27 -3
- package/src/github-ci/index.test.ts +175 -2
- package/src/github-ci/index.ts +274 -31
- package/src/monorepo/__tests__/ci-workflow.test.ts +10 -4
- package/src/monorepo/__tests__/pr-preview-cleanup-workflow.test.ts +23 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +7 -5
- package/src/monorepo/ci-workflow.ts +16 -6
- package/src/monorepo/managed-files.test.ts +56 -1
- package/src/monorepo/managed-files.ts +20 -1
- package/src/monorepo/pr-preview-cleanup-workflow.ts +44 -0
- package/src/monorepo/publish-workflow.ts +8 -5
- package/src/monorepo/tool-validation.test.ts +85 -0
- package/src/monorepo/tool-validation.ts +94 -5
- package/src/playwright/index.test.ts +90 -0
- package/src/playwright/index.ts +73 -0
- package/src/release/__tests__/bootstrap-npm-packages.test.ts +63 -2
- package/src/release/bootstrap-npm-packages.ts +34 -0
- package/src/release/index.ts +30 -4
- package/src/wrangler/cloudflare.test.ts +76 -0
- package/src/wrangler/cloudflare.ts +292 -0
- package/src/wrangler/deploy-environment.test.ts +354 -0
- package/src/wrangler/deploy-environment.ts +445 -0
- package/src/wrangler/environment.test.ts +173 -0
- package/src/wrangler/environment.ts +366 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type EnvironmentToken = 'staging' | 'production' | `pr${number}`;
|
|
2
|
+
export declare function pullRequestEnvironment(prNumber: number): `pr${number}`;
|
|
3
|
+
export declare function parseEnvironmentToken(value: string): EnvironmentToken;
|
|
4
|
+
export declare function isPullRequestEnvironment(environment: EnvironmentToken): environment is `pr${number}`;
|
|
5
|
+
export declare function environmentDomain(environment: string, zone: string): string;
|
|
6
|
+
export declare function environmentResourceName(base: string, environment: string): string;
|
|
7
|
+
export declare function hasExactEnvironmentSegment(value: string, environment: `pr${number}`): boolean;
|
|
8
|
+
export interface KvBinding {
|
|
9
|
+
binding: string;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
export interface R2Binding {
|
|
13
|
+
binding: string;
|
|
14
|
+
bucketName: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LiveKvNamespace {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PullRequestKvResource {
|
|
21
|
+
binding: string;
|
|
22
|
+
stagingId: string;
|
|
23
|
+
stagingTitle: string;
|
|
24
|
+
title: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PullRequestResourcePlan {
|
|
27
|
+
environment: `pr${number}`;
|
|
28
|
+
workerName: string;
|
|
29
|
+
workerBaseName: string;
|
|
30
|
+
kvNamespaces: PullRequestKvResource[];
|
|
31
|
+
r2Buckets: R2Binding[];
|
|
32
|
+
routes: Array<{
|
|
33
|
+
pattern: string;
|
|
34
|
+
zoneName?: string;
|
|
35
|
+
customDomain: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export interface ConfiguredEnvironmentResourcePlan {
|
|
39
|
+
environment: EnvironmentToken;
|
|
40
|
+
workerName: string;
|
|
41
|
+
kvNamespaces: KvBinding[];
|
|
42
|
+
r2Buckets: R2Binding[];
|
|
43
|
+
routes: Array<{
|
|
44
|
+
pattern: string;
|
|
45
|
+
zoneName?: string;
|
|
46
|
+
customDomain: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
export declare function planConfiguredEnvironmentResources(toml: string, environment: EnvironmentToken): ConfiguredEnvironmentResourcePlan;
|
|
50
|
+
export declare function planPullRequestResources(toml: string, environment: `pr${number}`, liveNamespaces: LiveKvNamespace[]): PullRequestResourcePlan;
|
|
51
|
+
export interface DerivePullRequestConfigOptions {
|
|
52
|
+
environment: `pr${number}`;
|
|
53
|
+
accountId: string;
|
|
54
|
+
kvNamespaceIds: ReadonlyMap<string, string>;
|
|
55
|
+
}
|
|
56
|
+
export declare function derivePullRequestWranglerConfig(toml: string, options: DerivePullRequestConfigOptions): string;
|
|
57
|
+
export declare function rateLimitNamespaceId(accountId: string, workerBaseName: string, environment: string, bindingName: string): string;
|
|
58
|
+
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/wrangler/environment.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,MAAM,EAAE,CAAC;AAKxE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,CAKtE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAQrE;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,gBAAgB,GAAG,WAAW,IAAI,KAAK,MAAM,EAAE,CAEpG;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAMjF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAG7F;AAgBD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AACD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9E;AACD,MAAM,WAAW,iCAAiC;IAChD,WAAW,EAAE,gBAAgB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,gBAAgB,GAC5B,iCAAiC,CAoBnC;AAyBD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,KAAK,MAAM,EAAE,EAC1B,cAAc,EAAE,eAAe,EAAE,GAChC,uBAAuB,CAwCzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAkD7G;AAgDD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAKR"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { getStaticTOMLValue, parseTOML } from 'toml-eslint-parser';
|
|
3
|
+
import typia from 'typia';
|
|
4
|
+
import { cloneEnvBlock } from './prepare-env.js';
|
|
5
|
+
const MAX_PULL_REQUEST_NUMBER = 999999999;
|
|
6
|
+
const ENVIRONMENT_PATTERN = /^(?:staging|production|pr[1-9][0-9]{0,8})$/;
|
|
7
|
+
export function pullRequestEnvironment(prNumber) {
|
|
8
|
+
if (!Number.isInteger(prNumber) || prNumber < 1 || prNumber > MAX_PULL_REQUEST_NUMBER) {
|
|
9
|
+
throw new Error(`Pull request number must be an integer from 1 through ${MAX_PULL_REQUEST_NUMBER}.`);
|
|
10
|
+
}
|
|
11
|
+
return `pr${prNumber}`;
|
|
12
|
+
}
|
|
13
|
+
export function parseEnvironmentToken(value) {
|
|
14
|
+
if (!ENVIRONMENT_PATTERN.test(value)) {
|
|
15
|
+
throw new Error('Environment must be exactly staging, production, or pr followed by an integer from 1 through 999999999.');
|
|
16
|
+
}
|
|
17
|
+
if (value === 'staging' || value === 'production')
|
|
18
|
+
return value;
|
|
19
|
+
return pullRequestEnvironment(Number(value.slice(2)));
|
|
20
|
+
}
|
|
21
|
+
export function isPullRequestEnvironment(environment) {
|
|
22
|
+
return environment.startsWith('pr');
|
|
23
|
+
}
|
|
24
|
+
export function environmentDomain(environment, zone) {
|
|
25
|
+
const token = parseEnvironmentToken(environment);
|
|
26
|
+
if (!zone || zone.startsWith('.') || zone.endsWith('.')) {
|
|
27
|
+
throw new Error('Zone must be a non-empty DNS name without leading or trailing dots.');
|
|
28
|
+
}
|
|
29
|
+
return token === 'production' ? zone : `${token}.${zone}`;
|
|
30
|
+
}
|
|
31
|
+
export function environmentResourceName(base, environment) {
|
|
32
|
+
const token = parseEnvironmentToken(environment);
|
|
33
|
+
if (!base) {
|
|
34
|
+
throw new Error('Resource base name must not be empty.');
|
|
35
|
+
}
|
|
36
|
+
return token === 'production' ? base : `${base}-${token}`;
|
|
37
|
+
}
|
|
38
|
+
export function hasExactEnvironmentSegment(value, environment) {
|
|
39
|
+
const escaped = environment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
40
|
+
return new RegExp(`(?:^|[-.])${escaped}(?=$|[-.])`).test(value);
|
|
41
|
+
}
|
|
42
|
+
const isWranglerRoot = (() => {
|
|
43
|
+
const _io0 = input => undefined === input.env || "object" === typeof input.env && null !== input.env && false === Array.isArray(input.env) && _io1(input.env);
|
|
44
|
+
const _io1 = input => Object.keys(input).every(key => {
|
|
45
|
+
const value = input[key];
|
|
46
|
+
if (undefined === value)
|
|
47
|
+
return true;
|
|
48
|
+
return undefined === value || "object" === typeof value && null !== value && false === Array.isArray(value) && _io2(value);
|
|
49
|
+
});
|
|
50
|
+
const _io2 = input => true && true && true && true && true && true && Object.keys(input).every(key => {
|
|
51
|
+
if (["name", "routes", "kv_namespaces", "r2_buckets", "ratelimits", "vars"].some(prop => key === prop))
|
|
52
|
+
return true;
|
|
53
|
+
const value = input[key];
|
|
54
|
+
if (undefined === value)
|
|
55
|
+
return true;
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
59
|
+
})();
|
|
60
|
+
const isWranglerEnvironment = (() => {
|
|
61
|
+
const _io0 = input => true && true && true && true && true && true && Object.keys(input).every(key => {
|
|
62
|
+
if (["name", "routes", "kv_namespaces", "r2_buckets", "ratelimits", "vars"].some(prop => key === prop))
|
|
63
|
+
return true;
|
|
64
|
+
const value = input[key];
|
|
65
|
+
if (undefined === value)
|
|
66
|
+
return true;
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
69
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
70
|
+
})();
|
|
71
|
+
const isUnknownRecord = (() => {
|
|
72
|
+
const _io0 = input => Object.keys(input).every(key => {
|
|
73
|
+
const value = input[key];
|
|
74
|
+
if (undefined === value)
|
|
75
|
+
return true;
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
79
|
+
})();
|
|
80
|
+
const isUnknownRows = (() => {
|
|
81
|
+
const _io0 = input => Object.keys(input).every(key => {
|
|
82
|
+
const value = input[key];
|
|
83
|
+
if (undefined === value)
|
|
84
|
+
return true;
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
return input => Array.isArray(input) && input.every(elem => "object" === typeof elem && null !== elem && false === Array.isArray(elem) && _io0(elem));
|
|
88
|
+
})();
|
|
89
|
+
export function planConfiguredEnvironmentResources(toml, environment) {
|
|
90
|
+
const block = parseRoot(toml).env?.[environment];
|
|
91
|
+
if (!isWranglerEnvironment(block)) {
|
|
92
|
+
throw new Error(`Wrangler configuration must declare [env.${environment}].`);
|
|
93
|
+
}
|
|
94
|
+
const workerName = requiredString(block, 'name', `[env.${environment}]`);
|
|
95
|
+
return {
|
|
96
|
+
environment,
|
|
97
|
+
workerName,
|
|
98
|
+
kvNamespaces: readKvBindings(block.kv_namespaces),
|
|
99
|
+
r2Buckets: readRows(block.r2_buckets).map((row) => {
|
|
100
|
+
const binding = requiredString(row, 'binding', 'R2 binding');
|
|
101
|
+
return { binding, bucketName: requiredString(row, 'bucket_name', `R2 binding ${binding}`) };
|
|
102
|
+
}),
|
|
103
|
+
routes: readRows(block.routes).map((row) => ({
|
|
104
|
+
pattern: requiredString(row, 'pattern', 'route'),
|
|
105
|
+
...(typeof row.zone_name === 'string' ? { zoneName: row.zone_name } : {}),
|
|
106
|
+
customDomain: row.custom_domain === true,
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function parseRoot(toml) {
|
|
111
|
+
const value = getStaticTOMLValue(parseTOML(toml));
|
|
112
|
+
if (!isWranglerRoot(value)) {
|
|
113
|
+
throw new Error('Wrangler configuration is not a valid TOML environment document.');
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function stagingEnvironment(toml) {
|
|
118
|
+
const staging = parseRoot(toml).env?.staging;
|
|
119
|
+
if (!isWranglerEnvironment(staging)) {
|
|
120
|
+
throw new Error('Wrangler configuration must declare [env.staging].');
|
|
121
|
+
}
|
|
122
|
+
return staging;
|
|
123
|
+
}
|
|
124
|
+
function stagingWorkerName(staging) {
|
|
125
|
+
if (typeof staging.name !== 'string' || !staging.name.endsWith('-staging')) {
|
|
126
|
+
throw new Error('[env.staging].name must end with the exact suffix -staging.');
|
|
127
|
+
}
|
|
128
|
+
return { workerName: staging.name, workerBaseName: staging.name.slice(0, -'-staging'.length) };
|
|
129
|
+
}
|
|
130
|
+
export function planPullRequestResources(toml, environment, liveNamespaces) {
|
|
131
|
+
parseEnvironmentToken(environment);
|
|
132
|
+
const staging = stagingEnvironment(toml);
|
|
133
|
+
const { workerBaseName } = stagingWorkerName(staging);
|
|
134
|
+
const namespaceById = new Map(liveNamespaces.map((namespace) => [namespace.id, namespace]));
|
|
135
|
+
const kvNamespaces = readKvBindings(staging.kv_namespaces).map(({ binding, id }) => {
|
|
136
|
+
const stagingNamespace = namespaceById.get(id);
|
|
137
|
+
if (!stagingNamespace) {
|
|
138
|
+
throw new Error(`Staging KV binding ${binding} references namespace ${id}, which is absent from the account listing.`);
|
|
139
|
+
}
|
|
140
|
+
const title = replaceExactToken(stagingNamespace.title, 'staging', environment);
|
|
141
|
+
if (title === stagingNamespace.title) {
|
|
142
|
+
throw new Error(`Staging KV namespace title ${stagingNamespace.title} has no exact staging segment.`);
|
|
143
|
+
}
|
|
144
|
+
return { binding, stagingId: id, stagingTitle: stagingNamespace.title, title };
|
|
145
|
+
});
|
|
146
|
+
const r2Buckets = readRows(staging.r2_buckets).map((row) => {
|
|
147
|
+
const binding = requiredString(row, 'binding', 'R2 binding');
|
|
148
|
+
const stagingBucket = requiredString(row, 'bucket_name', `R2 binding ${binding}`);
|
|
149
|
+
const bucketName = replaceExactToken(stagingBucket, 'staging', environment);
|
|
150
|
+
if (bucketName === stagingBucket) {
|
|
151
|
+
throw new Error(`Staging R2 bucket ${stagingBucket} has no exact staging segment.`);
|
|
152
|
+
}
|
|
153
|
+
return { binding, bucketName };
|
|
154
|
+
});
|
|
155
|
+
const routes = readRows(staging.routes).map((row) => ({
|
|
156
|
+
pattern: replaceHostnameLabel(requiredString(row, 'pattern', 'route'), environment),
|
|
157
|
+
...(typeof row.zone_name === 'string' ? { zoneName: row.zone_name } : {}),
|
|
158
|
+
customDomain: row.custom_domain === true,
|
|
159
|
+
}));
|
|
160
|
+
return {
|
|
161
|
+
environment,
|
|
162
|
+
workerName: environmentResourceName(workerBaseName, environment),
|
|
163
|
+
workerBaseName,
|
|
164
|
+
kvNamespaces,
|
|
165
|
+
r2Buckets,
|
|
166
|
+
routes,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function derivePullRequestWranglerConfig(toml, options) {
|
|
170
|
+
const environment = options.environment;
|
|
171
|
+
parseEnvironmentToken(environment);
|
|
172
|
+
if (!options.accountId) {
|
|
173
|
+
throw new Error('Cloudflare account id is required to derive rate-limit namespaces.');
|
|
174
|
+
}
|
|
175
|
+
const staging = stagingEnvironment(toml);
|
|
176
|
+
const { workerBaseName } = stagingWorkerName(staging);
|
|
177
|
+
const cloned = cloneEnvBlock(toml, 'staging', environment);
|
|
178
|
+
const program = parseTOML(cloned);
|
|
179
|
+
const rootValue = getStaticTOMLValue(program);
|
|
180
|
+
if (!isWranglerRoot(rootValue)) {
|
|
181
|
+
throw new Error('Derived Wrangler configuration is not a valid environment document.');
|
|
182
|
+
}
|
|
183
|
+
const root = rootValue;
|
|
184
|
+
const edits = [];
|
|
185
|
+
for (const table of program.body[0].body) {
|
|
186
|
+
if (table.type !== 'TOMLTable' || table.resolvedKey[0] !== 'env' || table.resolvedKey[1] !== environment) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const tableValue = valueAtPath(root, table.resolvedKey);
|
|
190
|
+
if (!isUnknownRecord(tableValue)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
for (const keyValue of table.body) {
|
|
194
|
+
const key = cloned.slice(keyValue.key.range[0], keyValue.key.range[1]).trim();
|
|
195
|
+
const current = tableValue[key];
|
|
196
|
+
const next = deriveFieldValue(table.resolvedKey.slice(2), tableValue, key, current, environment, workerBaseName, options.accountId, options.kvNamespaceIds);
|
|
197
|
+
if (next !== current) {
|
|
198
|
+
edits.push({ start: keyValue.value.range[0], end: keyValue.value.range[1], value: tomlLiteral(next) });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let derived = cloned;
|
|
203
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
204
|
+
derived = derived.slice(0, edit.start) + edit.value + derived.slice(edit.end);
|
|
205
|
+
}
|
|
206
|
+
parseTOML(derived);
|
|
207
|
+
return derived;
|
|
208
|
+
}
|
|
209
|
+
function deriveFieldValue(path, table, key, current, environment, workerBaseName, accountId, kvNamespaceIds) {
|
|
210
|
+
const section = path[0];
|
|
211
|
+
if (path.length === 0 && key === 'name') {
|
|
212
|
+
return environmentResourceName(workerBaseName, environment);
|
|
213
|
+
}
|
|
214
|
+
if (section === 'routes' && key === 'pattern' && typeof current === 'string') {
|
|
215
|
+
return replaceHostnameLabel(current, environment);
|
|
216
|
+
}
|
|
217
|
+
if (section === 'vars' && typeof current === 'string') {
|
|
218
|
+
if (key === 'ENVIRONMENT') {
|
|
219
|
+
return environment;
|
|
220
|
+
}
|
|
221
|
+
if (key === 'AUTH_KEYS_INSTANCE_NAME') {
|
|
222
|
+
return replaceExactToken(current, 'staging', environment);
|
|
223
|
+
}
|
|
224
|
+
return replaceHostnameLabel(current, environment);
|
|
225
|
+
}
|
|
226
|
+
if (section === 'send_email' && key === 'allowed_sender_addresses' && Array.isArray(current)) {
|
|
227
|
+
return current.map((value) => (typeof value === 'string' ? replaceHostnameLabel(value, environment) : value));
|
|
228
|
+
}
|
|
229
|
+
if (section === 'kv_namespaces' && key === 'id' && typeof current === 'string') {
|
|
230
|
+
const derived = kvNamespaceIds.get(current);
|
|
231
|
+
if (!derived) {
|
|
232
|
+
throw new Error(`No derived KV namespace id was supplied for staging namespace ${current}.`);
|
|
233
|
+
}
|
|
234
|
+
return derived;
|
|
235
|
+
}
|
|
236
|
+
if (section === 'r2_buckets' && key === 'bucket_name' && typeof current === 'string') {
|
|
237
|
+
return replaceExactToken(current, 'staging', environment);
|
|
238
|
+
}
|
|
239
|
+
if (section === 'ratelimits' && key === 'namespace_id') {
|
|
240
|
+
const bindingName = requiredString(table, 'name', 'Rate-limit binding');
|
|
241
|
+
return rateLimitNamespaceId(accountId, workerBaseName, environment, bindingName);
|
|
242
|
+
}
|
|
243
|
+
return current;
|
|
244
|
+
}
|
|
245
|
+
export function rateLimitNamespaceId(accountId, workerBaseName, environment, bindingName) {
|
|
246
|
+
const token = parseEnvironmentToken(environment);
|
|
247
|
+
const digest = createHash('sha256').update(`${accountId}:${workerBaseName}:${token}:${bindingName}`).digest();
|
|
248
|
+
const value = digest.readUInt32BE(0) & 2147483647;
|
|
249
|
+
return String(value === 0 ? 1 : value);
|
|
250
|
+
}
|
|
251
|
+
function replaceHostnameLabel(value, environment) {
|
|
252
|
+
return value.replace(/(^|[.@/])staging(?=\.)/g, `$1${environment}`);
|
|
253
|
+
}
|
|
254
|
+
function replaceExactToken(value, from, to) {
|
|
255
|
+
const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
256
|
+
return value.replace(new RegExp(`(^|[-.])${escaped}(?=$|[-.])`, 'g'), `$1${to}`);
|
|
257
|
+
}
|
|
258
|
+
function readKvBindings(value) {
|
|
259
|
+
return readRows(value).map((row) => ({
|
|
260
|
+
binding: requiredString(row, 'binding', 'KV namespace'),
|
|
261
|
+
id: requiredString(row, 'id', 'KV namespace'),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
function readRows(value) {
|
|
265
|
+
return isUnknownRows(value) ? value : [];
|
|
266
|
+
}
|
|
267
|
+
function requiredString(row, key, context) {
|
|
268
|
+
const value = row[key];
|
|
269
|
+
if (typeof value !== 'string' || !value) {
|
|
270
|
+
throw new Error(`${context} must declare a non-empty ${key}.`);
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
function valueAtPath(root, path) {
|
|
275
|
+
let value = root;
|
|
276
|
+
for (const segment of path) {
|
|
277
|
+
if (typeof segment === 'number') {
|
|
278
|
+
if (!Array.isArray(value))
|
|
279
|
+
return undefined;
|
|
280
|
+
value = value[segment];
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
if (!isUnknownRecord(value))
|
|
284
|
+
return undefined;
|
|
285
|
+
value = value[segment];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
function tomlLiteral(value) {
|
|
291
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || Array.isArray(value)) {
|
|
292
|
+
const literal = JSON.stringify(value);
|
|
293
|
+
if (literal !== undefined)
|
|
294
|
+
return literal;
|
|
295
|
+
}
|
|
296
|
+
throw new Error(`Cannot materialize Wrangler TOML value of type ${typeof value}.`);
|
|
297
|
+
}
|
|
@@ -4,15 +4,14 @@ set -euo pipefail
|
|
|
4
4
|
NIX_STORE_NAR="${NIX_STORE_NAR:-/tmp/nix-store.nar}"
|
|
5
5
|
nix_store_cmd="/nix/var/nix/profiles/default/bin/nix-store"
|
|
6
6
|
DEVENV_FLAKE="${DEVENV_FLAKE:-github:cachix/devenv}"
|
|
7
|
-
# Host CI cache (GARM bind-mount). Prefer env from runner profile.
|
|
8
|
-
# Path comes from the runner (GARM profile). Ephemeral runners leave unset;
|
|
9
|
-
# devenv enterShell defaults to $PWD/.cache/ttsc.
|
|
10
|
-
TTSC_CACHE_DIR="${TTSC_CACHE_DIR:-}"
|
|
11
7
|
# Resolve from this script's location, not the caller's cwd. GitHub Actions
|
|
12
8
|
# runs this from tooling/direnv today, but direct cwd-changing helpers are easy
|
|
13
9
|
# to misuse and break on repeated calls.
|
|
14
10
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
11
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
|
12
|
+
# Host CI supplies its bind-mounted cache through the runner profile. Ephemeral
|
|
13
|
+
# runners must use the same repository path restored by cache-ttsc-plugins.
|
|
14
|
+
TTSC_CACHE_DIR="${TTSC_CACHE_DIR:-$repo_root/.cache/ttsc}"
|
|
16
15
|
|
|
17
16
|
clear_devenv_cache_state() {
|
|
18
17
|
rm -rf "$repo_root/tooling/direnv/.devenv" "$repo_root/tooling/direnv/.direnv"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SmoothBricks monorepo automation CLI",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,13 @@
|
|
|
47
47
|
"development": "./src/wrangler/prepare-env.ts",
|
|
48
48
|
"import": "./dist/wrangler/prepare-env.js",
|
|
49
49
|
"default": "./dist/wrangler/prepare-env.js"
|
|
50
|
+
},
|
|
51
|
+
"./wrangler/environment": {
|
|
52
|
+
"types": "./dist/wrangler/environment.d.ts",
|
|
53
|
+
"bun": "./src/wrangler/environment.ts",
|
|
54
|
+
"development": "./src/wrangler/environment.ts",
|
|
55
|
+
"import": "./dist/wrangler/environment.js",
|
|
56
|
+
"default": "./dist/wrangler/environment.js"
|
|
50
57
|
}
|
|
51
58
|
},
|
|
52
59
|
"sideEffects": [
|
|
@@ -64,7 +71,7 @@
|
|
|
64
71
|
"dependencies": {
|
|
65
72
|
"@arethetypeswrong/core": "^0.18.2",
|
|
66
73
|
"@prettier/sync": "^0.6.1",
|
|
67
|
-
"@smoothbricks/nx-plugin": "0.3.
|
|
74
|
+
"@smoothbricks/nx-plugin": "0.3.6",
|
|
68
75
|
"@smoothbricks/validation": "0.1.5",
|
|
69
76
|
"commander": "^14.0.3",
|
|
70
77
|
"prettier": "^3.6.1",
|
package/src/cli.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { Command, CommanderError } from 'commander';
|
|
|
2
2
|
import { variants } from './generate/index.js';
|
|
3
3
|
import { cliPackageVersion } from './lib/cli-package.js';
|
|
4
4
|
import { findRepoRoot } from './lib/run.js';
|
|
5
|
+
import { ensureChromium } from './playwright/index.js';
|
|
5
6
|
import { resolvePrConflicts } from './pr/index.js';
|
|
7
|
+
import { cleanupPullRequest, deployEnvironment } from './wrangler/deploy-environment.js';
|
|
6
8
|
import { scaffold } from './wrangler/scaffold.js';
|
|
7
9
|
|
|
8
10
|
export async function runCli(argv = process.argv.slice(2)): Promise<void> {
|
|
@@ -173,7 +175,8 @@ function buildProgram(): Command {
|
|
|
173
175
|
.requiredOption('--targets <targets>', 'comma-separated Nx platform target names or globs')
|
|
174
176
|
.requiredOption('--output <path>', 'output directory for current and repair artifacts')
|
|
175
177
|
.option('--ref <ref>', 'fixed release graph ref to inspect')
|
|
176
|
-
.
|
|
178
|
+
.option('--github-output <path>', 'append selected current platform projects to a GitHub Actions output file')
|
|
179
|
+
.action(async (options: { bump: string; githubOutput?: string; output: string; ref?: string; targets: string }) => {
|
|
177
180
|
// The source self-hosting shim has no Typia transform; release commands import transformed output validators.
|
|
178
181
|
const { releaseCollectPlatformOutputs } = await import('./release/index.js');
|
|
179
182
|
await releaseCollectPlatformOutputs(await findRepoRoot(), options);
|
|
@@ -373,14 +376,14 @@ function buildProgram(): Command {
|
|
|
373
376
|
});
|
|
374
377
|
githubCi
|
|
375
378
|
.command('nx-deploy')
|
|
376
|
-
.
|
|
379
|
+
.option('--environment <environment>', 'explicit staging, production, or prN override')
|
|
377
380
|
.option('--mode <mode>', 'auto, affected, or run-many', 'run-many')
|
|
378
381
|
.option('--name <name>')
|
|
379
382
|
.option('--step <step>')
|
|
380
383
|
.option('--verify', 'run build, lint, and test before deploy')
|
|
381
384
|
.action(
|
|
382
385
|
async (options: {
|
|
383
|
-
|
|
386
|
+
environment?: string;
|
|
384
387
|
mode?: 'auto' | 'affected' | 'run-many';
|
|
385
388
|
name?: string;
|
|
386
389
|
step?: string;
|
|
@@ -403,6 +406,15 @@ function buildProgram(): Command {
|
|
|
403
406
|
}
|
|
404
407
|
});
|
|
405
408
|
|
|
409
|
+
const playwright = program.command('playwright').description('Manage Playwright browsers');
|
|
410
|
+
const playwrightEnsure = playwright.command('ensure').description('Ensure a Playwright browser is available');
|
|
411
|
+
playwrightEnsure
|
|
412
|
+
.command('chromium')
|
|
413
|
+
.description('Ensure Chromium is available for browser tests')
|
|
414
|
+
.action(async () => {
|
|
415
|
+
await ensureChromium();
|
|
416
|
+
});
|
|
417
|
+
|
|
406
418
|
const wrangler = program.command('wrangler').description('Cloudflare wrangler project helpers');
|
|
407
419
|
wrangler
|
|
408
420
|
.command('scaffold <project>')
|
|
@@ -411,6 +423,18 @@ function buildProgram(): Command {
|
|
|
411
423
|
.action(async (project: string, options: { force?: boolean }) => {
|
|
412
424
|
scaffold(await findRepoRoot(), project, { force: options.force });
|
|
413
425
|
});
|
|
426
|
+
wrangler
|
|
427
|
+
.command('deploy-environment')
|
|
428
|
+
.requiredOption('--environment <environment>', 'staging, production, or prN')
|
|
429
|
+
.action(async (options: { environment: string }) => {
|
|
430
|
+
await deployEnvironment(process.cwd(), options.environment);
|
|
431
|
+
});
|
|
432
|
+
wrangler
|
|
433
|
+
.command('cleanup-pr')
|
|
434
|
+
.requiredOption('--pr <number>', 'pull-request number')
|
|
435
|
+
.action(async (options: { pr: string }) => {
|
|
436
|
+
await cleanupPullRequest(process.cwd(), Number(options.pr));
|
|
437
|
+
});
|
|
414
438
|
|
|
415
439
|
return program;
|
|
416
440
|
}
|