create-safest-tools 0.3.0 → 0.3.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/README.md +2 -0
- package/package.json +1 -1
- package/src/config.mjs +4 -3
- package/template/package.json +2 -2
- package/template/scripts/reports-owner-setup.mjs +22 -7
package/README.md
CHANGED
|
@@ -48,6 +48,8 @@ npm run setup
|
|
|
48
48
|
|
|
49
49
|
After deployment, setup prints a 256-bit, single-use owner link that expires after 15 minutes. The raw token stays in the URL fragment, D1 stores only its digest, and the installer never writes it to disk. Open it to create the Safest owner account. If it expires, run `npm run owner:setup`. For later break-glass recovery, authenticate Wrangler and run `npm run owner:recover`.
|
|
50
50
|
|
|
51
|
+
Custom-domain installations disable both the Worker’s `workers.dev` route and version preview URLs. A `workers.dev` public URL is available only when explicitly supplied as the installation URL.
|
|
52
|
+
|
|
51
53
|
The owner then invites administrators, and administrators invite analysts. All users sign in with Safest accounts and do not need Cloudflare accounts.
|
|
52
54
|
|
|
53
55
|
Invited users may join with a password or any configured OAuth provider, then choose a natural display name and optional JPEG, PNG, or WebP profile picture up to 2 MB. Better Auth stores credentials, OAuth accounts, sessions, verification state, and one-time password-reset tokens in D1. Cloudflare Email Service sends invitations, verification links, and 30-minute password-reset links.
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -69,14 +69,15 @@ export function buildConfiguration(options) {
|
|
|
69
69
|
|
|
70
70
|
export function buildWranglerConfiguration(config) {
|
|
71
71
|
const publicHostname = new URL(config.publicBaseUrl).hostname.toLowerCase();
|
|
72
|
+
const usesWorkersDev = publicHostname.endsWith(".workers.dev");
|
|
72
73
|
const wrangler = {
|
|
73
74
|
$schema: "node_modules/wrangler/config-schema.json",
|
|
74
75
|
name: config.resources.workerName,
|
|
75
76
|
main: "src/index.ts",
|
|
76
77
|
compatibility_date: "2026-08-28",
|
|
77
78
|
compatibility_flags: ["nodejs_compat"],
|
|
78
|
-
workers_dev:
|
|
79
|
-
preview_urls:
|
|
79
|
+
workers_dev: usesWorkersDev,
|
|
80
|
+
preview_urls: false,
|
|
80
81
|
observability: { enabled: true },
|
|
81
82
|
ai: { binding: "AI", remote: true },
|
|
82
83
|
assets: { directory: "public", binding: "ASSETS", run_worker_first: true },
|
|
@@ -131,7 +132,7 @@ export function buildWranglerConfiguration(config) {
|
|
|
131
132
|
},
|
|
132
133
|
triggers: { crons: ["*/5 * * * *"] },
|
|
133
134
|
};
|
|
134
|
-
if (!
|
|
135
|
+
if (!usesWorkersDev) {
|
|
135
136
|
wrangler.routes = [{ pattern: publicHostname, custom_domain: true }];
|
|
136
137
|
}
|
|
137
138
|
wrangler.send_email = [{ name: "EMAIL" }];
|
package/template/package.json
CHANGED
|
@@ -13,11 +13,18 @@ function sqlLiteral(value) {
|
|
|
13
13
|
return `'${String(value).replaceAll("'", "''")}'`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export function ownerSetupWranglerEnvironment(env = process.env) {
|
|
17
|
+
return { ...env, WRANGLER_LOG: "log", WRANGLER_WRITE_LOGS: "false" };
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
function runWrangler(args) {
|
|
17
21
|
return new Promise((resolvePromise, reject) => {
|
|
18
22
|
const child = spawn(process.execPath, [wrangler, ...args], {
|
|
19
23
|
cwd: projectRoot,
|
|
20
|
-
|
|
24
|
+
// Wrangler 4.127 emits `d1 execute --json` through its logger. Keep log
|
|
25
|
+
// output in the captured pipe while preventing the OAuth-backed response
|
|
26
|
+
// from being persisted to Wrangler's log directory.
|
|
27
|
+
env: ownerSetupWranglerEnvironment(),
|
|
21
28
|
stdio: ["ignore", "pipe", "pipe"],
|
|
22
29
|
});
|
|
23
30
|
let stdout = "";
|
|
@@ -82,18 +89,26 @@ RETURNING id, email, purpose, expires_at;
|
|
|
82
89
|
`.trim();
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
export function ownerSetupExecutionRows(output) {
|
|
93
|
+
const candidates = typeof output === "string" ? [output] : [output?.stdout, output?.stderr];
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
if (typeof candidate !== "string" || !candidate.trim()) continue;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(candidate.trim());
|
|
98
|
+
const results = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.result) ? parsed.result : [parsed];
|
|
99
|
+
return results.flatMap((entry) => Array.isArray(entry?.results) ? entry.results : []);
|
|
100
|
+
} catch {
|
|
101
|
+
// Try the other captured stream before reporting incompatible output.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw new Error("Wrangler returned unreadable D1 JSON while creating the owner setup token");
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
export async function issueOwnerSetup({ config, email, purpose = "bootstrap", runner = runWrangler, now, token, id } = {}) {
|
|
93
108
|
if (!config?.publicBaseUrl || !config?.owner?.email) throw new Error("owner setup requires a parsed Safest Resolve configuration");
|
|
94
109
|
const record = createOwnerSetupRecord({ email: email || config.owner.email, purpose, now, token, id });
|
|
95
110
|
const result = await runner(["d1", "execute", "DB", "--remote", "--command", ownerSetupSql(record), "--json", "--yes"]);
|
|
96
|
-
const created =
|
|
111
|
+
const created = ownerSetupExecutionRows(result).find((row) => row?.id === record.id);
|
|
97
112
|
if (!created) {
|
|
98
113
|
throw new Error(purpose === "bootstrap"
|
|
99
114
|
? "An active infrastructure owner already exists. Use `npm run owner:recover` for break-glass access."
|