@smoothbricks/cli 0.10.9 → 0.10.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/cli.js +6 -5
- package/dist/github-ci/index.d.ts +8 -6
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +35 -35
- package/dist/monorepo/ci-workflow.d.ts +3 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.js +59 -15
- package/dist/monorepo/managed-files.d.ts +1 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +15 -5
- package/dist/monorepo/publish-workflow.d.ts +2 -2
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +6 -6
- package/dist/wrangler/cloudflare.d.ts +1 -1
- package/dist/wrangler/cloudflare.d.ts.map +1 -1
- package/dist/wrangler/{deploy-environment.d.ts → deploy-stage.d.ts} +6 -6
- package/dist/wrangler/deploy-stage.d.ts.map +1 -0
- package/dist/wrangler/{deploy-environment.js → deploy-stage.js} +26 -26
- package/dist/wrangler/stage.d.ts +58 -0
- package/dist/wrangler/stage.d.ts.map +1 -0
- package/dist/wrangler/{environment.js → stage.js} +44 -44
- package/package.json +7 -7
- package/src/cli.ts +9 -7
- package/src/github-ci/index.test.ts +101 -32
- package/src/github-ci/index.ts +51 -45
- package/src/monorepo/__tests__/ci-workflow.test.ts +87 -49
- package/src/monorepo/__tests__/publish-workflow.test.ts +8 -8
- package/src/monorepo/ci-workflow.ts +67 -14
- package/src/monorepo/managed-files.test.ts +26 -5
- package/src/monorepo/managed-files.ts +18 -5
- package/src/monorepo/publish-workflow.ts +9 -9
- package/src/wrangler/cloudflare.ts +1 -1
- package/src/wrangler/{deploy-environment.test.ts → deploy-stage.test.ts} +11 -11
- package/src/wrangler/{deploy-environment.ts → deploy-stage.ts} +41 -41
- package/src/wrangler/{environment.test.ts → stage.test.ts} +15 -15
- package/src/wrangler/{environment.ts → stage.ts} +49 -52
- package/dist/wrangler/deploy-environment.d.ts.map +0 -1
- package/dist/wrangler/environment.d.ts +0 -58
- package/dist/wrangler/environment.d.ts.map +0 -1
|
@@ -4,19 +4,19 @@ import { readFile, rm, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import typia from 'typia';
|
|
6
6
|
import { type CloudflareClient, CloudflareRestClient } from './cloudflare.js';
|
|
7
|
+
import { parseDevVarsExample } from './prepare-env.js';
|
|
7
8
|
import {
|
|
8
|
-
type
|
|
9
|
+
type ConfiguredStageResourcePlan,
|
|
10
|
+
type DeploymentStage,
|
|
9
11
|
derivePullRequestWranglerConfig,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
parseEnvironmentToken,
|
|
15
|
-
planConfiguredEnvironmentResources,
|
|
12
|
+
hasExactStageSegment,
|
|
13
|
+
isPullRequestStage,
|
|
14
|
+
parseDeploymentStage,
|
|
15
|
+
planConfiguredStageResources,
|
|
16
16
|
planPullRequestResources,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
pullRequestStage,
|
|
18
|
+
stageResourceName,
|
|
19
|
+
} from './stage.js';
|
|
20
20
|
|
|
21
21
|
export interface ProcessResult {
|
|
22
22
|
exitCode: number;
|
|
@@ -56,8 +56,8 @@ export interface WranglerCommandDependencies {
|
|
|
56
56
|
processEnv?: NodeJS.ProcessEnv;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export interface
|
|
60
|
-
|
|
59
|
+
export interface DeployStageResult {
|
|
60
|
+
stage: DeploymentStage;
|
|
61
61
|
workerName: string;
|
|
62
62
|
action: 'deployed' | 'activated' | 'remote-cache-hit';
|
|
63
63
|
versionTag?: string;
|
|
@@ -65,12 +65,12 @@ export interface DeployEnvironmentResult {
|
|
|
65
65
|
|
|
66
66
|
const isUnknownRecord = typia.createIs<Record<string, unknown>>();
|
|
67
67
|
|
|
68
|
-
export async function
|
|
68
|
+
export async function deployStage(
|
|
69
69
|
cwd: string,
|
|
70
|
-
|
|
70
|
+
stageValue: string,
|
|
71
71
|
dependencies: WranglerCommandDependencies = {},
|
|
72
|
-
): Promise<
|
|
73
|
-
const
|
|
72
|
+
): Promise<DeployStageResult> {
|
|
73
|
+
const stage = parseDeploymentStage(stageValue);
|
|
74
74
|
const processEnv = dependencies.processEnv ?? process.env;
|
|
75
75
|
const accountId = processEnv.CLOUDFLARE_ACCOUNT_ID;
|
|
76
76
|
const apiToken = processEnv.CLOUDFLARE_API_TOKEN;
|
|
@@ -92,12 +92,12 @@ export async function deployEnvironment(
|
|
|
92
92
|
let temporaryConfigPath: string | undefined;
|
|
93
93
|
let temporarySecretsPath: string | undefined;
|
|
94
94
|
try {
|
|
95
|
-
if (
|
|
96
|
-
const staging =
|
|
95
|
+
if (isPullRequestStage(stage)) {
|
|
96
|
+
const staging = planConfiguredStageResources(committedToml, 'staging');
|
|
97
97
|
if (!staging.workerName.endsWith('-staging')) {
|
|
98
98
|
throw new Error('[env.staging].name must end with the exact suffix -staging.');
|
|
99
99
|
}
|
|
100
|
-
const workerName =
|
|
100
|
+
const workerName = stageResourceName(staging.workerName.slice(0, -'-staging'.length), stage);
|
|
101
101
|
const firstDeployment = !(await cloudflare.listWorkerScripts()).some((script) => script.id === workerName);
|
|
102
102
|
if (firstDeployment && missingSecrets.length > 0) {
|
|
103
103
|
throw new Error(
|
|
@@ -105,7 +105,7 @@ export async function deployEnvironment(
|
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
const liveNamespaces = await cloudflare.listKvNamespaces();
|
|
108
|
-
const plan = planPullRequestResources(committedToml,
|
|
108
|
+
const plan = planPullRequestResources(committedToml, stage, liveNamespaces);
|
|
109
109
|
const derivedIds = new Map<string, string>();
|
|
110
110
|
const byTitle = new Map(liveNamespaces.map((namespace) => [namespace.title, namespace]));
|
|
111
111
|
for (const namespace of plan.kvNamespaces) {
|
|
@@ -122,7 +122,7 @@ export async function deployEnvironment(
|
|
|
122
122
|
derivedIds.set(namespace.stagingId, live.id);
|
|
123
123
|
}
|
|
124
124
|
const derivedToml = derivePullRequestWranglerConfig(committedToml, {
|
|
125
|
-
|
|
125
|
+
stage: stage,
|
|
126
126
|
accountId,
|
|
127
127
|
kvNamespaceIds: derivedIds,
|
|
128
128
|
});
|
|
@@ -132,10 +132,10 @@ export async function deployEnvironment(
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
const toml = configPath === committedConfigPath ? committedToml : await readFile(configPath, 'utf8');
|
|
135
|
-
const plan =
|
|
136
|
-
const workerExists = await
|
|
135
|
+
const plan = planConfiguredStageResources(toml, stage);
|
|
136
|
+
const workerExists = await reconcileStageResources(plan, cloudflare);
|
|
137
137
|
const versionTag = nxTaskVersionTag(processEnv);
|
|
138
|
-
const commandContext = { cwd, configPath,
|
|
138
|
+
const commandContext = { cwd, configPath, stage, workerName: plan.workerName };
|
|
139
139
|
|
|
140
140
|
if (versionTag && workerExists) {
|
|
141
141
|
const versions = await wranglerJson(runner, ['versions', 'list', '--name', plan.workerName, '--json'], cwd);
|
|
@@ -146,7 +146,7 @@ export async function deployEnvironment(
|
|
|
146
146
|
);
|
|
147
147
|
const versionId = findVersionIdByTag(versions, versionTag);
|
|
148
148
|
if (versionId && isFullCurrentDeployment(deployments, versionId)) {
|
|
149
|
-
return {
|
|
149
|
+
return { stage, workerName: plan.workerName, action: 'remote-cache-hit', versionTag };
|
|
150
150
|
}
|
|
151
151
|
if (versionId) {
|
|
152
152
|
await wrangler(
|
|
@@ -161,16 +161,16 @@ export async function deployEnvironment(
|
|
|
161
161
|
'--config',
|
|
162
162
|
configPath,
|
|
163
163
|
'--env',
|
|
164
|
-
|
|
164
|
+
stage,
|
|
165
165
|
'--yes',
|
|
166
166
|
],
|
|
167
167
|
cwd,
|
|
168
168
|
);
|
|
169
|
-
return {
|
|
169
|
+
return { stage, workerName: plan.workerName, action: 'activated', versionTag };
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
const deployArgs = ['deploy', '--config', commandContext.configPath, '--env', commandContext.
|
|
173
|
+
const deployArgs = ['deploy', '--config', commandContext.configPath, '--env', commandContext.stage];
|
|
174
174
|
if (versionTag) deployArgs.push('--tag', versionTag);
|
|
175
175
|
if (Object.keys(secretValues).length > 0) {
|
|
176
176
|
temporarySecretsPath = join(cwd, `.wrangler-secrets.smoo-${process.pid}-${randomUUID()}.json`);
|
|
@@ -179,7 +179,7 @@ export async function deployEnvironment(
|
|
|
179
179
|
}
|
|
180
180
|
await wrangler(runner, deployArgs, cwd);
|
|
181
181
|
return {
|
|
182
|
-
|
|
182
|
+
stage,
|
|
183
183
|
workerName: commandContext.workerName,
|
|
184
184
|
action: 'deployed',
|
|
185
185
|
...(versionTag ? { versionTag } : {}),
|
|
@@ -194,7 +194,7 @@ export async function deployEnvironment(
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
export interface CleanupResult {
|
|
197
|
-
|
|
197
|
+
stage: `pr${number}`;
|
|
198
198
|
deleted: {
|
|
199
199
|
workers: number;
|
|
200
200
|
routes: number;
|
|
@@ -211,7 +211,7 @@ export async function cleanupPullRequest(
|
|
|
211
211
|
prNumber: number,
|
|
212
212
|
dependencies: WranglerCommandDependencies = {},
|
|
213
213
|
): Promise<CleanupResult> {
|
|
214
|
-
const
|
|
214
|
+
const stage = pullRequestStage(prNumber);
|
|
215
215
|
const processEnv = dependencies.processEnv ?? process.env;
|
|
216
216
|
const cloudflare =
|
|
217
217
|
dependencies.cloudflare ??
|
|
@@ -231,36 +231,36 @@ export async function cleanupPullRequest(
|
|
|
231
231
|
};
|
|
232
232
|
|
|
233
233
|
for (const domain of await cloudflare.listWorkerDomains()) {
|
|
234
|
-
if (!
|
|
234
|
+
if (!hasExactStageSegment(domain.hostname, stage)) continue;
|
|
235
235
|
await cloudflare.deleteWorkerDomain(domain.id);
|
|
236
236
|
deleted.domains += 1;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
for (const zone of await cloudflare.listZones()) {
|
|
240
240
|
for (const route of await cloudflare.listWorkerRoutes(zone.id)) {
|
|
241
|
-
if (!
|
|
241
|
+
if (!hasExactStageSegment(route.pattern, stage)) continue;
|
|
242
242
|
await cloudflare.deleteWorkerRoute(zone.id, route.id);
|
|
243
243
|
deleted.routes += 1;
|
|
244
244
|
}
|
|
245
245
|
for (const record of await cloudflare.listDnsRecords(zone.id)) {
|
|
246
|
-
if (!
|
|
246
|
+
if (!hasExactStageSegment(record.name, stage)) continue;
|
|
247
247
|
await cloudflare.deleteDnsRecord(zone.id, record.id);
|
|
248
248
|
deleted.dnsRecords += 1;
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
for (const script of await cloudflare.listWorkerScripts()) {
|
|
253
|
-
if (!
|
|
253
|
+
if (!hasExactStageSegment(script.id, stage)) continue;
|
|
254
254
|
await cloudflare.deleteWorkerScript(script.id);
|
|
255
255
|
deleted.workers += 1;
|
|
256
256
|
}
|
|
257
257
|
for (const namespace of await cloudflare.listKvNamespaces()) {
|
|
258
|
-
if (!
|
|
258
|
+
if (!hasExactStageSegment(namespace.title, stage)) continue;
|
|
259
259
|
await cloudflare.deleteKvNamespace(namespace.id);
|
|
260
260
|
deleted.kvNamespaces += 1;
|
|
261
261
|
}
|
|
262
262
|
for (const bucket of await cloudflare.listR2Buckets()) {
|
|
263
|
-
if (!
|
|
263
|
+
if (!hasExactStageSegment(bucket.name, stage)) continue;
|
|
264
264
|
for (const key of await cloudflare.listR2Objects(bucket.name)) {
|
|
265
265
|
await cloudflare.deleteR2Object(bucket.name, key);
|
|
266
266
|
deleted.r2Objects += 1;
|
|
@@ -268,11 +268,11 @@ export async function cleanupPullRequest(
|
|
|
268
268
|
await cloudflare.deleteR2Bucket(bucket.name);
|
|
269
269
|
deleted.r2Buckets += 1;
|
|
270
270
|
}
|
|
271
|
-
return {
|
|
271
|
+
return { stage, deleted };
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
async function
|
|
275
|
-
plan:
|
|
274
|
+
async function reconcileStageResources(
|
|
275
|
+
plan: ConfiguredStageResourcePlan,
|
|
276
276
|
cloudflare: CloudflareClient,
|
|
277
277
|
): Promise<boolean> {
|
|
278
278
|
const namespaces = await cloudflare.listKvNamespaces();
|
|
@@ -364,7 +364,7 @@ export function nxTaskVersionTag(environment: NodeJS.ProcessEnv): string | undef
|
|
|
364
364
|
environment.NX_TASK_TARGET_PROJECT !== undefined ||
|
|
365
365
|
environment.NX_TASK_TARGET_TARGET !== undefined;
|
|
366
366
|
if (!underNx) return undefined;
|
|
367
|
-
if (!hash) throw new Error('NX_TASK_HASH is required when deploy-
|
|
367
|
+
if (!hash) throw new Error('NX_TASK_HASH is required when deploy-stage runs under Nx.');
|
|
368
368
|
if (/^(?:0|[1-9][0-9]*)$/.test(hash)) {
|
|
369
369
|
return `nx-${hash}`;
|
|
370
370
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test';
|
|
2
2
|
import {
|
|
3
3
|
derivePullRequestWranglerConfig,
|
|
4
|
-
environmentDomain,
|
|
5
|
-
environmentResourceName,
|
|
6
4
|
planPullRequestResources,
|
|
7
|
-
|
|
5
|
+
pullRequestStage,
|
|
8
6
|
rateLimitNamespaceId,
|
|
9
|
-
|
|
7
|
+
stageDomain,
|
|
8
|
+
stageResourceName,
|
|
9
|
+
} from './stage.js';
|
|
10
10
|
|
|
11
11
|
const APP_FIXTURE = `name = "conloca-app"
|
|
12
12
|
compatibility_date = "2026-05-06"
|
|
@@ -103,21 +103,21 @@ const DERIVED_IDS = new Map([
|
|
|
103
103
|
['kv-mail-staging-id', 'kv-mail-pr123-id'],
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
|
-
describe('Wrangler
|
|
106
|
+
describe('Wrangler deployment stage convention', () => {
|
|
107
107
|
it('validates pull-request numbers and derives generic names', () => {
|
|
108
|
-
expect(
|
|
109
|
-
expect(() =>
|
|
110
|
-
expect(() =>
|
|
111
|
-
expect(() =>
|
|
112
|
-
expect(
|
|
113
|
-
expect(
|
|
114
|
-
expect(
|
|
115
|
-
expect(
|
|
108
|
+
expect(pullRequestStage(123)).toBe('pr123');
|
|
109
|
+
expect(() => pullRequestStage(0)).toThrow(/1 through 999999999/);
|
|
110
|
+
expect(() => pullRequestStage(1.5)).toThrow(/integer/);
|
|
111
|
+
expect(() => pullRequestStage(1_000_000_000)).toThrow(/1 through 999999999/);
|
|
112
|
+
expect(stageDomain('pr123', 'conloca.com')).toBe('pr123.conloca.com');
|
|
113
|
+
expect(stageDomain('production', 'conloca.com')).toBe('conloca.com');
|
|
114
|
+
expect(stageResourceName('conloca-app', 'staging')).toBe('conloca-app-staging');
|
|
115
|
+
expect(stageResourceName('conloca-app', 'production')).toBe('conloca-app');
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it('derives the app staging block without changing inherited/static semantics', () => {
|
|
119
119
|
const derived = derivePullRequestWranglerConfig(APP_FIXTURE, {
|
|
120
|
-
|
|
120
|
+
stage: 'pr123',
|
|
121
121
|
accountId: 'account-1',
|
|
122
122
|
kvNamespaceIds: new Map<string, string>(),
|
|
123
123
|
});
|
|
@@ -141,7 +141,7 @@ describe('Wrangler environment convention', () => {
|
|
|
141
141
|
expect(plan.r2Buckets).toEqual([{ binding: 'MEDIA', bucketName: 'conloca-media-pr123' }]);
|
|
142
142
|
|
|
143
143
|
const derived = derivePullRequestWranglerConfig(BACKEND_FIXTURE, {
|
|
144
|
-
|
|
144
|
+
stage: 'pr123',
|
|
145
145
|
accountId: 'account-1',
|
|
146
146
|
kvNamespaceIds: DERIVED_IDS,
|
|
147
147
|
});
|
|
@@ -3,50 +3,50 @@ import { getStaticTOMLValue, parseTOML } from 'toml-eslint-parser';
|
|
|
3
3
|
import typia from 'typia';
|
|
4
4
|
import { cloneEnvBlock } from './prepare-env.js';
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type DeploymentStage = 'staging' | 'production' | `pr${number}`;
|
|
7
7
|
|
|
8
8
|
const MAX_PULL_REQUEST_NUMBER = 999_999_999;
|
|
9
|
-
const
|
|
9
|
+
const STAGE_PATTERN = /^(?:staging|production|pr[1-9][0-9]{0,8})$/;
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function pullRequestStage(prNumber: number): `pr${number}` {
|
|
12
12
|
if (!Number.isInteger(prNumber) || prNumber < 1 || prNumber > MAX_PULL_REQUEST_NUMBER) {
|
|
13
13
|
throw new Error(`Pull request number must be an integer from 1 through ${MAX_PULL_REQUEST_NUMBER}.`);
|
|
14
14
|
}
|
|
15
15
|
return `pr${prNumber}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function
|
|
19
|
-
if (!
|
|
18
|
+
export function parseDeploymentStage(value: string): DeploymentStage {
|
|
19
|
+
if (!STAGE_PATTERN.test(value)) {
|
|
20
20
|
throw new Error(
|
|
21
|
-
'
|
|
21
|
+
'Deployment stage must be exactly staging, production, or pr followed by an integer from 1 through 999999999.',
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
if (value === 'staging' || value === 'production') return value;
|
|
25
|
-
return
|
|
25
|
+
return pullRequestStage(Number(value.slice(2)));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function
|
|
29
|
-
return
|
|
28
|
+
export function isPullRequestStage(stage: DeploymentStage): stage is `pr${number}` {
|
|
29
|
+
return stage.startsWith('pr');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function
|
|
33
|
-
const token =
|
|
32
|
+
export function stageDomain(stage: string, zone: string): string {
|
|
33
|
+
const token = parseDeploymentStage(stage);
|
|
34
34
|
if (!zone || zone.startsWith('.') || zone.endsWith('.')) {
|
|
35
35
|
throw new Error('Zone must be a non-empty DNS name without leading or trailing dots.');
|
|
36
36
|
}
|
|
37
37
|
return token === 'production' ? zone : `${token}.${zone}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function
|
|
41
|
-
const token =
|
|
40
|
+
export function stageResourceName(base: string, stage: string): string {
|
|
41
|
+
const token = parseDeploymentStage(stage);
|
|
42
42
|
if (!base) {
|
|
43
43
|
throw new Error('Resource base name must not be empty.');
|
|
44
44
|
}
|
|
45
45
|
return token === 'production' ? base : `${base}-${token}`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
49
|
-
const escaped =
|
|
48
|
+
export function hasExactStageSegment(value: string, stage: `pr${number}`): boolean {
|
|
49
|
+
const escaped = stage.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
50
50
|
return new RegExp(`(?:^|[-.])${escaped}(?=$|[-.])`).test(value);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -90,32 +90,29 @@ export interface PullRequestKvResource {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export interface PullRequestResourcePlan {
|
|
93
|
-
|
|
93
|
+
stage: `pr${number}`;
|
|
94
94
|
workerName: string;
|
|
95
95
|
workerBaseName: string;
|
|
96
96
|
kvNamespaces: PullRequestKvResource[];
|
|
97
97
|
r2Buckets: R2Binding[];
|
|
98
98
|
routes: Array<{ pattern: string; zoneName?: string; customDomain: boolean }>;
|
|
99
99
|
}
|
|
100
|
-
export interface
|
|
101
|
-
|
|
100
|
+
export interface ConfiguredStageResourcePlan {
|
|
101
|
+
stage: DeploymentStage;
|
|
102
102
|
workerName: string;
|
|
103
103
|
kvNamespaces: KvBinding[];
|
|
104
104
|
r2Buckets: R2Binding[];
|
|
105
105
|
routes: Array<{ pattern: string; zoneName?: string; customDomain: boolean }>;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export function
|
|
109
|
-
toml
|
|
110
|
-
environment: EnvironmentToken,
|
|
111
|
-
): ConfiguredEnvironmentResourcePlan {
|
|
112
|
-
const block = parseRoot(toml).env?.[environment];
|
|
108
|
+
export function planConfiguredStageResources(toml: string, stage: DeploymentStage): ConfiguredStageResourcePlan {
|
|
109
|
+
const block = parseRoot(toml).env?.[stage];
|
|
113
110
|
if (!isWranglerEnvironment(block)) {
|
|
114
|
-
throw new Error(`Wrangler configuration must declare [env.${
|
|
111
|
+
throw new Error(`Wrangler configuration must declare [env.${stage}].`);
|
|
115
112
|
}
|
|
116
|
-
const workerName = requiredString(block, 'name', `[env.${
|
|
113
|
+
const workerName = requiredString(block, 'name', `[env.${stage}]`);
|
|
117
114
|
return {
|
|
118
|
-
|
|
115
|
+
stage,
|
|
119
116
|
workerName,
|
|
120
117
|
kvNamespaces: readKvBindings(block.kv_namespaces),
|
|
121
118
|
r2Buckets: readRows(block.r2_buckets).map((row) => {
|
|
@@ -155,10 +152,10 @@ function stagingWorkerName(staging: WranglerEnvironment): { workerName: string;
|
|
|
155
152
|
|
|
156
153
|
export function planPullRequestResources(
|
|
157
154
|
toml: string,
|
|
158
|
-
|
|
155
|
+
stage: `pr${number}`,
|
|
159
156
|
liveNamespaces: LiveKvNamespace[],
|
|
160
157
|
): PullRequestResourcePlan {
|
|
161
|
-
|
|
158
|
+
parseDeploymentStage(stage);
|
|
162
159
|
const staging = stagingEnvironment(toml);
|
|
163
160
|
const { workerBaseName } = stagingWorkerName(staging);
|
|
164
161
|
const namespaceById = new Map(liveNamespaces.map((namespace) => [namespace.id, namespace]));
|
|
@@ -169,7 +166,7 @@ export function planPullRequestResources(
|
|
|
169
166
|
`Staging KV binding ${binding} references namespace ${id}, which is absent from the account listing.`,
|
|
170
167
|
);
|
|
171
168
|
}
|
|
172
|
-
const title = replaceExactToken(stagingNamespace.title, 'staging',
|
|
169
|
+
const title = replaceExactToken(stagingNamespace.title, 'staging', stage);
|
|
173
170
|
if (title === stagingNamespace.title) {
|
|
174
171
|
throw new Error(`Staging KV namespace title ${stagingNamespace.title} has no exact staging segment.`);
|
|
175
172
|
}
|
|
@@ -178,20 +175,20 @@ export function planPullRequestResources(
|
|
|
178
175
|
const r2Buckets = readRows(staging.r2_buckets).map((row) => {
|
|
179
176
|
const binding = requiredString(row, 'binding', 'R2 binding');
|
|
180
177
|
const stagingBucket = requiredString(row, 'bucket_name', `R2 binding ${binding}`);
|
|
181
|
-
const bucketName = replaceExactToken(stagingBucket, 'staging',
|
|
178
|
+
const bucketName = replaceExactToken(stagingBucket, 'staging', stage);
|
|
182
179
|
if (bucketName === stagingBucket) {
|
|
183
180
|
throw new Error(`Staging R2 bucket ${stagingBucket} has no exact staging segment.`);
|
|
184
181
|
}
|
|
185
182
|
return { binding, bucketName };
|
|
186
183
|
});
|
|
187
184
|
const routes = readRows(staging.routes).map((row) => ({
|
|
188
|
-
pattern: replaceHostnameLabel(requiredString(row, 'pattern', 'route'),
|
|
185
|
+
pattern: replaceHostnameLabel(requiredString(row, 'pattern', 'route'), stage),
|
|
189
186
|
...(typeof row.zone_name === 'string' ? { zoneName: row.zone_name } : {}),
|
|
190
187
|
customDomain: row.custom_domain === true,
|
|
191
188
|
}));
|
|
192
189
|
return {
|
|
193
|
-
|
|
194
|
-
workerName:
|
|
190
|
+
stage,
|
|
191
|
+
workerName: stageResourceName(workerBaseName, stage),
|
|
195
192
|
workerBaseName,
|
|
196
193
|
kvNamespaces,
|
|
197
194
|
r2Buckets,
|
|
@@ -200,20 +197,20 @@ export function planPullRequestResources(
|
|
|
200
197
|
}
|
|
201
198
|
|
|
202
199
|
export interface DerivePullRequestConfigOptions {
|
|
203
|
-
|
|
200
|
+
stage: `pr${number}`;
|
|
204
201
|
accountId: string;
|
|
205
202
|
kvNamespaceIds: ReadonlyMap<string, string>;
|
|
206
203
|
}
|
|
207
204
|
|
|
208
205
|
export function derivePullRequestWranglerConfig(toml: string, options: DerivePullRequestConfigOptions): string {
|
|
209
|
-
const
|
|
210
|
-
|
|
206
|
+
const stage = options.stage;
|
|
207
|
+
parseDeploymentStage(stage);
|
|
211
208
|
if (!options.accountId) {
|
|
212
209
|
throw new Error('Cloudflare account id is required to derive rate-limit namespaces.');
|
|
213
210
|
}
|
|
214
211
|
const staging = stagingEnvironment(toml);
|
|
215
212
|
const { workerBaseName } = stagingWorkerName(staging);
|
|
216
|
-
const cloned = cloneEnvBlock(toml, 'staging',
|
|
213
|
+
const cloned = cloneEnvBlock(toml, 'staging', stage);
|
|
217
214
|
const program = parseTOML(cloned);
|
|
218
215
|
const rootValue: unknown = getStaticTOMLValue(program);
|
|
219
216
|
if (!isWranglerRoot(rootValue)) {
|
|
@@ -223,7 +220,7 @@ export function derivePullRequestWranglerConfig(toml: string, options: DerivePul
|
|
|
223
220
|
const edits: Array<{ start: number; end: number; value: string }> = [];
|
|
224
221
|
|
|
225
222
|
for (const table of program.body[0].body) {
|
|
226
|
-
if (table.type !== 'TOMLTable' || table.resolvedKey[0] !== 'env' || table.resolvedKey[1] !==
|
|
223
|
+
if (table.type !== 'TOMLTable' || table.resolvedKey[0] !== 'env' || table.resolvedKey[1] !== stage) {
|
|
227
224
|
continue;
|
|
228
225
|
}
|
|
229
226
|
const tableValue = valueAtPath(root, table.resolvedKey);
|
|
@@ -238,7 +235,7 @@ export function derivePullRequestWranglerConfig(toml: string, options: DerivePul
|
|
|
238
235
|
tableValue,
|
|
239
236
|
key,
|
|
240
237
|
current,
|
|
241
|
-
|
|
238
|
+
stage,
|
|
242
239
|
workerBaseName,
|
|
243
240
|
options.accountId,
|
|
244
241
|
options.kvNamespaceIds,
|
|
@@ -262,29 +259,29 @@ function deriveFieldValue(
|
|
|
262
259
|
table: Record<string, unknown>,
|
|
263
260
|
key: string,
|
|
264
261
|
current: unknown,
|
|
265
|
-
|
|
262
|
+
stage: `pr${number}`,
|
|
266
263
|
workerBaseName: string,
|
|
267
264
|
accountId: string,
|
|
268
265
|
kvNamespaceIds: ReadonlyMap<string, string>,
|
|
269
266
|
): unknown {
|
|
270
267
|
const section = path[0];
|
|
271
268
|
if (path.length === 0 && key === 'name') {
|
|
272
|
-
return
|
|
269
|
+
return stageResourceName(workerBaseName, stage);
|
|
273
270
|
}
|
|
274
271
|
if (section === 'routes' && key === 'pattern' && typeof current === 'string') {
|
|
275
|
-
return replaceHostnameLabel(current,
|
|
272
|
+
return replaceHostnameLabel(current, stage);
|
|
276
273
|
}
|
|
277
274
|
if (section === 'vars' && typeof current === 'string') {
|
|
278
275
|
if (key === 'ENVIRONMENT') {
|
|
279
|
-
return
|
|
276
|
+
return stage;
|
|
280
277
|
}
|
|
281
278
|
if (key === 'AUTH_KEYS_INSTANCE_NAME') {
|
|
282
|
-
return replaceExactToken(current, 'staging',
|
|
279
|
+
return replaceExactToken(current, 'staging', stage);
|
|
283
280
|
}
|
|
284
|
-
return replaceHostnameLabel(current,
|
|
281
|
+
return replaceHostnameLabel(current, stage);
|
|
285
282
|
}
|
|
286
283
|
if (section === 'send_email' && key === 'allowed_sender_addresses' && Array.isArray(current)) {
|
|
287
|
-
return current.map((value) => (typeof value === 'string' ? replaceHostnameLabel(value,
|
|
284
|
+
return current.map((value) => (typeof value === 'string' ? replaceHostnameLabel(value, stage) : value));
|
|
288
285
|
}
|
|
289
286
|
if (section === 'kv_namespaces' && key === 'id' && typeof current === 'string') {
|
|
290
287
|
const derived = kvNamespaceIds.get(current);
|
|
@@ -294,11 +291,11 @@ function deriveFieldValue(
|
|
|
294
291
|
return derived;
|
|
295
292
|
}
|
|
296
293
|
if (section === 'r2_buckets' && key === 'bucket_name' && typeof current === 'string') {
|
|
297
|
-
return replaceExactToken(current, 'staging',
|
|
294
|
+
return replaceExactToken(current, 'staging', stage);
|
|
298
295
|
}
|
|
299
296
|
if (section === 'ratelimits' && key === 'namespace_id') {
|
|
300
297
|
const bindingName = requiredString(table, 'name', 'Rate-limit binding');
|
|
301
|
-
return rateLimitNamespaceId(accountId, workerBaseName,
|
|
298
|
+
return rateLimitNamespaceId(accountId, workerBaseName, stage, bindingName);
|
|
302
299
|
}
|
|
303
300
|
return current;
|
|
304
301
|
}
|
|
@@ -306,17 +303,17 @@ function deriveFieldValue(
|
|
|
306
303
|
export function rateLimitNamespaceId(
|
|
307
304
|
accountId: string,
|
|
308
305
|
workerBaseName: string,
|
|
309
|
-
|
|
306
|
+
stage: string,
|
|
310
307
|
bindingName: string,
|
|
311
308
|
): string {
|
|
312
|
-
const token =
|
|
309
|
+
const token = parseDeploymentStage(stage);
|
|
313
310
|
const digest = createHash('sha256').update(`${accountId}:${workerBaseName}:${token}:${bindingName}`).digest();
|
|
314
311
|
const value = digest.readUInt32BE(0) & 0x7fff_ffff;
|
|
315
312
|
return String(value === 0 ? 1 : value);
|
|
316
313
|
}
|
|
317
314
|
|
|
318
|
-
function replaceHostnameLabel(value: string,
|
|
319
|
-
return value.replace(/(^|[.@/])staging(?=\.)/g, `$1${
|
|
315
|
+
function replaceHostnameLabel(value: string, stage: `pr${number}`): string {
|
|
316
|
+
return value.replace(/(^|[.@/])staging(?=\.)/g, `$1${stage}`);
|
|
320
317
|
}
|
|
321
318
|
|
|
322
319
|
function replaceExactToken(value: string, from: string, to: string): string {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-environment.d.ts","sourceRoot":"","sources":["../../src/wrangler/deploy-environment.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAGL,KAAK,gBAAgB,EAQtB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACtH;AAED,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,GAAG,CACP,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACrD,OAAO,CAAC,aAAa,CAAC,CAcxB;CACF;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,kBAAkB,CAAC;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,gBAAgB,EAAE,MAAM,EACxB,YAAY,GAAE,2BAAgC,GAC7C,OAAO,CAAC,uBAAuB,CAAC,CA2HlC;AACD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,YAAY,GAAE,2BAAgC,GAC7C,OAAO,CAAC,aAAa,CAAC,CA2DxB;AAwFD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS,CAgBnF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7E;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAelF"}
|
|
@@ -1,58 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|