deepspace 0.18.0 → 0.19.0
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 +24 -0
- package/README.md +21 -0
- package/dist/cli.js +416 -83
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +93 -25
- package/dist/index.js.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +40 -9
- package/dist/server.js +151 -44
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +28 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +61 -9
- package/dist/worker.js +190 -44
- package/dist/worker.js.map +1 -1
- package/features/documentation/feature.json +2 -1
- package/features/documents/feature.json +1 -1
- package/features/documents/src/DocumentsEditorPage.tsx +16 -52
- package/features/documents/src/editor/useDocEditor.ts +1 -1
- package/features/documents/src/use-documents-presence-access.ts +1 -3
- package/features/tasks/src/TasksPage.tsx +6 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -342,7 +342,7 @@ function defineDeepspaceCommand(def) {
|
|
|
342
342
|
...def.args ?? {},
|
|
343
343
|
json: {
|
|
344
344
|
type: "boolean",
|
|
345
|
-
description: "Emit a single-line JSON result for scripts/agents",
|
|
345
|
+
description: def.jsonDescription ?? "Emit a single-line JSON result for scripts/agents",
|
|
346
346
|
default: false
|
|
347
347
|
}
|
|
348
348
|
};
|
|
@@ -2412,7 +2412,7 @@ function lintSchema(schema) {
|
|
|
2412
2412
|
if (schema.ownerField) {
|
|
2413
2413
|
const col = schema.columns.find((c) => c.name === schema.ownerField);
|
|
2414
2414
|
const spoofableRoles = Object.entries(schema.permissions).filter(
|
|
2415
|
-
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true
|
|
2415
|
+
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true && permissions.update !== "unclaimed-or-own"
|
|
2416
2416
|
).map(([role]) => role);
|
|
2417
2417
|
if (col && !col.userBound && spoofableRoles.length > 0) {
|
|
2418
2418
|
warnings.push(
|
|
@@ -3045,7 +3045,7 @@ function normalizeTestAccountScope(value) {
|
|
|
3045
3045
|
}
|
|
3046
3046
|
function loadAllTestAccounts(scope = currentTestAccountScope()) {
|
|
3047
3047
|
const store = readTestAccountStore();
|
|
3048
|
-
return
|
|
3048
|
+
return store.scopes[scope] ?? [];
|
|
3049
3049
|
}
|
|
3050
3050
|
function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
3051
3051
|
const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope);
|
|
@@ -3054,24 +3054,20 @@ function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
|
|
|
3054
3054
|
}
|
|
3055
3055
|
function reconcileTestAccountScopes(store, remote, scope) {
|
|
3056
3056
|
const scoped = store.scopes[scope] ?? [];
|
|
3057
|
-
const
|
|
3058
|
-
const migrated = reconcileTestAccounts(store.unscoped, remote);
|
|
3059
|
-
const accounts = mergeTestAccounts(current, migrated);
|
|
3057
|
+
const accounts = reconcileTestAccounts(scoped, remote);
|
|
3060
3058
|
return {
|
|
3061
3059
|
accounts,
|
|
3062
|
-
removed: Math.max(0, scoped.length -
|
|
3060
|
+
removed: Math.max(0, scoped.length - accounts.length),
|
|
3063
3061
|
store: {
|
|
3064
3062
|
version: 2,
|
|
3065
|
-
scopes: { ...store.scopes, [scope]: accounts }
|
|
3066
|
-
unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item)))
|
|
3063
|
+
scopes: { ...store.scopes, [scope]: accounts }
|
|
3067
3064
|
}
|
|
3068
3065
|
};
|
|
3069
3066
|
}
|
|
3070
3067
|
function reconcileTestAccounts(local, remote) {
|
|
3071
3068
|
const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
|
|
3072
|
-
const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]));
|
|
3073
3069
|
return remote.flatMap((account) => {
|
|
3074
|
-
const stored = localById.get(account.id)
|
|
3070
|
+
const stored = localById.get(account.id);
|
|
3075
3071
|
return stored ? [{ ...stored, ...account }] : [];
|
|
3076
3072
|
});
|
|
3077
3073
|
}
|
|
@@ -3082,10 +3078,7 @@ function upsertTestAccount(account, scope = currentTestAccountScope()) {
|
|
|
3082
3078
|
);
|
|
3083
3079
|
saveTestAccountStore({
|
|
3084
3080
|
version: 2,
|
|
3085
|
-
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3086
|
-
unscoped: store.unscoped.filter(
|
|
3087
|
-
(stored) => stored.id !== account.id && stored.email !== account.email
|
|
3088
|
-
)
|
|
3081
|
+
scopes: { ...store.scopes, [scope]: [...accounts, account] }
|
|
3089
3082
|
});
|
|
3090
3083
|
}
|
|
3091
3084
|
function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope()) {
|
|
@@ -3095,41 +3088,31 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
|
|
|
3095
3088
|
const store = readTestAccountStore();
|
|
3096
3089
|
saveTestAccountStore({
|
|
3097
3090
|
version: 2,
|
|
3098
|
-
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3099
|
-
unscoped: store.unscoped.filter(keep)
|
|
3091
|
+
scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) }
|
|
3100
3092
|
});
|
|
3101
3093
|
}
|
|
3102
3094
|
function readTestAccountStore() {
|
|
3103
|
-
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}
|
|
3095
|
+
if (!existsSync11(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
|
|
3104
3096
|
try {
|
|
3105
3097
|
const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
|
|
3106
|
-
if (Array.isArray(raw)) return { version: 2, scopes: {}
|
|
3107
|
-
if (!raw || typeof raw !== "object"
|
|
3108
|
-
return { version: 2, scopes: {}
|
|
3098
|
+
if (Array.isArray(raw)) return { version: 2, scopes: {} };
|
|
3099
|
+
if (!raw || typeof raw !== "object") {
|
|
3100
|
+
return { version: 2, scopes: {} };
|
|
3109
3101
|
}
|
|
3110
3102
|
const candidate = raw;
|
|
3111
3103
|
const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
|
|
3112
|
-
return { version: 2, scopes
|
|
3104
|
+
return { version: 2, scopes };
|
|
3113
3105
|
} catch {
|
|
3114
|
-
return { version: 2, scopes: {}
|
|
3106
|
+
return { version: 2, scopes: {} };
|
|
3115
3107
|
}
|
|
3116
3108
|
}
|
|
3117
3109
|
function validTestAccounts(value) {
|
|
3118
3110
|
if (!Array.isArray(value)) return [];
|
|
3119
|
-
return value.filter(
|
|
3120
|
-
item
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
3125
|
-
for (const account of groups.flat()) {
|
|
3126
|
-
const key = account.id ? `id:${account.id}` : `email:${account.email}`;
|
|
3127
|
-
if (!accounts.has(key)) accounts.set(key, account);
|
|
3128
|
-
}
|
|
3129
|
-
return [...accounts.values()];
|
|
3130
|
-
}
|
|
3131
|
-
function accountMatchesRemote(local, remote) {
|
|
3132
|
-
return local.id ? local.id === remote.id : local.email === remote.email;
|
|
3111
|
+
return value.filter(
|
|
3112
|
+
(item) => Boolean(
|
|
3113
|
+
item && typeof item === "object" && typeof item.id === "string" && typeof item.email === "string" && typeof item.password === "string"
|
|
3114
|
+
)
|
|
3115
|
+
);
|
|
3133
3116
|
}
|
|
3134
3117
|
function saveTestAccountStore(store) {
|
|
3135
3118
|
mkdirSync5(join13(homedir3(), ".deepspace"), { recursive: true });
|
|
@@ -3187,11 +3170,24 @@ async function syncTestAccountStore() {
|
|
|
3187
3170
|
|
|
3188
3171
|
// src/cli/commands/test.ts
|
|
3189
3172
|
var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
3173
|
+
var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
|
|
3174
|
+
function playwrightTestArgs(testFiles) {
|
|
3175
|
+
return [
|
|
3176
|
+
"playwright",
|
|
3177
|
+
"test",
|
|
3178
|
+
"--config",
|
|
3179
|
+
"tests/playwright.config.ts",
|
|
3180
|
+
"--output",
|
|
3181
|
+
PLAYWRIGHT_OUTPUT_DIR,
|
|
3182
|
+
...testFiles
|
|
3183
|
+
];
|
|
3184
|
+
}
|
|
3190
3185
|
var test_default = defineDeepspaceCommand({
|
|
3191
3186
|
meta: {
|
|
3192
3187
|
name: "test",
|
|
3193
3188
|
description: "Run tests for your DeepSpace app"
|
|
3194
3189
|
},
|
|
3190
|
+
jsonDescription: "Stream test output; emit the final JSON result on the last line",
|
|
3195
3191
|
args: {
|
|
3196
3192
|
suite: {
|
|
3197
3193
|
type: "positional",
|
|
@@ -3324,15 +3320,14 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv) {
|
|
|
3324
3320
|
return 1;
|
|
3325
3321
|
}
|
|
3326
3322
|
try {
|
|
3327
|
-
const result = spawnSync4(
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
{
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
);
|
|
3323
|
+
const result = spawnSync4("npx", playwrightTestArgs(testFiles), {
|
|
3324
|
+
cwd: appDir,
|
|
3325
|
+
stdio: "inherit",
|
|
3326
|
+
env: wranglerViteEnv(process.env, wranglerConfig, {
|
|
3327
|
+
DEEPSPACE_PORT: String(port),
|
|
3328
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
|
|
3329
|
+
})
|
|
3330
|
+
});
|
|
3336
3331
|
return result.status ?? 1;
|
|
3337
3332
|
} finally {
|
|
3338
3333
|
wranglerConfig.cleanup();
|
|
@@ -3850,7 +3845,7 @@ function describeFilesFailure(status3, body) {
|
|
|
3850
3845
|
import * as p6 from "@clack/prompts";
|
|
3851
3846
|
import { execSync as execSync2 } from "child_process";
|
|
3852
3847
|
import { createHash as createHash2 } from "crypto";
|
|
3853
|
-
import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
|
|
3848
|
+
import { chmodSync as chmodSync2, existsSync as existsSync14, lstatSync as lstatSync2, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
|
|
3854
3849
|
import { dirname as dirname6, join as join16, resolve as resolve11 } from "path";
|
|
3855
3850
|
|
|
3856
3851
|
// src/documentation/deploy.ts
|
|
@@ -3885,6 +3880,7 @@ import { join as join15 } from "path";
|
|
|
3885
3880
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3886
3881
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
3887
3882
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
3883
|
+
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
3888
3884
|
function validateAppMigrationIds(value) {
|
|
3889
3885
|
if (!Array.isArray(value)) {
|
|
3890
3886
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -3918,6 +3914,12 @@ var SOURCE_MIGRATIONS = [
|
|
|
3918
3914
|
description: "Let the worker answer misses, so deleted files 404 instead of serving HTML",
|
|
3919
3915
|
transform: migrateWorkerOwnedNotFound,
|
|
3920
3916
|
findBlockers: findWorkerOwnedNotFoundBlockers
|
|
3917
|
+
},
|
|
3918
|
+
{
|
|
3919
|
+
id: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
3920
|
+
description: "Secure room identity, job mutations, and production debug routes",
|
|
3921
|
+
transform: migrateSecureRoomBoundaries,
|
|
3922
|
+
findBlockers: findSecureRoomBoundaryBlockers
|
|
3921
3923
|
}
|
|
3922
3924
|
];
|
|
3923
3925
|
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
@@ -4061,6 +4063,248 @@ function isSourcePath(repoPath, appPrefix) {
|
|
|
4061
4063
|
if (repoPath.endsWith("wrangler.toml")) return true;
|
|
4062
4064
|
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
4063
4065
|
}
|
|
4066
|
+
var LEGACY_AUTHENTICATED_ROOM_HELPER = `const IDENTITY_QUERY_PARAMS = ['userId', 'userName', 'userEmail', 'userImageUrl', 'role'] as const
|
|
4067
|
+
|
|
4068
|
+
function authenticatedRoomUrl(
|
|
4069
|
+
requestUrl: string,
|
|
4070
|
+
auth: VerifyResult | null,
|
|
4071
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4072
|
+
): URL {
|
|
4073
|
+
const doUrl = new URL(requestUrl)
|
|
4074
|
+
doUrl.searchParams.delete('token')
|
|
4075
|
+
for (const key of IDENTITY_QUERY_PARAMS) {
|
|
4076
|
+
doUrl.searchParams.delete(key)
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
if (!auth) return doUrl
|
|
4080
|
+
|
|
4081
|
+
doUrl.searchParams.set('userId', auth.userId)
|
|
4082
|
+
if (auth.claims.name) doUrl.searchParams.set('userName', auth.claims.name)
|
|
4083
|
+
if (auth.claims.email) doUrl.searchParams.set('userEmail', auth.claims.email)
|
|
4084
|
+
if (auth.claims.image) doUrl.searchParams.set('userImageUrl', auth.claims.image)
|
|
4085
|
+
if (extraParams) {
|
|
4086
|
+
for (const [key, value] of Object.entries(extraParams(auth))) {
|
|
4087
|
+
doUrl.searchParams.set(key, value)
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
return doUrl
|
|
4091
|
+
}`;
|
|
4092
|
+
var IDENTITY_ROUTE_REPLACEMENTS = [
|
|
4093
|
+
[
|
|
4094
|
+
`import { verifyJwt } from 'deepspace/worker'`,
|
|
4095
|
+
`import { authenticatedRoomRequest, verifyJwt } from 'deepspace/worker'`,
|
|
4096
|
+
1
|
|
4097
|
+
],
|
|
4098
|
+
[LEGACY_AUTHENTICATED_ROOM_HELPER, "", 1],
|
|
4099
|
+
[
|
|
4100
|
+
`function wsRoute(
|
|
4101
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4102
|
+
extraParams?: (auth: VerifyResult) => Record<string, string>,
|
|
4103
|
+
) {`,
|
|
4104
|
+
`function wsRoute(
|
|
4105
|
+
doNamespace: (env: Env) => DurableObjectNamespace,
|
|
4106
|
+
extraIdentity?: (auth: VerifyResult) => { role?: string },
|
|
4107
|
+
) {`,
|
|
4108
|
+
1
|
|
4109
|
+
],
|
|
4110
|
+
[
|
|
4111
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, extraParams)`,
|
|
4112
|
+
` const roomRequest = authenticatedRoomRequest(
|
|
4113
|
+
c.req.raw,
|
|
4114
|
+
auth,
|
|
4115
|
+
auth ? extraIdentity?.(auth) : undefined,
|
|
4116
|
+
)`,
|
|
4117
|
+
1
|
|
4118
|
+
],
|
|
4119
|
+
[
|
|
4120
|
+
` const doUrl = authenticatedRoomUrl(c.req.url, auth, () => ({ role }))`,
|
|
4121
|
+
` const roomRequest = authenticatedRoomRequest(c.req.raw, auth, { role })`,
|
|
4122
|
+
1
|
|
4123
|
+
],
|
|
4124
|
+
[
|
|
4125
|
+
` return stub.fetch(new Request(doUrl.toString(), c.req.raw))`,
|
|
4126
|
+
` return stub.fetch(roomRequest)`,
|
|
4127
|
+
2
|
|
4128
|
+
]
|
|
4129
|
+
];
|
|
4130
|
+
function occurrences(source, pattern) {
|
|
4131
|
+
return source.split(pattern).length - 1;
|
|
4132
|
+
}
|
|
4133
|
+
var SECURE_JOB_ROOM_CONSTRUCTOR = ` constructor(state: DurableObjectState, env: Env) {
|
|
4134
|
+
super(state, env, {
|
|
4135
|
+
authorizeWrite: async (user) => {
|
|
4136
|
+
if (user.userId.startsWith('anon-')) return false
|
|
4137
|
+
const role = await resolveAppRole(env, user.userId)
|
|
4138
|
+
return role === 'member' || role === 'admin'
|
|
4139
|
+
},
|
|
4140
|
+
})
|
|
4141
|
+
}`;
|
|
4142
|
+
var APP_JOB_ROOM_CLASS = `export class AppJobRoom extends JobRoom<Env> {`;
|
|
4143
|
+
var SECURE_APP_JOB_ROOM = `${APP_JOB_ROOM_CLASS}
|
|
4144
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`;
|
|
4145
|
+
function hasSecureAppJobRoom(source) {
|
|
4146
|
+
return occurrences(source, "class AppJobRoom") === 1 && source.includes(SECURE_APP_JOB_ROOM);
|
|
4147
|
+
}
|
|
4148
|
+
var WORKER_JOB_REPLACEMENTS = [
|
|
4149
|
+
[
|
|
4150
|
+
`import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`,
|
|
4151
|
+
`import { resolveAppRole } from 'deepspace/worker'
|
|
4152
|
+
import type { DOBindings, DOManifest, Job, JobContext } from 'deepspace/worker'`
|
|
4153
|
+
],
|
|
4154
|
+
[
|
|
4155
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4156
|
+
constructor(state: DurableObjectState, env: Env) {
|
|
4157
|
+
super(state, env)
|
|
4158
|
+
}`,
|
|
4159
|
+
`export class AppJobRoom extends JobRoom<Env> {
|
|
4160
|
+
${SECURE_JOB_ROOM_CONSTRUCTOR}`
|
|
4161
|
+
]
|
|
4162
|
+
];
|
|
4163
|
+
var HTTP_ROLE_IMPORT = `import { resolveAppRole } from 'deepspace/worker'`;
|
|
4164
|
+
var HTTP_ROLE_IMPORT_SEAM = `import type { AppContext, Env } from '../../worker.js'`;
|
|
4165
|
+
var DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4166
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4167
|
+
return c.notFound()
|
|
4168
|
+
}
|
|
4169
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4170
|
+
var DOUBLE_QUOTED_DEBUG_ROUTE_SEAM = DEBUG_ROUTE_SEAM.replace(
|
|
4171
|
+
"app.all('/api/debug/*'",
|
|
4172
|
+
'app.all("/api/debug/*"'
|
|
4173
|
+
);
|
|
4174
|
+
var SECURE_DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
|
|
4175
|
+
if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
|
|
4176
|
+
return c.notFound()
|
|
4177
|
+
}
|
|
4178
|
+
const auth = await resolveAuth(c.req.raw, c.env)
|
|
4179
|
+
if (!auth) return c.json({ error: 'unauthorized' }, 401)
|
|
4180
|
+
if ((await resolveAppRole(c.env, auth.userId)) !== 'admin') {
|
|
4181
|
+
return c.json({ error: 'forbidden' }, 403)
|
|
4182
|
+
}
|
|
4183
|
+
const stub = c.env.RECORD_ROOMS.get(`;
|
|
4184
|
+
var DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM = SECURE_DEBUG_ROUTE_SEAM.replace(
|
|
4185
|
+
"app.all('/api/debug/*'",
|
|
4186
|
+
'app.all("/api/debug/*"'
|
|
4187
|
+
);
|
|
4188
|
+
var DEBUG_ROUTE_REPLACEMENTS = [
|
|
4189
|
+
[DEBUG_ROUTE_SEAM, SECURE_DEBUG_ROUTE_SEAM],
|
|
4190
|
+
[DOUBLE_QUOTED_DEBUG_ROUTE_SEAM, DOUBLE_QUOTED_SECURE_DEBUG_ROUTE_SEAM]
|
|
4191
|
+
];
|
|
4192
|
+
var DEBUG_ROUTE_PATTERN = /\bapp\.all\(\s*(['"])\/api\/debug\/\*\1\s*,/;
|
|
4193
|
+
function debugRouteOccurrences(source) {
|
|
4194
|
+
return source.match(new RegExp(DEBUG_ROUTE_PATTERN.source, "g"))?.length ?? 0;
|
|
4195
|
+
}
|
|
4196
|
+
function secureDebugRouteOccurrences(source) {
|
|
4197
|
+
return DEBUG_ROUTE_REPLACEMENTS.reduce(
|
|
4198
|
+
(count, [, secure]) => count + occurrences(source, secure),
|
|
4199
|
+
0
|
|
4200
|
+
);
|
|
4201
|
+
}
|
|
4202
|
+
function hasSecureDebugRoute(source) {
|
|
4203
|
+
return debugRouteOccurrences(source) === 1 && secureDebugRouteOccurrences(source) === 1 && occurrences(source, HTTP_ROLE_IMPORT) === 1;
|
|
4204
|
+
}
|
|
4205
|
+
function migrateSecureRoomBoundaries(source, file) {
|
|
4206
|
+
if (file.endsWith("src/server/realtime-routes.ts")) {
|
|
4207
|
+
return migrateRealtimeRoomBoundaries(source);
|
|
4208
|
+
}
|
|
4209
|
+
if (file.endsWith("worker.ts")) return migrateWorkerJobAuthorization(source);
|
|
4210
|
+
if (file.endsWith("src/server/http-routes.ts")) return migrateDebugRouteAuthorization(source);
|
|
4211
|
+
return { source, replacements: 0 };
|
|
4212
|
+
}
|
|
4213
|
+
function migrateRealtimeRoomBoundaries(source) {
|
|
4214
|
+
let migrated = source;
|
|
4215
|
+
let replacements = 0;
|
|
4216
|
+
if (!migrated.includes("authenticatedRoomRequest(")) {
|
|
4217
|
+
if (IDENTITY_ROUTE_REPLACEMENTS.some(
|
|
4218
|
+
([before, , expected]) => occurrences(migrated, before) !== expected
|
|
4219
|
+
)) {
|
|
4220
|
+
return { source, replacements: 0 };
|
|
4221
|
+
}
|
|
4222
|
+
for (const [before, after, expected] of IDENTITY_ROUTE_REPLACEMENTS) {
|
|
4223
|
+
migrated = migrated.replaceAll(before, after);
|
|
4224
|
+
replacements += expected;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return { source: migrated, replacements };
|
|
4228
|
+
}
|
|
4229
|
+
function migrateWorkerJobAuthorization(source) {
|
|
4230
|
+
if (!source.includes("class AppJobRoom")) return { source, replacements: 0 };
|
|
4231
|
+
if (hasSecureAppJobRoom(source)) return { source, replacements: 0 };
|
|
4232
|
+
if (WORKER_JOB_REPLACEMENTS.some(([before]) => occurrences(source, before) !== 1)) {
|
|
4233
|
+
return { source, replacements: 0 };
|
|
4234
|
+
}
|
|
4235
|
+
let migrated = source;
|
|
4236
|
+
for (const [before, after] of WORKER_JOB_REPLACEMENTS) migrated = migrated.replace(before, after);
|
|
4237
|
+
return { source: migrated, replacements: WORKER_JOB_REPLACEMENTS.length };
|
|
4238
|
+
}
|
|
4239
|
+
function migrateDebugRouteAuthorization(source) {
|
|
4240
|
+
if (debugRouteOccurrences(source) === 0 || hasSecureDebugRoute(source)) {
|
|
4241
|
+
return { source, replacements: 0 };
|
|
4242
|
+
}
|
|
4243
|
+
const matchingRoutes = DEBUG_ROUTE_REPLACEMENTS.filter(
|
|
4244
|
+
([before2]) => occurrences(source, before2) === 1
|
|
4245
|
+
);
|
|
4246
|
+
if (debugRouteOccurrences(source) !== 1 || matchingRoutes.length !== 1 || occurrences(source, HTTP_ROLE_IMPORT) !== 0 || occurrences(source, HTTP_ROLE_IMPORT_SEAM) !== 1) {
|
|
4247
|
+
return { source, replacements: 0 };
|
|
4248
|
+
}
|
|
4249
|
+
const [before, after] = matchingRoutes[0];
|
|
4250
|
+
return {
|
|
4251
|
+
source: source.replace(HTTP_ROLE_IMPORT_SEAM, `${HTTP_ROLE_IMPORT_SEAM}
|
|
4252
|
+
${HTTP_ROLE_IMPORT}`).replace(before, after),
|
|
4253
|
+
replacements: 2
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
4256
|
+
function findSecureRoomBoundaryBlockers(source, file) {
|
|
4257
|
+
if (file.endsWith("worker.ts") && source.includes("class AppJobRoom")) {
|
|
4258
|
+
if (!hasSecureAppJobRoom(source)) {
|
|
4259
|
+
return [
|
|
4260
|
+
roomBoundaryBlocker(
|
|
4261
|
+
file,
|
|
4262
|
+
source,
|
|
4263
|
+
"class AppJobRoom",
|
|
4264
|
+
"Configure AppJobRoom with authorizeWrite using the current users.role."
|
|
4265
|
+
)
|
|
4266
|
+
];
|
|
4267
|
+
}
|
|
4268
|
+
return [];
|
|
4269
|
+
}
|
|
4270
|
+
if (file.endsWith("src/server/http-routes.ts") && debugRouteOccurrences(source) > 0) {
|
|
4271
|
+
if (!hasSecureDebugRoute(source)) {
|
|
4272
|
+
const route = source.match(DEBUG_ROUTE_PATTERN)?.[0] ?? "/api/debug/*";
|
|
4273
|
+
return [
|
|
4274
|
+
roomBoundaryBlocker(
|
|
4275
|
+
file,
|
|
4276
|
+
source,
|
|
4277
|
+
route,
|
|
4278
|
+
"Require verified owner/admin access inside the enabled debug-route handler."
|
|
4279
|
+
)
|
|
4280
|
+
];
|
|
4281
|
+
}
|
|
4282
|
+
return [];
|
|
4283
|
+
}
|
|
4284
|
+
if (!file.endsWith("src/server/realtime-routes.ts")) return [];
|
|
4285
|
+
const lines = source.split("\n");
|
|
4286
|
+
let index = lines.findIndex(
|
|
4287
|
+
(line) => line.includes("authenticatedRoomUrl") || /searchParams\.(?:set|append)\(\s*['"](?:userId|userName|userEmail|userImageUrl|role)['"]/.test(
|
|
4288
|
+
line
|
|
4289
|
+
)
|
|
4290
|
+
);
|
|
4291
|
+
if (index < 0 && /\bauth\.(?:userId|claims)\b/.test(source)) {
|
|
4292
|
+
index = lines.findIndex((line) => /searchParams\.(?:set|append)\s*\(/.test(line));
|
|
4293
|
+
}
|
|
4294
|
+
if (index < 0) return [];
|
|
4295
|
+
return [
|
|
4296
|
+
{
|
|
4297
|
+
migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
4298
|
+
file,
|
|
4299
|
+
line: index + 1,
|
|
4300
|
+
message: "This customized room proxy still places verified user identity in its internal URL. Replace that forwarding code with authenticatedRoomRequest() from deepspace/worker before the Durable Object fetch."
|
|
4301
|
+
}
|
|
4302
|
+
];
|
|
4303
|
+
}
|
|
4304
|
+
function roomBoundaryBlocker(file, source, seam, message) {
|
|
4305
|
+
const line = source.slice(0, source.indexOf(seam)).split("\n").length;
|
|
4306
|
+
return { migrationId: SECURE_ROOM_BOUNDARIES_MIGRATION_ID, file, line, message };
|
|
4307
|
+
}
|
|
4064
4308
|
function migrateWorkerOwnedNotFound(source, file) {
|
|
4065
4309
|
let replacements = 0;
|
|
4066
4310
|
if (file.endsWith("wrangler.toml")) {
|
|
@@ -4126,6 +4370,16 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
|
|
|
4126
4370
|
|
|
4127
4371
|
// src/cli/commands/deploy/build.ts
|
|
4128
4372
|
var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
|
|
4373
|
+
function secureBuildDevVars(workerDir) {
|
|
4374
|
+
const path = join16(workerDir, ".dev.vars");
|
|
4375
|
+
if (!existsSync14(path)) return false;
|
|
4376
|
+
const stat = lstatSync2(path);
|
|
4377
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
4378
|
+
throw new Error(`Refusing unsafe build secret path: ${path}`);
|
|
4379
|
+
}
|
|
4380
|
+
chmodSync2(path, 384);
|
|
4381
|
+
return true;
|
|
4382
|
+
}
|
|
4129
4383
|
function declaredCompatibilityDate(config) {
|
|
4130
4384
|
const declared = config.compatibility_date;
|
|
4131
4385
|
return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
|
|
@@ -4182,6 +4436,11 @@ async function buildDeployBundle(options) {
|
|
|
4182
4436
|
);
|
|
4183
4437
|
}
|
|
4184
4438
|
const workerDir = dirname6(outputWranglerPath);
|
|
4439
|
+
try {
|
|
4440
|
+
secureBuildDevVars(workerDir);
|
|
4441
|
+
} catch (error) {
|
|
4442
|
+
output.die(errorMessage(error), "build_output_unsafe");
|
|
4443
|
+
}
|
|
4185
4444
|
const workerBundlePath = join16(workerDir, outputConfig.main);
|
|
4186
4445
|
const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
|
|
4187
4446
|
if (!existsSync14(workerBundlePath)) {
|
|
@@ -4455,7 +4714,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
4455
4714
|
spinner2.stop(`Synced ${plans.length} plan${plans.length === 1 ? "" : "s"} to Stripe`);
|
|
4456
4715
|
if (body.connectMissing) {
|
|
4457
4716
|
p7.log.warn(
|
|
4458
|
-
"Stripe Connect not
|
|
4717
|
+
"Stripe Connect is not ready. Paid checkout will remain unavailable until the app owner finishes onboarding at /earnings on the dashboard."
|
|
4459
4718
|
);
|
|
4460
4719
|
}
|
|
4461
4720
|
const changes = (body.planChanges ?? []).filter((change) => change.affectedSubscribers > 0);
|
|
@@ -5096,7 +5355,16 @@ function selectGitHubRemote(cwd, options = {}) {
|
|
|
5096
5355
|
return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
|
|
5097
5356
|
}
|
|
5098
5357
|
function remotePublicRefs(cwd, remote) {
|
|
5099
|
-
|
|
5358
|
+
let output;
|
|
5359
|
+
try {
|
|
5360
|
+
output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
|
|
5361
|
+
} catch (error) {
|
|
5362
|
+
if (!isGitCredentialFailure(error)) throw error;
|
|
5363
|
+
throw new GitError(
|
|
5364
|
+
"Git could not authenticate the remote. In a container, forward an SSH agent or use an ephemeral Git credential helper, then retry. Do not put tokens in remote URLs.",
|
|
5365
|
+
"github_credentials_required"
|
|
5366
|
+
);
|
|
5367
|
+
}
|
|
5100
5368
|
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
5101
5369
|
const [oid, name] = line.split(/\s+/, 2);
|
|
5102
5370
|
return { oid, name };
|
|
@@ -5104,6 +5372,12 @@ function remotePublicRefs(cwd, remote) {
|
|
|
5104
5372
|
(ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
|
|
5105
5373
|
);
|
|
5106
5374
|
}
|
|
5375
|
+
function isGitCredentialFailure(error) {
|
|
5376
|
+
if (!(error instanceof Error)) return false;
|
|
5377
|
+
return /authentication failed|could not read username|permission denied \(publickey\)|repository not found|terminal prompts disabled/i.test(
|
|
5378
|
+
error.message
|
|
5379
|
+
);
|
|
5380
|
+
}
|
|
5107
5381
|
function remoteBranchOid(cwd, remote, branch) {
|
|
5108
5382
|
return remotePublicRefs(cwd, remote).find((ref) => ref.name === `refs/heads/${branch}`)?.oid ?? null;
|
|
5109
5383
|
}
|
|
@@ -5145,6 +5419,25 @@ function setAppSource(deployUrl, token, appId, input) {
|
|
|
5145
5419
|
}
|
|
5146
5420
|
|
|
5147
5421
|
// src/cli/commands/deploy/repository.ts
|
|
5422
|
+
function preflightDeployRepository(options) {
|
|
5423
|
+
const { appDir, push, source } = options;
|
|
5424
|
+
if (!push || source?.provider === "github") return null;
|
|
5425
|
+
try {
|
|
5426
|
+
assertSyncableRepo(appDir);
|
|
5427
|
+
if (!source && listGitHubRemotes(appDir).length > 0) {
|
|
5428
|
+
return {
|
|
5429
|
+
code: "source_unclaimed",
|
|
5430
|
+
error: "This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again."
|
|
5431
|
+
};
|
|
5432
|
+
}
|
|
5433
|
+
const branch = currentBranch(appDir);
|
|
5434
|
+
if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch);
|
|
5435
|
+
if (!branch) return detachedHeadRefusal();
|
|
5436
|
+
return null;
|
|
5437
|
+
} catch (error) {
|
|
5438
|
+
return deployRepositoryFailure(error, appDir);
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5148
5441
|
async function syncDeployRepository(options) {
|
|
5149
5442
|
const { deployUrl, appDir, appId, token, push, ignoreStale, output, sourceState } = options;
|
|
5150
5443
|
let commitOid = null;
|
|
@@ -5165,21 +5458,12 @@ async function syncDeployRepository(options) {
|
|
|
5165
5458
|
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
5166
5459
|
}
|
|
5167
5460
|
try {
|
|
5168
|
-
|
|
5169
|
-
if (
|
|
5170
|
-
output.die(
|
|
5171
|
-
"This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again.",
|
|
5172
|
-
"source_unclaimed"
|
|
5173
|
-
);
|
|
5174
|
-
}
|
|
5461
|
+
const preflight = preflightDeployRepository({ appDir, push, source });
|
|
5462
|
+
if (preflight) output.die(preflight.error, preflight.code);
|
|
5175
5463
|
const sourceRemote = spaceRemoteName();
|
|
5176
5464
|
ensureSpaceRemote(appDir, appId, sourceRemote);
|
|
5177
5465
|
const branch = currentBranch(appDir);
|
|
5178
5466
|
const workspaceBranchId = workspaceIdFromBranch(branch);
|
|
5179
|
-
if (!isWorkTreeClean(appDir)) {
|
|
5180
|
-
const refusal = dirtyWorktreeRefusal(branch);
|
|
5181
|
-
output.die(refusal.error, refusal.code);
|
|
5182
|
-
}
|
|
5183
5467
|
if (!branch) {
|
|
5184
5468
|
const refusal = detachedHeadRefusal();
|
|
5185
5469
|
output.die(refusal.error, refusal.code);
|
|
@@ -5281,12 +5565,7 @@ function classifyRemoteState(appDir, token, branch, localTip, remote) {
|
|
|
5281
5565
|
let remoteTip = null;
|
|
5282
5566
|
const remoteRef = `refs/remotes/${remote}/${branch}`;
|
|
5283
5567
|
try {
|
|
5284
|
-
runGitRemote(appDir, token, [
|
|
5285
|
-
"fetch",
|
|
5286
|
-
"--quiet",
|
|
5287
|
-
remote,
|
|
5288
|
-
`+refs/heads/${branch}:${remoteRef}`
|
|
5289
|
-
]);
|
|
5568
|
+
runGitRemote(appDir, token, ["fetch", "--quiet", remote, `+refs/heads/${branch}:${remoteRef}`]);
|
|
5290
5569
|
remoteTip = resolveCommit(appDir, remoteRef);
|
|
5291
5570
|
return {
|
|
5292
5571
|
remoteTip,
|
|
@@ -5860,6 +6139,14 @@ var deploy_default = defineCommand3({
|
|
|
5860
6139
|
output
|
|
5861
6140
|
});
|
|
5862
6141
|
const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
|
|
6142
|
+
const repositoryPreflight = preflightDeployRepository({
|
|
6143
|
+
appDir,
|
|
6144
|
+
push: args.push !== false,
|
|
6145
|
+
source: sourceState.source
|
|
6146
|
+
});
|
|
6147
|
+
if (repositoryPreflight) {
|
|
6148
|
+
output.die(repositoryPreflight.error, repositoryPreflight.code);
|
|
6149
|
+
}
|
|
5863
6150
|
const spinner2 = createSpinner();
|
|
5864
6151
|
const bundle = await buildDeployBundle({
|
|
5865
6152
|
appDir,
|
|
@@ -6322,11 +6609,17 @@ function parseSince(input, now = Date.now()) {
|
|
|
6322
6609
|
} else {
|
|
6323
6610
|
ts = Date.parse(trimmed);
|
|
6324
6611
|
if (Number.isNaN(ts)) {
|
|
6325
|
-
throw new InputError(
|
|
6612
|
+
throw new InputError(
|
|
6613
|
+
`Invalid --since "${input}" \u2014 use 30s, 15m, 2h, 7d, or an ISO timestamp.`,
|
|
6614
|
+
"invalid_since"
|
|
6615
|
+
);
|
|
6326
6616
|
}
|
|
6327
6617
|
}
|
|
6328
6618
|
if (now - ts > RETENTION_MS) {
|
|
6329
|
-
throw new InputError(
|
|
6619
|
+
throw new InputError(
|
|
6620
|
+
"Logs are retained for 7 days \u2014 --since can reach back at most 7d.",
|
|
6621
|
+
"since_out_of_range"
|
|
6622
|
+
);
|
|
6330
6623
|
}
|
|
6331
6624
|
return ts;
|
|
6332
6625
|
}
|
|
@@ -6457,13 +6750,19 @@ var logs_default = defineCommand4({
|
|
|
6457
6750
|
const token = await ensureToken();
|
|
6458
6751
|
const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
|
|
6459
6752
|
if (args.level && !LOG_LEVELS.includes(args.level)) {
|
|
6460
|
-
throw new InputError(
|
|
6753
|
+
throw new InputError(
|
|
6754
|
+
`Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`,
|
|
6755
|
+
"invalid_level"
|
|
6756
|
+
);
|
|
6461
6757
|
}
|
|
6462
6758
|
let limit;
|
|
6463
6759
|
if (args.limit !== void 0) {
|
|
6464
6760
|
limit = Number(args.limit);
|
|
6465
6761
|
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
6466
|
-
throw new InputError(
|
|
6762
|
+
throw new InputError(
|
|
6763
|
+
"Invalid --limit \u2014 expected an integer between 1 and 500.",
|
|
6764
|
+
"invalid_limit"
|
|
6765
|
+
);
|
|
6467
6766
|
}
|
|
6468
6767
|
}
|
|
6469
6768
|
const initialSince = args.since ? parseSince(args.since) : Date.now() - DEFAULT_WINDOW_MS;
|
|
@@ -6479,7 +6778,9 @@ var logs_default = defineCommand4({
|
|
|
6479
6778
|
};
|
|
6480
6779
|
const print = (events) => {
|
|
6481
6780
|
for (const e of events) {
|
|
6482
|
-
process.stdout.write(
|
|
6781
|
+
process.stdout.write(
|
|
6782
|
+
args.json ? JSON.stringify(e) + "\n" : formatEvent(e, stdoutColor()) + "\n"
|
|
6783
|
+
);
|
|
6483
6784
|
}
|
|
6484
6785
|
};
|
|
6485
6786
|
const first = await fetchPage(initialSince, args.follow ? followLimit : limit, token);
|
|
@@ -6487,15 +6788,25 @@ var logs_default = defineCommand4({
|
|
|
6487
6788
|
if (!args.follow) {
|
|
6488
6789
|
if (first.events.length === 0) {
|
|
6489
6790
|
if (args.json) {
|
|
6490
|
-
process.stdout.write(
|
|
6791
|
+
process.stdout.write(
|
|
6792
|
+
JSON.stringify({ type: "meta", count: 0, window: windowLabel }) + "\n"
|
|
6793
|
+
);
|
|
6491
6794
|
} else {
|
|
6492
6795
|
console.log(
|
|
6493
6796
|
`No logs in the last ${windowLabel} for ${appId}. New logs can take up to a minute to appear; retention is 7 days.`
|
|
6494
6797
|
);
|
|
6495
6798
|
}
|
|
6496
6799
|
} else if (first.truncated) {
|
|
6497
|
-
if (args.json)
|
|
6498
|
-
|
|
6800
|
+
if (args.json)
|
|
6801
|
+
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6802
|
+
else if (args.search)
|
|
6803
|
+
console.error(
|
|
6804
|
+
`(showing the newest ${first.events.length} matches \u2014 search scans at most the newest 500 level-filtered events per fetch; narrow with --since/--level or raise --limit)`
|
|
6805
|
+
);
|
|
6806
|
+
else
|
|
6807
|
+
console.error(
|
|
6808
|
+
`(showing the newest ${first.events.length} events \u2014 narrow with --since/--level)`
|
|
6809
|
+
);
|
|
6499
6810
|
}
|
|
6500
6811
|
return;
|
|
6501
6812
|
}
|
|
@@ -6515,7 +6826,11 @@ var logs_default = defineCommand4({
|
|
|
6515
6826
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6516
6827
|
} else {
|
|
6517
6828
|
console.error(
|
|
6518
|
-
paint(
|
|
6829
|
+
paint(
|
|
6830
|
+
"33",
|
|
6831
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events in the initial ${windowLabel} window \u2014 older matches may be omitted; narrow with --since/--level)` : `(more than ${followLimit} events in the initial ${windowLabel} window \u2014 some older events not shown; narrow with --since/--level)`,
|
|
6832
|
+
stderrColor()
|
|
6833
|
+
)
|
|
6519
6834
|
);
|
|
6520
6835
|
}
|
|
6521
6836
|
}
|
|
@@ -6529,21 +6844,27 @@ var logs_default = defineCommand4({
|
|
|
6529
6844
|
if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;
|
|
6530
6845
|
backoff = Math.min(backoff ? backoff * 2 : MIN_POLL_MS, 3e4);
|
|
6531
6846
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6532
|
-
console.error(
|
|
6847
|
+
console.error(
|
|
6848
|
+
paint("2", `(logs fetch failed: ${msg} \u2014 retrying in ${backoff / 1e3}s)`, stderrColor())
|
|
6849
|
+
);
|
|
6533
6850
|
continue;
|
|
6534
6851
|
}
|
|
6535
6852
|
backoff = 0;
|
|
6536
6853
|
interval = Math.max(page.pollIntervalMs || 0, MIN_POLL_MS);
|
|
6537
6854
|
const fresh = seen.fresh(page.events);
|
|
6538
6855
|
print(fresh);
|
|
6539
|
-
const realDrop = fresh.length >= followLimit;
|
|
6856
|
+
const realDrop = fresh.length >= followLimit || Boolean(args.search && page.truncated);
|
|
6540
6857
|
if (realDrop && !warnedTruncated) {
|
|
6541
6858
|
warnedTruncated = true;
|
|
6542
6859
|
if (args.json) {
|
|
6543
6860
|
process.stdout.write(JSON.stringify({ type: "meta", truncated: true }) + "\n");
|
|
6544
6861
|
} else {
|
|
6545
6862
|
console.error(
|
|
6546
|
-
paint(
|
|
6863
|
+
paint(
|
|
6864
|
+
"33",
|
|
6865
|
+
args.search ? `(search scanned only the newest ${MAX_LIMIT} level-filtered events this poll \u2014 older matches may be omitted; narrow with --level)` : `(burst exceeded ${followLimit} events/poll \u2014 some may be dropped; narrow with --level)`,
|
|
6866
|
+
stderrColor()
|
|
6867
|
+
)
|
|
6547
6868
|
);
|
|
6548
6869
|
}
|
|
6549
6870
|
} else if (!realDrop) {
|
|
@@ -9368,7 +9689,7 @@ function ok(json, data, human) {
|
|
|
9368
9689
|
function warnIfDebugRoutesEnabled(secrets) {
|
|
9369
9690
|
if (secrets.ALLOW_DEBUG_ROUTES !== "true") return false;
|
|
9370
9691
|
console.warn(
|
|
9371
|
-
"\n\u26A0 ALLOW_DEBUG_ROUTES=true
|
|
9692
|
+
"\n\u26A0 ALLOW_DEBUG_ROUTES=true enables the owner/admin debug API on the deployed app \u2014\n those users can read and mutate any app record through /api/debug/*. Redeploy to apply;\n `deepspace secrets delete ALLOW_DEBUG_ROUTES` (then redeploy) turns it back off."
|
|
9372
9693
|
);
|
|
9373
9694
|
return true;
|
|
9374
9695
|
}
|
|
@@ -11137,6 +11458,11 @@ function appDirInWorktree(appDir, worktreeRoot) {
|
|
|
11137
11458
|
const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
|
|
11138
11459
|
return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
|
|
11139
11460
|
}
|
|
11461
|
+
function primaryAppDir(appDir) {
|
|
11462
|
+
const primary = listWorktrees(appDir)[0];
|
|
11463
|
+
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
11464
|
+
return appDirInWorktree(appDir, primary.path);
|
|
11465
|
+
}
|
|
11140
11466
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
11141
11467
|
const createdOid = resolveCommit(appDir, startPoint);
|
|
11142
11468
|
if (!createdOid) throw new Error(`Could not resolve workspace start point ${startPoint}`);
|
|
@@ -12163,6 +12489,11 @@ function conflictMarkerFiles(diffCheckOutput) {
|
|
|
12163
12489
|
}
|
|
12164
12490
|
return [...files];
|
|
12165
12491
|
}
|
|
12492
|
+
function pullAfterLandAction(primaryDir, intoBranch, landedOid) {
|
|
12493
|
+
if (currentBranch(primaryDir) !== intoBranch) return null;
|
|
12494
|
+
if (resolveCommit(primaryDir, "HEAD") === landedOid) return null;
|
|
12495
|
+
return { cwd: primaryDir, argv: ["deepspace", "pull"] };
|
|
12496
|
+
}
|
|
12166
12497
|
function landResumeArgv(args) {
|
|
12167
12498
|
const argv = ["deepspace", "workspace", "land"];
|
|
12168
12499
|
const workspace = args.workspace?.trim();
|
|
@@ -12441,6 +12772,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12441
12772
|
);
|
|
12442
12773
|
}
|
|
12443
12774
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
12775
|
+
const primaryDir = primaryAppDir(appDir);
|
|
12444
12776
|
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
12445
12777
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
12446
12778
|
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
|
|
@@ -12458,7 +12790,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12458
12790
|
p30.log.info(`Retained checkout: ${retainedWorktree}`);
|
|
12459
12791
|
}
|
|
12460
12792
|
if (cleanup?.error) {
|
|
12461
|
-
const
|
|
12793
|
+
const action2 = cleanupAction({
|
|
12462
12794
|
worktreeDir: cleanup.worktreeDir,
|
|
12463
12795
|
branch: cleanup.branch
|
|
12464
12796
|
});
|
|
@@ -12469,10 +12801,11 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
12469
12801
|
branch: cleanup.branch
|
|
12470
12802
|
}),
|
|
12471
12803
|
"cleanup_incomplete",
|
|
12472
|
-
{ actionRequired: true, action, extra: data }
|
|
12804
|
+
{ actionRequired: true, action: action2, extra: data }
|
|
12473
12805
|
);
|
|
12474
12806
|
}
|
|
12475
|
-
|
|
12807
|
+
const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
|
|
12808
|
+
return { data, ...action ? { action } : {} };
|
|
12476
12809
|
}
|
|
12477
12810
|
});
|
|
12478
12811
|
|
|
@@ -12912,7 +13245,7 @@ var source_default = defineDeepspaceCommand({
|
|
|
12912
13245
|
args: {
|
|
12913
13246
|
provider: {
|
|
12914
13247
|
type: "positional",
|
|
12915
|
-
description: "Source
|
|
13248
|
+
description: "Source authority: deepspace is platform-hosted and commit-first; github deploys the local working tree without Git sync (omit to show)",
|
|
12916
13249
|
required: false
|
|
12917
13250
|
},
|
|
12918
13251
|
app: {
|