@package-broker/cli 0.19.8 → 2.0.0-rc.2
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/LICENSE +661 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +12 -0
- package/dist/bin.js.map +1 -0
- package/dist/build-artifacts.d.ts +27 -0
- package/dist/build-artifacts.d.ts.map +1 -0
- package/dist/build-artifacts.js +169 -0
- package/dist/build-artifacts.js.map +1 -0
- package/dist/build-assets.d.ts +36 -0
- package/dist/build-assets.d.ts.map +1 -0
- package/dist/build-assets.js +224 -0
- package/dist/build-assets.js.map +1 -0
- package/dist/build.d.ts +31 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +277 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +828 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloudflare/d1-http-executor.d.ts +30 -0
- package/dist/cloudflare/d1-http-executor.d.ts.map +1 -0
- package/dist/cloudflare/d1-http-executor.js +230 -0
- package/dist/cloudflare/d1-http-executor.js.map +1 -0
- package/dist/cloudflare/d1-http-response.d.ts +4 -0
- package/dist/cloudflare/d1-http-response.d.ts.map +1 -0
- package/dist/cloudflare/d1-http-response.js +113 -0
- package/dist/cloudflare/d1-http-response.js.map +1 -0
- package/dist/commands/admin.d.ts +47 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/admin.js +447 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/built-manifest.d.ts +13 -0
- package/dist/commands/built-manifest.d.ts.map +1 -0
- package/dist/commands/built-manifest.js +197 -0
- package/dist/commands/built-manifest.js.map +1 -0
- package/dist/commands/cloudflare/api.d.ts +132 -0
- package/dist/commands/cloudflare/api.d.ts.map +1 -0
- package/dist/commands/cloudflare/api.js +502 -0
- package/dist/commands/cloudflare/api.js.map +1 -0
- package/dist/commands/cloudflare/deploy.d.ts +111 -0
- package/dist/commands/cloudflare/deploy.d.ts.map +1 -0
- package/dist/commands/cloudflare/deploy.js +2170 -0
- package/dist/commands/cloudflare/deploy.js.map +1 -0
- package/dist/commands/cloudflare/inventory.d.ts +257 -0
- package/dist/commands/cloudflare/inventory.d.ts.map +1 -0
- package/dist/commands/cloudflare/inventory.js +842 -0
- package/dist/commands/cloudflare/inventory.js.map +1 -0
- package/dist/commands/cloudflare/janitor.d.ts +10 -0
- package/dist/commands/cloudflare/janitor.d.ts.map +1 -0
- package/dist/commands/cloudflare/janitor.js +84 -0
- package/dist/commands/cloudflare/janitor.js.map +1 -0
- package/dist/commands/cloudflare/migrations-check.d.ts +5 -0
- package/dist/commands/cloudflare/migrations-check.d.ts.map +1 -0
- package/dist/commands/cloudflare/migrations-check.js +10 -0
- package/dist/commands/cloudflare/migrations-check.js.map +1 -0
- package/dist/commands/cloudflare/providers-file.d.ts +17 -0
- package/dist/commands/cloudflare/providers-file.d.ts.map +1 -0
- package/dist/commands/cloudflare/providers-file.js +155 -0
- package/dist/commands/cloudflare/providers-file.js.map +1 -0
- package/dist/commands/cloudflare/provision.d.ts +79 -0
- package/dist/commands/cloudflare/provision.d.ts.map +1 -0
- package/dist/commands/cloudflare/provision.js +961 -0
- package/dist/commands/cloudflare/provision.js.map +1 -0
- package/dist/commands/cloudflare/queue-consumer.d.ts +4 -0
- package/dist/commands/cloudflare/queue-consumer.d.ts.map +1 -0
- package/dist/commands/cloudflare/queue-consumer.js +57 -0
- package/dist/commands/cloudflare/queue-consumer.js.map +1 -0
- package/dist/commands/cloudflare/routes.d.ts +3 -0
- package/dist/commands/cloudflare/routes.d.ts.map +1 -0
- package/dist/commands/cloudflare/routes.js +26 -0
- package/dist/commands/cloudflare/routes.js.map +1 -0
- package/dist/commands/cloudflare/setup.d.ts +50 -0
- package/dist/commands/cloudflare/setup.d.ts.map +1 -0
- package/dist/commands/cloudflare/setup.js +836 -0
- package/dist/commands/cloudflare/setup.js.map +1 -0
- package/dist/commands/cloudflare/smoke.d.ts +29 -0
- package/dist/commands/cloudflare/smoke.d.ts.map +1 -0
- package/dist/commands/cloudflare/smoke.js +701 -0
- package/dist/commands/cloudflare/smoke.js.map +1 -0
- package/dist/commands/cloudflare/teardown.d.ts +65 -0
- package/dist/commands/cloudflare/teardown.d.ts.map +1 -0
- package/dist/commands/cloudflare/teardown.js +984 -0
- package/dist/commands/cloudflare/teardown.js.map +1 -0
- package/dist/commands/cloudflare/worker-settings.d.ts +4 -0
- package/dist/commands/cloudflare/worker-settings.d.ts.map +1 -0
- package/dist/commands/cloudflare/worker-settings.js +93 -0
- package/dist/commands/cloudflare/worker-settings.js.map +1 -0
- package/dist/commands/cloudflare/zone.d.ts +3 -0
- package/dist/commands/cloudflare/zone.d.ts.map +1 -0
- package/dist/commands/cloudflare/zone.js +73 -0
- package/dist/commands/cloudflare/zone.js.map +1 -0
- package/dist/commands/cloudflare.d.ts +55 -0
- package/dist/commands/cloudflare.d.ts.map +1 -0
- package/dist/commands/cloudflare.js +393 -0
- package/dist/commands/cloudflare.js.map +1 -0
- package/dist/commands/import-cutover.d.ts +5 -0
- package/dist/commands/import-cutover.d.ts.map +1 -0
- package/dist/commands/import-cutover.js +25 -0
- package/dist/commands/import-cutover.js.map +1 -0
- package/dist/commands/import-private-packagist.d.ts +38 -0
- package/dist/commands/import-private-packagist.d.ts.map +1 -0
- package/dist/commands/import-private-packagist.js +232 -0
- package/dist/commands/import-private-packagist.js.map +1 -0
- package/dist/commands/import-satis.d.ts +11 -0
- package/dist/commands/import-satis.d.ts.map +1 -0
- package/dist/commands/import-satis.js +112 -0
- package/dist/commands/import-satis.js.map +1 -0
- package/dist/commands/import.d.ts +22 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +343 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/openapi-specs.d.ts +4 -0
- package/dist/commands/openapi-specs.d.ts.map +1 -0
- package/dist/commands/openapi-specs.js +94 -0
- package/dist/commands/openapi-specs.js.map +1 -0
- package/dist/commands/openapi.d.ts +12 -0
- package/dist/commands/openapi.d.ts.map +1 -0
- package/dist/commands/openapi.js +44 -0
- package/dist/commands/openapi.js.map +1 -0
- package/dist/commands/source.d.ts +8 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +61 -0
- package/dist/commands/source.js.map +1 -0
- package/dist/commands/sqlite.d.ts +11 -0
- package/dist/commands/sqlite.d.ts.map +1 -0
- package/dist/commands/sqlite.js +76 -0
- package/dist/commands/sqlite.js.map +1 -0
- package/dist/import/client.d.ts +71 -0
- package/dist/import/client.d.ts.map +1 -0
- package/dist/import/client.js +348 -0
- package/dist/import/client.js.map +1 -0
- package/dist/import/credential-refs.d.ts +2 -0
- package/dist/import/credential-refs.d.ts.map +1 -0
- package/dist/import/credential-refs.js +22 -0
- package/dist/import/credential-refs.js.map +1 -0
- package/dist/import/cutover.d.ts +64 -0
- package/dist/import/cutover.d.ts.map +1 -0
- package/dist/import/cutover.js +544 -0
- package/dist/import/cutover.js.map +1 -0
- package/dist/import/private-packagist/api.d.ts +57 -0
- package/dist/import/private-packagist/api.d.ts.map +1 -0
- package/dist/import/private-packagist/api.js +246 -0
- package/dist/import/private-packagist/api.js.map +1 -0
- package/dist/import/private-packagist/dates.d.ts +30 -0
- package/dist/import/private-packagist/dates.d.ts.map +1 -0
- package/dist/import/private-packagist/dates.js +189 -0
- package/dist/import/private-packagist/dates.js.map +1 -0
- package/dist/import/private-packagist/map.d.ts +48 -0
- package/dist/import/private-packagist/map.d.ts.map +1 -0
- package/dist/import/private-packagist/map.js +162 -0
- package/dist/import/private-packagist/map.js.map +1 -0
- package/dist/import/private-packagist/plan.d.ts +61 -0
- package/dist/import/private-packagist/plan.d.ts.map +1 -0
- package/dist/import/private-packagist/plan.js +936 -0
- package/dist/import/private-packagist/plan.js.map +1 -0
- package/dist/import/private-packagist/snapshot.d.ts +31 -0
- package/dist/import/private-packagist/snapshot.d.ts.map +1 -0
- package/dist/import/private-packagist/snapshot.js +406 -0
- package/dist/import/private-packagist/snapshot.js.map +1 -0
- package/dist/import/satis.d.ts +93 -0
- package/dist/import/satis.d.ts.map +1 -0
- package/dist/import/satis.js +873 -0
- package/dist/import/satis.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -96
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +56 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +137 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations-collect.d.ts +6 -0
- package/dist/migrations-collect.d.ts.map +1 -0
- package/dist/migrations-collect.js +39 -0
- package/dist/migrations-collect.js.map +1 -0
- package/dist/prebuilt-expected.json +7 -0
- package/dist/prebuilt.d.ts +27 -0
- package/dist/prebuilt.d.ts.map +1 -0
- package/dist/prebuilt.js +391 -0
- package/dist/prebuilt.js.map +1 -0
- package/package.json +22 -25
- package/README.md +0 -29
- package/dist/__tests__/migrate.test.d.ts +0 -2
- package/dist/__tests__/migrate.test.d.ts.map +0 -1
- package/dist/__tests__/migrate.test.js +0 -116
- package/dist/__tests__/migrate.test.js.map +0 -1
- package/dist/migrate.d.ts +0 -40
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -335
- package/dist/migrate.js.map +0 -1
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open, realpath } from 'node:fs/promises';
|
|
4
|
+
import { dirname, isAbsolute, parse, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { ORG_SLUG_PATTERN } from '@package-broker/core/db/organization';
|
|
8
|
+
import { normalizeEmail } from '@package-broker/core/identity/policy';
|
|
9
|
+
import { createOtlpExporter } from '@package-broker/core/platform/otlp';
|
|
10
|
+
import { formatMasterKey, generateMasterKey, parseMasterKey, } from '@package-broker/core/secrets/master-key';
|
|
11
|
+
import { assertPrebuiltConfiguration, materializePrebuilt, prebuiltEnvelope, removePrebuilt, } from '../../prebuilt.js';
|
|
12
|
+
import { bootstrapAdmin } from '../admin.js';
|
|
13
|
+
import { bootstrapCloudflare, migrateCloudflare } from '../cloudflare.js';
|
|
14
|
+
import { CloudflareApi, CloudflareError, cloudflareCredentials, commandEnvelope, failCommand, } from './api.js';
|
|
15
|
+
import { active, assertProvidersAccepted, cloudflareDeployContract, deploy, deploymentDomain, workersDevOrigin, validateDeploymentArtifact, } from './deploy.js';
|
|
16
|
+
import { InventoryStore, InventoryTransaction, installIdentity, } from './inventory.js';
|
|
17
|
+
import { provision } from './provision.js';
|
|
18
|
+
import { smoke } from './smoke.js';
|
|
19
|
+
// No inherited extension, provider, runtime or arbitrary configuration input crosses this boundary.
|
|
20
|
+
export function setupBuildEnvironment(env) {
|
|
21
|
+
const clean = {};
|
|
22
|
+
for (const name of [
|
|
23
|
+
'PATH',
|
|
24
|
+
'HOME',
|
|
25
|
+
'USERPROFILE',
|
|
26
|
+
'TMPDIR',
|
|
27
|
+
'TMP',
|
|
28
|
+
'TEMP',
|
|
29
|
+
'SystemRoot',
|
|
30
|
+
'WINDIR',
|
|
31
|
+
'LANG',
|
|
32
|
+
'LC_ALL',
|
|
33
|
+
'TZ',
|
|
34
|
+
'SOURCE_DATE_EPOCH',
|
|
35
|
+
]) {
|
|
36
|
+
if (env[name] !== undefined)
|
|
37
|
+
clean[name] = env[name];
|
|
38
|
+
}
|
|
39
|
+
return clean;
|
|
40
|
+
}
|
|
41
|
+
export async function buildSetupArtifact(configPath, outDir, env) {
|
|
42
|
+
try {
|
|
43
|
+
await promisify(execFile)(process.execPath, [
|
|
44
|
+
'--input-type=module',
|
|
45
|
+
'--eval',
|
|
46
|
+
'const { build } = await import(process.argv[1]); await build({ configPath: process.argv[2], outDir: process.argv[3], target: "cloudflare" });',
|
|
47
|
+
new URL('../../build.js', import.meta.url).href,
|
|
48
|
+
configPath,
|
|
49
|
+
outDir,
|
|
50
|
+
], { env: setupBuildEnvironment(env), maxBuffer: 1024 * 1024 });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new CloudflareError('invalid_input', 2, false, 'build');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const invalid = () => {
|
|
57
|
+
throw new CloudflareError('invalid_input', 2, false, 'validate');
|
|
58
|
+
};
|
|
59
|
+
const unsafe = () => {
|
|
60
|
+
throw new CloudflareError('unsafe_path', 2, false, 'validate');
|
|
61
|
+
};
|
|
62
|
+
const inside = (root, path) => {
|
|
63
|
+
const rel = relative(root, path);
|
|
64
|
+
return !rel || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`));
|
|
65
|
+
};
|
|
66
|
+
async function stat(path) {
|
|
67
|
+
try {
|
|
68
|
+
return await lstat(path);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code === 'ENOENT')
|
|
72
|
+
return null;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function safeDeliveryPath(path, roots, recovery, required = recovery) {
|
|
77
|
+
const absolute = resolve(path);
|
|
78
|
+
let current = parse(absolute).root;
|
|
79
|
+
let parent = current;
|
|
80
|
+
for (const part of relative(current, absolute).split(sep)) {
|
|
81
|
+
current = resolve(current, part);
|
|
82
|
+
const entry = await stat(current);
|
|
83
|
+
if (entry?.isSymbolicLink() || (entry && current !== absolute && !entry.isDirectory()))
|
|
84
|
+
unsafe();
|
|
85
|
+
if (current !== absolute) {
|
|
86
|
+
if (!entry && (!recovery || required)) {
|
|
87
|
+
if (required)
|
|
88
|
+
throw new CloudflareError('key_recovery_required', 2, false, 'key_delivery');
|
|
89
|
+
unsafe();
|
|
90
|
+
}
|
|
91
|
+
if (entry)
|
|
92
|
+
parent = current;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
parent = await realpath(parent);
|
|
96
|
+
if (roots.some((root) => inside(root, absolute)))
|
|
97
|
+
unsafe();
|
|
98
|
+
try {
|
|
99
|
+
await promisify(execFile)('git', ['rev-parse', '--show-toplevel'], { cwd: parent });
|
|
100
|
+
try {
|
|
101
|
+
await promisify(execFile)('git', ['check-ignore', '--quiet', absolute], { cwd: parent });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return unsafe();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof CloudflareError)
|
|
109
|
+
throw error;
|
|
110
|
+
if (error.code !== 128)
|
|
111
|
+
return unsafe();
|
|
112
|
+
}
|
|
113
|
+
const entry = await stat(absolute);
|
|
114
|
+
if (entry &&
|
|
115
|
+
(!recovery ||
|
|
116
|
+
!entry.isFile() ||
|
|
117
|
+
entry.nlink !== 1 ||
|
|
118
|
+
(entry.mode & 0o077) !== 0 ||
|
|
119
|
+
entry.uid !== process.getuid?.()))
|
|
120
|
+
unsafe();
|
|
121
|
+
if (required && !entry)
|
|
122
|
+
throw new CloudflareError('key_recovery_required', 2, false, 'key_delivery');
|
|
123
|
+
}
|
|
124
|
+
async function readSavedKey(path) {
|
|
125
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
126
|
+
try {
|
|
127
|
+
const entry = await file.stat();
|
|
128
|
+
if (!entry.isFile() ||
|
|
129
|
+
entry.nlink !== 1 ||
|
|
130
|
+
entry.size > 1024 ||
|
|
131
|
+
(entry.mode & 0o077) !== 0 ||
|
|
132
|
+
entry.uid !== process.getuid?.())
|
|
133
|
+
unsafe();
|
|
134
|
+
return (await file.readFile('utf8')).trim();
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await file.close();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function writeKey(path, key) {
|
|
141
|
+
const file = await open(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
|
|
142
|
+
try {
|
|
143
|
+
await file.writeFile(`${key}\n`);
|
|
144
|
+
await file.sync();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await file.close();
|
|
148
|
+
}
|
|
149
|
+
const directory = await open(dirname(path), 'r');
|
|
150
|
+
try {
|
|
151
|
+
await directory.sync();
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await directory.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function prompt(message) {
|
|
158
|
+
const reader = createInterface({ input: process.stdin, output: process.stderr });
|
|
159
|
+
try {
|
|
160
|
+
return await reader.question(message);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
reader.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function reveal(secret) {
|
|
167
|
+
const terminal = await open('/dev/tty', 'w');
|
|
168
|
+
try {
|
|
169
|
+
await terminal.writeFile(`${secret}\n`);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
await terminal.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function hiddenPrompt(message) {
|
|
176
|
+
process.stderr.write(message);
|
|
177
|
+
const input = process.stdin;
|
|
178
|
+
const wasRaw = input.isRaw;
|
|
179
|
+
input.setRawMode(true);
|
|
180
|
+
input.resume();
|
|
181
|
+
return new Promise((accept, reject) => {
|
|
182
|
+
let value = '';
|
|
183
|
+
const finish = (cancel) => {
|
|
184
|
+
input.off('data', onData);
|
|
185
|
+
input.setRawMode(wasRaw);
|
|
186
|
+
input.pause();
|
|
187
|
+
process.stderr.write('\n');
|
|
188
|
+
if (cancel)
|
|
189
|
+
reject(new CloudflareError('confirmation_declined', 2, false, 'key_delivery'));
|
|
190
|
+
else
|
|
191
|
+
accept(value);
|
|
192
|
+
value = '';
|
|
193
|
+
};
|
|
194
|
+
const onData = (data) => {
|
|
195
|
+
for (const char of data.toString('utf8')) {
|
|
196
|
+
if (char === '\u0003' || char === '\u0004')
|
|
197
|
+
return finish(true);
|
|
198
|
+
if (char === '\r' || char === '\n')
|
|
199
|
+
return finish(false);
|
|
200
|
+
if (char === '\u007f')
|
|
201
|
+
value = value.slice(0, -1);
|
|
202
|
+
else if (value.length < 128)
|
|
203
|
+
value += char;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
input.on('data', onData);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function validateRemoteInputs(o) {
|
|
210
|
+
if ([o.keyFile, o.tokenFile, o.masterKeyEnv].some((value) => value !== undefined && value === ''))
|
|
211
|
+
invalid();
|
|
212
|
+
if (o.domain !== undefined &&
|
|
213
|
+
(o.domain.length > 253 ||
|
|
214
|
+
!o.domain.split('.').every((p) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(p)) ||
|
|
215
|
+
!o.domain.includes('.')))
|
|
216
|
+
invalid();
|
|
217
|
+
if (o.publicUrl !== undefined) {
|
|
218
|
+
let url;
|
|
219
|
+
try {
|
|
220
|
+
url = new URL(o.publicUrl);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return invalid();
|
|
224
|
+
}
|
|
225
|
+
if (url.protocol !== 'https:' ||
|
|
226
|
+
url.username ||
|
|
227
|
+
url.password ||
|
|
228
|
+
url.search ||
|
|
229
|
+
url.hash ||
|
|
230
|
+
url.port ||
|
|
231
|
+
url.pathname !== '/' ||
|
|
232
|
+
!url.hostname.includes('.') ||
|
|
233
|
+
(o.domain && url.hostname !== o.domain))
|
|
234
|
+
invalid();
|
|
235
|
+
}
|
|
236
|
+
if (o.routes !== undefined) {
|
|
237
|
+
if (!o.publicUrl)
|
|
238
|
+
return invalid();
|
|
239
|
+
const lines = o.routes.split('\n');
|
|
240
|
+
if (new Set(lines).size !== lines.length)
|
|
241
|
+
invalid();
|
|
242
|
+
for (const line of lines) {
|
|
243
|
+
const match = /^[a-f0-9]{32}:([a-zA-Z0-9*./:-]{1,256})$/.exec(line);
|
|
244
|
+
if (!match ||
|
|
245
|
+
match[1].replace(/^https:\/\//, '').split('/')[0] !== new URL(o.publicUrl).hostname)
|
|
246
|
+
invalid();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (o.rollout !== undefined && (!Number.isInteger(o.rollout) || o.rollout < 1 || o.rollout > 100))
|
|
250
|
+
invalid();
|
|
251
|
+
if (o.deadlineSeconds !== undefined &&
|
|
252
|
+
(!Number.isInteger(o.deadlineSeconds) || o.deadlineSeconds < 1 || o.deadlineSeconds > 300))
|
|
253
|
+
invalid();
|
|
254
|
+
const selectedSecrets = [...(o.secret ?? []), ...(o.secrets?.split('\n') ?? [])];
|
|
255
|
+
if (new Set(selectedSecrets).size !== selectedSecrets.length)
|
|
256
|
+
invalid();
|
|
257
|
+
for (const name of [
|
|
258
|
+
...(o.secret ?? []),
|
|
259
|
+
...(o.secrets?.split('\n') ?? []),
|
|
260
|
+
...(o.masterKeyEnv === undefined ? [] : [o.masterKeyEnv]),
|
|
261
|
+
...(o.smokeTokenEnv === undefined ? [] : [o.smokeTokenEnv]),
|
|
262
|
+
]) {
|
|
263
|
+
if (!/^[A-Z][A-Z0-9_]{0,127}$/.test(name) || name === 'PB_TOKEN_PEPPER')
|
|
264
|
+
invalid();
|
|
265
|
+
}
|
|
266
|
+
if (o.smokeTokenEnv !== undefined &&
|
|
267
|
+
(['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'].includes(o.smokeTokenEnv) || !o.smokeProject))
|
|
268
|
+
invalid();
|
|
269
|
+
if (o.smokeProject !== undefined &&
|
|
270
|
+
(!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(o.smokeProject) ||
|
|
271
|
+
['health', '_pb', 'auth', 'setup', 'callback', 'login', 'pb-smoke-unassigned'].includes(o.smokeProject)))
|
|
272
|
+
invalid();
|
|
273
|
+
if (o.keyFile !== undefined && o.masterKeyEnv !== undefined)
|
|
274
|
+
invalid();
|
|
275
|
+
}
|
|
276
|
+
function validateSmokeToken(o) {
|
|
277
|
+
const smokeName = o.smokeTokenEnv ?? 'PB_SMOKE_TOKEN';
|
|
278
|
+
const smokeToken = o.env[smokeName];
|
|
279
|
+
if (['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'].includes(smokeName) ||
|
|
280
|
+
(o.smokeTokenEnv !== undefined && !smokeToken) ||
|
|
281
|
+
(smokeToken &&
|
|
282
|
+
(!o.smokeProject ||
|
|
283
|
+
!/^[\x21-\x7e]+$/.test(smokeToken) ||
|
|
284
|
+
smokeToken === o.env.CLOUDFLARE_API_TOKEN)))
|
|
285
|
+
invalid();
|
|
286
|
+
}
|
|
287
|
+
function validateBindingCollisions(generatedNames, secretNames, endpointEnv) {
|
|
288
|
+
const plainNames = [...generatedNames, ...(endpointEnv ? [endpointEnv] : [])];
|
|
289
|
+
if (new Set(plainNames).size !== plainNames.length ||
|
|
290
|
+
secretNames.some((name) => plainNames.includes(name)))
|
|
291
|
+
invalid();
|
|
292
|
+
}
|
|
293
|
+
function validateTelemetry(endpoint, rawHeaders) {
|
|
294
|
+
try {
|
|
295
|
+
const headers = rawHeaders ? JSON.parse(rawHeaders) : {};
|
|
296
|
+
if (!headers ||
|
|
297
|
+
typeof headers !== 'object' ||
|
|
298
|
+
Array.isArray(headers) ||
|
|
299
|
+
Object.values(headers).some((value) => typeof value !== 'string'))
|
|
300
|
+
invalid();
|
|
301
|
+
createOtlpExporter({ endpoint, headers: headers });
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
invalid();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export async function setup(o, overrides = {}) {
|
|
308
|
+
const operations = {
|
|
309
|
+
build: buildSetupArtifact,
|
|
310
|
+
validateArtifact: validateDeploymentArtifact,
|
|
311
|
+
provision,
|
|
312
|
+
migrate: migrateCloudflare,
|
|
313
|
+
organization: bootstrapCloudflare,
|
|
314
|
+
deploy,
|
|
315
|
+
smoke,
|
|
316
|
+
admin: bootstrapAdmin,
|
|
317
|
+
...overrides,
|
|
318
|
+
};
|
|
319
|
+
const output = commandEnvelope('setup', o.dryRun);
|
|
320
|
+
const tty = !o.nonInteractive &&
|
|
321
|
+
!o.json &&
|
|
322
|
+
(o.tty ?? (process.stdin.isTTY === true && process.stderr.isTTY === true));
|
|
323
|
+
const ask = o.prompt ?? prompt;
|
|
324
|
+
const progress = o.progress ?? ((message) => console.error(message));
|
|
325
|
+
let prebuiltOut;
|
|
326
|
+
let transaction;
|
|
327
|
+
let stage = 'validate';
|
|
328
|
+
let completed = { exitCode: 1, output };
|
|
329
|
+
const merge = (result) => {
|
|
330
|
+
const plan = [...output.plan, ...result.output.plan];
|
|
331
|
+
const checks = [...output.checks, ...result.output.checks];
|
|
332
|
+
for (const [key, value] of Object.entries(result.output)) {
|
|
333
|
+
if (!['command', 'dry_run', 'plan', 'checks'].includes(key) &&
|
|
334
|
+
value !== null &&
|
|
335
|
+
(key !== 'prebuilt' || value !== undefined) &&
|
|
336
|
+
(!Array.isArray(value) || value.length))
|
|
337
|
+
Object.assign(output, { [key]: value });
|
|
338
|
+
}
|
|
339
|
+
output.plan = plan;
|
|
340
|
+
output.checks = checks;
|
|
341
|
+
return (completed = { exitCode: result.exitCode, output });
|
|
342
|
+
};
|
|
343
|
+
try {
|
|
344
|
+
if (o.prebuilt)
|
|
345
|
+
await assertPrebuiltConfiguration(resolve(o.project ?? '.'), o.configPath);
|
|
346
|
+
const guided = tty && (!o.workerName || !o.orgSlug || !o.orgName || !o.adminEmail);
|
|
347
|
+
const values = {
|
|
348
|
+
workerName: o.workerName,
|
|
349
|
+
orgSlug: o.orgSlug,
|
|
350
|
+
orgName: o.orgName,
|
|
351
|
+
adminEmail: o.adminEmail,
|
|
352
|
+
};
|
|
353
|
+
for (const name of ['workerName', 'orgSlug', 'orgName', 'adminEmail']) {
|
|
354
|
+
if (!values[name] && tty)
|
|
355
|
+
values[name] = await ask(`${name}: `);
|
|
356
|
+
if (!values[name])
|
|
357
|
+
invalid();
|
|
358
|
+
}
|
|
359
|
+
if (guided) {
|
|
360
|
+
o = { ...o };
|
|
361
|
+
o.environment ??= (await ask('Environment [production]: ')) || 'production';
|
|
362
|
+
o.deploymentId ??=
|
|
363
|
+
(await ask('Deployment ID [worker-environment]: ')) ||
|
|
364
|
+
`${values.workerName}-${o.environment}`;
|
|
365
|
+
if (!o.jurisdiction) {
|
|
366
|
+
const answer = (await ask('Jurisdiction [eu/default; default eu]: ')) || 'eu';
|
|
367
|
+
if (answer !== 'eu' && answer !== 'default')
|
|
368
|
+
return invalid();
|
|
369
|
+
o.jurisdiction = answer;
|
|
370
|
+
}
|
|
371
|
+
if (o.locationHint === undefined) {
|
|
372
|
+
const answer = await ask('Optional creation location hint [none]: ');
|
|
373
|
+
if (answer)
|
|
374
|
+
o.locationHint = answer;
|
|
375
|
+
}
|
|
376
|
+
if (!o.publicUrl && !o.domain) {
|
|
377
|
+
const answer = await ask('Canonical HTTPS origin [workers.dev]: ');
|
|
378
|
+
if (answer)
|
|
379
|
+
o.publicUrl = answer;
|
|
380
|
+
const domain = await ask('Optional custom domain [none]: ');
|
|
381
|
+
if (domain)
|
|
382
|
+
o.domain = domain;
|
|
383
|
+
}
|
|
384
|
+
if (!o.keyFile && !o.masterKeyEnv) {
|
|
385
|
+
const answer = (await ask('Master key delivery [terminal/key-file/env; default terminal]: ')) ||
|
|
386
|
+
'terminal';
|
|
387
|
+
if (answer === 'key-file')
|
|
388
|
+
o.keyFile = await ask('Key file outside the checkout: ');
|
|
389
|
+
else if (answer === 'env')
|
|
390
|
+
o.masterKeyEnv = await ask('Saved master key environment variable: ');
|
|
391
|
+
else if (answer !== 'terminal')
|
|
392
|
+
return invalid();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const slug = values.orgSlug, name = values.orgName, email = values.adminEmail;
|
|
396
|
+
if (!ORG_SLUG_PATTERN.test(slug) || !name.trim() || normalizeEmail(email) === null)
|
|
397
|
+
invalid();
|
|
398
|
+
validateRemoteInputs(o);
|
|
399
|
+
if (o.publicUrl !== undefined)
|
|
400
|
+
o = { ...o, publicUrl: new URL(o.publicUrl).origin };
|
|
401
|
+
const configPath = resolve(o.configPath ?? 'broker.config.ts');
|
|
402
|
+
const project = await realpath(resolve(o.project ?? dirname(configPath)));
|
|
403
|
+
let outDir = resolve(project, o.outDir ?? (o.prebuilt ? '.pb/prebuilt/cloudflare' : '.pb/cloudflare'));
|
|
404
|
+
const auth = cloudflareCredentials(o.env);
|
|
405
|
+
const identity = installIdentity({ ...o, ...auth, workerName: values.workerName });
|
|
406
|
+
const store = await InventoryStore.create({
|
|
407
|
+
project,
|
|
408
|
+
outDir,
|
|
409
|
+
identity,
|
|
410
|
+
...(o.inventory === undefined ? {} : { inventory: o.inventory }),
|
|
411
|
+
});
|
|
412
|
+
let journal = await store.read();
|
|
413
|
+
const keyState = journal?.secret_delivery.master_key ?? 'not_started';
|
|
414
|
+
const bootstrapState = journal?.bootstrap.state ?? 'not_started';
|
|
415
|
+
const needsToken = bootstrapState === 'not_started' ||
|
|
416
|
+
(o.refreshSetupToken && bootstrapState !== 'already_set_up');
|
|
417
|
+
if (needsToken && !tty && !o.tokenFile)
|
|
418
|
+
invalid();
|
|
419
|
+
if (keyState === 'not_started' && !tty && !o.keyFile && !o.masterKeyEnv)
|
|
420
|
+
invalid();
|
|
421
|
+
const checkout = (await promisify(execFile)('git', ['rev-parse', '--show-toplevel'], { cwd: project })).stdout.trim();
|
|
422
|
+
const roots = [await realpath(checkout), project, outDir, store.stateRoot];
|
|
423
|
+
if (o.keyFile)
|
|
424
|
+
await safeDeliveryPath(o.keyFile, roots, keyState !== 'not_started', false);
|
|
425
|
+
if (o.tokenFile)
|
|
426
|
+
await safeDeliveryPath(o.tokenFile, roots, !needsToken, false);
|
|
427
|
+
if (o.keyFile && o.tokenFile && resolve(o.keyFile) === resolve(o.tokenFile))
|
|
428
|
+
unsafe();
|
|
429
|
+
stage = 'build';
|
|
430
|
+
const buildEnv = setupBuildEnvironment(o.env);
|
|
431
|
+
const selectedSecrets = [...(o.secret ?? []), ...(o.secrets?.split('\n') ?? [])];
|
|
432
|
+
for (const selected of [...selectedSecrets, o.masterKeyEnv, o.smokeTokenEnv])
|
|
433
|
+
if (selected)
|
|
434
|
+
delete buildEnv[selected];
|
|
435
|
+
if (o.prebuilt) {
|
|
436
|
+
const materialized = await (operations.materializePrebuilt ?? materializePrebuilt)({
|
|
437
|
+
project,
|
|
438
|
+
outDir,
|
|
439
|
+
});
|
|
440
|
+
outDir = prebuiltOut = materialized.outDir;
|
|
441
|
+
output.prebuilt = prebuiltEnvelope(materialized);
|
|
442
|
+
}
|
|
443
|
+
else
|
|
444
|
+
await operations.build(configPath, outDir, buildEnv);
|
|
445
|
+
validateSmokeToken(o);
|
|
446
|
+
const artifact = await operations.validateArtifact(outDir);
|
|
447
|
+
assertProvidersAccepted(o.providers, artifact.identity);
|
|
448
|
+
output.build_info = artifact.info ?? null;
|
|
449
|
+
output.artifact_sha256 = artifact.artifactHash ?? null;
|
|
450
|
+
const generatedNames = [
|
|
451
|
+
'DB',
|
|
452
|
+
'DEPLOYMENT_ID',
|
|
453
|
+
'PB_PUBLIC_URL',
|
|
454
|
+
'ASSETS',
|
|
455
|
+
...(artifact.ports
|
|
456
|
+
? [
|
|
457
|
+
artifact.ports.blob.binding,
|
|
458
|
+
artifact.ports.jobs.binding,
|
|
459
|
+
artifact.ports.observability.analyticsBinding,
|
|
460
|
+
]
|
|
461
|
+
: []),
|
|
462
|
+
].filter((name) => name !== undefined);
|
|
463
|
+
const endpointEnv = artifact.ports?.observability.endpointEnv;
|
|
464
|
+
const headersEnv = artifact.ports?.observability.headersEnv;
|
|
465
|
+
validateBindingCollisions(generatedNames, [...selectedSecrets, 'PB_MASTER_KEY'], endpointEnv);
|
|
466
|
+
if (headersEnv && (headersEnv === 'PB_MASTER_KEY' || generatedNames.includes(headersEnv)))
|
|
467
|
+
invalid();
|
|
468
|
+
if (endpointEnv) {
|
|
469
|
+
const endpoint = o.env[endpointEnv];
|
|
470
|
+
if (!endpoint)
|
|
471
|
+
return invalid();
|
|
472
|
+
validateTelemetry(endpoint);
|
|
473
|
+
}
|
|
474
|
+
if (!o.dryRun) {
|
|
475
|
+
transaction = await InventoryTransaction.begin(store, (o.now ?? Date.now)());
|
|
476
|
+
if (JSON.stringify(await store.read()) !== JSON.stringify(journal))
|
|
477
|
+
throw new CloudflareError('plan_changed', 8, false, 'validate');
|
|
478
|
+
}
|
|
479
|
+
const { providers, workersPaid, ...sharedOptions } = o;
|
|
480
|
+
const common = {
|
|
481
|
+
...sharedOptions,
|
|
482
|
+
workerName: values.workerName,
|
|
483
|
+
prebuilt: false,
|
|
484
|
+
project,
|
|
485
|
+
configPath,
|
|
486
|
+
outDir,
|
|
487
|
+
...(transaction ? { transaction } : {}),
|
|
488
|
+
};
|
|
489
|
+
// The lock spans planning, key delivery, activation, smoke and admin issuance.
|
|
490
|
+
const save = async (update) => {
|
|
491
|
+
journal = await store.read();
|
|
492
|
+
if (!journal)
|
|
493
|
+
throw new CloudflareError('invalid_inventory', 2, false, stage);
|
|
494
|
+
update(journal);
|
|
495
|
+
journal.revision++;
|
|
496
|
+
journal.updated_at = new Date((o.now ?? Date.now)()).toISOString();
|
|
497
|
+
await store.write(journal);
|
|
498
|
+
};
|
|
499
|
+
stage = 'provision';
|
|
500
|
+
const planned = await operations.provision({
|
|
501
|
+
...common,
|
|
502
|
+
dryRun: true,
|
|
503
|
+
progress: () => undefined,
|
|
504
|
+
});
|
|
505
|
+
if (planned.exitCode)
|
|
506
|
+
return merge(planned);
|
|
507
|
+
merge(planned);
|
|
508
|
+
const api = new CloudflareApi({
|
|
509
|
+
...auth,
|
|
510
|
+
...(o.fetch ? { fetch: o.fetch } : {}),
|
|
511
|
+
...(o.now ? { now: o.now } : {}),
|
|
512
|
+
...(o.sleep ? { sleep: o.sleep } : {}),
|
|
513
|
+
});
|
|
514
|
+
const workers = await api.snapshot('/workers/scripts');
|
|
515
|
+
const matches = workers.filter((w) => w.id === identity.script_name);
|
|
516
|
+
if (matches.length > 1)
|
|
517
|
+
throw new CloudflareError('ownership_conflict', 8, false, stage);
|
|
518
|
+
const worker = matches[0];
|
|
519
|
+
if (worker) {
|
|
520
|
+
if ((!Array.isArray(worker.tags) ||
|
|
521
|
+
!worker.tags.includes(`pb-owner-${identity.owner_digest}`) ||
|
|
522
|
+
!worker.tags.includes('pb-install-v1')) &&
|
|
523
|
+
!o.adoptResource?.includes(`worker:${identity.script_name}`))
|
|
524
|
+
throw new CloudflareError('ownership_conflict', 8, false, stage);
|
|
525
|
+
}
|
|
526
|
+
else
|
|
527
|
+
output.plan.push({
|
|
528
|
+
stage: 'deploy',
|
|
529
|
+
action: 'create',
|
|
530
|
+
kind: 'worker',
|
|
531
|
+
name: identity.script_name,
|
|
532
|
+
});
|
|
533
|
+
const installedNames = worker
|
|
534
|
+
? (await api.snapshot(`/workers/scripts/${identity.script_name}/secrets`)).map((s) => {
|
|
535
|
+
if (s.type !== 'secret_text' ||
|
|
536
|
+
typeof s.name !== 'string' ||
|
|
537
|
+
!/^[A-Z][A-Z0-9_]{0,127}$/.test(s.name) ||
|
|
538
|
+
'text' in s ||
|
|
539
|
+
'value' in s)
|
|
540
|
+
throw new CloudflareError('configuration_conflict', 8, false, 'key_delivery');
|
|
541
|
+
return s.name;
|
|
542
|
+
})
|
|
543
|
+
: [];
|
|
544
|
+
if (new Set(installedNames).size !== installedNames.length ||
|
|
545
|
+
installedNames.includes('PB_TOKEN_PEPPER'))
|
|
546
|
+
throw new CloudflareError('configuration_conflict', 8, false, 'key_delivery');
|
|
547
|
+
const installed = installedNames.includes('PB_MASTER_KEY');
|
|
548
|
+
journal = await store.read();
|
|
549
|
+
const pending = journal?.deployment?.attempt;
|
|
550
|
+
const pendingNames = pending &&
|
|
551
|
+
['upload_pending', 'uploaded', 'configuring', 'activation_pending'].includes(pending.phase)
|
|
552
|
+
? pending.secret_names
|
|
553
|
+
: [];
|
|
554
|
+
const retainedNames = new Set([...installedNames, ...pendingNames]);
|
|
555
|
+
validateBindingCollisions(generatedNames, [...retainedNames], endpointEnv);
|
|
556
|
+
if ((o.rollout ?? 100) < 100) {
|
|
557
|
+
if (!worker)
|
|
558
|
+
invalid();
|
|
559
|
+
const current = await active(api, `/workers/scripts/${identity.script_name}`);
|
|
560
|
+
if (!current.versions.length)
|
|
561
|
+
invalid();
|
|
562
|
+
}
|
|
563
|
+
if ((!o.publicUrl && !o.domain) || (o.rollout ?? 100) < 100)
|
|
564
|
+
await workersDevOrigin(api, identity.script_name);
|
|
565
|
+
if (o.domain && !(await deploymentDomain(api, identity.script_name, o.domain))) {
|
|
566
|
+
const zone = await cloudflareDeployContract.domainZone(api, o.domain);
|
|
567
|
+
if (!/^[a-f0-9]{32}$/.test(zone))
|
|
568
|
+
throw new CloudflareError('provider_failure', 1, false, 'deploy');
|
|
569
|
+
}
|
|
570
|
+
for (const name of [...selectedSecrets, ...(headersEnv ? [headersEnv] : [])]) {
|
|
571
|
+
if (name !== 'PB_MASTER_KEY' && !retainedNames.has(name)) {
|
|
572
|
+
if (!selectedSecrets.includes(name) || !o.env[name])
|
|
573
|
+
invalid();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (endpointEnv && headersEnv && !retainedNames.has(headersEnv))
|
|
577
|
+
validateTelemetry(o.env[endpointEnv], o.env[headersEnv]);
|
|
578
|
+
const reconcileKey = pending?.secret_names.includes('PB_MASTER_KEY') &&
|
|
579
|
+
['upload_pending', 'uploaded', 'configuring', 'activation_pending'].includes(pending.phase);
|
|
580
|
+
if (!installed && !reconcileKey && keyState !== 'not_started' && !o.keyFile && !o.masterKeyEnv)
|
|
581
|
+
throw new CloudflareError('key_recovery_required', 2, false, 'key_delivery');
|
|
582
|
+
if (!installed && !reconcileKey && !tty && !o.keyFile && !o.masterKeyEnv)
|
|
583
|
+
invalid();
|
|
584
|
+
let recoveredKey;
|
|
585
|
+
if (!installed && !reconcileKey) {
|
|
586
|
+
if (o.masterKeyEnv)
|
|
587
|
+
recoveredKey = o.env[o.masterKeyEnv];
|
|
588
|
+
else if (keyState !== 'not_started' && o.keyFile) {
|
|
589
|
+
await safeDeliveryPath(o.keyFile, roots, true);
|
|
590
|
+
recoveredKey = await readSavedKey(o.keyFile);
|
|
591
|
+
}
|
|
592
|
+
if (o.masterKeyEnv || (keyState !== 'not_started' && o.keyFile)) {
|
|
593
|
+
try {
|
|
594
|
+
if (!recoveredKey)
|
|
595
|
+
throw new Error();
|
|
596
|
+
parseMasterKey(recoveredKey, 'PB_MASTER_KEY').bytes.fill(0);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
throw new CloudflareError('invalid_input', 2, false, 'key_delivery');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
progress(JSON.stringify(output.plan));
|
|
604
|
+
if (o.dryRun) {
|
|
605
|
+
output.plan.push(...['migrate', 'org_bootstrap', 'key_delivery', 'deploy', 'smoke', 'admin_bootstrap'].map((stage) => ({ stage, action: 'plan' })));
|
|
606
|
+
output.status = 'planned';
|
|
607
|
+
return (completed = { exitCode: 0, output });
|
|
608
|
+
}
|
|
609
|
+
if (output.plan.some((entry) => entry.action === 'create') && !o.yes) {
|
|
610
|
+
if (!tty)
|
|
611
|
+
throw new CloudflareError('confirmation_required', 2, false, 'provision');
|
|
612
|
+
if ((await ask('Type yes to create these resources: ')) !== 'yes')
|
|
613
|
+
throw new CloudflareError('confirmation_declined', 2, false, 'provision');
|
|
614
|
+
}
|
|
615
|
+
const provisioned = await operations.provision({
|
|
616
|
+
...common,
|
|
617
|
+
yes: true,
|
|
618
|
+
dryRun: false,
|
|
619
|
+
progress: () => undefined,
|
|
620
|
+
});
|
|
621
|
+
if (provisioned.exitCode)
|
|
622
|
+
return merge(provisioned);
|
|
623
|
+
merge(provisioned);
|
|
624
|
+
const databaseId = provisioned.output.database_id;
|
|
625
|
+
if (!databaseId)
|
|
626
|
+
return invalid();
|
|
627
|
+
stage = 'migrate';
|
|
628
|
+
const migrated = await operations.migrate({
|
|
629
|
+
...common,
|
|
630
|
+
databaseId,
|
|
631
|
+
outDir,
|
|
632
|
+
dryRun: false,
|
|
633
|
+
verifyOnly: o.migrate === false,
|
|
634
|
+
});
|
|
635
|
+
if (migrated.exitCode)
|
|
636
|
+
return merge(migrated);
|
|
637
|
+
merge(migrated);
|
|
638
|
+
if (o.migrate === false &&
|
|
639
|
+
(migrated.value === null || migrated.value.pending.length || migrated.value.unknown.length))
|
|
640
|
+
throw new CloudflareError('configuration_conflict', 11, false, stage);
|
|
641
|
+
stage = 'org_bootstrap';
|
|
642
|
+
const organization = await operations.organization({
|
|
643
|
+
databaseId,
|
|
644
|
+
slug,
|
|
645
|
+
name,
|
|
646
|
+
env: o.env,
|
|
647
|
+
...(o.fetch ? { fetch: o.fetch } : {}),
|
|
648
|
+
});
|
|
649
|
+
if (organization.exitCode)
|
|
650
|
+
return merge(organization);
|
|
651
|
+
stage = 'key_delivery';
|
|
652
|
+
let key = recoveredKey;
|
|
653
|
+
if (installed) {
|
|
654
|
+
await save((j) => {
|
|
655
|
+
j.secret_delivery.master_key = 'installed';
|
|
656
|
+
});
|
|
657
|
+
progress('Existing master key secret retained.');
|
|
658
|
+
}
|
|
659
|
+
else if (!reconcileKey) {
|
|
660
|
+
if (!key) {
|
|
661
|
+
if (keyState !== 'not_started')
|
|
662
|
+
throw new CloudflareError('key_recovery_required', 2, false, stage);
|
|
663
|
+
await save((j) => {
|
|
664
|
+
j.secret_delivery.master_key = 'export_pending';
|
|
665
|
+
});
|
|
666
|
+
const generated = generateMasterKey(1);
|
|
667
|
+
key = formatMasterKey(generated);
|
|
668
|
+
generated.bytes.fill(0);
|
|
669
|
+
if (o.keyFile) {
|
|
670
|
+
await safeDeliveryPath(o.keyFile, roots, false);
|
|
671
|
+
await writeKey(o.keyFile, key);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
if (!tty)
|
|
675
|
+
invalid();
|
|
676
|
+
await (o.reveal ?? reveal)(key);
|
|
677
|
+
progress('Store this key in a secret manager. It is revealed once.');
|
|
678
|
+
if ((await (o.hiddenPrompt ?? hiddenPrompt)('Re-enter the last 6 characters (hidden): ')) !== key.slice(-6))
|
|
679
|
+
throw new CloudflareError('confirmation_declined', 2, false, stage);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (!key)
|
|
683
|
+
return invalid();
|
|
684
|
+
try {
|
|
685
|
+
parseMasterKey(key, 'PB_MASTER_KEY').bytes.fill(0);
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
return (completed = failCommand(output, new CloudflareError('invalid_input', 2, false, stage)));
|
|
689
|
+
}
|
|
690
|
+
await save((j) => {
|
|
691
|
+
j.secret_delivery.master_key = 'exported';
|
|
692
|
+
});
|
|
693
|
+
if (o.keyFile)
|
|
694
|
+
progress('Move the key to a secret manager, verify storage, then delete the local key file. Setup does not delete it.');
|
|
695
|
+
await save((j) => {
|
|
696
|
+
j.secret_delivery.master_key = 'upload_pending';
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
stage = 'deploy';
|
|
700
|
+
const deployCommon = { ...common };
|
|
701
|
+
delete deployCommon.secrets;
|
|
702
|
+
const deployEnv = {
|
|
703
|
+
CLOUDFLARE_API_TOKEN: auth.apiToken,
|
|
704
|
+
CLOUDFLARE_ACCOUNT_ID: auth.accountId,
|
|
705
|
+
PB_MASTER_KEY: key,
|
|
706
|
+
};
|
|
707
|
+
for (const name of selectedSecrets) {
|
|
708
|
+
if (name !== 'PB_MASTER_KEY' && !retainedNames.has(name))
|
|
709
|
+
deployEnv[name] = o.env[name];
|
|
710
|
+
}
|
|
711
|
+
if (endpointEnv)
|
|
712
|
+
deployEnv[endpointEnv] = o.env[endpointEnv];
|
|
713
|
+
const deployOptions = {
|
|
714
|
+
contract: cloudflareDeployContract,
|
|
715
|
+
...deployCommon,
|
|
716
|
+
...(providers === undefined ? {} : { providers }),
|
|
717
|
+
...(workersPaid === undefined ? {} : { workersPaid }),
|
|
718
|
+
env: deployEnv,
|
|
719
|
+
secret: selectedSecrets
|
|
720
|
+
.filter((s) => s !== 'PB_MASTER_KEY' && !retainedNames.has(s))
|
|
721
|
+
.concat(key ? ['PB_MASTER_KEY'] : []),
|
|
722
|
+
};
|
|
723
|
+
const deployed = await operations.deploy(deployOptions);
|
|
724
|
+
key = undefined;
|
|
725
|
+
recoveredKey = undefined;
|
|
726
|
+
deployEnv.PB_MASTER_KEY = undefined;
|
|
727
|
+
if (deployed.exitCode)
|
|
728
|
+
return merge(deployed);
|
|
729
|
+
merge(deployed);
|
|
730
|
+
await save((j) => {
|
|
731
|
+
j.secret_delivery.master_key = 'installed';
|
|
732
|
+
});
|
|
733
|
+
stage = 'smoke';
|
|
734
|
+
const smokeCommon = { ...common };
|
|
735
|
+
delete smokeCommon.project;
|
|
736
|
+
const smokeOptions = {
|
|
737
|
+
...smokeCommon,
|
|
738
|
+
projectRoot: project,
|
|
739
|
+
...(o.smokeProject ? { project: o.smokeProject } : {}),
|
|
740
|
+
};
|
|
741
|
+
const smoked = await operations.smoke(smokeOptions);
|
|
742
|
+
if (smoked.exitCode)
|
|
743
|
+
return merge(smoked);
|
|
744
|
+
merge(smoked);
|
|
745
|
+
stage = 'admin_bootstrap';
|
|
746
|
+
journal = await store.read();
|
|
747
|
+
const state = journal?.bootstrap.state;
|
|
748
|
+
if ((state === 'issuing' || state === 'unknown') && !o.refreshSetupToken)
|
|
749
|
+
throw new CloudflareError('token_refresh_required', 7, false, stage);
|
|
750
|
+
if (state === 'not_started' ||
|
|
751
|
+
((state === 'issued' || state === 'issuing' || state === 'unknown') && o.refreshSetupToken)) {
|
|
752
|
+
if (o.tokenFile)
|
|
753
|
+
await safeDeliveryPath(o.tokenFile, roots, false);
|
|
754
|
+
await save((j) => {
|
|
755
|
+
j.bootstrap.state = 'issuing';
|
|
756
|
+
});
|
|
757
|
+
const result = await operations.admin({
|
|
758
|
+
email,
|
|
759
|
+
target: {
|
|
760
|
+
kind: 'cloudflare',
|
|
761
|
+
databaseId,
|
|
762
|
+
env: o.env,
|
|
763
|
+
...(o.fetch ? { fetch: o.fetch } : {}),
|
|
764
|
+
},
|
|
765
|
+
...(o.tokenFile ? { tokenFile: o.tokenFile } : {}),
|
|
766
|
+
stdoutIsTTY: tty,
|
|
767
|
+
}, {
|
|
768
|
+
diagnostic: () => undefined,
|
|
769
|
+
output: () => undefined,
|
|
770
|
+
...(!o.tokenFile ? { deliver: o.reveal ?? reveal } : {}),
|
|
771
|
+
});
|
|
772
|
+
if (result.state === 'issued' || result.state === 'already_set_up') {
|
|
773
|
+
await save((j) => {
|
|
774
|
+
j.bootstrap = {
|
|
775
|
+
state: result.state === 'issued' ? 'issued' : 'already_set_up',
|
|
776
|
+
expires_at: result.expiresAt,
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
await save((j) => {
|
|
782
|
+
j.bootstrap.state = 'unknown';
|
|
783
|
+
});
|
|
784
|
+
throw new CloudflareError(result.exitCode === 5
|
|
785
|
+
? 'organization_not_found'
|
|
786
|
+
: result.exitCode === 7
|
|
787
|
+
? 'outcome_unknown'
|
|
788
|
+
: 'provider_failure', result.exitCode || 1, false, stage);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (!output.worker_url)
|
|
792
|
+
throw new CloudflareError('configuration_conflict', 8, false, 'deploy');
|
|
793
|
+
const url = new URL(output.worker_url);
|
|
794
|
+
if (url.protocol !== 'https:' ||
|
|
795
|
+
url.username ||
|
|
796
|
+
url.password ||
|
|
797
|
+
url.search ||
|
|
798
|
+
url.hash ||
|
|
799
|
+
url.pathname !== '/')
|
|
800
|
+
throw new CloudflareError('configuration_conflict', 8, false, 'deploy');
|
|
801
|
+
output.bootstrap_url = `${url.origin}/_pb/ui/setup`;
|
|
802
|
+
journal = await store.read();
|
|
803
|
+
if (journal?.bootstrap.expires_at)
|
|
804
|
+
progress(`Setup token expires at ${journal.bootstrap.expires_at}. Use --refresh-setup-token explicitly if a new token is needed.`);
|
|
805
|
+
output.status = 'ok';
|
|
806
|
+
progress('Open bootstrap_url and paste the separately delivered setup token. Curl alternative: POST /_pb/auth/local/setup/start {"token"}, then POST /_pb/auth/local/setup/finish {"token","password","code"}; use the broker Origin and application/json. Keep credential request and response files private; never print the TOTP response. See docs/workers-install.md.');
|
|
807
|
+
return (completed = { exitCode: 0, output });
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
return (completed = failCommand(output, error instanceof CloudflareError
|
|
811
|
+
? error
|
|
812
|
+
: new CloudflareError(stage === 'admin_bootstrap' ? 'outcome_unknown' : 'provider_failure', stage === 'admin_bootstrap' ? 7 : 1, false, stage)));
|
|
813
|
+
}
|
|
814
|
+
finally {
|
|
815
|
+
if (prebuiltOut)
|
|
816
|
+
await removePrebuilt(prebuiltOut).catch(() => undefined);
|
|
817
|
+
try {
|
|
818
|
+
await transaction?.close();
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
const failure = failCommand(output, new CloudflareError('install_locked', 4, false, stage));
|
|
822
|
+
completed.exitCode = failure.exitCode;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
export async function runCloudflareSetup(o) {
|
|
827
|
+
const result = await setup(o);
|
|
828
|
+
if (o.json)
|
|
829
|
+
console.log(JSON.stringify(result.output));
|
|
830
|
+
else if (result.exitCode)
|
|
831
|
+
console.error(result.output.error?.message);
|
|
832
|
+
else
|
|
833
|
+
console.log(JSON.stringify(result.output));
|
|
834
|
+
return result.exitCode;
|
|
835
|
+
}
|
|
836
|
+
//# sourceMappingURL=setup.js.map
|