deepspace 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -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-UUCAFDYW.js} +2 -2
- package/dist/{chunk-A7VKZRCB.js.map → chunk-UUCAFDYW.js.map} +1 -1
- package/dist/cli.js +735 -559
- 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 +261 -88
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +70 -3
- package/dist/worker.js +444 -272
- 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
|
|
1134
|
+
);
|
|
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
|
|
950
1145
|
);
|
|
951
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.)`;
|
|
952
1177
|
}
|
|
1178
|
+
function hasWranglerConfig(appDir) {
|
|
1179
|
+
return existsSync3(join2(appDir, "wrangler.toml"));
|
|
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
|
);
|
|
@@ -1492,197 +1713,6 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
|
1492
1713
|
return body.token;
|
|
1493
1714
|
}
|
|
1494
1715
|
|
|
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";
|
|
1671
|
-
}
|
|
1672
|
-
};
|
|
1673
|
-
function readWranglerConfig(appDir) {
|
|
1674
|
-
const path = join6(appDir, "wrangler.toml");
|
|
1675
|
-
const raw = readFileSync9(path, "utf-8");
|
|
1676
|
-
try {
|
|
1677
|
-
return parseToml3(raw);
|
|
1678
|
-
} catch (err) {
|
|
1679
|
-
throw new WranglerConfigError(path, err);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
function hasWranglerConfig(appDir) {
|
|
1683
|
-
return existsSync6(join6(appDir, "wrangler.toml"));
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
1716
|
// src/cli/lib/dev-vars.ts
|
|
1687
1717
|
var HEADER = [
|
|
1688
1718
|
"# Generated by deepspace \u2014 do not edit; this file is rewritten on every",
|
|
@@ -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",
|
|
@@ -3350,6 +3422,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3350
3422
|
ensurePlaywright(appDir);
|
|
3351
3423
|
}
|
|
3352
3424
|
let exitCode = 0;
|
|
3425
|
+
let skippedSpecs = [];
|
|
3353
3426
|
switch (suite) {
|
|
3354
3427
|
case "smoke":
|
|
3355
3428
|
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
|
|
@@ -3371,13 +3444,13 @@ var test_default = defineDeepspaceCommand({
|
|
|
3371
3444
|
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
|
|
3372
3445
|
break;
|
|
3373
3446
|
case "default":
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3447
|
+
skippedSpecs = specsSkippedByDefaultSuite(appDir);
|
|
3448
|
+
exitCode = runPlaywright(appDir, [...DEFAULT_SUITE_SPECS], port, wranglerEnv, forwarded);
|
|
3449
|
+
if (skippedSpecs.length > 0) {
|
|
3450
|
+
say(
|
|
3451
|
+
`skipped ${skippedSpecs.length} spec file(s) not in smoke+api (${skippedSpecs.join(", ")}) \u2014 run 'deepspace test run all'`
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3381
3454
|
break;
|
|
3382
3455
|
default:
|
|
3383
3456
|
if (suite.endsWith(".spec.ts")) {
|
|
@@ -3392,10 +3465,10 @@ Available: smoke, api, e2e, unit, all`,
|
|
|
3392
3465
|
}
|
|
3393
3466
|
if (exitCode !== 0) {
|
|
3394
3467
|
throw new Refusal(`Test suite '${suite}' failed (exit ${exitCode}).`, "tests_failed", {
|
|
3395
|
-
extra: { suite, port, exitCode }
|
|
3468
|
+
extra: { suite, port, exitCode, skippedSpecs }
|
|
3396
3469
|
});
|
|
3397
3470
|
}
|
|
3398
|
-
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
3471
|
+
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs } };
|
|
3399
3472
|
}
|
|
3400
3473
|
});
|
|
3401
3474
|
function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
|
|
@@ -3829,7 +3902,7 @@ var recover = defineDeepspaceCommand({
|
|
|
3829
3902
|
for (const target2 of targets) {
|
|
3830
3903
|
try {
|
|
3831
3904
|
const account = await recoverRemoteTestAccount(target2.id);
|
|
3832
|
-
const name = localNames.get(account.email);
|
|
3905
|
+
const name = account.name ?? localNames.get(account.email);
|
|
3833
3906
|
upsertTestAccount({
|
|
3834
3907
|
id: account.id,
|
|
3835
3908
|
email: account.email,
|
|
@@ -3880,7 +3953,7 @@ var test_accounts_default = defineCommand2({
|
|
|
3880
3953
|
// src/cli/commands/deploy.ts
|
|
3881
3954
|
import * as p12 from "@clack/prompts";
|
|
3882
3955
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3883
|
-
import { resolve as
|
|
3956
|
+
import { join as join19, resolve as resolve14 } from "path";
|
|
3884
3957
|
|
|
3885
3958
|
// src/cli/lib/edge-propagation.ts
|
|
3886
3959
|
import { request as httpRequest } from "http";
|
|
@@ -3945,14 +4018,14 @@ async function servesRelease(probe, releaseStamp) {
|
|
|
3945
4018
|
}
|
|
3946
4019
|
function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
3947
4020
|
const send = url.protocol === "http:" ? httpRequest : httpsRequest;
|
|
3948
|
-
return new Promise((
|
|
4021
|
+
return new Promise((resolve22) => {
|
|
3949
4022
|
const request2 = send(
|
|
3950
4023
|
url,
|
|
3951
4024
|
{ method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
|
|
3952
4025
|
(response) => {
|
|
3953
4026
|
if (response.statusCode !== 200) {
|
|
3954
4027
|
response.resume();
|
|
3955
|
-
|
|
4028
|
+
resolve22(null);
|
|
3956
4029
|
return;
|
|
3957
4030
|
}
|
|
3958
4031
|
let body = "";
|
|
@@ -3960,20 +4033,20 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
|
3960
4033
|
response.on("data", (chunk) => {
|
|
3961
4034
|
if (body.length < 4096) body += chunk;
|
|
3962
4035
|
});
|
|
3963
|
-
response.on("end", () =>
|
|
3964
|
-
response.on("error", () =>
|
|
4036
|
+
response.on("end", () => resolve22(body));
|
|
4037
|
+
response.on("error", () => resolve22(null));
|
|
3965
4038
|
}
|
|
3966
4039
|
);
|
|
3967
4040
|
request2.on("timeout", () => {
|
|
3968
4041
|
request2.destroy();
|
|
3969
|
-
|
|
4042
|
+
resolve22(null);
|
|
3970
4043
|
});
|
|
3971
|
-
request2.on("error", () =>
|
|
4044
|
+
request2.on("error", () => resolve22(null));
|
|
3972
4045
|
request2.end();
|
|
3973
4046
|
});
|
|
3974
4047
|
}
|
|
3975
4048
|
function sleep2(ms) {
|
|
3976
|
-
return new Promise((
|
|
4049
|
+
return new Promise((resolve22) => setTimeout(resolve22, ms));
|
|
3977
4050
|
}
|
|
3978
4051
|
|
|
3979
4052
|
// src/shared/app-files.ts
|
|
@@ -4038,36 +4111,49 @@ function describeFilesFailure(status3, body) {
|
|
|
4038
4111
|
import * as p6 from "@clack/prompts";
|
|
4039
4112
|
import { execSync as execSync2 } from "child_process";
|
|
4040
4113
|
import { createHash as createHash2 } from "crypto";
|
|
4041
|
-
import { existsSync as
|
|
4042
|
-
import { dirname as dirname7, join as
|
|
4114
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "fs";
|
|
4115
|
+
import { dirname as dirname7, join as join17, resolve as resolve12 } from "path";
|
|
4116
|
+
|
|
4117
|
+
// src/build/plugin.ts
|
|
4118
|
+
import { existsSync as existsSync12, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
4119
|
+
import { join as join14, resolve as resolve11 } from "path";
|
|
4120
|
+
function removeBuildDevVars(workerDir) {
|
|
4121
|
+
const path = join14(workerDir, ".dev.vars");
|
|
4122
|
+
if (!existsSync12(path)) return false;
|
|
4123
|
+
if (!lstatSync(path).isFile()) {
|
|
4124
|
+
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4125
|
+
}
|
|
4126
|
+
unlinkSync2(path);
|
|
4127
|
+
return true;
|
|
4128
|
+
}
|
|
4043
4129
|
|
|
4044
4130
|
// src/documentation/deploy.ts
|
|
4045
|
-
import { existsSync as
|
|
4046
|
-
import { join as
|
|
4131
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4132
|
+
import { join as join15 } from "path";
|
|
4047
4133
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
4048
|
-
if (!
|
|
4049
|
-
const manifestPath =
|
|
4050
|
-
if (!
|
|
4134
|
+
if (!existsSync13(join15(appDir, "documentation.json"))) return void 0;
|
|
4135
|
+
const manifestPath = join15(clientDir, "_documentation", "manifest.json");
|
|
4136
|
+
if (!existsSync13(manifestPath)) {
|
|
4051
4137
|
throw new Error(
|
|
4052
4138
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
4053
4139
|
);
|
|
4054
4140
|
}
|
|
4055
|
-
return JSON.parse(
|
|
4141
|
+
return JSON.parse(readFileSync14(manifestPath, "utf8"));
|
|
4056
4142
|
}
|
|
4057
4143
|
|
|
4058
4144
|
// src/cli/commands/update/app-migrations.ts
|
|
4059
4145
|
import {
|
|
4060
4146
|
closeSync,
|
|
4061
4147
|
constants,
|
|
4062
|
-
existsSync as
|
|
4148
|
+
existsSync as existsSync14,
|
|
4063
4149
|
fstatSync,
|
|
4064
4150
|
ftruncateSync,
|
|
4065
|
-
lstatSync,
|
|
4151
|
+
lstatSync as lstatSync2,
|
|
4066
4152
|
openSync,
|
|
4067
|
-
readFileSync as
|
|
4153
|
+
readFileSync as readFileSync15,
|
|
4068
4154
|
writeSync
|
|
4069
4155
|
} from "fs";
|
|
4070
|
-
import { join as
|
|
4156
|
+
import { join as join16 } from "path";
|
|
4071
4157
|
|
|
4072
4158
|
// src/shared/protocol/app-migrations.ts
|
|
4073
4159
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
@@ -4119,8 +4205,8 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
|
4119
4205
|
({ id, description }) => ({ id, description })
|
|
4120
4206
|
);
|
|
4121
4207
|
function readAppliedAppMigrations(appDir) {
|
|
4122
|
-
const path =
|
|
4123
|
-
if (!
|
|
4208
|
+
const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4209
|
+
if (!existsSync14(path)) return [];
|
|
4124
4210
|
const source = readRegularFile(path);
|
|
4125
4211
|
let parsed;
|
|
4126
4212
|
try {
|
|
@@ -4146,10 +4232,10 @@ function planAppMigrations(appDir) {
|
|
|
4146
4232
|
const edits = [];
|
|
4147
4233
|
for (const repoPath of tracked) {
|
|
4148
4234
|
if (!isSourcePath(repoPath, appPrefix)) continue;
|
|
4149
|
-
const absolutePath =
|
|
4235
|
+
const absolutePath = join16(repo, repoPath);
|
|
4150
4236
|
let stat;
|
|
4151
4237
|
try {
|
|
4152
|
-
stat =
|
|
4238
|
+
stat = lstatSync2(absolutePath);
|
|
4153
4239
|
} catch {
|
|
4154
4240
|
continue;
|
|
4155
4241
|
}
|
|
@@ -4183,8 +4269,8 @@ function planAppMigrations(appDir) {
|
|
|
4183
4269
|
});
|
|
4184
4270
|
if (pending.length > 0 && blockers.length === 0) {
|
|
4185
4271
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
4186
|
-
const absoluteManifestPath =
|
|
4187
|
-
const before =
|
|
4272
|
+
const absoluteManifestPath = join16(repo, manifestPath);
|
|
4273
|
+
const before = existsSync14(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
4188
4274
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
4189
4275
|
`;
|
|
4190
4276
|
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
@@ -4199,13 +4285,13 @@ function planAppMigrations(appDir) {
|
|
|
4199
4285
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4200
4286
|
const repo = repoToplevel(appDir);
|
|
4201
4287
|
for (const edit of plan.edits) {
|
|
4202
|
-
const absolutePath =
|
|
4203
|
-
const current =
|
|
4288
|
+
const absolutePath = join16(repo, edit.path);
|
|
4289
|
+
const current = existsSync14(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4204
4290
|
if (current !== edit.before) {
|
|
4205
4291
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4206
4292
|
}
|
|
4207
4293
|
}
|
|
4208
|
-
for (const edit of plan.edits) writeRegularFile(
|
|
4294
|
+
for (const edit of plan.edits) writeRegularFile(join16(repo, edit.path), edit.before, edit.after);
|
|
4209
4295
|
}
|
|
4210
4296
|
function normalizeEol(source) {
|
|
4211
4297
|
return source.replaceAll("\r\n", "\n");
|
|
@@ -4214,18 +4300,18 @@ function restoreEol(source, checkout) {
|
|
|
4214
4300
|
return checkout.includes("\r\n") ? source.replaceAll("\n", "\r\n") : source;
|
|
4215
4301
|
}
|
|
4216
4302
|
function readRegularFile(path) {
|
|
4217
|
-
const before =
|
|
4303
|
+
const before = lstatSync2(path);
|
|
4218
4304
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
4219
4305
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4220
4306
|
}
|
|
4221
4307
|
const fd = openSync(path, constants.O_RDONLY);
|
|
4222
4308
|
try {
|
|
4223
4309
|
const opened = fstatSync(fd);
|
|
4224
|
-
const after =
|
|
4310
|
+
const after = lstatSync2(path);
|
|
4225
4311
|
if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
4226
4312
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4227
4313
|
}
|
|
4228
|
-
return
|
|
4314
|
+
return readFileSync15(fd, "utf-8");
|
|
4229
4315
|
} finally {
|
|
4230
4316
|
closeSync(fd);
|
|
4231
4317
|
}
|
|
@@ -4240,18 +4326,18 @@ function writeRegularFile(path, before, after) {
|
|
|
4240
4326
|
}
|
|
4241
4327
|
return;
|
|
4242
4328
|
}
|
|
4243
|
-
const pathStat =
|
|
4329
|
+
const pathStat = lstatSync2(path);
|
|
4244
4330
|
if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
|
|
4245
4331
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4246
4332
|
}
|
|
4247
4333
|
const fd = openSync(path, constants.O_RDWR);
|
|
4248
4334
|
try {
|
|
4249
4335
|
const opened = fstatSync(fd);
|
|
4250
|
-
const currentPath =
|
|
4336
|
+
const currentPath = lstatSync2(path);
|
|
4251
4337
|
if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
|
|
4252
4338
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4253
4339
|
}
|
|
4254
|
-
if (
|
|
4340
|
+
if (readFileSync15(fd, "utf-8") !== before) {
|
|
4255
4341
|
throw new Error(`Migration input changed after planning: ${path}`);
|
|
4256
4342
|
}
|
|
4257
4343
|
ftruncateSync(fd, 0);
|
|
@@ -4578,16 +4664,6 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4578
4664
|
|
|
4579
4665
|
// src/cli/commands/deploy/build.ts
|
|
4580
4666
|
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
4667
|
function declaredCompatibilityDate(config) {
|
|
4592
4668
|
const declared = config.compatibility_date;
|
|
4593
4669
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
@@ -4624,19 +4700,19 @@ async function buildDeployBundle(options) {
|
|
|
4624
4700
|
preparedWranglerConfig?.cleanup();
|
|
4625
4701
|
}
|
|
4626
4702
|
spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
|
|
4627
|
-
const deployConfigPath =
|
|
4628
|
-
if (!
|
|
4703
|
+
const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
|
|
4704
|
+
if (!existsSync15(deployConfigPath)) {
|
|
4629
4705
|
output.die(
|
|
4630
4706
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4631
4707
|
"build_output_missing"
|
|
4632
4708
|
);
|
|
4633
4709
|
}
|
|
4634
|
-
const deployConfig = JSON.parse(
|
|
4635
|
-
const outputWranglerPath =
|
|
4636
|
-
if (!
|
|
4710
|
+
const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
|
|
4711
|
+
const outputWranglerPath = resolve12(dirname7(deployConfigPath), deployConfig.configPath);
|
|
4712
|
+
if (!existsSync15(outputWranglerPath)) {
|
|
4637
4713
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4638
4714
|
}
|
|
4639
|
-
const outputConfig = JSON.parse(
|
|
4715
|
+
const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
|
|
4640
4716
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
4641
4717
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
4642
4718
|
output.die(
|
|
@@ -4650,12 +4726,12 @@ async function buildDeployBundle(options) {
|
|
|
4650
4726
|
} catch (error) {
|
|
4651
4727
|
output.die(errorMessage(error), "build_output_unsafe");
|
|
4652
4728
|
}
|
|
4653
|
-
const workerBundlePath =
|
|
4654
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
4655
|
-
if (!
|
|
4729
|
+
const workerBundlePath = join17(workerDir, outputConfig.main);
|
|
4730
|
+
const clientDir = outputConfig.assets?.directory ? resolve12(workerDir, outputConfig.assets.directory) : null;
|
|
4731
|
+
if (!existsSync15(workerBundlePath)) {
|
|
4656
4732
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4657
4733
|
}
|
|
4658
|
-
if (!clientDir || !
|
|
4734
|
+
if (!clientDir || !existsSync15(clientDir)) {
|
|
4659
4735
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4660
4736
|
}
|
|
4661
4737
|
try {
|
|
@@ -4670,7 +4746,7 @@ async function buildDeployBundle(options) {
|
|
|
4670
4746
|
spinner2.stop(`Collected ${assets.length} assets`);
|
|
4671
4747
|
const appIdRefusal = clientAppIdRefusal(assets, appId, envName);
|
|
4672
4748
|
if (appIdRefusal) output.die(appIdRefusal.message, appIdRefusal.code);
|
|
4673
|
-
const workerJs =
|
|
4749
|
+
const workerJs = readFileSync16(workerBundlePath, "utf-8");
|
|
4674
4750
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
4675
4751
|
const sqliteClasses = new Set(
|
|
4676
4752
|
outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
|
|
@@ -4753,7 +4829,7 @@ function clientAppIdRefusal(assets, appId, envName) {
|
|
|
4753
4829
|
let unsubstituted;
|
|
4754
4830
|
for (const asset of assets) {
|
|
4755
4831
|
if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
|
|
4756
|
-
const source =
|
|
4832
|
+
const source = readFileSync16(asset.sourcePath, "utf-8");
|
|
4757
4833
|
if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
|
|
4758
4834
|
for (const found of source.matchAll(APP_ID_LITERAL)) {
|
|
4759
4835
|
if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
|
|
@@ -4780,14 +4856,14 @@ var LARGE_MEDIA_HOME = "Large media belongs in your app files (`deepspace app fi
|
|
|
4780
4856
|
function collectAssets(dir) {
|
|
4781
4857
|
const assets = [];
|
|
4782
4858
|
const walk = (currentDir, prefix) => {
|
|
4783
|
-
for (const entry of
|
|
4859
|
+
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
4784
4860
|
if (isDeployAssetControlFile(entry.name)) continue;
|
|
4785
|
-
const fullPath =
|
|
4861
|
+
const fullPath = join17(currentDir, entry.name);
|
|
4786
4862
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
4787
4863
|
if (entry.isDirectory()) {
|
|
4788
4864
|
walk(fullPath, relativePath);
|
|
4789
4865
|
} else {
|
|
4790
|
-
const bytes =
|
|
4866
|
+
const bytes = readFileSync16(fullPath);
|
|
4791
4867
|
assets.push({
|
|
4792
4868
|
path: `/${relativePath}`,
|
|
4793
4869
|
hash: createHash2("sha256").update(bytes).digest("hex"),
|
|
@@ -4806,9 +4882,9 @@ function readDeployAssetConfig(clientDir) {
|
|
|
4806
4882
|
["_headers", "_headers"],
|
|
4807
4883
|
["_redirects", "_redirects"]
|
|
4808
4884
|
]) {
|
|
4809
|
-
const filePath =
|
|
4810
|
-
if (!
|
|
4811
|
-
const content =
|
|
4885
|
+
const filePath = join17(clientDir, file);
|
|
4886
|
+
if (!existsSync15(filePath)) continue;
|
|
4887
|
+
const content = readFileSync16(filePath, "utf8");
|
|
4812
4888
|
if (content.trim()) config[key] = content;
|
|
4813
4889
|
}
|
|
4814
4890
|
return config;
|
|
@@ -4822,8 +4898,8 @@ function errorMessage(error) {
|
|
|
4822
4898
|
|
|
4823
4899
|
// src/cli/commands/deploy/commerce.ts
|
|
4824
4900
|
import * as p7 from "@clack/prompts";
|
|
4825
|
-
import { existsSync as
|
|
4826
|
-
import { join as
|
|
4901
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4902
|
+
import { join as join18 } from "path";
|
|
4827
4903
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4828
4904
|
|
|
4829
4905
|
// src/shared/api-error.ts
|
|
@@ -4875,10 +4951,10 @@ function errorMessage2(error) {
|
|
|
4875
4951
|
return error instanceof Error ? error.message : String(error);
|
|
4876
4952
|
}
|
|
4877
4953
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4878
|
-
const sourcePath =
|
|
4879
|
-
if (!
|
|
4954
|
+
const sourcePath = join18(appDir, "src", sourceFile);
|
|
4955
|
+
if (!existsSync16(sourcePath)) return void 0;
|
|
4880
4956
|
const esbuild = await import("esbuild");
|
|
4881
|
-
const outputPath =
|
|
4957
|
+
const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
|
|
4882
4958
|
await esbuild.build({
|
|
4883
4959
|
entryPoints: [sourcePath],
|
|
4884
4960
|
bundle: true,
|
|
@@ -4971,7 +5047,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4971
5047
|
}
|
|
4972
5048
|
}
|
|
4973
5049
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4974
|
-
if (!
|
|
5050
|
+
if (!existsSync16(join18(appDir, "src", "subscriptions.ts"))) return;
|
|
4975
5051
|
let plans;
|
|
4976
5052
|
try {
|
|
4977
5053
|
plans = await loadDeclaration(
|
|
@@ -5026,7 +5102,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
5026
5102
|
}
|
|
5027
5103
|
}
|
|
5028
5104
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
5029
|
-
if (!
|
|
5105
|
+
if (!existsSync16(join18(appDir, "src", "products.ts"))) return;
|
|
5030
5106
|
let products;
|
|
5031
5107
|
try {
|
|
5032
5108
|
products = await loadDeclaration(
|
|
@@ -5128,7 +5204,7 @@ function createDeployOutput(json) {
|
|
|
5128
5204
|
|
|
5129
5205
|
// src/cli/commands/deploy/request.ts
|
|
5130
5206
|
import * as p10 from "@clack/prompts";
|
|
5131
|
-
import { readFileSync as
|
|
5207
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
5132
5208
|
|
|
5133
5209
|
// src/cli/commands/deploy/repository.ts
|
|
5134
5210
|
import * as p9 from "@clack/prompts";
|
|
@@ -5319,9 +5395,9 @@ function repoApi(deployUrl, token, appId) {
|
|
|
5319
5395
|
}
|
|
5320
5396
|
|
|
5321
5397
|
// src/cli/lib/vc-remote.ts
|
|
5322
|
-
import { existsSync as
|
|
5398
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5323
5399
|
import { tmpdir } from "os";
|
|
5324
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
5400
|
+
import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve13 } from "path";
|
|
5325
5401
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
5326
5402
|
if (environment === "staging") return "space-staging";
|
|
5327
5403
|
if (environment === "production") return "space";
|
|
@@ -5367,14 +5443,14 @@ function entryIsTransient(cwd) {
|
|
|
5367
5443
|
const entry = process.argv[1];
|
|
5368
5444
|
if (!entry) return true;
|
|
5369
5445
|
if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
|
|
5370
|
-
const rel =
|
|
5446
|
+
const rel = relative2(resolve13(cwd), resolve13(entry));
|
|
5371
5447
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
5372
5448
|
}
|
|
5373
5449
|
function helperEntryUsable(command) {
|
|
5374
5450
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
5375
5451
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
5376
5452
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
5377
|
-
return
|
|
5453
|
+
return existsSync17(match[2]) && existsSync17(match[3]);
|
|
5378
5454
|
}
|
|
5379
5455
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
5380
5456
|
const entry = process.argv[1];
|
|
@@ -5509,12 +5585,6 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5509
5585
|
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
5586
|
};
|
|
5511
5587
|
}
|
|
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
5588
|
if (/(?:HTTP |error: )429\b/i.test(message)) {
|
|
5519
5589
|
return {
|
|
5520
5590
|
code: "rate_limited",
|
|
@@ -5524,6 +5594,12 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5524
5594
|
if (/(?:HTTP |error: )413\b/i.test(message)) {
|
|
5525
5595
|
return { code: "push_too_large", error: pushTooLargeMessage(cwd) };
|
|
5526
5596
|
}
|
|
5597
|
+
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
5598
|
+
return {
|
|
5599
|
+
code: "source_managed_by_github",
|
|
5600
|
+
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."
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5527
5603
|
return null;
|
|
5528
5604
|
}
|
|
5529
5605
|
function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
@@ -5895,7 +5971,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5895
5971
|
const message = errorMessage3(error);
|
|
5896
5972
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5897
5973
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5898
|
-
await new Promise((
|
|
5974
|
+
await new Promise((resolve22) => setTimeout(resolve22, backoffMs[attempt]));
|
|
5899
5975
|
continue;
|
|
5900
5976
|
}
|
|
5901
5977
|
throw error;
|
|
@@ -6179,7 +6255,7 @@ async function uploadDeployAssets(options) {
|
|
|
6179
6255
|
// against the plan before it touches storage, and a stream body
|
|
6180
6256
|
// would be sent chunked with no length at all; a retry also needs a
|
|
6181
6257
|
// body it can send again.
|
|
6182
|
-
body:
|
|
6258
|
+
body: readFileSync17(asset.sourcePath)
|
|
6183
6259
|
})
|
|
6184
6260
|
);
|
|
6185
6261
|
if (!response.ok) {
|
|
@@ -6260,7 +6336,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
6260
6336
|
lastError = error;
|
|
6261
6337
|
}
|
|
6262
6338
|
if (attempt < attempts) {
|
|
6263
|
-
await new Promise((
|
|
6339
|
+
await new Promise((resolve22) => setTimeout(resolve22, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
6264
6340
|
}
|
|
6265
6341
|
}
|
|
6266
6342
|
if (lastResponse) return lastResponse;
|
|
@@ -6396,12 +6472,12 @@ var deploy_default = defineCommand3({
|
|
|
6396
6472
|
async run({ args }) {
|
|
6397
6473
|
const output = createDeployOutput(args.json === true);
|
|
6398
6474
|
preflightNodeVersion("deploy");
|
|
6399
|
-
const appDir =
|
|
6475
|
+
const appDir = resolve14(args.dir ?? ".");
|
|
6400
6476
|
const selectorRefusal = blankSelectorRefusal(args);
|
|
6401
6477
|
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
6402
6478
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
6403
6479
|
if (!hasWranglerConfig(appDir)) {
|
|
6404
|
-
output.die("
|
|
6480
|
+
output.die(noWranglerConfigMessage(join19(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
6405
6481
|
}
|
|
6406
6482
|
try {
|
|
6407
6483
|
ensureInstallReady(appDir);
|
|
@@ -6442,6 +6518,13 @@ var deploy_default = defineCommand3({
|
|
|
6442
6518
|
);
|
|
6443
6519
|
}
|
|
6444
6520
|
p12.log.info(`Id: ${appId}`);
|
|
6521
|
+
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6522
|
+
if (!sourceState.registered) {
|
|
6523
|
+
output.die(
|
|
6524
|
+
`${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\`.`,
|
|
6525
|
+
"app_not_registered"
|
|
6526
|
+
);
|
|
6527
|
+
}
|
|
6445
6528
|
const secretsCache = await loadDeploySecrets({
|
|
6446
6529
|
deployUrl: DEPLOY_URL3,
|
|
6447
6530
|
appDir,
|
|
@@ -6451,12 +6534,17 @@ var deploy_default = defineCommand3({
|
|
|
6451
6534
|
token,
|
|
6452
6535
|
output
|
|
6453
6536
|
});
|
|
6454
|
-
const
|
|
6455
|
-
if (
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
)
|
|
6537
|
+
const ownerJwtRefusal = ownerJwtMissingRefusal(sourceState);
|
|
6538
|
+
if (ownerJwtRefusal) output.die(ownerJwtRefusal.error, ownerJwtRefusal.code);
|
|
6539
|
+
let confirmRename = args.rename === true;
|
|
6540
|
+
const rename2 = pendingRename(sourceState.registeredHost, appName);
|
|
6541
|
+
if (rename2 && !confirmRename) {
|
|
6542
|
+
if (output.nonInteractive) {
|
|
6543
|
+
output.die(renameRefusalMessage(rename2), "rename_required");
|
|
6544
|
+
}
|
|
6545
|
+
const confirmed = await p12.confirm({ message: renamePromptMessage(rename2) });
|
|
6546
|
+
if (p12.isCancel(confirmed) || !confirmed) output.die("Deploy cancelled.", "rename_declined");
|
|
6547
|
+
confirmRename = true;
|
|
6460
6548
|
}
|
|
6461
6549
|
const repositoryPreflight = preflightDeployRepository({
|
|
6462
6550
|
appDir,
|
|
@@ -6502,7 +6590,7 @@ var deploy_default = defineCommand3({
|
|
|
6502
6590
|
appId,
|
|
6503
6591
|
appName,
|
|
6504
6592
|
token,
|
|
6505
|
-
rename:
|
|
6593
|
+
rename: confirmRename,
|
|
6506
6594
|
claimReleased: args["claim-released"] === true,
|
|
6507
6595
|
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6508
6596
|
bundle,
|
|
@@ -6569,6 +6657,25 @@ async function syncCommerce(appDir, appId, token, nonInteractive) {
|
|
|
6569
6657
|
await syncSubscriptionPlans(appDir, appId, token, nonInteractive);
|
|
6570
6658
|
await syncOneTimeProducts(appDir, appId, token);
|
|
6571
6659
|
}
|
|
6660
|
+
function ownerJwtMissingRefusal(state) {
|
|
6661
|
+
if (state.onBehalf?.ownerJwtLive !== false) return null;
|
|
6662
|
+
return {
|
|
6663
|
+
code: "owner_jwt_missing",
|
|
6664
|
+
error: "Cannot preserve the existing secrets: this app has no live deployment carrying an APP_OWNER_JWT. Ask the owner to redeploy."
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6667
|
+
function pendingRename(registeredHost, appName) {
|
|
6668
|
+
if (!registeredHost) return null;
|
|
6669
|
+
const [label, ...domain] = registeredHost.split(".");
|
|
6670
|
+
if (label === appName) return null;
|
|
6671
|
+
return { fromHost: registeredHost, toHost: [appName, ...domain].join(".") };
|
|
6672
|
+
}
|
|
6673
|
+
function renameRefusalMessage(rename2) {
|
|
6674
|
+
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.`;
|
|
6675
|
+
}
|
|
6676
|
+
function renamePromptMessage(rename2) {
|
|
6677
|
+
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. (Meant a separate app? Run \`deepspace app init --new-id\` instead.) Rename?`;
|
|
6678
|
+
}
|
|
6572
6679
|
function blankSelectorRefusal(args) {
|
|
6573
6680
|
if (args.env !== void 0 && !String(args.env).trim()) {
|
|
6574
6681
|
return {
|
|
@@ -6629,15 +6736,18 @@ function requireAppIdArg(explicit) {
|
|
|
6629
6736
|
function liveAppUrl(app2) {
|
|
6630
6737
|
return app2.status === "active" && app2.deployedAt !== null ? app2.url : null;
|
|
6631
6738
|
}
|
|
6632
|
-
async function
|
|
6633
|
-
const
|
|
6634
|
-
if (!Array.isArray(apps)) {
|
|
6739
|
+
async function listAppsPage(deployUrl, token) {
|
|
6740
|
+
const body = await apiFetch(deployUrl, token, "/api/apps");
|
|
6741
|
+
if (!Array.isArray(body.apps)) {
|
|
6635
6742
|
throw new InputError(
|
|
6636
6743
|
`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
6744
|
"invalid_response"
|
|
6638
6745
|
);
|
|
6639
6746
|
}
|
|
6640
|
-
return apps;
|
|
6747
|
+
return { apps: body.apps, pendingTransfers: body.pendingTransfers ?? [] };
|
|
6748
|
+
}
|
|
6749
|
+
async function listApps(deployUrl, token) {
|
|
6750
|
+
return (await listAppsPage(deployUrl, token)).apps;
|
|
6641
6751
|
}
|
|
6642
6752
|
function matchAppSelector(apps, selector) {
|
|
6643
6753
|
const byId = apps.find((app2) => app2.appId === selector);
|
|
@@ -6879,17 +6989,17 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6879
6989
|
},
|
|
6880
6990
|
async run({ args }) {
|
|
6881
6991
|
const token = await ensureToken();
|
|
6882
|
-
const apps = await
|
|
6992
|
+
const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL5, token);
|
|
6883
6993
|
if (!args.json) {
|
|
6884
|
-
if (!apps.length) {
|
|
6994
|
+
if (!apps.length && !pendingTransfers.length) {
|
|
6885
6995
|
console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
|
|
6886
|
-
} else {
|
|
6996
|
+
} else if (apps.length) {
|
|
6887
6997
|
const rows = [...apps].sort(
|
|
6888
6998
|
(left, right) => Number(right.status !== "undeployed") - Number(left.status !== "undeployed")
|
|
6889
6999
|
);
|
|
6890
7000
|
const roleOf = (a) => a.role ?? "owner";
|
|
6891
7001
|
const roleWidth = Math.max(4, ...rows.map((a) => roleOf(a).length));
|
|
6892
|
-
const nameWidth = Math.max(4, ...rows.map((a) => (a
|
|
7002
|
+
const nameWidth = Math.max(4, ...rows.map((a) => nameCell(a).length));
|
|
6893
7003
|
const statusWidth = Math.max(6, ...rows.map((a) => a.status.length));
|
|
6894
7004
|
const idWidth = Math.max(6, ...rows.map((a) => a.appId.length));
|
|
6895
7005
|
console.log(
|
|
@@ -6898,14 +7008,36 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6898
7008
|
for (const a of rows) {
|
|
6899
7009
|
const url = liveAppUrl(a) ?? "(not deployed)";
|
|
6900
7010
|
console.log(
|
|
6901
|
-
`${(a
|
|
7011
|
+
`${nameCell(a).padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${roleOf(a).padEnd(roleWidth)} ${a.appId.padEnd(idWidth)} ${url}`
|
|
6902
7012
|
);
|
|
6903
7013
|
}
|
|
7014
|
+
const reserved = rows.filter((a) => a.reservedName && a.reservedUntil);
|
|
7015
|
+
if (reserved.length) {
|
|
7016
|
+
console.log("\nReserved names return to the open pool when they expire:");
|
|
7017
|
+
for (const a of reserved) {
|
|
7018
|
+
console.log(
|
|
7019
|
+
` ${a.reservedName} \u2014 held for you until ${a.reservedUntil}${a.name ? ` (${a.appId} now serves ${a.name})` : ""}`
|
|
7020
|
+
);
|
|
7021
|
+
}
|
|
7022
|
+
}
|
|
7023
|
+
}
|
|
7024
|
+
if (pendingTransfers.length) {
|
|
7025
|
+
console.log("\nTransfer offers waiting for you:");
|
|
7026
|
+
for (const offer2 of pendingTransfers) {
|
|
7027
|
+
console.log(` ${offer2.appId} \u2014 expires ${offer2.expiresAt}`);
|
|
7028
|
+
}
|
|
7029
|
+
console.log(
|
|
7030
|
+
" Accept with `deepspace app transfer accept --app <APP ID>`; `deepspace app transfer status --app <APP ID>` names the offerer."
|
|
7031
|
+
);
|
|
6904
7032
|
}
|
|
6905
7033
|
}
|
|
6906
|
-
return { data: { apps } };
|
|
7034
|
+
return { data: { apps, pendingTransfers } };
|
|
6907
7035
|
}
|
|
6908
7036
|
});
|
|
7037
|
+
function nameCell(app2) {
|
|
7038
|
+
if (app2.name) return app2.name;
|
|
7039
|
+
return app2.reservedName ? `${app2.reservedName} (reserved)` : "\u2014";
|
|
7040
|
+
}
|
|
6909
7041
|
|
|
6910
7042
|
// src/cli/commands/logs.ts
|
|
6911
7043
|
import { defineCommand as defineCommand4 } from "citty";
|
|
@@ -7289,37 +7421,37 @@ var usage_default = defineDeepspaceCommand({
|
|
|
7289
7421
|
});
|
|
7290
7422
|
|
|
7291
7423
|
// src/cli/commands/add.ts
|
|
7292
|
-
import { existsSync as
|
|
7293
|
-
import { dirname as dirname9, join as
|
|
7424
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
|
|
7425
|
+
import { dirname as dirname9, join as join21, resolve as resolve16 } from "path";
|
|
7294
7426
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7295
7427
|
|
|
7296
7428
|
// src/cli/commands/feature-installer.ts
|
|
7297
7429
|
import {
|
|
7298
7430
|
closeSync as closeSync2,
|
|
7299
7431
|
copyFileSync,
|
|
7300
|
-
existsSync as
|
|
7432
|
+
existsSync as existsSync18,
|
|
7301
7433
|
fstatSync as fstatSync2,
|
|
7302
7434
|
mkdirSync as mkdirSync6,
|
|
7303
7435
|
mkdtempSync,
|
|
7304
7436
|
openSync as openSync2,
|
|
7305
|
-
readFileSync as
|
|
7437
|
+
readFileSync as readFileSync18,
|
|
7306
7438
|
readSync,
|
|
7307
|
-
readdirSync as
|
|
7439
|
+
readdirSync as readdirSync7,
|
|
7308
7440
|
rmSync as rmSync4,
|
|
7309
7441
|
writeFileSync as writeFileSync5
|
|
7310
7442
|
} from "fs";
|
|
7311
7443
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7312
7444
|
import { tmpdir as tmpdir2 } from "os";
|
|
7313
|
-
import { dirname as dirname8, isAbsolute as isAbsolute4, join as
|
|
7445
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, resolve as resolve15 } from "path";
|
|
7314
7446
|
import { fileURLToPath } from "url";
|
|
7315
7447
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
7316
7448
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
7317
7449
|
let dir = dirname8(fileURLToPath(moduleUrl));
|
|
7318
7450
|
for (; ; ) {
|
|
7319
|
-
const packagePath =
|
|
7320
|
-
if (
|
|
7451
|
+
const packagePath = join20(dir, "package.json");
|
|
7452
|
+
if (existsSync18(packagePath)) {
|
|
7321
7453
|
try {
|
|
7322
|
-
const pkg2 = JSON.parse(
|
|
7454
|
+
const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7323
7455
|
if (pkg2.name === "deepspace") return dir;
|
|
7324
7456
|
} catch {
|
|
7325
7457
|
}
|
|
@@ -7330,13 +7462,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
7330
7462
|
}
|
|
7331
7463
|
}
|
|
7332
7464
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
7333
|
-
return
|
|
7465
|
+
return existsSync18(join20(packageRoot, "features"));
|
|
7334
7466
|
}
|
|
7335
7467
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
7336
|
-
const configPath =
|
|
7337
|
-
if (!
|
|
7468
|
+
const configPath = join20(packageRoot, "features", id, "feature.json");
|
|
7469
|
+
if (!existsSync18(configPath)) return null;
|
|
7338
7470
|
try {
|
|
7339
|
-
const config = JSON.parse(
|
|
7471
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
7340
7472
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
7341
7473
|
return null;
|
|
7342
7474
|
}
|
|
@@ -7346,9 +7478,9 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
7346
7478
|
}
|
|
7347
7479
|
}
|
|
7348
7480
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
7349
|
-
const featuresDir =
|
|
7350
|
-
if (!
|
|
7351
|
-
return
|
|
7481
|
+
const featuresDir = join20(packageRoot, "features");
|
|
7482
|
+
if (!existsSync18(featuresDir)) return [];
|
|
7483
|
+
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
7484
|
}
|
|
7353
7485
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
7354
7486
|
return listFeatureConfigs(packageRoot).map((config) => ({
|
|
@@ -7385,7 +7517,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
7385
7517
|
return closest !== null && closestDistance <= threshold ? closest : null;
|
|
7386
7518
|
}
|
|
7387
7519
|
function resolveTargetDir(raw) {
|
|
7388
|
-
return isAbsolute4(raw) ? raw :
|
|
7520
|
+
return isAbsolute4(raw) ? raw : resolve15(raw);
|
|
7389
7521
|
}
|
|
7390
7522
|
function resolveFeatureDestPath(fileDest, route) {
|
|
7391
7523
|
const pages = "src/pages/";
|
|
@@ -7401,7 +7533,7 @@ function copyFile(src, dest) {
|
|
|
7401
7533
|
function copyFileWithImportShift(src, dest, levels) {
|
|
7402
7534
|
mkdirSync6(dirname8(dest), { recursive: true });
|
|
7403
7535
|
const prefix = "../".repeat(levels);
|
|
7404
|
-
const shifted =
|
|
7536
|
+
const shifted = readFileSync18(src, "utf-8").replace(
|
|
7405
7537
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
7406
7538
|
`$1$2${prefix}$3`
|
|
7407
7539
|
);
|
|
@@ -7414,12 +7546,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
7414
7546
|
const preexisting = [];
|
|
7415
7547
|
const missing = [];
|
|
7416
7548
|
for (const file of config.files) {
|
|
7417
|
-
const srcPath =
|
|
7549
|
+
const srcPath = join20(packageRoot, "features", config.id, file.src);
|
|
7418
7550
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
7419
|
-
const destPath =
|
|
7420
|
-
const existed =
|
|
7551
|
+
const destPath = join20(targetDir, resolvedDest);
|
|
7552
|
+
const existed = existsSync18(destPath);
|
|
7421
7553
|
if (existed) preexisting.push(resolvedDest);
|
|
7422
|
-
if (!
|
|
7554
|
+
if (!existsSync18(srcPath)) {
|
|
7423
7555
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
7424
7556
|
if (!existed) missing.push(resolvedDest);
|
|
7425
7557
|
continue;
|
|
@@ -7456,11 +7588,11 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
7456
7588
|
}
|
|
7457
7589
|
function integrateSchema(config, targetDir, emit) {
|
|
7458
7590
|
if (!config.schema) return [];
|
|
7459
|
-
const schemasPath =
|
|
7460
|
-
if (!
|
|
7591
|
+
const schemasPath = join20(targetDir, "src", "schemas.ts");
|
|
7592
|
+
if (!existsSync18(schemasPath)) {
|
|
7461
7593
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
7462
7594
|
}
|
|
7463
|
-
let content =
|
|
7595
|
+
let content = readFileSync18(schemasPath, "utf-8");
|
|
7464
7596
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
7465
7597
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
7466
7598
|
if (importPattern.test(content)) {
|
|
@@ -7513,11 +7645,11 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
7513
7645
|
}
|
|
7514
7646
|
function integrateActions(config, targetDir, emit) {
|
|
7515
7647
|
if (!config.actions?.length) return [];
|
|
7516
|
-
const actionsPath =
|
|
7517
|
-
if (!
|
|
7648
|
+
const actionsPath = join20(targetDir, "src", "actions", "index.ts");
|
|
7649
|
+
if (!existsSync18(actionsPath)) {
|
|
7518
7650
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
7519
7651
|
}
|
|
7520
|
-
let content =
|
|
7652
|
+
let content = readFileSync18(actionsPath, "utf-8");
|
|
7521
7653
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
7522
7654
|
if (!registryPattern.test(content)) {
|
|
7523
7655
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -7563,8 +7695,8 @@ ${registryEntry}`);
|
|
|
7563
7695
|
function integrateCode(config, targetDir, emit) {
|
|
7564
7696
|
const unresolved = [];
|
|
7565
7697
|
for (const insertion of config.code ?? []) {
|
|
7566
|
-
const targetPath =
|
|
7567
|
-
if (!
|
|
7698
|
+
const targetPath = join20(targetDir, insertion.file);
|
|
7699
|
+
if (!existsSync18(targetPath)) {
|
|
7568
7700
|
unresolved.push({
|
|
7569
7701
|
area: "code",
|
|
7570
7702
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7572,7 +7704,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7572
7704
|
});
|
|
7573
7705
|
continue;
|
|
7574
7706
|
}
|
|
7575
|
-
const original =
|
|
7707
|
+
const original = readFileSync18(targetPath, "utf-8");
|
|
7576
7708
|
if (original.includes(insertion.insert.trim())) {
|
|
7577
7709
|
emit(` Integration already present: ${insertion.file}`);
|
|
7578
7710
|
continue;
|
|
@@ -7609,8 +7741,8 @@ ${content.slice(
|
|
|
7609
7741
|
}
|
|
7610
7742
|
function integrateIgnore(config, targetDir) {
|
|
7611
7743
|
if (!config.ignore?.length) return;
|
|
7612
|
-
const targetPath =
|
|
7613
|
-
const current =
|
|
7744
|
+
const targetPath = join20(targetDir, ".gitignore");
|
|
7745
|
+
const current = existsSync18(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
|
|
7614
7746
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7615
7747
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7616
7748
|
if (missing.length === 0) return;
|
|
@@ -7622,15 +7754,15 @@ function integrateWrangler(config, targetDir, emit) {
|
|
|
7622
7754
|
const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
|
|
7623
7755
|
const rateLimits = config.wrangler?.rateLimits ?? [];
|
|
7624
7756
|
if (!runWorkerFirst && rateLimits.length === 0) return [];
|
|
7625
|
-
const targetPath =
|
|
7757
|
+
const targetPath = join20(targetDir, "wrangler.toml");
|
|
7626
7758
|
const steps = [
|
|
7627
7759
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7628
7760
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7629
7761
|
];
|
|
7630
|
-
if (!
|
|
7762
|
+
if (!existsSync18(targetPath)) {
|
|
7631
7763
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7632
7764
|
}
|
|
7633
|
-
let source =
|
|
7765
|
+
let source = readFileSync18(targetPath, "utf-8");
|
|
7634
7766
|
if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
|
|
7635
7767
|
return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
|
|
7636
7768
|
}
|
|
@@ -7706,8 +7838,8 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7706
7838
|
}
|
|
7707
7839
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7708
7840
|
if (!config.css?.length) return [];
|
|
7709
|
-
const stylesPath =
|
|
7710
|
-
if (!
|
|
7841
|
+
const stylesPath = join20(targetDir, "src", "styles.css");
|
|
7842
|
+
if (!existsSync18(stylesPath)) {
|
|
7711
7843
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7712
7844
|
return [
|
|
7713
7845
|
{
|
|
@@ -7719,12 +7851,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7719
7851
|
}
|
|
7720
7852
|
];
|
|
7721
7853
|
}
|
|
7722
|
-
let styles =
|
|
7854
|
+
let styles = readFileSync18(stylesPath, "utf-8");
|
|
7723
7855
|
let integrated = 0;
|
|
7724
7856
|
const unresolved = [];
|
|
7725
7857
|
for (const cssFile of config.css) {
|
|
7726
|
-
const srcPath =
|
|
7727
|
-
if (!
|
|
7858
|
+
const srcPath = join20(packageRoot, "features", config.id, cssFile);
|
|
7859
|
+
if (!existsSync18(srcPath)) {
|
|
7728
7860
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7729
7861
|
unresolved.push({
|
|
7730
7862
|
area: "css",
|
|
@@ -7733,7 +7865,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7733
7865
|
});
|
|
7734
7866
|
continue;
|
|
7735
7867
|
}
|
|
7736
|
-
const css =
|
|
7868
|
+
const css = readFileSync18(srcPath, "utf-8");
|
|
7737
7869
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
7738
7870
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
7739
7871
|
if (marker && styles.includes(marker)) {
|
|
@@ -7767,11 +7899,11 @@ function routeRolesSuffix(route) {
|
|
|
7767
7899
|
}
|
|
7768
7900
|
function integrateRoute(config, targetDir, emit) {
|
|
7769
7901
|
if (!config.route) return [];
|
|
7770
|
-
const navPath =
|
|
7771
|
-
if (!
|
|
7902
|
+
const navPath = join20(targetDir, "src", "nav.ts");
|
|
7903
|
+
if (!existsSync18(navPath)) {
|
|
7772
7904
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7773
7905
|
}
|
|
7774
|
-
let content =
|
|
7906
|
+
let content = readFileSync18(navPath, "utf-8");
|
|
7775
7907
|
const routePath = config.route.path;
|
|
7776
7908
|
if (content.includes(`'${routePath}'`)) {
|
|
7777
7909
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -7818,10 +7950,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
7818
7950
|
const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
|
|
7819
7951
|
return { error: child.error, status: child.status };
|
|
7820
7952
|
}
|
|
7821
|
-
const captureDir = mkdtempSync(
|
|
7953
|
+
const captureDir = mkdtempSync(join20(tmpdir2(), "deepspace-add-install-"));
|
|
7822
7954
|
let outputFd;
|
|
7823
7955
|
try {
|
|
7824
|
-
outputFd = openSync2(
|
|
7956
|
+
outputFd = openSync2(join20(captureDir, "output.log"), "w+");
|
|
7825
7957
|
const child = spawnSync6(packageManager, ["install"], {
|
|
7826
7958
|
...options,
|
|
7827
7959
|
stdio: ["ignore", outputFd, outputFd]
|
|
@@ -7845,14 +7977,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7845
7977
|
if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
|
|
7846
7978
|
return { added: [], installRan: false, packageManager };
|
|
7847
7979
|
}
|
|
7848
|
-
const packagePath =
|
|
7849
|
-
if (!
|
|
7980
|
+
const packagePath = join20(targetDir, "package.json");
|
|
7981
|
+
if (!existsSync18(packagePath)) {
|
|
7850
7982
|
emit(" Could not patch package.json (file not found)");
|
|
7851
7983
|
return { added: [], installRan: false, packageManager };
|
|
7852
7984
|
}
|
|
7853
7985
|
let pkg2;
|
|
7854
7986
|
try {
|
|
7855
|
-
pkg2 = JSON.parse(
|
|
7987
|
+
pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7856
7988
|
} catch (error) {
|
|
7857
7989
|
emit(
|
|
7858
7990
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -7912,7 +8044,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7912
8044
|
}
|
|
7913
8045
|
function dependencySnapshot(targetDir) {
|
|
7914
8046
|
try {
|
|
7915
|
-
const pkg2 = JSON.parse(
|
|
8047
|
+
const pkg2 = JSON.parse(readFileSync18(join20(targetDir, "package.json"), "utf-8"));
|
|
7916
8048
|
return {
|
|
7917
8049
|
dependencies: pkg2.dependencies ?? {},
|
|
7918
8050
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7924,7 +8056,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7924
8056
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7925
8057
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7926
8058
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7927
|
-
return
|
|
8059
|
+
return existsSync18(join20(targetDir, signature));
|
|
7928
8060
|
}).map((feature) => feature.id);
|
|
7929
8061
|
}
|
|
7930
8062
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -8157,11 +8289,11 @@ function renderFeatureInfo(config) {
|
|
|
8157
8289
|
return lines.join("\n");
|
|
8158
8290
|
}
|
|
8159
8291
|
function isDeepSpaceApp(dir) {
|
|
8160
|
-
if (
|
|
8161
|
-
const packagePath =
|
|
8162
|
-
if (!
|
|
8292
|
+
if (existsSync19(join21(dir, "wrangler.toml"))) return true;
|
|
8293
|
+
const packagePath = join21(dir, "package.json");
|
|
8294
|
+
if (!existsSync19(packagePath)) return false;
|
|
8163
8295
|
try {
|
|
8164
|
-
const pkg2 = JSON.parse(
|
|
8296
|
+
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
8165
8297
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
8166
8298
|
} catch {
|
|
8167
8299
|
return false;
|
|
@@ -8170,8 +8302,8 @@ function isDeepSpaceApp(dir) {
|
|
|
8170
8302
|
function installedPackageRoot(fromDir) {
|
|
8171
8303
|
let dir = fromDir;
|
|
8172
8304
|
for (; ; ) {
|
|
8173
|
-
const candidate =
|
|
8174
|
-
if (
|
|
8305
|
+
const candidate = join21(dir, "node_modules", "deepspace");
|
|
8306
|
+
if (existsSync19(join21(candidate, "package.json"))) return candidate;
|
|
8175
8307
|
const parent = dirname9(dir);
|
|
8176
8308
|
if (parent === dir) return null;
|
|
8177
8309
|
dir = parent;
|
|
@@ -8188,9 +8320,9 @@ async function targetInstaller(appDir) {
|
|
|
8188
8320
|
if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
|
|
8189
8321
|
return { installFeature };
|
|
8190
8322
|
}
|
|
8191
|
-
const entry =
|
|
8192
|
-
const catalog =
|
|
8193
|
-
if (!
|
|
8323
|
+
const entry = join21(installedRoot, "dist", "feature-installer.js");
|
|
8324
|
+
const catalog = join21(installedRoot, "features");
|
|
8325
|
+
if (!existsSync19(entry) || !existsSync19(catalog)) {
|
|
8194
8326
|
throw new Refusal(
|
|
8195
8327
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
8196
8328
|
Expected: ${entry}
|
|
@@ -8282,8 +8414,8 @@ var add_default = defineDeepspaceCommand({
|
|
|
8282
8414
|
}
|
|
8283
8415
|
return { data: { features: featureCatalog(root) } };
|
|
8284
8416
|
}
|
|
8285
|
-
const appDir =
|
|
8286
|
-
if (!
|
|
8417
|
+
const appDir = resolve16(args.dir ?? ".");
|
|
8418
|
+
if (!existsSync19(appDir)) {
|
|
8287
8419
|
throw new Refusal(
|
|
8288
8420
|
`Target directory not found: ${appDir}
|
|
8289
8421
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -8363,11 +8495,11 @@ function fmtDate(iso) {
|
|
|
8363
8495
|
if (!iso) return "\u2014";
|
|
8364
8496
|
return iso.slice(0, 10);
|
|
8365
8497
|
}
|
|
8366
|
-
async function
|
|
8498
|
+
async function confirm5(message) {
|
|
8367
8499
|
process.stdout.write(`${message} [y/N] `);
|
|
8368
|
-
const answer = await new Promise((
|
|
8500
|
+
const answer = await new Promise((resolve22) => {
|
|
8369
8501
|
process.stdin.setEncoding("utf-8");
|
|
8370
|
-
process.stdin.once("data", (data) =>
|
|
8502
|
+
process.stdin.once("data", (data) => resolve22(data.toString().trim().toLowerCase()));
|
|
8371
8503
|
});
|
|
8372
8504
|
process.stdin.pause();
|
|
8373
8505
|
return answer === "y" || answer === "yes";
|
|
@@ -8442,7 +8574,7 @@ var buy = defineDeepspaceCommand({
|
|
|
8442
8574
|
"confirmation_required"
|
|
8443
8575
|
);
|
|
8444
8576
|
}
|
|
8445
|
-
const ok2 = await
|
|
8577
|
+
const ok2 = await confirm5(
|
|
8446
8578
|
`Buy ${domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appId}? Provisioning takes ${expectedTime}.`
|
|
8447
8579
|
);
|
|
8448
8580
|
if (!ok2) {
|
|
@@ -8612,7 +8744,7 @@ var detach = defineDeepspaceCommand({
|
|
|
8612
8744
|
"confirmation_required"
|
|
8613
8745
|
);
|
|
8614
8746
|
}
|
|
8615
|
-
const ok2 = await
|
|
8747
|
+
const ok2 = await confirm5(
|
|
8616
8748
|
`Detach ${domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
8617
8749
|
);
|
|
8618
8750
|
if (!ok2) {
|
|
@@ -8658,8 +8790,8 @@ var domain_default = defineCommand5({
|
|
|
8658
8790
|
|
|
8659
8791
|
// src/cli/commands/app-files.ts
|
|
8660
8792
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8661
|
-
import { basename as basename4, resolve as
|
|
8662
|
-
import { existsSync as
|
|
8793
|
+
import { basename as basename4, resolve as resolve17 } from "path";
|
|
8794
|
+
import { existsSync as existsSync20, statSync as statSync2 } from "fs";
|
|
8663
8795
|
|
|
8664
8796
|
// src/cli/lib/app-files-api.ts
|
|
8665
8797
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -8937,8 +9069,8 @@ var put = defineDeepspaceCommand({
|
|
|
8937
9069
|
app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
|
|
8938
9070
|
},
|
|
8939
9071
|
async run({ args }) {
|
|
8940
|
-
const localPath =
|
|
8941
|
-
if (!
|
|
9072
|
+
const localPath = resolve17(String(args.file));
|
|
9073
|
+
if (!existsSync20(localPath) || !statSync2(localPath).isFile()) {
|
|
8942
9074
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
8943
9075
|
}
|
|
8944
9076
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -9038,7 +9170,7 @@ var get = defineDeepspaceCommand({
|
|
|
9038
9170
|
},
|
|
9039
9171
|
async run({ args }) {
|
|
9040
9172
|
const key = requireKey(String(args.key));
|
|
9041
|
-
const destination =
|
|
9173
|
+
const destination = resolve17(String(args.out ?? basename4(key)));
|
|
9042
9174
|
const { token, appId } = await target(args.app);
|
|
9043
9175
|
const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
|
|
9044
9176
|
if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
|
|
@@ -9118,7 +9250,10 @@ var list4 = defineDeepspaceCommand({
|
|
|
9118
9250
|
}
|
|
9119
9251
|
});
|
|
9120
9252
|
var add = defineDeepspaceCommand({
|
|
9121
|
-
meta: {
|
|
9253
|
+
meta: {
|
|
9254
|
+
name: "add",
|
|
9255
|
+
description: "Authorize someone to deploy your app \u2014 a collaborator can also read and change every app secret (plaintext); they cannot undeploy or transfer"
|
|
9256
|
+
},
|
|
9122
9257
|
args: {
|
|
9123
9258
|
email: {
|
|
9124
9259
|
type: "positional",
|
|
@@ -9328,7 +9463,11 @@ var offer = defineDeepspaceCommand({
|
|
|
9328
9463
|
if (replaced) console.log(`\u25B2 Replaced the pending offer to ${pendingTo}.`);
|
|
9329
9464
|
console.log(
|
|
9330
9465
|
`\u2713 Offered ${app2} to ${email} (expires ${expiresAt}).
|
|
9331
|
-
They accept with: deepspace app transfer accept --app ${app2}
|
|
9466
|
+
They accept with: deepspace app transfer accept --app ${app2}
|
|
9467
|
+
(it also shows in their \`deepspace app list\`)
|
|
9468
|
+
\u25B2 On acceptance you lose ALL access to ${app2} \u2014 you are not kept on as a
|
|
9469
|
+
collaborator. Only the new owner can add you back. Collaborators you added
|
|
9470
|
+
stay on the app for the new owner.`
|
|
9332
9471
|
);
|
|
9333
9472
|
}
|
|
9334
9473
|
return {
|
|
@@ -9355,7 +9494,9 @@ var status2 = defineDeepspaceCommand({
|
|
|
9355
9494
|
return { data: { app: app2, transfer: null } };
|
|
9356
9495
|
}
|
|
9357
9496
|
if (!args.json) {
|
|
9358
|
-
console.log(
|
|
9497
|
+
console.log(
|
|
9498
|
+
`Pending: ${app2} \u2014 from ${transfer.fromEmailDisplay} to ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`
|
|
9499
|
+
);
|
|
9359
9500
|
}
|
|
9360
9501
|
return { data: { app: app2, transfer } };
|
|
9361
9502
|
}
|
|
@@ -9375,15 +9516,32 @@ var accept2 = defineDeepspaceCommand({
|
|
|
9375
9516
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9376
9517
|
throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
|
|
9377
9518
|
}
|
|
9378
|
-
await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
|
|
9519
|
+
const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
|
|
9379
9520
|
method: "POST"
|
|
9380
9521
|
});
|
|
9381
9522
|
if (!args.json) {
|
|
9382
9523
|
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.`
|
|
9524
|
+
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
9525
|
);
|
|
9526
|
+
if (!replayed && inheritedCollaborators === void 0) {
|
|
9527
|
+
console.log(
|
|
9528
|
+
`\u25B2 Could not list collaborators the previous owner may have left on the app \u2014 review with \`deepspace app collaborators list --app ${app2}\`.`
|
|
9529
|
+
);
|
|
9530
|
+
} else if (!replayed && inheritedCollaborators && inheritedCollaborators.length > 0) {
|
|
9531
|
+
console.log(
|
|
9532
|
+
`\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}\`.`
|
|
9533
|
+
);
|
|
9534
|
+
}
|
|
9385
9535
|
}
|
|
9386
|
-
return {
|
|
9536
|
+
return {
|
|
9537
|
+
data: {
|
|
9538
|
+
app: app2,
|
|
9539
|
+
accepted: !replayed,
|
|
9540
|
+
replayed: replayed === true,
|
|
9541
|
+
...acceptedAt ? { acceptedAt } : {},
|
|
9542
|
+
...inheritedCollaborators === void 0 ? {} : { inheritedCollaborators }
|
|
9543
|
+
}
|
|
9544
|
+
};
|
|
9387
9545
|
}
|
|
9388
9546
|
});
|
|
9389
9547
|
var cancel4 = defineDeepspaceCommand({
|
|
@@ -9408,7 +9566,7 @@ var transfer_default = defineCommand8({
|
|
|
9408
9566
|
import { defineCommand as defineCommand9 } from "citty";
|
|
9409
9567
|
|
|
9410
9568
|
// src/cli/commands/_invoke-impl.ts
|
|
9411
|
-
import { readFileSync as
|
|
9569
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
9412
9570
|
import * as p15 from "@clack/prompts";
|
|
9413
9571
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
9414
9572
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -9449,7 +9607,7 @@ function resolveBody(opts) {
|
|
|
9449
9607
|
if (opts.body != null) {
|
|
9450
9608
|
raw = opts.body;
|
|
9451
9609
|
} else if (opts.bodyFile != null) {
|
|
9452
|
-
raw = opts.bodyFile === "-" ?
|
|
9610
|
+
raw = opts.bodyFile === "-" ? readFileSync20(0, "utf-8") : readFileSync20(opts.bodyFile, "utf-8");
|
|
9453
9611
|
} else {
|
|
9454
9612
|
return "{}";
|
|
9455
9613
|
}
|
|
@@ -9743,8 +9901,8 @@ var integrations_default = defineCommand9({
|
|
|
9743
9901
|
|
|
9744
9902
|
// src/cli/commands/feedback.ts
|
|
9745
9903
|
import * as p16 from "@clack/prompts";
|
|
9746
|
-
import { existsSync as
|
|
9747
|
-
import { dirname as dirname10, join as
|
|
9904
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
9905
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
9748
9906
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9749
9907
|
import { release } from "os";
|
|
9750
9908
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -9767,12 +9925,12 @@ function readCliVersion() {
|
|
|
9767
9925
|
try {
|
|
9768
9926
|
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
9769
9927
|
const candidates = [
|
|
9770
|
-
|
|
9771
|
-
|
|
9928
|
+
join22(here, "..", "package.json"),
|
|
9929
|
+
join22(here, "..", "..", "..", "package.json")
|
|
9772
9930
|
];
|
|
9773
9931
|
for (const path of candidates) {
|
|
9774
|
-
if (!
|
|
9775
|
-
const pkg2 = JSON.parse(
|
|
9932
|
+
if (!existsSync21(path)) continue;
|
|
9933
|
+
const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
|
|
9776
9934
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
9777
9935
|
}
|
|
9778
9936
|
} catch {
|
|
@@ -9968,7 +10126,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
9968
10126
|
|
|
9969
10127
|
// src/cli/commands/secrets.ts
|
|
9970
10128
|
import { defineCommand as defineCommand10 } from "citty";
|
|
9971
|
-
import { readFileSync as
|
|
10129
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
9972
10130
|
|
|
9973
10131
|
// src/cli/lib/citty-args.ts
|
|
9974
10132
|
function dedupePositionals(bound, rest) {
|
|
@@ -10070,6 +10228,7 @@ function warnIfDebugRoutesEnabled(secrets) {
|
|
|
10070
10228
|
return true;
|
|
10071
10229
|
}
|
|
10072
10230
|
var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
|
|
10231
|
+
var APPLIES_AT_DEPLOY = { appliesAtDeploy: true };
|
|
10073
10232
|
var LOCAL_DEV_HINT = "Running `deepspace dev start`? Restart it to load the change locally.";
|
|
10074
10233
|
var list6 = defineCommand10({
|
|
10075
10234
|
meta: { name: "list", description: "List masked secrets in a config" },
|
|
@@ -10136,7 +10295,7 @@ var set = defineCommand10({
|
|
|
10136
10295
|
const n = Object.keys(secrets).length;
|
|
10137
10296
|
ok(
|
|
10138
10297
|
args.json === true,
|
|
10139
|
-
{ appId: t.appId, config: t.configName, set: Object.keys(secrets) },
|
|
10298
|
+
{ appId: t.appId, config: t.configName, set: Object.keys(secrets), ...APPLIES_AT_DEPLOY },
|
|
10140
10299
|
() => {
|
|
10141
10300
|
console.log(`Set ${n} secret${n === 1 ? "" : "s"} in ${t.configName}.`);
|
|
10142
10301
|
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
@@ -10209,7 +10368,7 @@ var del2 = defineCommand10({
|
|
|
10209
10368
|
}
|
|
10210
10369
|
}
|
|
10211
10370
|
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
10212
|
-
ok(args.json === true, { appId: t.appId, config: t.configName, deleted, absent }, () => {
|
|
10371
|
+
ok(args.json === true, { appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY }, () => {
|
|
10213
10372
|
console.log(
|
|
10214
10373
|
`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
|
|
10215
10374
|
);
|
|
@@ -10238,7 +10397,7 @@ var upload = defineCommand10({
|
|
|
10238
10397
|
async run({ args }) {
|
|
10239
10398
|
try {
|
|
10240
10399
|
const t = await resolveTarget(args);
|
|
10241
|
-
const content = args.file === "-" ?
|
|
10400
|
+
const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
|
|
10242
10401
|
const secrets = parseSecretsUpload(content);
|
|
10243
10402
|
if (Object.keys(secrets).length === 0)
|
|
10244
10403
|
throw new InputError("No secrets found in the input.", "empty_input");
|
|
@@ -10246,7 +10405,7 @@ var upload = defineCommand10({
|
|
|
10246
10405
|
const uploaded = Object.keys(secrets);
|
|
10247
10406
|
ok(
|
|
10248
10407
|
args.json === true,
|
|
10249
|
-
{ appId: t.appId, config: t.configName, uploaded, replaced: args.replace === true },
|
|
10408
|
+
{ appId: t.appId, config: t.configName, uploaded, replaced: args.replace === true, ...APPLIES_AT_DEPLOY },
|
|
10250
10409
|
() => {
|
|
10251
10410
|
console.log(`Uploaded ${uploaded.length} secrets to ${t.configName}.`);
|
|
10252
10411
|
if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
|
|
@@ -10421,12 +10580,19 @@ var secrets_default = defineCommand10({
|
|
|
10421
10580
|
});
|
|
10422
10581
|
|
|
10423
10582
|
// src/cli/commands/init.ts
|
|
10424
|
-
import { existsSync as
|
|
10425
|
-
import { join as
|
|
10583
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10584
|
+
import { join as join23 } from "path";
|
|
10426
10585
|
var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
10586
|
+
function wranglerConfigUncommitted(appDir) {
|
|
10587
|
+
if (!existsSync22(join23(appDir, ".git"))) return false;
|
|
10588
|
+
const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
|
|
10589
|
+
allowFail: true
|
|
10590
|
+
});
|
|
10591
|
+
return dryRun.status === 0;
|
|
10592
|
+
}
|
|
10427
10593
|
function commitScaffoldIfUnborn(appDir, token) {
|
|
10428
10594
|
try {
|
|
10429
|
-
if (!
|
|
10595
|
+
if (!existsSync22(join23(appDir, ".git"))) return false;
|
|
10430
10596
|
if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
|
|
10431
10597
|
return false;
|
|
10432
10598
|
}
|
|
@@ -10503,13 +10669,13 @@ var init_default = defineDeepspaceCommand({
|
|
|
10503
10669
|
writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
|
|
10504
10670
|
const committedScaffold = commitScaffoldIfUnborn(appDir, token);
|
|
10505
10671
|
if (!args.json) {
|
|
10672
|
+
const envSuffix = envName ? ` (env: ${envName})` : "";
|
|
10673
|
+
const commitNote = committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.";
|
|
10506
10674
|
if (existing) {
|
|
10507
|
-
console.log(`Forked: ${existing} \u2192 ${appId}${
|
|
10675
|
+
console.log(`Forked: ${existing} \u2192 ${appId}${envSuffix}${commitNote}`);
|
|
10508
10676
|
console.log("Registered as a NEW app under your account; the original is untouched.");
|
|
10509
10677
|
} else {
|
|
10510
|
-
console.log(
|
|
10511
|
-
`Registered ${appId}${envName ? ` (env: ${envName})` : ""} to your account` + (committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.")
|
|
10512
|
-
);
|
|
10678
|
+
console.log(`Registered ${appId}${envSuffix} to your account${commitNote}`);
|
|
10513
10679
|
console.log("The first deploy claims the `name` subdomain.");
|
|
10514
10680
|
}
|
|
10515
10681
|
console.log(`App dir: ${appDir}`);
|
|
@@ -10522,7 +10688,18 @@ var init_default = defineDeepspaceCommand({
|
|
|
10522
10688
|
committedScaffold,
|
|
10523
10689
|
appDir,
|
|
10524
10690
|
env: envName ?? null
|
|
10525
|
-
}
|
|
10691
|
+
},
|
|
10692
|
+
// The one follow-up when the id was written into an existing history:
|
|
10693
|
+
// rendered as the `Next:` line and carried in `--json`, so a machine
|
|
10694
|
+
// caller does not have to infer it from `committedScaffold: false`.
|
|
10695
|
+
// Offered only when the file is actually uncommitted — `git commit` on
|
|
10696
|
+
// a clean file fails, and an action must be executable as handed over.
|
|
10697
|
+
...!committedScaffold && wranglerConfigUncommitted(appDir) ? {
|
|
10698
|
+
action: {
|
|
10699
|
+
cwd: appDir,
|
|
10700
|
+
argv: ["git", "commit", "-m", existing ? `Fork as ${appId}` : `Register ${appId}`, "--", "wrangler.toml"]
|
|
10701
|
+
}
|
|
10702
|
+
} : {}
|
|
10526
10703
|
};
|
|
10527
10704
|
}
|
|
10528
10705
|
});
|
|
@@ -10530,7 +10707,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
10530
10707
|
// src/cli/commands/status.ts
|
|
10531
10708
|
import { defineCommand as defineCommand11 } from "citty";
|
|
10532
10709
|
import * as p17 from "@clack/prompts";
|
|
10533
|
-
import { existsSync as
|
|
10710
|
+
import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
|
|
10534
10711
|
function timeAgo(iso) {
|
|
10535
10712
|
const ms = Date.now() - Date.parse(iso);
|
|
10536
10713
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -10615,7 +10792,7 @@ var status_default = defineCommand11({
|
|
|
10615
10792
|
json.services = urls;
|
|
10616
10793
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
10617
10794
|
}
|
|
10618
|
-
facts.loggedIn =
|
|
10795
|
+
facts.loggedIn = existsSync23(SESSION_PATH);
|
|
10619
10796
|
let userId = null;
|
|
10620
10797
|
if (!facts.loggedIn) {
|
|
10621
10798
|
lines.push(["Session", "not logged in"]);
|
|
@@ -10624,7 +10801,7 @@ var status_default = defineCommand11({
|
|
|
10624
10801
|
let who = "logged in";
|
|
10625
10802
|
try {
|
|
10626
10803
|
const payload = decodeJwtPayload(
|
|
10627
|
-
|
|
10804
|
+
readFileSync23(TOKEN_PATH, "utf-8").trim()
|
|
10628
10805
|
);
|
|
10629
10806
|
if (payload.email) who = payload.email;
|
|
10630
10807
|
userId = payload.sub ?? null;
|
|
@@ -10868,11 +11045,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
10868
11045
|
const force = args.force === true;
|
|
10869
11046
|
const appDir = findAppDir();
|
|
10870
11047
|
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
|
-
);
|
|
11048
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
10876
11049
|
assertSyncableRepo(appDir);
|
|
10877
11050
|
if (branchArg !== void 0) {
|
|
10878
11051
|
const b = branchArg.trim();
|
|
@@ -10895,9 +11068,11 @@ var push_default = defineDeepspaceCommand({
|
|
|
10895
11068
|
"unknown_branch"
|
|
10896
11069
|
);
|
|
10897
11070
|
}
|
|
11071
|
+
const uninitialized = hasWranglerConfig(appDir) && readAppId(appDir) === null;
|
|
10898
11072
|
throw new Refusal(
|
|
10899
|
-
`Branch "${branch}" has no commits yet \u2014 commit first, then push.`,
|
|
10900
|
-
"no_commits"
|
|
11073
|
+
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.`,
|
|
11074
|
+
"no_commits",
|
|
11075
|
+
uninitialized ? { action: { cwd: appDir, argv: ["deepspace", "app", "init"] } } : {}
|
|
10901
11076
|
);
|
|
10902
11077
|
}
|
|
10903
11078
|
const spinner2 = args.json ? null : createSpinner();
|
|
@@ -10961,6 +11136,13 @@ var push_default = defineDeepspaceCommand({
|
|
|
10961
11136
|
"app_not_registered"
|
|
10962
11137
|
);
|
|
10963
11138
|
}
|
|
11139
|
+
if (sourceState.source?.provider === "github") {
|
|
11140
|
+
throw new Refusal(
|
|
11141
|
+
`This app uses GitHub source (${sourceState.source.repository}). Manage commits with normal Git/GitHub; \`deepspace deploy\` ships the local working tree without changing Git.`,
|
|
11142
|
+
"source_managed_by_github",
|
|
11143
|
+
{ extra: { appId, repository: sourceState.source.repository } }
|
|
11144
|
+
);
|
|
11145
|
+
}
|
|
10964
11146
|
const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
|
|
10965
11147
|
ensureSpaceRemote(appDir, appId);
|
|
10966
11148
|
if (force) {
|
|
@@ -11100,11 +11282,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11100
11282
|
const branchArg = args.branch === void 0 ? void 0 : String(args.branch);
|
|
11101
11283
|
const appArg = typeof args.app === "string" ? args.app : void 0;
|
|
11102
11284
|
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
|
-
);
|
|
11285
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
11108
11286
|
if (branchArg !== void 0) {
|
|
11109
11287
|
const b = branchArg.trim();
|
|
11110
11288
|
if (!b) throw new Refusal("--branch was given an empty branch name.", "invalid_branch");
|
|
@@ -11322,18 +11500,18 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11322
11500
|
|
|
11323
11501
|
// src/cli/commands/clone.ts
|
|
11324
11502
|
import * as p20 from "@clack/prompts";
|
|
11325
|
-
import { existsSync as
|
|
11326
|
-
import { resolve as
|
|
11503
|
+
import { existsSync as existsSync24, readdirSync as readdirSync8, rmSync as rmSync5 } from "fs";
|
|
11504
|
+
import { resolve as resolve18, basename as basename5, join as join24 } from "path";
|
|
11327
11505
|
function cleanFailedCloneDir(dir) {
|
|
11328
11506
|
let entries;
|
|
11329
11507
|
try {
|
|
11330
|
-
entries =
|
|
11508
|
+
entries = readdirSync8(dir);
|
|
11331
11509
|
} catch {
|
|
11332
|
-
return
|
|
11510
|
+
return existsSync24(dir) ? ["<unreadable>"] : [];
|
|
11333
11511
|
}
|
|
11334
11512
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
11335
11513
|
try {
|
|
11336
|
-
rmSync5(
|
|
11514
|
+
rmSync5(join24(dir, ".git"), { recursive: true, force: true });
|
|
11337
11515
|
return [];
|
|
11338
11516
|
} catch {
|
|
11339
11517
|
return entries;
|
|
@@ -11342,10 +11520,10 @@ function cleanFailedCloneDir(dir) {
|
|
|
11342
11520
|
return entries;
|
|
11343
11521
|
}
|
|
11344
11522
|
function validateCloneTarget(dir) {
|
|
11345
|
-
if (!
|
|
11523
|
+
if (!existsSync24(dir)) return false;
|
|
11346
11524
|
let entries;
|
|
11347
11525
|
try {
|
|
11348
|
-
entries =
|
|
11526
|
+
entries = readdirSync8(dir);
|
|
11349
11527
|
} catch (error) {
|
|
11350
11528
|
if (error.code === "ENOTDIR") {
|
|
11351
11529
|
throw new Refusal(`${dir} already exists and is not a directory.`, "dir_exists");
|
|
@@ -11377,7 +11555,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
11377
11555
|
async run({ args }) {
|
|
11378
11556
|
assertAppTargetResolvable(String(args.app));
|
|
11379
11557
|
const selector = String(args.app).trim();
|
|
11380
|
-
const dir =
|
|
11558
|
+
const dir = resolve18(String(args.dir ?? selector));
|
|
11381
11559
|
const dirPreexisted = validateCloneTarget(dir);
|
|
11382
11560
|
const spinner2 = args.json ? null : createSpinner();
|
|
11383
11561
|
spinner2?.start(`Resolving ${selector}\u2026`);
|
|
@@ -11550,7 +11728,7 @@ function pickPreviousRelease(releases) {
|
|
|
11550
11728
|
var rollback_default = defineDeepspaceCommand({
|
|
11551
11729
|
meta: {
|
|
11552
11730
|
name: "rollback",
|
|
11553
|
-
description: "Re-deploy a prior release's exact bundle (no rebuild)"
|
|
11731
|
+
description: "Re-deploy a prior release's exact bundle (no rebuild); the live secrets are kept, not the release's"
|
|
11554
11732
|
},
|
|
11555
11733
|
args: {
|
|
11556
11734
|
release: {
|
|
@@ -11685,7 +11863,7 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
11685
11863
|
|
|
11686
11864
|
// src/cli/commands/workspace/new.ts
|
|
11687
11865
|
import * as p25 from "@clack/prompts";
|
|
11688
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
11866
|
+
import { isAbsolute as isAbsolute6, resolve as resolve19 } from "path";
|
|
11689
11867
|
|
|
11690
11868
|
// src/cli/commands/workspace/analysis.ts
|
|
11691
11869
|
import * as p23 from "@clack/prompts";
|
|
@@ -11819,25 +11997,25 @@ function printOverlaps(overlaps, json) {
|
|
|
11819
11997
|
// src/cli/commands/workspace/local.ts
|
|
11820
11998
|
import * as p24 from "@clack/prompts";
|
|
11821
11999
|
import {
|
|
11822
|
-
existsSync as
|
|
12000
|
+
existsSync as existsSync25,
|
|
11823
12001
|
mkdirSync as mkdirSync7,
|
|
11824
|
-
readFileSync as
|
|
12002
|
+
readFileSync as readFileSync24,
|
|
11825
12003
|
realpathSync as realpathSync6,
|
|
11826
12004
|
renameSync as renameSync2,
|
|
11827
12005
|
statSync as statSync3,
|
|
11828
12006
|
writeFileSync as writeFileSync6
|
|
11829
12007
|
} from "fs";
|
|
11830
|
-
import { dirname as dirname11, isAbsolute as isAbsolute5, join as
|
|
12008
|
+
import { dirname as dirname11, isAbsolute as isAbsolute5, join as join25, relative as relative3, sep as sep3 } from "path";
|
|
11831
12009
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11832
12010
|
function excludeWorktreeDir(appDir, dir) {
|
|
11833
12011
|
try {
|
|
11834
12012
|
ensureLocalExclude(appDir, ".deepspace/");
|
|
11835
12013
|
const realDir = realpathSync6(dir);
|
|
11836
|
-
const rel =
|
|
11837
|
-
if (!rel || rel === ".." || rel.startsWith(`..${
|
|
11838
|
-
const pattern = rel.split(
|
|
12014
|
+
const rel = relative3(realpathSync6(repoToplevel(appDir)), realDir);
|
|
12015
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute5(rel)) return;
|
|
12016
|
+
const pattern = rel.split(sep3).join("/");
|
|
11839
12017
|
if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
|
|
11840
|
-
const relApp =
|
|
12018
|
+
const relApp = relative3(realpathSync6(appDir), realDir).split(sep3).join("/");
|
|
11841
12019
|
if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
|
|
11842
12020
|
ensureLocalExclude(appDir, `/${pattern}/`);
|
|
11843
12021
|
} catch {
|
|
@@ -11848,7 +12026,7 @@ function worktreeMarkerPath(worktreeRoot) {
|
|
|
11848
12026
|
const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
|
|
11849
12027
|
if (result.status !== 0) return null;
|
|
11850
12028
|
const gitDir = result.stdout.toString("utf-8").trim();
|
|
11851
|
-
return isAbsolute5(gitDir) ?
|
|
12029
|
+
return isAbsolute5(gitDir) ? join25(gitDir, WORKSPACE_MARKER) : null;
|
|
11852
12030
|
} catch {
|
|
11853
12031
|
return null;
|
|
11854
12032
|
}
|
|
@@ -11884,7 +12062,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11884
12062
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
11885
12063
|
if (!markerPath) return false;
|
|
11886
12064
|
try {
|
|
11887
|
-
const marker = JSON.parse(
|
|
12065
|
+
const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
|
|
11888
12066
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
11889
12067
|
} catch {
|
|
11890
12068
|
return false;
|
|
@@ -11893,11 +12071,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11893
12071
|
function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
11894
12072
|
const primary = listWorktrees(appDir)[0];
|
|
11895
12073
|
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11896
|
-
return
|
|
12074
|
+
return join25(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
11897
12075
|
}
|
|
11898
12076
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
11899
|
-
const appRelative =
|
|
11900
|
-
return appRelative ?
|
|
12077
|
+
const appRelative = relative3(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
12078
|
+
return appRelative ? join25(worktreeRoot, appRelative) : worktreeRoot;
|
|
11901
12079
|
}
|
|
11902
12080
|
function primaryAppDir(appDir) {
|
|
11903
12081
|
const primary = listWorktrees(appDir)[0];
|
|
@@ -11932,7 +12110,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
|
|
|
11932
12110
|
function inOwnLinkedWorktree(appDir, id) {
|
|
11933
12111
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
11934
12112
|
try {
|
|
11935
|
-
return statSync3(
|
|
12113
|
+
return statSync3(join25(repoToplevel(appDir), ".git")).isFile();
|
|
11936
12114
|
} catch {
|
|
11937
12115
|
return false;
|
|
11938
12116
|
}
|
|
@@ -12057,7 +12235,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
12057
12235
|
}
|
|
12058
12236
|
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
12059
12237
|
out.worktreeDir = null;
|
|
12060
|
-
const leftover =
|
|
12238
|
+
const leftover = existsSync25(linked.path);
|
|
12061
12239
|
if (leftover) out.leftoverDir = linked.path;
|
|
12062
12240
|
else out.worktreeRemoved = linked.path;
|
|
12063
12241
|
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
@@ -12137,7 +12315,7 @@ async function resolveTarget2(appArg) {
|
|
|
12137
12315
|
const appDir = findAppDir();
|
|
12138
12316
|
if (!appDir) {
|
|
12139
12317
|
throw new Refusal(
|
|
12140
|
-
|
|
12318
|
+
`${noWranglerConfigMessage(process.cwd())} (Or use -a <app> for list, and for drop with an explicit ws_ id.)`,
|
|
12141
12319
|
"not_in_app_repo"
|
|
12142
12320
|
);
|
|
12143
12321
|
}
|
|
@@ -12317,7 +12495,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12317
12495
|
idempotencyKey: mintIdempotencyKey()
|
|
12318
12496
|
});
|
|
12319
12497
|
const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12320
|
-
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput :
|
|
12498
|
+
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve19(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
|
|
12321
12499
|
const dir = appDirInWorktree(appDir, worktreeRoot);
|
|
12322
12500
|
try {
|
|
12323
12501
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
|
|
@@ -12355,14 +12533,14 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12355
12533
|
|
|
12356
12534
|
// src/cli/commands/workspace/attach.ts
|
|
12357
12535
|
import * as p26 from "@clack/prompts";
|
|
12358
|
-
import { existsSync as
|
|
12359
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
12536
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
|
|
12537
|
+
import { isAbsolute as isAbsolute7, resolve as resolve20 } from "path";
|
|
12360
12538
|
function samePath(a, b) {
|
|
12361
12539
|
const real = (p35) => {
|
|
12362
12540
|
try {
|
|
12363
12541
|
return realpathSync7(p35);
|
|
12364
12542
|
} catch {
|
|
12365
|
-
return
|
|
12543
|
+
return resolve20(p35);
|
|
12366
12544
|
}
|
|
12367
12545
|
};
|
|
12368
12546
|
return real(a) === real(b);
|
|
@@ -12375,10 +12553,10 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
12375
12553
|
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
12554
|
}
|
|
12377
12555
|
function cleanFailedFreshAttachDir(dir) {
|
|
12378
|
-
if (!
|
|
12556
|
+
if (!existsSync26(dir)) return [];
|
|
12379
12557
|
let entries;
|
|
12380
12558
|
try {
|
|
12381
|
-
entries =
|
|
12559
|
+
entries = readdirSync9(dir);
|
|
12382
12560
|
} catch {
|
|
12383
12561
|
return ["<unreadable>"];
|
|
12384
12562
|
}
|
|
@@ -12452,12 +12630,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12452
12630
|
);
|
|
12453
12631
|
}
|
|
12454
12632
|
const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12455
|
-
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir :
|
|
12633
|
+
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve20(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
|
|
12456
12634
|
const dir2 = appDirInWorktree(appDir, worktreeRoot);
|
|
12457
12635
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
12458
12636
|
if (localTip) {
|
|
12459
12637
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
12460
|
-
if (existing && !
|
|
12638
|
+
if (existing && !existsSync26(existing)) {
|
|
12461
12639
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
12462
12640
|
existing = null;
|
|
12463
12641
|
}
|
|
@@ -12571,11 +12749,11 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12571
12749
|
extra: { status: view.workspace.status }
|
|
12572
12750
|
});
|
|
12573
12751
|
}
|
|
12574
|
-
const dir =
|
|
12752
|
+
const dir = resolve20(
|
|
12575
12753
|
process.cwd(),
|
|
12576
12754
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
12577
12755
|
);
|
|
12578
|
-
if (
|
|
12756
|
+
if (existsSync26(dir)) {
|
|
12579
12757
|
spinner2?.stop("Target exists.");
|
|
12580
12758
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
12581
12759
|
}
|
|
@@ -12912,13 +13090,13 @@ import * as p30 from "@clack/prompts";
|
|
|
12912
13090
|
|
|
12913
13091
|
// src/cli/lib/validation.ts
|
|
12914
13092
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12915
|
-
import { readFileSync as
|
|
12916
|
-
import { join as
|
|
13093
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
13094
|
+
import { join as join26 } from "path";
|
|
12917
13095
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12918
13096
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12919
13097
|
function configuredCommand(appDir) {
|
|
12920
13098
|
try {
|
|
12921
|
-
const pkg2 = JSON.parse(
|
|
13099
|
+
const pkg2 = JSON.parse(readFileSync25(join26(appDir, "package.json"), "utf-8"));
|
|
12922
13100
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12923
13101
|
} catch {
|
|
12924
13102
|
}
|
|
@@ -13994,13 +14172,13 @@ function sameRefs(left, right) {
|
|
|
13994
14172
|
|
|
13995
14173
|
// src/cli/commands/update.ts
|
|
13996
14174
|
import * as p34 from "@clack/prompts";
|
|
13997
|
-
import { existsSync as
|
|
13998
|
-
import { join as
|
|
14175
|
+
import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync7 } from "fs";
|
|
14176
|
+
import { join as join27, resolve as resolve21 } from "path";
|
|
13999
14177
|
|
|
14000
14178
|
// package.json
|
|
14001
14179
|
var package_default = {
|
|
14002
14180
|
name: "deepspace",
|
|
14003
|
-
version: "0.23.
|
|
14181
|
+
version: "0.23.1",
|
|
14004
14182
|
engines: {
|
|
14005
14183
|
node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
|
|
14006
14184
|
},
|
|
@@ -14175,24 +14353,25 @@ function gapIsTooWide(from, to) {
|
|
|
14175
14353
|
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
14176
14354
|
}
|
|
14177
14355
|
function readAppSdkVersion(appDir) {
|
|
14178
|
-
const installed =
|
|
14179
|
-
if (
|
|
14180
|
-
const parsed2 = JSON.parse(
|
|
14356
|
+
const installed = join27(appDir, "node_modules", "deepspace", "package.json");
|
|
14357
|
+
if (existsSync27(installed)) {
|
|
14358
|
+
const parsed2 = JSON.parse(readFileSync26(installed, "utf8"));
|
|
14181
14359
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
14182
14360
|
}
|
|
14183
|
-
const manifest =
|
|
14184
|
-
if (!
|
|
14185
|
-
const parsed = JSON.parse(
|
|
14361
|
+
const manifest = join27(appDir, "package.json");
|
|
14362
|
+
if (!existsSync27(manifest)) return null;
|
|
14363
|
+
const parsed = JSON.parse(readFileSync26(manifest, "utf8"));
|
|
14186
14364
|
return parsed.dependencies?.deepspace ?? null;
|
|
14187
14365
|
}
|
|
14188
14366
|
function pinSdkVersion(appDir, version) {
|
|
14189
|
-
const path =
|
|
14190
|
-
const source =
|
|
14367
|
+
const path = join27(appDir, "package.json");
|
|
14368
|
+
const source = readFileSync26(path, "utf8");
|
|
14191
14369
|
const manifest = JSON.parse(source);
|
|
14192
14370
|
const dependencies = manifest.dependencies;
|
|
14193
14371
|
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) return false;
|
|
14194
14372
|
let changed = false;
|
|
14195
|
-
|
|
14373
|
+
const registrySpec = (spec) => !/^(file|link|workspace|git\+|github|https?):/.test(spec) && !spec.includes("/");
|
|
14374
|
+
if (Object.prototype.hasOwnProperty.call(dependencies, "deepspace") && typeof dependencies.deepspace === "string" && registrySpec(dependencies.deepspace) && dependencies.deepspace !== `^${version}`) {
|
|
14196
14375
|
dependencies.deepspace = `^${version}`;
|
|
14197
14376
|
changed = true;
|
|
14198
14377
|
}
|
|
@@ -14213,9 +14392,9 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
|
14213
14392
|
"vite.config.mts",
|
|
14214
14393
|
"vite.config.js",
|
|
14215
14394
|
"vite.config.mjs"
|
|
14216
|
-
].find((candidate) =>
|
|
14395
|
+
].find((candidate) => existsSync27(join27(appDir, candidate)));
|
|
14217
14396
|
if (!configName) return null;
|
|
14218
|
-
const source =
|
|
14397
|
+
const source = readFileSync26(join27(appDir, configName), "utf8");
|
|
14219
14398
|
if (source.includes("deepspace-remove-build-preview-secrets") || source.includes("deepspaceBuild(") || source.includes("'deepspace/build'") || source.includes('"deepspace/build"')) {
|
|
14220
14399
|
return null;
|
|
14221
14400
|
}
|
|
@@ -14223,9 +14402,9 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
|
14223
14402
|
}
|
|
14224
14403
|
function usersSchemaVisibilityUpgradeInstruction(appDir) {
|
|
14225
14404
|
const schemaName = "src/schemas/users-schema.ts";
|
|
14226
|
-
const path =
|
|
14227
|
-
if (!
|
|
14228
|
-
const source =
|
|
14405
|
+
const path = join27(appDir, schemaName);
|
|
14406
|
+
if (!existsSync27(path)) return null;
|
|
14407
|
+
const source = readFileSync26(path, "utf8");
|
|
14229
14408
|
if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
|
|
14230
14409
|
return `${schemaName}: review the member user-row visibility change at ${USERS_SCHEMA_VISIBILITY_GUIDE}; app update left this app-owned schema unchanged.`;
|
|
14231
14410
|
}
|
|
@@ -14285,12 +14464,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
14285
14464
|
}
|
|
14286
14465
|
},
|
|
14287
14466
|
async run({ args }) {
|
|
14288
|
-
const appDir =
|
|
14467
|
+
const appDir = resolve21(".");
|
|
14289
14468
|
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
|
-
);
|
|
14469
|
+
throw new Refusal(noWranglerConfigMessage(join27(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
14294
14470
|
}
|
|
14295
14471
|
assertSyncableRepo(appDir);
|
|
14296
14472
|
const currentRaw = readAppSdkVersion(appDir);
|
|
@@ -14376,8 +14552,8 @@ var update_default = defineDeepspaceCommand({
|
|
|
14376
14552
|
|
|
14377
14553
|
// src/cli/cli.ts
|
|
14378
14554
|
var __dirname = dirname12(fileURLToPath3(import.meta.url));
|
|
14379
|
-
var pkg = JSON.parse(
|
|
14380
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
14555
|
+
var pkg = JSON.parse(readFileSync27(join28(__dirname, "..", "package.json"), "utf-8"));
|
|
14556
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync28("/etc/ssl/certs/ca-certificates.crt")) {
|
|
14381
14557
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
14382
14558
|
}
|
|
14383
14559
|
var create2 = defineCommand14({
|