deepspace 0.23.0 → 0.23.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/CHANGELOG.md +21 -0
- package/dist/build.d.ts +2 -1
- package/dist/build.js +89 -16
- package/dist/build.js.map +1 -1
- package/dist/{chunk-A7VKZRCB.js → chunk-2U2VG6L5.js} +2 -2
- package/dist/{chunk-A7VKZRCB.js.map → chunk-2U2VG6L5.js.map} +1 -1
- package/dist/cli.js +937 -739
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +1 -1
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +1 -1
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +84 -14
- package/dist/server.js +279 -90
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +70 -3
- package/dist/worker.js +461 -273
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
renderUsage
|
|
9
9
|
} from "citty";
|
|
10
10
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
11
|
-
import { readFileSync as
|
|
12
|
-
import { dirname as dirname12, join as
|
|
11
|
+
import { readFileSync as readFileSync27, existsSync as existsSync28 } from "fs";
|
|
12
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
13
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14
14
|
|
|
15
15
|
// src/cli/lib/spinner.ts
|
|
@@ -215,6 +215,8 @@ var CliExit = class extends Error {
|
|
|
215
215
|
function errorCode(err) {
|
|
216
216
|
if (err instanceof ApiError || err instanceof InputError) return err.code;
|
|
217
217
|
if (err instanceof Error && err.name === "GitError") return err.code ?? "git_error";
|
|
218
|
+
if (err instanceof Error && err.name === "WranglerConfigError")
|
|
219
|
+
return err.code ?? "invalid_config";
|
|
218
220
|
return void 0;
|
|
219
221
|
}
|
|
220
222
|
var API_ERROR_HINTS = {
|
|
@@ -428,7 +430,7 @@ function splitTokenPath(token, table) {
|
|
|
428
430
|
}
|
|
429
431
|
return parts;
|
|
430
432
|
}
|
|
431
|
-
var DESTRUCTIVE_VERBS = /* @__PURE__ */ new Set(["rm", "delete", "remove", "drop", "clear", "undeploy", "kill"]);
|
|
433
|
+
var DESTRUCTIVE_VERBS = /* @__PURE__ */ new Set(["rm", "delete", "remove", "drop", "clear", "undeploy", "kill", "logout"]);
|
|
432
434
|
function editDistance(a, b) {
|
|
433
435
|
const left = a.toLowerCase();
|
|
434
436
|
const right = b.toLowerCase();
|
|
@@ -473,11 +475,12 @@ function findUnknownCommand(argv, rootCommands) {
|
|
|
473
475
|
const split = splitTokenPath(token, table);
|
|
474
476
|
const relativeSuggestion = split ?? closestCommandPath(token, table);
|
|
475
477
|
if (!relativeSuggestion) return null;
|
|
478
|
+
const guessed = relativeSuggestion.at(-1) ?? "";
|
|
476
479
|
return {
|
|
477
480
|
attemptedPath: ["deepspace", ...accepted, token],
|
|
478
481
|
helpPath: ["deepspace", ...accepted],
|
|
479
482
|
suggestion: ["deepspace", ...accepted, ...relativeSuggestion],
|
|
480
|
-
executable: !DESTRUCTIVE_VERBS.has(
|
|
483
|
+
executable: split !== null && !DESTRUCTIVE_VERBS.has(guessed),
|
|
481
484
|
...split ? { quotedToken: true } : {}
|
|
482
485
|
};
|
|
483
486
|
}
|
|
@@ -834,7 +837,7 @@ async function provisionProfile(jwt) {
|
|
|
834
837
|
}
|
|
835
838
|
}
|
|
836
839
|
function sleep(ms) {
|
|
837
|
-
return new Promise((
|
|
840
|
+
return new Promise((resolve22) => setTimeout(resolve22, ms));
|
|
838
841
|
}
|
|
839
842
|
function base64url(bytes) {
|
|
840
843
|
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -906,9 +909,66 @@ var logout_default = defineDeepspaceCommand({
|
|
|
906
909
|
});
|
|
907
910
|
|
|
908
911
|
// src/cli/lib/app-identity.ts
|
|
909
|
-
import { existsSync as
|
|
910
|
-
import { join as
|
|
911
|
-
|
|
912
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
913
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
914
|
+
|
|
915
|
+
// src/cli/lib/wrangler-env.ts
|
|
916
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
917
|
+
import { join as join2 } from "path";
|
|
918
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
919
|
+
|
|
920
|
+
// src/server/rooms/app-name.ts
|
|
921
|
+
var APP_NAME_RULES = {
|
|
922
|
+
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
923
|
+
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
924
|
+
minLength: 2,
|
|
925
|
+
maxLength: 63
|
|
926
|
+
};
|
|
927
|
+
function validateAppName(raw) {
|
|
928
|
+
if (typeof raw !== "string") {
|
|
929
|
+
return { valid: false, reason: "App name must be a string" };
|
|
930
|
+
}
|
|
931
|
+
const name = raw.trim();
|
|
932
|
+
if (!name) {
|
|
933
|
+
return { valid: false, reason: "App name is required" };
|
|
934
|
+
}
|
|
935
|
+
if (name.length < APP_NAME_RULES.minLength) {
|
|
936
|
+
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
937
|
+
}
|
|
938
|
+
if (name.length > APP_NAME_RULES.maxLength) {
|
|
939
|
+
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
940
|
+
}
|
|
941
|
+
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
942
|
+
return {
|
|
943
|
+
valid: false,
|
|
944
|
+
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.`
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
return { valid: true, name };
|
|
948
|
+
}
|
|
949
|
+
function resolveAppName(raw) {
|
|
950
|
+
const strict = validateAppName(raw);
|
|
951
|
+
if (strict.valid) return { ok: true, name: strict.name };
|
|
952
|
+
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
953
|
+
const trimmed = raw.trim();
|
|
954
|
+
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
955
|
+
const sanitized = sanitizeAppName(trimmed);
|
|
956
|
+
const sanitizedStrict = validateAppName(sanitized);
|
|
957
|
+
if (!sanitizedStrict.valid) {
|
|
958
|
+
return {
|
|
959
|
+
ok: false,
|
|
960
|
+
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.`
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
ok: true,
|
|
965
|
+
name: sanitized,
|
|
966
|
+
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function sanitizeAppName(raw) {
|
|
970
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
971
|
+
}
|
|
912
972
|
|
|
913
973
|
// src/server/utils/registry-client.ts
|
|
914
974
|
var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
@@ -937,29 +997,198 @@ function mintUlid(now = Date.now()) {
|
|
|
937
997
|
return `${ts}${rs}`.slice(0, 26);
|
|
938
998
|
}
|
|
939
999
|
|
|
940
|
-
// src/cli/lib/
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1000
|
+
// src/cli/lib/wrangler-env.ts
|
|
1001
|
+
var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1002
|
+
"define",
|
|
1003
|
+
"vars",
|
|
1004
|
+
"durable_objects",
|
|
1005
|
+
"kv_namespaces",
|
|
1006
|
+
"r2_buckets",
|
|
1007
|
+
"ai_search_namespaces",
|
|
1008
|
+
"ai_search",
|
|
1009
|
+
"ratelimits",
|
|
1010
|
+
"vectorize",
|
|
1011
|
+
"services",
|
|
1012
|
+
"queues",
|
|
1013
|
+
"workflows",
|
|
1014
|
+
"tail_consumers",
|
|
1015
|
+
"secrets",
|
|
1016
|
+
"secrets_store_secrets"
|
|
1017
|
+
]);
|
|
1018
|
+
var generatedConfigSeq = 0;
|
|
1019
|
+
function resolveAppNameForEnv(config, envName) {
|
|
1020
|
+
if (!envName) {
|
|
1021
|
+
return resolveAppName(config.name);
|
|
1022
|
+
}
|
|
1023
|
+
const envBlock = config.env?.[envName];
|
|
1024
|
+
if (!envBlock) {
|
|
1025
|
+
return {
|
|
1026
|
+
ok: false,
|
|
1027
|
+
code: "missing_env_block",
|
|
1028
|
+
reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
|
|
1032
|
+
return {
|
|
1033
|
+
ok: false,
|
|
1034
|
+
reason: `wrangler.toml: [env.${envName}].name is missing. Set a distinct app name (e.g. "${typeof config.name === "string" ? config.name : "myapp"}-${envName}") so this environment doesn't overwrite the default.`
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
if (envBlock.name === config.name) {
|
|
1038
|
+
return {
|
|
1039
|
+
ok: false,
|
|
1040
|
+
reason: `wrangler.toml: [env.${envName}].name "${envBlock.name}" is the same as the top-level name. Pick a distinct name (e.g. "${config.name}-${envName}") so deploys don't collide.`
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
return resolveAppName(envBlock.name);
|
|
1044
|
+
}
|
|
1045
|
+
function devVarsPathFor(appDir) {
|
|
1046
|
+
return join2(appDir, ".dev.vars");
|
|
1047
|
+
}
|
|
1048
|
+
function prepareWranglerEnvConfig(appDir, envName) {
|
|
1049
|
+
if (!envName) return { cleanup() {
|
|
1050
|
+
} };
|
|
1051
|
+
const config = readWranglerConfig(appDir);
|
|
1052
|
+
const resolved = resolveAppNameForEnv(config, envName);
|
|
1053
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1054
|
+
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
1055
|
+
const configPath = join2(
|
|
1056
|
+
appDir,
|
|
1057
|
+
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
1058
|
+
);
|
|
1059
|
+
writeFileSync2(
|
|
1060
|
+
configPath,
|
|
1061
|
+
[
|
|
1062
|
+
"# Generated by DeepSpace for this CLI run.",
|
|
1063
|
+
"# Do not edit; wrangler.toml remains the source of truth.",
|
|
1064
|
+
stringifyToml(flattened)
|
|
1065
|
+
].join("\n").replace(/\s*$/u, "\n")
|
|
1066
|
+
);
|
|
1067
|
+
return {
|
|
1068
|
+
configPath,
|
|
1069
|
+
cleanup() {
|
|
1070
|
+
rmSync2(configPath, { force: true });
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
1075
|
+
const env = { ...baseEnv, ...extraEnv };
|
|
1076
|
+
if (prepared.configPath) {
|
|
1077
|
+
delete env.CLOUDFLARE_ENV;
|
|
1078
|
+
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
1079
|
+
}
|
|
1080
|
+
return env;
|
|
1081
|
+
}
|
|
1082
|
+
function flattenWranglerEnvConfig(config, envName) {
|
|
1083
|
+
const envRoot = asTomlRecord(config.env);
|
|
1084
|
+
const envBlock = asTomlRecord(envRoot?.[envName]);
|
|
1085
|
+
if (!envBlock) {
|
|
1086
|
+
throw new Error(`wrangler.toml: no [env.${envName}] block found.`);
|
|
1087
|
+
}
|
|
1088
|
+
const flattened = {};
|
|
1089
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1090
|
+
if (key === "env" || NON_INHERITABLE_ENV_KEYS.has(key)) continue;
|
|
1091
|
+
flattened[key] = value;
|
|
1092
|
+
}
|
|
1093
|
+
for (const [key, value] of Object.entries(envBlock)) {
|
|
1094
|
+
flattened[key] = value;
|
|
1095
|
+
}
|
|
1096
|
+
if (!("durable_objects" in envBlock) && config.durable_objects) {
|
|
1097
|
+
flattened.durable_objects = config.durable_objects;
|
|
1098
|
+
}
|
|
1099
|
+
const envVars = asTomlRecord(envBlock.vars) ?? {};
|
|
1100
|
+
if (!("APP_NAME" in envVars) && typeof envBlock.name === "string") {
|
|
1101
|
+
flattened.vars = { ...envVars, APP_NAME: envBlock.name };
|
|
1102
|
+
}
|
|
1103
|
+
return flattened;
|
|
1104
|
+
}
|
|
1105
|
+
function asTomlRecord(value) {
|
|
1106
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1107
|
+
}
|
|
1108
|
+
function safeGeneratedConfigSegment(value) {
|
|
1109
|
+
return value.replace(/[^A-Za-z0-9._-]/gu, "_");
|
|
1110
|
+
}
|
|
1111
|
+
var WranglerConfigError = class extends Error {
|
|
1112
|
+
constructor(path, message, code = "invalid_config", cause) {
|
|
1113
|
+
super(message);
|
|
1114
|
+
this.path = path;
|
|
1115
|
+
this.code = code;
|
|
1116
|
+
this.cause = cause;
|
|
1117
|
+
this.name = "WranglerConfigError";
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
function causeDetail(err) {
|
|
1121
|
+
return err instanceof Error ? err.message : String(err);
|
|
1122
|
+
}
|
|
1123
|
+
function readWranglerConfigFile(path) {
|
|
1124
|
+
let raw;
|
|
944
1125
|
try {
|
|
945
|
-
|
|
1126
|
+
raw = readFileSync5(path, "utf-8");
|
|
946
1127
|
} catch (err) {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1128
|
+
const missing = err?.code === "ENOENT";
|
|
1129
|
+
throw new WranglerConfigError(
|
|
1130
|
+
path,
|
|
1131
|
+
missing ? noWranglerConfigMessage(path) : `wrangler.toml: could not read ${path}: ${causeDetail(err)}`,
|
|
1132
|
+
missing ? "not_in_app_repo" : "invalid_config",
|
|
1133
|
+
err
|
|
950
1134
|
);
|
|
951
1135
|
}
|
|
1136
|
+
let config;
|
|
1137
|
+
try {
|
|
1138
|
+
config = parseToml(raw);
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
throw new WranglerConfigError(
|
|
1141
|
+
path,
|
|
1142
|
+
`wrangler.toml: malformed TOML at ${path}: ${causeDetail(err)}`,
|
|
1143
|
+
"invalid_config",
|
|
1144
|
+
err
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
assertOneSectionPerAppId(config, path);
|
|
1148
|
+
return config;
|
|
1149
|
+
}
|
|
1150
|
+
function readWranglerConfig(appDir) {
|
|
1151
|
+
return readWranglerConfigFile(join2(appDir, "wrangler.toml"));
|
|
1152
|
+
}
|
|
1153
|
+
function readAppIdVar(config, envName) {
|
|
1154
|
+
return (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID;
|
|
1155
|
+
}
|
|
1156
|
+
function assertOneSectionPerAppId(config, path) {
|
|
1157
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1158
|
+
const record = (section, value) => {
|
|
1159
|
+
if (typeof value !== "string" || !APP_ID_RE.test(value)) return;
|
|
1160
|
+
sections.set(value, [...sections.get(value) ?? [], section]);
|
|
1161
|
+
};
|
|
1162
|
+
record("[vars]", config.vars?.DEEPSPACE_APP_ID);
|
|
1163
|
+
for (const envName of Object.keys(config.env ?? {})) {
|
|
1164
|
+
record(`[env.${envName}.vars]`, config.env?.[envName]?.vars?.DEEPSPACE_APP_ID);
|
|
1165
|
+
}
|
|
1166
|
+
for (const [appId, where] of sections) {
|
|
1167
|
+
if (where.length < 2) continue;
|
|
1168
|
+
throw new WranglerConfigError(
|
|
1169
|
+
path,
|
|
1170
|
+
`wrangler.toml: ${where.join(" and ")} ${where.length === 2 ? "both" : "all"} set DEEPSPACE_APP_ID = "${appId}" in ${path}. One id under several sections points every environment's data, secrets, and billing at the same app while the deploys go to different places \u2014 each environment is its own app \u2014 run \`deepspace app init --env <name>\` to mint one for it.`,
|
|
1171
|
+
"duplicate_app_id"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
function noWranglerConfigMessage(where) {
|
|
1176
|
+
return `No wrangler.toml at ${where}. Are you in a DeepSpace app directory? (Scaffold one with \`npm create deepspace\`, or cd into an existing app.)`;
|
|
1177
|
+
}
|
|
1178
|
+
function hasWranglerConfig(appDir) {
|
|
1179
|
+
return existsSync3(join2(appDir, "wrangler.toml"));
|
|
952
1180
|
}
|
|
1181
|
+
|
|
1182
|
+
// src/cli/lib/app-identity.ts
|
|
953
1183
|
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
954
|
-
const
|
|
955
|
-
if (!
|
|
956
|
-
const
|
|
957
|
-
const id = vars?.DEEPSPACE_APP_ID;
|
|
1184
|
+
const appDir = resolve2(cwd);
|
|
1185
|
+
if (!hasWranglerConfig(appDir)) return null;
|
|
1186
|
+
const id = readAppIdVar(readWranglerConfig(appDir), wranglerEnv);
|
|
958
1187
|
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
959
1188
|
}
|
|
960
1189
|
function writeAppId(cwd, appId, opts = {}) {
|
|
961
|
-
const wranglerPath =
|
|
962
|
-
if (!
|
|
1190
|
+
const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
|
|
1191
|
+
if (!existsSync4(wranglerPath)) {
|
|
963
1192
|
throw new Error(`No wrangler.toml in ${resolve2(cwd)}`);
|
|
964
1193
|
}
|
|
965
1194
|
const existing = readAppId(cwd, opts.wranglerEnv);
|
|
@@ -969,7 +1198,7 @@ function writeAppId(cwd, appId, opts = {}) {
|
|
|
969
1198
|
`wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
|
|
970
1199
|
);
|
|
971
1200
|
}
|
|
972
|
-
let src =
|
|
1201
|
+
let src = readFileSync6(wranglerPath, "utf-8");
|
|
973
1202
|
const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
|
|
974
1203
|
const line = `DEEPSPACE_APP_ID = "${appId}"`;
|
|
975
1204
|
const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
|
|
@@ -991,7 +1220,7 @@ ${header}
|
|
|
991
1220
|
${line}
|
|
992
1221
|
`;
|
|
993
1222
|
}
|
|
994
|
-
|
|
1223
|
+
writeFileSync3(wranglerPath, src);
|
|
995
1224
|
}
|
|
996
1225
|
|
|
997
1226
|
// src/cli/commands/dev.ts
|
|
@@ -999,26 +1228,18 @@ import { resolve as resolve8, basename as basename2, join as join11 } from "path
|
|
|
999
1228
|
import spawn2 from "cross-spawn";
|
|
1000
1229
|
|
|
1001
1230
|
// src/cli/lib/app-context.ts
|
|
1002
|
-
import { existsSync as
|
|
1003
|
-
import { dirname as dirname3, join as
|
|
1004
|
-
import { parse as parseToml2 } from "smol-toml";
|
|
1231
|
+
import { existsSync as existsSync5, readdirSync } from "fs";
|
|
1232
|
+
import { dirname as dirname3, join as join4, resolve as resolve3 } from "path";
|
|
1005
1233
|
function detectAppName(cwd = process.cwd()) {
|
|
1006
|
-
const
|
|
1007
|
-
if (!
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
return typeof config.name === "string" && config.name.length > 0 ? config.name : null;
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
throw new InputError(
|
|
1013
|
-
`Could not parse ${wranglerPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1014
|
-
"invalid_config"
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1234
|
+
const appDir = resolve3(cwd);
|
|
1235
|
+
if (!existsSync5(join4(appDir, "wrangler.toml"))) return null;
|
|
1236
|
+
const { name } = readWranglerConfig(appDir);
|
|
1237
|
+
return typeof name === "string" && name.length > 0 ? name : null;
|
|
1017
1238
|
}
|
|
1018
1239
|
function findAppDir(start = process.cwd()) {
|
|
1019
1240
|
let dir = resolve3(start);
|
|
1020
1241
|
for (; ; ) {
|
|
1021
|
-
if (
|
|
1242
|
+
if (existsSync5(join4(dir, "wrangler.toml"))) return dir;
|
|
1022
1243
|
const parent = dirname3(dir);
|
|
1023
1244
|
if (parent === dir) return null;
|
|
1024
1245
|
dir = parent;
|
|
@@ -1026,7 +1247,7 @@ function findAppDir(start = process.cwd()) {
|
|
|
1026
1247
|
}
|
|
1027
1248
|
function findChildApps(dir) {
|
|
1028
1249
|
try {
|
|
1029
|
-
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() &&
|
|
1250
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join4(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
|
|
1030
1251
|
} catch {
|
|
1031
1252
|
return [];
|
|
1032
1253
|
}
|
|
@@ -1034,19 +1255,19 @@ function findChildApps(dir) {
|
|
|
1034
1255
|
|
|
1035
1256
|
// src/cli/lib/launch-config.ts
|
|
1036
1257
|
import {
|
|
1037
|
-
existsSync as
|
|
1258
|
+
existsSync as existsSync6,
|
|
1038
1259
|
readFileSync as readFileSync8,
|
|
1039
1260
|
realpathSync as realpathSync2,
|
|
1040
|
-
writeFileSync as
|
|
1261
|
+
writeFileSync as writeFileSync4,
|
|
1041
1262
|
mkdirSync as mkdirSync4,
|
|
1042
1263
|
renameSync,
|
|
1043
1264
|
unlinkSync
|
|
1044
1265
|
} from "fs";
|
|
1045
|
-
import { join as
|
|
1266
|
+
import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
|
|
1046
1267
|
|
|
1047
1268
|
// src/cli/lib/git/repository.ts
|
|
1048
1269
|
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1049
|
-
import { dirname as dirname4, join as
|
|
1270
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
1050
1271
|
|
|
1051
1272
|
// src/cli/lib/git/process.ts
|
|
1052
1273
|
import { sync as spawnSync } from "cross-spawn";
|
|
@@ -1250,7 +1471,7 @@ function listWorktrees(cwd) {
|
|
|
1250
1471
|
}
|
|
1251
1472
|
function ensureLocalExclude(cwd, pattern) {
|
|
1252
1473
|
const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
|
|
1253
|
-
const excludePath =
|
|
1474
|
+
const excludePath = join5(commonDir, "info", "exclude");
|
|
1254
1475
|
let current = "";
|
|
1255
1476
|
try {
|
|
1256
1477
|
current = readFileSync7(excludePath, "utf-8");
|
|
@@ -1272,7 +1493,7 @@ function ensureLocalExclude(cwd, pattern) {
|
|
|
1272
1493
|
function writeLaunchFile(launchPath, config) {
|
|
1273
1494
|
mkdirSync4(dirname5(launchPath), { recursive: true });
|
|
1274
1495
|
const tmpPath = `${launchPath}.${process.pid}.tmp`;
|
|
1275
|
-
|
|
1496
|
+
writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1276
1497
|
try {
|
|
1277
1498
|
renameSync(tmpPath, launchPath);
|
|
1278
1499
|
} catch (err) {
|
|
@@ -1285,7 +1506,7 @@ function writeLaunchFile(launchPath, config) {
|
|
|
1285
1506
|
}
|
|
1286
1507
|
function readLaunchFile(launchPath) {
|
|
1287
1508
|
const empty = { version: "0.0.1", configurations: [] };
|
|
1288
|
-
if (!
|
|
1509
|
+
if (!existsSync6(launchPath)) return empty;
|
|
1289
1510
|
const raw = readFileSync8(launchPath, "utf-8").trim();
|
|
1290
1511
|
if (!raw) return empty;
|
|
1291
1512
|
const parsed = JSON.parse(raw);
|
|
@@ -1293,7 +1514,7 @@ function readLaunchFile(launchPath) {
|
|
|
1293
1514
|
return parsed;
|
|
1294
1515
|
}
|
|
1295
1516
|
function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
1296
|
-
const launchPath =
|
|
1517
|
+
const launchPath = join6(appDir, ".claude", "launch.json");
|
|
1297
1518
|
try {
|
|
1298
1519
|
const config = readLaunchFile(launchPath);
|
|
1299
1520
|
if (!config) return;
|
|
@@ -1334,7 +1555,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
|
1334
1555
|
}
|
|
1335
1556
|
function resolveAppLaunchPort(appDir) {
|
|
1336
1557
|
try {
|
|
1337
|
-
const config = readLaunchFile(
|
|
1558
|
+
const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
|
|
1338
1559
|
if (!config) return null;
|
|
1339
1560
|
const appName = detectAppName(appDir) ?? basename(appDir);
|
|
1340
1561
|
const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
|
|
@@ -1388,15 +1609,15 @@ function deriveWorktreePort(worktreeKey) {
|
|
|
1388
1609
|
return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
|
|
1389
1610
|
}
|
|
1390
1611
|
function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
1391
|
-
const launchPath =
|
|
1612
|
+
const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
|
|
1392
1613
|
const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
|
|
1393
1614
|
try {
|
|
1394
1615
|
const config = readLaunchFile(launchPath);
|
|
1395
1616
|
if (!config) return null;
|
|
1396
|
-
const worktreesRoot =
|
|
1617
|
+
const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
|
|
1397
1618
|
config.configurations = config.configurations.filter((c) => {
|
|
1398
1619
|
if (c?.name === entryName) return false;
|
|
1399
|
-
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !
|
|
1620
|
+
const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync6(c.cwd);
|
|
1400
1621
|
return !isStaleWorktreeEntry;
|
|
1401
1622
|
});
|
|
1402
1623
|
let port = opts.port;
|
|
@@ -1431,7 +1652,7 @@ function resolveWorktreePort(appDir) {
|
|
|
1431
1652
|
const claude = detectClaudeWorktree(appDir);
|
|
1432
1653
|
if (claude) {
|
|
1433
1654
|
try {
|
|
1434
|
-
const config = readLaunchFile(
|
|
1655
|
+
const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
|
|
1435
1656
|
const entry = config?.configurations.find(
|
|
1436
1657
|
(c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
|
|
1437
1658
|
);
|
|
@@ -1478,209 +1699,18 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
|
1478
1699
|
{
|
|
1479
1700
|
method: "POST",
|
|
1480
1701
|
headers: { Authorization: `Bearer ${callerJwt}` }
|
|
1481
|
-
}
|
|
1482
|
-
);
|
|
1483
|
-
if (response.status === 404) return null;
|
|
1484
|
-
if (!response.ok) {
|
|
1485
|
-
const detail = await response.text().catch(() => "");
|
|
1486
|
-
throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`);
|
|
1487
|
-
}
|
|
1488
|
-
const body = await response.json();
|
|
1489
|
-
if (!body.token) {
|
|
1490
|
-
throw new Error(`Deploy worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1491
|
-
}
|
|
1492
|
-
return body.token;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
// src/cli/lib/wrangler-env.ts
|
|
1496
|
-
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1497
|
-
import { join as join6 } from "path";
|
|
1498
|
-
import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
|
|
1499
|
-
|
|
1500
|
-
// src/server/rooms/app-name.ts
|
|
1501
|
-
var APP_NAME_RULES = {
|
|
1502
|
-
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1503
|
-
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1504
|
-
minLength: 2,
|
|
1505
|
-
maxLength: 63
|
|
1506
|
-
};
|
|
1507
|
-
function validateAppName(raw) {
|
|
1508
|
-
if (typeof raw !== "string") {
|
|
1509
|
-
return { valid: false, reason: "App name must be a string" };
|
|
1510
|
-
}
|
|
1511
|
-
const name = raw.trim();
|
|
1512
|
-
if (!name) {
|
|
1513
|
-
return { valid: false, reason: "App name is required" };
|
|
1514
|
-
}
|
|
1515
|
-
if (name.length < APP_NAME_RULES.minLength) {
|
|
1516
|
-
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1517
|
-
}
|
|
1518
|
-
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1519
|
-
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1520
|
-
}
|
|
1521
|
-
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1522
|
-
return {
|
|
1523
|
-
valid: false,
|
|
1524
|
-
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.`
|
|
1525
|
-
};
|
|
1526
|
-
}
|
|
1527
|
-
return { valid: true, name };
|
|
1528
|
-
}
|
|
1529
|
-
function resolveAppName(raw) {
|
|
1530
|
-
const strict = validateAppName(raw);
|
|
1531
|
-
if (strict.valid) return { ok: true, name: strict.name };
|
|
1532
|
-
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1533
|
-
const trimmed = raw.trim();
|
|
1534
|
-
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1535
|
-
const sanitized = sanitizeAppName(trimmed);
|
|
1536
|
-
const sanitizedStrict = validateAppName(sanitized);
|
|
1537
|
-
if (!sanitizedStrict.valid) {
|
|
1538
|
-
return {
|
|
1539
|
-
ok: false,
|
|
1540
|
-
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.`
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
return {
|
|
1544
|
-
ok: true,
|
|
1545
|
-
name: sanitized,
|
|
1546
|
-
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
function sanitizeAppName(raw) {
|
|
1550
|
-
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// src/cli/lib/wrangler-env.ts
|
|
1554
|
-
var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1555
|
-
"define",
|
|
1556
|
-
"vars",
|
|
1557
|
-
"durable_objects",
|
|
1558
|
-
"kv_namespaces",
|
|
1559
|
-
"r2_buckets",
|
|
1560
|
-
"ai_search_namespaces",
|
|
1561
|
-
"ai_search",
|
|
1562
|
-
"ratelimits",
|
|
1563
|
-
"vectorize",
|
|
1564
|
-
"services",
|
|
1565
|
-
"queues",
|
|
1566
|
-
"workflows",
|
|
1567
|
-
"tail_consumers",
|
|
1568
|
-
"secrets",
|
|
1569
|
-
"secrets_store_secrets"
|
|
1570
|
-
]);
|
|
1571
|
-
var generatedConfigSeq = 0;
|
|
1572
|
-
function resolveAppNameForEnv(config, envName) {
|
|
1573
|
-
if (!envName) {
|
|
1574
|
-
return resolveAppName(config.name);
|
|
1575
|
-
}
|
|
1576
|
-
const envBlock = config.env?.[envName];
|
|
1577
|
-
if (!envBlock) {
|
|
1578
|
-
return {
|
|
1579
|
-
ok: false,
|
|
1580
|
-
code: "missing_env_block",
|
|
1581
|
-
reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
|
-
if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
|
|
1585
|
-
return {
|
|
1586
|
-
ok: false,
|
|
1587
|
-
reason: `wrangler.toml: [env.${envName}].name is missing. Set a distinct app name (e.g. "${typeof config.name === "string" ? config.name : "myapp"}-${envName}") so this environment doesn't overwrite the default.`
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
if (envBlock.name === config.name) {
|
|
1591
|
-
return {
|
|
1592
|
-
ok: false,
|
|
1593
|
-
reason: `wrangler.toml: [env.${envName}].name "${envBlock.name}" is the same as the top-level name. Pick a distinct name (e.g. "${config.name}-${envName}") so deploys don't collide.`
|
|
1594
|
-
};
|
|
1595
|
-
}
|
|
1596
|
-
return resolveAppName(envBlock.name);
|
|
1597
|
-
}
|
|
1598
|
-
function devVarsPathFor(appDir) {
|
|
1599
|
-
return join6(appDir, ".dev.vars");
|
|
1600
|
-
}
|
|
1601
|
-
function prepareWranglerEnvConfig(appDir, envName) {
|
|
1602
|
-
if (!envName) return { cleanup() {
|
|
1603
|
-
} };
|
|
1604
|
-
const config = readWranglerConfig(appDir);
|
|
1605
|
-
const resolved = resolveAppNameForEnv(config, envName);
|
|
1606
|
-
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1607
|
-
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
1608
|
-
const configPath = join6(
|
|
1609
|
-
appDir,
|
|
1610
|
-
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
1611
|
-
);
|
|
1612
|
-
writeFileSync4(
|
|
1613
|
-
configPath,
|
|
1614
|
-
[
|
|
1615
|
-
"# Generated by DeepSpace for this CLI run.",
|
|
1616
|
-
"# Do not edit; wrangler.toml remains the source of truth.",
|
|
1617
|
-
stringifyToml(flattened)
|
|
1618
|
-
].join("\n").replace(/\s*$/u, "\n")
|
|
1619
|
-
);
|
|
1620
|
-
return {
|
|
1621
|
-
configPath,
|
|
1622
|
-
cleanup() {
|
|
1623
|
-
rmSync2(configPath, { force: true });
|
|
1624
|
-
}
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
1628
|
-
const env = { ...baseEnv, ...extraEnv };
|
|
1629
|
-
if (prepared.configPath) {
|
|
1630
|
-
delete env.CLOUDFLARE_ENV;
|
|
1631
|
-
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
1632
|
-
}
|
|
1633
|
-
return env;
|
|
1634
|
-
}
|
|
1635
|
-
function flattenWranglerEnvConfig(config, envName) {
|
|
1636
|
-
const envRoot = asTomlRecord(config.env);
|
|
1637
|
-
const envBlock = asTomlRecord(envRoot?.[envName]);
|
|
1638
|
-
if (!envBlock) {
|
|
1639
|
-
throw new Error(`wrangler.toml: no [env.${envName}] block found.`);
|
|
1640
|
-
}
|
|
1641
|
-
const flattened = {};
|
|
1642
|
-
for (const [key, value] of Object.entries(config)) {
|
|
1643
|
-
if (key === "env" || NON_INHERITABLE_ENV_KEYS.has(key)) continue;
|
|
1644
|
-
flattened[key] = value;
|
|
1645
|
-
}
|
|
1646
|
-
for (const [key, value] of Object.entries(envBlock)) {
|
|
1647
|
-
flattened[key] = value;
|
|
1648
|
-
}
|
|
1649
|
-
if (!("durable_objects" in envBlock) && config.durable_objects) {
|
|
1650
|
-
flattened.durable_objects = config.durable_objects;
|
|
1651
|
-
}
|
|
1652
|
-
const envVars = asTomlRecord(envBlock.vars) ?? {};
|
|
1653
|
-
if (!("APP_NAME" in envVars) && typeof envBlock.name === "string") {
|
|
1654
|
-
flattened.vars = { ...envVars, APP_NAME: envBlock.name };
|
|
1655
|
-
}
|
|
1656
|
-
return flattened;
|
|
1657
|
-
}
|
|
1658
|
-
function asTomlRecord(value) {
|
|
1659
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1660
|
-
}
|
|
1661
|
-
function safeGeneratedConfigSegment(value) {
|
|
1662
|
-
return value.replace(/[^A-Za-z0-9._-]/gu, "_");
|
|
1663
|
-
}
|
|
1664
|
-
var WranglerConfigError = class extends Error {
|
|
1665
|
-
constructor(path, cause) {
|
|
1666
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1667
|
-
super(`wrangler.toml: malformed TOML at ${path}: ${detail}`);
|
|
1668
|
-
this.path = path;
|
|
1669
|
-
this.cause = cause;
|
|
1670
|
-
this.name = "WranglerConfigError";
|
|
1702
|
+
}
|
|
1703
|
+
);
|
|
1704
|
+
if (response.status === 404) return null;
|
|
1705
|
+
if (!response.ok) {
|
|
1706
|
+
const detail = await response.text().catch(() => "");
|
|
1707
|
+
throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`);
|
|
1671
1708
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const raw = readFileSync9(path, "utf-8");
|
|
1676
|
-
try {
|
|
1677
|
-
return parseToml3(raw);
|
|
1678
|
-
} catch (err) {
|
|
1679
|
-
throw new WranglerConfigError(path, err);
|
|
1709
|
+
const body = await response.json();
|
|
1710
|
+
if (!body.token) {
|
|
1711
|
+
throw new Error(`Deploy worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1680
1712
|
}
|
|
1681
|
-
|
|
1682
|
-
function hasWranglerConfig(appDir) {
|
|
1683
|
-
return existsSync6(join6(appDir, "wrangler.toml"));
|
|
1713
|
+
return body.token;
|
|
1684
1714
|
}
|
|
1685
1715
|
|
|
1686
1716
|
// src/cli/lib/dev-vars.ts
|
|
@@ -1722,15 +1752,15 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
|
|
|
1722
1752
|
}
|
|
1723
1753
|
|
|
1724
1754
|
// src/cli/lib/install-status.ts
|
|
1725
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1755
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "fs";
|
|
1726
1756
|
import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
|
|
1727
1757
|
|
|
1728
1758
|
// src/cli/lib/package-manager.ts
|
|
1729
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1759
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
1730
1760
|
import { join as join7 } from "path";
|
|
1731
1761
|
function detectPackageManager(appDir) {
|
|
1732
1762
|
try {
|
|
1733
|
-
const pkg2 = JSON.parse(
|
|
1763
|
+
const pkg2 = JSON.parse(readFileSync9(join7(appDir, "package.json"), "utf-8"));
|
|
1734
1764
|
const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
|
|
1735
1765
|
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
1736
1766
|
} catch {
|
|
@@ -1768,7 +1798,7 @@ function ensureInstallReady(appDir) {
|
|
|
1768
1798
|
const donePath = join8(sentinelDir, "install.done");
|
|
1769
1799
|
const logPath = join8(sentinelDir, "install.log");
|
|
1770
1800
|
if (existsSync8(errPath)) {
|
|
1771
|
-
const detail =
|
|
1801
|
+
const detail = readFileSync10(errPath, "utf-8").trimEnd();
|
|
1772
1802
|
const log29 = existsSync8(logPath) ? ` Full log: ${logPath}.` : "";
|
|
1773
1803
|
throw new Refusal(
|
|
1774
1804
|
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
@@ -1824,7 +1854,7 @@ function canonicalPath2(path) {
|
|
|
1824
1854
|
function installWorkerAlive(sentinelDir) {
|
|
1825
1855
|
const pidPath = join8(sentinelDir, "install.pid");
|
|
1826
1856
|
if (!existsSync8(pidPath)) return false;
|
|
1827
|
-
const pid = Number(
|
|
1857
|
+
const pid = Number(readFileSync10(pidPath, "utf-8").trim());
|
|
1828
1858
|
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
1829
1859
|
try {
|
|
1830
1860
|
process.kill(pid, 0);
|
|
@@ -2180,11 +2210,7 @@ async function secretsApi(deployUrl, token, path, init = {}) {
|
|
|
2180
2210
|
});
|
|
2181
2211
|
const body = await res.json().catch(() => ({}));
|
|
2182
2212
|
if (!res.ok) {
|
|
2183
|
-
|
|
2184
|
-
err.status = res.status;
|
|
2185
|
-
err.code = body.code;
|
|
2186
|
-
err.apiPath = path;
|
|
2187
|
-
throw err;
|
|
2213
|
+
throw new ApiError(body.error ?? `Secrets request failed (${res.status})`, res.status, body.code, path);
|
|
2188
2214
|
}
|
|
2189
2215
|
return body;
|
|
2190
2216
|
}
|
|
@@ -2447,6 +2473,23 @@ function lintSchema(schema) {
|
|
|
2447
2473
|
}
|
|
2448
2474
|
return warnings;
|
|
2449
2475
|
}
|
|
2476
|
+
function lintSchemas(schemas) {
|
|
2477
|
+
return [...schemas.flatMap((schema) => lintSchema(schema)), ...lintSchemaSet(schemas)];
|
|
2478
|
+
}
|
|
2479
|
+
function lintSchemaSet(schemas) {
|
|
2480
|
+
const warnings = [];
|
|
2481
|
+
if (!schemas.some((schema) => schema.name === "team_members")) {
|
|
2482
|
+
for (const schema of schemas) {
|
|
2483
|
+
const teamRoles = Object.entries(schema.permissions).filter(([, p35]) => [p35.read, p35.update, p35.delete].includes("team")).map(([role]) => role);
|
|
2484
|
+
if (teamRoles.length > 0) {
|
|
2485
|
+
warnings.push(
|
|
2486
|
+
`[${schema.name}] role(s) ${teamRoles.join(", ")} use the 'team' permission level, but no 'team_members' collection is registered. 'team' resolves membership from team_members (teamId, userId, status), so without it those roles are denied everything \u2014 silently. Register a team_members schema or use another level.`
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return warnings;
|
|
2492
|
+
}
|
|
2450
2493
|
|
|
2451
2494
|
// src/cli/lib/schema-lint.ts
|
|
2452
2495
|
async function lintProjectSchemas(appDir) {
|
|
@@ -2475,15 +2518,20 @@ async function lintProjectSchemas(appDir) {
|
|
|
2475
2518
|
return null;
|
|
2476
2519
|
}
|
|
2477
2520
|
if (!Array.isArray(schemas)) return null;
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
if (!schema || typeof schema !== "object") continue;
|
|
2521
|
+
const wellFormed = schemas.filter((schema) => {
|
|
2522
|
+
if (!schema || typeof schema !== "object") return false;
|
|
2481
2523
|
try {
|
|
2482
|
-
|
|
2524
|
+
lintSchema(schema);
|
|
2525
|
+
return true;
|
|
2483
2526
|
} catch {
|
|
2527
|
+
return false;
|
|
2484
2528
|
}
|
|
2529
|
+
});
|
|
2530
|
+
try {
|
|
2531
|
+
return lintSchemas(wellFormed);
|
|
2532
|
+
} catch {
|
|
2533
|
+
return null;
|
|
2485
2534
|
}
|
|
2486
|
-
return findings;
|
|
2487
2535
|
}
|
|
2488
2536
|
var MAX_PRINTED_FINDINGS = 5;
|
|
2489
2537
|
function formatSchemaLintFindings(findings) {
|
|
@@ -2503,11 +2551,11 @@ import { createServer, Socket } from "net";
|
|
|
2503
2551
|
var DEFAULT_PORT = 5173;
|
|
2504
2552
|
function isPortListening(port, host, timeoutMs = 500) {
|
|
2505
2553
|
const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2506
|
-
return new Promise((
|
|
2554
|
+
return new Promise((resolve22) => {
|
|
2507
2555
|
const socket = new Socket();
|
|
2508
2556
|
const done = (listening) => {
|
|
2509
2557
|
socket.destroy();
|
|
2510
|
-
|
|
2558
|
+
resolve22(listening);
|
|
2511
2559
|
};
|
|
2512
2560
|
socket.setTimeout(timeoutMs);
|
|
2513
2561
|
socket.once("connect", () => done(true));
|
|
@@ -2520,7 +2568,7 @@ async function waitForPortListening(port, host, timeoutMs, signal) {
|
|
|
2520
2568
|
const deadline = Date.now() + timeoutMs;
|
|
2521
2569
|
while (Date.now() < deadline && !signal?.aborted) {
|
|
2522
2570
|
if (await isPortListening(port, host)) return true;
|
|
2523
|
-
await new Promise((
|
|
2571
|
+
await new Promise((resolve22) => setTimeout(resolve22, 250));
|
|
2524
2572
|
}
|
|
2525
2573
|
return false;
|
|
2526
2574
|
}
|
|
@@ -2738,7 +2786,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2738
2786
|
|
|
2739
2787
|
// src/cli/commands/kill.ts
|
|
2740
2788
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2741
|
-
import { readFileSync as
|
|
2789
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync3, readlinkSync } from "fs";
|
|
2742
2790
|
import { setTimeout as wait } from "timers/promises";
|
|
2743
2791
|
var SIGTERM_GRACE_MS = 1500;
|
|
2744
2792
|
var IS_WIN = process.platform === "win32";
|
|
@@ -2889,7 +2937,7 @@ function listenerPidsViaProc(port) {
|
|
|
2889
2937
|
for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
2890
2938
|
let data;
|
|
2891
2939
|
try {
|
|
2892
|
-
data =
|
|
2940
|
+
data = readFileSync11(f, "utf-8");
|
|
2893
2941
|
} catch {
|
|
2894
2942
|
continue;
|
|
2895
2943
|
}
|
|
@@ -3027,7 +3075,8 @@ function isAlive(pid) {
|
|
|
3027
3075
|
}
|
|
3028
3076
|
|
|
3029
3077
|
// src/cli/commands/test.ts
|
|
3030
|
-
import {
|
|
3078
|
+
import { readdirSync as readdirSync4 } from "fs";
|
|
3079
|
+
import { join as join13, relative, resolve as resolve9, sep as sep2 } from "path";
|
|
3031
3080
|
import { sync as spawnSync4 } from "cross-spawn";
|
|
3032
3081
|
|
|
3033
3082
|
// src/cli/lib/playwright.ts
|
|
@@ -3056,10 +3105,10 @@ function ensurePlaywright(appDir) {
|
|
|
3056
3105
|
}
|
|
3057
3106
|
|
|
3058
3107
|
// src/cli/lib/test-account-service.ts
|
|
3059
|
-
import { readFileSync as
|
|
3108
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
3060
3109
|
|
|
3061
3110
|
// src/testing/accounts.ts
|
|
3062
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as
|
|
3111
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
3063
3112
|
import { homedir as homedir2 } from "os";
|
|
3064
3113
|
import { join as join12 } from "path";
|
|
3065
3114
|
var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
|
|
@@ -3129,7 +3178,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3129
3178
|
function readTestAccountStore() {
|
|
3130
3179
|
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3131
3180
|
try {
|
|
3132
|
-
const raw = JSON.parse(
|
|
3181
|
+
const raw = JSON.parse(readFileSync12(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3133
3182
|
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
3134
3183
|
if (!raw || typeof raw !== "object") {
|
|
3135
3184
|
return { version: 2, scopes: {} };
|
|
@@ -3160,7 +3209,7 @@ function saveTestAccountStore(store) {
|
|
|
3160
3209
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
3161
3210
|
var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
3162
3211
|
function sessionCookie() {
|
|
3163
|
-
const token =
|
|
3212
|
+
const token = readFileSync13(SESSION_PATH, "utf-8").trim();
|
|
3164
3213
|
return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
|
|
3165
3214
|
}
|
|
3166
3215
|
async function fetchRemoteTestAccounts() {
|
|
@@ -3208,6 +3257,7 @@ async function recoverRemoteTestAccount(id) {
|
|
|
3208
3257
|
email: data.email,
|
|
3209
3258
|
userId: data.userId,
|
|
3210
3259
|
label: data.label ?? null,
|
|
3260
|
+
name: typeof data.name === "string" ? data.name : null,
|
|
3211
3261
|
createdAt: data.createdAt ?? Date.now(),
|
|
3212
3262
|
password: data.password
|
|
3213
3263
|
};
|
|
@@ -3231,6 +3281,28 @@ async function syncTestAccountStore() {
|
|
|
3231
3281
|
// src/cli/commands/test.ts
|
|
3232
3282
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3233
3283
|
var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
|
|
3284
|
+
var DEFAULT_SUITE_SPECS = ["tests/smoke.spec.ts", "tests/api.spec.ts"];
|
|
3285
|
+
function specsSkippedByDefaultSuite(appDir) {
|
|
3286
|
+
const found = [];
|
|
3287
|
+
const walk = (dir) => {
|
|
3288
|
+
let entries;
|
|
3289
|
+
try {
|
|
3290
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
3291
|
+
} catch {
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
for (const entry of entries) {
|
|
3295
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
3296
|
+
const full = join13(dir, entry.name);
|
|
3297
|
+
if (entry.isDirectory()) walk(full);
|
|
3298
|
+
else if (entry.name.endsWith(".spec.ts")) {
|
|
3299
|
+
found.push(relative(appDir, full).split(sep2).join("/"));
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
};
|
|
3303
|
+
walk(join13(appDir, "tests"));
|
|
3304
|
+
return found.filter((spec) => !DEFAULT_SUITE_SPECS.includes(spec)).sort();
|
|
3305
|
+
}
|
|
3234
3306
|
function playwrightTestArgs(testFiles, flags = {}) {
|
|
3235
3307
|
return [
|
|
3236
3308
|
"playwright",
|
|
@@ -3250,7 +3322,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3250
3322
|
name: "test",
|
|
3251
3323
|
description: "Run tests for your DeepSpace app"
|
|
3252
3324
|
},
|
|
3253
|
-
jsonDescription: "Stream test output;
|
|
3325
|
+
jsonDescription: "Stream test output on stderr; stdout is the single-line JSON result",
|
|
3254
3326
|
args: {
|
|
3255
3327
|
suite: {
|
|
3256
3328
|
type: "positional",
|
|
@@ -3285,6 +3357,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3285
3357
|
}
|
|
3286
3358
|
},
|
|
3287
3359
|
async run({ args }) {
|
|
3360
|
+
suiteOutputToStderr = args.json === true;
|
|
3288
3361
|
const say = (line) => {
|
|
3289
3362
|
if (!args.json) console.log(line);
|
|
3290
3363
|
};
|
|
@@ -3350,6 +3423,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3350
3423
|
ensurePlaywright(appDir);
|
|
3351
3424
|
}
|
|
3352
3425
|
let exitCode = 0;
|
|
3426
|
+
let skippedSpecs = [];
|
|
3353
3427
|
switch (suite) {
|
|
3354
3428
|
case "smoke":
|
|
3355
3429
|
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
|
|
@@ -3371,13 +3445,13 @@ var test_default = defineDeepspaceCommand({
|
|
|
3371
3445
|
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
|
|
3372
3446
|
break;
|
|
3373
3447
|
case "default":
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3448
|
+
skippedSpecs = specsSkippedByDefaultSuite(appDir);
|
|
3449
|
+
exitCode = runPlaywright(appDir, [...DEFAULT_SUITE_SPECS], port, wranglerEnv, forwarded);
|
|
3450
|
+
if (skippedSpecs.length > 0) {
|
|
3451
|
+
say(
|
|
3452
|
+
`skipped ${skippedSpecs.length} spec file(s) not in smoke+api (${skippedSpecs.join(", ")}) \u2014 run 'deepspace test run all'`
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3381
3455
|
break;
|
|
3382
3456
|
default:
|
|
3383
3457
|
if (suite.endsWith(".spec.ts")) {
|
|
@@ -3392,12 +3466,13 @@ Available: smoke, api, e2e, unit, all`,
|
|
|
3392
3466
|
}
|
|
3393
3467
|
if (exitCode !== 0) {
|
|
3394
3468
|
throw new Refusal(`Test suite '${suite}' failed (exit ${exitCode}).`, "tests_failed", {
|
|
3395
|
-
extra: { suite, port, exitCode }
|
|
3469
|
+
extra: { suite, port, exitCode, skippedSpecs }
|
|
3396
3470
|
});
|
|
3397
3471
|
}
|
|
3398
|
-
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
3472
|
+
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs } };
|
|
3399
3473
|
}
|
|
3400
3474
|
});
|
|
3475
|
+
var suiteOutputToStderr = false;
|
|
3401
3476
|
function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
|
|
3402
3477
|
let wranglerConfig;
|
|
3403
3478
|
try {
|
|
@@ -3409,7 +3484,9 @@ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
|
|
|
3409
3484
|
try {
|
|
3410
3485
|
const result = spawnSync4("npx", argv, {
|
|
3411
3486
|
cwd: appDir,
|
|
3412
|
-
|
|
3487
|
+
// Under --json the child's stdout is routed to our stderr: the live
|
|
3488
|
+
// suite output stays visible and stdout stays a single JSON line.
|
|
3489
|
+
stdio: suiteOutputToStderr ? ["inherit", 2, 2] : "inherit",
|
|
3413
3490
|
env: wranglerViteEnv(process.env, wranglerConfig, extraEnv)
|
|
3414
3491
|
});
|
|
3415
3492
|
return result.status ?? 1;
|
|
@@ -3829,7 +3906,7 @@ var recover = defineDeepspaceCommand({
|
|
|
3829
3906
|
for (const target2 of targets) {
|
|
3830
3907
|
try {
|
|
3831
3908
|
const account = await recoverRemoteTestAccount(target2.id);
|
|
3832
|
-
const name = localNames.get(account.email);
|
|
3909
|
+
const name = account.name ?? localNames.get(account.email);
|
|
3833
3910
|
upsertTestAccount({
|
|
3834
3911
|
id: account.id,
|
|
3835
3912
|
email: account.email,
|
|
@@ -3880,7 +3957,7 @@ var test_accounts_default = defineCommand2({
|
|
|
3880
3957
|
// src/cli/commands/deploy.ts
|
|
3881
3958
|
import * as p12 from "@clack/prompts";
|
|
3882
3959
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3883
|
-
import { resolve as
|
|
3960
|
+
import { join as join19, resolve as resolve14 } from "path";
|
|
3884
3961
|
|
|
3885
3962
|
// src/cli/lib/edge-propagation.ts
|
|
3886
3963
|
import { request as httpRequest } from "http";
|
|
@@ -3945,14 +4022,14 @@ async function servesRelease(probe, releaseStamp) {
|
|
|
3945
4022
|
}
|
|
3946
4023
|
function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
3947
4024
|
const send = url.protocol === "http:" ? httpRequest : httpsRequest;
|
|
3948
|
-
return new Promise((
|
|
4025
|
+
return new Promise((resolve22) => {
|
|
3949
4026
|
const request2 = send(
|
|
3950
4027
|
url,
|
|
3951
4028
|
{ method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
|
|
3952
4029
|
(response) => {
|
|
3953
4030
|
if (response.statusCode !== 200) {
|
|
3954
4031
|
response.resume();
|
|
3955
|
-
|
|
4032
|
+
resolve22(null);
|
|
3956
4033
|
return;
|
|
3957
4034
|
}
|
|
3958
4035
|
let body = "";
|
|
@@ -3960,20 +4037,20 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
|
3960
4037
|
response.on("data", (chunk) => {
|
|
3961
4038
|
if (body.length < 4096) body += chunk;
|
|
3962
4039
|
});
|
|
3963
|
-
response.on("end", () =>
|
|
3964
|
-
response.on("error", () =>
|
|
4040
|
+
response.on("end", () => resolve22(body));
|
|
4041
|
+
response.on("error", () => resolve22(null));
|
|
3965
4042
|
}
|
|
3966
4043
|
);
|
|
3967
4044
|
request2.on("timeout", () => {
|
|
3968
4045
|
request2.destroy();
|
|
3969
|
-
|
|
4046
|
+
resolve22(null);
|
|
3970
4047
|
});
|
|
3971
|
-
request2.on("error", () =>
|
|
4048
|
+
request2.on("error", () => resolve22(null));
|
|
3972
4049
|
request2.end();
|
|
3973
4050
|
});
|
|
3974
4051
|
}
|
|
3975
4052
|
function sleep2(ms) {
|
|
3976
|
-
return new Promise((
|
|
4053
|
+
return new Promise((resolve22) => setTimeout(resolve22, ms));
|
|
3977
4054
|
}
|
|
3978
4055
|
|
|
3979
4056
|
// src/shared/app-files.ts
|
|
@@ -4038,36 +4115,49 @@ function describeFilesFailure(status3, body) {
|
|
|
4038
4115
|
import * as p6 from "@clack/prompts";
|
|
4039
4116
|
import { execSync as execSync2 } from "child_process";
|
|
4040
4117
|
import { createHash as createHash2 } from "crypto";
|
|
4041
|
-
import { existsSync as
|
|
4042
|
-
import { dirname as dirname7, join as
|
|
4118
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "fs";
|
|
4119
|
+
import { dirname as dirname7, join as join17, resolve as resolve12 } from "path";
|
|
4120
|
+
|
|
4121
|
+
// src/build/plugin.ts
|
|
4122
|
+
import { existsSync as existsSync12, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
4123
|
+
import { join as join14, resolve as resolve11 } from "path";
|
|
4124
|
+
function removeBuildDevVars(workerDir) {
|
|
4125
|
+
const path = join14(workerDir, ".dev.vars");
|
|
4126
|
+
if (!existsSync12(path)) return false;
|
|
4127
|
+
if (!lstatSync(path).isFile()) {
|
|
4128
|
+
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4129
|
+
}
|
|
4130
|
+
unlinkSync2(path);
|
|
4131
|
+
return true;
|
|
4132
|
+
}
|
|
4043
4133
|
|
|
4044
4134
|
// src/documentation/deploy.ts
|
|
4045
|
-
import { existsSync as
|
|
4046
|
-
import { join as
|
|
4135
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4136
|
+
import { join as join15 } from "path";
|
|
4047
4137
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
4048
|
-
if (!
|
|
4049
|
-
const manifestPath =
|
|
4050
|
-
if (!
|
|
4138
|
+
if (!existsSync13(join15(appDir, "documentation.json"))) return void 0;
|
|
4139
|
+
const manifestPath = join15(clientDir, "_documentation", "manifest.json");
|
|
4140
|
+
if (!existsSync13(manifestPath)) {
|
|
4051
4141
|
throw new Error(
|
|
4052
4142
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
4053
4143
|
);
|
|
4054
4144
|
}
|
|
4055
|
-
return JSON.parse(
|
|
4145
|
+
return JSON.parse(readFileSync14(manifestPath, "utf8"));
|
|
4056
4146
|
}
|
|
4057
4147
|
|
|
4058
4148
|
// src/cli/commands/update/app-migrations.ts
|
|
4059
4149
|
import {
|
|
4060
4150
|
closeSync,
|
|
4061
4151
|
constants,
|
|
4062
|
-
existsSync as
|
|
4152
|
+
existsSync as existsSync14,
|
|
4063
4153
|
fstatSync,
|
|
4064
4154
|
ftruncateSync,
|
|
4065
|
-
lstatSync,
|
|
4155
|
+
lstatSync as lstatSync2,
|
|
4066
4156
|
openSync,
|
|
4067
|
-
readFileSync as
|
|
4157
|
+
readFileSync as readFileSync15,
|
|
4068
4158
|
writeSync
|
|
4069
4159
|
} from "fs";
|
|
4070
|
-
import { join as
|
|
4160
|
+
import { join as join16 } from "path";
|
|
4071
4161
|
|
|
4072
4162
|
// src/shared/protocol/app-migrations.ts
|
|
4073
4163
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
@@ -4119,8 +4209,8 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
|
4119
4209
|
({ id, description }) => ({ id, description })
|
|
4120
4210
|
);
|
|
4121
4211
|
function readAppliedAppMigrations(appDir) {
|
|
4122
|
-
const path =
|
|
4123
|
-
if (!
|
|
4212
|
+
const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4213
|
+
if (!existsSync14(path)) return [];
|
|
4124
4214
|
const source = readRegularFile(path);
|
|
4125
4215
|
let parsed;
|
|
4126
4216
|
try {
|
|
@@ -4146,10 +4236,10 @@ function planAppMigrations(appDir) {
|
|
|
4146
4236
|
const edits = [];
|
|
4147
4237
|
for (const repoPath of tracked) {
|
|
4148
4238
|
if (!isSourcePath(repoPath, appPrefix)) continue;
|
|
4149
|
-
const absolutePath =
|
|
4239
|
+
const absolutePath = join16(repo, repoPath);
|
|
4150
4240
|
let stat;
|
|
4151
4241
|
try {
|
|
4152
|
-
stat =
|
|
4242
|
+
stat = lstatSync2(absolutePath);
|
|
4153
4243
|
} catch {
|
|
4154
4244
|
continue;
|
|
4155
4245
|
}
|
|
@@ -4183,8 +4273,8 @@ function planAppMigrations(appDir) {
|
|
|
4183
4273
|
});
|
|
4184
4274
|
if (pending.length > 0 && blockers.length === 0) {
|
|
4185
4275
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
4186
|
-
const absoluteManifestPath =
|
|
4187
|
-
const before =
|
|
4276
|
+
const absoluteManifestPath = join16(repo, manifestPath);
|
|
4277
|
+
const before = existsSync14(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
4188
4278
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
4189
4279
|
`;
|
|
4190
4280
|
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
@@ -4199,13 +4289,13 @@ function planAppMigrations(appDir) {
|
|
|
4199
4289
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4200
4290
|
const repo = repoToplevel(appDir);
|
|
4201
4291
|
for (const edit of plan.edits) {
|
|
4202
|
-
const absolutePath =
|
|
4203
|
-
const current =
|
|
4292
|
+
const absolutePath = join16(repo, edit.path);
|
|
4293
|
+
const current = existsSync14(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4204
4294
|
if (current !== edit.before) {
|
|
4205
4295
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4206
4296
|
}
|
|
4207
4297
|
}
|
|
4208
|
-
for (const edit of plan.edits) writeRegularFile(
|
|
4298
|
+
for (const edit of plan.edits) writeRegularFile(join16(repo, edit.path), edit.before, edit.after);
|
|
4209
4299
|
}
|
|
4210
4300
|
function normalizeEol(source) {
|
|
4211
4301
|
return source.replaceAll("\r\n", "\n");
|
|
@@ -4214,18 +4304,18 @@ function restoreEol(source, checkout) {
|
|
|
4214
4304
|
return checkout.includes("\r\n") ? source.replaceAll("\n", "\r\n") : source;
|
|
4215
4305
|
}
|
|
4216
4306
|
function readRegularFile(path) {
|
|
4217
|
-
const before =
|
|
4307
|
+
const before = lstatSync2(path);
|
|
4218
4308
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
4219
4309
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4220
4310
|
}
|
|
4221
4311
|
const fd = openSync(path, constants.O_RDONLY);
|
|
4222
4312
|
try {
|
|
4223
4313
|
const opened = fstatSync(fd);
|
|
4224
|
-
const after =
|
|
4314
|
+
const after = lstatSync2(path);
|
|
4225
4315
|
if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
4226
4316
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4227
4317
|
}
|
|
4228
|
-
return
|
|
4318
|
+
return readFileSync15(fd, "utf-8");
|
|
4229
4319
|
} finally {
|
|
4230
4320
|
closeSync(fd);
|
|
4231
4321
|
}
|
|
@@ -4240,18 +4330,18 @@ function writeRegularFile(path, before, after) {
|
|
|
4240
4330
|
}
|
|
4241
4331
|
return;
|
|
4242
4332
|
}
|
|
4243
|
-
const pathStat =
|
|
4333
|
+
const pathStat = lstatSync2(path);
|
|
4244
4334
|
if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
|
|
4245
4335
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4246
4336
|
}
|
|
4247
4337
|
const fd = openSync(path, constants.O_RDWR);
|
|
4248
4338
|
try {
|
|
4249
4339
|
const opened = fstatSync(fd);
|
|
4250
|
-
const currentPath =
|
|
4340
|
+
const currentPath = lstatSync2(path);
|
|
4251
4341
|
if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
|
|
4252
4342
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4253
4343
|
}
|
|
4254
|
-
if (
|
|
4344
|
+
if (readFileSync15(fd, "utf-8") !== before) {
|
|
4255
4345
|
throw new Error(`Migration input changed after planning: ${path}`);
|
|
4256
4346
|
}
|
|
4257
4347
|
ftruncateSync(fd, 0);
|
|
@@ -4578,16 +4668,6 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4578
4668
|
|
|
4579
4669
|
// src/cli/commands/deploy/build.ts
|
|
4580
4670
|
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4581
|
-
function removeBuildDevVars(workerDir) {
|
|
4582
|
-
const path = join15(workerDir, ".dev.vars");
|
|
4583
|
-
if (!existsSync14(path)) return false;
|
|
4584
|
-
const stat = lstatSync2(path);
|
|
4585
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
4586
|
-
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4587
|
-
}
|
|
4588
|
-
unlinkSync2(path);
|
|
4589
|
-
return true;
|
|
4590
|
-
}
|
|
4591
4671
|
function declaredCompatibilityDate(config) {
|
|
4592
4672
|
const declared = config.compatibility_date;
|
|
4593
4673
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
@@ -4624,19 +4704,19 @@ async function buildDeployBundle(options) {
|
|
|
4624
4704
|
preparedWranglerConfig?.cleanup();
|
|
4625
4705
|
}
|
|
4626
4706
|
spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
|
|
4627
|
-
const deployConfigPath =
|
|
4628
|
-
if (!
|
|
4707
|
+
const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
|
|
4708
|
+
if (!existsSync15(deployConfigPath)) {
|
|
4629
4709
|
output.die(
|
|
4630
4710
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4631
4711
|
"build_output_missing"
|
|
4632
4712
|
);
|
|
4633
4713
|
}
|
|
4634
|
-
const deployConfig = JSON.parse(
|
|
4635
|
-
const outputWranglerPath =
|
|
4636
|
-
if (!
|
|
4714
|
+
const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
|
|
4715
|
+
const outputWranglerPath = resolve12(dirname7(deployConfigPath), deployConfig.configPath);
|
|
4716
|
+
if (!existsSync15(outputWranglerPath)) {
|
|
4637
4717
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4638
4718
|
}
|
|
4639
|
-
const outputConfig = JSON.parse(
|
|
4719
|
+
const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
|
|
4640
4720
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
4641
4721
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
4642
4722
|
output.die(
|
|
@@ -4650,12 +4730,12 @@ async function buildDeployBundle(options) {
|
|
|
4650
4730
|
} catch (error) {
|
|
4651
4731
|
output.die(errorMessage(error), "build_output_unsafe");
|
|
4652
4732
|
}
|
|
4653
|
-
const workerBundlePath =
|
|
4654
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
4655
|
-
if (!
|
|
4733
|
+
const workerBundlePath = join17(workerDir, outputConfig.main);
|
|
4734
|
+
const clientDir = outputConfig.assets?.directory ? resolve12(workerDir, outputConfig.assets.directory) : null;
|
|
4735
|
+
if (!existsSync15(workerBundlePath)) {
|
|
4656
4736
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4657
4737
|
}
|
|
4658
|
-
if (!clientDir || !
|
|
4738
|
+
if (!clientDir || !existsSync15(clientDir)) {
|
|
4659
4739
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4660
4740
|
}
|
|
4661
4741
|
try {
|
|
@@ -4670,7 +4750,7 @@ async function buildDeployBundle(options) {
|
|
|
4670
4750
|
spinner2.stop(`Collected ${assets.length} assets`);
|
|
4671
4751
|
const appIdRefusal = clientAppIdRefusal(assets, appId, envName);
|
|
4672
4752
|
if (appIdRefusal) output.die(appIdRefusal.message, appIdRefusal.code);
|
|
4673
|
-
const workerJs =
|
|
4753
|
+
const workerJs = readFileSync16(workerBundlePath, "utf-8");
|
|
4674
4754
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
4675
4755
|
const sqliteClasses = new Set(
|
|
4676
4756
|
outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
|
|
@@ -4753,7 +4833,7 @@ function clientAppIdRefusal(assets, appId, envName) {
|
|
|
4753
4833
|
let unsubstituted;
|
|
4754
4834
|
for (const asset of assets) {
|
|
4755
4835
|
if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
|
|
4756
|
-
const source =
|
|
4836
|
+
const source = readFileSync16(asset.sourcePath, "utf-8");
|
|
4757
4837
|
if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
|
|
4758
4838
|
for (const found of source.matchAll(APP_ID_LITERAL)) {
|
|
4759
4839
|
if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
|
|
@@ -4780,14 +4860,14 @@ var LARGE_MEDIA_HOME = "Large media belongs in your app files (`deepspace app fi
|
|
|
4780
4860
|
function collectAssets(dir) {
|
|
4781
4861
|
const assets = [];
|
|
4782
4862
|
const walk = (currentDir, prefix) => {
|
|
4783
|
-
for (const entry of
|
|
4863
|
+
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
4784
4864
|
if (isDeployAssetControlFile(entry.name)) continue;
|
|
4785
|
-
const fullPath =
|
|
4865
|
+
const fullPath = join17(currentDir, entry.name);
|
|
4786
4866
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
4787
4867
|
if (entry.isDirectory()) {
|
|
4788
4868
|
walk(fullPath, relativePath);
|
|
4789
4869
|
} else {
|
|
4790
|
-
const bytes =
|
|
4870
|
+
const bytes = readFileSync16(fullPath);
|
|
4791
4871
|
assets.push({
|
|
4792
4872
|
path: `/${relativePath}`,
|
|
4793
4873
|
hash: createHash2("sha256").update(bytes).digest("hex"),
|
|
@@ -4806,9 +4886,9 @@ function readDeployAssetConfig(clientDir) {
|
|
|
4806
4886
|
["_headers", "_headers"],
|
|
4807
4887
|
["_redirects", "_redirects"]
|
|
4808
4888
|
]) {
|
|
4809
|
-
const filePath =
|
|
4810
|
-
if (!
|
|
4811
|
-
const content =
|
|
4889
|
+
const filePath = join17(clientDir, file);
|
|
4890
|
+
if (!existsSync15(filePath)) continue;
|
|
4891
|
+
const content = readFileSync16(filePath, "utf8");
|
|
4812
4892
|
if (content.trim()) config[key] = content;
|
|
4813
4893
|
}
|
|
4814
4894
|
return config;
|
|
@@ -4822,8 +4902,8 @@ function errorMessage(error) {
|
|
|
4822
4902
|
|
|
4823
4903
|
// src/cli/commands/deploy/commerce.ts
|
|
4824
4904
|
import * as p7 from "@clack/prompts";
|
|
4825
|
-
import { existsSync as
|
|
4826
|
-
import { join as
|
|
4905
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4906
|
+
import { join as join18 } from "path";
|
|
4827
4907
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4828
4908
|
|
|
4829
4909
|
// src/shared/api-error.ts
|
|
@@ -4875,10 +4955,10 @@ function errorMessage2(error) {
|
|
|
4875
4955
|
return error instanceof Error ? error.message : String(error);
|
|
4876
4956
|
}
|
|
4877
4957
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4878
|
-
const sourcePath =
|
|
4879
|
-
if (!
|
|
4958
|
+
const sourcePath = join18(appDir, "src", sourceFile);
|
|
4959
|
+
if (!existsSync16(sourcePath)) return void 0;
|
|
4880
4960
|
const esbuild = await import("esbuild");
|
|
4881
|
-
const outputPath =
|
|
4961
|
+
const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
|
|
4882
4962
|
await esbuild.build({
|
|
4883
4963
|
entryPoints: [sourcePath],
|
|
4884
4964
|
bundle: true,
|
|
@@ -4971,7 +5051,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4971
5051
|
}
|
|
4972
5052
|
}
|
|
4973
5053
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4974
|
-
if (!
|
|
5054
|
+
if (!existsSync16(join18(appDir, "src", "subscriptions.ts"))) return;
|
|
4975
5055
|
let plans;
|
|
4976
5056
|
try {
|
|
4977
5057
|
plans = await loadDeclaration(
|
|
@@ -5026,7 +5106,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
5026
5106
|
}
|
|
5027
5107
|
}
|
|
5028
5108
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
5029
|
-
if (!
|
|
5109
|
+
if (!existsSync16(join18(appDir, "src", "products.ts"))) return;
|
|
5030
5110
|
let products;
|
|
5031
5111
|
try {
|
|
5032
5112
|
products = await loadDeclaration(
|
|
@@ -5128,7 +5208,7 @@ function createDeployOutput(json) {
|
|
|
5128
5208
|
|
|
5129
5209
|
// src/cli/commands/deploy/request.ts
|
|
5130
5210
|
import * as p10 from "@clack/prompts";
|
|
5131
|
-
import { readFileSync as
|
|
5211
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
5132
5212
|
|
|
5133
5213
|
// src/cli/commands/deploy/repository.ts
|
|
5134
5214
|
import * as p9 from "@clack/prompts";
|
|
@@ -5319,9 +5399,9 @@ function repoApi(deployUrl, token, appId) {
|
|
|
5319
5399
|
}
|
|
5320
5400
|
|
|
5321
5401
|
// src/cli/lib/vc-remote.ts
|
|
5322
|
-
import { existsSync as
|
|
5402
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5323
5403
|
import { tmpdir } from "os";
|
|
5324
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
5404
|
+
import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve13 } from "path";
|
|
5325
5405
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
5326
5406
|
if (environment === "staging") return "space-staging";
|
|
5327
5407
|
if (environment === "production") return "space";
|
|
@@ -5367,14 +5447,14 @@ function entryIsTransient(cwd) {
|
|
|
5367
5447
|
const entry = process.argv[1];
|
|
5368
5448
|
if (!entry) return true;
|
|
5369
5449
|
if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
|
|
5370
|
-
const rel =
|
|
5450
|
+
const rel = relative2(resolve13(cwd), resolve13(entry));
|
|
5371
5451
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
5372
5452
|
}
|
|
5373
5453
|
function helperEntryUsable(command) {
|
|
5374
5454
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
5375
5455
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
5376
5456
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
5377
|
-
return
|
|
5457
|
+
return existsSync17(match[2]) && existsSync17(match[3]);
|
|
5378
5458
|
}
|
|
5379
5459
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
5380
5460
|
const entry = process.argv[1];
|
|
@@ -5509,12 +5589,6 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5509
5589
|
error: `A first push to this new app hit your plan's active-app quota. Use \`deepspace app list\` to choose an app to undeploy, or upgrade your plan, then retry.`
|
|
5510
5590
|
};
|
|
5511
5591
|
}
|
|
5512
|
-
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
5513
|
-
return {
|
|
5514
|
-
code: "source_managed_by_github",
|
|
5515
|
-
error: "This app uses GitHub source. Manage commits with normal Git/GitHub; `deepspace deploy` ships the local working tree without changing Git."
|
|
5516
|
-
};
|
|
5517
|
-
}
|
|
5518
5592
|
if (/(?:HTTP |error: )429\b/i.test(message)) {
|
|
5519
5593
|
return {
|
|
5520
5594
|
code: "rate_limited",
|
|
@@ -5524,6 +5598,12 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5524
5598
|
if (/(?:HTTP |error: )413\b/i.test(message)) {
|
|
5525
5599
|
return { code: "push_too_large", error: pushTooLargeMessage(cwd) };
|
|
5526
5600
|
}
|
|
5601
|
+
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
5602
|
+
return {
|
|
5603
|
+
code: "source_managed_by_github",
|
|
5604
|
+
error: "This app's source is managed by GitHub, so pushes to the DeepSpace repo are refused. Push to the GitHub repository instead \u2014 `deepspace app status` names it."
|
|
5605
|
+
};
|
|
5606
|
+
}
|
|
5527
5607
|
return null;
|
|
5528
5608
|
}
|
|
5529
5609
|
function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
@@ -5895,7 +5975,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5895
5975
|
const message = errorMessage3(error);
|
|
5896
5976
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5897
5977
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5898
|
-
await new Promise((
|
|
5978
|
+
await new Promise((resolve22) => setTimeout(resolve22, backoffMs[attempt]));
|
|
5899
5979
|
continue;
|
|
5900
5980
|
}
|
|
5901
5981
|
throw error;
|
|
@@ -5924,6 +6004,12 @@ function detachedHeadRefusal() {
|
|
|
5924
6004
|
}
|
|
5925
6005
|
|
|
5926
6006
|
// src/cli/commands/deploy/request.ts
|
|
6007
|
+
function renameRefusalMessage(rename2) {
|
|
6008
|
+
return `This deploy renames the app: ${rename2.fromHost} \u2192 ${rename2.toHost}. Confirmation needs an interactive terminal \u2014 re-run with --rename to approve the rename, or \`deepspace app init --new-id\` if you meant a separate app.`;
|
|
6009
|
+
}
|
|
6010
|
+
function renamePromptMessage(rename2) {
|
|
6011
|
+
return `This deploy renames the app: ${rename2.fromHost} \u2192 ${rename2.toHost}. The URL changes and the old one stops serving right away; data, secrets, and collaborators travel with it \u2014 the display name in \`src/constants.ts\` (\`APP_NAME\`) does not, update it yourself. (Meant a separate app? Run \`deepspace app init --new-id\` instead.) Rename?`;
|
|
6012
|
+
}
|
|
5927
6013
|
var CLOUDFLARE_DEPLOY_ERROR_HINT = "Deployment reached the DeepSpace deploy worker, but Cloudflare's deployment control-plane API failed. This is often caused by a Cloudflare Dashboard/API incident or Workers for Platforms entitlement outage. Already deployed apps should keep serving; wait for Cloudflare to recover and retry.";
|
|
5928
6014
|
var DEPLOY_SERVICE_LIMIT_HINT = "The DeepSpace deploy service hit a resource limit processing this deploy (the deploy worker was terminated before it could answer). This is a DeepSpace-side failure, not a Cloudflare incident \u2014 your app was not changed. Retry; if it keeps happening, report the app id and asset count.";
|
|
5929
6015
|
var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
|
|
@@ -6043,16 +6129,11 @@ async function deployBuiltBundle(options) {
|
|
|
6043
6129
|
let body = await response.json().catch(() => ({}));
|
|
6044
6130
|
if (response.status === 409 && body.code === "rename_required" && !confirmRename) {
|
|
6045
6131
|
spinner2.stop("Rename confirmation needed");
|
|
6132
|
+
const rename2 = { fromHost: body.fromHost ?? "", toHost: body.toHost ?? "" };
|
|
6046
6133
|
if (output.nonInteractive) {
|
|
6047
|
-
await bail(
|
|
6048
|
-
`This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. Confirmation needs an interactive terminal \u2014 re-run with --rename to approve the rename, or \`deepspace app init --new-id\` if you meant a separate app.`,
|
|
6049
|
-
null,
|
|
6050
|
-
"rename_required"
|
|
6051
|
-
);
|
|
6134
|
+
await bail(renameRefusalMessage(rename2), null, "rename_required");
|
|
6052
6135
|
}
|
|
6053
|
-
const confirmed = await p10.confirm({
|
|
6054
|
-
message: `This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. The URL changes and the old one stops serving right away; data, secrets, and collaborators travel with it. (Meant a separate app? Run \`deepspace app init --new-id\` instead.) Rename?`
|
|
6055
|
-
});
|
|
6136
|
+
const confirmed = await p10.confirm({ message: renamePromptMessage(rename2) });
|
|
6056
6137
|
if (p10.isCancel(confirmed) || !confirmed) await bail("Deploy cancelled.", null);
|
|
6057
6138
|
confirmRename = true;
|
|
6058
6139
|
spinner2.start(`Deploying ${appName}...`);
|
|
@@ -6179,7 +6260,7 @@ async function uploadDeployAssets(options) {
|
|
|
6179
6260
|
// against the plan before it touches storage, and a stream body
|
|
6180
6261
|
// would be sent chunked with no length at all; a retry also needs a
|
|
6181
6262
|
// body it can send again.
|
|
6182
|
-
body:
|
|
6263
|
+
body: readFileSync17(asset.sourcePath)
|
|
6183
6264
|
})
|
|
6184
6265
|
);
|
|
6185
6266
|
if (!response.ok) {
|
|
@@ -6260,7 +6341,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
6260
6341
|
lastError = error;
|
|
6261
6342
|
}
|
|
6262
6343
|
if (attempt < attempts) {
|
|
6263
|
-
await new Promise((
|
|
6344
|
+
await new Promise((resolve22) => setTimeout(resolve22, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
6264
6345
|
}
|
|
6265
6346
|
}
|
|
6266
6347
|
if (lastResponse) return lastResponse;
|
|
@@ -6395,180 +6476,213 @@ var deploy_default = defineCommand3({
|
|
|
6395
6476
|
},
|
|
6396
6477
|
async run({ args }) {
|
|
6397
6478
|
const output = createDeployOutput(args.json === true);
|
|
6398
|
-
preflightNodeVersion("deploy");
|
|
6399
|
-
const appDir = resolve13(args.dir ?? ".");
|
|
6400
|
-
const selectorRefusal = blankSelectorRefusal(args);
|
|
6401
|
-
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
6402
|
-
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
6403
|
-
if (!hasWranglerConfig(appDir)) {
|
|
6404
|
-
output.die("No wrangler.toml found. Are you in a DeepSpace app directory?", "not_in_app_repo");
|
|
6405
|
-
}
|
|
6406
6479
|
try {
|
|
6407
|
-
|
|
6480
|
+
await runDeploy(args, output);
|
|
6408
6481
|
} catch (error) {
|
|
6409
|
-
|
|
6410
|
-
output.die(
|
|
6411
|
-
action: refusal.action
|
|
6412
|
-
});
|
|
6482
|
+
if (error instanceof CliExit) throw error;
|
|
6483
|
+
output.die(formatCliError(error), errorCode(error) ?? "deploy_failed");
|
|
6413
6484
|
}
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6485
|
+
}
|
|
6486
|
+
});
|
|
6487
|
+
async function runDeploy(args, output) {
|
|
6488
|
+
preflightNodeVersion("deploy");
|
|
6489
|
+
const appDir = resolve14(args.dir ?? ".");
|
|
6490
|
+
const selectorRefusal = blankSelectorRefusal(args);
|
|
6491
|
+
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
6492
|
+
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
6493
|
+
if (!hasWranglerConfig(appDir)) {
|
|
6494
|
+
output.die(noWranglerConfigMessage(join19(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
6495
|
+
}
|
|
6496
|
+
try {
|
|
6497
|
+
ensureInstallReady(appDir);
|
|
6498
|
+
} catch (error) {
|
|
6499
|
+
const refusal = error;
|
|
6500
|
+
output.die(refusal.message ?? String(error), refusal.code ?? "deps_missing", {
|
|
6501
|
+
action: refusal.action
|
|
6502
|
+
});
|
|
6503
|
+
}
|
|
6504
|
+
p12.intro("Deploying DeepSpace app");
|
|
6505
|
+
output.showIntro();
|
|
6506
|
+
const wranglerConfig = readWranglerConfig(appDir);
|
|
6507
|
+
const nameResult = resolveAppNameForEnv(wranglerConfig, envName);
|
|
6508
|
+
if (!nameResult.ok) return output.die(nameResult.reason, nameResult.code ?? "invalid_app_name");
|
|
6509
|
+
const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
|
|
6510
|
+
if (declaredName !== nameResult.name) {
|
|
6511
|
+
const location = envName ? `[env.${envName}].name` : "`name`";
|
|
6512
|
+
output.die(
|
|
6513
|
+
`wrangler.toml: ${location} "${declaredName}" is not in canonical form. Update it to "${nameResult.name}" and re-run.`,
|
|
6514
|
+
"invalid_app_name"
|
|
6515
|
+
);
|
|
6516
|
+
}
|
|
6517
|
+
const appName = nameResult.name;
|
|
6518
|
+
const appId = readAppId(appDir, envName);
|
|
6519
|
+
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
6520
|
+
const { token, ownerId } = await authenticate(appDir, output);
|
|
6521
|
+
if (!appId) {
|
|
6522
|
+
return output.die(
|
|
6523
|
+
`No app id in wrangler.toml${envName ? ` for [env.${envName}]` : ""}. Run \`deepspace app init${envName ? ` --env ${envName}` : ""}\` to register this app, then re-deploy.`,
|
|
6524
|
+
"app_not_initialized",
|
|
6525
|
+
{
|
|
6526
|
+
action: {
|
|
6527
|
+
cwd: appDir,
|
|
6528
|
+
argv: ["deepspace", "app", "init", ...envName ? ["--env", envName] : []]
|
|
6529
|
+
},
|
|
6530
|
+
actionRequired: true
|
|
6531
|
+
}
|
|
6532
|
+
);
|
|
6533
|
+
}
|
|
6534
|
+
p12.log.info(`Id: ${appId}`);
|
|
6535
|
+
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6536
|
+
if (!sourceState.registered) {
|
|
6537
|
+
output.die(
|
|
6538
|
+
`${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init\`.`,
|
|
6539
|
+
"app_not_registered"
|
|
6540
|
+
);
|
|
6541
|
+
}
|
|
6542
|
+
const secretsCache = await loadDeploySecrets({
|
|
6543
|
+
deployUrl: DEPLOY_URL3,
|
|
6544
|
+
appDir,
|
|
6545
|
+
appId,
|
|
6546
|
+
envName,
|
|
6547
|
+
ownerId,
|
|
6548
|
+
token,
|
|
6549
|
+
output
|
|
6550
|
+
});
|
|
6551
|
+
const ownerJwtRefusal = ownerJwtMissingRefusal(sourceState);
|
|
6552
|
+
if (ownerJwtRefusal) output.die(ownerJwtRefusal.error, ownerJwtRefusal.code);
|
|
6553
|
+
let confirmRename = args.rename === true;
|
|
6554
|
+
const rename2 = pendingRename(sourceState.registeredHost, appName);
|
|
6555
|
+
if (rename2 && !confirmRename) {
|
|
6556
|
+
if (output.nonInteractive) {
|
|
6557
|
+
output.die(renameRefusalMessage(rename2), "rename_required");
|
|
6426
6558
|
}
|
|
6427
|
-
const
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6559
|
+
const confirmed = await p12.confirm({ message: renamePromptMessage(rename2) });
|
|
6560
|
+
if (p12.isCancel(confirmed) || !confirmed) output.die("Deploy cancelled.", "rename_declined");
|
|
6561
|
+
confirmRename = true;
|
|
6562
|
+
}
|
|
6563
|
+
const repositoryPreflight = preflightDeployRepository({
|
|
6564
|
+
appDir,
|
|
6565
|
+
push: args.push !== false,
|
|
6566
|
+
source: sourceState.source
|
|
6567
|
+
});
|
|
6568
|
+
if (repositoryPreflight) {
|
|
6569
|
+
output.die(repositoryPreflight.error, repositoryPreflight.code);
|
|
6570
|
+
}
|
|
6571
|
+
const spinner2 = createSpinner();
|
|
6572
|
+
const bundle = await buildDeployBundle({
|
|
6573
|
+
appDir,
|
|
6574
|
+
appName,
|
|
6575
|
+
appId,
|
|
6576
|
+
envName,
|
|
6577
|
+
output,
|
|
6578
|
+
spinner: spinner2
|
|
6579
|
+
});
|
|
6580
|
+
const oversized = oversizedAssetRefusal(bundle.assets);
|
|
6581
|
+
if (oversized) {
|
|
6582
|
+
spinner2.stop("Deploy failed");
|
|
6583
|
+
output.die(oversized, "assets_too_large");
|
|
6584
|
+
}
|
|
6585
|
+
const repository = await syncDeployRepository({
|
|
6586
|
+
deployUrl: DEPLOY_URL3,
|
|
6587
|
+
appDir,
|
|
6588
|
+
appId,
|
|
6589
|
+
token,
|
|
6590
|
+
push: args.push !== false,
|
|
6591
|
+
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6592
|
+
output,
|
|
6593
|
+
sourceState
|
|
6594
|
+
});
|
|
6595
|
+
const secrets = prepareDeploySecrets({
|
|
6596
|
+
cache: secretsCache,
|
|
6597
|
+
customBindings: bundle.customBindings,
|
|
6598
|
+
doManifest: bundle.doManifest,
|
|
6599
|
+
output
|
|
6600
|
+
});
|
|
6601
|
+
const body = await deployBuiltBundle({
|
|
6602
|
+
deployUrl: DEPLOY_URL3,
|
|
6603
|
+
appDir,
|
|
6604
|
+
appId,
|
|
6605
|
+
appName,
|
|
6606
|
+
token,
|
|
6607
|
+
rename: confirmRename,
|
|
6608
|
+
claimReleased: args["claim-released"] === true,
|
|
6609
|
+
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6610
|
+
bundle,
|
|
6611
|
+
secrets,
|
|
6612
|
+
repository,
|
|
6613
|
+
output,
|
|
6614
|
+
spinner: spinner2
|
|
6615
|
+
});
|
|
6616
|
+
let serving = "unverifiable";
|
|
6617
|
+
const edgeWaitStarted = Date.now();
|
|
6618
|
+
if (body.url) {
|
|
6619
|
+
spinner2.message("Waiting for the edge to serve this release...");
|
|
6620
|
+
serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
|
|
6621
|
+
if (serving !== "confirmed") {
|
|
6622
|
+
spinner2.stop(
|
|
6623
|
+
serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
|
|
6442
6624
|
);
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
const secretsCache = await loadDeploySecrets({
|
|
6446
|
-
deployUrl: DEPLOY_URL3,
|
|
6447
|
-
appDir,
|
|
6448
|
-
appId,
|
|
6449
|
-
envName,
|
|
6450
|
-
ownerId,
|
|
6451
|
-
token,
|
|
6452
|
-
output
|
|
6453
|
-
});
|
|
6454
|
-
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6455
|
-
if (!sourceState.registered) {
|
|
6456
|
-
output.die(
|
|
6457
|
-
`${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init\`.`,
|
|
6458
|
-
"app_not_registered"
|
|
6625
|
+
p12.log.warn(
|
|
6626
|
+
serving === "unconfirmed" ? "Some requests still get the previous release. Cloudflare rolls a version out per edge machine; it usually settles within a couple of minutes. Wait before asserting against it." : "This release carries no serving stamp (older platform, or a resumed deploy), so the CLI cannot tell which release the edge answers with."
|
|
6459
6627
|
);
|
|
6628
|
+
p12.log.info(`URL: ${body.url}`);
|
|
6629
|
+
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6630
|
+
if (output.json) {
|
|
6631
|
+
return output.emitJson({
|
|
6632
|
+
ok: true,
|
|
6633
|
+
appId,
|
|
6634
|
+
appName,
|
|
6635
|
+
url: body.url,
|
|
6636
|
+
releaseId: body.releaseId ?? null,
|
|
6637
|
+
bundleRetained: body.bundleRetained ?? null,
|
|
6638
|
+
serving,
|
|
6639
|
+
recoverable: repository.recoverable,
|
|
6640
|
+
...staleBaseGuardFields(body)
|
|
6641
|
+
});
|
|
6642
|
+
}
|
|
6643
|
+
p12.outro("Done");
|
|
6644
|
+
return;
|
|
6460
6645
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
const bundle = await buildDeployBundle({
|
|
6471
|
-
appDir,
|
|
6472
|
-
appName,
|
|
6473
|
-
appId,
|
|
6474
|
-
envName,
|
|
6475
|
-
output,
|
|
6476
|
-
spinner: spinner2
|
|
6477
|
-
});
|
|
6478
|
-
const oversized = oversizedAssetRefusal(bundle.assets);
|
|
6479
|
-
if (oversized) {
|
|
6480
|
-
spinner2.stop("Deploy failed");
|
|
6481
|
-
output.die(oversized, "assets_too_large");
|
|
6482
|
-
}
|
|
6483
|
-
const repository = await syncDeployRepository({
|
|
6484
|
-
deployUrl: DEPLOY_URL3,
|
|
6485
|
-
appDir,
|
|
6486
|
-
appId,
|
|
6487
|
-
token,
|
|
6488
|
-
push: args.push !== false,
|
|
6489
|
-
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6490
|
-
output,
|
|
6491
|
-
sourceState
|
|
6492
|
-
});
|
|
6493
|
-
const secrets = prepareDeploySecrets({
|
|
6494
|
-
cache: secretsCache,
|
|
6495
|
-
customBindings: bundle.customBindings,
|
|
6496
|
-
doManifest: bundle.doManifest,
|
|
6497
|
-
output
|
|
6498
|
-
});
|
|
6499
|
-
const body = await deployBuiltBundle({
|
|
6500
|
-
deployUrl: DEPLOY_URL3,
|
|
6501
|
-
appDir,
|
|
6646
|
+
}
|
|
6647
|
+
spinner2.stop(
|
|
6648
|
+
`Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
|
|
6649
|
+
);
|
|
6650
|
+
p12.log.success(`Live at: ${body.url}`);
|
|
6651
|
+
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6652
|
+
if (output.json) {
|
|
6653
|
+
return output.emitJson({
|
|
6654
|
+
ok: true,
|
|
6502
6655
|
appId,
|
|
6503
6656
|
appName,
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
repository,
|
|
6511
|
-
|
|
6512
|
-
spinner: spinner2
|
|
6657
|
+
url: body.url ?? null,
|
|
6658
|
+
// Always present, so a caller branches on one field instead of
|
|
6659
|
+
// inferring success from an absent one.
|
|
6660
|
+
serving,
|
|
6661
|
+
releaseId: body.releaseId ?? null,
|
|
6662
|
+
bundleRetained: body.bundleRetained ?? null,
|
|
6663
|
+
recoverable: repository.recoverable,
|
|
6664
|
+
...staleBaseGuardFields(body)
|
|
6513
6665
|
});
|
|
6514
|
-
let serving = "unverifiable";
|
|
6515
|
-
const edgeWaitStarted = Date.now();
|
|
6516
|
-
if (body.url) {
|
|
6517
|
-
spinner2.message("Waiting for the edge to serve this release...");
|
|
6518
|
-
serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
|
|
6519
|
-
if (serving !== "confirmed") {
|
|
6520
|
-
spinner2.stop(
|
|
6521
|
-
serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
|
|
6522
|
-
);
|
|
6523
|
-
p12.log.warn(
|
|
6524
|
-
serving === "unconfirmed" ? "Some requests still get the previous release. Cloudflare rolls a version out per edge machine; it usually settles within a couple of minutes. Wait before asserting against it." : "This release carries no serving stamp (older platform, or a resumed deploy), so the CLI cannot tell which release the edge answers with."
|
|
6525
|
-
);
|
|
6526
|
-
p12.log.info(`URL: ${body.url}`);
|
|
6527
|
-
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6528
|
-
if (output.json) {
|
|
6529
|
-
return output.emitJson({
|
|
6530
|
-
ok: true,
|
|
6531
|
-
appId,
|
|
6532
|
-
appName,
|
|
6533
|
-
url: body.url,
|
|
6534
|
-
releaseId: body.releaseId ?? null,
|
|
6535
|
-
bundleRetained: body.bundleRetained ?? null,
|
|
6536
|
-
serving,
|
|
6537
|
-
recoverable: repository.recoverable,
|
|
6538
|
-
...staleBaseGuardFields(body)
|
|
6539
|
-
});
|
|
6540
|
-
}
|
|
6541
|
-
p12.outro("Done");
|
|
6542
|
-
return;
|
|
6543
|
-
}
|
|
6544
|
-
}
|
|
6545
|
-
spinner2.stop(
|
|
6546
|
-
`Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
|
|
6547
|
-
);
|
|
6548
|
-
p12.log.success(`Live at: ${body.url}`);
|
|
6549
|
-
await syncCommerce(appDir, appId, token, output.nonInteractive);
|
|
6550
|
-
if (output.json) {
|
|
6551
|
-
return output.emitJson({
|
|
6552
|
-
ok: true,
|
|
6553
|
-
appId,
|
|
6554
|
-
appName,
|
|
6555
|
-
url: body.url ?? null,
|
|
6556
|
-
// Always present, so a caller branches on one field instead of
|
|
6557
|
-
// inferring success from an absent one.
|
|
6558
|
-
serving,
|
|
6559
|
-
releaseId: body.releaseId ?? null,
|
|
6560
|
-
bundleRetained: body.bundleRetained ?? null,
|
|
6561
|
-
recoverable: repository.recoverable,
|
|
6562
|
-
...staleBaseGuardFields(body)
|
|
6563
|
-
});
|
|
6564
|
-
}
|
|
6565
|
-
p12.outro("Done");
|
|
6566
6666
|
}
|
|
6567
|
-
|
|
6667
|
+
p12.outro("Done");
|
|
6668
|
+
}
|
|
6568
6669
|
async function syncCommerce(appDir, appId, token, nonInteractive) {
|
|
6569
6670
|
await syncSubscriptionPlans(appDir, appId, token, nonInteractive);
|
|
6570
6671
|
await syncOneTimeProducts(appDir, appId, token);
|
|
6571
6672
|
}
|
|
6673
|
+
function ownerJwtMissingRefusal(state) {
|
|
6674
|
+
if (state.onBehalf?.ownerJwtLive !== false) return null;
|
|
6675
|
+
return {
|
|
6676
|
+
code: "owner_jwt_missing",
|
|
6677
|
+
error: "Cannot preserve the existing secrets: this app has no live deployment carrying an APP_OWNER_JWT. Ask the owner to redeploy."
|
|
6678
|
+
};
|
|
6679
|
+
}
|
|
6680
|
+
function pendingRename(registeredHost, appName) {
|
|
6681
|
+
if (!registeredHost) return null;
|
|
6682
|
+
const [label, ...domain] = registeredHost.split(".");
|
|
6683
|
+
if (label === appName) return null;
|
|
6684
|
+
return { fromHost: registeredHost, toHost: [appName, ...domain].join(".") };
|
|
6685
|
+
}
|
|
6572
6686
|
function blankSelectorRefusal(args) {
|
|
6573
6687
|
if (args.env !== void 0 && !String(args.env).trim()) {
|
|
6574
6688
|
return {
|
|
@@ -6629,15 +6743,18 @@ function requireAppIdArg(explicit) {
|
|
|
6629
6743
|
function liveAppUrl(app2) {
|
|
6630
6744
|
return app2.status === "active" && app2.deployedAt !== null ? app2.url : null;
|
|
6631
6745
|
}
|
|
6632
|
-
async function
|
|
6633
|
-
const
|
|
6634
|
-
if (!Array.isArray(apps)) {
|
|
6746
|
+
async function listAppsPage(deployUrl, token) {
|
|
6747
|
+
const body = await apiFetch(deployUrl, token, "/api/apps");
|
|
6748
|
+
if (!Array.isArray(body.apps)) {
|
|
6635
6749
|
throw new InputError(
|
|
6636
6750
|
`The deploy service at ${deployUrl} returned an unexpected response shape for the app list \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
|
|
6637
6751
|
"invalid_response"
|
|
6638
6752
|
);
|
|
6639
6753
|
}
|
|
6640
|
-
return apps;
|
|
6754
|
+
return { apps: body.apps, pendingTransfers: body.pendingTransfers ?? [] };
|
|
6755
|
+
}
|
|
6756
|
+
async function listApps(deployUrl, token) {
|
|
6757
|
+
return (await listAppsPage(deployUrl, token)).apps;
|
|
6641
6758
|
}
|
|
6642
6759
|
function matchAppSelector(apps, selector) {
|
|
6643
6760
|
const byId = apps.find((app2) => app2.appId === selector);
|
|
@@ -6759,6 +6876,11 @@ var undeploy_default = defineDeepspaceCommand({
|
|
|
6759
6876
|
type: "string",
|
|
6760
6877
|
description: "wrangler.toml [env.<name>] block whose deployed app to remove (e.g. --env staging). Ignored if a positional name is given.",
|
|
6761
6878
|
required: false
|
|
6879
|
+
},
|
|
6880
|
+
yes: {
|
|
6881
|
+
type: "boolean",
|
|
6882
|
+
description: "Skip the interactive confirmation. (Scripts and --json callers are never prompted; the command itself is their consent.)",
|
|
6883
|
+
default: false
|
|
6762
6884
|
}
|
|
6763
6885
|
},
|
|
6764
6886
|
async run({ args }) {
|
|
@@ -6784,7 +6906,18 @@ var undeploy_default = defineDeepspaceCommand({
|
|
|
6784
6906
|
"not_in_app_repo"
|
|
6785
6907
|
);
|
|
6786
6908
|
}
|
|
6787
|
-
if (!args.json
|
|
6909
|
+
if (!args.json && !args.yes && process.stdin.isTTY) {
|
|
6910
|
+
p13.intro(`Undeploying ${appId}`);
|
|
6911
|
+
const confirmed = await p13.confirm({
|
|
6912
|
+
message: `Take ${appId} offline now? Its URL stops serving immediately; data, secrets, and the registration stay (the name is reserved for you for 30 days).`,
|
|
6913
|
+
initialValue: false
|
|
6914
|
+
});
|
|
6915
|
+
if (p13.isCancel(confirmed) || !confirmed) {
|
|
6916
|
+
throw new Refusal("Undeploy cancelled.", "undeploy_declined");
|
|
6917
|
+
}
|
|
6918
|
+
} else if (!args.json) {
|
|
6919
|
+
p13.intro(`Undeploying ${appId}`);
|
|
6920
|
+
}
|
|
6788
6921
|
const s = args.json ? null : createSpinner();
|
|
6789
6922
|
s?.start("Removing...");
|
|
6790
6923
|
const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
|
|
@@ -6879,17 +7012,17 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6879
7012
|
},
|
|
6880
7013
|
async run({ args }) {
|
|
6881
7014
|
const token = await ensureToken();
|
|
6882
|
-
const apps = await
|
|
7015
|
+
const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL5, token);
|
|
6883
7016
|
if (!args.json) {
|
|
6884
|
-
if (!apps.length) {
|
|
7017
|
+
if (!apps.length && !pendingTransfers.length) {
|
|
6885
7018
|
console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
|
|
6886
|
-
} else {
|
|
7019
|
+
} else if (apps.length) {
|
|
6887
7020
|
const rows = [...apps].sort(
|
|
6888
7021
|
(left, right) => Number(right.status !== "undeployed") - Number(left.status !== "undeployed")
|
|
6889
7022
|
);
|
|
6890
7023
|
const roleOf = (a) => a.role ?? "owner";
|
|
6891
7024
|
const roleWidth = Math.max(4, ...rows.map((a) => roleOf(a).length));
|
|
6892
|
-
const nameWidth = Math.max(4, ...rows.map((a) => (a
|
|
7025
|
+
const nameWidth = Math.max(4, ...rows.map((a) => nameCell(a).length));
|
|
6893
7026
|
const statusWidth = Math.max(6, ...rows.map((a) => a.status.length));
|
|
6894
7027
|
const idWidth = Math.max(6, ...rows.map((a) => a.appId.length));
|
|
6895
7028
|
console.log(
|
|
@@ -6898,14 +7031,36 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6898
7031
|
for (const a of rows) {
|
|
6899
7032
|
const url = liveAppUrl(a) ?? "(not deployed)";
|
|
6900
7033
|
console.log(
|
|
6901
|
-
`${(a
|
|
7034
|
+
`${nameCell(a).padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${roleOf(a).padEnd(roleWidth)} ${a.appId.padEnd(idWidth)} ${url}`
|
|
6902
7035
|
);
|
|
6903
7036
|
}
|
|
7037
|
+
const reserved = rows.filter((a) => a.reservedName && a.reservedUntil);
|
|
7038
|
+
if (reserved.length) {
|
|
7039
|
+
console.log("\nReserved names return to the open pool when they expire:");
|
|
7040
|
+
for (const a of reserved) {
|
|
7041
|
+
console.log(
|
|
7042
|
+
` ${a.reservedName} \u2014 held for you until ${a.reservedUntil}${a.name ? ` (${a.appId} now serves ${a.name})` : ""}`
|
|
7043
|
+
);
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
if (pendingTransfers.length) {
|
|
7048
|
+
console.log("\nTransfer offers waiting for you:");
|
|
7049
|
+
for (const offer2 of pendingTransfers) {
|
|
7050
|
+
console.log(` ${offer2.appId} \u2014 expires ${offer2.expiresAt}`);
|
|
7051
|
+
}
|
|
7052
|
+
console.log(
|
|
7053
|
+
" Accept with `deepspace app transfer accept --app <APP ID>`; `deepspace app transfer status --app <APP ID>` names the offerer."
|
|
7054
|
+
);
|
|
6904
7055
|
}
|
|
6905
7056
|
}
|
|
6906
|
-
return { data: { apps } };
|
|
7057
|
+
return { data: { apps, pendingTransfers } };
|
|
6907
7058
|
}
|
|
6908
7059
|
});
|
|
7060
|
+
function nameCell(app2) {
|
|
7061
|
+
if (app2.name) return app2.name;
|
|
7062
|
+
return app2.reservedName ? `${app2.reservedName} (reserved)` : "\u2014";
|
|
7063
|
+
}
|
|
6909
7064
|
|
|
6910
7065
|
// src/cli/commands/logs.ts
|
|
6911
7066
|
import { defineCommand as defineCommand4 } from "citty";
|
|
@@ -7289,37 +7444,37 @@ var usage_default = defineDeepspaceCommand({
|
|
|
7289
7444
|
});
|
|
7290
7445
|
|
|
7291
7446
|
// src/cli/commands/add.ts
|
|
7292
|
-
import { existsSync as
|
|
7293
|
-
import { dirname as dirname9, join as
|
|
7447
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
|
|
7448
|
+
import { dirname as dirname9, join as join21, resolve as resolve16 } from "path";
|
|
7294
7449
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7295
7450
|
|
|
7296
7451
|
// src/cli/commands/feature-installer.ts
|
|
7297
7452
|
import {
|
|
7298
7453
|
closeSync as closeSync2,
|
|
7299
7454
|
copyFileSync,
|
|
7300
|
-
existsSync as
|
|
7455
|
+
existsSync as existsSync18,
|
|
7301
7456
|
fstatSync as fstatSync2,
|
|
7302
7457
|
mkdirSync as mkdirSync6,
|
|
7303
7458
|
mkdtempSync,
|
|
7304
7459
|
openSync as openSync2,
|
|
7305
|
-
readFileSync as
|
|
7460
|
+
readFileSync as readFileSync18,
|
|
7306
7461
|
readSync,
|
|
7307
|
-
readdirSync as
|
|
7462
|
+
readdirSync as readdirSync7,
|
|
7308
7463
|
rmSync as rmSync4,
|
|
7309
7464
|
writeFileSync as writeFileSync5
|
|
7310
7465
|
} from "fs";
|
|
7311
7466
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7312
7467
|
import { tmpdir as tmpdir2 } from "os";
|
|
7313
|
-
import { dirname as dirname8, isAbsolute as isAbsolute4, join as
|
|
7468
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, resolve as resolve15 } from "path";
|
|
7314
7469
|
import { fileURLToPath } from "url";
|
|
7315
7470
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
7316
7471
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
7317
7472
|
let dir = dirname8(fileURLToPath(moduleUrl));
|
|
7318
7473
|
for (; ; ) {
|
|
7319
|
-
const packagePath =
|
|
7320
|
-
if (
|
|
7474
|
+
const packagePath = join20(dir, "package.json");
|
|
7475
|
+
if (existsSync18(packagePath)) {
|
|
7321
7476
|
try {
|
|
7322
|
-
const pkg2 = JSON.parse(
|
|
7477
|
+
const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7323
7478
|
if (pkg2.name === "deepspace") return dir;
|
|
7324
7479
|
} catch {
|
|
7325
7480
|
}
|
|
@@ -7330,13 +7485,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
7330
7485
|
}
|
|
7331
7486
|
}
|
|
7332
7487
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
7333
|
-
return
|
|
7488
|
+
return existsSync18(join20(packageRoot, "features"));
|
|
7334
7489
|
}
|
|
7335
7490
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
7336
|
-
const configPath =
|
|
7337
|
-
if (!
|
|
7491
|
+
const configPath = join20(packageRoot, "features", id, "feature.json");
|
|
7492
|
+
if (!existsSync18(configPath)) return null;
|
|
7338
7493
|
try {
|
|
7339
|
-
const config = JSON.parse(
|
|
7494
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
7340
7495
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
7341
7496
|
return null;
|
|
7342
7497
|
}
|
|
@@ -7346,9 +7501,9 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
7346
7501
|
}
|
|
7347
7502
|
}
|
|
7348
7503
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
7349
|
-
const featuresDir =
|
|
7350
|
-
if (!
|
|
7351
|
-
return
|
|
7504
|
+
const featuresDir = join20(packageRoot, "features");
|
|
7505
|
+
if (!existsSync18(featuresDir)) return [];
|
|
7506
|
+
return readdirSync7(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
|
|
7352
7507
|
}
|
|
7353
7508
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
7354
7509
|
return listFeatureConfigs(packageRoot).map((config) => ({
|
|
@@ -7385,7 +7540,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
7385
7540
|
return closest !== null && closestDistance <= threshold ? closest : null;
|
|
7386
7541
|
}
|
|
7387
7542
|
function resolveTargetDir(raw) {
|
|
7388
|
-
return isAbsolute4(raw) ? raw :
|
|
7543
|
+
return isAbsolute4(raw) ? raw : resolve15(raw);
|
|
7389
7544
|
}
|
|
7390
7545
|
function resolveFeatureDestPath(fileDest, route) {
|
|
7391
7546
|
const pages = "src/pages/";
|
|
@@ -7401,7 +7556,7 @@ function copyFile(src, dest) {
|
|
|
7401
7556
|
function copyFileWithImportShift(src, dest, levels) {
|
|
7402
7557
|
mkdirSync6(dirname8(dest), { recursive: true });
|
|
7403
7558
|
const prefix = "../".repeat(levels);
|
|
7404
|
-
const shifted =
|
|
7559
|
+
const shifted = readFileSync18(src, "utf-8").replace(
|
|
7405
7560
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
7406
7561
|
`$1$2${prefix}$3`
|
|
7407
7562
|
);
|
|
@@ -7414,12 +7569,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
7414
7569
|
const preexisting = [];
|
|
7415
7570
|
const missing = [];
|
|
7416
7571
|
for (const file of config.files) {
|
|
7417
|
-
const srcPath =
|
|
7572
|
+
const srcPath = join20(packageRoot, "features", config.id, file.src);
|
|
7418
7573
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
7419
|
-
const destPath =
|
|
7420
|
-
const existed =
|
|
7574
|
+
const destPath = join20(targetDir, resolvedDest);
|
|
7575
|
+
const existed = existsSync18(destPath);
|
|
7421
7576
|
if (existed) preexisting.push(resolvedDest);
|
|
7422
|
-
if (!
|
|
7577
|
+
if (!existsSync18(srcPath)) {
|
|
7423
7578
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
7424
7579
|
if (!existed) missing.push(resolvedDest);
|
|
7425
7580
|
continue;
|
|
@@ -7456,11 +7611,11 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
7456
7611
|
}
|
|
7457
7612
|
function integrateSchema(config, targetDir, emit) {
|
|
7458
7613
|
if (!config.schema) return [];
|
|
7459
|
-
const schemasPath =
|
|
7460
|
-
if (!
|
|
7614
|
+
const schemasPath = join20(targetDir, "src", "schemas.ts");
|
|
7615
|
+
if (!existsSync18(schemasPath)) {
|
|
7461
7616
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
7462
7617
|
}
|
|
7463
|
-
let content =
|
|
7618
|
+
let content = readFileSync18(schemasPath, "utf-8");
|
|
7464
7619
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
7465
7620
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
7466
7621
|
if (importPattern.test(content)) {
|
|
@@ -7513,11 +7668,11 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
7513
7668
|
}
|
|
7514
7669
|
function integrateActions(config, targetDir, emit) {
|
|
7515
7670
|
if (!config.actions?.length) return [];
|
|
7516
|
-
const actionsPath =
|
|
7517
|
-
if (!
|
|
7671
|
+
const actionsPath = join20(targetDir, "src", "actions", "index.ts");
|
|
7672
|
+
if (!existsSync18(actionsPath)) {
|
|
7518
7673
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
7519
7674
|
}
|
|
7520
|
-
let content =
|
|
7675
|
+
let content = readFileSync18(actionsPath, "utf-8");
|
|
7521
7676
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
7522
7677
|
if (!registryPattern.test(content)) {
|
|
7523
7678
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -7563,8 +7718,8 @@ ${registryEntry}`);
|
|
|
7563
7718
|
function integrateCode(config, targetDir, emit) {
|
|
7564
7719
|
const unresolved = [];
|
|
7565
7720
|
for (const insertion of config.code ?? []) {
|
|
7566
|
-
const targetPath =
|
|
7567
|
-
if (!
|
|
7721
|
+
const targetPath = join20(targetDir, insertion.file);
|
|
7722
|
+
if (!existsSync18(targetPath)) {
|
|
7568
7723
|
unresolved.push({
|
|
7569
7724
|
area: "code",
|
|
7570
7725
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7572,7 +7727,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7572
7727
|
});
|
|
7573
7728
|
continue;
|
|
7574
7729
|
}
|
|
7575
|
-
const original =
|
|
7730
|
+
const original = readFileSync18(targetPath, "utf-8");
|
|
7576
7731
|
if (original.includes(insertion.insert.trim())) {
|
|
7577
7732
|
emit(` Integration already present: ${insertion.file}`);
|
|
7578
7733
|
continue;
|
|
@@ -7609,8 +7764,8 @@ ${content.slice(
|
|
|
7609
7764
|
}
|
|
7610
7765
|
function integrateIgnore(config, targetDir) {
|
|
7611
7766
|
if (!config.ignore?.length) return;
|
|
7612
|
-
const targetPath =
|
|
7613
|
-
const current =
|
|
7767
|
+
const targetPath = join20(targetDir, ".gitignore");
|
|
7768
|
+
const current = existsSync18(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
|
|
7614
7769
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7615
7770
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7616
7771
|
if (missing.length === 0) return;
|
|
@@ -7622,15 +7777,15 @@ function integrateWrangler(config, targetDir, emit) {
|
|
|
7622
7777
|
const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
|
|
7623
7778
|
const rateLimits = config.wrangler?.rateLimits ?? [];
|
|
7624
7779
|
if (!runWorkerFirst && rateLimits.length === 0) return [];
|
|
7625
|
-
const targetPath =
|
|
7780
|
+
const targetPath = join20(targetDir, "wrangler.toml");
|
|
7626
7781
|
const steps = [
|
|
7627
7782
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7628
7783
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7629
7784
|
];
|
|
7630
|
-
if (!
|
|
7785
|
+
if (!existsSync18(targetPath)) {
|
|
7631
7786
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7632
7787
|
}
|
|
7633
|
-
let source =
|
|
7788
|
+
let source = readFileSync18(targetPath, "utf-8");
|
|
7634
7789
|
if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
|
|
7635
7790
|
return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
|
|
7636
7791
|
}
|
|
@@ -7706,8 +7861,8 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7706
7861
|
}
|
|
7707
7862
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7708
7863
|
if (!config.css?.length) return [];
|
|
7709
|
-
const stylesPath =
|
|
7710
|
-
if (!
|
|
7864
|
+
const stylesPath = join20(targetDir, "src", "styles.css");
|
|
7865
|
+
if (!existsSync18(stylesPath)) {
|
|
7711
7866
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7712
7867
|
return [
|
|
7713
7868
|
{
|
|
@@ -7719,12 +7874,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7719
7874
|
}
|
|
7720
7875
|
];
|
|
7721
7876
|
}
|
|
7722
|
-
let styles =
|
|
7877
|
+
let styles = readFileSync18(stylesPath, "utf-8");
|
|
7723
7878
|
let integrated = 0;
|
|
7724
7879
|
const unresolved = [];
|
|
7725
7880
|
for (const cssFile of config.css) {
|
|
7726
|
-
const srcPath =
|
|
7727
|
-
if (!
|
|
7881
|
+
const srcPath = join20(packageRoot, "features", config.id, cssFile);
|
|
7882
|
+
if (!existsSync18(srcPath)) {
|
|
7728
7883
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7729
7884
|
unresolved.push({
|
|
7730
7885
|
area: "css",
|
|
@@ -7733,7 +7888,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7733
7888
|
});
|
|
7734
7889
|
continue;
|
|
7735
7890
|
}
|
|
7736
|
-
const css =
|
|
7891
|
+
const css = readFileSync18(srcPath, "utf-8");
|
|
7737
7892
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
7738
7893
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
7739
7894
|
if (marker && styles.includes(marker)) {
|
|
@@ -7767,11 +7922,11 @@ function routeRolesSuffix(route) {
|
|
|
7767
7922
|
}
|
|
7768
7923
|
function integrateRoute(config, targetDir, emit) {
|
|
7769
7924
|
if (!config.route) return [];
|
|
7770
|
-
const navPath =
|
|
7771
|
-
if (!
|
|
7925
|
+
const navPath = join20(targetDir, "src", "nav.ts");
|
|
7926
|
+
if (!existsSync18(navPath)) {
|
|
7772
7927
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7773
7928
|
}
|
|
7774
|
-
let content =
|
|
7929
|
+
let content = readFileSync18(navPath, "utf-8");
|
|
7775
7930
|
const routePath = config.route.path;
|
|
7776
7931
|
if (content.includes(`'${routePath}'`)) {
|
|
7777
7932
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -7818,10 +7973,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
7818
7973
|
const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
|
|
7819
7974
|
return { error: child.error, status: child.status };
|
|
7820
7975
|
}
|
|
7821
|
-
const captureDir = mkdtempSync(
|
|
7976
|
+
const captureDir = mkdtempSync(join20(tmpdir2(), "deepspace-add-install-"));
|
|
7822
7977
|
let outputFd;
|
|
7823
7978
|
try {
|
|
7824
|
-
outputFd = openSync2(
|
|
7979
|
+
outputFd = openSync2(join20(captureDir, "output.log"), "w+");
|
|
7825
7980
|
const child = spawnSync6(packageManager, ["install"], {
|
|
7826
7981
|
...options,
|
|
7827
7982
|
stdio: ["ignore", outputFd, outputFd]
|
|
@@ -7845,14 +8000,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7845
8000
|
if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
|
|
7846
8001
|
return { added: [], installRan: false, packageManager };
|
|
7847
8002
|
}
|
|
7848
|
-
const packagePath =
|
|
7849
|
-
if (!
|
|
8003
|
+
const packagePath = join20(targetDir, "package.json");
|
|
8004
|
+
if (!existsSync18(packagePath)) {
|
|
7850
8005
|
emit(" Could not patch package.json (file not found)");
|
|
7851
8006
|
return { added: [], installRan: false, packageManager };
|
|
7852
8007
|
}
|
|
7853
8008
|
let pkg2;
|
|
7854
8009
|
try {
|
|
7855
|
-
pkg2 = JSON.parse(
|
|
8010
|
+
pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7856
8011
|
} catch (error) {
|
|
7857
8012
|
emit(
|
|
7858
8013
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -7912,7 +8067,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7912
8067
|
}
|
|
7913
8068
|
function dependencySnapshot(targetDir) {
|
|
7914
8069
|
try {
|
|
7915
|
-
const pkg2 = JSON.parse(
|
|
8070
|
+
const pkg2 = JSON.parse(readFileSync18(join20(targetDir, "package.json"), "utf-8"));
|
|
7916
8071
|
return {
|
|
7917
8072
|
dependencies: pkg2.dependencies ?? {},
|
|
7918
8073
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7924,7 +8079,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7924
8079
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7925
8080
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7926
8081
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7927
|
-
return
|
|
8082
|
+
return existsSync18(join20(targetDir, signature));
|
|
7928
8083
|
}).map((feature) => feature.id);
|
|
7929
8084
|
}
|
|
7930
8085
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -8157,11 +8312,11 @@ function renderFeatureInfo(config) {
|
|
|
8157
8312
|
return lines.join("\n");
|
|
8158
8313
|
}
|
|
8159
8314
|
function isDeepSpaceApp(dir) {
|
|
8160
|
-
if (
|
|
8161
|
-
const packagePath =
|
|
8162
|
-
if (!
|
|
8315
|
+
if (existsSync19(join21(dir, "wrangler.toml"))) return true;
|
|
8316
|
+
const packagePath = join21(dir, "package.json");
|
|
8317
|
+
if (!existsSync19(packagePath)) return false;
|
|
8163
8318
|
try {
|
|
8164
|
-
const pkg2 = JSON.parse(
|
|
8319
|
+
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
8165
8320
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
8166
8321
|
} catch {
|
|
8167
8322
|
return false;
|
|
@@ -8170,8 +8325,8 @@ function isDeepSpaceApp(dir) {
|
|
|
8170
8325
|
function installedPackageRoot(fromDir) {
|
|
8171
8326
|
let dir = fromDir;
|
|
8172
8327
|
for (; ; ) {
|
|
8173
|
-
const candidate =
|
|
8174
|
-
if (
|
|
8328
|
+
const candidate = join21(dir, "node_modules", "deepspace");
|
|
8329
|
+
if (existsSync19(join21(candidate, "package.json"))) return candidate;
|
|
8175
8330
|
const parent = dirname9(dir);
|
|
8176
8331
|
if (parent === dir) return null;
|
|
8177
8332
|
dir = parent;
|
|
@@ -8188,9 +8343,9 @@ async function targetInstaller(appDir) {
|
|
|
8188
8343
|
if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
|
|
8189
8344
|
return { installFeature };
|
|
8190
8345
|
}
|
|
8191
|
-
const entry =
|
|
8192
|
-
const catalog =
|
|
8193
|
-
if (!
|
|
8346
|
+
const entry = join21(installedRoot, "dist", "feature-installer.js");
|
|
8347
|
+
const catalog = join21(installedRoot, "features");
|
|
8348
|
+
if (!existsSync19(entry) || !existsSync19(catalog)) {
|
|
8194
8349
|
throw new Refusal(
|
|
8195
8350
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
8196
8351
|
Expected: ${entry}
|
|
@@ -8282,8 +8437,8 @@ var add_default = defineDeepspaceCommand({
|
|
|
8282
8437
|
}
|
|
8283
8438
|
return { data: { features: featureCatalog(root) } };
|
|
8284
8439
|
}
|
|
8285
|
-
const appDir =
|
|
8286
|
-
if (!
|
|
8440
|
+
const appDir = resolve16(args.dir ?? ".");
|
|
8441
|
+
if (!existsSync19(appDir)) {
|
|
8287
8442
|
throw new Refusal(
|
|
8288
8443
|
`Target directory not found: ${appDir}
|
|
8289
8444
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -8363,11 +8518,11 @@ function fmtDate(iso) {
|
|
|
8363
8518
|
if (!iso) return "\u2014";
|
|
8364
8519
|
return iso.slice(0, 10);
|
|
8365
8520
|
}
|
|
8366
|
-
async function
|
|
8521
|
+
async function confirm6(message) {
|
|
8367
8522
|
process.stdout.write(`${message} [y/N] `);
|
|
8368
|
-
const answer = await new Promise((
|
|
8523
|
+
const answer = await new Promise((resolve22) => {
|
|
8369
8524
|
process.stdin.setEncoding("utf-8");
|
|
8370
|
-
process.stdin.once("data", (data) =>
|
|
8525
|
+
process.stdin.once("data", (data) => resolve22(data.toString().trim().toLowerCase()));
|
|
8371
8526
|
});
|
|
8372
8527
|
process.stdin.pause();
|
|
8373
8528
|
return answer === "y" || answer === "yes";
|
|
@@ -8442,7 +8597,7 @@ var buy = defineDeepspaceCommand({
|
|
|
8442
8597
|
"confirmation_required"
|
|
8443
8598
|
);
|
|
8444
8599
|
}
|
|
8445
|
-
const ok2 = await
|
|
8600
|
+
const ok2 = await confirm6(
|
|
8446
8601
|
`Buy ${domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appId}? Provisioning takes ${expectedTime}.`
|
|
8447
8602
|
);
|
|
8448
8603
|
if (!ok2) {
|
|
@@ -8612,7 +8767,7 @@ var detach = defineDeepspaceCommand({
|
|
|
8612
8767
|
"confirmation_required"
|
|
8613
8768
|
);
|
|
8614
8769
|
}
|
|
8615
|
-
const ok2 = await
|
|
8770
|
+
const ok2 = await confirm6(
|
|
8616
8771
|
`Detach ${domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
8617
8772
|
);
|
|
8618
8773
|
if (!ok2) {
|
|
@@ -8658,8 +8813,8 @@ var domain_default = defineCommand5({
|
|
|
8658
8813
|
|
|
8659
8814
|
// src/cli/commands/app-files.ts
|
|
8660
8815
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8661
|
-
import { basename as basename4, resolve as
|
|
8662
|
-
import { existsSync as
|
|
8816
|
+
import { basename as basename4, resolve as resolve17 } from "path";
|
|
8817
|
+
import { existsSync as existsSync20, statSync as statSync2 } from "fs";
|
|
8663
8818
|
|
|
8664
8819
|
// src/cli/lib/app-files-api.ts
|
|
8665
8820
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -8937,8 +9092,8 @@ var put = defineDeepspaceCommand({
|
|
|
8937
9092
|
app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
|
|
8938
9093
|
},
|
|
8939
9094
|
async run({ args }) {
|
|
8940
|
-
const localPath =
|
|
8941
|
-
if (!
|
|
9095
|
+
const localPath = resolve17(String(args.file));
|
|
9096
|
+
if (!existsSync20(localPath) || !statSync2(localPath).isFile()) {
|
|
8942
9097
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
8943
9098
|
}
|
|
8944
9099
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -9038,7 +9193,7 @@ var get = defineDeepspaceCommand({
|
|
|
9038
9193
|
},
|
|
9039
9194
|
async run({ args }) {
|
|
9040
9195
|
const key = requireKey(String(args.key));
|
|
9041
|
-
const destination =
|
|
9196
|
+
const destination = resolve17(String(args.out ?? basename4(key)));
|
|
9042
9197
|
const { token, appId } = await target(args.app);
|
|
9043
9198
|
const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
|
|
9044
9199
|
if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
|
|
@@ -9118,7 +9273,10 @@ var list4 = defineDeepspaceCommand({
|
|
|
9118
9273
|
}
|
|
9119
9274
|
});
|
|
9120
9275
|
var add = defineDeepspaceCommand({
|
|
9121
|
-
meta: {
|
|
9276
|
+
meta: {
|
|
9277
|
+
name: "add",
|
|
9278
|
+
description: "Authorize someone to deploy your app \u2014 a collaborator can also read and change every app secret (plaintext); they cannot undeploy or transfer"
|
|
9279
|
+
},
|
|
9122
9280
|
args: {
|
|
9123
9281
|
email: {
|
|
9124
9282
|
type: "positional",
|
|
@@ -9328,7 +9486,11 @@ var offer = defineDeepspaceCommand({
|
|
|
9328
9486
|
if (replaced) console.log(`\u25B2 Replaced the pending offer to ${pendingTo}.`);
|
|
9329
9487
|
console.log(
|
|
9330
9488
|
`\u2713 Offered ${app2} to ${email} (expires ${expiresAt}).
|
|
9331
|
-
They accept with: deepspace app transfer accept --app ${app2}
|
|
9489
|
+
They accept with: deepspace app transfer accept --app ${app2}
|
|
9490
|
+
(it also shows in their \`deepspace app list\`)
|
|
9491
|
+
\u25B2 On acceptance you lose ALL access to ${app2} \u2014 you are not kept on as a
|
|
9492
|
+
collaborator. Only the new owner can add you back. Collaborators you added
|
|
9493
|
+
stay on the app for the new owner.`
|
|
9332
9494
|
);
|
|
9333
9495
|
}
|
|
9334
9496
|
return {
|
|
@@ -9355,7 +9517,9 @@ var status2 = defineDeepspaceCommand({
|
|
|
9355
9517
|
return { data: { app: app2, transfer: null } };
|
|
9356
9518
|
}
|
|
9357
9519
|
if (!args.json) {
|
|
9358
|
-
console.log(
|
|
9520
|
+
console.log(
|
|
9521
|
+
`Pending: ${app2} \u2014 from ${transfer.fromEmailDisplay} to ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`
|
|
9522
|
+
);
|
|
9359
9523
|
}
|
|
9360
9524
|
return { data: { app: app2, transfer } };
|
|
9361
9525
|
}
|
|
@@ -9375,15 +9539,32 @@ var accept2 = defineDeepspaceCommand({
|
|
|
9375
9539
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9376
9540
|
throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
|
|
9377
9541
|
}
|
|
9378
|
-
await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
|
|
9542
|
+
const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
|
|
9379
9543
|
method: "POST"
|
|
9380
9544
|
});
|
|
9381
9545
|
if (!args.json) {
|
|
9382
9546
|
console.log(
|
|
9383
|
-
`\u2713 You now own ${app2}. Run \`deepspace app init\` in a fresh clone (or set DEEPSPACE_APP_ID = "${app2}" in wrangler.toml) and deploy.`
|
|
9547
|
+
replayed ? `\u2022 ${app2} is already yours${acceptedAt ? ` (since ${acceptedAt})` : ""} \u2014 no pending offer was accepted. Run \`deepspace app list\` to see any other offers.` : `\u2713 You now own ${app2}. Run \`deepspace app init\` in a fresh clone (or set DEEPSPACE_APP_ID = "${app2}" in wrangler.toml) and deploy.`
|
|
9384
9548
|
);
|
|
9549
|
+
if (!replayed && inheritedCollaborators === void 0) {
|
|
9550
|
+
console.log(
|
|
9551
|
+
`\u25B2 Could not list collaborators the previous owner may have left on the app \u2014 review with \`deepspace app collaborators list --app ${app2}\`.`
|
|
9552
|
+
);
|
|
9553
|
+
} else if (!replayed && inheritedCollaborators && inheritedCollaborators.length > 0) {
|
|
9554
|
+
console.log(
|
|
9555
|
+
`\u25B2 ${inheritedCollaborators.length} collaborator(s) added by the previous owner stay on the app with deploy and secrets access \u2014 review with \`deepspace app collaborators list --app ${app2}\`.`
|
|
9556
|
+
);
|
|
9557
|
+
}
|
|
9385
9558
|
}
|
|
9386
|
-
return {
|
|
9559
|
+
return {
|
|
9560
|
+
data: {
|
|
9561
|
+
app: app2,
|
|
9562
|
+
accepted: !replayed,
|
|
9563
|
+
replayed: replayed === true,
|
|
9564
|
+
...acceptedAt ? { acceptedAt } : {},
|
|
9565
|
+
...inheritedCollaborators === void 0 ? {} : { inheritedCollaborators }
|
|
9566
|
+
}
|
|
9567
|
+
};
|
|
9387
9568
|
}
|
|
9388
9569
|
});
|
|
9389
9570
|
var cancel4 = defineDeepspaceCommand({
|
|
@@ -9408,7 +9589,7 @@ var transfer_default = defineCommand8({
|
|
|
9408
9589
|
import { defineCommand as defineCommand9 } from "citty";
|
|
9409
9590
|
|
|
9410
9591
|
// src/cli/commands/_invoke-impl.ts
|
|
9411
|
-
import { readFileSync as
|
|
9592
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
9412
9593
|
import * as p15 from "@clack/prompts";
|
|
9413
9594
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
9414
9595
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -9449,7 +9630,7 @@ function resolveBody(opts) {
|
|
|
9449
9630
|
if (opts.body != null) {
|
|
9450
9631
|
raw = opts.body;
|
|
9451
9632
|
} else if (opts.bodyFile != null) {
|
|
9452
|
-
raw = opts.bodyFile === "-" ?
|
|
9633
|
+
raw = opts.bodyFile === "-" ? readFileSync20(0, "utf-8") : readFileSync20(opts.bodyFile, "utf-8");
|
|
9453
9634
|
} else {
|
|
9454
9635
|
return "{}";
|
|
9455
9636
|
}
|
|
@@ -9743,8 +9924,8 @@ var integrations_default = defineCommand9({
|
|
|
9743
9924
|
|
|
9744
9925
|
// src/cli/commands/feedback.ts
|
|
9745
9926
|
import * as p16 from "@clack/prompts";
|
|
9746
|
-
import { existsSync as
|
|
9747
|
-
import { dirname as dirname10, join as
|
|
9927
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
9928
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
9748
9929
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9749
9930
|
import { release } from "os";
|
|
9750
9931
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -9767,12 +9948,12 @@ function readCliVersion() {
|
|
|
9767
9948
|
try {
|
|
9768
9949
|
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
9769
9950
|
const candidates = [
|
|
9770
|
-
|
|
9771
|
-
|
|
9951
|
+
join22(here, "..", "package.json"),
|
|
9952
|
+
join22(here, "..", "..", "..", "package.json")
|
|
9772
9953
|
];
|
|
9773
9954
|
for (const path of candidates) {
|
|
9774
|
-
if (!
|
|
9775
|
-
const pkg2 = JSON.parse(
|
|
9955
|
+
if (!existsSync21(path)) continue;
|
|
9956
|
+
const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
|
|
9776
9957
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
9777
9958
|
}
|
|
9778
9959
|
} catch {
|
|
@@ -9968,7 +10149,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
9968
10149
|
|
|
9969
10150
|
// src/cli/commands/secrets.ts
|
|
9970
10151
|
import { defineCommand as defineCommand10 } from "citty";
|
|
9971
|
-
import { readFileSync as
|
|
10152
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
9972
10153
|
|
|
9973
10154
|
// src/cli/lib/citty-args.ts
|
|
9974
10155
|
function dedupePositionals(bound, rest) {
|
|
@@ -10070,6 +10251,7 @@ function warnIfDebugRoutesEnabled(secrets) {
|
|
|
10070
10251
|
return true;
|
|
10071
10252
|
}
|
|
10072
10253
|
var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
|
|
10254
|
+
var APPLIES_AT_DEPLOY = { appliesAtDeploy: true };
|
|
10073
10255
|
var LOCAL_DEV_HINT = "Running `deepspace dev start`? Restart it to load the change locally.";
|
|
10074
10256
|
var list6 = defineCommand10({
|
|
10075
10257
|
meta: { name: "list", description: "List masked secrets in a config" },
|
|
@@ -10136,7 +10318,7 @@ var set = defineCommand10({
|
|
|
10136
10318
|
const n = Object.keys(secrets).length;
|
|
10137
10319
|
ok(
|
|
10138
10320
|
args.json === true,
|
|
10139
|
-
{ appId: t.appId, config: t.configName, set: Object.keys(secrets) },
|
|
10321
|
+
{ appId: t.appId, config: t.configName, set: Object.keys(secrets), ...APPLIES_AT_DEPLOY },
|
|
10140
10322
|
() => {
|
|
10141
10323
|
console.log(`Set ${n} secret${n === 1 ? "" : "s"} in ${t.configName}.`);
|
|
10142
10324
|
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
@@ -10209,7 +10391,7 @@ var del2 = defineCommand10({
|
|
|
10209
10391
|
}
|
|
10210
10392
|
}
|
|
10211
10393
|
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
10212
|
-
ok(args.json === true, { appId: t.appId, config: t.configName, deleted, absent }, () => {
|
|
10394
|
+
ok(args.json === true, { appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY }, () => {
|
|
10213
10395
|
console.log(
|
|
10214
10396
|
`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
|
|
10215
10397
|
);
|
|
@@ -10238,7 +10420,7 @@ var upload = defineCommand10({
|
|
|
10238
10420
|
async run({ args }) {
|
|
10239
10421
|
try {
|
|
10240
10422
|
const t = await resolveTarget(args);
|
|
10241
|
-
const content = args.file === "-" ?
|
|
10423
|
+
const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
|
|
10242
10424
|
const secrets = parseSecretsUpload(content);
|
|
10243
10425
|
if (Object.keys(secrets).length === 0)
|
|
10244
10426
|
throw new InputError("No secrets found in the input.", "empty_input");
|
|
@@ -10246,7 +10428,7 @@ var upload = defineCommand10({
|
|
|
10246
10428
|
const uploaded = Object.keys(secrets);
|
|
10247
10429
|
ok(
|
|
10248
10430
|
args.json === true,
|
|
10249
|
-
{ appId: t.appId, config: t.configName, uploaded, replaced: args.replace === true },
|
|
10431
|
+
{ appId: t.appId, config: t.configName, uploaded, replaced: args.replace === true, ...APPLIES_AT_DEPLOY },
|
|
10250
10432
|
() => {
|
|
10251
10433
|
console.log(`Uploaded ${uploaded.length} secrets to ${t.configName}.`);
|
|
10252
10434
|
if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
|
|
@@ -10421,12 +10603,19 @@ var secrets_default = defineCommand10({
|
|
|
10421
10603
|
});
|
|
10422
10604
|
|
|
10423
10605
|
// src/cli/commands/init.ts
|
|
10424
|
-
import { existsSync as
|
|
10425
|
-
import { join as
|
|
10606
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10607
|
+
import { join as join23 } from "path";
|
|
10426
10608
|
var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
10609
|
+
function wranglerConfigUncommitted(appDir) {
|
|
10610
|
+
if (!existsSync22(join23(appDir, ".git"))) return false;
|
|
10611
|
+
const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
|
|
10612
|
+
allowFail: true
|
|
10613
|
+
});
|
|
10614
|
+
return dryRun.status === 0;
|
|
10615
|
+
}
|
|
10427
10616
|
function commitScaffoldIfUnborn(appDir, token) {
|
|
10428
10617
|
try {
|
|
10429
|
-
if (!
|
|
10618
|
+
if (!existsSync22(join23(appDir, ".git"))) return false;
|
|
10430
10619
|
if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
|
|
10431
10620
|
return false;
|
|
10432
10621
|
}
|
|
@@ -10503,13 +10692,13 @@ var init_default = defineDeepspaceCommand({
|
|
|
10503
10692
|
writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
|
|
10504
10693
|
const committedScaffold = commitScaffoldIfUnborn(appDir, token);
|
|
10505
10694
|
if (!args.json) {
|
|
10695
|
+
const envSuffix = envName ? ` (env: ${envName})` : "";
|
|
10696
|
+
const commitNote = committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.";
|
|
10506
10697
|
if (existing) {
|
|
10507
|
-
console.log(`Forked: ${existing} \u2192 ${appId}${
|
|
10698
|
+
console.log(`Forked: ${existing} \u2192 ${appId}${envSuffix}${commitNote}`);
|
|
10508
10699
|
console.log("Registered as a NEW app under your account; the original is untouched.");
|
|
10509
10700
|
} else {
|
|
10510
|
-
console.log(
|
|
10511
|
-
`Registered ${appId}${envName ? ` (env: ${envName})` : ""} to your account` + (committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.")
|
|
10512
|
-
);
|
|
10701
|
+
console.log(`Registered ${appId}${envSuffix} to your account${commitNote}`);
|
|
10513
10702
|
console.log("The first deploy claims the `name` subdomain.");
|
|
10514
10703
|
}
|
|
10515
10704
|
console.log(`App dir: ${appDir}`);
|
|
@@ -10522,7 +10711,18 @@ var init_default = defineDeepspaceCommand({
|
|
|
10522
10711
|
committedScaffold,
|
|
10523
10712
|
appDir,
|
|
10524
10713
|
env: envName ?? null
|
|
10525
|
-
}
|
|
10714
|
+
},
|
|
10715
|
+
// The one follow-up when the id was written into an existing history:
|
|
10716
|
+
// rendered as the `Next:` line and carried in `--json`, so a machine
|
|
10717
|
+
// caller does not have to infer it from `committedScaffold: false`.
|
|
10718
|
+
// Offered only when the file is actually uncommitted — `git commit` on
|
|
10719
|
+
// a clean file fails, and an action must be executable as handed over.
|
|
10720
|
+
...!committedScaffold && wranglerConfigUncommitted(appDir) ? {
|
|
10721
|
+
action: {
|
|
10722
|
+
cwd: appDir,
|
|
10723
|
+
argv: ["git", "commit", "-m", existing ? `Fork as ${appId}` : `Register ${appId}`, "--", "wrangler.toml"]
|
|
10724
|
+
}
|
|
10725
|
+
} : {}
|
|
10526
10726
|
};
|
|
10527
10727
|
}
|
|
10528
10728
|
});
|
|
@@ -10530,7 +10730,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
10530
10730
|
// src/cli/commands/status.ts
|
|
10531
10731
|
import { defineCommand as defineCommand11 } from "citty";
|
|
10532
10732
|
import * as p17 from "@clack/prompts";
|
|
10533
|
-
import { existsSync as
|
|
10733
|
+
import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
|
|
10534
10734
|
function timeAgo(iso) {
|
|
10535
10735
|
const ms = Date.now() - Date.parse(iso);
|
|
10536
10736
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -10600,22 +10800,21 @@ var status_default = defineCommand11({
|
|
|
10600
10800
|
"DEEPSPACE_API_URL",
|
|
10601
10801
|
"DEEPSPACE_PLATFORM_URL"
|
|
10602
10802
|
].filter((k) => process.env[k]);
|
|
10803
|
+
const urls = effectivePlatformUrls();
|
|
10804
|
+
lines.push([
|
|
10805
|
+
"Env",
|
|
10806
|
+
[DEEPSPACE_ENV, ...urlOverrides.map((k) => `${k} set`)].join(" \xB7 ")
|
|
10807
|
+
]);
|
|
10808
|
+
json.env = DEEPSPACE_ENV;
|
|
10809
|
+
json.services = urls;
|
|
10603
10810
|
if (DEEPSPACE_ENV !== "production" || urlOverrides.length > 0) {
|
|
10604
|
-
const urls = effectivePlatformUrls();
|
|
10605
|
-
const detail = [
|
|
10606
|
-
DEEPSPACE_ENV !== "production" ? DEEPSPACE_ENV : null,
|
|
10607
|
-
...urlOverrides.map((k) => `${k} set`)
|
|
10608
|
-
].filter(Boolean).join(" \xB7 ");
|
|
10609
|
-
lines.push(["Env", detail]);
|
|
10610
10811
|
lines.push([
|
|
10611
10812
|
"Services",
|
|
10612
10813
|
`auth ${urls.auth} \xB7 api ${urls.api} \xB7 platform ${urls.platform} \xB7 deploy ${urls.deploy}`
|
|
10613
10814
|
]);
|
|
10614
|
-
json.env = DEEPSPACE_ENV;
|
|
10615
|
-
json.services = urls;
|
|
10616
10815
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
10617
10816
|
}
|
|
10618
|
-
facts.loggedIn =
|
|
10817
|
+
facts.loggedIn = existsSync23(SESSION_PATH);
|
|
10619
10818
|
let userId = null;
|
|
10620
10819
|
if (!facts.loggedIn) {
|
|
10621
10820
|
lines.push(["Session", "not logged in"]);
|
|
@@ -10624,7 +10823,7 @@ var status_default = defineCommand11({
|
|
|
10624
10823
|
let who = "logged in";
|
|
10625
10824
|
try {
|
|
10626
10825
|
const payload = decodeJwtPayload(
|
|
10627
|
-
|
|
10826
|
+
readFileSync23(TOKEN_PATH, "utf-8").trim()
|
|
10628
10827
|
);
|
|
10629
10828
|
if (payload.email) who = payload.email;
|
|
10630
10829
|
userId = payload.sub ?? null;
|
|
@@ -10868,11 +11067,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
10868
11067
|
const force = args.force === true;
|
|
10869
11068
|
const appDir = findAppDir();
|
|
10870
11069
|
try {
|
|
10871
|
-
if (!appDir)
|
|
10872
|
-
throw new Refusal(
|
|
10873
|
-
"No wrangler.toml found \u2014 run from inside an app directory.",
|
|
10874
|
-
"not_in_app_repo"
|
|
10875
|
-
);
|
|
11070
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
10876
11071
|
assertSyncableRepo(appDir);
|
|
10877
11072
|
if (branchArg !== void 0) {
|
|
10878
11073
|
const b = branchArg.trim();
|
|
@@ -10895,9 +11090,11 @@ var push_default = defineDeepspaceCommand({
|
|
|
10895
11090
|
"unknown_branch"
|
|
10896
11091
|
);
|
|
10897
11092
|
}
|
|
11093
|
+
const uninitialized = hasWranglerConfig(appDir) && readAppId(appDir) === null;
|
|
10898
11094
|
throw new Refusal(
|
|
10899
|
-
`Branch "${branch}" has no commits yet \u2014 commit first, then push.`,
|
|
10900
|
-
"no_commits"
|
|
11095
|
+
uninitialized ? `Branch "${branch}" has no commits yet, and wrangler.toml still holds the \`__APP_ID__\` placeholder \u2014 run \`deepspace app init\` first: it registers the app and makes the initial commit (committing first would put the unregistered placeholder in history).` : `Branch "${branch}" has no commits yet \u2014 commit first, then push.`,
|
|
11096
|
+
"no_commits",
|
|
11097
|
+
uninitialized ? { action: { cwd: appDir, argv: ["deepspace", "app", "init"] } } : {}
|
|
10901
11098
|
);
|
|
10902
11099
|
}
|
|
10903
11100
|
const spinner2 = args.json ? null : createSpinner();
|
|
@@ -10961,6 +11158,13 @@ var push_default = defineDeepspaceCommand({
|
|
|
10961
11158
|
"app_not_registered"
|
|
10962
11159
|
);
|
|
10963
11160
|
}
|
|
11161
|
+
if (sourceState.source?.provider === "github") {
|
|
11162
|
+
throw new Refusal(
|
|
11163
|
+
`This app uses GitHub source (${sourceState.source.repository}). Manage commits with normal Git/GitHub; \`deepspace deploy\` ships the local working tree without changing Git.`,
|
|
11164
|
+
"source_managed_by_github",
|
|
11165
|
+
{ extra: { appId, repository: sourceState.source.repository } }
|
|
11166
|
+
);
|
|
11167
|
+
}
|
|
10964
11168
|
const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
|
|
10965
11169
|
ensureSpaceRemote(appDir, appId);
|
|
10966
11170
|
if (force) {
|
|
@@ -11100,11 +11304,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11100
11304
|
const branchArg = args.branch === void 0 ? void 0 : String(args.branch);
|
|
11101
11305
|
const appArg = typeof args.app === "string" ? args.app : void 0;
|
|
11102
11306
|
const appDir = findAppDir();
|
|
11103
|
-
if (!appDir)
|
|
11104
|
-
throw new Refusal(
|
|
11105
|
-
"No wrangler.toml found \u2014 run from inside an app directory.",
|
|
11106
|
-
"not_in_app_repo"
|
|
11107
|
-
);
|
|
11307
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
11108
11308
|
if (branchArg !== void 0) {
|
|
11109
11309
|
const b = branchArg.trim();
|
|
11110
11310
|
if (!b) throw new Refusal("--branch was given an empty branch name.", "invalid_branch");
|
|
@@ -11132,9 +11332,9 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11132
11332
|
const token = await ensureToken();
|
|
11133
11333
|
const deployUrl = deployBaseUrl();
|
|
11134
11334
|
const appId = await resolveAppTarget(deployUrl, token, appArg);
|
|
11135
|
-
ensureSpaceRemote(appDir, appId);
|
|
11136
11335
|
spinner2?.message(`Checking the cloud ${branch} ref\u2026`);
|
|
11137
11336
|
const remote = await repoApi(deployUrl, token, appId).getRefs();
|
|
11337
|
+
ensureSpaceRemote(appDir, appId);
|
|
11138
11338
|
if (!remote) {
|
|
11139
11339
|
spinner2?.stop("No cloud repo yet.");
|
|
11140
11340
|
const pushCommand2 = targetedVcCommand("push", appId, branch);
|
|
@@ -11322,18 +11522,18 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11322
11522
|
|
|
11323
11523
|
// src/cli/commands/clone.ts
|
|
11324
11524
|
import * as p20 from "@clack/prompts";
|
|
11325
|
-
import { existsSync as
|
|
11326
|
-
import { resolve as
|
|
11525
|
+
import { existsSync as existsSync24, readdirSync as readdirSync8, rmSync as rmSync5 } from "fs";
|
|
11526
|
+
import { resolve as resolve18, basename as basename5, join as join24 } from "path";
|
|
11327
11527
|
function cleanFailedCloneDir(dir) {
|
|
11328
11528
|
let entries;
|
|
11329
11529
|
try {
|
|
11330
|
-
entries =
|
|
11530
|
+
entries = readdirSync8(dir);
|
|
11331
11531
|
} catch {
|
|
11332
|
-
return
|
|
11532
|
+
return existsSync24(dir) ? ["<unreadable>"] : [];
|
|
11333
11533
|
}
|
|
11334
11534
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
11335
11535
|
try {
|
|
11336
|
-
rmSync5(
|
|
11536
|
+
rmSync5(join24(dir, ".git"), { recursive: true, force: true });
|
|
11337
11537
|
return [];
|
|
11338
11538
|
} catch {
|
|
11339
11539
|
return entries;
|
|
@@ -11342,10 +11542,10 @@ function cleanFailedCloneDir(dir) {
|
|
|
11342
11542
|
return entries;
|
|
11343
11543
|
}
|
|
11344
11544
|
function validateCloneTarget(dir) {
|
|
11345
|
-
if (!
|
|
11545
|
+
if (!existsSync24(dir)) return false;
|
|
11346
11546
|
let entries;
|
|
11347
11547
|
try {
|
|
11348
|
-
entries =
|
|
11548
|
+
entries = readdirSync8(dir);
|
|
11349
11549
|
} catch (error) {
|
|
11350
11550
|
if (error.code === "ENOTDIR") {
|
|
11351
11551
|
throw new Refusal(`${dir} already exists and is not a directory.`, "dir_exists");
|
|
@@ -11377,7 +11577,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
11377
11577
|
async run({ args }) {
|
|
11378
11578
|
assertAppTargetResolvable(String(args.app));
|
|
11379
11579
|
const selector = String(args.app).trim();
|
|
11380
|
-
const dir =
|
|
11580
|
+
const dir = resolve18(String(args.dir ?? selector));
|
|
11381
11581
|
const dirPreexisted = validateCloneTarget(dir);
|
|
11382
11582
|
const spinner2 = args.json ? null : createSpinner();
|
|
11383
11583
|
spinner2?.start(`Resolving ${selector}\u2026`);
|
|
@@ -11550,7 +11750,7 @@ function pickPreviousRelease(releases) {
|
|
|
11550
11750
|
var rollback_default = defineDeepspaceCommand({
|
|
11551
11751
|
meta: {
|
|
11552
11752
|
name: "rollback",
|
|
11553
|
-
description: "Re-deploy a prior release's exact bundle (no rebuild)"
|
|
11753
|
+
description: "Re-deploy a prior release's exact bundle (no rebuild); the live secrets are kept, not the release's"
|
|
11554
11754
|
},
|
|
11555
11755
|
args: {
|
|
11556
11756
|
release: {
|
|
@@ -11685,7 +11885,7 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
11685
11885
|
|
|
11686
11886
|
// src/cli/commands/workspace/new.ts
|
|
11687
11887
|
import * as p25 from "@clack/prompts";
|
|
11688
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
11888
|
+
import { isAbsolute as isAbsolute6, resolve as resolve19 } from "path";
|
|
11689
11889
|
|
|
11690
11890
|
// src/cli/commands/workspace/analysis.ts
|
|
11691
11891
|
import * as p23 from "@clack/prompts";
|
|
@@ -11819,25 +12019,25 @@ function printOverlaps(overlaps, json) {
|
|
|
11819
12019
|
// src/cli/commands/workspace/local.ts
|
|
11820
12020
|
import * as p24 from "@clack/prompts";
|
|
11821
12021
|
import {
|
|
11822
|
-
existsSync as
|
|
12022
|
+
existsSync as existsSync25,
|
|
11823
12023
|
mkdirSync as mkdirSync7,
|
|
11824
|
-
readFileSync as
|
|
12024
|
+
readFileSync as readFileSync24,
|
|
11825
12025
|
realpathSync as realpathSync6,
|
|
11826
12026
|
renameSync as renameSync2,
|
|
11827
12027
|
statSync as statSync3,
|
|
11828
12028
|
writeFileSync as writeFileSync6
|
|
11829
12029
|
} from "fs";
|
|
11830
|
-
import { dirname as dirname11, isAbsolute as isAbsolute5, join as
|
|
12030
|
+
import { dirname as dirname11, isAbsolute as isAbsolute5, join as join25, relative as relative3, sep as sep3 } from "path";
|
|
11831
12031
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11832
12032
|
function excludeWorktreeDir(appDir, dir) {
|
|
11833
12033
|
try {
|
|
11834
12034
|
ensureLocalExclude(appDir, ".deepspace/");
|
|
11835
12035
|
const realDir = realpathSync6(dir);
|
|
11836
|
-
const rel =
|
|
11837
|
-
if (!rel || rel === ".." || rel.startsWith(`..${
|
|
11838
|
-
const pattern = rel.split(
|
|
12036
|
+
const rel = relative3(realpathSync6(repoToplevel(appDir)), realDir);
|
|
12037
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute5(rel)) return;
|
|
12038
|
+
const pattern = rel.split(sep3).join("/");
|
|
11839
12039
|
if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
|
|
11840
|
-
const relApp =
|
|
12040
|
+
const relApp = relative3(realpathSync6(appDir), realDir).split(sep3).join("/");
|
|
11841
12041
|
if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
|
|
11842
12042
|
ensureLocalExclude(appDir, `/${pattern}/`);
|
|
11843
12043
|
} catch {
|
|
@@ -11848,7 +12048,7 @@ function worktreeMarkerPath(worktreeRoot) {
|
|
|
11848
12048
|
const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
|
|
11849
12049
|
if (result.status !== 0) return null;
|
|
11850
12050
|
const gitDir = result.stdout.toString("utf-8").trim();
|
|
11851
|
-
return isAbsolute5(gitDir) ?
|
|
12051
|
+
return isAbsolute5(gitDir) ? join25(gitDir, WORKSPACE_MARKER) : null;
|
|
11852
12052
|
} catch {
|
|
11853
12053
|
return null;
|
|
11854
12054
|
}
|
|
@@ -11884,7 +12084,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11884
12084
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
11885
12085
|
if (!markerPath) return false;
|
|
11886
12086
|
try {
|
|
11887
|
-
const marker = JSON.parse(
|
|
12087
|
+
const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
|
|
11888
12088
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
11889
12089
|
} catch {
|
|
11890
12090
|
return false;
|
|
@@ -11893,11 +12093,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11893
12093
|
function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
11894
12094
|
const primary = listWorktrees(appDir)[0];
|
|
11895
12095
|
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11896
|
-
return
|
|
12096
|
+
return join25(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
11897
12097
|
}
|
|
11898
12098
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
11899
|
-
const appRelative =
|
|
11900
|
-
return appRelative ?
|
|
12099
|
+
const appRelative = relative3(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
12100
|
+
return appRelative ? join25(worktreeRoot, appRelative) : worktreeRoot;
|
|
11901
12101
|
}
|
|
11902
12102
|
function primaryAppDir(appDir) {
|
|
11903
12103
|
const primary = listWorktrees(appDir)[0];
|
|
@@ -11932,7 +12132,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
|
|
|
11932
12132
|
function inOwnLinkedWorktree(appDir, id) {
|
|
11933
12133
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
11934
12134
|
try {
|
|
11935
|
-
return statSync3(
|
|
12135
|
+
return statSync3(join25(repoToplevel(appDir), ".git")).isFile();
|
|
11936
12136
|
} catch {
|
|
11937
12137
|
return false;
|
|
11938
12138
|
}
|
|
@@ -12057,7 +12257,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
12057
12257
|
}
|
|
12058
12258
|
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
12059
12259
|
out.worktreeDir = null;
|
|
12060
|
-
const leftover =
|
|
12260
|
+
const leftover = existsSync25(linked.path);
|
|
12061
12261
|
if (leftover) out.leftoverDir = linked.path;
|
|
12062
12262
|
else out.worktreeRemoved = linked.path;
|
|
12063
12263
|
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
@@ -12137,7 +12337,7 @@ async function resolveTarget2(appArg) {
|
|
|
12137
12337
|
const appDir = findAppDir();
|
|
12138
12338
|
if (!appDir) {
|
|
12139
12339
|
throw new Refusal(
|
|
12140
|
-
|
|
12340
|
+
`${noWranglerConfigMessage(process.cwd())} (Or use -a <app> for list, and for drop with an explicit ws_ id.)`,
|
|
12141
12341
|
"not_in_app_repo"
|
|
12142
12342
|
);
|
|
12143
12343
|
}
|
|
@@ -12317,7 +12517,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12317
12517
|
idempotencyKey: mintIdempotencyKey()
|
|
12318
12518
|
});
|
|
12319
12519
|
const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12320
|
-
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput :
|
|
12520
|
+
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve19(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
|
|
12321
12521
|
const dir = appDirInWorktree(appDir, worktreeRoot);
|
|
12322
12522
|
try {
|
|
12323
12523
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
|
|
@@ -12355,14 +12555,14 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12355
12555
|
|
|
12356
12556
|
// src/cli/commands/workspace/attach.ts
|
|
12357
12557
|
import * as p26 from "@clack/prompts";
|
|
12358
|
-
import { existsSync as
|
|
12359
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
12558
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
|
|
12559
|
+
import { isAbsolute as isAbsolute7, resolve as resolve20 } from "path";
|
|
12360
12560
|
function samePath(a, b) {
|
|
12361
12561
|
const real = (p35) => {
|
|
12362
12562
|
try {
|
|
12363
12563
|
return realpathSync7(p35);
|
|
12364
12564
|
} catch {
|
|
12365
|
-
return
|
|
12565
|
+
return resolve20(p35);
|
|
12366
12566
|
}
|
|
12367
12567
|
};
|
|
12368
12568
|
return real(a) === real(b);
|
|
@@ -12375,10 +12575,10 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
12375
12575
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
12376
12576
|
}
|
|
12377
12577
|
function cleanFailedFreshAttachDir(dir) {
|
|
12378
|
-
if (!
|
|
12578
|
+
if (!existsSync26(dir)) return [];
|
|
12379
12579
|
let entries;
|
|
12380
12580
|
try {
|
|
12381
|
-
entries =
|
|
12581
|
+
entries = readdirSync9(dir);
|
|
12382
12582
|
} catch {
|
|
12383
12583
|
return ["<unreadable>"];
|
|
12384
12584
|
}
|
|
@@ -12452,12 +12652,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12452
12652
|
);
|
|
12453
12653
|
}
|
|
12454
12654
|
const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12455
|
-
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir :
|
|
12655
|
+
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve20(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
|
|
12456
12656
|
const dir2 = appDirInWorktree(appDir, worktreeRoot);
|
|
12457
12657
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
12458
12658
|
if (localTip) {
|
|
12459
12659
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
12460
|
-
if (existing && !
|
|
12660
|
+
if (existing && !existsSync26(existing)) {
|
|
12461
12661
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
12462
12662
|
existing = null;
|
|
12463
12663
|
}
|
|
@@ -12571,11 +12771,11 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12571
12771
|
extra: { status: view.workspace.status }
|
|
12572
12772
|
});
|
|
12573
12773
|
}
|
|
12574
|
-
const dir =
|
|
12774
|
+
const dir = resolve20(
|
|
12575
12775
|
process.cwd(),
|
|
12576
12776
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
12577
12777
|
);
|
|
12578
|
-
if (
|
|
12778
|
+
if (existsSync26(dir)) {
|
|
12579
12779
|
spinner2?.stop("Target exists.");
|
|
12580
12780
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
12581
12781
|
}
|
|
@@ -12912,13 +13112,13 @@ import * as p30 from "@clack/prompts";
|
|
|
12912
13112
|
|
|
12913
13113
|
// src/cli/lib/validation.ts
|
|
12914
13114
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12915
|
-
import { readFileSync as
|
|
12916
|
-
import { join as
|
|
13115
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
13116
|
+
import { join as join26 } from "path";
|
|
12917
13117
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12918
13118
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12919
13119
|
function configuredCommand(appDir) {
|
|
12920
13120
|
try {
|
|
12921
|
-
const pkg2 = JSON.parse(
|
|
13121
|
+
const pkg2 = JSON.parse(readFileSync25(join26(appDir, "package.json"), "utf-8"));
|
|
12922
13122
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12923
13123
|
} catch {
|
|
12924
13124
|
}
|
|
@@ -13994,13 +14194,13 @@ function sameRefs(left, right) {
|
|
|
13994
14194
|
|
|
13995
14195
|
// src/cli/commands/update.ts
|
|
13996
14196
|
import * as p34 from "@clack/prompts";
|
|
13997
|
-
import { existsSync as
|
|
13998
|
-
import { join as
|
|
14197
|
+
import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync7 } from "fs";
|
|
14198
|
+
import { join as join27, resolve as resolve21 } from "path";
|
|
13999
14199
|
|
|
14000
14200
|
// package.json
|
|
14001
14201
|
var package_default = {
|
|
14002
14202
|
name: "deepspace",
|
|
14003
|
-
version: "0.23.
|
|
14203
|
+
version: "0.23.2",
|
|
14004
14204
|
engines: {
|
|
14005
14205
|
node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
|
|
14006
14206
|
},
|
|
@@ -14175,24 +14375,25 @@ function gapIsTooWide(from, to) {
|
|
|
14175
14375
|
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
14176
14376
|
}
|
|
14177
14377
|
function readAppSdkVersion(appDir) {
|
|
14178
|
-
const installed =
|
|
14179
|
-
if (
|
|
14180
|
-
const parsed2 = JSON.parse(
|
|
14378
|
+
const installed = join27(appDir, "node_modules", "deepspace", "package.json");
|
|
14379
|
+
if (existsSync27(installed)) {
|
|
14380
|
+
const parsed2 = JSON.parse(readFileSync26(installed, "utf8"));
|
|
14181
14381
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
14182
14382
|
}
|
|
14183
|
-
const manifest =
|
|
14184
|
-
if (!
|
|
14185
|
-
const parsed = JSON.parse(
|
|
14383
|
+
const manifest = join27(appDir, "package.json");
|
|
14384
|
+
if (!existsSync27(manifest)) return null;
|
|
14385
|
+
const parsed = JSON.parse(readFileSync26(manifest, "utf8"));
|
|
14186
14386
|
return parsed.dependencies?.deepspace ?? null;
|
|
14187
14387
|
}
|
|
14188
14388
|
function pinSdkVersion(appDir, version) {
|
|
14189
|
-
const path =
|
|
14190
|
-
const source =
|
|
14389
|
+
const path = join27(appDir, "package.json");
|
|
14390
|
+
const source = readFileSync26(path, "utf8");
|
|
14191
14391
|
const manifest = JSON.parse(source);
|
|
14192
14392
|
const dependencies = manifest.dependencies;
|
|
14193
14393
|
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) return false;
|
|
14194
14394
|
let changed = false;
|
|
14195
|
-
|
|
14395
|
+
const registrySpec = (spec) => !/^(file|link|workspace|git\+|github|https?):/.test(spec) && !spec.includes("/");
|
|
14396
|
+
if (Object.prototype.hasOwnProperty.call(dependencies, "deepspace") && typeof dependencies.deepspace === "string" && registrySpec(dependencies.deepspace) && dependencies.deepspace !== `^${version}`) {
|
|
14196
14397
|
dependencies.deepspace = `^${version}`;
|
|
14197
14398
|
changed = true;
|
|
14198
14399
|
}
|
|
@@ -14213,9 +14414,9 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
|
14213
14414
|
"vite.config.mts",
|
|
14214
14415
|
"vite.config.js",
|
|
14215
14416
|
"vite.config.mjs"
|
|
14216
|
-
].find((candidate) =>
|
|
14417
|
+
].find((candidate) => existsSync27(join27(appDir, candidate)));
|
|
14217
14418
|
if (!configName) return null;
|
|
14218
|
-
const source =
|
|
14419
|
+
const source = readFileSync26(join27(appDir, configName), "utf8");
|
|
14219
14420
|
if (source.includes("deepspace-remove-build-preview-secrets") || source.includes("deepspaceBuild(") || source.includes("'deepspace/build'") || source.includes('"deepspace/build"')) {
|
|
14220
14421
|
return null;
|
|
14221
14422
|
}
|
|
@@ -14223,9 +14424,9 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
|
14223
14424
|
}
|
|
14224
14425
|
function usersSchemaVisibilityUpgradeInstruction(appDir) {
|
|
14225
14426
|
const schemaName = "src/schemas/users-schema.ts";
|
|
14226
|
-
const path =
|
|
14227
|
-
if (!
|
|
14228
|
-
const source =
|
|
14427
|
+
const path = join27(appDir, schemaName);
|
|
14428
|
+
if (!existsSync27(path)) return null;
|
|
14429
|
+
const source = readFileSync26(path, "utf8");
|
|
14229
14430
|
if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
|
|
14230
14431
|
return `${schemaName}: review the member user-row visibility change at ${USERS_SCHEMA_VISIBILITY_GUIDE}; app update left this app-owned schema unchanged.`;
|
|
14231
14432
|
}
|
|
@@ -14285,12 +14486,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
14285
14486
|
}
|
|
14286
14487
|
},
|
|
14287
14488
|
async run({ args }) {
|
|
14288
|
-
const appDir =
|
|
14489
|
+
const appDir = resolve21(".");
|
|
14289
14490
|
if (!hasWranglerConfig(appDir)) {
|
|
14290
|
-
throw new Refusal(
|
|
14291
|
-
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
14292
|
-
"not_in_app_repo"
|
|
14293
|
-
);
|
|
14491
|
+
throw new Refusal(noWranglerConfigMessage(join27(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
14294
14492
|
}
|
|
14295
14493
|
assertSyncableRepo(appDir);
|
|
14296
14494
|
const currentRaw = readAppSdkVersion(appDir);
|
|
@@ -14376,8 +14574,8 @@ var update_default = defineDeepspaceCommand({
|
|
|
14376
14574
|
|
|
14377
14575
|
// src/cli/cli.ts
|
|
14378
14576
|
var __dirname = dirname12(fileURLToPath3(import.meta.url));
|
|
14379
|
-
var pkg = JSON.parse(
|
|
14380
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
14577
|
+
var pkg = JSON.parse(readFileSync27(join28(__dirname, "..", "package.json"), "utf-8"));
|
|
14578
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync28("/etc/ssl/certs/ca-certificates.crt")) {
|
|
14381
14579
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
14382
14580
|
}
|
|
14383
14581
|
var create2 = defineCommand14({
|