deepspace 0.22.1 → 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 +29 -0
- package/dist/build.d.ts +116 -0
- package/dist/build.js +139 -0
- package/dist/build.js.map +1 -0
- package/dist/{chunk-XXOLIJB5.js → chunk-UUCAFDYW.js} +2 -2
- package/dist/{chunk-XXOLIJB5.js.map → chunk-UUCAFDYW.js.map} +1 -1
- package/dist/cli.js +978 -633
- 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 +41 -7
- package/dist/index.js +29 -29
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +6 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +134 -25
- package/dist/server.js +450 -234
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +21 -9
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +123 -14
- package/dist/worker.js +558 -342
- package/dist/worker.js.map +1 -1
- package/package.json +5 -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
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
assertOneSectionPerAppId(config, path);
|
|
1148
|
+
return config;
|
|
1149
|
+
}
|
|
1150
|
+
function readWranglerConfig(appDir) {
|
|
1151
|
+
return readWranglerConfigFile(join2(appDir, "wrangler.toml"));
|
|
1152
|
+
}
|
|
1153
|
+
function readAppIdVar(config, envName) {
|
|
1154
|
+
return (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID;
|
|
1155
|
+
}
|
|
1156
|
+
function assertOneSectionPerAppId(config, path) {
|
|
1157
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1158
|
+
const record = (section, value) => {
|
|
1159
|
+
if (typeof value !== "string" || !APP_ID_RE.test(value)) return;
|
|
1160
|
+
sections.set(value, [...sections.get(value) ?? [], section]);
|
|
1161
|
+
};
|
|
1162
|
+
record("[vars]", config.vars?.DEEPSPACE_APP_ID);
|
|
1163
|
+
for (const envName of Object.keys(config.env ?? {})) {
|
|
1164
|
+
record(`[env.${envName}.vars]`, config.env?.[envName]?.vars?.DEEPSPACE_APP_ID);
|
|
1165
|
+
}
|
|
1166
|
+
for (const [appId, where] of sections) {
|
|
1167
|
+
if (where.length < 2) continue;
|
|
1168
|
+
throw new WranglerConfigError(
|
|
1169
|
+
path,
|
|
1170
|
+
`wrangler.toml: ${where.join(" and ")} ${where.length === 2 ? "both" : "all"} set DEEPSPACE_APP_ID = "${appId}" in ${path}. One id under several sections points every environment's data, secrets, and billing at the same app while the deploys go to different places \u2014 each environment is its own app \u2014 run \`deepspace app init --env <name>\` to mint one for it.`,
|
|
1171
|
+
"duplicate_app_id"
|
|
950
1172
|
);
|
|
951
1173
|
}
|
|
952
1174
|
}
|
|
1175
|
+
function noWranglerConfigMessage(where) {
|
|
1176
|
+
return `No wrangler.toml at ${where}. Are you in a DeepSpace app directory? (Scaffold one with \`npm create deepspace\`, or cd into an existing app.)`;
|
|
1177
|
+
}
|
|
1178
|
+
function hasWranglerConfig(appDir) {
|
|
1179
|
+
return existsSync3(join2(appDir, "wrangler.toml"));
|
|
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,19 +1220,7 @@ ${header}
|
|
|
991
1220
|
${line}
|
|
992
1221
|
`;
|
|
993
1222
|
}
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
var APP_ID_PLACEHOLDER = "__APP_ID__";
|
|
997
|
-
function stampAppIdInSources(cwd, appId, previousAppId) {
|
|
998
|
-
const constantsPath = join2(resolve2(cwd), "src", "constants.ts");
|
|
999
|
-
if (!existsSync3(constantsPath)) return false;
|
|
1000
|
-
const needles = [APP_ID_PLACEHOLDER, ...previousAppId ? [previousAppId] : []];
|
|
1001
|
-
const content = readFileSync5(constantsPath, "utf-8");
|
|
1002
|
-
let next = content;
|
|
1003
|
-
for (const needle of needles) next = next.replaceAll(needle, appId);
|
|
1004
|
-
if (next === content) return false;
|
|
1005
|
-
writeFileSync2(constantsPath, next);
|
|
1006
|
-
return true;
|
|
1223
|
+
writeFileSync3(wranglerPath, src);
|
|
1007
1224
|
}
|
|
1008
1225
|
|
|
1009
1226
|
// src/cli/commands/dev.ts
|
|
@@ -1011,26 +1228,18 @@ import { resolve as resolve8, basename as basename2, join as join11 } from "path
|
|
|
1011
1228
|
import spawn2 from "cross-spawn";
|
|
1012
1229
|
|
|
1013
1230
|
// src/cli/lib/app-context.ts
|
|
1014
|
-
import { existsSync as
|
|
1015
|
-
import { dirname as dirname3, join as
|
|
1016
|
-
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";
|
|
1017
1233
|
function detectAppName(cwd = process.cwd()) {
|
|
1018
|
-
const
|
|
1019
|
-
if (!
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return typeof config.name === "string" && config.name.length > 0 ? config.name : null;
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
throw new InputError(
|
|
1025
|
-
`Could not parse ${wranglerPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1026
|
-
"invalid_config"
|
|
1027
|
-
);
|
|
1028
|
-
}
|
|
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;
|
|
1029
1238
|
}
|
|
1030
1239
|
function findAppDir(start = process.cwd()) {
|
|
1031
1240
|
let dir = resolve3(start);
|
|
1032
1241
|
for (; ; ) {
|
|
1033
|
-
if (
|
|
1242
|
+
if (existsSync5(join4(dir, "wrangler.toml"))) return dir;
|
|
1034
1243
|
const parent = dirname3(dir);
|
|
1035
1244
|
if (parent === dir) return null;
|
|
1036
1245
|
dir = parent;
|
|
@@ -1038,7 +1247,7 @@ function findAppDir(start = process.cwd()) {
|
|
|
1038
1247
|
}
|
|
1039
1248
|
function findChildApps(dir) {
|
|
1040
1249
|
try {
|
|
1041
|
-
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();
|
|
1042
1251
|
} catch {
|
|
1043
1252
|
return [];
|
|
1044
1253
|
}
|
|
@@ -1046,19 +1255,19 @@ function findChildApps(dir) {
|
|
|
1046
1255
|
|
|
1047
1256
|
// src/cli/lib/launch-config.ts
|
|
1048
1257
|
import {
|
|
1049
|
-
existsSync as
|
|
1258
|
+
existsSync as existsSync6,
|
|
1050
1259
|
readFileSync as readFileSync8,
|
|
1051
1260
|
realpathSync as realpathSync2,
|
|
1052
|
-
writeFileSync as
|
|
1261
|
+
writeFileSync as writeFileSync4,
|
|
1053
1262
|
mkdirSync as mkdirSync4,
|
|
1054
1263
|
renameSync,
|
|
1055
1264
|
unlinkSync
|
|
1056
1265
|
} from "fs";
|
|
1057
|
-
import { join as
|
|
1266
|
+
import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
|
|
1058
1267
|
|
|
1059
1268
|
// src/cli/lib/git/repository.ts
|
|
1060
1269
|
import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1061
|
-
import { dirname as dirname4, join as
|
|
1270
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
1062
1271
|
|
|
1063
1272
|
// src/cli/lib/git/process.ts
|
|
1064
1273
|
import { sync as spawnSync } from "cross-spawn";
|
|
@@ -1262,7 +1471,7 @@ function listWorktrees(cwd) {
|
|
|
1262
1471
|
}
|
|
1263
1472
|
function ensureLocalExclude(cwd, pattern) {
|
|
1264
1473
|
const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
|
|
1265
|
-
const excludePath =
|
|
1474
|
+
const excludePath = join5(commonDir, "info", "exclude");
|
|
1266
1475
|
let current = "";
|
|
1267
1476
|
try {
|
|
1268
1477
|
current = readFileSync7(excludePath, "utf-8");
|
|
@@ -1284,7 +1493,7 @@ function ensureLocalExclude(cwd, pattern) {
|
|
|
1284
1493
|
function writeLaunchFile(launchPath, config) {
|
|
1285
1494
|
mkdirSync4(dirname5(launchPath), { recursive: true });
|
|
1286
1495
|
const tmpPath = `${launchPath}.${process.pid}.tmp`;
|
|
1287
|
-
|
|
1496
|
+
writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1288
1497
|
try {
|
|
1289
1498
|
renameSync(tmpPath, launchPath);
|
|
1290
1499
|
} catch (err) {
|
|
@@ -1297,7 +1506,7 @@ function writeLaunchFile(launchPath, config) {
|
|
|
1297
1506
|
}
|
|
1298
1507
|
function readLaunchFile(launchPath) {
|
|
1299
1508
|
const empty = { version: "0.0.1", configurations: [] };
|
|
1300
|
-
if (!
|
|
1509
|
+
if (!existsSync6(launchPath)) return empty;
|
|
1301
1510
|
const raw = readFileSync8(launchPath, "utf-8").trim();
|
|
1302
1511
|
if (!raw) return empty;
|
|
1303
1512
|
const parsed = JSON.parse(raw);
|
|
@@ -1305,7 +1514,7 @@ function readLaunchFile(launchPath) {
|
|
|
1305
1514
|
return parsed;
|
|
1306
1515
|
}
|
|
1307
1516
|
function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
1308
|
-
const launchPath =
|
|
1517
|
+
const launchPath = join6(appDir, ".claude", "launch.json");
|
|
1309
1518
|
try {
|
|
1310
1519
|
const config = readLaunchFile(launchPath);
|
|
1311
1520
|
if (!config) return;
|
|
@@ -1346,7 +1555,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
|
|
|
1346
1555
|
}
|
|
1347
1556
|
function resolveAppLaunchPort(appDir) {
|
|
1348
1557
|
try {
|
|
1349
|
-
const config = readLaunchFile(
|
|
1558
|
+
const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
|
|
1350
1559
|
if (!config) return null;
|
|
1351
1560
|
const appName = detectAppName(appDir) ?? basename(appDir);
|
|
1352
1561
|
const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
|
|
@@ -1400,15 +1609,15 @@ function deriveWorktreePort(worktreeKey) {
|
|
|
1400
1609
|
return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
|
|
1401
1610
|
}
|
|
1402
1611
|
function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
|
|
1403
|
-
const launchPath =
|
|
1612
|
+
const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
|
|
1404
1613
|
const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
|
|
1405
1614
|
try {
|
|
1406
1615
|
const config = readLaunchFile(launchPath);
|
|
1407
1616
|
if (!config) return null;
|
|
1408
|
-
const worktreesRoot =
|
|
1617
|
+
const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
|
|
1409
1618
|
config.configurations = config.configurations.filter((c) => {
|
|
1410
1619
|
if (c?.name === entryName) return false;
|
|
1411
|
-
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);
|
|
1412
1621
|
return !isStaleWorktreeEntry;
|
|
1413
1622
|
});
|
|
1414
1623
|
let port = opts.port;
|
|
@@ -1443,7 +1652,7 @@ function resolveWorktreePort(appDir) {
|
|
|
1443
1652
|
const claude = detectClaudeWorktree(appDir);
|
|
1444
1653
|
if (claude) {
|
|
1445
1654
|
try {
|
|
1446
|
-
const config = readLaunchFile(
|
|
1655
|
+
const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
|
|
1447
1656
|
const entry = config?.configurations.find(
|
|
1448
1657
|
(c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
|
|
1449
1658
|
);
|
|
@@ -1467,231 +1676,41 @@ async function fetchPublicKey(authUrl) {
|
|
|
1467
1676
|
}
|
|
1468
1677
|
async function mintAppOwnerJwt(authUrl, callerJwt, appId) {
|
|
1469
1678
|
const response = await fetch(`${authUrl}/api/auth/mint-app-token`, {
|
|
1470
|
-
method: "POST",
|
|
1471
|
-
headers: {
|
|
1472
|
-
"Content-Type": "application/json",
|
|
1473
|
-
Authorization: `Bearer ${callerJwt}`
|
|
1474
|
-
},
|
|
1475
|
-
body: JSON.stringify({ appId })
|
|
1476
|
-
});
|
|
1477
|
-
if (!response.ok) {
|
|
1478
|
-
const detail = await response.text().catch(() => "");
|
|
1479
|
-
throw new Error(`Failed to mint APP_OWNER_JWT (${response.status}): ${detail}`);
|
|
1480
|
-
}
|
|
1481
|
-
const body = await response.json();
|
|
1482
|
-
if (!body.token) {
|
|
1483
|
-
throw new Error(`Auth worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1484
|
-
}
|
|
1485
|
-
return body.token;
|
|
1486
|
-
}
|
|
1487
|
-
async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
1488
|
-
const response = await fetch(
|
|
1489
|
-
`${deployUrl}/api/apps/${encodeURIComponent(appId)}/identity-token`,
|
|
1490
|
-
{
|
|
1491
|
-
method: "POST",
|
|
1492
|
-
headers: { Authorization: `Bearer ${callerJwt}` }
|
|
1493
|
-
}
|
|
1494
|
-
);
|
|
1495
|
-
if (response.status === 404) return null;
|
|
1496
|
-
if (!response.ok) {
|
|
1497
|
-
const detail = await response.text().catch(() => "");
|
|
1498
|
-
throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`);
|
|
1499
|
-
}
|
|
1500
|
-
const body = await response.json();
|
|
1501
|
-
if (!body.token) {
|
|
1502
|
-
throw new Error(`Deploy worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1503
|
-
}
|
|
1504
|
-
return body.token;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// src/cli/lib/wrangler-env.ts
|
|
1508
|
-
import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1509
|
-
import { join as join6 } from "path";
|
|
1510
|
-
import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
|
|
1511
|
-
|
|
1512
|
-
// src/server/rooms/app-name.ts
|
|
1513
|
-
var APP_NAME_RULES = {
|
|
1514
|
-
/** ^[a-z0-9](-?[a-z0-9])+$ — RFC 1035 host label, no consecutive dashes. */
|
|
1515
|
-
pattern: /^[a-z0-9](?:-?[a-z0-9])+$/,
|
|
1516
|
-
minLength: 2,
|
|
1517
|
-
maxLength: 63
|
|
1518
|
-
};
|
|
1519
|
-
function validateAppName(raw) {
|
|
1520
|
-
if (typeof raw !== "string") {
|
|
1521
|
-
return { valid: false, reason: "App name must be a string" };
|
|
1522
|
-
}
|
|
1523
|
-
const name = raw.trim();
|
|
1524
|
-
if (!name) {
|
|
1525
|
-
return { valid: false, reason: "App name is required" };
|
|
1526
|
-
}
|
|
1527
|
-
if (name.length < APP_NAME_RULES.minLength) {
|
|
1528
|
-
return { valid: false, reason: `App name "${name}" is too short (min ${APP_NAME_RULES.minLength} chars)` };
|
|
1529
|
-
}
|
|
1530
|
-
if (name.length > APP_NAME_RULES.maxLength) {
|
|
1531
|
-
return { valid: false, reason: `App name "${name}" is too long (max ${APP_NAME_RULES.maxLength} chars)` };
|
|
1532
|
-
}
|
|
1533
|
-
if (!APP_NAME_RULES.pattern.test(name)) {
|
|
1534
|
-
return {
|
|
1535
|
-
valid: false,
|
|
1536
|
-
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.`
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
return { valid: true, name };
|
|
1540
|
-
}
|
|
1541
|
-
function resolveAppName(raw) {
|
|
1542
|
-
const strict = validateAppName(raw);
|
|
1543
|
-
if (strict.valid) return { ok: true, name: strict.name };
|
|
1544
|
-
if (typeof raw !== "string") return { ok: false, reason: strict.reason };
|
|
1545
|
-
const trimmed = raw.trim();
|
|
1546
|
-
if (!trimmed) return { ok: false, reason: "App name is required" };
|
|
1547
|
-
const sanitized = sanitizeAppName(trimmed);
|
|
1548
|
-
const sanitizedStrict = validateAppName(sanitized);
|
|
1549
|
-
if (!sanitizedStrict.valid) {
|
|
1550
|
-
return {
|
|
1551
|
-
ok: false,
|
|
1552
|
-
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.`
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
return {
|
|
1556
|
-
ok: true,
|
|
1557
|
-
name: sanitized,
|
|
1558
|
-
warning: `App name "${trimmed}" doesn't conform to deploy rules \u2014 using "${sanitized}" instead. Update wrangler.toml's \`name\` to silence this warning.`
|
|
1559
|
-
};
|
|
1560
|
-
}
|
|
1561
|
-
function sanitizeAppName(raw) {
|
|
1562
|
-
return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// src/cli/lib/wrangler-env.ts
|
|
1566
|
-
var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1567
|
-
"define",
|
|
1568
|
-
"vars",
|
|
1569
|
-
"durable_objects",
|
|
1570
|
-
"kv_namespaces",
|
|
1571
|
-
"r2_buckets",
|
|
1572
|
-
"ai_search_namespaces",
|
|
1573
|
-
"ai_search",
|
|
1574
|
-
"ratelimits",
|
|
1575
|
-
"vectorize",
|
|
1576
|
-
"services",
|
|
1577
|
-
"queues",
|
|
1578
|
-
"workflows",
|
|
1579
|
-
"tail_consumers",
|
|
1580
|
-
"secrets",
|
|
1581
|
-
"secrets_store_secrets"
|
|
1582
|
-
]);
|
|
1583
|
-
var generatedConfigSeq = 0;
|
|
1584
|
-
function resolveAppNameForEnv(config, envName) {
|
|
1585
|
-
if (!envName) {
|
|
1586
|
-
return resolveAppName(config.name);
|
|
1587
|
-
}
|
|
1588
|
-
const envBlock = config.env?.[envName];
|
|
1589
|
-
if (!envBlock) {
|
|
1590
|
-
return {
|
|
1591
|
-
ok: false,
|
|
1592
|
-
reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
|
|
1596
|
-
return {
|
|
1597
|
-
ok: false,
|
|
1598
|
-
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.`
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
if (envBlock.name === config.name) {
|
|
1602
|
-
return {
|
|
1603
|
-
ok: false,
|
|
1604
|
-
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.`
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1607
|
-
return resolveAppName(envBlock.name);
|
|
1608
|
-
}
|
|
1609
|
-
function devVarsPathFor(appDir) {
|
|
1610
|
-
return join6(appDir, ".dev.vars");
|
|
1611
|
-
}
|
|
1612
|
-
function prepareWranglerEnvConfig(appDir, envName) {
|
|
1613
|
-
if (!envName) return { cleanup() {
|
|
1614
|
-
} };
|
|
1615
|
-
const config = readWranglerConfig(appDir);
|
|
1616
|
-
const resolved = resolveAppNameForEnv(config, envName);
|
|
1617
|
-
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1618
|
-
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
1619
|
-
const configPath = join6(
|
|
1620
|
-
appDir,
|
|
1621
|
-
`.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
|
|
1622
|
-
);
|
|
1623
|
-
writeFileSync4(
|
|
1624
|
-
configPath,
|
|
1625
|
-
[
|
|
1626
|
-
"# Generated by DeepSpace for this CLI run.",
|
|
1627
|
-
"# Do not edit; wrangler.toml remains the source of truth.",
|
|
1628
|
-
stringifyToml(flattened)
|
|
1629
|
-
].join("\n").replace(/\s*$/u, "\n")
|
|
1630
|
-
);
|
|
1631
|
-
return {
|
|
1632
|
-
configPath,
|
|
1633
|
-
cleanup() {
|
|
1634
|
-
rmSync2(configPath, { force: true });
|
|
1635
|
-
}
|
|
1636
|
-
};
|
|
1637
|
-
}
|
|
1638
|
-
function wranglerViteEnv(baseEnv, prepared, extraEnv = {}) {
|
|
1639
|
-
const env = { ...baseEnv, ...extraEnv };
|
|
1640
|
-
if (prepared.configPath) {
|
|
1641
|
-
delete env.CLOUDFLARE_ENV;
|
|
1642
|
-
env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath;
|
|
1643
|
-
}
|
|
1644
|
-
return env;
|
|
1645
|
-
}
|
|
1646
|
-
function flattenWranglerEnvConfig(config, envName) {
|
|
1647
|
-
const envRoot = asTomlRecord(config.env);
|
|
1648
|
-
const envBlock = asTomlRecord(envRoot?.[envName]);
|
|
1649
|
-
if (!envBlock) {
|
|
1650
|
-
throw new Error(`wrangler.toml: no [env.${envName}] block found.`);
|
|
1651
|
-
}
|
|
1652
|
-
const flattened = {};
|
|
1653
|
-
for (const [key, value] of Object.entries(config)) {
|
|
1654
|
-
if (key === "env" || NON_INHERITABLE_ENV_KEYS.has(key)) continue;
|
|
1655
|
-
flattened[key] = value;
|
|
1656
|
-
}
|
|
1657
|
-
for (const [key, value] of Object.entries(envBlock)) {
|
|
1658
|
-
flattened[key] = value;
|
|
1659
|
-
}
|
|
1660
|
-
if (!("durable_objects" in envBlock) && config.durable_objects) {
|
|
1661
|
-
flattened.durable_objects = config.durable_objects;
|
|
1679
|
+
method: "POST",
|
|
1680
|
+
headers: {
|
|
1681
|
+
"Content-Type": "application/json",
|
|
1682
|
+
Authorization: `Bearer ${callerJwt}`
|
|
1683
|
+
},
|
|
1684
|
+
body: JSON.stringify({ appId })
|
|
1685
|
+
});
|
|
1686
|
+
if (!response.ok) {
|
|
1687
|
+
const detail = await response.text().catch(() => "");
|
|
1688
|
+
throw new Error(`Failed to mint APP_OWNER_JWT (${response.status}): ${detail}`);
|
|
1662
1689
|
}
|
|
1663
|
-
const
|
|
1664
|
-
if (!
|
|
1665
|
-
|
|
1690
|
+
const body = await response.json();
|
|
1691
|
+
if (!body.token) {
|
|
1692
|
+
throw new Error(`Auth worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1666
1693
|
}
|
|
1667
|
-
return
|
|
1668
|
-
}
|
|
1669
|
-
function asTomlRecord(value) {
|
|
1670
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1671
|
-
}
|
|
1672
|
-
function safeGeneratedConfigSegment(value) {
|
|
1673
|
-
return value.replace(/[^A-Za-z0-9._-]/gu, "_");
|
|
1694
|
+
return body.token;
|
|
1674
1695
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1696
|
+
async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
|
|
1697
|
+
const response = await fetch(
|
|
1698
|
+
`${deployUrl}/api/apps/${encodeURIComponent(appId)}/identity-token`,
|
|
1699
|
+
{
|
|
1700
|
+
method: "POST",
|
|
1701
|
+
headers: { Authorization: `Bearer ${callerJwt}` }
|
|
1702
|
+
}
|
|
1703
|
+
);
|
|
1704
|
+
if (response.status === 404) return null;
|
|
1705
|
+
if (!response.ok) {
|
|
1706
|
+
const detail = await response.text().catch(() => "");
|
|
1707
|
+
throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`);
|
|
1682
1708
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const raw = readFileSync9(path, "utf-8");
|
|
1687
|
-
try {
|
|
1688
|
-
return parseToml3(raw);
|
|
1689
|
-
} catch (err) {
|
|
1690
|
-
throw new WranglerConfigError(path, err);
|
|
1709
|
+
const body = await response.json();
|
|
1710
|
+
if (!body.token) {
|
|
1711
|
+
throw new Error(`Deploy worker returned no token: ${body.error ?? "unknown error"}`);
|
|
1691
1712
|
}
|
|
1692
|
-
|
|
1693
|
-
function hasWranglerConfig(appDir) {
|
|
1694
|
-
return existsSync6(join6(appDir, "wrangler.toml"));
|
|
1713
|
+
return body.token;
|
|
1695
1714
|
}
|
|
1696
1715
|
|
|
1697
1716
|
// src/cli/lib/dev-vars.ts
|
|
@@ -1733,15 +1752,15 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
|
|
|
1733
1752
|
}
|
|
1734
1753
|
|
|
1735
1754
|
// src/cli/lib/install-status.ts
|
|
1736
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1755
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "fs";
|
|
1737
1756
|
import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
|
|
1738
1757
|
|
|
1739
1758
|
// src/cli/lib/package-manager.ts
|
|
1740
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1759
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
1741
1760
|
import { join as join7 } from "path";
|
|
1742
1761
|
function detectPackageManager(appDir) {
|
|
1743
1762
|
try {
|
|
1744
|
-
const pkg2 = JSON.parse(
|
|
1763
|
+
const pkg2 = JSON.parse(readFileSync9(join7(appDir, "package.json"), "utf-8"));
|
|
1745
1764
|
const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
|
|
1746
1765
|
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
1747
1766
|
} catch {
|
|
@@ -1779,7 +1798,7 @@ function ensureInstallReady(appDir) {
|
|
|
1779
1798
|
const donePath = join8(sentinelDir, "install.done");
|
|
1780
1799
|
const logPath = join8(sentinelDir, "install.log");
|
|
1781
1800
|
if (existsSync8(errPath)) {
|
|
1782
|
-
const detail =
|
|
1801
|
+
const detail = readFileSync10(errPath, "utf-8").trimEnd();
|
|
1783
1802
|
const log29 = existsSync8(logPath) ? ` Full log: ${logPath}.` : "";
|
|
1784
1803
|
throw new Refusal(
|
|
1785
1804
|
`Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
|
|
@@ -1835,7 +1854,7 @@ function canonicalPath2(path) {
|
|
|
1835
1854
|
function installWorkerAlive(sentinelDir) {
|
|
1836
1855
|
const pidPath = join8(sentinelDir, "install.pid");
|
|
1837
1856
|
if (!existsSync8(pidPath)) return false;
|
|
1838
|
-
const pid = Number(
|
|
1857
|
+
const pid = Number(readFileSync10(pidPath, "utf-8").trim());
|
|
1839
1858
|
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
1840
1859
|
try {
|
|
1841
1860
|
process.kill(pid, 0);
|
|
@@ -2191,11 +2210,7 @@ async function secretsApi(deployUrl, token, path, init = {}) {
|
|
|
2191
2210
|
});
|
|
2192
2211
|
const body = await res.json().catch(() => ({}));
|
|
2193
2212
|
if (!res.ok) {
|
|
2194
|
-
|
|
2195
|
-
err.status = res.status;
|
|
2196
|
-
err.code = body.code;
|
|
2197
|
-
err.apiPath = path;
|
|
2198
|
-
throw err;
|
|
2213
|
+
throw new ApiError(body.error ?? `Secrets request failed (${res.status})`, res.status, body.code, path);
|
|
2199
2214
|
}
|
|
2200
2215
|
return body;
|
|
2201
2216
|
}
|
|
@@ -2400,6 +2415,12 @@ import { join as join10 } from "path";
|
|
|
2400
2415
|
import { pathToFileURL } from "url";
|
|
2401
2416
|
|
|
2402
2417
|
// src/server/schemas/registry.ts
|
|
2418
|
+
var SYSTEM_ASSIGNED_COLUMNS = /* @__PURE__ */ new Set(["email", "name", "imageUrl", "role"]);
|
|
2419
|
+
var SYSTEM_MAINTAINED_COLUMNS = /* @__PURE__ */ new Set(["createdAt", "lastSeenAt"]);
|
|
2420
|
+
var SYSTEM_MANAGED_COLUMNS = /* @__PURE__ */ new Set([
|
|
2421
|
+
...SYSTEM_ASSIGNED_COLUMNS,
|
|
2422
|
+
...SYSTEM_MAINTAINED_COLUMNS
|
|
2423
|
+
]);
|
|
2403
2424
|
var USERS_COLUMNS = [
|
|
2404
2425
|
{ name: "email", storage: "text", interpretation: "plain" },
|
|
2405
2426
|
{ name: "name", storage: "text", interpretation: "plain" },
|
|
@@ -2452,6 +2473,23 @@ function lintSchema(schema) {
|
|
|
2452
2473
|
}
|
|
2453
2474
|
return warnings;
|
|
2454
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
|
+
}
|
|
2455
2493
|
|
|
2456
2494
|
// src/cli/lib/schema-lint.ts
|
|
2457
2495
|
async function lintProjectSchemas(appDir) {
|
|
@@ -2480,15 +2518,20 @@ async function lintProjectSchemas(appDir) {
|
|
|
2480
2518
|
return null;
|
|
2481
2519
|
}
|
|
2482
2520
|
if (!Array.isArray(schemas)) return null;
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
if (!schema || typeof schema !== "object") continue;
|
|
2521
|
+
const wellFormed = schemas.filter((schema) => {
|
|
2522
|
+
if (!schema || typeof schema !== "object") return false;
|
|
2486
2523
|
try {
|
|
2487
|
-
|
|
2524
|
+
lintSchema(schema);
|
|
2525
|
+
return true;
|
|
2488
2526
|
} catch {
|
|
2527
|
+
return false;
|
|
2489
2528
|
}
|
|
2529
|
+
});
|
|
2530
|
+
try {
|
|
2531
|
+
return lintSchemas(wellFormed);
|
|
2532
|
+
} catch {
|
|
2533
|
+
return null;
|
|
2490
2534
|
}
|
|
2491
|
-
return findings;
|
|
2492
2535
|
}
|
|
2493
2536
|
var MAX_PRINTED_FINDINGS = 5;
|
|
2494
2537
|
function formatSchemaLintFindings(findings) {
|
|
@@ -2508,11 +2551,11 @@ import { createServer, Socket } from "net";
|
|
|
2508
2551
|
var DEFAULT_PORT = 5173;
|
|
2509
2552
|
function isPortListening(port, host, timeoutMs = 500) {
|
|
2510
2553
|
const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2511
|
-
return new Promise((
|
|
2554
|
+
return new Promise((resolve22) => {
|
|
2512
2555
|
const socket = new Socket();
|
|
2513
2556
|
const done = (listening) => {
|
|
2514
2557
|
socket.destroy();
|
|
2515
|
-
|
|
2558
|
+
resolve22(listening);
|
|
2516
2559
|
};
|
|
2517
2560
|
socket.setTimeout(timeoutMs);
|
|
2518
2561
|
socket.once("connect", () => done(true));
|
|
@@ -2525,7 +2568,7 @@ async function waitForPortListening(port, host, timeoutMs, signal) {
|
|
|
2525
2568
|
const deadline = Date.now() + timeoutMs;
|
|
2526
2569
|
while (Date.now() < deadline && !signal?.aborted) {
|
|
2527
2570
|
if (await isPortListening(port, host)) return true;
|
|
2528
|
-
await new Promise((
|
|
2571
|
+
await new Promise((resolve22) => setTimeout(resolve22, 250));
|
|
2529
2572
|
}
|
|
2530
2573
|
return false;
|
|
2531
2574
|
}
|
|
@@ -2743,7 +2786,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
|
|
|
2743
2786
|
|
|
2744
2787
|
// src/cli/commands/kill.ts
|
|
2745
2788
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2746
|
-
import { readFileSync as
|
|
2789
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync3, readlinkSync } from "fs";
|
|
2747
2790
|
import { setTimeout as wait } from "timers/promises";
|
|
2748
2791
|
var SIGTERM_GRACE_MS = 1500;
|
|
2749
2792
|
var IS_WIN = process.platform === "win32";
|
|
@@ -2894,7 +2937,7 @@ function listenerPidsViaProc(port) {
|
|
|
2894
2937
|
for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
2895
2938
|
let data;
|
|
2896
2939
|
try {
|
|
2897
|
-
data =
|
|
2940
|
+
data = readFileSync11(f, "utf-8");
|
|
2898
2941
|
} catch {
|
|
2899
2942
|
continue;
|
|
2900
2943
|
}
|
|
@@ -3032,7 +3075,8 @@ function isAlive(pid) {
|
|
|
3032
3075
|
}
|
|
3033
3076
|
|
|
3034
3077
|
// src/cli/commands/test.ts
|
|
3035
|
-
import {
|
|
3078
|
+
import { readdirSync as readdirSync4 } from "fs";
|
|
3079
|
+
import { join as join13, relative, resolve as resolve9, sep as sep2 } from "path";
|
|
3036
3080
|
import { sync as spawnSync4 } from "cross-spawn";
|
|
3037
3081
|
|
|
3038
3082
|
// src/cli/lib/playwright.ts
|
|
@@ -3061,10 +3105,10 @@ function ensurePlaywright(appDir) {
|
|
|
3061
3105
|
}
|
|
3062
3106
|
|
|
3063
3107
|
// src/cli/lib/test-account-service.ts
|
|
3064
|
-
import { readFileSync as
|
|
3108
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
3065
3109
|
|
|
3066
3110
|
// src/testing/accounts.ts
|
|
3067
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as
|
|
3111
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
3068
3112
|
import { homedir as homedir2 } from "os";
|
|
3069
3113
|
import { join as join12 } from "path";
|
|
3070
3114
|
var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
|
|
@@ -3103,7 +3147,9 @@ function reconcileTestAccountScopes(store, remote, scope) {
|
|
|
3103
3147
|
};
|
|
3104
3148
|
}
|
|
3105
3149
|
function reconcileTestAccounts(local, remote) {
|
|
3106
|
-
const localById = new Map(
|
|
3150
|
+
const localById = new Map(
|
|
3151
|
+
local.filter((account) => account.id).map((account) => [account.id, account])
|
|
3152
|
+
);
|
|
3107
3153
|
return remote.flatMap((account) => {
|
|
3108
3154
|
const stored = localById.get(account.id);
|
|
3109
3155
|
return stored ? [{ ...stored, ...account }] : [];
|
|
@@ -3132,13 +3178,15 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3132
3178
|
function readTestAccountStore() {
|
|
3133
3179
|
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3134
3180
|
try {
|
|
3135
|
-
const raw = JSON.parse(
|
|
3181
|
+
const raw = JSON.parse(readFileSync12(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3136
3182
|
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
3137
3183
|
if (!raw || typeof raw !== "object") {
|
|
3138
3184
|
return { version: 2, scopes: {} };
|
|
3139
3185
|
}
|
|
3140
3186
|
const candidate = raw;
|
|
3141
|
-
const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(
|
|
3187
|
+
const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(
|
|
3188
|
+
Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])
|
|
3189
|
+
) : {};
|
|
3142
3190
|
return { version: 2, scopes };
|
|
3143
3191
|
} catch {
|
|
3144
3192
|
return { version: 2, scopes: {} };
|
|
@@ -3154,14 +3202,14 @@ function validTestAccounts(value) {
|
|
|
3154
3202
|
}
|
|
3155
3203
|
function saveTestAccountStore(store) {
|
|
3156
3204
|
mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
|
|
3157
|
-
|
|
3205
|
+
writeSecretFileSync(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2));
|
|
3158
3206
|
}
|
|
3159
3207
|
|
|
3160
3208
|
// src/cli/lib/test-account-service.ts
|
|
3161
3209
|
var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
|
|
3162
3210
|
var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
|
|
3163
3211
|
function sessionCookie() {
|
|
3164
|
-
const token =
|
|
3212
|
+
const token = readFileSync13(SESSION_PATH, "utf-8").trim();
|
|
3165
3213
|
return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
|
|
3166
3214
|
}
|
|
3167
3215
|
async function fetchRemoteTestAccounts() {
|
|
@@ -3190,6 +3238,30 @@ async function createRemoteTestAccount(input) {
|
|
|
3190
3238
|
}
|
|
3191
3239
|
return { ...data, label: data.label ?? null };
|
|
3192
3240
|
}
|
|
3241
|
+
async function recoverRemoteTestAccount(id) {
|
|
3242
|
+
const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}/credential`, {
|
|
3243
|
+
method: "POST",
|
|
3244
|
+
headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
|
|
3245
|
+
});
|
|
3246
|
+
const data = await response.json().catch(() => ({}));
|
|
3247
|
+
if (response.status === 404) {
|
|
3248
|
+
throw new Error(
|
|
3249
|
+
`No test account ${id} on this account. Run \`deepspace test accounts list\` for the current pool.`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
if (!response.ok || typeof data.password !== "string" || typeof data.email !== "string" || typeof data.userId !== "string") {
|
|
3253
|
+
throw new Error(data.error ?? "Failed to re-issue the test account credential");
|
|
3254
|
+
}
|
|
3255
|
+
return {
|
|
3256
|
+
id,
|
|
3257
|
+
email: data.email,
|
|
3258
|
+
userId: data.userId,
|
|
3259
|
+
label: data.label ?? null,
|
|
3260
|
+
name: typeof data.name === "string" ? data.name : null,
|
|
3261
|
+
createdAt: data.createdAt ?? Date.now(),
|
|
3262
|
+
password: data.password
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3193
3265
|
async function deleteRemoteTestAccount(id) {
|
|
3194
3266
|
const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
|
|
3195
3267
|
method: "DELETE",
|
|
@@ -3209,6 +3281,28 @@ async function syncTestAccountStore() {
|
|
|
3209
3281
|
// src/cli/commands/test.ts
|
|
3210
3282
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3211
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
|
+
}
|
|
3212
3306
|
function playwrightTestArgs(testFiles, flags = {}) {
|
|
3213
3307
|
return [
|
|
3214
3308
|
"playwright",
|
|
@@ -3328,6 +3422,7 @@ var test_default = defineDeepspaceCommand({
|
|
|
3328
3422
|
ensurePlaywright(appDir);
|
|
3329
3423
|
}
|
|
3330
3424
|
let exitCode = 0;
|
|
3425
|
+
let skippedSpecs = [];
|
|
3331
3426
|
switch (suite) {
|
|
3332
3427
|
case "smoke":
|
|
3333
3428
|
exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
|
|
@@ -3342,20 +3437,20 @@ var test_default = defineDeepspaceCommand({
|
|
|
3342
3437
|
if (forwarded.grep || forwarded.project || forwarded.headed) {
|
|
3343
3438
|
console.error("note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`");
|
|
3344
3439
|
}
|
|
3345
|
-
exitCode = runVitest(appDir);
|
|
3440
|
+
exitCode = runVitest(appDir, wranglerEnv);
|
|
3346
3441
|
break;
|
|
3347
3442
|
case "all":
|
|
3348
|
-
exitCode = runVitest(appDir);
|
|
3443
|
+
exitCode = runVitest(appDir, wranglerEnv);
|
|
3349
3444
|
if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
|
|
3350
3445
|
break;
|
|
3351
3446
|
case "default":
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
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
|
+
}
|
|
3359
3454
|
break;
|
|
3360
3455
|
default:
|
|
3361
3456
|
if (suite.endsWith(".spec.ts")) {
|
|
@@ -3370,13 +3465,13 @@ Available: smoke, api, e2e, unit, all`,
|
|
|
3370
3465
|
}
|
|
3371
3466
|
if (exitCode !== 0) {
|
|
3372
3467
|
throw new Refusal(`Test suite '${suite}' failed (exit ${exitCode}).`, "tests_failed", {
|
|
3373
|
-
extra: { suite, port, exitCode }
|
|
3468
|
+
extra: { suite, port, exitCode, skippedSpecs }
|
|
3374
3469
|
});
|
|
3375
3470
|
}
|
|
3376
|
-
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
|
|
3471
|
+
return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs } };
|
|
3377
3472
|
}
|
|
3378
3473
|
});
|
|
3379
|
-
function
|
|
3474
|
+
function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
|
|
3380
3475
|
let wranglerConfig;
|
|
3381
3476
|
try {
|
|
3382
3477
|
wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
|
|
@@ -3385,25 +3480,24 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv, flags) {
|
|
|
3385
3480
|
return 1;
|
|
3386
3481
|
}
|
|
3387
3482
|
try {
|
|
3388
|
-
const result = spawnSync4("npx",
|
|
3483
|
+
const result = spawnSync4("npx", argv, {
|
|
3389
3484
|
cwd: appDir,
|
|
3390
3485
|
stdio: "inherit",
|
|
3391
|
-
env: wranglerViteEnv(process.env, wranglerConfig,
|
|
3392
|
-
DEEPSPACE_PORT: String(port),
|
|
3393
|
-
PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
|
|
3394
|
-
})
|
|
3486
|
+
env: wranglerViteEnv(process.env, wranglerConfig, extraEnv)
|
|
3395
3487
|
});
|
|
3396
3488
|
return result.status ?? 1;
|
|
3397
3489
|
} finally {
|
|
3398
3490
|
wranglerConfig.cleanup();
|
|
3399
3491
|
}
|
|
3400
3492
|
}
|
|
3401
|
-
function
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3493
|
+
function runPlaywright(appDir, testFiles, port, wranglerEnv, flags = {}) {
|
|
3494
|
+
return runSuite(appDir, playwrightTestArgs(testFiles, flags), wranglerEnv, {
|
|
3495
|
+
DEEPSPACE_PORT: String(port),
|
|
3496
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
|
|
3405
3497
|
});
|
|
3406
|
-
|
|
3498
|
+
}
|
|
3499
|
+
function runVitest(appDir, wranglerEnv) {
|
|
3500
|
+
return runSuite(appDir, ["vitest", "run", "--passWithNoTests"], wranglerEnv);
|
|
3407
3501
|
}
|
|
3408
3502
|
|
|
3409
3503
|
// src/cli/commands/screenshot.ts
|
|
@@ -3741,6 +3835,107 @@ Failed:
|
|
|
3741
3835
|
return { data: { deleted: ok2, requested: targets.length, failures: [] } };
|
|
3742
3836
|
}
|
|
3743
3837
|
});
|
|
3838
|
+
var recover = defineDeepspaceCommand({
|
|
3839
|
+
meta: {
|
|
3840
|
+
name: "recover",
|
|
3841
|
+
description: "Re-issue the local credential for a test account you own"
|
|
3842
|
+
},
|
|
3843
|
+
args: {
|
|
3844
|
+
email: {
|
|
3845
|
+
type: "string",
|
|
3846
|
+
description: "Email of the test account to recover",
|
|
3847
|
+
required: false
|
|
3848
|
+
},
|
|
3849
|
+
id: {
|
|
3850
|
+
type: "string",
|
|
3851
|
+
description: "ID of the test account to recover",
|
|
3852
|
+
required: false
|
|
3853
|
+
},
|
|
3854
|
+
all: {
|
|
3855
|
+
type: "boolean",
|
|
3856
|
+
description: "Recover every pool account missing a local credential",
|
|
3857
|
+
required: false
|
|
3858
|
+
}
|
|
3859
|
+
},
|
|
3860
|
+
async run({ args }) {
|
|
3861
|
+
const selectors = [args.email, args.id, args.all].filter(Boolean).length;
|
|
3862
|
+
if (selectors === 0) {
|
|
3863
|
+
throw new Refusal("Provide --email <email>, --id <id>, or --all.", "missing_argument");
|
|
3864
|
+
}
|
|
3865
|
+
if (selectors > 1) {
|
|
3866
|
+
throw new Refusal("Provide exactly one of --email, --id, or --all.", "conflicting_arguments");
|
|
3867
|
+
}
|
|
3868
|
+
await ensureToken();
|
|
3869
|
+
let remote;
|
|
3870
|
+
try {
|
|
3871
|
+
remote = await fetchRemoteTestAccounts();
|
|
3872
|
+
} catch (err) {
|
|
3873
|
+
throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
|
|
3874
|
+
}
|
|
3875
|
+
const localAccounts = loadAllTestAccounts();
|
|
3876
|
+
const known = new Set(localAccounts.map((account) => account.id));
|
|
3877
|
+
const localNames = new Map(
|
|
3878
|
+
localAccounts.flatMap(
|
|
3879
|
+
(account) => account.name ? [[account.email, account.name]] : []
|
|
3880
|
+
)
|
|
3881
|
+
);
|
|
3882
|
+
let targets;
|
|
3883
|
+
if (args.all) {
|
|
3884
|
+
targets = remote.filter((account) => !known.has(account.id));
|
|
3885
|
+
if (targets.length === 0) {
|
|
3886
|
+
if (!args.json) console.log("Every pool account already has a local credential.");
|
|
3887
|
+
return { data: { recovered: [], alreadyLocal: remote.length } };
|
|
3888
|
+
}
|
|
3889
|
+
} else {
|
|
3890
|
+
const match = args.id ? remote.find((account) => account.id === args.id) : remote.find((account) => account.email === args.email);
|
|
3891
|
+
if (!match) {
|
|
3892
|
+
throw new Refusal(
|
|
3893
|
+
`No test account ${args.id ? `with id ${args.id}` : `with email ${args.email}`} on this account.`,
|
|
3894
|
+
"test_account_not_found",
|
|
3895
|
+
{ action: cliAction("deepspace", "test", "accounts", "list") }
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
3898
|
+
targets = [match];
|
|
3899
|
+
}
|
|
3900
|
+
const recovered = [];
|
|
3901
|
+
const failures = [];
|
|
3902
|
+
for (const target2 of targets) {
|
|
3903
|
+
try {
|
|
3904
|
+
const account = await recoverRemoteTestAccount(target2.id);
|
|
3905
|
+
const name = account.name ?? localNames.get(account.email);
|
|
3906
|
+
upsertTestAccount({
|
|
3907
|
+
id: account.id,
|
|
3908
|
+
email: account.email,
|
|
3909
|
+
password: account.password,
|
|
3910
|
+
userId: account.userId,
|
|
3911
|
+
label: account.label,
|
|
3912
|
+
...name ? { name } : {},
|
|
3913
|
+
createdAt: account.createdAt
|
|
3914
|
+
});
|
|
3915
|
+
recovered.push({ id: account.id, email: account.email });
|
|
3916
|
+
} catch (err) {
|
|
3917
|
+
failures.push({ email: target2.email, error: err.message });
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
if (failures.length > 0) {
|
|
3921
|
+
throw new Refusal(
|
|
3922
|
+
`Recovered ${recovered.length}/${targets.length} test account(s).
|
|
3923
|
+
Failed:
|
|
3924
|
+
` + failures.map((failure) => ` ${failure.email}: ${failure.error}`).join("\n"),
|
|
3925
|
+
"test_account_recover_failed",
|
|
3926
|
+
{ extra: { recovered, requested: targets.length, failures } }
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
if (!args.json) {
|
|
3930
|
+
for (const account of recovered) console.log(`Recovered ${account.email}`);
|
|
3931
|
+
console.log(
|
|
3932
|
+
`
|
|
3933
|
+
Stored in ${TEST_ACCOUNTS_PATH}. The previous password (if any machine still holds one) no longer works \u2014 recover there too.`
|
|
3934
|
+
);
|
|
3935
|
+
}
|
|
3936
|
+
return { data: { recovered, requested: targets.length, failures: [] } };
|
|
3937
|
+
}
|
|
3938
|
+
});
|
|
3744
3939
|
var test_accounts_default = defineCommand2({
|
|
3745
3940
|
meta: {
|
|
3746
3941
|
name: "test-accounts",
|
|
@@ -3750,14 +3945,15 @@ var test_accounts_default = defineCommand2({
|
|
|
3750
3945
|
create,
|
|
3751
3946
|
list,
|
|
3752
3947
|
delete: del,
|
|
3753
|
-
clear
|
|
3948
|
+
clear,
|
|
3949
|
+
recover
|
|
3754
3950
|
}
|
|
3755
3951
|
});
|
|
3756
3952
|
|
|
3757
3953
|
// src/cli/commands/deploy.ts
|
|
3758
3954
|
import * as p12 from "@clack/prompts";
|
|
3759
3955
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3760
|
-
import { resolve as
|
|
3956
|
+
import { join as join19, resolve as resolve14 } from "path";
|
|
3761
3957
|
|
|
3762
3958
|
// src/cli/lib/edge-propagation.ts
|
|
3763
3959
|
import { request as httpRequest } from "http";
|
|
@@ -3822,14 +4018,14 @@ async function servesRelease(probe, releaseStamp) {
|
|
|
3822
4018
|
}
|
|
3823
4019
|
function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
3824
4020
|
const send = url.protocol === "http:" ? httpRequest : httpsRequest;
|
|
3825
|
-
return new Promise((
|
|
4021
|
+
return new Promise((resolve22) => {
|
|
3826
4022
|
const request2 = send(
|
|
3827
4023
|
url,
|
|
3828
4024
|
{ method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
|
|
3829
4025
|
(response) => {
|
|
3830
4026
|
if (response.statusCode !== 200) {
|
|
3831
4027
|
response.resume();
|
|
3832
|
-
|
|
4028
|
+
resolve22(null);
|
|
3833
4029
|
return;
|
|
3834
4030
|
}
|
|
3835
4031
|
let body = "";
|
|
@@ -3837,20 +4033,20 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
|
|
|
3837
4033
|
response.on("data", (chunk) => {
|
|
3838
4034
|
if (body.length < 4096) body += chunk;
|
|
3839
4035
|
});
|
|
3840
|
-
response.on("end", () =>
|
|
3841
|
-
response.on("error", () =>
|
|
4036
|
+
response.on("end", () => resolve22(body));
|
|
4037
|
+
response.on("error", () => resolve22(null));
|
|
3842
4038
|
}
|
|
3843
4039
|
);
|
|
3844
4040
|
request2.on("timeout", () => {
|
|
3845
4041
|
request2.destroy();
|
|
3846
|
-
|
|
4042
|
+
resolve22(null);
|
|
3847
4043
|
});
|
|
3848
|
-
request2.on("error", () =>
|
|
4044
|
+
request2.on("error", () => resolve22(null));
|
|
3849
4045
|
request2.end();
|
|
3850
4046
|
});
|
|
3851
4047
|
}
|
|
3852
4048
|
function sleep2(ms) {
|
|
3853
|
-
return new Promise((
|
|
4049
|
+
return new Promise((resolve22) => setTimeout(resolve22, ms));
|
|
3854
4050
|
}
|
|
3855
4051
|
|
|
3856
4052
|
// src/shared/app-files.ts
|
|
@@ -3915,36 +4111,49 @@ function describeFilesFailure(status3, body) {
|
|
|
3915
4111
|
import * as p6 from "@clack/prompts";
|
|
3916
4112
|
import { execSync as execSync2 } from "child_process";
|
|
3917
4113
|
import { createHash as createHash2 } from "crypto";
|
|
3918
|
-
import { existsSync as
|
|
3919
|
-
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
|
+
}
|
|
3920
4129
|
|
|
3921
4130
|
// src/documentation/deploy.ts
|
|
3922
|
-
import { existsSync as
|
|
3923
|
-
import { join as
|
|
4131
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4132
|
+
import { join as join15 } from "path";
|
|
3924
4133
|
function readDocumentationDeployManifest(appDir, clientDir) {
|
|
3925
|
-
if (!
|
|
3926
|
-
const manifestPath =
|
|
3927
|
-
if (!
|
|
4134
|
+
if (!existsSync13(join15(appDir, "documentation.json"))) return void 0;
|
|
4135
|
+
const manifestPath = join15(clientDir, "_documentation", "manifest.json");
|
|
4136
|
+
if (!existsSync13(manifestPath)) {
|
|
3928
4137
|
throw new Error(
|
|
3929
4138
|
"documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
|
|
3930
4139
|
);
|
|
3931
4140
|
}
|
|
3932
|
-
return JSON.parse(
|
|
4141
|
+
return JSON.parse(readFileSync14(manifestPath, "utf8"));
|
|
3933
4142
|
}
|
|
3934
4143
|
|
|
3935
4144
|
// src/cli/commands/update/app-migrations.ts
|
|
3936
4145
|
import {
|
|
3937
4146
|
closeSync,
|
|
3938
4147
|
constants,
|
|
3939
|
-
existsSync as
|
|
4148
|
+
existsSync as existsSync14,
|
|
3940
4149
|
fstatSync,
|
|
3941
4150
|
ftruncateSync,
|
|
3942
|
-
lstatSync,
|
|
4151
|
+
lstatSync as lstatSync2,
|
|
3943
4152
|
openSync,
|
|
3944
|
-
readFileSync as
|
|
4153
|
+
readFileSync as readFileSync15,
|
|
3945
4154
|
writeSync
|
|
3946
4155
|
} from "fs";
|
|
3947
|
-
import { join as
|
|
4156
|
+
import { join as join16 } from "path";
|
|
3948
4157
|
|
|
3949
4158
|
// src/shared/protocol/app-migrations.ts
|
|
3950
4159
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
@@ -3996,8 +4205,8 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
|
3996
4205
|
({ id, description }) => ({ id, description })
|
|
3997
4206
|
);
|
|
3998
4207
|
function readAppliedAppMigrations(appDir) {
|
|
3999
|
-
const path =
|
|
4000
|
-
if (!
|
|
4208
|
+
const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
|
|
4209
|
+
if (!existsSync14(path)) return [];
|
|
4001
4210
|
const source = readRegularFile(path);
|
|
4002
4211
|
let parsed;
|
|
4003
4212
|
try {
|
|
@@ -4023,10 +4232,10 @@ function planAppMigrations(appDir) {
|
|
|
4023
4232
|
const edits = [];
|
|
4024
4233
|
for (const repoPath of tracked) {
|
|
4025
4234
|
if (!isSourcePath(repoPath, appPrefix)) continue;
|
|
4026
|
-
const absolutePath =
|
|
4235
|
+
const absolutePath = join16(repo, repoPath);
|
|
4027
4236
|
let stat;
|
|
4028
4237
|
try {
|
|
4029
|
-
stat =
|
|
4238
|
+
stat = lstatSync2(absolutePath);
|
|
4030
4239
|
} catch {
|
|
4031
4240
|
continue;
|
|
4032
4241
|
}
|
|
@@ -4060,8 +4269,8 @@ function planAppMigrations(appDir) {
|
|
|
4060
4269
|
});
|
|
4061
4270
|
if (pending.length > 0 && blockers.length === 0) {
|
|
4062
4271
|
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
4063
|
-
const absoluteManifestPath =
|
|
4064
|
-
const before =
|
|
4272
|
+
const absoluteManifestPath = join16(repo, manifestPath);
|
|
4273
|
+
const before = existsSync14(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
4065
4274
|
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
4066
4275
|
`;
|
|
4067
4276
|
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
@@ -4076,13 +4285,13 @@ function planAppMigrations(appDir) {
|
|
|
4076
4285
|
function applyAppMigrationPlan(appDir, plan) {
|
|
4077
4286
|
const repo = repoToplevel(appDir);
|
|
4078
4287
|
for (const edit of plan.edits) {
|
|
4079
|
-
const absolutePath =
|
|
4080
|
-
const current =
|
|
4288
|
+
const absolutePath = join16(repo, edit.path);
|
|
4289
|
+
const current = existsSync14(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
4081
4290
|
if (current !== edit.before) {
|
|
4082
4291
|
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
4083
4292
|
}
|
|
4084
4293
|
}
|
|
4085
|
-
for (const edit of plan.edits) writeRegularFile(
|
|
4294
|
+
for (const edit of plan.edits) writeRegularFile(join16(repo, edit.path), edit.before, edit.after);
|
|
4086
4295
|
}
|
|
4087
4296
|
function normalizeEol(source) {
|
|
4088
4297
|
return source.replaceAll("\r\n", "\n");
|
|
@@ -4091,18 +4300,18 @@ function restoreEol(source, checkout) {
|
|
|
4091
4300
|
return checkout.includes("\r\n") ? source.replaceAll("\n", "\r\n") : source;
|
|
4092
4301
|
}
|
|
4093
4302
|
function readRegularFile(path) {
|
|
4094
|
-
const before =
|
|
4303
|
+
const before = lstatSync2(path);
|
|
4095
4304
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
4096
4305
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4097
4306
|
}
|
|
4098
4307
|
const fd = openSync(path, constants.O_RDONLY);
|
|
4099
4308
|
try {
|
|
4100
4309
|
const opened = fstatSync(fd);
|
|
4101
|
-
const after =
|
|
4310
|
+
const after = lstatSync2(path);
|
|
4102
4311
|
if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
4103
4312
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4104
4313
|
}
|
|
4105
|
-
return
|
|
4314
|
+
return readFileSync15(fd, "utf-8");
|
|
4106
4315
|
} finally {
|
|
4107
4316
|
closeSync(fd);
|
|
4108
4317
|
}
|
|
@@ -4117,18 +4326,18 @@ function writeRegularFile(path, before, after) {
|
|
|
4117
4326
|
}
|
|
4118
4327
|
return;
|
|
4119
4328
|
}
|
|
4120
|
-
const pathStat =
|
|
4329
|
+
const pathStat = lstatSync2(path);
|
|
4121
4330
|
if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
|
|
4122
4331
|
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
4123
4332
|
}
|
|
4124
4333
|
const fd = openSync(path, constants.O_RDWR);
|
|
4125
4334
|
try {
|
|
4126
4335
|
const opened = fstatSync(fd);
|
|
4127
|
-
const currentPath =
|
|
4336
|
+
const currentPath = lstatSync2(path);
|
|
4128
4337
|
if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
|
|
4129
4338
|
throw new Error(`Migration input changed while opening: ${path}`);
|
|
4130
4339
|
}
|
|
4131
|
-
if (
|
|
4340
|
+
if (readFileSync15(fd, "utf-8") !== before) {
|
|
4132
4341
|
throw new Error(`Migration input changed after planning: ${path}`);
|
|
4133
4342
|
}
|
|
4134
4343
|
ftruncateSync(fd, 0);
|
|
@@ -4455,22 +4664,12 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4455
4664
|
|
|
4456
4665
|
// src/cli/commands/deploy/build.ts
|
|
4457
4666
|
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4458
|
-
function removeBuildDevVars(workerDir) {
|
|
4459
|
-
const path = join15(workerDir, ".dev.vars");
|
|
4460
|
-
if (!existsSync14(path)) return false;
|
|
4461
|
-
const stat = lstatSync2(path);
|
|
4462
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
4463
|
-
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4464
|
-
}
|
|
4465
|
-
unlinkSync2(path);
|
|
4466
|
-
return true;
|
|
4467
|
-
}
|
|
4468
4667
|
function declaredCompatibilityDate(config) {
|
|
4469
4668
|
const declared = config.compatibility_date;
|
|
4470
4669
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
4471
4670
|
}
|
|
4472
4671
|
async function buildDeployBundle(options) {
|
|
4473
|
-
const { appDir, appName, envName, output, spinner: spinner2 } = options;
|
|
4672
|
+
const { appDir, appName, appId, envName, output, spinner: spinner2 } = options;
|
|
4474
4673
|
const junk = removeMacosJunk(appDir);
|
|
4475
4674
|
if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
|
|
4476
4675
|
const lintFindings = await lintProjectSchemas(appDir);
|
|
@@ -4501,19 +4700,19 @@ async function buildDeployBundle(options) {
|
|
|
4501
4700
|
preparedWranglerConfig?.cleanup();
|
|
4502
4701
|
}
|
|
4503
4702
|
spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
|
|
4504
|
-
const deployConfigPath =
|
|
4505
|
-
if (!
|
|
4703
|
+
const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
|
|
4704
|
+
if (!existsSync15(deployConfigPath)) {
|
|
4506
4705
|
output.die(
|
|
4507
4706
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
4508
4707
|
"build_output_missing"
|
|
4509
4708
|
);
|
|
4510
4709
|
}
|
|
4511
|
-
const deployConfig = JSON.parse(
|
|
4512
|
-
const outputWranglerPath =
|
|
4513
|
-
if (!
|
|
4710
|
+
const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
|
|
4711
|
+
const outputWranglerPath = resolve12(dirname7(deployConfigPath), deployConfig.configPath);
|
|
4712
|
+
if (!existsSync15(outputWranglerPath)) {
|
|
4514
4713
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
4515
4714
|
}
|
|
4516
|
-
const outputConfig = JSON.parse(
|
|
4715
|
+
const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
|
|
4517
4716
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
4518
4717
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
4519
4718
|
output.die(
|
|
@@ -4527,12 +4726,12 @@ async function buildDeployBundle(options) {
|
|
|
4527
4726
|
} catch (error) {
|
|
4528
4727
|
output.die(errorMessage(error), "build_output_unsafe");
|
|
4529
4728
|
}
|
|
4530
|
-
const workerBundlePath =
|
|
4531
|
-
const clientDir = outputConfig.assets?.directory ?
|
|
4532
|
-
if (!
|
|
4729
|
+
const workerBundlePath = join17(workerDir, outputConfig.main);
|
|
4730
|
+
const clientDir = outputConfig.assets?.directory ? resolve12(workerDir, outputConfig.assets.directory) : null;
|
|
4731
|
+
if (!existsSync15(workerBundlePath)) {
|
|
4533
4732
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
4534
4733
|
}
|
|
4535
|
-
if (!clientDir || !
|
|
4734
|
+
if (!clientDir || !existsSync15(clientDir)) {
|
|
4536
4735
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
4537
4736
|
}
|
|
4538
4737
|
try {
|
|
@@ -4545,7 +4744,9 @@ async function buildDeployBundle(options) {
|
|
|
4545
4744
|
const assets = collectAssets(clientDir);
|
|
4546
4745
|
const assetConfig = readDeployAssetConfig(clientDir);
|
|
4547
4746
|
spinner2.stop(`Collected ${assets.length} assets`);
|
|
4548
|
-
const
|
|
4747
|
+
const appIdRefusal = clientAppIdRefusal(assets, appId, envName);
|
|
4748
|
+
if (appIdRefusal) output.die(appIdRefusal.message, appIdRefusal.code);
|
|
4749
|
+
const workerJs = readFileSync16(workerBundlePath, "utf-8");
|
|
4549
4750
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
4550
4751
|
const sqliteClasses = new Set(
|
|
4551
4752
|
outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
|
|
@@ -4613,18 +4814,56 @@ function oversizedAssetRefusal(assets) {
|
|
|
4613
4814
|
if (!tooBig) return null;
|
|
4614
4815
|
return `${tooBig.path} is ${formatBytes(tooBig.size)}; the per-file limit for deploy assets is ${formatBytes(MAX_DEPLOY_ASSET_FILE_BYTES)}. ${LARGE_MEDIA_HOME}`;
|
|
4615
4816
|
}
|
|
4817
|
+
var CLIENT_CODE_ASSET = /\.(?:js|mjs|html)$/i;
|
|
4818
|
+
var APP_ID_LITERAL = /app_[0-9A-HJKMNP-TV-Z]{26}/g;
|
|
4819
|
+
var APP_ID_DEFINE = "__DEEPSPACE_APP_ID__";
|
|
4820
|
+
var APP_ID_ADOPTION_STEPS = `Point the client at the build-injected id \u2014 the SDK's \`deepspace/build\` supplies it:
|
|
4821
|
+
\u2022 vite.config.ts: import { deepspaceBuild } from 'deepspace/build' and add
|
|
4822
|
+
deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins
|
|
4823
|
+
\u2022 vitest.config.ts: import { appIdDefine } and set define: appIdDefine({ appDir })
|
|
4824
|
+
\u2022 src/constants.ts: declare const __DEEPSPACE_APP_ID__: string; export it as APP_ID
|
|
4825
|
+
(drop the frozen literal)
|
|
4826
|
+
Apply all three together: src/constants.ts on its own builds clean and then throws in the browser. A newly scaffolded app already has this.`;
|
|
4827
|
+
function clientAppIdRefusal(assets, appId, envName) {
|
|
4828
|
+
const foreign = /* @__PURE__ */ new Map();
|
|
4829
|
+
let unsubstituted;
|
|
4830
|
+
for (const asset of assets) {
|
|
4831
|
+
if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
|
|
4832
|
+
const source = readFileSync16(asset.sourcePath, "utf-8");
|
|
4833
|
+
if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
|
|
4834
|
+
for (const found of source.matchAll(APP_ID_LITERAL)) {
|
|
4835
|
+
if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
if (unsubstituted !== void 0) {
|
|
4839
|
+
return {
|
|
4840
|
+
code: "app_id_define_unsubstituted",
|
|
4841
|
+
message: `Built client assets still contain \`${APP_ID_DEFINE}\` (first in ${unsubstituted}) \u2014 nothing replaced it at build time. The browser evaluates that identifier and throws ReferenceError before the app renders, so this bundle is a blank page for every visitor. src/constants.ts was moved onto the build-time app id without the build config that supplies it.
|
|
4842
|
+
${APP_ID_ADOPTION_STEPS}`
|
|
4843
|
+
};
|
|
4844
|
+
}
|
|
4845
|
+
if (foreign.size === 0) return null;
|
|
4846
|
+
const target2 = envName ? `--env ${envName} (${appId})` : appId;
|
|
4847
|
+
return {
|
|
4848
|
+
code: "app_id_env_mismatch",
|
|
4849
|
+
message: `Built client assets carry a different app id than the one being deployed. Deploying ${target2}, but the bundle contains:
|
|
4850
|
+
` + [...foreign].map(([id, path]) => ` \u2022 ${id} in ${path}`).join("\n") + `
|
|
4851
|
+
The browser keys its RecordRooms to the id in the bundle, so this would serve ${envName ? `${envName}'s` : "this app's"} worker against another app's data. The usual cause is an app scaffolded before the id was build-injected: src/constants.ts still holds \`export const APP_ID = 'app_\u2026'\` frozen at scaffold time.
|
|
4852
|
+
${APP_ID_ADOPTION_STEPS}`
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4616
4855
|
var LARGE_MEDIA_HOME = "Large media belongs in your app files (`deepspace app files put`), not in the deploy bundle \u2014 move it out of `public/` and reference it at runtime.";
|
|
4617
4856
|
function collectAssets(dir) {
|
|
4618
4857
|
const assets = [];
|
|
4619
4858
|
const walk = (currentDir, prefix) => {
|
|
4620
|
-
for (const entry of
|
|
4859
|
+
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
4621
4860
|
if (isDeployAssetControlFile(entry.name)) continue;
|
|
4622
|
-
const fullPath =
|
|
4861
|
+
const fullPath = join17(currentDir, entry.name);
|
|
4623
4862
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
4624
4863
|
if (entry.isDirectory()) {
|
|
4625
4864
|
walk(fullPath, relativePath);
|
|
4626
4865
|
} else {
|
|
4627
|
-
const bytes =
|
|
4866
|
+
const bytes = readFileSync16(fullPath);
|
|
4628
4867
|
assets.push({
|
|
4629
4868
|
path: `/${relativePath}`,
|
|
4630
4869
|
hash: createHash2("sha256").update(bytes).digest("hex"),
|
|
@@ -4643,9 +4882,9 @@ function readDeployAssetConfig(clientDir) {
|
|
|
4643
4882
|
["_headers", "_headers"],
|
|
4644
4883
|
["_redirects", "_redirects"]
|
|
4645
4884
|
]) {
|
|
4646
|
-
const filePath =
|
|
4647
|
-
if (!
|
|
4648
|
-
const content =
|
|
4885
|
+
const filePath = join17(clientDir, file);
|
|
4886
|
+
if (!existsSync15(filePath)) continue;
|
|
4887
|
+
const content = readFileSync16(filePath, "utf8");
|
|
4649
4888
|
if (content.trim()) config[key] = content;
|
|
4650
4889
|
}
|
|
4651
4890
|
return config;
|
|
@@ -4659,8 +4898,8 @@ function errorMessage(error) {
|
|
|
4659
4898
|
|
|
4660
4899
|
// src/cli/commands/deploy/commerce.ts
|
|
4661
4900
|
import * as p7 from "@clack/prompts";
|
|
4662
|
-
import { existsSync as
|
|
4663
|
-
import { join as
|
|
4901
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4902
|
+
import { join as join18 } from "path";
|
|
4664
4903
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4665
4904
|
|
|
4666
4905
|
// src/shared/api-error.ts
|
|
@@ -4712,10 +4951,10 @@ function errorMessage2(error) {
|
|
|
4712
4951
|
return error instanceof Error ? error.message : String(error);
|
|
4713
4952
|
}
|
|
4714
4953
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
4715
|
-
const sourcePath =
|
|
4716
|
-
if (!
|
|
4954
|
+
const sourcePath = join18(appDir, "src", sourceFile);
|
|
4955
|
+
if (!existsSync16(sourcePath)) return void 0;
|
|
4717
4956
|
const esbuild = await import("esbuild");
|
|
4718
|
-
const outputPath =
|
|
4957
|
+
const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
|
|
4719
4958
|
await esbuild.build({
|
|
4720
4959
|
entryPoints: [sourcePath],
|
|
4721
4960
|
bundle: true,
|
|
@@ -4808,7 +5047,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
4808
5047
|
}
|
|
4809
5048
|
}
|
|
4810
5049
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
4811
|
-
if (!
|
|
5050
|
+
if (!existsSync16(join18(appDir, "src", "subscriptions.ts"))) return;
|
|
4812
5051
|
let plans;
|
|
4813
5052
|
try {
|
|
4814
5053
|
plans = await loadDeclaration(
|
|
@@ -4863,7 +5102,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4863
5102
|
}
|
|
4864
5103
|
}
|
|
4865
5104
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
4866
|
-
if (!
|
|
5105
|
+
if (!existsSync16(join18(appDir, "src", "products.ts"))) return;
|
|
4867
5106
|
let products;
|
|
4868
5107
|
try {
|
|
4869
5108
|
products = await loadDeclaration(
|
|
@@ -4965,7 +5204,7 @@ function createDeployOutput(json) {
|
|
|
4965
5204
|
|
|
4966
5205
|
// src/cli/commands/deploy/request.ts
|
|
4967
5206
|
import * as p10 from "@clack/prompts";
|
|
4968
|
-
import { readFileSync as
|
|
5207
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
4969
5208
|
|
|
4970
5209
|
// src/cli/commands/deploy/repository.ts
|
|
4971
5210
|
import * as p9 from "@clack/prompts";
|
|
@@ -5156,9 +5395,9 @@ function repoApi(deployUrl, token, appId) {
|
|
|
5156
5395
|
}
|
|
5157
5396
|
|
|
5158
5397
|
// src/cli/lib/vc-remote.ts
|
|
5159
|
-
import { existsSync as
|
|
5398
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5160
5399
|
import { tmpdir } from "os";
|
|
5161
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
5400
|
+
import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve13 } from "path";
|
|
5162
5401
|
function spaceRemoteName(environment = DEEPSPACE_ENV) {
|
|
5163
5402
|
if (environment === "staging") return "space-staging";
|
|
5164
5403
|
if (environment === "production") return "space";
|
|
@@ -5204,14 +5443,14 @@ function entryIsTransient(cwd) {
|
|
|
5204
5443
|
const entry = process.argv[1];
|
|
5205
5444
|
if (!entry) return true;
|
|
5206
5445
|
if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
|
|
5207
|
-
const rel =
|
|
5446
|
+
const rel = relative2(resolve13(cwd), resolve13(entry));
|
|
5208
5447
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
5209
5448
|
}
|
|
5210
5449
|
function helperEntryUsable(command) {
|
|
5211
5450
|
const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
5212
5451
|
const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
|
|
5213
5452
|
if (!match || match[1] !== expectedEnvironment) return false;
|
|
5214
|
-
return
|
|
5453
|
+
return existsSync17(match[2]) && existsSync17(match[3]);
|
|
5215
5454
|
}
|
|
5216
5455
|
function credentialHelperCommand(environment = DEEPSPACE_ENV) {
|
|
5217
5456
|
const entry = process.argv[1];
|
|
@@ -5346,12 +5585,6 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5346
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.`
|
|
5347
5586
|
};
|
|
5348
5587
|
}
|
|
5349
|
-
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
5350
|
-
return {
|
|
5351
|
-
code: "source_managed_by_github",
|
|
5352
|
-
error: "This app uses GitHub source. Manage commits with normal Git/GitHub; `deepspace deploy` ships the local working tree without changing Git."
|
|
5353
|
-
};
|
|
5354
|
-
}
|
|
5355
5588
|
if (/(?:HTTP |error: )429\b/i.test(message)) {
|
|
5356
5589
|
return {
|
|
5357
5590
|
code: "rate_limited",
|
|
@@ -5361,6 +5594,12 @@ function classifyPushTransportFailure(error, cwd) {
|
|
|
5361
5594
|
if (/(?:HTTP |error: )413\b/i.test(message)) {
|
|
5362
5595
|
return { code: "push_too_large", error: pushTooLargeMessage(cwd) };
|
|
5363
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
|
+
}
|
|
5364
5603
|
return null;
|
|
5365
5604
|
}
|
|
5366
5605
|
function pushToSpace(cwd, token, refspec, opts = {}) {
|
|
@@ -5732,7 +5971,7 @@ async function pushWithTransientRetry(push) {
|
|
|
5732
5971
|
const message = errorMessage3(error);
|
|
5733
5972
|
const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
|
|
5734
5973
|
if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
|
|
5735
|
-
await new Promise((
|
|
5974
|
+
await new Promise((resolve22) => setTimeout(resolve22, backoffMs[attempt]));
|
|
5736
5975
|
continue;
|
|
5737
5976
|
}
|
|
5738
5977
|
throw error;
|
|
@@ -6016,7 +6255,7 @@ async function uploadDeployAssets(options) {
|
|
|
6016
6255
|
// against the plan before it touches storage, and a stream body
|
|
6017
6256
|
// would be sent chunked with no length at all; a retry also needs a
|
|
6018
6257
|
// body it can send again.
|
|
6019
|
-
body:
|
|
6258
|
+
body: readFileSync17(asset.sourcePath)
|
|
6020
6259
|
})
|
|
6021
6260
|
);
|
|
6022
6261
|
if (!response.ok) {
|
|
@@ -6097,7 +6336,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
6097
6336
|
lastError = error;
|
|
6098
6337
|
}
|
|
6099
6338
|
if (attempt < attempts) {
|
|
6100
|
-
await new Promise((
|
|
6339
|
+
await new Promise((resolve22) => setTimeout(resolve22, Math.min(8e3, 500 * 2 ** (attempt - 1))));
|
|
6101
6340
|
}
|
|
6102
6341
|
}
|
|
6103
6342
|
if (lastResponse) return lastResponse;
|
|
@@ -6233,12 +6472,12 @@ var deploy_default = defineCommand3({
|
|
|
6233
6472
|
async run({ args }) {
|
|
6234
6473
|
const output = createDeployOutput(args.json === true);
|
|
6235
6474
|
preflightNodeVersion("deploy");
|
|
6236
|
-
const appDir =
|
|
6475
|
+
const appDir = resolve14(args.dir ?? ".");
|
|
6237
6476
|
const selectorRefusal = blankSelectorRefusal(args);
|
|
6238
6477
|
if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
|
|
6239
6478
|
const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
|
|
6240
6479
|
if (!hasWranglerConfig(appDir)) {
|
|
6241
|
-
output.die("
|
|
6480
|
+
output.die(noWranglerConfigMessage(join19(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
6242
6481
|
}
|
|
6243
6482
|
try {
|
|
6244
6483
|
ensureInstallReady(appDir);
|
|
@@ -6252,7 +6491,7 @@ var deploy_default = defineCommand3({
|
|
|
6252
6491
|
output.showIntro();
|
|
6253
6492
|
const wranglerConfig = readWranglerConfig(appDir);
|
|
6254
6493
|
const nameResult = resolveAppNameForEnv(wranglerConfig, envName);
|
|
6255
|
-
if (!nameResult.ok) return output.die(nameResult.reason, "invalid_app_name");
|
|
6494
|
+
if (!nameResult.ok) return output.die(nameResult.reason, nameResult.code ?? "invalid_app_name");
|
|
6256
6495
|
const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
|
|
6257
6496
|
if (declaredName !== nameResult.name) {
|
|
6258
6497
|
const location = envName ? `[env.${envName}].name` : "`name`";
|
|
@@ -6279,6 +6518,13 @@ var deploy_default = defineCommand3({
|
|
|
6279
6518
|
);
|
|
6280
6519
|
}
|
|
6281
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
|
+
}
|
|
6282
6528
|
const secretsCache = await loadDeploySecrets({
|
|
6283
6529
|
deployUrl: DEPLOY_URL3,
|
|
6284
6530
|
appDir,
|
|
@@ -6288,12 +6534,17 @@ var deploy_default = defineCommand3({
|
|
|
6288
6534
|
token,
|
|
6289
6535
|
output
|
|
6290
6536
|
});
|
|
6291
|
-
const
|
|
6292
|
-
if (
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
)
|
|
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;
|
|
6297
6548
|
}
|
|
6298
6549
|
const repositoryPreflight = preflightDeployRepository({
|
|
6299
6550
|
appDir,
|
|
@@ -6307,6 +6558,7 @@ var deploy_default = defineCommand3({
|
|
|
6307
6558
|
const bundle = await buildDeployBundle({
|
|
6308
6559
|
appDir,
|
|
6309
6560
|
appName,
|
|
6561
|
+
appId,
|
|
6310
6562
|
envName,
|
|
6311
6563
|
output,
|
|
6312
6564
|
spinner: spinner2
|
|
@@ -6338,7 +6590,7 @@ var deploy_default = defineCommand3({
|
|
|
6338
6590
|
appId,
|
|
6339
6591
|
appName,
|
|
6340
6592
|
token,
|
|
6341
|
-
rename:
|
|
6593
|
+
rename: confirmRename,
|
|
6342
6594
|
claimReleased: args["claim-released"] === true,
|
|
6343
6595
|
ignoreStale: Boolean(args["ignore-stale"]),
|
|
6344
6596
|
bundle,
|
|
@@ -6405,6 +6657,25 @@ async function syncCommerce(appDir, appId, token, nonInteractive) {
|
|
|
6405
6657
|
await syncSubscriptionPlans(appDir, appId, token, nonInteractive);
|
|
6406
6658
|
await syncOneTimeProducts(appDir, appId, token);
|
|
6407
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
|
+
}
|
|
6408
6679
|
function blankSelectorRefusal(args) {
|
|
6409
6680
|
if (args.env !== void 0 && !String(args.env).trim()) {
|
|
6410
6681
|
return {
|
|
@@ -6465,15 +6736,18 @@ function requireAppIdArg(explicit) {
|
|
|
6465
6736
|
function liveAppUrl(app2) {
|
|
6466
6737
|
return app2.status === "active" && app2.deployedAt !== null ? app2.url : null;
|
|
6467
6738
|
}
|
|
6468
|
-
async function
|
|
6469
|
-
const
|
|
6470
|
-
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)) {
|
|
6471
6742
|
throw new InputError(
|
|
6472
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?).`,
|
|
6473
6744
|
"invalid_response"
|
|
6474
6745
|
);
|
|
6475
6746
|
}
|
|
6476
|
-
return apps;
|
|
6747
|
+
return { apps: body.apps, pendingTransfers: body.pendingTransfers ?? [] };
|
|
6748
|
+
}
|
|
6749
|
+
async function listApps(deployUrl, token) {
|
|
6750
|
+
return (await listAppsPage(deployUrl, token)).apps;
|
|
6477
6751
|
}
|
|
6478
6752
|
function matchAppSelector(apps, selector) {
|
|
6479
6753
|
const byId = apps.find((app2) => app2.appId === selector);
|
|
@@ -6715,17 +6989,17 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6715
6989
|
},
|
|
6716
6990
|
async run({ args }) {
|
|
6717
6991
|
const token = await ensureToken();
|
|
6718
|
-
const apps = await
|
|
6992
|
+
const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL5, token);
|
|
6719
6993
|
if (!args.json) {
|
|
6720
|
-
if (!apps.length) {
|
|
6994
|
+
if (!apps.length && !pendingTransfers.length) {
|
|
6721
6995
|
console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
|
|
6722
|
-
} else {
|
|
6996
|
+
} else if (apps.length) {
|
|
6723
6997
|
const rows = [...apps].sort(
|
|
6724
6998
|
(left, right) => Number(right.status !== "undeployed") - Number(left.status !== "undeployed")
|
|
6725
6999
|
);
|
|
6726
7000
|
const roleOf = (a) => a.role ?? "owner";
|
|
6727
7001
|
const roleWidth = Math.max(4, ...rows.map((a) => roleOf(a).length));
|
|
6728
|
-
const nameWidth = Math.max(4, ...rows.map((a) => (a
|
|
7002
|
+
const nameWidth = Math.max(4, ...rows.map((a) => nameCell(a).length));
|
|
6729
7003
|
const statusWidth = Math.max(6, ...rows.map((a) => a.status.length));
|
|
6730
7004
|
const idWidth = Math.max(6, ...rows.map((a) => a.appId.length));
|
|
6731
7005
|
console.log(
|
|
@@ -6734,14 +7008,36 @@ var apps_default = defineDeepspaceCommand({
|
|
|
6734
7008
|
for (const a of rows) {
|
|
6735
7009
|
const url = liveAppUrl(a) ?? "(not deployed)";
|
|
6736
7010
|
console.log(
|
|
6737
|
-
`${(a
|
|
7011
|
+
`${nameCell(a).padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${roleOf(a).padEnd(roleWidth)} ${a.appId.padEnd(idWidth)} ${url}`
|
|
6738
7012
|
);
|
|
6739
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
|
+
);
|
|
6740
7032
|
}
|
|
6741
7033
|
}
|
|
6742
|
-
return { data: { apps } };
|
|
7034
|
+
return { data: { apps, pendingTransfers } };
|
|
6743
7035
|
}
|
|
6744
7036
|
});
|
|
7037
|
+
function nameCell(app2) {
|
|
7038
|
+
if (app2.name) return app2.name;
|
|
7039
|
+
return app2.reservedName ? `${app2.reservedName} (reserved)` : "\u2014";
|
|
7040
|
+
}
|
|
6745
7041
|
|
|
6746
7042
|
// src/cli/commands/logs.ts
|
|
6747
7043
|
import { defineCommand as defineCommand4 } from "citty";
|
|
@@ -7125,37 +7421,37 @@ var usage_default = defineDeepspaceCommand({
|
|
|
7125
7421
|
});
|
|
7126
7422
|
|
|
7127
7423
|
// src/cli/commands/add.ts
|
|
7128
|
-
import { existsSync as
|
|
7129
|
-
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";
|
|
7130
7426
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7131
7427
|
|
|
7132
7428
|
// src/cli/commands/feature-installer.ts
|
|
7133
7429
|
import {
|
|
7134
7430
|
closeSync as closeSync2,
|
|
7135
7431
|
copyFileSync,
|
|
7136
|
-
existsSync as
|
|
7432
|
+
existsSync as existsSync18,
|
|
7137
7433
|
fstatSync as fstatSync2,
|
|
7138
7434
|
mkdirSync as mkdirSync6,
|
|
7139
7435
|
mkdtempSync,
|
|
7140
7436
|
openSync as openSync2,
|
|
7141
|
-
readFileSync as
|
|
7437
|
+
readFileSync as readFileSync18,
|
|
7142
7438
|
readSync,
|
|
7143
|
-
readdirSync as
|
|
7439
|
+
readdirSync as readdirSync7,
|
|
7144
7440
|
rmSync as rmSync4,
|
|
7145
|
-
writeFileSync as
|
|
7441
|
+
writeFileSync as writeFileSync5
|
|
7146
7442
|
} from "fs";
|
|
7147
7443
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7148
7444
|
import { tmpdir as tmpdir2 } from "os";
|
|
7149
|
-
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";
|
|
7150
7446
|
import { fileURLToPath } from "url";
|
|
7151
7447
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
7152
7448
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
7153
7449
|
let dir = dirname8(fileURLToPath(moduleUrl));
|
|
7154
7450
|
for (; ; ) {
|
|
7155
|
-
const packagePath =
|
|
7156
|
-
if (
|
|
7451
|
+
const packagePath = join20(dir, "package.json");
|
|
7452
|
+
if (existsSync18(packagePath)) {
|
|
7157
7453
|
try {
|
|
7158
|
-
const pkg2 = JSON.parse(
|
|
7454
|
+
const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7159
7455
|
if (pkg2.name === "deepspace") return dir;
|
|
7160
7456
|
} catch {
|
|
7161
7457
|
}
|
|
@@ -7166,13 +7462,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
7166
7462
|
}
|
|
7167
7463
|
}
|
|
7168
7464
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
7169
|
-
return
|
|
7465
|
+
return existsSync18(join20(packageRoot, "features"));
|
|
7170
7466
|
}
|
|
7171
7467
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
7172
|
-
const configPath =
|
|
7173
|
-
if (!
|
|
7468
|
+
const configPath = join20(packageRoot, "features", id, "feature.json");
|
|
7469
|
+
if (!existsSync18(configPath)) return null;
|
|
7174
7470
|
try {
|
|
7175
|
-
const config = JSON.parse(
|
|
7471
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
7176
7472
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
7177
7473
|
return null;
|
|
7178
7474
|
}
|
|
@@ -7182,9 +7478,9 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
7182
7478
|
}
|
|
7183
7479
|
}
|
|
7184
7480
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
7185
|
-
const featuresDir =
|
|
7186
|
-
if (!
|
|
7187
|
-
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));
|
|
7188
7484
|
}
|
|
7189
7485
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
7190
7486
|
return listFeatureConfigs(packageRoot).map((config) => ({
|
|
@@ -7221,7 +7517,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
|
|
|
7221
7517
|
return closest !== null && closestDistance <= threshold ? closest : null;
|
|
7222
7518
|
}
|
|
7223
7519
|
function resolveTargetDir(raw) {
|
|
7224
|
-
return isAbsolute4(raw) ? raw :
|
|
7520
|
+
return isAbsolute4(raw) ? raw : resolve15(raw);
|
|
7225
7521
|
}
|
|
7226
7522
|
function resolveFeatureDestPath(fileDest, route) {
|
|
7227
7523
|
const pages = "src/pages/";
|
|
@@ -7237,11 +7533,11 @@ function copyFile(src, dest) {
|
|
|
7237
7533
|
function copyFileWithImportShift(src, dest, levels) {
|
|
7238
7534
|
mkdirSync6(dirname8(dest), { recursive: true });
|
|
7239
7535
|
const prefix = "../".repeat(levels);
|
|
7240
|
-
const shifted =
|
|
7536
|
+
const shifted = readFileSync18(src, "utf-8").replace(
|
|
7241
7537
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
7242
7538
|
`$1$2${prefix}$3`
|
|
7243
7539
|
);
|
|
7244
|
-
|
|
7540
|
+
writeFileSync5(dest, shifted);
|
|
7245
7541
|
}
|
|
7246
7542
|
function installFiles(packageRoot, config, targetDir, emit) {
|
|
7247
7543
|
let copied = 0;
|
|
@@ -7250,12 +7546,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
7250
7546
|
const preexisting = [];
|
|
7251
7547
|
const missing = [];
|
|
7252
7548
|
for (const file of config.files) {
|
|
7253
|
-
const srcPath =
|
|
7549
|
+
const srcPath = join20(packageRoot, "features", config.id, file.src);
|
|
7254
7550
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
7255
|
-
const destPath =
|
|
7256
|
-
const existed =
|
|
7551
|
+
const destPath = join20(targetDir, resolvedDest);
|
|
7552
|
+
const existed = existsSync18(destPath);
|
|
7257
7553
|
if (existed) preexisting.push(resolvedDest);
|
|
7258
|
-
if (!
|
|
7554
|
+
if (!existsSync18(srcPath)) {
|
|
7259
7555
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
7260
7556
|
if (!existed) missing.push(resolvedDest);
|
|
7261
7557
|
continue;
|
|
@@ -7292,11 +7588,11 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
7292
7588
|
}
|
|
7293
7589
|
function integrateSchema(config, targetDir, emit) {
|
|
7294
7590
|
if (!config.schema) return [];
|
|
7295
|
-
const schemasPath =
|
|
7296
|
-
if (!
|
|
7591
|
+
const schemasPath = join20(targetDir, "src", "schemas.ts");
|
|
7592
|
+
if (!existsSync18(schemasPath)) {
|
|
7297
7593
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
7298
7594
|
}
|
|
7299
|
-
let content =
|
|
7595
|
+
let content = readFileSync18(schemasPath, "utf-8");
|
|
7300
7596
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
7301
7597
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
7302
7598
|
if (importPattern.test(content)) {
|
|
@@ -7327,7 +7623,7 @@ export const schemas`
|
|
|
7327
7623
|
return [unresolvedSchema(config.schema, reason, emit)];
|
|
7328
7624
|
}
|
|
7329
7625
|
content = withEntry;
|
|
7330
|
-
|
|
7626
|
+
writeFileSync5(schemasPath, content);
|
|
7331
7627
|
emit(` Schema integrated: ${exportName} -> schemas.ts`);
|
|
7332
7628
|
return [];
|
|
7333
7629
|
}
|
|
@@ -7349,11 +7645,11 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
7349
7645
|
}
|
|
7350
7646
|
function integrateActions(config, targetDir, emit) {
|
|
7351
7647
|
if (!config.actions?.length) return [];
|
|
7352
|
-
const actionsPath =
|
|
7353
|
-
if (!
|
|
7648
|
+
const actionsPath = join20(targetDir, "src", "actions", "index.ts");
|
|
7649
|
+
if (!existsSync18(actionsPath)) {
|
|
7354
7650
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
7355
7651
|
}
|
|
7356
|
-
let content =
|
|
7652
|
+
let content = readFileSync18(actionsPath, "utf-8");
|
|
7357
7653
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
7358
7654
|
if (!registryPattern.test(content)) {
|
|
7359
7655
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -7393,14 +7689,14 @@ ${registryEntry}`);
|
|
|
7393
7689
|
integrated++;
|
|
7394
7690
|
emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
|
|
7395
7691
|
}
|
|
7396
|
-
if (integrated > 0)
|
|
7692
|
+
if (integrated > 0) writeFileSync5(actionsPath, content);
|
|
7397
7693
|
return unresolved;
|
|
7398
7694
|
}
|
|
7399
7695
|
function integrateCode(config, targetDir, emit) {
|
|
7400
7696
|
const unresolved = [];
|
|
7401
7697
|
for (const insertion of config.code ?? []) {
|
|
7402
|
-
const targetPath =
|
|
7403
|
-
if (!
|
|
7698
|
+
const targetPath = join20(targetDir, insertion.file);
|
|
7699
|
+
if (!existsSync18(targetPath)) {
|
|
7404
7700
|
unresolved.push({
|
|
7405
7701
|
area: "code",
|
|
7406
7702
|
reason: `${insertion.file} was not found.`,
|
|
@@ -7408,7 +7704,7 @@ function integrateCode(config, targetDir, emit) {
|
|
|
7408
7704
|
});
|
|
7409
7705
|
continue;
|
|
7410
7706
|
}
|
|
7411
|
-
const original =
|
|
7707
|
+
const original = readFileSync18(targetPath, "utf-8");
|
|
7412
7708
|
if (original.includes(insertion.insert.trim())) {
|
|
7413
7709
|
emit(` Integration already present: ${insertion.file}`);
|
|
7414
7710
|
continue;
|
|
@@ -7438,35 +7734,35 @@ ${content.slice(
|
|
|
7438
7734
|
const prefix = insertion.placement === "before" ? "" : "\n";
|
|
7439
7735
|
const suffix = insertion.placement === "before" ? "\n" : "";
|
|
7440
7736
|
content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
|
|
7441
|
-
|
|
7737
|
+
writeFileSync5(targetPath, content);
|
|
7442
7738
|
emit(` Integrated: ${insertion.file}`);
|
|
7443
7739
|
}
|
|
7444
7740
|
return unresolved;
|
|
7445
7741
|
}
|
|
7446
7742
|
function integrateIgnore(config, targetDir) {
|
|
7447
7743
|
if (!config.ignore?.length) return;
|
|
7448
|
-
const targetPath =
|
|
7449
|
-
const current =
|
|
7744
|
+
const targetPath = join20(targetDir, ".gitignore");
|
|
7745
|
+
const current = existsSync18(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
|
|
7450
7746
|
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
7451
7747
|
const missing = config.ignore.filter((pattern) => !present.has(pattern));
|
|
7452
7748
|
if (missing.length === 0) return;
|
|
7453
7749
|
const separator = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
7454
|
-
|
|
7750
|
+
writeFileSync5(targetPath, `${current}${separator}${missing.join("\n")}
|
|
7455
7751
|
`);
|
|
7456
7752
|
}
|
|
7457
7753
|
function integrateWrangler(config, targetDir, emit) {
|
|
7458
7754
|
const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
|
|
7459
7755
|
const rateLimits = config.wrangler?.rateLimits ?? [];
|
|
7460
7756
|
if (!runWorkerFirst && rateLimits.length === 0) return [];
|
|
7461
|
-
const targetPath =
|
|
7757
|
+
const targetPath = join20(targetDir, "wrangler.toml");
|
|
7462
7758
|
const steps = [
|
|
7463
7759
|
...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
|
|
7464
7760
|
...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
|
|
7465
7761
|
];
|
|
7466
|
-
if (!
|
|
7762
|
+
if (!existsSync18(targetPath)) {
|
|
7467
7763
|
return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
|
|
7468
7764
|
}
|
|
7469
|
-
let source =
|
|
7765
|
+
let source = readFileSync18(targetPath, "utf-8");
|
|
7470
7766
|
if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
|
|
7471
7767
|
return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
|
|
7472
7768
|
}
|
|
@@ -7488,7 +7784,7 @@ ${rateLimitBindingBlock(scope, binding)}
|
|
|
7488
7784
|
}
|
|
7489
7785
|
}
|
|
7490
7786
|
if (changed) {
|
|
7491
|
-
|
|
7787
|
+
writeFileSync5(targetPath, source);
|
|
7492
7788
|
emit(" Integrated: wrangler.toml");
|
|
7493
7789
|
} else emit(" Integration already present: wrangler.toml");
|
|
7494
7790
|
return [];
|
|
@@ -7542,8 +7838,8 @@ function rateLimitBindingBlock(scope, binding) {
|
|
|
7542
7838
|
}
|
|
7543
7839
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
7544
7840
|
if (!config.css?.length) return [];
|
|
7545
|
-
const stylesPath =
|
|
7546
|
-
if (!
|
|
7841
|
+
const stylesPath = join20(targetDir, "src", "styles.css");
|
|
7842
|
+
if (!existsSync18(stylesPath)) {
|
|
7547
7843
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
7548
7844
|
return [
|
|
7549
7845
|
{
|
|
@@ -7555,12 +7851,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7555
7851
|
}
|
|
7556
7852
|
];
|
|
7557
7853
|
}
|
|
7558
|
-
let styles =
|
|
7854
|
+
let styles = readFileSync18(stylesPath, "utf-8");
|
|
7559
7855
|
let integrated = 0;
|
|
7560
7856
|
const unresolved = [];
|
|
7561
7857
|
for (const cssFile of config.css) {
|
|
7562
|
-
const srcPath =
|
|
7563
|
-
if (!
|
|
7858
|
+
const srcPath = join20(packageRoot, "features", config.id, cssFile);
|
|
7859
|
+
if (!existsSync18(srcPath)) {
|
|
7564
7860
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
7565
7861
|
unresolved.push({
|
|
7566
7862
|
area: "css",
|
|
@@ -7569,7 +7865,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
7569
7865
|
});
|
|
7570
7866
|
continue;
|
|
7571
7867
|
}
|
|
7572
|
-
const css =
|
|
7868
|
+
const css = readFileSync18(srcPath, "utf-8");
|
|
7573
7869
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
7574
7870
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
7575
7871
|
if (marker && styles.includes(marker)) {
|
|
@@ -7581,7 +7877,7 @@ ${css}`;
|
|
|
7581
7877
|
integrated++;
|
|
7582
7878
|
emit(` CSS integrated: ${cssFile} -> styles.css`);
|
|
7583
7879
|
}
|
|
7584
|
-
if (integrated > 0)
|
|
7880
|
+
if (integrated > 0) writeFileSync5(stylesPath, styles);
|
|
7585
7881
|
return unresolved;
|
|
7586
7882
|
}
|
|
7587
7883
|
function unresolvedRoute(route, config, reason, emit) {
|
|
@@ -7603,11 +7899,11 @@ function routeRolesSuffix(route) {
|
|
|
7603
7899
|
}
|
|
7604
7900
|
function integrateRoute(config, targetDir, emit) {
|
|
7605
7901
|
if (!config.route) return [];
|
|
7606
|
-
const navPath =
|
|
7607
|
-
if (!
|
|
7902
|
+
const navPath = join20(targetDir, "src", "nav.ts");
|
|
7903
|
+
if (!existsSync18(navPath)) {
|
|
7608
7904
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
7609
7905
|
}
|
|
7610
|
-
let content =
|
|
7906
|
+
let content = readFileSync18(navPath, "utf-8");
|
|
7611
7907
|
const routePath = config.route.path;
|
|
7612
7908
|
if (content.includes(`'${routePath}'`)) {
|
|
7613
7909
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -7628,7 +7924,7 @@ function integrateRoute(config, targetDir, emit) {
|
|
|
7628
7924
|
const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
|
|
7629
7925
|
content = content.replace(NAV_MARKER, `${NAV_MARKER}
|
|
7630
7926
|
${entry}`);
|
|
7631
|
-
|
|
7927
|
+
writeFileSync5(navPath, content);
|
|
7632
7928
|
emit(` Nav wired: ${routePath} (${config.name})`);
|
|
7633
7929
|
emit(" Route: automatic (file-based routing via src/pages/)");
|
|
7634
7930
|
return [];
|
|
@@ -7654,10 +7950,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
7654
7950
|
const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
|
|
7655
7951
|
return { error: child.error, status: child.status };
|
|
7656
7952
|
}
|
|
7657
|
-
const captureDir = mkdtempSync(
|
|
7953
|
+
const captureDir = mkdtempSync(join20(tmpdir2(), "deepspace-add-install-"));
|
|
7658
7954
|
let outputFd;
|
|
7659
7955
|
try {
|
|
7660
|
-
outputFd = openSync2(
|
|
7956
|
+
outputFd = openSync2(join20(captureDir, "output.log"), "w+");
|
|
7661
7957
|
const child = spawnSync6(packageManager, ["install"], {
|
|
7662
7958
|
...options,
|
|
7663
7959
|
stdio: ["ignore", outputFd, outputFd]
|
|
@@ -7681,14 +7977,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7681
7977
|
if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
|
|
7682
7978
|
return { added: [], installRan: false, packageManager };
|
|
7683
7979
|
}
|
|
7684
|
-
const packagePath =
|
|
7685
|
-
if (!
|
|
7980
|
+
const packagePath = join20(targetDir, "package.json");
|
|
7981
|
+
if (!existsSync18(packagePath)) {
|
|
7686
7982
|
emit(" Could not patch package.json (file not found)");
|
|
7687
7983
|
return { added: [], installRan: false, packageManager };
|
|
7688
7984
|
}
|
|
7689
7985
|
let pkg2;
|
|
7690
7986
|
try {
|
|
7691
|
-
pkg2 = JSON.parse(
|
|
7987
|
+
pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
7692
7988
|
} catch (error) {
|
|
7693
7989
|
emit(
|
|
7694
7990
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -7712,7 +8008,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
7712
8008
|
if (added.length === 0) {
|
|
7713
8009
|
emit(" Dependencies already present");
|
|
7714
8010
|
} else {
|
|
7715
|
-
|
|
8011
|
+
writeFileSync5(packagePath, `${JSON.stringify(pkg2, null, 2)}
|
|
7716
8012
|
`, "utf-8");
|
|
7717
8013
|
emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
|
|
7718
8014
|
if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
|
|
@@ -7748,7 +8044,7 @@ ${child.diagnosticTail}` : "";
|
|
|
7748
8044
|
}
|
|
7749
8045
|
function dependencySnapshot(targetDir) {
|
|
7750
8046
|
try {
|
|
7751
|
-
const pkg2 = JSON.parse(
|
|
8047
|
+
const pkg2 = JSON.parse(readFileSync18(join20(targetDir, "package.json"), "utf-8"));
|
|
7752
8048
|
return {
|
|
7753
8049
|
dependencies: pkg2.dependencies ?? {},
|
|
7754
8050
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -7760,7 +8056,7 @@ function dependencySnapshot(targetDir) {
|
|
|
7760
8056
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
7761
8057
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
7762
8058
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
7763
|
-
return
|
|
8059
|
+
return existsSync18(join20(targetDir, signature));
|
|
7764
8060
|
}).map((feature) => feature.id);
|
|
7765
8061
|
}
|
|
7766
8062
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -7993,11 +8289,11 @@ function renderFeatureInfo(config) {
|
|
|
7993
8289
|
return lines.join("\n");
|
|
7994
8290
|
}
|
|
7995
8291
|
function isDeepSpaceApp(dir) {
|
|
7996
|
-
if (
|
|
7997
|
-
const packagePath =
|
|
7998
|
-
if (!
|
|
8292
|
+
if (existsSync19(join21(dir, "wrangler.toml"))) return true;
|
|
8293
|
+
const packagePath = join21(dir, "package.json");
|
|
8294
|
+
if (!existsSync19(packagePath)) return false;
|
|
7999
8295
|
try {
|
|
8000
|
-
const pkg2 = JSON.parse(
|
|
8296
|
+
const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
|
|
8001
8297
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
8002
8298
|
} catch {
|
|
8003
8299
|
return false;
|
|
@@ -8006,8 +8302,8 @@ function isDeepSpaceApp(dir) {
|
|
|
8006
8302
|
function installedPackageRoot(fromDir) {
|
|
8007
8303
|
let dir = fromDir;
|
|
8008
8304
|
for (; ; ) {
|
|
8009
|
-
const candidate =
|
|
8010
|
-
if (
|
|
8305
|
+
const candidate = join21(dir, "node_modules", "deepspace");
|
|
8306
|
+
if (existsSync19(join21(candidate, "package.json"))) return candidate;
|
|
8011
8307
|
const parent = dirname9(dir);
|
|
8012
8308
|
if (parent === dir) return null;
|
|
8013
8309
|
dir = parent;
|
|
@@ -8024,9 +8320,9 @@ async function targetInstaller(appDir) {
|
|
|
8024
8320
|
if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
|
|
8025
8321
|
return { installFeature };
|
|
8026
8322
|
}
|
|
8027
|
-
const entry =
|
|
8028
|
-
const catalog =
|
|
8029
|
-
if (!
|
|
8323
|
+
const entry = join21(installedRoot, "dist", "feature-installer.js");
|
|
8324
|
+
const catalog = join21(installedRoot, "features");
|
|
8325
|
+
if (!existsSync19(entry) || !existsSync19(catalog)) {
|
|
8030
8326
|
throw new Refusal(
|
|
8031
8327
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
8032
8328
|
Expected: ${entry}
|
|
@@ -8118,8 +8414,8 @@ var add_default = defineDeepspaceCommand({
|
|
|
8118
8414
|
}
|
|
8119
8415
|
return { data: { features: featureCatalog(root) } };
|
|
8120
8416
|
}
|
|
8121
|
-
const appDir =
|
|
8122
|
-
if (!
|
|
8417
|
+
const appDir = resolve16(args.dir ?? ".");
|
|
8418
|
+
if (!existsSync19(appDir)) {
|
|
8123
8419
|
throw new Refusal(
|
|
8124
8420
|
`Target directory not found: ${appDir}
|
|
8125
8421
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -8199,11 +8495,11 @@ function fmtDate(iso) {
|
|
|
8199
8495
|
if (!iso) return "\u2014";
|
|
8200
8496
|
return iso.slice(0, 10);
|
|
8201
8497
|
}
|
|
8202
|
-
async function
|
|
8498
|
+
async function confirm5(message) {
|
|
8203
8499
|
process.stdout.write(`${message} [y/N] `);
|
|
8204
|
-
const answer = await new Promise((
|
|
8500
|
+
const answer = await new Promise((resolve22) => {
|
|
8205
8501
|
process.stdin.setEncoding("utf-8");
|
|
8206
|
-
process.stdin.once("data", (data) =>
|
|
8502
|
+
process.stdin.once("data", (data) => resolve22(data.toString().trim().toLowerCase()));
|
|
8207
8503
|
});
|
|
8208
8504
|
process.stdin.pause();
|
|
8209
8505
|
return answer === "y" || answer === "yes";
|
|
@@ -8278,7 +8574,7 @@ var buy = defineDeepspaceCommand({
|
|
|
8278
8574
|
"confirmation_required"
|
|
8279
8575
|
);
|
|
8280
8576
|
}
|
|
8281
|
-
const ok2 = await
|
|
8577
|
+
const ok2 = await confirm5(
|
|
8282
8578
|
`Buy ${domain} for ${price}/yr (auto-renews; non-refundable), attach to ${appId}? Provisioning takes ${expectedTime}.`
|
|
8283
8579
|
);
|
|
8284
8580
|
if (!ok2) {
|
|
@@ -8448,7 +8744,7 @@ var detach = defineDeepspaceCommand({
|
|
|
8448
8744
|
"confirmation_required"
|
|
8449
8745
|
);
|
|
8450
8746
|
}
|
|
8451
|
-
const ok2 = await
|
|
8747
|
+
const ok2 = await confirm5(
|
|
8452
8748
|
`Detach ${domain}? Routing will stop; the domain stays registered (auto-renew is unchanged).`
|
|
8453
8749
|
);
|
|
8454
8750
|
if (!ok2) {
|
|
@@ -8494,8 +8790,8 @@ var domain_default = defineCommand5({
|
|
|
8494
8790
|
|
|
8495
8791
|
// src/cli/commands/app-files.ts
|
|
8496
8792
|
import { defineCommand as defineCommand6 } from "citty";
|
|
8497
|
-
import { basename as basename4, resolve as
|
|
8498
|
-
import { existsSync as
|
|
8793
|
+
import { basename as basename4, resolve as resolve17 } from "path";
|
|
8794
|
+
import { existsSync as existsSync20, statSync as statSync2 } from "fs";
|
|
8499
8795
|
|
|
8500
8796
|
// src/cli/lib/app-files-api.ts
|
|
8501
8797
|
import { createWriteStream, openAsBlob, statSync } from "fs";
|
|
@@ -8773,8 +9069,8 @@ var put = defineDeepspaceCommand({
|
|
|
8773
9069
|
app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
|
|
8774
9070
|
},
|
|
8775
9071
|
async run({ args }) {
|
|
8776
|
-
const localPath =
|
|
8777
|
-
if (!
|
|
9072
|
+
const localPath = resolve17(String(args.file));
|
|
9073
|
+
if (!existsSync20(localPath) || !statSync2(localPath).isFile()) {
|
|
8778
9074
|
throw new Refusal(`No such file: ${args.file}`, "file_not_found");
|
|
8779
9075
|
}
|
|
8780
9076
|
const key = requireKey(args.key ?? basename4(localPath));
|
|
@@ -8874,7 +9170,7 @@ var get = defineDeepspaceCommand({
|
|
|
8874
9170
|
},
|
|
8875
9171
|
async run({ args }) {
|
|
8876
9172
|
const key = requireKey(String(args.key));
|
|
8877
|
-
const destination =
|
|
9173
|
+
const destination = resolve17(String(args.out ?? basename4(key)));
|
|
8878
9174
|
const { token, appId } = await target(args.app);
|
|
8879
9175
|
const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
|
|
8880
9176
|
if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
|
|
@@ -8954,7 +9250,10 @@ var list4 = defineDeepspaceCommand({
|
|
|
8954
9250
|
}
|
|
8955
9251
|
});
|
|
8956
9252
|
var add = defineDeepspaceCommand({
|
|
8957
|
-
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
|
+
},
|
|
8958
9257
|
args: {
|
|
8959
9258
|
email: {
|
|
8960
9259
|
type: "positional",
|
|
@@ -9164,7 +9463,11 @@ var offer = defineDeepspaceCommand({
|
|
|
9164
9463
|
if (replaced) console.log(`\u25B2 Replaced the pending offer to ${pendingTo}.`);
|
|
9165
9464
|
console.log(
|
|
9166
9465
|
`\u2713 Offered ${app2} to ${email} (expires ${expiresAt}).
|
|
9167
|
-
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.`
|
|
9168
9471
|
);
|
|
9169
9472
|
}
|
|
9170
9473
|
return {
|
|
@@ -9191,7 +9494,9 @@ var status2 = defineDeepspaceCommand({
|
|
|
9191
9494
|
return { data: { app: app2, transfer: null } };
|
|
9192
9495
|
}
|
|
9193
9496
|
if (!args.json) {
|
|
9194
|
-
console.log(
|
|
9497
|
+
console.log(
|
|
9498
|
+
`Pending: ${app2} \u2014 from ${transfer.fromEmailDisplay} to ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`
|
|
9499
|
+
);
|
|
9195
9500
|
}
|
|
9196
9501
|
return { data: { app: app2, transfer } };
|
|
9197
9502
|
}
|
|
@@ -9211,15 +9516,32 @@ var accept2 = defineDeepspaceCommand({
|
|
|
9211
9516
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9212
9517
|
throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
|
|
9213
9518
|
}
|
|
9214
|
-
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`, {
|
|
9215
9520
|
method: "POST"
|
|
9216
9521
|
});
|
|
9217
9522
|
if (!args.json) {
|
|
9218
9523
|
console.log(
|
|
9219
|
-
`\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.`
|
|
9220
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
|
+
}
|
|
9221
9535
|
}
|
|
9222
|
-
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
|
+
};
|
|
9223
9545
|
}
|
|
9224
9546
|
});
|
|
9225
9547
|
var cancel4 = defineDeepspaceCommand({
|
|
@@ -9244,7 +9566,7 @@ var transfer_default = defineCommand8({
|
|
|
9244
9566
|
import { defineCommand as defineCommand9 } from "citty";
|
|
9245
9567
|
|
|
9246
9568
|
// src/cli/commands/_invoke-impl.ts
|
|
9247
|
-
import { readFileSync as
|
|
9569
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
9248
9570
|
import * as p15 from "@clack/prompts";
|
|
9249
9571
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
9250
9572
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -9285,7 +9607,7 @@ function resolveBody(opts) {
|
|
|
9285
9607
|
if (opts.body != null) {
|
|
9286
9608
|
raw = opts.body;
|
|
9287
9609
|
} else if (opts.bodyFile != null) {
|
|
9288
|
-
raw = opts.bodyFile === "-" ?
|
|
9610
|
+
raw = opts.bodyFile === "-" ? readFileSync20(0, "utf-8") : readFileSync20(opts.bodyFile, "utf-8");
|
|
9289
9611
|
} else {
|
|
9290
9612
|
return "{}";
|
|
9291
9613
|
}
|
|
@@ -9579,8 +9901,8 @@ var integrations_default = defineCommand9({
|
|
|
9579
9901
|
|
|
9580
9902
|
// src/cli/commands/feedback.ts
|
|
9581
9903
|
import * as p16 from "@clack/prompts";
|
|
9582
|
-
import { existsSync as
|
|
9583
|
-
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";
|
|
9584
9906
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9585
9907
|
import { release } from "os";
|
|
9586
9908
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -9603,12 +9925,12 @@ function readCliVersion() {
|
|
|
9603
9925
|
try {
|
|
9604
9926
|
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
9605
9927
|
const candidates = [
|
|
9606
|
-
|
|
9607
|
-
|
|
9928
|
+
join22(here, "..", "package.json"),
|
|
9929
|
+
join22(here, "..", "..", "..", "package.json")
|
|
9608
9930
|
];
|
|
9609
9931
|
for (const path of candidates) {
|
|
9610
|
-
if (!
|
|
9611
|
-
const pkg2 = JSON.parse(
|
|
9932
|
+
if (!existsSync21(path)) continue;
|
|
9933
|
+
const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
|
|
9612
9934
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
9613
9935
|
}
|
|
9614
9936
|
} catch {
|
|
@@ -9804,7 +10126,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
9804
10126
|
|
|
9805
10127
|
// src/cli/commands/secrets.ts
|
|
9806
10128
|
import { defineCommand as defineCommand10 } from "citty";
|
|
9807
|
-
import { readFileSync as
|
|
10129
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
9808
10130
|
|
|
9809
10131
|
// src/cli/lib/citty-args.ts
|
|
9810
10132
|
function dedupePositionals(bound, rest) {
|
|
@@ -9906,6 +10228,7 @@ function warnIfDebugRoutesEnabled(secrets) {
|
|
|
9906
10228
|
return true;
|
|
9907
10229
|
}
|
|
9908
10230
|
var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
|
|
10231
|
+
var APPLIES_AT_DEPLOY = { appliesAtDeploy: true };
|
|
9909
10232
|
var LOCAL_DEV_HINT = "Running `deepspace dev start`? Restart it to load the change locally.";
|
|
9910
10233
|
var list6 = defineCommand10({
|
|
9911
10234
|
meta: { name: "list", description: "List masked secrets in a config" },
|
|
@@ -9972,7 +10295,7 @@ var set = defineCommand10({
|
|
|
9972
10295
|
const n = Object.keys(secrets).length;
|
|
9973
10296
|
ok(
|
|
9974
10297
|
args.json === true,
|
|
9975
|
-
{ appId: t.appId, config: t.configName, set: Object.keys(secrets) },
|
|
10298
|
+
{ appId: t.appId, config: t.configName, set: Object.keys(secrets), ...APPLIES_AT_DEPLOY },
|
|
9976
10299
|
() => {
|
|
9977
10300
|
console.log(`Set ${n} secret${n === 1 ? "" : "s"} in ${t.configName}.`);
|
|
9978
10301
|
if (!warnIfDebugRoutesEnabled(secrets)) {
|
|
@@ -10045,7 +10368,7 @@ var del2 = defineCommand10({
|
|
|
10045
10368
|
}
|
|
10046
10369
|
}
|
|
10047
10370
|
const note3 = absent > 0 ? ` (${absent} already absent)` : "";
|
|
10048
|
-
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 }, () => {
|
|
10049
10372
|
console.log(
|
|
10050
10373
|
`Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
|
|
10051
10374
|
);
|
|
@@ -10074,7 +10397,7 @@ var upload = defineCommand10({
|
|
|
10074
10397
|
async run({ args }) {
|
|
10075
10398
|
try {
|
|
10076
10399
|
const t = await resolveTarget(args);
|
|
10077
|
-
const content = args.file === "-" ?
|
|
10400
|
+
const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
|
|
10078
10401
|
const secrets = parseSecretsUpload(content);
|
|
10079
10402
|
if (Object.keys(secrets).length === 0)
|
|
10080
10403
|
throw new InputError("No secrets found in the input.", "empty_input");
|
|
@@ -10082,7 +10405,7 @@ var upload = defineCommand10({
|
|
|
10082
10405
|
const uploaded = Object.keys(secrets);
|
|
10083
10406
|
ok(
|
|
10084
10407
|
args.json === true,
|
|
10085
|
-
{ 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 },
|
|
10086
10409
|
() => {
|
|
10087
10410
|
console.log(`Uploaded ${uploaded.length} secrets to ${t.configName}.`);
|
|
10088
10411
|
if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
|
|
@@ -10257,12 +10580,19 @@ var secrets_default = defineCommand10({
|
|
|
10257
10580
|
});
|
|
10258
10581
|
|
|
10259
10582
|
// src/cli/commands/init.ts
|
|
10260
|
-
import { existsSync as
|
|
10261
|
-
import { join as
|
|
10583
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10584
|
+
import { join as join23 } from "path";
|
|
10262
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
|
+
}
|
|
10263
10593
|
function commitScaffoldIfUnborn(appDir, token) {
|
|
10264
10594
|
try {
|
|
10265
|
-
if (!
|
|
10595
|
+
if (!existsSync22(join23(appDir, ".git"))) return false;
|
|
10266
10596
|
if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
|
|
10267
10597
|
return false;
|
|
10268
10598
|
}
|
|
@@ -10337,16 +10667,15 @@ var init_default = defineDeepspaceCommand({
|
|
|
10337
10667
|
method: "POST"
|
|
10338
10668
|
});
|
|
10339
10669
|
writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
|
|
10340
|
-
if (!envName) stampAppIdInSources(appDir, appId, existing);
|
|
10341
10670
|
const committedScaffold = commitScaffoldIfUnborn(appDir, token);
|
|
10342
10671
|
if (!args.json) {
|
|
10672
|
+
const envSuffix = envName ? ` (env: ${envName})` : "";
|
|
10673
|
+
const commitNote = committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.";
|
|
10343
10674
|
if (existing) {
|
|
10344
|
-
console.log(`Forked: ${existing} \u2192 ${appId}${
|
|
10675
|
+
console.log(`Forked: ${existing} \u2192 ${appId}${envSuffix}${commitNote}`);
|
|
10345
10676
|
console.log("Registered as a NEW app under your account; the original is untouched.");
|
|
10346
10677
|
} else {
|
|
10347
|
-
console.log(
|
|
10348
|
-
`Registered ${appId}${envName ? ` (env: ${envName})` : ""} to your account` + (committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.")
|
|
10349
|
-
);
|
|
10678
|
+
console.log(`Registered ${appId}${envSuffix} to your account${commitNote}`);
|
|
10350
10679
|
console.log("The first deploy claims the `name` subdomain.");
|
|
10351
10680
|
}
|
|
10352
10681
|
console.log(`App dir: ${appDir}`);
|
|
@@ -10359,7 +10688,18 @@ var init_default = defineDeepspaceCommand({
|
|
|
10359
10688
|
committedScaffold,
|
|
10360
10689
|
appDir,
|
|
10361
10690
|
env: envName ?? null
|
|
10362
|
-
}
|
|
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
|
+
} : {}
|
|
10363
10703
|
};
|
|
10364
10704
|
}
|
|
10365
10705
|
});
|
|
@@ -10367,7 +10707,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
10367
10707
|
// src/cli/commands/status.ts
|
|
10368
10708
|
import { defineCommand as defineCommand11 } from "citty";
|
|
10369
10709
|
import * as p17 from "@clack/prompts";
|
|
10370
|
-
import { existsSync as
|
|
10710
|
+
import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
|
|
10371
10711
|
function timeAgo(iso) {
|
|
10372
10712
|
const ms = Date.now() - Date.parse(iso);
|
|
10373
10713
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -10452,7 +10792,7 @@ var status_default = defineCommand11({
|
|
|
10452
10792
|
json.services = urls;
|
|
10453
10793
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
10454
10794
|
}
|
|
10455
|
-
facts.loggedIn =
|
|
10795
|
+
facts.loggedIn = existsSync23(SESSION_PATH);
|
|
10456
10796
|
let userId = null;
|
|
10457
10797
|
if (!facts.loggedIn) {
|
|
10458
10798
|
lines.push(["Session", "not logged in"]);
|
|
@@ -10461,7 +10801,7 @@ var status_default = defineCommand11({
|
|
|
10461
10801
|
let who = "logged in";
|
|
10462
10802
|
try {
|
|
10463
10803
|
const payload = decodeJwtPayload(
|
|
10464
|
-
|
|
10804
|
+
readFileSync23(TOKEN_PATH, "utf-8").trim()
|
|
10465
10805
|
);
|
|
10466
10806
|
if (payload.email) who = payload.email;
|
|
10467
10807
|
userId = payload.sub ?? null;
|
|
@@ -10705,11 +11045,7 @@ var push_default = defineDeepspaceCommand({
|
|
|
10705
11045
|
const force = args.force === true;
|
|
10706
11046
|
const appDir = findAppDir();
|
|
10707
11047
|
try {
|
|
10708
|
-
if (!appDir)
|
|
10709
|
-
throw new Refusal(
|
|
10710
|
-
"No wrangler.toml found \u2014 run from inside an app directory.",
|
|
10711
|
-
"not_in_app_repo"
|
|
10712
|
-
);
|
|
11048
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
10713
11049
|
assertSyncableRepo(appDir);
|
|
10714
11050
|
if (branchArg !== void 0) {
|
|
10715
11051
|
const b = branchArg.trim();
|
|
@@ -10732,9 +11068,11 @@ var push_default = defineDeepspaceCommand({
|
|
|
10732
11068
|
"unknown_branch"
|
|
10733
11069
|
);
|
|
10734
11070
|
}
|
|
11071
|
+
const uninitialized = hasWranglerConfig(appDir) && readAppId(appDir) === null;
|
|
10735
11072
|
throw new Refusal(
|
|
10736
|
-
`Branch "${branch}" has no commits yet \u2014 commit first, then push.`,
|
|
10737
|
-
"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"] } } : {}
|
|
10738
11076
|
);
|
|
10739
11077
|
}
|
|
10740
11078
|
const spinner2 = args.json ? null : createSpinner();
|
|
@@ -10798,6 +11136,13 @@ var push_default = defineDeepspaceCommand({
|
|
|
10798
11136
|
"app_not_registered"
|
|
10799
11137
|
);
|
|
10800
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
|
+
}
|
|
10801
11146
|
const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
|
|
10802
11147
|
ensureSpaceRemote(appDir, appId);
|
|
10803
11148
|
if (force) {
|
|
@@ -10937,11 +11282,7 @@ var pull_default = defineDeepspaceCommand({
|
|
|
10937
11282
|
const branchArg = args.branch === void 0 ? void 0 : String(args.branch);
|
|
10938
11283
|
const appArg = typeof args.app === "string" ? args.app : void 0;
|
|
10939
11284
|
const appDir = findAppDir();
|
|
10940
|
-
if (!appDir)
|
|
10941
|
-
throw new Refusal(
|
|
10942
|
-
"No wrangler.toml found \u2014 run from inside an app directory.",
|
|
10943
|
-
"not_in_app_repo"
|
|
10944
|
-
);
|
|
11285
|
+
if (!appDir) throw new Refusal(noWranglerConfigMessage(process.cwd()), "not_in_app_repo");
|
|
10945
11286
|
if (branchArg !== void 0) {
|
|
10946
11287
|
const b = branchArg.trim();
|
|
10947
11288
|
if (!b) throw new Refusal("--branch was given an empty branch name.", "invalid_branch");
|
|
@@ -11159,18 +11500,18 @@ var pull_default = defineDeepspaceCommand({
|
|
|
11159
11500
|
|
|
11160
11501
|
// src/cli/commands/clone.ts
|
|
11161
11502
|
import * as p20 from "@clack/prompts";
|
|
11162
|
-
import { existsSync as
|
|
11163
|
-
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";
|
|
11164
11505
|
function cleanFailedCloneDir(dir) {
|
|
11165
11506
|
let entries;
|
|
11166
11507
|
try {
|
|
11167
|
-
entries =
|
|
11508
|
+
entries = readdirSync8(dir);
|
|
11168
11509
|
} catch {
|
|
11169
|
-
return
|
|
11510
|
+
return existsSync24(dir) ? ["<unreadable>"] : [];
|
|
11170
11511
|
}
|
|
11171
11512
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
11172
11513
|
try {
|
|
11173
|
-
rmSync5(
|
|
11514
|
+
rmSync5(join24(dir, ".git"), { recursive: true, force: true });
|
|
11174
11515
|
return [];
|
|
11175
11516
|
} catch {
|
|
11176
11517
|
return entries;
|
|
@@ -11179,10 +11520,10 @@ function cleanFailedCloneDir(dir) {
|
|
|
11179
11520
|
return entries;
|
|
11180
11521
|
}
|
|
11181
11522
|
function validateCloneTarget(dir) {
|
|
11182
|
-
if (!
|
|
11523
|
+
if (!existsSync24(dir)) return false;
|
|
11183
11524
|
let entries;
|
|
11184
11525
|
try {
|
|
11185
|
-
entries =
|
|
11526
|
+
entries = readdirSync8(dir);
|
|
11186
11527
|
} catch (error) {
|
|
11187
11528
|
if (error.code === "ENOTDIR") {
|
|
11188
11529
|
throw new Refusal(`${dir} already exists and is not a directory.`, "dir_exists");
|
|
@@ -11214,7 +11555,7 @@ var clone_default = defineDeepspaceCommand({
|
|
|
11214
11555
|
async run({ args }) {
|
|
11215
11556
|
assertAppTargetResolvable(String(args.app));
|
|
11216
11557
|
const selector = String(args.app).trim();
|
|
11217
|
-
const dir =
|
|
11558
|
+
const dir = resolve18(String(args.dir ?? selector));
|
|
11218
11559
|
const dirPreexisted = validateCloneTarget(dir);
|
|
11219
11560
|
const spinner2 = args.json ? null : createSpinner();
|
|
11220
11561
|
spinner2?.start(`Resolving ${selector}\u2026`);
|
|
@@ -11387,7 +11728,7 @@ function pickPreviousRelease(releases) {
|
|
|
11387
11728
|
var rollback_default = defineDeepspaceCommand({
|
|
11388
11729
|
meta: {
|
|
11389
11730
|
name: "rollback",
|
|
11390
|
-
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"
|
|
11391
11732
|
},
|
|
11392
11733
|
args: {
|
|
11393
11734
|
release: {
|
|
@@ -11522,7 +11863,7 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
11522
11863
|
|
|
11523
11864
|
// src/cli/commands/workspace/new.ts
|
|
11524
11865
|
import * as p25 from "@clack/prompts";
|
|
11525
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
11866
|
+
import { isAbsolute as isAbsolute6, resolve as resolve19 } from "path";
|
|
11526
11867
|
|
|
11527
11868
|
// src/cli/commands/workspace/analysis.ts
|
|
11528
11869
|
import * as p23 from "@clack/prompts";
|
|
@@ -11656,25 +11997,25 @@ function printOverlaps(overlaps, json) {
|
|
|
11656
11997
|
// src/cli/commands/workspace/local.ts
|
|
11657
11998
|
import * as p24 from "@clack/prompts";
|
|
11658
11999
|
import {
|
|
11659
|
-
existsSync as
|
|
12000
|
+
existsSync as existsSync25,
|
|
11660
12001
|
mkdirSync as mkdirSync7,
|
|
11661
|
-
readFileSync as
|
|
12002
|
+
readFileSync as readFileSync24,
|
|
11662
12003
|
realpathSync as realpathSync6,
|
|
11663
12004
|
renameSync as renameSync2,
|
|
11664
12005
|
statSync as statSync3,
|
|
11665
|
-
writeFileSync as
|
|
12006
|
+
writeFileSync as writeFileSync6
|
|
11666
12007
|
} from "fs";
|
|
11667
|
-
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";
|
|
11668
12009
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
11669
12010
|
function excludeWorktreeDir(appDir, dir) {
|
|
11670
12011
|
try {
|
|
11671
12012
|
ensureLocalExclude(appDir, ".deepspace/");
|
|
11672
12013
|
const realDir = realpathSync6(dir);
|
|
11673
|
-
const rel =
|
|
11674
|
-
if (!rel || rel === ".." || rel.startsWith(`..${
|
|
11675
|
-
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("/");
|
|
11676
12017
|
if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
|
|
11677
|
-
const relApp =
|
|
12018
|
+
const relApp = relative3(realpathSync6(appDir), realDir).split(sep3).join("/");
|
|
11678
12019
|
if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
|
|
11679
12020
|
ensureLocalExclude(appDir, `/${pattern}/`);
|
|
11680
12021
|
} catch {
|
|
@@ -11685,7 +12026,7 @@ function worktreeMarkerPath(worktreeRoot) {
|
|
|
11685
12026
|
const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
|
|
11686
12027
|
if (result.status !== 0) return null;
|
|
11687
12028
|
const gitDir = result.stdout.toString("utf-8").trim();
|
|
11688
|
-
return isAbsolute5(gitDir) ?
|
|
12029
|
+
return isAbsolute5(gitDir) ? join25(gitDir, WORKSPACE_MARKER) : null;
|
|
11689
12030
|
} catch {
|
|
11690
12031
|
return null;
|
|
11691
12032
|
}
|
|
@@ -11697,7 +12038,7 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
|
|
|
11697
12038
|
const tempPath = `${markerPath}.${process.pid}.tmp`;
|
|
11698
12039
|
try {
|
|
11699
12040
|
mkdirSync7(dirname11(markerPath), { recursive: true });
|
|
11700
|
-
|
|
12041
|
+
writeFileSync6(tempPath, JSON.stringify(marker) + "\n");
|
|
11701
12042
|
renameSync2(tempPath, markerPath);
|
|
11702
12043
|
return true;
|
|
11703
12044
|
} catch {
|
|
@@ -11721,7 +12062,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11721
12062
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
11722
12063
|
if (!markerPath) return false;
|
|
11723
12064
|
try {
|
|
11724
|
-
const marker = JSON.parse(
|
|
12065
|
+
const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
|
|
11725
12066
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
11726
12067
|
} catch {
|
|
11727
12068
|
return false;
|
|
@@ -11730,11 +12071,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
11730
12071
|
function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
11731
12072
|
const primary = listWorktrees(appDir)[0];
|
|
11732
12073
|
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11733
|
-
return
|
|
12074
|
+
return join25(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
11734
12075
|
}
|
|
11735
12076
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
11736
|
-
const appRelative =
|
|
11737
|
-
return appRelative ?
|
|
12077
|
+
const appRelative = relative3(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
12078
|
+
return appRelative ? join25(worktreeRoot, appRelative) : worktreeRoot;
|
|
11738
12079
|
}
|
|
11739
12080
|
function primaryAppDir(appDir) {
|
|
11740
12081
|
const primary = listWorktrees(appDir)[0];
|
|
@@ -11769,7 +12110,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
|
|
|
11769
12110
|
function inOwnLinkedWorktree(appDir, id) {
|
|
11770
12111
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
11771
12112
|
try {
|
|
11772
|
-
return statSync3(
|
|
12113
|
+
return statSync3(join25(repoToplevel(appDir), ".git")).isFile();
|
|
11773
12114
|
} catch {
|
|
11774
12115
|
return false;
|
|
11775
12116
|
}
|
|
@@ -11894,7 +12235,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
|
11894
12235
|
}
|
|
11895
12236
|
runGit(mainDir, ["worktree", "prune"], { allowFail: true });
|
|
11896
12237
|
out.worktreeDir = null;
|
|
11897
|
-
const leftover =
|
|
12238
|
+
const leftover = existsSync25(linked.path);
|
|
11898
12239
|
if (leftover) out.leftoverDir = linked.path;
|
|
11899
12240
|
else out.worktreeRemoved = linked.path;
|
|
11900
12241
|
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
@@ -11974,7 +12315,7 @@ async function resolveTarget2(appArg) {
|
|
|
11974
12315
|
const appDir = findAppDir();
|
|
11975
12316
|
if (!appDir) {
|
|
11976
12317
|
throw new Refusal(
|
|
11977
|
-
|
|
12318
|
+
`${noWranglerConfigMessage(process.cwd())} (Or use -a <app> for list, and for drop with an explicit ws_ id.)`,
|
|
11978
12319
|
"not_in_app_repo"
|
|
11979
12320
|
);
|
|
11980
12321
|
}
|
|
@@ -12154,7 +12495,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12154
12495
|
idempotencyKey: mintIdempotencyKey()
|
|
12155
12496
|
});
|
|
12156
12497
|
const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12157
|
-
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput :
|
|
12498
|
+
const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve19(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
|
|
12158
12499
|
const dir = appDirInWorktree(appDir, worktreeRoot);
|
|
12159
12500
|
try {
|
|
12160
12501
|
materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
|
|
@@ -12192,14 +12533,14 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12192
12533
|
|
|
12193
12534
|
// src/cli/commands/workspace/attach.ts
|
|
12194
12535
|
import * as p26 from "@clack/prompts";
|
|
12195
|
-
import { existsSync as
|
|
12196
|
-
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";
|
|
12197
12538
|
function samePath(a, b) {
|
|
12198
12539
|
const real = (p35) => {
|
|
12199
12540
|
try {
|
|
12200
12541
|
return realpathSync7(p35);
|
|
12201
12542
|
} catch {
|
|
12202
|
-
return
|
|
12543
|
+
return resolve20(p35);
|
|
12203
12544
|
}
|
|
12204
12545
|
};
|
|
12205
12546
|
return real(a) === real(b);
|
|
@@ -12212,10 +12553,10 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
12212
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.`;
|
|
12213
12554
|
}
|
|
12214
12555
|
function cleanFailedFreshAttachDir(dir) {
|
|
12215
|
-
if (!
|
|
12556
|
+
if (!existsSync26(dir)) return [];
|
|
12216
12557
|
let entries;
|
|
12217
12558
|
try {
|
|
12218
|
-
entries =
|
|
12559
|
+
entries = readdirSync9(dir);
|
|
12219
12560
|
} catch {
|
|
12220
12561
|
return ["<unreadable>"];
|
|
12221
12562
|
}
|
|
@@ -12289,12 +12630,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12289
12630
|
);
|
|
12290
12631
|
}
|
|
12291
12632
|
const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
|
|
12292
|
-
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir :
|
|
12633
|
+
const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve20(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
|
|
12293
12634
|
const dir2 = appDirInWorktree(appDir, worktreeRoot);
|
|
12294
12635
|
const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
12295
12636
|
if (localTip) {
|
|
12296
12637
|
let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
|
|
12297
|
-
if (existing && !
|
|
12638
|
+
if (existing && !existsSync26(existing)) {
|
|
12298
12639
|
runGit(appDir, ["worktree", "prune"], { allowFail: true });
|
|
12299
12640
|
existing = null;
|
|
12300
12641
|
}
|
|
@@ -12408,11 +12749,11 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12408
12749
|
extra: { status: view.workspace.status }
|
|
12409
12750
|
});
|
|
12410
12751
|
}
|
|
12411
|
-
const dir =
|
|
12752
|
+
const dir = resolve20(
|
|
12412
12753
|
process.cwd(),
|
|
12413
12754
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
12414
12755
|
);
|
|
12415
|
-
if (
|
|
12756
|
+
if (existsSync26(dir)) {
|
|
12416
12757
|
spinner2?.stop("Target exists.");
|
|
12417
12758
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
12418
12759
|
}
|
|
@@ -12749,13 +13090,13 @@ import * as p30 from "@clack/prompts";
|
|
|
12749
13090
|
|
|
12750
13091
|
// src/cli/lib/validation.ts
|
|
12751
13092
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
12752
|
-
import { readFileSync as
|
|
12753
|
-
import { join as
|
|
13093
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
13094
|
+
import { join as join26 } from "path";
|
|
12754
13095
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
12755
13096
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
12756
13097
|
function configuredCommand(appDir) {
|
|
12757
13098
|
try {
|
|
12758
|
-
const pkg2 = JSON.parse(
|
|
13099
|
+
const pkg2 = JSON.parse(readFileSync25(join26(appDir, "package.json"), "utf-8"));
|
|
12759
13100
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
12760
13101
|
} catch {
|
|
12761
13102
|
}
|
|
@@ -13831,13 +14172,13 @@ function sameRefs(left, right) {
|
|
|
13831
14172
|
|
|
13832
14173
|
// src/cli/commands/update.ts
|
|
13833
14174
|
import * as p34 from "@clack/prompts";
|
|
13834
|
-
import { existsSync as
|
|
13835
|
-
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";
|
|
13836
14177
|
|
|
13837
14178
|
// package.json
|
|
13838
14179
|
var package_default = {
|
|
13839
14180
|
name: "deepspace",
|
|
13840
|
-
version: "0.
|
|
14181
|
+
version: "0.23.1",
|
|
13841
14182
|
engines: {
|
|
13842
14183
|
node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
|
|
13843
14184
|
},
|
|
@@ -13874,6 +14215,10 @@ var package_default = {
|
|
|
13874
14215
|
types: "./dist/testing-mcp.d.ts",
|
|
13875
14216
|
import: "./dist/testing-mcp.js"
|
|
13876
14217
|
},
|
|
14218
|
+
"./build": {
|
|
14219
|
+
types: "./dist/build.d.ts",
|
|
14220
|
+
import: "./dist/build.js"
|
|
14221
|
+
},
|
|
13877
14222
|
"./documentation": {
|
|
13878
14223
|
types: "./dist/documentation.d.ts",
|
|
13879
14224
|
import: "./dist/documentation.js"
|
|
@@ -14008,24 +14353,25 @@ function gapIsTooWide(from, to) {
|
|
|
14008
14353
|
return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
|
|
14009
14354
|
}
|
|
14010
14355
|
function readAppSdkVersion(appDir) {
|
|
14011
|
-
const installed =
|
|
14012
|
-
if (
|
|
14013
|
-
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"));
|
|
14014
14359
|
if (typeof parsed2.version === "string") return parsed2.version;
|
|
14015
14360
|
}
|
|
14016
|
-
const manifest =
|
|
14017
|
-
if (!
|
|
14018
|
-
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"));
|
|
14019
14364
|
return parsed.dependencies?.deepspace ?? null;
|
|
14020
14365
|
}
|
|
14021
14366
|
function pinSdkVersion(appDir, version) {
|
|
14022
|
-
const path =
|
|
14023
|
-
const source =
|
|
14367
|
+
const path = join27(appDir, "package.json");
|
|
14368
|
+
const source = readFileSync26(path, "utf8");
|
|
14024
14369
|
const manifest = JSON.parse(source);
|
|
14025
14370
|
const dependencies = manifest.dependencies;
|
|
14026
14371
|
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) return false;
|
|
14027
14372
|
let changed = false;
|
|
14028
|
-
|
|
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}`) {
|
|
14029
14375
|
dependencies.deepspace = `^${version}`;
|
|
14030
14376
|
changed = true;
|
|
14031
14377
|
}
|
|
@@ -14037,7 +14383,7 @@ function pinSdkVersion(appDir, version) {
|
|
|
14037
14383
|
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
14038
14384
|
const indent = source.match(/\r?\n([\t ]+)"/)?.[1] ?? " ";
|
|
14039
14385
|
const finalEol = source.endsWith("\n") ? eol : "";
|
|
14040
|
-
|
|
14386
|
+
writeFileSync7(path, JSON.stringify(manifest, null, indent).replaceAll("\n", eol) + finalEol);
|
|
14041
14387
|
return true;
|
|
14042
14388
|
}
|
|
14043
14389
|
function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
@@ -14046,17 +14392,19 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
|
|
|
14046
14392
|
"vite.config.mts",
|
|
14047
14393
|
"vite.config.js",
|
|
14048
14394
|
"vite.config.mjs"
|
|
14049
|
-
].find((candidate) =>
|
|
14395
|
+
].find((candidate) => existsSync27(join27(appDir, candidate)));
|
|
14050
14396
|
if (!configName) return null;
|
|
14051
|
-
const source =
|
|
14052
|
-
if (source.includes("deepspace-remove-build-preview-secrets"))
|
|
14053
|
-
|
|
14397
|
+
const source = readFileSync26(join27(appDir, configName), "utf8");
|
|
14398
|
+
if (source.includes("deepspace-remove-build-preview-secrets") || source.includes("deepspaceBuild(") || source.includes("'deepspace/build'") || source.includes('"deepspace/build"')) {
|
|
14399
|
+
return null;
|
|
14400
|
+
}
|
|
14401
|
+
return `${configName}: adopt the deepspaceBuild() plugin from deepspace/build (which owns this cleanup), or add the inline cleanup from ${BUILD_PREVIEW_SECRETS_GUIDE}; app update left this app-owned file unchanged.`;
|
|
14054
14402
|
}
|
|
14055
14403
|
function usersSchemaVisibilityUpgradeInstruction(appDir) {
|
|
14056
14404
|
const schemaName = "src/schemas/users-schema.ts";
|
|
14057
|
-
const path =
|
|
14058
|
-
if (!
|
|
14059
|
-
const source =
|
|
14405
|
+
const path = join27(appDir, schemaName);
|
|
14406
|
+
if (!existsSync27(path)) return null;
|
|
14407
|
+
const source = readFileSync26(path, "utf8");
|
|
14060
14408
|
if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
|
|
14061
14409
|
return `${schemaName}: review the member user-row visibility change at ${USERS_SCHEMA_VISIBILITY_GUIDE}; app update left this app-owned schema unchanged.`;
|
|
14062
14410
|
}
|
|
@@ -14116,12 +14464,9 @@ var update_default = defineDeepspaceCommand({
|
|
|
14116
14464
|
}
|
|
14117
14465
|
},
|
|
14118
14466
|
async run({ args }) {
|
|
14119
|
-
const appDir =
|
|
14467
|
+
const appDir = resolve21(".");
|
|
14120
14468
|
if (!hasWranglerConfig(appDir)) {
|
|
14121
|
-
throw new Refusal(
|
|
14122
|
-
"No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
|
|
14123
|
-
"not_in_app_repo"
|
|
14124
|
-
);
|
|
14469
|
+
throw new Refusal(noWranglerConfigMessage(join27(appDir, "wrangler.toml")), "not_in_app_repo");
|
|
14125
14470
|
}
|
|
14126
14471
|
assertSyncableRepo(appDir);
|
|
14127
14472
|
const currentRaw = readAppSdkVersion(appDir);
|
|
@@ -14207,8 +14552,8 @@ var update_default = defineDeepspaceCommand({
|
|
|
14207
14552
|
|
|
14208
14553
|
// src/cli/cli.ts
|
|
14209
14554
|
var __dirname = dirname12(fileURLToPath3(import.meta.url));
|
|
14210
|
-
var pkg = JSON.parse(
|
|
14211
|
-
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")) {
|
|
14212
14557
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
14213
14558
|
}
|
|
14214
14559
|
var create2 = defineCommand14({
|