deepspace 0.2.8 → 0.3.3
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 +473 -5
- package/dist/cli.js.map +1 -1
- package/dist/worker.d.ts +453 -1
- package/dist/worker.js +444 -0
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -75,12 +75,127 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
|
|
|
75
75
|
}
|
|
76
76
|
return body.token;
|
|
77
77
|
}
|
|
78
|
+
var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
|
|
79
|
+
"AUTH_JWT_PUBLIC_KEY",
|
|
80
|
+
"AUTH_JWT_ISSUER",
|
|
81
|
+
"AUTH_WORKER_URL",
|
|
82
|
+
"API_WORKER_URL",
|
|
83
|
+
"PLATFORM_WORKER_URL",
|
|
84
|
+
"OWNER_USER_ID",
|
|
85
|
+
"APP_OWNER_JWT",
|
|
86
|
+
"INTERNAL_STORAGE_HMAC_SECRET",
|
|
87
|
+
"ALLOW_DEBUG_ROUTES"
|
|
88
|
+
]);
|
|
89
|
+
var DEV_VARS_DIVIDER = "# --- not managed by the SDK; preserved across dev/test runs ---";
|
|
90
|
+
function extractCustomDevVars(content) {
|
|
91
|
+
const lines = content.split("\n");
|
|
92
|
+
const out = [];
|
|
93
|
+
let i = 0;
|
|
94
|
+
while (i < lines.length) {
|
|
95
|
+
const line = lines[i];
|
|
96
|
+
if (line.trim() === DEV_VARS_DIVIDER.trim()) {
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const eq = line.indexOf("=");
|
|
101
|
+
const keyMatch = eq >= 0 ? line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/) : null;
|
|
102
|
+
if (!keyMatch) {
|
|
103
|
+
out.push(line);
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const key = keyMatch[1];
|
|
108
|
+
const valStart = line.slice(eq + 1);
|
|
109
|
+
const block = [line];
|
|
110
|
+
if (valStart.startsWith('"') && !hasUnescapedQuote(valStart.slice(1))) {
|
|
111
|
+
i++;
|
|
112
|
+
while (i < lines.length) {
|
|
113
|
+
block.push(lines[i]);
|
|
114
|
+
const closed = hasUnescapedQuote(lines[i]);
|
|
115
|
+
i++;
|
|
116
|
+
if (closed) break;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
if (!SDK_MANAGED_KEYS.has(key)) {
|
|
122
|
+
out.push(...block);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
while (out.length && out[out.length - 1].trim() === "") out.pop();
|
|
126
|
+
return out.join("\n");
|
|
127
|
+
}
|
|
128
|
+
function hasUnescapedQuote(s) {
|
|
129
|
+
let escaped = false;
|
|
130
|
+
for (const ch of s) {
|
|
131
|
+
if (escaped) {
|
|
132
|
+
escaped = false;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (ch === "\\") {
|
|
136
|
+
escaped = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (ch === '"') return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function parseDevVars(content) {
|
|
144
|
+
const result = {};
|
|
145
|
+
const lines = content.split("\n");
|
|
146
|
+
let i = 0;
|
|
147
|
+
while (i < lines.length) {
|
|
148
|
+
const line = lines[i];
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const eq = line.indexOf("=");
|
|
155
|
+
if (eq < 0) {
|
|
156
|
+
i++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const keyMatch = line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/);
|
|
160
|
+
if (!keyMatch) {
|
|
161
|
+
i++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const key = keyMatch[1];
|
|
165
|
+
let value = line.slice(eq + 1);
|
|
166
|
+
let closed = !value.startsWith('"') || hasUnescapedQuote(value.slice(1));
|
|
167
|
+
if (!closed) {
|
|
168
|
+
i++;
|
|
169
|
+
while (i < lines.length) {
|
|
170
|
+
value += "\n" + lines[i];
|
|
171
|
+
if (hasUnescapedQuote(lines[i])) {
|
|
172
|
+
closed = true;
|
|
173
|
+
i++;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
if (value.startsWith('"') && !closed) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`parseDevVars: unterminated quoted value for key "${key}" \u2014 check your .dev.vars file for a missing closing quote.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
187
|
+
value = value.slice(1, -1).replace(/\\"/g, '"');
|
|
188
|
+
}
|
|
189
|
+
result[key] = value;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
78
193
|
async function writeDevVars(appDir, env, ownerId, callerJwt) {
|
|
79
194
|
const urls = ENVS[env];
|
|
80
195
|
const publicKey = await fetchPublicKey(urls.auth);
|
|
81
196
|
const appName = readAppName(appDir);
|
|
82
197
|
const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
|
|
83
|
-
const
|
|
198
|
+
const sdkVars = [
|
|
84
199
|
`AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
|
|
85
200
|
`AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
|
|
86
201
|
`AUTH_WORKER_URL=${urls.auth}`,
|
|
@@ -93,7 +208,16 @@ async function writeDevVars(appDir, env, ownerId, callerJwt) {
|
|
|
93
208
|
// Production secrets never set this, so deployed apps 404 the route.
|
|
94
209
|
`ALLOW_DEBUG_ROUTES=true`
|
|
95
210
|
].join("\n");
|
|
96
|
-
|
|
211
|
+
const devVarsPath = join(appDir, ".dev.vars");
|
|
212
|
+
const existing = existsSync(devVarsPath) ? readFileSync(devVarsPath, "utf-8") : "";
|
|
213
|
+
const custom = extractCustomDevVars(existing);
|
|
214
|
+
const body = custom ? `${sdkVars}
|
|
215
|
+
|
|
216
|
+
${DEV_VARS_DIVIDER}
|
|
217
|
+
${custom}
|
|
218
|
+
` : `${sdkVars}
|
|
219
|
+
`;
|
|
220
|
+
writeFileSync(devVarsPath, body);
|
|
97
221
|
}
|
|
98
222
|
|
|
99
223
|
// src/cli/session.ts
|
|
@@ -976,6 +1100,264 @@ import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync7 }
|
|
|
976
1100
|
import { parse as parseToml2 } from "smol-toml";
|
|
977
1101
|
import { join as join8, resolve as resolve3, dirname } from "path";
|
|
978
1102
|
import * as p3 from "@clack/prompts";
|
|
1103
|
+
|
|
1104
|
+
// src/server/rooms/binding-manifest.ts
|
|
1105
|
+
var AUTO_PROVISION_SENTINEL = "auto";
|
|
1106
|
+
var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
|
|
1107
|
+
var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
1108
|
+
"vectorize",
|
|
1109
|
+
"ai",
|
|
1110
|
+
"r2_bucket",
|
|
1111
|
+
"kv_namespace",
|
|
1112
|
+
"d1",
|
|
1113
|
+
"queue",
|
|
1114
|
+
"browser_rendering",
|
|
1115
|
+
"analytics_engine",
|
|
1116
|
+
"hyperdrive"
|
|
1117
|
+
]);
|
|
1118
|
+
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
1119
|
+
"ASSETS",
|
|
1120
|
+
"PLATFORM_WORKER",
|
|
1121
|
+
"API_WORKER",
|
|
1122
|
+
"APP_NAME",
|
|
1123
|
+
"OWNER_USER_ID",
|
|
1124
|
+
"AUTH_JWT_PUBLIC_KEY",
|
|
1125
|
+
"AUTH_JWT_ISSUER",
|
|
1126
|
+
"AUTH_WORKER_URL",
|
|
1127
|
+
"APP_IDENTITY_TOKEN",
|
|
1128
|
+
"APP_OWNER_JWT",
|
|
1129
|
+
"INTERNAL_STORAGE_HMAC_SECRET",
|
|
1130
|
+
"USAGE_EVENTS"
|
|
1131
|
+
]);
|
|
1132
|
+
function validateBindingManifest(manifest) {
|
|
1133
|
+
if (!Array.isArray(manifest)) {
|
|
1134
|
+
return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
|
|
1135
|
+
}
|
|
1136
|
+
const errors = [];
|
|
1137
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
1138
|
+
for (const entry of manifest) {
|
|
1139
|
+
if (!entry || typeof entry !== "object") {
|
|
1140
|
+
errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const e = entry;
|
|
1144
|
+
const type = e.type;
|
|
1145
|
+
const name = e.name;
|
|
1146
|
+
if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
|
|
1147
|
+
errors.push({
|
|
1148
|
+
reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
|
|
1149
|
+
});
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof name !== "string" || !name) {
|
|
1153
|
+
errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1157
|
+
errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (seenNames.has(name)) {
|
|
1161
|
+
errors.push({ reason: `Duplicate binding name "${name}"` });
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
seenNames.add(name);
|
|
1165
|
+
const binding = entry;
|
|
1166
|
+
const fieldErr = requiredFieldError(binding);
|
|
1167
|
+
if (fieldErr) errors.push({ binding, reason: fieldErr });
|
|
1168
|
+
}
|
|
1169
|
+
if (errors.length) return { valid: false, errors };
|
|
1170
|
+
return { valid: true, bindings: manifest };
|
|
1171
|
+
}
|
|
1172
|
+
function requiredFieldError(b) {
|
|
1173
|
+
switch (b.type) {
|
|
1174
|
+
case "vectorize": {
|
|
1175
|
+
if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
|
|
1176
|
+
if (b.index_name === AUTO_PROVISION_SENTINEL) {
|
|
1177
|
+
if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
|
|
1178
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
|
|
1179
|
+
}
|
|
1180
|
+
if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
|
|
1181
|
+
return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
case "r2_bucket":
|
|
1187
|
+
return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
|
|
1188
|
+
case "kv_namespace": {
|
|
1189
|
+
if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
|
|
1190
|
+
if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
|
|
1191
|
+
return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
|
|
1192
|
+
}
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
case "d1": {
|
|
1196
|
+
if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
|
|
1197
|
+
if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
|
|
1198
|
+
return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
|
|
1199
|
+
}
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
case "queue":
|
|
1203
|
+
return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
|
|
1204
|
+
case "hyperdrive":
|
|
1205
|
+
if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
|
|
1206
|
+
if (b.id === AUTO_PROVISION_SENTINEL) {
|
|
1207
|
+
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
1208
|
+
}
|
|
1209
|
+
return null;
|
|
1210
|
+
case "ai":
|
|
1211
|
+
case "browser_rendering":
|
|
1212
|
+
case "analytics_engine":
|
|
1213
|
+
return null;
|
|
1214
|
+
// no required fields beyond `name`
|
|
1215
|
+
default: {
|
|
1216
|
+
const _exhaustive = b;
|
|
1217
|
+
void _exhaustive;
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function bindingManifestFromOutputConfig(outputConfig) {
|
|
1223
|
+
const out = [];
|
|
1224
|
+
const aiBinding = pluckString(outputConfig.ai, "binding");
|
|
1225
|
+
if (aiBinding) out.push({ type: "ai", name: aiBinding });
|
|
1226
|
+
for (const v of asObjectArray(outputConfig.vectorize)) {
|
|
1227
|
+
const name = pluckString(v, "binding");
|
|
1228
|
+
const index_name = pluckString(v, "index_name");
|
|
1229
|
+
if (!name || !index_name) continue;
|
|
1230
|
+
const dimensions = pluckNumber(v, "dimensions");
|
|
1231
|
+
const metric = pluckString(v, "metric");
|
|
1232
|
+
out.push({
|
|
1233
|
+
type: "vectorize",
|
|
1234
|
+
name,
|
|
1235
|
+
index_name,
|
|
1236
|
+
...dimensions != null && { dimensions },
|
|
1237
|
+
...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
for (const r of asObjectArray(outputConfig.r2_buckets)) {
|
|
1241
|
+
const name = pluckString(r, "binding");
|
|
1242
|
+
const bucket_name = pluckString(r, "bucket_name");
|
|
1243
|
+
if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
|
|
1244
|
+
}
|
|
1245
|
+
for (const k of asObjectArray(outputConfig.kv_namespaces)) {
|
|
1246
|
+
const name = pluckString(k, "binding");
|
|
1247
|
+
const namespace_id = pluckString(k, "id");
|
|
1248
|
+
if (!name || !namespace_id) continue;
|
|
1249
|
+
const title = pluckString(k, "title");
|
|
1250
|
+
out.push({
|
|
1251
|
+
type: "kv_namespace",
|
|
1252
|
+
name,
|
|
1253
|
+
namespace_id,
|
|
1254
|
+
...title && { title }
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
for (const d of asObjectArray(outputConfig.d1_databases)) {
|
|
1258
|
+
const name = pluckString(d, "binding");
|
|
1259
|
+
const id = pluckString(d, "database_id");
|
|
1260
|
+
if (!name || !id) continue;
|
|
1261
|
+
const database_name = pluckString(d, "database_name");
|
|
1262
|
+
out.push({
|
|
1263
|
+
type: "d1",
|
|
1264
|
+
name,
|
|
1265
|
+
id,
|
|
1266
|
+
...database_name && { database_name }
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
|
|
1270
|
+
for (const q of asObjectArray(queues?.producers)) {
|
|
1271
|
+
const name = pluckString(q, "binding");
|
|
1272
|
+
const queue_name = pluckString(q, "queue");
|
|
1273
|
+
if (name && queue_name) out.push({ type: "queue", name, queue_name });
|
|
1274
|
+
}
|
|
1275
|
+
const browserBinding = pluckString(outputConfig.browser, "binding");
|
|
1276
|
+
if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
|
|
1277
|
+
for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
|
|
1278
|
+
const name = pluckString(a, "binding");
|
|
1279
|
+
const dataset = pluckString(a, "dataset");
|
|
1280
|
+
if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
|
|
1281
|
+
}
|
|
1282
|
+
for (const h of asObjectArray(outputConfig.hyperdrive)) {
|
|
1283
|
+
const name = pluckString(h, "binding");
|
|
1284
|
+
const id = pluckString(h, "id");
|
|
1285
|
+
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
1286
|
+
}
|
|
1287
|
+
return out;
|
|
1288
|
+
}
|
|
1289
|
+
function isObject(v) {
|
|
1290
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1291
|
+
}
|
|
1292
|
+
function pluckString(v, key) {
|
|
1293
|
+
if (!isObject(v)) return null;
|
|
1294
|
+
const val = v[key];
|
|
1295
|
+
return typeof val === "string" && val.length > 0 ? val : null;
|
|
1296
|
+
}
|
|
1297
|
+
function pluckNumber(v, key) {
|
|
1298
|
+
if (!isObject(v)) return null;
|
|
1299
|
+
const val = v[key];
|
|
1300
|
+
return typeof val === "number" && Number.isFinite(val) ? val : null;
|
|
1301
|
+
}
|
|
1302
|
+
function asObjectArray(v) {
|
|
1303
|
+
if (!Array.isArray(v)) return [];
|
|
1304
|
+
return v.filter(isObject);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/server/rooms/app-name.ts
|
|
1308
|
+
var APP_NAME_RULES = {
|
|
1309
|
+
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1310
|
+
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1311
|
+
minLength: 2,
|
|
1312
|
+
maxLength: 63
|
|
1313
|
+
};
|
|
1314
|
+
function validateAppName(raw) {
|
|
1315
|
+
if (typeof raw !== "string") {
|
|
1316
|
+
return { valid: false, reason: "App name must be a string" };
|
|
1317
|
+
}
|
|
1318
|
+
const name = raw.trim();
|
|
1319
|
+
if (!name) {
|
|
1320
|
+
return { valid: false, reason: "App name is required" };
|
|
1321
|
+
}
|
|
1322
|
+
if (name.length < APP_NAME_RULES.minLength) {
|
|
1323
|
+
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1324
|
+
}
|
|
1325
|
+
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1326
|
+
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1327
|
+
}
|
|
1328
|
+
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1329
|
+
return {
|
|
1330
|
+
valid: false,
|
|
1331
|
+
reason: `App name "${name}" is invalid. Use lowercase letters, digits, and single hyphens only (must start + end with alphanumeric). Examples: my-app, search-v2, unison-search.`
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return { valid: true, name };
|
|
1335
|
+
}
|
|
1336
|
+
function resolveAppName(raw) {
|
|
1337
|
+
const strict = validateAppName(raw);
|
|
1338
|
+
if (strict.valid) return { ok: true, name: strict.name };
|
|
1339
|
+
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1340
|
+
const trimmed = raw.trim();
|
|
1341
|
+
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1342
|
+
const sanitized = sanitizeAppName(trimmed);
|
|
1343
|
+
const sanitizedStrict = validateAppName(sanitized);
|
|
1344
|
+
if (!sanitizedStrict.valid) {
|
|
1345
|
+
return {
|
|
1346
|
+
ok: false,
|
|
1347
|
+
reason: `App name "${trimmed}" can't be sanitized into a valid name (${sanitizedStrict.reason}). Pick something matching: lowercase letters, digits, single hyphens, ${APP_NAME_RULES.minLength}-${APP_NAME_RULES.maxLength} chars.`
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
ok: true,
|
|
1352
|
+
name: sanitized,
|
|
1353
|
+
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function sanitizeAppName(raw) {
|
|
1357
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// src/cli/commands/deploy.ts
|
|
979
1361
|
var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
|
|
980
1362
|
var deploy_default = defineCommand7({
|
|
981
1363
|
meta: {
|
|
@@ -998,11 +1380,13 @@ var deploy_default = defineCommand7({
|
|
|
998
1380
|
process.exit(1);
|
|
999
1381
|
}
|
|
1000
1382
|
const wranglerConfig = parseToml2(readFileSync5(wranglerPath, "utf-8"));
|
|
1001
|
-
const
|
|
1002
|
-
if (!
|
|
1003
|
-
p3.cancel(
|
|
1383
|
+
const nameRes = resolveAppName(wranglerConfig.name);
|
|
1384
|
+
if (!nameRes.ok) {
|
|
1385
|
+
p3.cancel(`wrangler.toml: ${nameRes.reason}`);
|
|
1004
1386
|
process.exit(1);
|
|
1005
1387
|
}
|
|
1388
|
+
if (nameRes.warning) p3.log.warn(nameRes.warning);
|
|
1389
|
+
const appName = nameRes.name;
|
|
1006
1390
|
p3.log.info(`App: ${appName}`);
|
|
1007
1391
|
let token;
|
|
1008
1392
|
try {
|
|
@@ -1060,6 +1444,55 @@ var deploy_default = defineCommand7({
|
|
|
1060
1444
|
if (doManifest?.length) {
|
|
1061
1445
|
p3.log.info(`DO manifest: ${doManifest.length} binding(s)`);
|
|
1062
1446
|
}
|
|
1447
|
+
const customBindings = bindingManifestFromOutputConfig(outputConfig);
|
|
1448
|
+
const validation = validateBindingManifest(customBindings);
|
|
1449
|
+
if (!validation.valid) {
|
|
1450
|
+
p3.cancel(
|
|
1451
|
+
`Invalid binding(s) in wrangler.toml:
|
|
1452
|
+
` + validation.errors.map((e) => ` \u2022 ${e.reason}`).join("\n")
|
|
1453
|
+
);
|
|
1454
|
+
process.exit(1);
|
|
1455
|
+
}
|
|
1456
|
+
if (customBindings.length) {
|
|
1457
|
+
p3.log.info(
|
|
1458
|
+
`Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
const devVarsPath = join8(appDir, ".dev.vars");
|
|
1462
|
+
let userSecrets = {};
|
|
1463
|
+
if (existsSync7(devVarsPath)) {
|
|
1464
|
+
const customSection = extractCustomDevVars(readFileSync5(devVarsPath, "utf-8"));
|
|
1465
|
+
if (customSection) {
|
|
1466
|
+
try {
|
|
1467
|
+
userSecrets = parseDevVars(customSection);
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
p3.cancel(`.dev.vars: ${err.message}`);
|
|
1470
|
+
process.exit(1);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const userSecretNames = Object.keys(userSecrets);
|
|
1475
|
+
const declaredBindingNames = /* @__PURE__ */ new Set([
|
|
1476
|
+
...customBindings.map((b) => b.name),
|
|
1477
|
+
...(doManifest ?? []).map((d) => d.binding)
|
|
1478
|
+
]);
|
|
1479
|
+
for (const name of userSecretNames) {
|
|
1480
|
+
if (RESERVED_BINDING_NAMES.has(name)) {
|
|
1481
|
+
p3.cancel(
|
|
1482
|
+
`.dev.vars: "${name}" is a reserved binding name and cannot be used as an app secret.`
|
|
1483
|
+
);
|
|
1484
|
+
process.exit(1);
|
|
1485
|
+
}
|
|
1486
|
+
if (declaredBindingNames.has(name)) {
|
|
1487
|
+
p3.cancel(
|
|
1488
|
+
`.dev.vars: "${name}" collides with a binding declared in wrangler.toml \u2014 rename one or the other.`
|
|
1489
|
+
);
|
|
1490
|
+
process.exit(1);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (userSecretNames.length) {
|
|
1494
|
+
p3.log.info(`App secrets (from .dev.vars): ${userSecretNames.join(", ")}`);
|
|
1495
|
+
}
|
|
1063
1496
|
s.start(`Deploying to ${appName}.app.space...`);
|
|
1064
1497
|
const form = new FormData();
|
|
1065
1498
|
form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
|
|
@@ -1067,6 +1500,12 @@ var deploy_default = defineCommand7({
|
|
|
1067
1500
|
if (doManifest) {
|
|
1068
1501
|
form.append("doManifest", JSON.stringify(doManifest));
|
|
1069
1502
|
}
|
|
1503
|
+
if (customBindings.length) {
|
|
1504
|
+
form.append("bindingManifest", JSON.stringify(customBindings));
|
|
1505
|
+
}
|
|
1506
|
+
if (userSecretNames.length) {
|
|
1507
|
+
form.append("userSecrets", JSON.stringify(userSecrets));
|
|
1508
|
+
}
|
|
1070
1509
|
const res = await fetch(`${DEPLOY_URL}/api/deploy/${appName}`, {
|
|
1071
1510
|
method: "POST",
|
|
1072
1511
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -1078,11 +1517,40 @@ var deploy_default = defineCommand7({
|
|
|
1078
1517
|
p3.cancel(body.error ?? `Deployment error (${res.status})`);
|
|
1079
1518
|
process.exit(1);
|
|
1080
1519
|
}
|
|
1520
|
+
if (body.url) {
|
|
1521
|
+
s.message("Waiting for edge propagation...");
|
|
1522
|
+
const ok = await waitForAssetsReady(body.url, 9e4);
|
|
1523
|
+
if (!ok) {
|
|
1524
|
+
s.stop("Deployed (edge propagation still in progress after 90s)");
|
|
1525
|
+
p3.log.warn("First requests may briefly hit the assets transitional page; retry in a minute.");
|
|
1526
|
+
p3.log.success(`Live at: ${body.url}`);
|
|
1527
|
+
p3.outro("Done");
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1081
1531
|
s.stop("Deployed!");
|
|
1082
1532
|
p3.log.success(`Live at: ${body.url}`);
|
|
1083
1533
|
p3.outro("Done");
|
|
1084
1534
|
}
|
|
1085
1535
|
});
|
|
1536
|
+
async function waitForAssetsReady(url, timeoutMs) {
|
|
1537
|
+
const deadline = Date.now() + timeoutMs;
|
|
1538
|
+
let attempt = 0;
|
|
1539
|
+
while (Date.now() < deadline) {
|
|
1540
|
+
attempt++;
|
|
1541
|
+
try {
|
|
1542
|
+
const res = await fetch(url, { redirect: "manual" });
|
|
1543
|
+
const body = await res.text();
|
|
1544
|
+
if (!body.includes("Assets have not yet deployed")) {
|
|
1545
|
+
return true;
|
|
1546
|
+
}
|
|
1547
|
+
} catch {
|
|
1548
|
+
}
|
|
1549
|
+
const wait2 = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
|
|
1550
|
+
await new Promise((r) => setTimeout(r, wait2));
|
|
1551
|
+
}
|
|
1552
|
+
return false;
|
|
1553
|
+
}
|
|
1086
1554
|
function collectAssets(dir) {
|
|
1087
1555
|
const assets = [];
|
|
1088
1556
|
function walk(d, prefix) {
|