@smoothbricks/cli 0.9.0 → 0.10.1
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 +5 -2
- package/dist/monorepo/lockfile.d.ts +2 -0
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +13 -2
- package/dist/wrangler/prepare-env.d.ts +30 -9
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +175 -65
- package/managed/raw/tooling/git-hooks/pre-commit.sh +6 -0
- package/package.json +3 -3
- package/src/cli.ts +7 -4
- package/src/monorepo/lockfile.test.ts +157 -0
- package/src/monorepo/lockfile.ts +15 -2
- package/src/release/__tests__/fixture-repo.test.ts +8 -0
- package/src/release/__tests__/helpers/fixture-repo.ts +28 -8
- package/src/wrangler/prepare-env.test.ts +101 -0
- package/src/wrangler/prepare-env.ts +187 -71
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { afterAll, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import fc from 'fast-check';
|
|
7
|
+
import { syncBunLockfileVersions, validateBunLockfileVersions } from './lockfile.js';
|
|
8
|
+
|
|
9
|
+
const cleanupRoots: string[] = [];
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
for (const root of cleanupRoots) {
|
|
12
|
+
rmSync(root, { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function git(root: string, args: string): string {
|
|
17
|
+
return execSync(`git ${args}`, {
|
|
18
|
+
cwd: root,
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
GIT_CONFIG_GLOBAL: '/dev/null',
|
|
23
|
+
GIT_CONFIG_SYSTEM: '/dev/null',
|
|
24
|
+
GIT_AUTHOR_NAME: 'fixture',
|
|
25
|
+
GIT_AUTHOR_EMAIL: 'fixture@invalid',
|
|
26
|
+
GIT_COMMITTER_NAME: 'fixture',
|
|
27
|
+
GIT_COMMITTER_EMAIL: 'fixture@invalid',
|
|
28
|
+
},
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FixtureOptions {
|
|
34
|
+
packageVersion: string;
|
|
35
|
+
lockVersion: string;
|
|
36
|
+
stableTag?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeFixtureRepo(options: FixtureOptions): string {
|
|
40
|
+
const root = mkdtempSync(join(tmpdir(), 'smoo-lockfile-'));
|
|
41
|
+
cleanupRoots.push(root);
|
|
42
|
+
git(root, 'init -q');
|
|
43
|
+
git(root, 'commit -q --allow-empty -m init');
|
|
44
|
+
writeFileSync(
|
|
45
|
+
join(root, 'package.json'),
|
|
46
|
+
JSON.stringify({ name: 'fixture-root', version: '0.0.0', private: true, workspaces: ['packages/*'] }),
|
|
47
|
+
);
|
|
48
|
+
mkdirSync(join(root, 'packages', 'foo'), { recursive: true });
|
|
49
|
+
writeFixturePackage(root, options.packageVersion);
|
|
50
|
+
writeFixtureLock(root, options.lockVersion);
|
|
51
|
+
if (options.stableTag) {
|
|
52
|
+
git(root, `tag ${options.stableTag}`);
|
|
53
|
+
}
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeFixturePackage(root: string, version: string): void {
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(root, 'packages', 'foo', 'package.json'),
|
|
60
|
+
JSON.stringify({ name: '@fixture/foo', version, nx: { name: 'foo' } }),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeFixtureLock(root: string, version: string): void {
|
|
65
|
+
writeFileSync(
|
|
66
|
+
join(root, 'bun.lock'),
|
|
67
|
+
[
|
|
68
|
+
'{',
|
|
69
|
+
' "lockfileVersion": 1,',
|
|
70
|
+
' "workspaces": {',
|
|
71
|
+
' "": { "name": "fixture-root" },',
|
|
72
|
+
' "packages/foo": {',
|
|
73
|
+
' "name": "@fixture/foo",',
|
|
74
|
+
` "version": "${version}",`,
|
|
75
|
+
' },',
|
|
76
|
+
' },',
|
|
77
|
+
'}',
|
|
78
|
+
'',
|
|
79
|
+
].join('\n'),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function lockVersionIn(root: string): string {
|
|
84
|
+
const match = readFileSync(join(root, 'bun.lock'), 'utf8').match(/"packages\/foo":\s*\{[^}]*"version":\s*"([^"]+)"/);
|
|
85
|
+
if (!match) throw new Error('fixture lockfile lost its workspace entry');
|
|
86
|
+
return match[1] as string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe('bun.lock workspace version sync', () => {
|
|
90
|
+
it('a bun-install revert of the release-synced version fails validation', () => {
|
|
91
|
+
// The b3298fc4c shape: package.json holds the next prerelease, a stable tag
|
|
92
|
+
// exists, and `bun install` rewrote the lockfile back to the prerelease.
|
|
93
|
+
const root = makeFixtureRepo({
|
|
94
|
+
packageVersion: '0.1.3-next.0',
|
|
95
|
+
lockVersion: '0.1.3-next.0',
|
|
96
|
+
stableTag: 'foo@0.1.2',
|
|
97
|
+
});
|
|
98
|
+
expect(validateBunLockfileVersions(root)).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('sync restores the latest stable tag version and validation passes', () => {
|
|
102
|
+
const root = makeFixtureRepo({
|
|
103
|
+
packageVersion: '0.1.3-next.0',
|
|
104
|
+
lockVersion: '0.1.3-next.0',
|
|
105
|
+
stableTag: 'foo@0.1.2',
|
|
106
|
+
});
|
|
107
|
+
expect(syncBunLockfileVersions(root, { log: false })).toBe(1);
|
|
108
|
+
expect(lockVersionIn(root)).toBe('0.1.2');
|
|
109
|
+
expect(validateBunLockfileVersions(root)).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('a never-published prerelease package falls back to its manifest version', () => {
|
|
113
|
+
// The new-workspace-package shape (e.g. cowshed): no stable tag exists yet.
|
|
114
|
+
const root = makeFixtureRepo({ packageVersion: '0.2.0-next.0', lockVersion: '0.2.0-next.0' });
|
|
115
|
+
expect(validateBunLockfileVersions(root)).toBe(0);
|
|
116
|
+
expect(syncBunLockfileVersions(root, { log: false })).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('a stable manifest version is required verbatim in the lockfile', () => {
|
|
120
|
+
const root = makeFixtureRepo({ packageVersion: '0.1.0', lockVersion: '0.0.9' });
|
|
121
|
+
expect(validateBunLockfileVersions(root)).toBe(1);
|
|
122
|
+
expect(syncBunLockfileVersions(root, { log: false })).toBe(1);
|
|
123
|
+
expect(lockVersionIn(root)).toBe('0.1.0');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('stage: true stages the healed bun.lock', () => {
|
|
127
|
+
const root = makeFixtureRepo({
|
|
128
|
+
packageVersion: '0.1.3-next.0',
|
|
129
|
+
lockVersion: '0.1.3-next.0',
|
|
130
|
+
stableTag: 'foo@0.1.2',
|
|
131
|
+
});
|
|
132
|
+
expect(syncBunLockfileVersions(root, { log: false, stage: true })).toBe(1);
|
|
133
|
+
expect(git(root, 'diff --cached --name-only')).toContain('bun.lock');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('stage: true leaves a clean lockfile unstaged', () => {
|
|
137
|
+
const root = makeFixtureRepo({ packageVersion: '0.1.2', lockVersion: '0.1.2', stableTag: 'foo@0.1.2' });
|
|
138
|
+
expect(syncBunLockfileVersions(root, { log: false, stage: true })).toBe(0);
|
|
139
|
+
expect(git(root, 'diff --cached --name-only').trim()).toBe('');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('property: after sync, validation always passes', () => {
|
|
143
|
+
const version = fc
|
|
144
|
+
.tuple(fc.nat({ max: 20 }), fc.nat({ max: 20 }), fc.nat({ max: 20 }), fc.option(fc.nat({ max: 5 })))
|
|
145
|
+
.map(([major, minor, patch, next]) => `${major}.${minor}.${patch}${next === null ? '' : `-next.${next}`}`);
|
|
146
|
+
const root = makeFixtureRepo({ packageVersion: '0.1.2', lockVersion: '0.1.2', stableTag: 'foo@0.1.2' });
|
|
147
|
+
fc.assert(
|
|
148
|
+
fc.property(version, version, (packageVersion, lockVersion) => {
|
|
149
|
+
writeFixturePackage(root, packageVersion);
|
|
150
|
+
writeFixtureLock(root, lockVersion);
|
|
151
|
+
syncBunLockfileVersions(root, { log: false });
|
|
152
|
+
return validateBunLockfileVersions(root) === 0;
|
|
153
|
+
}),
|
|
154
|
+
{ numRuns: 25 },
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
package/src/monorepo/lockfile.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { escapeRegex, getWorkspacePackages } from '../lib/workspace.js';
|
|
|
5
5
|
|
|
6
6
|
export interface SyncBunLockfileVersionsOptions {
|
|
7
7
|
log?: boolean;
|
|
8
|
+
/** `git add` bun.lock after rewriting it, for the pre-commit self-heal path. */
|
|
9
|
+
stage?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
// Temporary Bun workaround. Delete this sync function, validateBunLockfileVersions,
|
|
@@ -16,6 +18,12 @@ export interface SyncBunLockfileVersionsOptions {
|
|
|
16
18
|
// - https://github.com/oven-sh/bun/issues/18906
|
|
17
19
|
// - https://github.com/oven-sh/bun/issues/20477
|
|
18
20
|
// - https://github.com/oven-sh/bun/issues/20829
|
|
21
|
+
//
|
|
22
|
+
// Any `bun install` rewrites the workspace versions from package.json, reverting
|
|
23
|
+
// this sync between releases. The pre-commit hook therefore runs
|
|
24
|
+
// `smoo monorepo sync-bun-lockfile-versions --stage` on every commit so drift is
|
|
25
|
+
// healed at the commit that would otherwise carry it — validation must never
|
|
26
|
+
// fail a committer for drift a routine `bun install` introduced.
|
|
19
27
|
export function syncBunLockfileVersions(root: string, options: SyncBunLockfileVersionsOptions = {}): number {
|
|
20
28
|
const log = options.log ?? true;
|
|
21
29
|
const lockfilePath = join(root, 'bun.lock');
|
|
@@ -59,10 +67,15 @@ export function syncBunLockfileVersions(root: string, options: SyncBunLockfileVe
|
|
|
59
67
|
}
|
|
60
68
|
if (updated > 0) {
|
|
61
69
|
writeFileSync(lockfilePath, lockfile);
|
|
70
|
+
if (options.stage) {
|
|
71
|
+
execSync('git add bun.lock', { cwd: root, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
72
|
+
}
|
|
62
73
|
}
|
|
63
|
-
if (log) {
|
|
74
|
+
if (log || updated > 0) {
|
|
64
75
|
console.log(
|
|
65
|
-
updated > 0
|
|
76
|
+
updated > 0
|
|
77
|
+
? `Updated ${updated} workspace version(s) in bun.lock${options.stage ? ' (staged)' : ''}`
|
|
78
|
+
: 'All workspace versions already in sync.',
|
|
66
79
|
);
|
|
67
80
|
}
|
|
68
81
|
return updated;
|
|
@@ -226,6 +226,14 @@ describe('release planning with fixture git repositories', () => {
|
|
|
226
226
|
});
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
it('kills timed-out fixture Git process groups so descendants cannot hold output pipes open', async () => {
|
|
230
|
+
await withFixtureRepo(async (root) => {
|
|
231
|
+
await expect(
|
|
232
|
+
git(root, ['-c', "alias.hold-pipes=!sh -c 'sleep 30 & exit 0'", 'hold-pipes'], undefined, 100),
|
|
233
|
+
).rejects.toThrow('timed out after 100ms');
|
|
234
|
+
});
|
|
235
|
+
}, 5_000);
|
|
236
|
+
|
|
229
237
|
it('repairs a scoped package from its project-name release tag without creating a package-name tag', async () => {
|
|
230
238
|
await withFixtureRepo(async (author) => {
|
|
231
239
|
await writeWorkspace(author);
|
|
@@ -82,9 +82,14 @@ export async function writeBuildablePackage(
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export async function git(
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
export async function git(
|
|
86
|
+
root: string,
|
|
87
|
+
args: string[],
|
|
88
|
+
env?: Record<string, string>,
|
|
89
|
+
timeoutMs = GIT_TIMEOUT_MS,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
const result = await gitResult(root, args, env, timeoutMs);
|
|
92
|
+
if (result.timedOut || result.exitCode !== 0) {
|
|
88
93
|
throw new Error(gitErrorMessage(root, args, result));
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -101,9 +106,15 @@ export async function gitSucceeds(root: string, args: string[]): Promise<boolean
|
|
|
101
106
|
return (await gitResult(root, args)).exitCode === 0;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
async function gitResult(
|
|
109
|
+
async function gitResult(
|
|
110
|
+
root: string,
|
|
111
|
+
args: string[],
|
|
112
|
+
env?: Record<string, string>,
|
|
113
|
+
timeoutMs = GIT_TIMEOUT_MS,
|
|
114
|
+
): Promise<GitResult> {
|
|
105
115
|
const proc = Bun.spawn(['git', ...args], {
|
|
106
116
|
cwd: root,
|
|
117
|
+
detached: true,
|
|
107
118
|
env: { ...process.env, ...GIT_FIXTURE_ENV, ...env },
|
|
108
119
|
stdout: 'pipe',
|
|
109
120
|
stderr: 'pipe',
|
|
@@ -111,15 +122,23 @@ async function gitResult(root: string, args: string[], env?: Record<string, stri
|
|
|
111
122
|
let timedOut = false;
|
|
112
123
|
const timeout = setTimeout(() => {
|
|
113
124
|
timedOut = true;
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
try {
|
|
126
|
+
// `git` may exit after spawning a transport or hook which still owns its
|
|
127
|
+
// pipes. Killing the detached process group closes every inherited pipe.
|
|
128
|
+
process.kill(-proc.pid, 'SIGKILL');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH')) {
|
|
131
|
+
proc.kill('SIGKILL');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, timeoutMs);
|
|
116
135
|
try {
|
|
117
136
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
118
137
|
proc.exited,
|
|
119
138
|
streamBytes(proc.stdout),
|
|
120
139
|
streamBytes(proc.stderr),
|
|
121
140
|
]);
|
|
122
|
-
return { exitCode, stdout, stderr, timedOut };
|
|
141
|
+
return { exitCode, stdout, stderr, timedOut, timeoutMs };
|
|
123
142
|
} finally {
|
|
124
143
|
clearTimeout(timeout);
|
|
125
144
|
}
|
|
@@ -151,12 +170,13 @@ interface GitResult {
|
|
|
151
170
|
stdout: Uint8Array;
|
|
152
171
|
stderr: Uint8Array;
|
|
153
172
|
timedOut: boolean;
|
|
173
|
+
timeoutMs: number;
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
function gitErrorMessage(root: string, args: string[], result: GitResult): string {
|
|
157
177
|
const stderr = new TextDecoder().decode(result.stderr).trim();
|
|
158
178
|
const timeoutText = result.timedOut
|
|
159
|
-
? ` timed out after ${
|
|
179
|
+
? ` timed out after ${result.timeoutMs}ms`
|
|
160
180
|
: ` failed with exit code ${result.exitCode}`;
|
|
161
181
|
return [`git ${args.join(' ')}${timeoutText}`, `cwd: ${root}`, stderr].filter(Boolean).join('\n');
|
|
162
182
|
}
|
|
@@ -4,10 +4,13 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import {
|
|
6
6
|
assertToml,
|
|
7
|
+
blankD1Ids,
|
|
7
8
|
blankEnvVars,
|
|
8
9
|
blankKvIds,
|
|
9
10
|
cloneEnvBlock,
|
|
10
11
|
firstWranglerEnv,
|
|
12
|
+
getD1Id,
|
|
13
|
+
getD1Name,
|
|
11
14
|
getKvId,
|
|
12
15
|
getVar,
|
|
13
16
|
hasEnvBlock,
|
|
@@ -16,6 +19,7 @@ import {
|
|
|
16
19
|
parseDevVarsExample,
|
|
17
20
|
readManifest,
|
|
18
21
|
readPemFile,
|
|
22
|
+
setD1Id,
|
|
19
23
|
setKvId,
|
|
20
24
|
setVar,
|
|
21
25
|
} from './prepare-env.js';
|
|
@@ -161,6 +165,81 @@ describe('blankEnvVars / blankKvIds', () => {
|
|
|
161
165
|
});
|
|
162
166
|
});
|
|
163
167
|
|
|
168
|
+
// D1 shell-outs (listD1Databases / ensureD1Database) invoke `bunx wrangler d1`
|
|
169
|
+
// against a live account and are integration-only — deliberately not exercised.
|
|
170
|
+
|
|
171
|
+
describe('getD1Id / getD1Name', () => {
|
|
172
|
+
const toml =
|
|
173
|
+
'[env.staging]\n\n' +
|
|
174
|
+
'[[env.staging.d1_databases]]\nbinding = "DB"\ndatabase_id = "abc123"\ndatabase_name = "app-staging-db"\n\n' +
|
|
175
|
+
'[env.production]\n\n' +
|
|
176
|
+
'[[env.production.d1_databases]]\nbinding = "DB"\ndatabase_id = ""\ndatabase_name = "app-db"\n';
|
|
177
|
+
|
|
178
|
+
it('reads the id for a matching binding and null for empty id, absent binding, and absent env', () => {
|
|
179
|
+
expect(getD1Id(toml, 'staging', 'DB')).toBe('abc123');
|
|
180
|
+
expect(getD1Id(toml, 'production', 'DB')).toBeNull(); // production id is ""
|
|
181
|
+
expect(getD1Id(toml, 'staging', 'MISSING')).toBeNull(); // absent binding
|
|
182
|
+
expect(getD1Id(toml, 'qa', 'DB')).toBeNull(); // absent env
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('reads the database_name for a matching binding and null when the binding or env is absent', () => {
|
|
186
|
+
expect(getD1Name(toml, 'staging', 'DB')).toBe('app-staging-db');
|
|
187
|
+
expect(getD1Name(toml, 'production', 'DB')).toBe('app-db'); // present even though its id is blank
|
|
188
|
+
expect(getD1Name(toml, 'staging', 'MISSING')).toBeNull();
|
|
189
|
+
expect(getD1Name(toml, 'qa', 'DB')).toBeNull();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('setD1Id', () => {
|
|
194
|
+
const toml =
|
|
195
|
+
'[[env.staging.d1_databases]]\nbinding = "DB"\ndatabase_id = ""\ndatabase_name = "app-staging-db"\n\n' +
|
|
196
|
+
'[[env.staging.d1_databases]]\nbinding = "ANALYTICS"\ndatabase_id = ""\ndatabase_name = "app-staging-analytics"\n\n' +
|
|
197
|
+
'[[env.production.d1_databases]]\nbinding = "DB"\ndatabase_id = "prod-existing"\ndatabase_name = "app-db"\n';
|
|
198
|
+
|
|
199
|
+
it('writes "<id>" # <name> against the matching env+binding, leaving siblings and other envs intact', () => {
|
|
200
|
+
const out = setD1Id(toml, 'staging', 'DB', 'new-staging-id', 'app-staging-db');
|
|
201
|
+
expect(out).toContain('"new-staging-id" # app-staging-db');
|
|
202
|
+
expect(getD1Id(out, 'staging', 'DB')).toBe('new-staging-id'); // round-trip
|
|
203
|
+
expect(getD1Id(out, 'staging', 'ANALYTICS')).toBeNull(); // sibling binding untouched
|
|
204
|
+
expect(getD1Id(out, 'production', 'DB')).toBe('prod-existing'); // other env untouched
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('tolerates database_name preceding database_id and preserves the name', () => {
|
|
208
|
+
const nameFirst =
|
|
209
|
+
'[[env.staging.d1_databases]]\nbinding = "DB"\ndatabase_name = "app-staging-db"\ndatabase_id = "old"\n';
|
|
210
|
+
const out = setD1Id(nameFirst, 'staging', 'DB', 'fresh', 'app-staging-db');
|
|
211
|
+
expect(getD1Id(out, 'staging', 'DB')).toBe('fresh');
|
|
212
|
+
expect(getD1Name(out, 'staging', 'DB')).toBe('app-staging-db'); // name line not clobbered
|
|
213
|
+
expect(() => assertToml(out)).not.toThrow();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('throws when the binding or the env is absent', () => {
|
|
217
|
+
expect(() => setD1Id(toml, 'staging', 'MISSING', 'x', 'n')).toThrow();
|
|
218
|
+
expect(() => setD1Id(toml, 'qa', 'DB', 'x', 'n')).toThrow();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('blankD1Ids', () => {
|
|
223
|
+
const toml =
|
|
224
|
+
'[[env.staging.d1_databases]]\nbinding = "DB"\ndatabase_id = "staging-db-id"\ndatabase_name = "app-staging-db"\n\n' +
|
|
225
|
+
'[[env.staging.d1_databases]]\nbinding = "ANALYTICS"\ndatabase_id = "staging-an-id"\ndatabase_name = "app-staging-analytics"\n\n' +
|
|
226
|
+
'[[env.production.d1_databases]]\nbinding = "DB"\ndatabase_id = "prod-db-id"\ndatabase_name = "app-db"\n';
|
|
227
|
+
|
|
228
|
+
it('blanks every d1 id under the env while names, bindings, and the other env survive', () => {
|
|
229
|
+
const out = blankD1Ids(toml, 'staging');
|
|
230
|
+
expect(getD1Id(out, 'staging', 'DB')).toBeNull();
|
|
231
|
+
expect(getD1Id(out, 'staging', 'ANALYTICS')).toBeNull();
|
|
232
|
+
// names survive the blanking
|
|
233
|
+
expect(getD1Name(out, 'staging', 'DB')).toBe('app-staging-db');
|
|
234
|
+
expect(getD1Name(out, 'staging', 'ANALYTICS')).toBe('app-staging-analytics');
|
|
235
|
+
// other env's id is untouched
|
|
236
|
+
expect(getD1Id(out, 'production', 'DB')).toBe('prod-db-id');
|
|
237
|
+
// bindings survived: setD1Id still resolves them and re-fills an id
|
|
238
|
+
const refilled = setD1Id(out, 'staging', 'DB', 're-provisioned', 'app-staging-db');
|
|
239
|
+
expect(getD1Id(refilled, 'staging', 'DB')).toBe('re-provisioned');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
164
243
|
describe('assertToml', () => {
|
|
165
244
|
it('returns for valid toml', () => {
|
|
166
245
|
expect(() => assertToml('name = "svc"\n[env.production]\n')).not.toThrow();
|
|
@@ -192,6 +271,7 @@ describe('readManifest', () => {
|
|
|
192
271
|
await writeFile(join(root, '.dev.vars.example'), '# secrets\nAPI_KEY=\nJWT_SECRET=""\n');
|
|
193
272
|
expect(readManifest(root, 'staging')).toEqual({
|
|
194
273
|
kvBindings: ['CACHE', 'SESSIONS'],
|
|
274
|
+
d1Bindings: [],
|
|
195
275
|
vars: ['PUBLIC_URL', 'FEATURE_FLAG'],
|
|
196
276
|
secrets: ['API_KEY', 'JWT_SECRET'],
|
|
197
277
|
});
|
|
@@ -212,6 +292,27 @@ describe('readManifest', () => {
|
|
|
212
292
|
await rm(root, { recursive: true, force: true });
|
|
213
293
|
}
|
|
214
294
|
});
|
|
295
|
+
|
|
296
|
+
it('reports d1 bindings alongside kv bindings and vars without dropping the existing fields', async () => {
|
|
297
|
+
const withD1 =
|
|
298
|
+
'name = "svc"\n\n' +
|
|
299
|
+
'[env.staging.vars]\nPUBLIC_URL = "https://staging"\n\n' +
|
|
300
|
+
'[[env.staging.kv_namespaces]]\nbinding = "CACHE"\nid = "abc"\n\n' +
|
|
301
|
+
'[[env.staging.d1_databases]]\nbinding = "DB"\ndatabase_id = "id-1"\ndatabase_name = "app-staging-db"\n\n' +
|
|
302
|
+
'[[env.staging.d1_databases]]\nbinding = "ANALYTICS"\ndatabase_id = ""\ndatabase_name = "app-analytics-db"\n';
|
|
303
|
+
const root = await mkdtemp(join(tmpdir(), 'smoo-prepare-env-'));
|
|
304
|
+
try {
|
|
305
|
+
await writeFile(join(root, 'wrangler.toml'), withD1);
|
|
306
|
+
expect(readManifest(root, 'staging')).toEqual({
|
|
307
|
+
kvBindings: ['CACHE'],
|
|
308
|
+
d1Bindings: ['DB', 'ANALYTICS'],
|
|
309
|
+
vars: ['PUBLIC_URL'],
|
|
310
|
+
secrets: [],
|
|
311
|
+
});
|
|
312
|
+
} finally {
|
|
313
|
+
await rm(root, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
});
|
|
215
316
|
});
|
|
216
317
|
|
|
217
318
|
describe('isPem / readPemFile / looksLikeFileSecret', () => {
|