@smoothbricks/cli 0.11.18 → 0.11.20
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/README.md +91 -11
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +28 -4
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +6 -20
- package/dist/lib/deploy-tags.d.ts +17 -3
- package/dist/lib/deploy-tags.d.ts.map +1 -1
- package/dist/lib/deploy-tags.js +23 -4
- package/dist/lib/json.d.ts +22 -1
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +15 -3
- package/dist/monorepo/cargo-policy.d.ts +17 -0
- package/dist/monorepo/cargo-policy.d.ts.map +1 -1
- package/dist/monorepo/cargo-policy.js +73 -1
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +4 -2
- package/dist/monorepo/managed-files.d.ts +5 -2
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +35 -21
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +6 -1
- package/dist/nx/index.d.ts +6 -0
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +14 -0
- package/dist/secrets/commands.d.ts +9 -5
- package/dist/secrets/commands.d.ts.map +1 -1
- package/dist/secrets/commands.js +122 -49
- package/dist/secrets/index.d.ts +42 -56
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +52 -79
- package/dist/secrets/resolver.d.ts +59 -0
- package/dist/secrets/resolver.d.ts.map +1 -0
- package/dist/secrets/resolver.js +100 -0
- package/dist/secrets/run.d.ts +23 -0
- package/dist/secrets/run.d.ts.map +1 -0
- package/dist/secrets/run.js +130 -0
- package/dist/secrets/status.d.ts +177 -0
- package/dist/secrets/status.d.ts.map +1 -0
- package/dist/secrets/status.js +724 -0
- package/dist/wrangler/deploy-stage.d.ts +1 -1
- package/dist/wrangler/deploy-stage.d.ts.map +1 -1
- package/dist/wrangler/deploy-stage.js +22 -20
- package/dist/wrangler/deployed-version.d.ts.map +1 -1
- package/dist/wrangler/deployed-version.js +7 -12
- package/dist/wrangler/flat-config.d.ts +1 -4
- package/dist/wrangler/flat-config.d.ts.map +1 -1
- package/dist/wrangler/flat-config.js +98 -44
- package/dist/wrangler/prepare-env.d.ts +4 -3
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +7 -5
- package/dist/wrangler/source-config.d.ts +24 -0
- package/dist/wrangler/source-config.d.ts.map +1 -0
- package/dist/wrangler/source-config.js +110 -0
- package/dist/wrangler/stage.d.ts +55 -23
- package/dist/wrangler/stage.d.ts.map +1 -1
- package/dist/wrangler/stage.js +81 -162
- package/managed/raw/tooling/direnv/devenv.smoo.nix +19 -3
- package/managed/raw/tooling/direnv/secret-references.ts +280 -76
- package/managed/raw/tooling/direnv/setup-environment.ts +58 -2
- package/managed/raw/tsconfig.lib.json +28 -0
- package/package.json +7 -2
- package/src/cli.ts +34 -5
- package/src/github-ci/index.test.ts +6 -7
- package/src/github-ci/index.ts +11 -19
- package/src/lib/deploy-tags.ts +22 -4
- package/src/lib/json.ts +23 -1
- package/src/monorepo/cargo-policy.test.ts +91 -1
- package/src/monorepo/cargo-policy.ts +87 -1
- package/src/monorepo/index.ts +8 -2
- package/src/monorepo/managed-files.test.ts +69 -37
- package/src/monorepo/managed-files.ts +38 -21
- package/src/monorepo/packs/index.ts +10 -1
- package/src/monorepo/secret-references.test.ts +412 -6
- package/src/monorepo/setup-environment.test.ts +181 -0
- package/src/nx/index.test.ts +8 -0
- package/src/nx/index.ts +20 -0
- package/src/secrets/commands.test.ts +186 -4
- package/src/secrets/commands.ts +142 -51
- package/src/secrets/index.test.ts +4 -10
- package/src/secrets/index.ts +54 -115
- package/src/secrets/resolver.ts +130 -0
- package/src/secrets/run.test.ts +359 -0
- package/src/secrets/run.ts +148 -0
- package/src/secrets/status.test.ts +164 -0
- package/src/secrets/status.ts +297 -0
- package/src/wrangler/deploy-stage.test.ts +95 -2
- package/src/wrangler/deploy-stage.ts +26 -23
- package/src/wrangler/deployed-version.ts +7 -11
- package/src/wrangler/flat-config.test.ts +9 -4
- package/src/wrangler/flat-config.ts +1 -20
- package/src/wrangler/format-parity.test.ts +433 -0
- package/src/wrangler/prepare-env.ts +7 -5
- package/src/wrangler/source-config.test.ts +102 -0
- package/src/wrangler/source-config.ts +110 -0
- package/src/wrangler/stage.test.ts +61 -32
- package/src/wrangler/stage.ts +124 -173
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { parseWranglerConfig } from './source-config.js';
|
|
2
3
|
import {
|
|
4
|
+
derivePullRequestDocument,
|
|
3
5
|
derivePullRequestStageConfig,
|
|
4
|
-
derivePullRequestWranglerConfig,
|
|
5
6
|
isPullRequestStage,
|
|
6
7
|
planPullRequestBindings,
|
|
7
8
|
planPullRequestResources,
|
|
@@ -9,8 +10,14 @@ import {
|
|
|
9
10
|
rateLimitNamespaceId,
|
|
10
11
|
stageDomain,
|
|
11
12
|
stageResourceName,
|
|
13
|
+
type WranglerDocument,
|
|
12
14
|
} from './stage.js';
|
|
13
15
|
|
|
16
|
+
/** The fixtures below are TOML; the derivation only ever sees the model a parser produces. */
|
|
17
|
+
function document(toml: string): WranglerDocument {
|
|
18
|
+
return parseWranglerConfig('wrangler.toml', toml, 'toml');
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
const APP_FIXTURE = `name = "app"
|
|
15
22
|
compatibility_date = "2026-05-06"
|
|
16
23
|
|
|
@@ -133,42 +140,50 @@ describe('Wrangler deployment stage convention', () => {
|
|
|
133
140
|
});
|
|
134
141
|
|
|
135
142
|
it('derives the app staging block without changing inherited/static semantics', () => {
|
|
136
|
-
const
|
|
143
|
+
const source = document(APP_FIXTURE);
|
|
144
|
+
const derived = derivePullRequestDocument(source, {
|
|
137
145
|
stage: 'pr123',
|
|
138
146
|
accountId: 'account-1',
|
|
139
147
|
kvNamespaceIds: new Map<string, string>(),
|
|
140
148
|
d1DatabaseIds: new Map<string, string>(),
|
|
141
149
|
});
|
|
142
150
|
|
|
143
|
-
expect(derived
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
expect(derived.env?.pr123).toEqual({
|
|
152
|
+
name: 'app-pr123',
|
|
153
|
+
workers_dev: false,
|
|
154
|
+
assets: { directory: './dist', not_found_handling: 'single-page-application' },
|
|
155
|
+
routes: [
|
|
156
|
+
{ pattern: '*.pr123.example.test/*', zone_name: 'example.test' },
|
|
157
|
+
{ pattern: 'pr123.example.test', custom_domain: true },
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
// Inherited top-level keys and the staging block the stage was derived from are untouched.
|
|
161
|
+
expect(derived.compatibility_date).toBe('2026-05-06');
|
|
162
|
+
expect(derived.env?.staging).toEqual(source.env?.staging);
|
|
163
|
+
expect(JSON.stringify(derived)).not.toContain('pr456');
|
|
150
164
|
});
|
|
151
165
|
|
|
152
|
-
it('refuses an all-pinned
|
|
153
|
-
const pinned = `name = "app"
|
|
166
|
+
it('refuses an all-pinned template before any resource is named', () => {
|
|
167
|
+
const pinned = document(`name = "app"
|
|
154
168
|
[env.staging]
|
|
155
169
|
name = "app-staging"
|
|
156
170
|
[[env.staging.routes]]
|
|
157
171
|
pattern = "next.example.test"
|
|
158
172
|
custom_domain = true
|
|
159
|
-
|
|
173
|
+
`);
|
|
160
174
|
expect(() => planPullRequestResources(pinned, 'pr123', [])).toThrow(/pinned/);
|
|
161
175
|
expect(() => planPullRequestResources(pinned, 'pr123', [])).toThrow(/next\.example\.test/);
|
|
162
176
|
});
|
|
163
177
|
|
|
164
178
|
it('derives backend resources from staging while preserving provider and DO identities', () => {
|
|
165
|
-
const
|
|
179
|
+
const source = document(BACKEND_FIXTURE);
|
|
180
|
+
const plan = planPullRequestResources(source, 'pr123', LIVE_NAMESPACES);
|
|
166
181
|
expect(plan.workerName).toBe('app-backend-pr123');
|
|
167
182
|
expect(plan.kvNamespaces.map(({ title }) => title)).toEqual(['alias-index-pr123', 'org-profiles-pr123']);
|
|
168
183
|
expect(plan.d1Databases).toEqual([{ binding: 'DB', stagingId: 'd1-staging-id', name: 'app-pr123-db' }]);
|
|
169
184
|
expect(plan.r2Buckets).toEqual([{ binding: 'MEDIA', bucketName: 'app-media-pr123' }]);
|
|
170
185
|
|
|
171
|
-
const derived =
|
|
186
|
+
const derived = derivePullRequestDocument(source, {
|
|
172
187
|
stage: 'pr123',
|
|
173
188
|
accountId: 'account-1',
|
|
174
189
|
kvNamespaceIds: DERIVED_IDS,
|
|
@@ -180,24 +195,38 @@ custom_domain = true
|
|
|
180
195
|
expect(Number(signupId)).toBeGreaterThan(0);
|
|
181
196
|
expect(Number(signupId)).toBeLessThanOrEqual(0x7fff_ffff);
|
|
182
197
|
expect(loginId).not.toBe(signupId);
|
|
183
|
-
expect(derived).
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
expect(derived.env?.pr123).toEqual({
|
|
199
|
+
name: 'app-backend-pr123',
|
|
200
|
+
workers_dev: false,
|
|
201
|
+
routes: [{ pattern: '*.pr123.example.test/auth/*', zone_name: 'example.test' }],
|
|
202
|
+
durable_objects: { bindings: [{ name: 'QUEUE', class_name: 'QueueDO' }] },
|
|
203
|
+
kv_namespaces: [
|
|
204
|
+
{ binding: 'ALIAS_INDEX', id: 'kv-alias-pr123-id' },
|
|
205
|
+
{ binding: 'ORG_PROFILES', id: 'kv-org-pr123-id' },
|
|
206
|
+
],
|
|
207
|
+
d1_databases: [{ binding: 'DB', database_name: 'app-pr123-db', database_id: 'd1-pr123-id' }],
|
|
208
|
+
services: [
|
|
209
|
+
{ binding: 'BACKEND', service: 'app-backend-pr123' },
|
|
210
|
+
{ binding: 'SHARED', service: 'auth-service' },
|
|
211
|
+
],
|
|
212
|
+
send_email: [{ name: 'EMAIL', allowed_sender_addresses: ['login@mail.pr123.example.test'] }],
|
|
213
|
+
ratelimits: [
|
|
214
|
+
{ name: 'SIGNUP', namespace_id: signupId, simple: { limit: 5, period: 60 } },
|
|
215
|
+
{ name: 'LOGIN', namespace_id: loginId, simple: { limit: 20, period: 60 } },
|
|
216
|
+
],
|
|
217
|
+
r2_buckets: [{ binding: 'MEDIA', bucket_name: 'app-media-pr123' }],
|
|
218
|
+
vars: {
|
|
219
|
+
ENVIRONMENT: 'pr123',
|
|
220
|
+
TLD_DOMAIN: 'pr123.example.test',
|
|
221
|
+
BUILD_ID: 'staging-20260716-2',
|
|
222
|
+
MAIL_RECIPIENTS: 'login-test@pr123.example.test,invite-test@pr123.example.test',
|
|
223
|
+
EMAIL_FROM_ADDRESS: 'login@mail.pr123.example.test',
|
|
224
|
+
APP_ORIGIN: 'https://app.pr123.example.test',
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
// The Durable Object migrations are a top-level, stage-independent declaration.
|
|
228
|
+
expect(derived.migrations).toEqual([{ tag: 'v1', new_sqlite_classes: ['QueueDO', 'StoreDO'] }]);
|
|
229
|
+
expect(JSON.stringify(derived)).not.toContain('pr456');
|
|
201
230
|
});
|
|
202
231
|
|
|
203
232
|
it('refuses unlabelled mutable KV, R2 and D1 before a pull-request stage can share them', () => {
|
package/src/wrangler/stage.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { getStaticTOMLValue, parseTOML } from 'toml-eslint-parser';
|
|
3
2
|
import typia from 'typia';
|
|
4
|
-
import { cloneEnvBlock } from './prepare-env.js';
|
|
5
3
|
import { derivedStagingName, hasStageLabel, replaceExactToken, replaceHostnameLabel } from './stage-labels.js';
|
|
6
4
|
|
|
7
5
|
export type DeploymentStage = 'staging' | 'production' | `pr${number}`;
|
|
@@ -53,23 +51,25 @@ export function hasExactStageSegment(value: string, stage: `pr${number}`): boole
|
|
|
53
51
|
return new RegExp(`(?:^|[-.])${escaped}(?=$|[-.])`).test(value);
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
/**
|
|
55
|
+
* A whole Wrangler configuration as data — the one model both source formats parse into
|
|
56
|
+
* (`source-config.ts` owns the parsers). Nothing in this module reads configuration text, which
|
|
57
|
+
* is what makes a stage derive identically from `wrangler.toml` and from `wrangler.jsonc`.
|
|
58
|
+
*/
|
|
59
|
+
export interface WranglerDocument {
|
|
57
60
|
env?: Record<string, WranglerEnvironment | undefined>;
|
|
61
|
+
[key: string]: unknown;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
d1_databases?: unknown;
|
|
66
|
-
services?: unknown;
|
|
67
|
-
ratelimits?: unknown;
|
|
68
|
-
vars?: unknown;
|
|
69
|
-
send_email?: unknown;
|
|
64
|
+
/**
|
|
65
|
+
* One `env` block, still unparsed. `StageConfigFields` is the typed statement of what this
|
|
66
|
+
* module reads out of it; every other key is carried into a derived stage, never interpreted.
|
|
67
|
+
*/
|
|
68
|
+
export interface WranglerEnvironment {
|
|
70
69
|
[key: string]: unknown;
|
|
71
70
|
}
|
|
72
71
|
|
|
72
|
+
/** One reconciled binding of the plan: exactly what Cloudflare is asked about, and nothing else. */
|
|
73
73
|
export interface KvBinding {
|
|
74
74
|
binding: string;
|
|
75
75
|
id: string;
|
|
@@ -79,23 +79,27 @@ export interface R2Binding {
|
|
|
79
79
|
bucketName: string;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
routes?: StageRoute[];
|
|
86
|
-
vars?: Record<string, unknown>;
|
|
87
|
-
kv_namespaces?: KvBinding[];
|
|
88
|
-
r2_buckets?: { binding: string; bucket_name: string }[];
|
|
89
|
-
d1_databases?: StageD1Binding[];
|
|
90
|
-
services?: StageServiceBinding[];
|
|
91
|
-
ratelimits?: StageRateLimit[];
|
|
92
|
-
send_email?: Array<{ allowed_sender_addresses?: unknown; [key: string]: unknown }>;
|
|
93
|
-
}
|
|
82
|
+
// Configuration rows, as opposed to plan rows: the fields the derivation proves and rewrites,
|
|
83
|
+
// plus every other key of the row carried verbatim. A route's `zone_id`, a KV binding's
|
|
84
|
+
// `preview_id` and anything Cloudflare adds next survive into the derived config untouched.
|
|
94
85
|
|
|
95
86
|
export interface StageRoute {
|
|
96
87
|
pattern: string;
|
|
97
88
|
zone_name?: string;
|
|
98
89
|
custom_domain?: boolean;
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface StageKvBinding {
|
|
94
|
+
binding: string;
|
|
95
|
+
id: string;
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface StageR2Binding {
|
|
100
|
+
binding: string;
|
|
101
|
+
bucket_name: string;
|
|
102
|
+
[key: string]: unknown;
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
export interface StageD1Binding {
|
|
@@ -103,24 +107,40 @@ export interface StageD1Binding {
|
|
|
103
107
|
database_name: string;
|
|
104
108
|
database_id: string;
|
|
105
109
|
migrations_dir?: string;
|
|
110
|
+
[key: string]: unknown;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
export interface StageServiceBinding {
|
|
109
114
|
binding: string;
|
|
110
115
|
service: string;
|
|
111
116
|
environment?: string;
|
|
117
|
+
[key: string]: unknown;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
export interface StageRateLimit {
|
|
115
121
|
name: string;
|
|
116
122
|
namespace_id: string;
|
|
117
|
-
|
|
123
|
+
[key: string]: unknown;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One-stage bindings that an env block and a flat JSON config both present. */
|
|
127
|
+
export interface StageConfigFields {
|
|
128
|
+
name: string;
|
|
129
|
+
routes?: StageRoute[];
|
|
130
|
+
vars?: Record<string, unknown>;
|
|
131
|
+
kv_namespaces?: StageKvBinding[];
|
|
132
|
+
r2_buckets?: StageR2Binding[];
|
|
133
|
+
d1_databases?: StageD1Binding[];
|
|
134
|
+
services?: StageServiceBinding[];
|
|
135
|
+
ratelimits?: StageRateLimit[];
|
|
136
|
+
send_email?: Record<string, unknown>[];
|
|
137
|
+
[key: string]: unknown;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
const isWranglerRoot = typia.createIs<WranglerRoot>();
|
|
121
140
|
const isWranglerEnvironment = typia.createIs<WranglerEnvironment>();
|
|
122
141
|
const isUnknownRecord = typia.createIs<Record<string, unknown>>();
|
|
123
142
|
const isUnknownRows = typia.createIs<Record<string, unknown>[]>();
|
|
143
|
+
const isRouteRows = typia.createIs<Array<string | Record<string, unknown>>>();
|
|
124
144
|
|
|
125
145
|
export interface LiveKvNamespace {
|
|
126
146
|
id: string;
|
|
@@ -157,35 +177,53 @@ export interface ConfiguredStageResourcePlan {
|
|
|
157
177
|
routes: Array<{ pattern: string; zoneName?: string; customDomain: boolean }>;
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!isWranglerEnvironment(block)) {
|
|
163
|
-
throw new Error(`Wrangler configuration must declare [env.${stage}].`);
|
|
164
|
-
}
|
|
165
|
-
const workerName = requiredString(block, 'name', `[env.${stage}]`);
|
|
180
|
+
/** The reconcile plan (`reconcileStageResources`) read off one resolved stage's bindings. */
|
|
181
|
+
export function planStageResources(config: StageConfigFields, stage: DeploymentStage): ConfiguredStageResourcePlan {
|
|
166
182
|
return {
|
|
167
183
|
stage,
|
|
168
|
-
workerName,
|
|
169
|
-
kvNamespaces:
|
|
170
|
-
r2Buckets:
|
|
171
|
-
routes:
|
|
184
|
+
workerName: config.name,
|
|
185
|
+
kvNamespaces: (config.kv_namespaces ?? []).map(({ binding, id }) => ({ binding, id })),
|
|
186
|
+
r2Buckets: (config.r2_buckets ?? []).map((bucket) => ({ binding: bucket.binding, bucketName: bucket.bucket_name })),
|
|
187
|
+
routes: (config.routes ?? []).map((route) => ({
|
|
188
|
+
pattern: route.pattern,
|
|
189
|
+
...(typeof route.zone_name === 'string' ? { zoneName: route.zone_name } : {}),
|
|
190
|
+
customDomain: route.custom_domain === true,
|
|
191
|
+
})),
|
|
172
192
|
};
|
|
173
193
|
}
|
|
174
194
|
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return value;
|
|
195
|
+
export function planConfiguredStageResources(
|
|
196
|
+
document: WranglerDocument,
|
|
197
|
+
stage: DeploymentStage,
|
|
198
|
+
): ConfiguredStageResourcePlan {
|
|
199
|
+
return planStageResources(stageEnvironmentConfig(document, stage), stage);
|
|
181
200
|
}
|
|
182
201
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
202
|
+
/** The `env.<stage>` block as the stage model, or the refusal that it is not declared. */
|
|
203
|
+
function stageEnvironmentConfig(document: WranglerDocument, stage: DeploymentStage): StageConfigFields {
|
|
204
|
+
const block = document.env?.[stage];
|
|
205
|
+
if (!isWranglerEnvironment(block)) {
|
|
206
|
+
throw new Error(`Wrangler configuration must declare [env.${stage}].`);
|
|
187
207
|
}
|
|
188
|
-
return
|
|
208
|
+
return environmentStageConfig(block, `[env.${stage}]`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* One env block as the stage model: every field the derivation reads proven present and typed,
|
|
213
|
+
* every other key carried verbatim. A field absent from the source stays absent, so the same
|
|
214
|
+
* worker written as TOML and as JSONC produces the same model down to the key set.
|
|
215
|
+
*/
|
|
216
|
+
function environmentStageConfig(environment: WranglerEnvironment, label: string): StageConfigFields {
|
|
217
|
+
const config: StageConfigFields = { ...environment, name: requiredString(environment, 'name', label) };
|
|
218
|
+
if (environment.routes !== undefined) config.routes = readStageRoutes(environment.routes);
|
|
219
|
+
if (isUnknownRecord(environment.vars)) config.vars = environment.vars;
|
|
220
|
+
if (environment.kv_namespaces !== undefined) config.kv_namespaces = readKvBindings(environment.kv_namespaces);
|
|
221
|
+
if (environment.r2_buckets !== undefined) config.r2_buckets = readR2Buckets(environment.r2_buckets);
|
|
222
|
+
if (environment.d1_databases !== undefined) config.d1_databases = readD1Bindings(environment.d1_databases);
|
|
223
|
+
if (environment.services !== undefined) config.services = readServices(environment.services);
|
|
224
|
+
if (environment.ratelimits !== undefined) config.ratelimits = readRateLimits(environment.ratelimits);
|
|
225
|
+
if (environment.send_email !== undefined) config.send_email = readRows(environment.send_email);
|
|
226
|
+
return config;
|
|
189
227
|
}
|
|
190
228
|
|
|
191
229
|
/** The staging worker name without its required `-staging` suffix. */
|
|
@@ -196,13 +234,6 @@ export function stagingWorkerBaseName(name: string): string {
|
|
|
196
234
|
return name.slice(0, -'-staging'.length);
|
|
197
235
|
}
|
|
198
236
|
|
|
199
|
-
function stagingWorkerName(staging: WranglerEnvironment): { workerName: string; workerBaseName: string } {
|
|
200
|
-
if (typeof staging.name !== 'string' || !staging.name) {
|
|
201
|
-
throw new Error('[env.staging] must declare a non-empty name.');
|
|
202
|
-
}
|
|
203
|
-
return { workerName: staging.name, workerBaseName: stagingWorkerBaseName(staging.name) };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
237
|
/**
|
|
207
238
|
* A template whose routes are all pinned (no derivable stage label) cannot serve a
|
|
208
239
|
* pull-request stage: it would deploy unrouted and Wrangler would expose it on workers.dev.
|
|
@@ -216,34 +247,15 @@ export function assertPullRequestRoutable(name: string, routes: StageRoute[] | u
|
|
|
216
247
|
);
|
|
217
248
|
}
|
|
218
249
|
|
|
219
|
-
function stagingStageConfig(toml: string): StageConfigFields {
|
|
220
|
-
const staging = stagingEnvironment(toml);
|
|
221
|
-
const { workerName } = stagingWorkerName(staging);
|
|
222
|
-
return {
|
|
223
|
-
name: workerName,
|
|
224
|
-
routes: readStageRoutes(staging.routes),
|
|
225
|
-
...(isUnknownRecord(staging.vars) ? { vars: staging.vars } : {}),
|
|
226
|
-
kv_namespaces: readKvBindings(staging.kv_namespaces),
|
|
227
|
-
r2_buckets: readRows(staging.r2_buckets).map((row) => {
|
|
228
|
-
const binding = requiredString(row, 'binding', 'R2 binding');
|
|
229
|
-
return { binding, bucket_name: requiredString(row, 'bucket_name', `R2 binding ${binding}`) };
|
|
230
|
-
}),
|
|
231
|
-
d1_databases: readD1Bindings(staging.d1_databases),
|
|
232
|
-
services: readServices(staging.services),
|
|
233
|
-
ratelimits: readRateLimits(staging.ratelimits),
|
|
234
|
-
send_email: readRows(staging.send_email),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
250
|
export function planPullRequestResources(
|
|
239
|
-
|
|
251
|
+
document: WranglerDocument,
|
|
240
252
|
stage: `pr${number}`,
|
|
241
253
|
liveNamespaces: LiveKvNamespace[],
|
|
242
254
|
): PullRequestResourcePlan {
|
|
243
|
-
return planPullRequestBindings(
|
|
255
|
+
return planPullRequestBindings(stageEnvironmentConfig(document, 'staging'), stage, liveNamespaces);
|
|
244
256
|
}
|
|
245
257
|
|
|
246
|
-
/** KV/R2/D1 isolation and route checks for a pull-request stage, shared by
|
|
258
|
+
/** KV/R2/D1 isolation and route checks for a pull-request stage, shared by every source format. */
|
|
247
259
|
export function planPullRequestBindings(
|
|
248
260
|
config: StageConfigFields,
|
|
249
261
|
stage: `pr${number}`,
|
|
@@ -454,50 +466,22 @@ function requiredDerivedString(value: unknown): string {
|
|
|
454
466
|
return value;
|
|
455
467
|
}
|
|
456
468
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
const tableValue = valueAtPath(root, table.resolvedKey);
|
|
473
|
-
if (!isUnknownRecord(tableValue)) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
const section = typeof table.resolvedKey[2] === 'string' ? table.resolvedKey[2] : undefined;
|
|
477
|
-
if (section === 'routes') {
|
|
478
|
-
const pattern = tableValue.pattern;
|
|
479
|
-
if (typeof pattern === 'string' && !hasStageLabel(pattern)) {
|
|
480
|
-
edits.push({ start: table.range[0], end: table.range[1], value: '' });
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
const nearbyName = typeof tableValue.name === 'string' ? tableValue.name : undefined;
|
|
485
|
-
for (const keyValue of table.body) {
|
|
486
|
-
const key = cloned.slice(keyValue.key.range[0], keyValue.key.range[1]).trim();
|
|
487
|
-
const current = tableValue[key];
|
|
488
|
-
const next = deriveStageField(section, key, current, ctx, nearbyName);
|
|
489
|
-
if (next !== current) {
|
|
490
|
-
edits.push({ start: keyValue.value.range[0], end: keyValue.value.range[1], value: tomlLiteral(next) });
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
let derived = cloned;
|
|
496
|
-
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
497
|
-
derived = derived.slice(0, edit.start) + edit.value + derived.slice(edit.end);
|
|
469
|
+
/**
|
|
470
|
+
* The whole document with `env.<prN>` added, derived from `env.staging`. Nothing read from disk
|
|
471
|
+
* is mutated — every level the derivation touches is rebuilt — so the committed source config is
|
|
472
|
+
* still exactly what the repo wrote once the deploy is done with it.
|
|
473
|
+
*/
|
|
474
|
+
export function derivePullRequestDocument(
|
|
475
|
+
document: WranglerDocument,
|
|
476
|
+
options: DerivePullRequestConfigOptions,
|
|
477
|
+
): WranglerDocument {
|
|
478
|
+
if (document.env?.[options.stage] !== undefined) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Wrangler configuration already declares [env.${options.stage}]; a pull-request stage is derived from [env.staging] and must not be committed.`,
|
|
481
|
+
);
|
|
498
482
|
}
|
|
499
|
-
|
|
500
|
-
return derived;
|
|
483
|
+
const derived = derivePullRequestStageConfig(stageEnvironmentConfig(document, 'staging'), options);
|
|
484
|
+
return { ...document, env: { ...document.env, [options.stage]: derived } };
|
|
501
485
|
}
|
|
502
486
|
|
|
503
487
|
export function rateLimitNamespaceId(
|
|
@@ -512,70 +496,59 @@ export function rateLimitNamespaceId(
|
|
|
512
496
|
return String(value === 0 ? 1 : value);
|
|
513
497
|
}
|
|
514
498
|
|
|
515
|
-
|
|
499
|
+
/**
|
|
500
|
+
* Wrangler accepts a route as an object or as a bare pattern string. Both become the object form
|
|
501
|
+
* here, so one derivation rewrites the hostname label either way — a string route used to be
|
|
502
|
+
* carried into the pull-request stage verbatim, pointing it at staging's own hostname.
|
|
503
|
+
*/
|
|
504
|
+
function readStageRoutes(value: unknown): StageRoute[] {
|
|
505
|
+
if (!isRouteRows(value)) return [];
|
|
506
|
+
return value.map((row) =>
|
|
507
|
+
typeof row === 'string' ? { pattern: row } : { ...row, pattern: requiredString(row, 'pattern', 'route') },
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function readKvBindings(value: unknown): StageKvBinding[] {
|
|
516
512
|
return readRows(value).map((row) => ({
|
|
513
|
+
...row,
|
|
517
514
|
binding: requiredString(row, 'binding', 'KV namespace'),
|
|
518
515
|
id: requiredString(row, 'id', 'KV namespace'),
|
|
519
516
|
}));
|
|
520
517
|
}
|
|
521
518
|
|
|
522
|
-
function
|
|
519
|
+
function readR2Buckets(value: unknown): StageR2Binding[] {
|
|
523
520
|
return readRows(value).map((row) => {
|
|
524
521
|
const binding = requiredString(row, 'binding', 'R2 binding');
|
|
525
|
-
return { binding,
|
|
522
|
+
return { ...row, binding, bucket_name: requiredString(row, 'bucket_name', `R2 binding ${binding}`) };
|
|
526
523
|
});
|
|
527
524
|
}
|
|
528
525
|
|
|
529
526
|
function readD1Bindings(value: unknown): StageD1Binding[] {
|
|
530
527
|
return readRows(value).map((row) => {
|
|
531
528
|
const binding = requiredString(row, 'binding', 'D1 database');
|
|
532
|
-
|
|
529
|
+
return {
|
|
530
|
+
...row,
|
|
533
531
|
binding,
|
|
534
532
|
database_name: requiredString(row, 'database_name', `D1 binding ${binding}`),
|
|
535
533
|
database_id: requiredString(row, 'database_id', `D1 binding ${binding}`),
|
|
536
534
|
};
|
|
537
|
-
return typeof row.migrations_dir === 'string' ? { ...database, migrations_dir: row.migrations_dir } : database;
|
|
538
535
|
});
|
|
539
536
|
}
|
|
540
537
|
|
|
541
538
|
function readServices(value: unknown): StageServiceBinding[] {
|
|
542
539
|
return readRows(value).map((row) => {
|
|
543
540
|
const binding = requiredString(row, 'binding', 'Service binding');
|
|
544
|
-
|
|
545
|
-
binding,
|
|
546
|
-
service: requiredString(row, 'service', `Service binding ${binding}`),
|
|
547
|
-
};
|
|
548
|
-
return typeof row.environment === 'string' ? { ...service, environment: row.environment } : service;
|
|
541
|
+
return { ...row, binding, service: requiredString(row, 'service', `Service binding ${binding}`) };
|
|
549
542
|
});
|
|
550
543
|
}
|
|
551
544
|
|
|
552
545
|
function readRateLimits(value: unknown): StageRateLimit[] {
|
|
553
546
|
return readRows(value).map((row) => {
|
|
554
547
|
const name = requiredString(row, 'name', 'Rate-limit binding');
|
|
555
|
-
|
|
556
|
-
name,
|
|
557
|
-
namespace_id: requiredString(row, 'namespace_id', `Rate-limit binding ${name}`),
|
|
558
|
-
};
|
|
559
|
-
return row.simple === undefined ? limit : { ...limit, simple: row.simple };
|
|
548
|
+
return { ...row, name, namespace_id: requiredString(row, 'namespace_id', `Rate-limit binding ${name}`) };
|
|
560
549
|
});
|
|
561
550
|
}
|
|
562
551
|
|
|
563
|
-
function readStageRoutes(value: unknown): StageRoute[] {
|
|
564
|
-
return readRows(value).map((row) => ({
|
|
565
|
-
pattern: requiredString(row, 'pattern', 'route'),
|
|
566
|
-
...(typeof row.zone_name === 'string' ? { zone_name: row.zone_name } : {}),
|
|
567
|
-
...(row.custom_domain === true ? { custom_domain: true } : {}),
|
|
568
|
-
}));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function readRoutes(value: unknown): Array<{ pattern: string; zoneName?: string; customDomain: boolean }> {
|
|
572
|
-
return readRows(value).map((row) => ({
|
|
573
|
-
pattern: requiredString(row, 'pattern', 'route'),
|
|
574
|
-
...(typeof row.zone_name === 'string' ? { zoneName: row.zone_name } : {}),
|
|
575
|
-
customDomain: row.custom_domain === true,
|
|
576
|
-
}));
|
|
577
|
-
}
|
|
578
|
-
|
|
579
552
|
function readRows(value: unknown): Record<string, unknown>[] {
|
|
580
553
|
return isUnknownRows(value) ? value : [];
|
|
581
554
|
}
|
|
@@ -587,25 +560,3 @@ function requiredString(row: Record<string, unknown>, key: string, context: stri
|
|
|
587
560
|
}
|
|
588
561
|
return value;
|
|
589
562
|
}
|
|
590
|
-
|
|
591
|
-
function valueAtPath(root: unknown, path: Array<string | number>): unknown {
|
|
592
|
-
let value = root;
|
|
593
|
-
for (const segment of path) {
|
|
594
|
-
if (typeof segment === 'number') {
|
|
595
|
-
if (!Array.isArray(value)) return undefined;
|
|
596
|
-
value = value[segment];
|
|
597
|
-
} else {
|
|
598
|
-
if (!isUnknownRecord(value)) return undefined;
|
|
599
|
-
value = value[segment];
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return value;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function tomlLiteral(value: unknown): string {
|
|
606
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || Array.isArray(value)) {
|
|
607
|
-
const literal = JSON.stringify(value);
|
|
608
|
-
if (literal !== undefined) return literal;
|
|
609
|
-
}
|
|
610
|
-
throw new Error(`Cannot materialize Wrangler TOML value of type ${typeof value}.`);
|
|
611
|
-
}
|