@ascorbic/pds 0.0.0
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 +213 -0
- package/dist/cli.js +528 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3130 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { experimental_patchConfig, experimental_readRawConfig } from "wrangler";
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import bcrypt from "bcryptjs";
|
|
10
|
+
import { Secp256k1Keypair } from "@atproto/crypto";
|
|
11
|
+
|
|
12
|
+
//#region src/cli/utils/wrangler.ts
|
|
13
|
+
/**
|
|
14
|
+
* Wrangler integration utilities for setting vars and secrets
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Set a var in wrangler.jsonc using experimental_patchConfig
|
|
18
|
+
*/
|
|
19
|
+
function setVar(name, value) {
|
|
20
|
+
const { configPath } = experimental_readRawConfig({});
|
|
21
|
+
if (!configPath) throw new Error("No wrangler config found");
|
|
22
|
+
experimental_patchConfig(configPath, { vars: { [name]: value } });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set multiple vars in wrangler.jsonc
|
|
26
|
+
*/
|
|
27
|
+
function setVars(vars) {
|
|
28
|
+
const { configPath } = experimental_readRawConfig({});
|
|
29
|
+
if (!configPath) throw new Error("No wrangler config found");
|
|
30
|
+
experimental_patchConfig(configPath, { vars });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get current vars from wrangler config
|
|
34
|
+
*/
|
|
35
|
+
function getVars() {
|
|
36
|
+
const { rawConfig } = experimental_readRawConfig({});
|
|
37
|
+
return rawConfig.vars || {};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Set a secret using wrangler secret put
|
|
41
|
+
*/
|
|
42
|
+
async function setSecret(name, value) {
|
|
43
|
+
return new Promise((resolve$1, reject) => {
|
|
44
|
+
const child = spawn("wrangler", [
|
|
45
|
+
"secret",
|
|
46
|
+
"put",
|
|
47
|
+
name
|
|
48
|
+
], { stdio: [
|
|
49
|
+
"pipe",
|
|
50
|
+
"inherit",
|
|
51
|
+
"inherit"
|
|
52
|
+
] });
|
|
53
|
+
child.stdin.write(value);
|
|
54
|
+
child.stdin.end();
|
|
55
|
+
child.on("close", (code) => {
|
|
56
|
+
if (code === 0) resolve$1();
|
|
57
|
+
else reject(/* @__PURE__ */ new Error(`wrangler secret put ${name} failed with code ${code}`));
|
|
58
|
+
});
|
|
59
|
+
child.on("error", reject);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/cli/utils/dotenv.ts
|
|
65
|
+
/**
|
|
66
|
+
* .dev.vars file utilities for local development
|
|
67
|
+
*/
|
|
68
|
+
const DEV_VARS_FILE = ".dev.vars";
|
|
69
|
+
/**
|
|
70
|
+
* Parse a .dev.vars file into a record
|
|
71
|
+
*/
|
|
72
|
+
function readDevVars(dir = process.cwd()) {
|
|
73
|
+
const filePath = resolve(dir, DEV_VARS_FILE);
|
|
74
|
+
if (!existsSync(filePath)) return {};
|
|
75
|
+
const content = readFileSync(filePath, "utf-8");
|
|
76
|
+
const vars = {};
|
|
77
|
+
for (const line of content.split("\n")) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
80
|
+
const eqIndex = trimmed.indexOf("=");
|
|
81
|
+
if (eqIndex === -1) continue;
|
|
82
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
83
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
84
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
85
|
+
vars[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return vars;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Quote a value if it contains special characters
|
|
91
|
+
*/
|
|
92
|
+
function quoteValue(value) {
|
|
93
|
+
if (value.includes(" ") || value.includes("\"") || value.includes("'")) return "\"" + value.replace(/"/g, "\\\"") + "\"";
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Write vars to .dev.vars file, preserving comments and order
|
|
98
|
+
*/
|
|
99
|
+
function writeDevVars(vars, dir = process.cwd()) {
|
|
100
|
+
const filePath = resolve(dir, DEV_VARS_FILE);
|
|
101
|
+
let existingLines = [];
|
|
102
|
+
if (existsSync(filePath)) existingLines = readFileSync(filePath, "utf-8").split("\n");
|
|
103
|
+
const outputLines = [];
|
|
104
|
+
const updatedKeys = /* @__PURE__ */ new Set();
|
|
105
|
+
for (const line of existingLines) {
|
|
106
|
+
const trimmed = line.trim();
|
|
107
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
108
|
+
outputLines.push(line);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const eqIndex = trimmed.indexOf("=");
|
|
112
|
+
if (eqIndex === -1) {
|
|
113
|
+
outputLines.push(line);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
117
|
+
if (key in vars) {
|
|
118
|
+
outputLines.push(key + "=" + quoteValue(vars[key]));
|
|
119
|
+
updatedKeys.add(key);
|
|
120
|
+
} else outputLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
for (const [key, value] of Object.entries(vars)) if (!updatedKeys.has(key)) outputLines.push(key + "=" + quoteValue(value));
|
|
123
|
+
writeFileSync(filePath, outputLines.join("\n").trimEnd() + "\n");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Set a single var in .dev.vars
|
|
127
|
+
*/
|
|
128
|
+
function setDevVar(key, value, dir = process.cwd()) {
|
|
129
|
+
const vars = readDevVars(dir);
|
|
130
|
+
vars[key] = value;
|
|
131
|
+
writeDevVars(vars, dir);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/cli/commands/secret/jwt.ts
|
|
136
|
+
/**
|
|
137
|
+
* JWT secret generation command
|
|
138
|
+
*/
|
|
139
|
+
const jwtCommand = defineCommand({
|
|
140
|
+
meta: {
|
|
141
|
+
name: "jwt",
|
|
142
|
+
description: "Generate and set JWT signing secret"
|
|
143
|
+
},
|
|
144
|
+
args: { local: {
|
|
145
|
+
type: "boolean",
|
|
146
|
+
description: "Write to .dev.vars instead of wrangler secrets",
|
|
147
|
+
default: false
|
|
148
|
+
} },
|
|
149
|
+
async run({ args }) {
|
|
150
|
+
p.intro("Generate JWT Secret");
|
|
151
|
+
const secret = randomBytes(32).toString("base64");
|
|
152
|
+
if (args.local) {
|
|
153
|
+
setDevVar("JWT_SECRET", secret);
|
|
154
|
+
p.outro("JWT_SECRET written to .dev.vars");
|
|
155
|
+
} else {
|
|
156
|
+
const spinner = p.spinner();
|
|
157
|
+
spinner.start("Setting JWT_SECRET via wrangler...");
|
|
158
|
+
try {
|
|
159
|
+
await setSecret("JWT_SECRET", secret);
|
|
160
|
+
spinner.stop("JWT_SECRET set successfully");
|
|
161
|
+
p.outro("Done!");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
spinner.stop("Failed to set JWT_SECRET");
|
|
164
|
+
p.log.error(String(error));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/cli/commands/secret/password.ts
|
|
173
|
+
/**
|
|
174
|
+
* Password hash generation command
|
|
175
|
+
*/
|
|
176
|
+
const passwordCommand = defineCommand({
|
|
177
|
+
meta: {
|
|
178
|
+
name: "password",
|
|
179
|
+
description: "Set account password (stored as bcrypt hash)"
|
|
180
|
+
},
|
|
181
|
+
args: { local: {
|
|
182
|
+
type: "boolean",
|
|
183
|
+
description: "Write to .dev.vars instead of wrangler secrets",
|
|
184
|
+
default: false
|
|
185
|
+
} },
|
|
186
|
+
async run({ args }) {
|
|
187
|
+
p.intro("Set Account Password");
|
|
188
|
+
const password = await p.password({ message: "Enter password:" });
|
|
189
|
+
if (p.isCancel(password)) {
|
|
190
|
+
p.cancel("Cancelled");
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
const confirm = await p.password({ message: "Confirm password:" });
|
|
194
|
+
if (p.isCancel(confirm)) {
|
|
195
|
+
p.cancel("Cancelled");
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
if (password !== confirm) {
|
|
199
|
+
p.log.error("Passwords do not match");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const spinner = p.spinner();
|
|
203
|
+
spinner.start("Hashing password...");
|
|
204
|
+
const passwordHash = await bcrypt.hash(password, 10);
|
|
205
|
+
spinner.stop("Password hashed");
|
|
206
|
+
if (args.local) {
|
|
207
|
+
setDevVar("PASSWORD_HASH", passwordHash);
|
|
208
|
+
p.outro("PASSWORD_HASH written to .dev.vars");
|
|
209
|
+
} else {
|
|
210
|
+
spinner.start("Setting PASSWORD_HASH via wrangler...");
|
|
211
|
+
try {
|
|
212
|
+
await setSecret("PASSWORD_HASH", passwordHash);
|
|
213
|
+
spinner.stop("PASSWORD_HASH set successfully");
|
|
214
|
+
p.outro("Done!");
|
|
215
|
+
} catch (error) {
|
|
216
|
+
spinner.stop("Failed to set PASSWORD_HASH");
|
|
217
|
+
p.log.error(String(error));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/cli/commands/secret/key.ts
|
|
226
|
+
/**
|
|
227
|
+
* Signing key generation command
|
|
228
|
+
*/
|
|
229
|
+
const keyCommand = defineCommand({
|
|
230
|
+
meta: {
|
|
231
|
+
name: "key",
|
|
232
|
+
description: "Generate and set signing keypair"
|
|
233
|
+
},
|
|
234
|
+
args: { local: {
|
|
235
|
+
type: "boolean",
|
|
236
|
+
description: "Write to .dev.vars instead of wrangler secrets/config",
|
|
237
|
+
default: false
|
|
238
|
+
} },
|
|
239
|
+
async run({ args }) {
|
|
240
|
+
p.intro("Generate Signing Keypair");
|
|
241
|
+
const spinner = p.spinner();
|
|
242
|
+
spinner.start("Generating secp256k1 keypair...");
|
|
243
|
+
const keypair = await Secp256k1Keypair.create({ exportable: true });
|
|
244
|
+
const privateKeyJwk = await keypair.export();
|
|
245
|
+
const publicKeyMultibase = keypair.did().replace("did:key:", "");
|
|
246
|
+
spinner.stop("Keypair generated");
|
|
247
|
+
const privateKeyJson = JSON.stringify(privateKeyJwk);
|
|
248
|
+
if (args.local) {
|
|
249
|
+
setDevVar("SIGNING_KEY", privateKeyJson);
|
|
250
|
+
setDevVar("SIGNING_KEY_PUBLIC", publicKeyMultibase);
|
|
251
|
+
p.outro("SIGNING_KEY and SIGNING_KEY_PUBLIC written to .dev.vars");
|
|
252
|
+
} else {
|
|
253
|
+
spinner.start("Setting SIGNING_KEY via wrangler secret...");
|
|
254
|
+
try {
|
|
255
|
+
await setSecret("SIGNING_KEY", privateKeyJson);
|
|
256
|
+
spinner.stop("SIGNING_KEY set");
|
|
257
|
+
spinner.start("Setting SIGNING_KEY_PUBLIC in wrangler.jsonc...");
|
|
258
|
+
setVar("SIGNING_KEY_PUBLIC", publicKeyMultibase);
|
|
259
|
+
spinner.stop("SIGNING_KEY_PUBLIC set");
|
|
260
|
+
p.outro("Done!");
|
|
261
|
+
} catch (error) {
|
|
262
|
+
spinner.stop("Failed");
|
|
263
|
+
p.log.error(String(error));
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
p.log.info("Public key (for DID document): " + publicKeyMultibase);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/cli/commands/secret/index.ts
|
|
273
|
+
/**
|
|
274
|
+
* Secret management commands
|
|
275
|
+
*/
|
|
276
|
+
const secretCommand = defineCommand({
|
|
277
|
+
meta: {
|
|
278
|
+
name: "secret",
|
|
279
|
+
description: "Manage PDS secrets"
|
|
280
|
+
},
|
|
281
|
+
subCommands: {
|
|
282
|
+
jwt: jwtCommand,
|
|
283
|
+
password: passwordCommand,
|
|
284
|
+
key: keyCommand
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/cli/commands/init.ts
|
|
290
|
+
/**
|
|
291
|
+
* Interactive PDS setup wizard
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* Run wrangler types to regenerate TypeScript types
|
|
295
|
+
*/
|
|
296
|
+
function runWranglerTypes() {
|
|
297
|
+
return new Promise((resolve$1, reject) => {
|
|
298
|
+
const child = spawn("wrangler", ["types"], { stdio: "inherit" });
|
|
299
|
+
child.on("close", (code) => {
|
|
300
|
+
if (code === 0) resolve$1();
|
|
301
|
+
else reject(/* @__PURE__ */ new Error(`wrangler types failed with code ${code}`));
|
|
302
|
+
});
|
|
303
|
+
child.on("error", reject);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const initCommand = defineCommand({
|
|
307
|
+
meta: {
|
|
308
|
+
name: "init",
|
|
309
|
+
description: "Interactive PDS setup wizard"
|
|
310
|
+
},
|
|
311
|
+
args: { production: {
|
|
312
|
+
type: "boolean",
|
|
313
|
+
description: "Deploy secrets to Cloudflare (prompts to reuse .dev.vars values)",
|
|
314
|
+
default: false
|
|
315
|
+
} },
|
|
316
|
+
async run({ args }) {
|
|
317
|
+
p.intro("PDS Setup Wizard");
|
|
318
|
+
const isProduction = args.production;
|
|
319
|
+
if (isProduction) p.log.info("Production mode: secrets will be deployed via wrangler");
|
|
320
|
+
const wranglerVars = getVars();
|
|
321
|
+
const devVars = readDevVars();
|
|
322
|
+
const currentVars = {
|
|
323
|
+
...devVars,
|
|
324
|
+
...wranglerVars
|
|
325
|
+
};
|
|
326
|
+
const hostname = await p.text({
|
|
327
|
+
message: "PDS hostname:",
|
|
328
|
+
placeholder: "pds.example.com",
|
|
329
|
+
initialValue: currentVars.PDS_HOSTNAME || "",
|
|
330
|
+
validate: (v) => !v ? "Hostname is required" : void 0
|
|
331
|
+
});
|
|
332
|
+
if (p.isCancel(hostname)) {
|
|
333
|
+
p.cancel("Cancelled");
|
|
334
|
+
process.exit(0);
|
|
335
|
+
}
|
|
336
|
+
const handle = await p.text({
|
|
337
|
+
message: "Account handle:",
|
|
338
|
+
placeholder: "alice." + hostname,
|
|
339
|
+
initialValue: currentVars.HANDLE || "",
|
|
340
|
+
validate: (v) => !v ? "Handle is required" : void 0
|
|
341
|
+
});
|
|
342
|
+
if (p.isCancel(handle)) {
|
|
343
|
+
p.cancel("Cancelled");
|
|
344
|
+
process.exit(0);
|
|
345
|
+
}
|
|
346
|
+
const didDefault = "did:web:" + hostname;
|
|
347
|
+
const did = await p.text({
|
|
348
|
+
message: "Account DID:",
|
|
349
|
+
placeholder: didDefault,
|
|
350
|
+
initialValue: currentVars.DID || didDefault,
|
|
351
|
+
validate: (v) => {
|
|
352
|
+
if (!v) return "DID is required";
|
|
353
|
+
if (!v.startsWith("did:")) return "DID must start with did:";
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
if (p.isCancel(did)) {
|
|
357
|
+
p.cancel("Cancelled");
|
|
358
|
+
process.exit(0);
|
|
359
|
+
}
|
|
360
|
+
const spinner = p.spinner();
|
|
361
|
+
let authToken;
|
|
362
|
+
let signingKey;
|
|
363
|
+
let signingKeyPublic;
|
|
364
|
+
let jwtSecret;
|
|
365
|
+
let passwordHash;
|
|
366
|
+
if (isProduction) {
|
|
367
|
+
authToken = await getOrGenerateSecret("AUTH_TOKEN", devVars, async () => {
|
|
368
|
+
spinner.start("Generating auth token...");
|
|
369
|
+
const token = randomBytes(32).toString("base64url");
|
|
370
|
+
spinner.stop("Auth token generated");
|
|
371
|
+
return token;
|
|
372
|
+
});
|
|
373
|
+
signingKey = await getOrGenerateSecret("SIGNING_KEY", devVars, async () => {
|
|
374
|
+
spinner.start("Generating signing keypair...");
|
|
375
|
+
const keypair = await Secp256k1Keypair.create({ exportable: true });
|
|
376
|
+
const key = JSON.stringify(await keypair.export());
|
|
377
|
+
spinner.stop("Signing keypair generated");
|
|
378
|
+
return key;
|
|
379
|
+
});
|
|
380
|
+
signingKeyPublic = (await Secp256k1Keypair.import(JSON.parse(signingKey))).did().replace("did:key:", "");
|
|
381
|
+
jwtSecret = await getOrGenerateSecret("JWT_SECRET", devVars, async () => {
|
|
382
|
+
spinner.start("Generating JWT secret...");
|
|
383
|
+
const secret = randomBytes(32).toString("base64");
|
|
384
|
+
spinner.stop("JWT secret generated");
|
|
385
|
+
return secret;
|
|
386
|
+
});
|
|
387
|
+
passwordHash = await getOrGenerateSecret("PASSWORD_HASH", devVars, async () => {
|
|
388
|
+
const password = await p.password({ message: "Account password:" });
|
|
389
|
+
if (p.isCancel(password)) {
|
|
390
|
+
p.cancel("Cancelled");
|
|
391
|
+
process.exit(0);
|
|
392
|
+
}
|
|
393
|
+
const confirm = await p.password({ message: "Confirm password:" });
|
|
394
|
+
if (p.isCancel(confirm)) {
|
|
395
|
+
p.cancel("Cancelled");
|
|
396
|
+
process.exit(0);
|
|
397
|
+
}
|
|
398
|
+
if (password !== confirm) {
|
|
399
|
+
p.log.error("Passwords do not match");
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
spinner.start("Hashing password...");
|
|
403
|
+
const hash = await bcrypt.hash(password, 10);
|
|
404
|
+
spinner.stop("Password hashed");
|
|
405
|
+
return hash;
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
const password = await p.password({ message: "Account password:" });
|
|
409
|
+
if (p.isCancel(password)) {
|
|
410
|
+
p.cancel("Cancelled");
|
|
411
|
+
process.exit(0);
|
|
412
|
+
}
|
|
413
|
+
const confirm = await p.password({ message: "Confirm password:" });
|
|
414
|
+
if (p.isCancel(confirm)) {
|
|
415
|
+
p.cancel("Cancelled");
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
418
|
+
if (password !== confirm) {
|
|
419
|
+
p.log.error("Passwords do not match");
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
spinner.start("Hashing password...");
|
|
423
|
+
passwordHash = await bcrypt.hash(password, 10);
|
|
424
|
+
spinner.stop("Password hashed");
|
|
425
|
+
spinner.start("Generating JWT secret...");
|
|
426
|
+
jwtSecret = randomBytes(32).toString("base64");
|
|
427
|
+
spinner.stop("JWT secret generated");
|
|
428
|
+
spinner.start("Generating auth token...");
|
|
429
|
+
authToken = randomBytes(32).toString("base64url");
|
|
430
|
+
spinner.stop("Auth token generated");
|
|
431
|
+
spinner.start("Generating signing keypair...");
|
|
432
|
+
const keypair = await Secp256k1Keypair.create({ exportable: true });
|
|
433
|
+
signingKey = JSON.stringify(await keypair.export());
|
|
434
|
+
signingKeyPublic = keypair.did().replace("did:key:", "");
|
|
435
|
+
spinner.stop("Signing keypair generated");
|
|
436
|
+
}
|
|
437
|
+
spinner.start("Updating wrangler.jsonc...");
|
|
438
|
+
setVars({
|
|
439
|
+
PDS_HOSTNAME: hostname,
|
|
440
|
+
DID: did,
|
|
441
|
+
HANDLE: handle,
|
|
442
|
+
SIGNING_KEY_PUBLIC: signingKeyPublic
|
|
443
|
+
});
|
|
444
|
+
spinner.stop("wrangler.jsonc updated");
|
|
445
|
+
if (isProduction) {
|
|
446
|
+
spinner.start("Setting AUTH_TOKEN...");
|
|
447
|
+
await setSecret("AUTH_TOKEN", authToken);
|
|
448
|
+
spinner.stop("AUTH_TOKEN set");
|
|
449
|
+
spinner.start("Setting SIGNING_KEY...");
|
|
450
|
+
await setSecret("SIGNING_KEY", signingKey);
|
|
451
|
+
spinner.stop("SIGNING_KEY set");
|
|
452
|
+
spinner.start("Setting JWT_SECRET...");
|
|
453
|
+
await setSecret("JWT_SECRET", jwtSecret);
|
|
454
|
+
spinner.stop("JWT_SECRET set");
|
|
455
|
+
spinner.start("Setting PASSWORD_HASH...");
|
|
456
|
+
await setSecret("PASSWORD_HASH", passwordHash);
|
|
457
|
+
spinner.stop("PASSWORD_HASH set");
|
|
458
|
+
} else {
|
|
459
|
+
spinner.start("Writing secrets to .dev.vars...");
|
|
460
|
+
writeDevVars({
|
|
461
|
+
AUTH_TOKEN: authToken,
|
|
462
|
+
SIGNING_KEY: signingKey,
|
|
463
|
+
JWT_SECRET: jwtSecret,
|
|
464
|
+
PASSWORD_HASH: passwordHash
|
|
465
|
+
});
|
|
466
|
+
spinner.stop("Secrets written to .dev.vars");
|
|
467
|
+
}
|
|
468
|
+
spinner.start("Generating TypeScript types...");
|
|
469
|
+
try {
|
|
470
|
+
await runWranglerTypes();
|
|
471
|
+
spinner.stop("TypeScript types generated");
|
|
472
|
+
} catch {
|
|
473
|
+
spinner.stop("Failed to generate types (wrangler types)");
|
|
474
|
+
}
|
|
475
|
+
p.note([
|
|
476
|
+
"Configuration summary:",
|
|
477
|
+
"",
|
|
478
|
+
" PDS_HOSTNAME: " + hostname,
|
|
479
|
+
" DID: " + did,
|
|
480
|
+
" HANDLE: " + handle,
|
|
481
|
+
" SIGNING_KEY_PUBLIC: " + signingKeyPublic,
|
|
482
|
+
"",
|
|
483
|
+
isProduction ? "Secrets deployed to Cloudflare" : "Secrets saved to .dev.vars",
|
|
484
|
+
"",
|
|
485
|
+
"Auth token (save this!):",
|
|
486
|
+
" " + authToken
|
|
487
|
+
].join("\n"), "Setup Complete");
|
|
488
|
+
if (isProduction) p.outro("Your PDS is configured! Run 'wrangler deploy' to deploy.");
|
|
489
|
+
else p.outro("Your PDS is configured! Run 'pnpm dev' to start locally.");
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
/**
|
|
493
|
+
* Helper to get a secret from .dev.vars or generate a new one
|
|
494
|
+
*/
|
|
495
|
+
async function getOrGenerateSecret(name, devVars, generate) {
|
|
496
|
+
if (devVars[name]) {
|
|
497
|
+
const useExisting = await p.confirm({
|
|
498
|
+
message: `Use ${name} from .dev.vars?`,
|
|
499
|
+
initialValue: true
|
|
500
|
+
});
|
|
501
|
+
if (p.isCancel(useExisting)) {
|
|
502
|
+
p.cancel("Cancelled");
|
|
503
|
+
process.exit(0);
|
|
504
|
+
}
|
|
505
|
+
if (useExisting) return devVars[name];
|
|
506
|
+
}
|
|
507
|
+
return generate();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region src/cli/index.ts
|
|
512
|
+
/**
|
|
513
|
+
* PDS CLI - Setup and management for AT Protocol PDS on Cloudflare Workers
|
|
514
|
+
*/
|
|
515
|
+
runMain(defineCommand({
|
|
516
|
+
meta: {
|
|
517
|
+
name: "pds",
|
|
518
|
+
version: "0.0.0",
|
|
519
|
+
description: "AT Protocol PDS setup and management CLI"
|
|
520
|
+
},
|
|
521
|
+
subCommands: {
|
|
522
|
+
init: initCommand,
|
|
523
|
+
secret: secretCommand
|
|
524
|
+
}
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
//#endregion
|
|
528
|
+
export { };
|