deepspace 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +24 -11
- package/dist/cli.js +632 -307
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -1
- package/dist/server.js +54 -12
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +13 -1
- package/dist/worker.js +85 -12
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/cli/cli.ts
|
|
4
4
|
import { defineCommand as defineCommand14, runMain, runCommand } from "citty";
|
|
5
5
|
import { sync as spawnSync8 } from "cross-spawn";
|
|
6
|
-
import { readFileSync as
|
|
7
|
-
import { dirname as dirname10, join as
|
|
6
|
+
import { readFileSync as readFileSync26, existsSync as existsSync25 } from "fs";
|
|
7
|
+
import { dirname as dirname10, join as join24 } from "path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/cli/lib/spinner.ts
|
|
@@ -788,7 +788,6 @@ import { parse as parseToml } from "smol-toml";
|
|
|
788
788
|
// src/server/utils/registry-client.ts
|
|
789
789
|
var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
790
790
|
var STRICT_APP_ID_RE = APP_ID_RE;
|
|
791
|
-
var LEGACY_APP_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
792
791
|
|
|
793
792
|
// src/cli/lib/app-identity.ts
|
|
794
793
|
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
@@ -817,7 +816,7 @@ function mintUlid(now = Date.now()) {
|
|
|
817
816
|
function mintAppId(now = Date.now()) {
|
|
818
817
|
return `app_${mintUlid(now)}`;
|
|
819
818
|
}
|
|
820
|
-
function
|
|
819
|
+
function readWranglerIdentityConfig(cwd) {
|
|
821
820
|
const wranglerPath = join3(resolve(cwd), "wrangler.toml");
|
|
822
821
|
if (!existsSync3(wranglerPath)) return null;
|
|
823
822
|
try {
|
|
@@ -830,46 +829,12 @@ function readWranglerConfig(cwd) {
|
|
|
830
829
|
}
|
|
831
830
|
}
|
|
832
831
|
function readAppId(cwd = process.cwd(), wranglerEnv) {
|
|
833
|
-
const cfg =
|
|
832
|
+
const cfg = readWranglerIdentityConfig(cwd);
|
|
834
833
|
if (!cfg) return null;
|
|
835
834
|
const vars = wranglerEnv ? cfg.env?.[wranglerEnv]?.vars : cfg.vars;
|
|
836
835
|
const id = vars?.DEEPSPACE_APP_ID;
|
|
837
836
|
return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
|
|
838
837
|
}
|
|
839
|
-
function readLegacyAppId(cwd = process.cwd(), wranglerEnv) {
|
|
840
|
-
const cfg = readWranglerConfig(cwd);
|
|
841
|
-
if (!cfg) return null;
|
|
842
|
-
const slot = wranglerEnv ? cfg.env?.[wranglerEnv] : cfg;
|
|
843
|
-
if (!slot) return null;
|
|
844
|
-
const declaredId = slot.vars?.DEEPSPACE_APP_ID;
|
|
845
|
-
if (declaredId !== void 0) {
|
|
846
|
-
if (typeof declaredId === "string" && LEGACY_APP_ID_RE.test(declaredId)) return declaredId;
|
|
847
|
-
throw new InputError(
|
|
848
|
-
"DEEPSPACE_APP_ID is present but invalid; correct it before migrating.",
|
|
849
|
-
"invalid_app_id"
|
|
850
|
-
);
|
|
851
|
-
}
|
|
852
|
-
const appName = typeof slot.vars?.APP_NAME === "string" ? slot.vars.APP_NAME : null;
|
|
853
|
-
const workerName = typeof slot.name === "string" ? slot.name : null;
|
|
854
|
-
if (!appName) return null;
|
|
855
|
-
if (!workerName) {
|
|
856
|
-
throw new InputError(
|
|
857
|
-
"Legacy migration requires both Worker name and APP_NAME to be present and identical.",
|
|
858
|
-
"ambiguous_legacy_app_id"
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
if (appName !== workerName) {
|
|
862
|
-
throw new InputError(
|
|
863
|
-
`Legacy APP_NAME "${appName}" does not match Worker name "${workerName}"; migration cannot choose safely.`,
|
|
864
|
-
"ambiguous_legacy_app_id"
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
const legacyAppId = appName;
|
|
868
|
-
if (!legacyAppId || !LEGACY_APP_ID_RE.test(legacyAppId) || APP_ID_RE.test(legacyAppId)) {
|
|
869
|
-
return null;
|
|
870
|
-
}
|
|
871
|
-
return legacyAppId;
|
|
872
|
-
}
|
|
873
838
|
function writeAppId(cwd, appId, opts = {}) {
|
|
874
839
|
const wranglerPath = join3(resolve(cwd), "wrangler.toml");
|
|
875
840
|
if (!existsSync3(wranglerPath)) {
|
|
@@ -1986,7 +1951,7 @@ function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
|
|
|
1986
1951
|
if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
|
|
1987
1952
|
} };
|
|
1988
1953
|
warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
|
|
1989
|
-
const config =
|
|
1954
|
+
const config = readWranglerConfig(appDir);
|
|
1990
1955
|
const resolved = resolveAppNameForEnv(config, envName);
|
|
1991
1956
|
if (!resolved.ok) throw new Error(resolved.reason);
|
|
1992
1957
|
const flattened = flattenWranglerEnvConfig(config, envName);
|
|
@@ -2050,7 +2015,7 @@ var WranglerConfigError = class extends Error {
|
|
|
2050
2015
|
this.name = "WranglerConfigError";
|
|
2051
2016
|
}
|
|
2052
2017
|
};
|
|
2053
|
-
function
|
|
2018
|
+
function readWranglerConfig(appDir) {
|
|
2054
2019
|
const path = join7(appDir, "wrangler.toml");
|
|
2055
2020
|
const raw = readFileSync8(path, "utf-8");
|
|
2056
2021
|
try {
|
|
@@ -2465,9 +2430,12 @@ function lintSchema(schema) {
|
|
|
2465
2430
|
}
|
|
2466
2431
|
if (schema.ownerField) {
|
|
2467
2432
|
const col = schema.columns.find((c) => c.name === schema.ownerField);
|
|
2468
|
-
|
|
2433
|
+
const spoofableRoles = Object.entries(schema.permissions).filter(
|
|
2434
|
+
([role, permissions]) => role !== "owner" && role !== "admin" && permissions.create === true
|
|
2435
|
+
).map(([role]) => role);
|
|
2436
|
+
if (col && !col.userBound && spoofableRoles.length > 0) {
|
|
2469
2437
|
warnings.push(
|
|
2470
|
-
`[${schema.name}] ownerField is '${schema.ownerField}' but that column is not marked userBound: true.
|
|
2438
|
+
`[${schema.name}] ownerField is '${schema.ownerField}' but that column is not marked userBound: true. Client role(s) ${spoofableRoles.join(", ")} can create a row with someone else's id in this field, bypassing 'own' permission checks. Add userBound: true (and ideally immutable: true) to the column.`
|
|
2471
2439
|
);
|
|
2472
2440
|
}
|
|
2473
2441
|
}
|
|
@@ -3594,8 +3562,298 @@ import { resolve as resolve12 } from "path";
|
|
|
3594
3562
|
// src/cli/commands/deploy/build.ts
|
|
3595
3563
|
import * as p6 from "@clack/prompts";
|
|
3596
3564
|
import { execSync as execSync2 } from "child_process";
|
|
3597
|
-
import { existsSync as
|
|
3598
|
-
import { dirname as dirname5, join as
|
|
3565
|
+
import { existsSync as existsSync14, readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
|
|
3566
|
+
import { dirname as dirname5, join as join15, resolve as resolve10 } from "path";
|
|
3567
|
+
|
|
3568
|
+
// src/cli/commands/migrate/app-migrations.ts
|
|
3569
|
+
import {
|
|
3570
|
+
closeSync,
|
|
3571
|
+
constants,
|
|
3572
|
+
existsSync as existsSync13,
|
|
3573
|
+
fstatSync,
|
|
3574
|
+
ftruncateSync,
|
|
3575
|
+
lstatSync,
|
|
3576
|
+
openSync,
|
|
3577
|
+
readFileSync as readFileSync14,
|
|
3578
|
+
writeSync
|
|
3579
|
+
} from "fs";
|
|
3580
|
+
import { join as join14 } from "path";
|
|
3581
|
+
|
|
3582
|
+
// src/shared/protocol/app-migrations.ts
|
|
3583
|
+
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
3584
|
+
var MAX_APP_MIGRATION_IDS = 256;
|
|
3585
|
+
var CANONICAL_APP_IDENTITY_MIGRATION_ID = "2026-08-canonical-app-identity-wire";
|
|
3586
|
+
function validateAppMigrationIds(value) {
|
|
3587
|
+
if (!Array.isArray(value)) {
|
|
3588
|
+
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
3589
|
+
}
|
|
3590
|
+
if (value.length > MAX_APP_MIGRATION_IDS) {
|
|
3591
|
+
return {
|
|
3592
|
+
valid: false,
|
|
3593
|
+
reason: `app migrations cannot contain more than ${MAX_APP_MIGRATION_IDS} entries`
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
const ids = [];
|
|
3597
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3598
|
+
for (const valueId of value) {
|
|
3599
|
+
if (typeof valueId !== "string" || !APP_MIGRATION_ID_RE.test(valueId)) {
|
|
3600
|
+
return { valid: false, reason: `invalid app migration id: ${String(valueId)}` };
|
|
3601
|
+
}
|
|
3602
|
+
if (seen.has(valueId)) {
|
|
3603
|
+
return { valid: false, reason: `duplicate app migration id: ${valueId}` };
|
|
3604
|
+
}
|
|
3605
|
+
seen.add(valueId);
|
|
3606
|
+
ids.push(valueId);
|
|
3607
|
+
}
|
|
3608
|
+
return { valid: true, ids };
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
// src/cli/commands/migrate/app-migrations.ts
|
|
3612
|
+
var APP_MIGRATIONS_MANIFEST = "deepspace.migrations.json";
|
|
3613
|
+
var SOURCE_MIGRATIONS = [
|
|
3614
|
+
{
|
|
3615
|
+
id: CANONICAL_APP_IDENTITY_MIGRATION_ID,
|
|
3616
|
+
description: "Use the immutable app id for authenticated platform requests",
|
|
3617
|
+
transform: migrateCanonicalIdentityWire,
|
|
3618
|
+
findBlockers: findLegacyIdentityWireBlockers
|
|
3619
|
+
}
|
|
3620
|
+
];
|
|
3621
|
+
var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
|
|
3622
|
+
({ id, description }) => ({ id, description })
|
|
3623
|
+
);
|
|
3624
|
+
function readAppliedAppMigrations(appDir) {
|
|
3625
|
+
const path = join14(appDir, APP_MIGRATIONS_MANIFEST);
|
|
3626
|
+
if (!existsSync13(path)) return [];
|
|
3627
|
+
const source = readRegularFile(path);
|
|
3628
|
+
let parsed;
|
|
3629
|
+
try {
|
|
3630
|
+
parsed = JSON.parse(source);
|
|
3631
|
+
} catch {
|
|
3632
|
+
throw new Error(`${APP_MIGRATIONS_MANIFEST} must contain valid JSON`);
|
|
3633
|
+
}
|
|
3634
|
+
const validation = validateAppMigrationIds(parsed);
|
|
3635
|
+
if (!validation.valid) {
|
|
3636
|
+
throw new Error(`${APP_MIGRATIONS_MANIFEST}: ${validation.reason}`);
|
|
3637
|
+
}
|
|
3638
|
+
return validation.ids;
|
|
3639
|
+
}
|
|
3640
|
+
function planAppMigrations(appDir) {
|
|
3641
|
+
const repo = repoToplevel(appDir);
|
|
3642
|
+
const appPrefix = gitLine(appDir, ["rev-parse", "--show-prefix"]);
|
|
3643
|
+
const appliedIds = readAppliedAppMigrations(appDir);
|
|
3644
|
+
const applied = new Set(appliedIds);
|
|
3645
|
+
const pendingMigrations = SOURCE_MIGRATIONS.filter((migration) => !applied.has(migration.id));
|
|
3646
|
+
const tracked = splitNulFields(runGit(repo, ["ls-files", "-z"]).stdout);
|
|
3647
|
+
const perMigration = /* @__PURE__ */ new Map();
|
|
3648
|
+
const blockers = [];
|
|
3649
|
+
const edits = [];
|
|
3650
|
+
for (const repoPath of tracked) {
|
|
3651
|
+
if (!isSourcePath(repoPath, appPrefix)) continue;
|
|
3652
|
+
const absolutePath = join14(repo, repoPath);
|
|
3653
|
+
let stat;
|
|
3654
|
+
try {
|
|
3655
|
+
stat = lstatSync(absolutePath);
|
|
3656
|
+
} catch {
|
|
3657
|
+
continue;
|
|
3658
|
+
}
|
|
3659
|
+
if (!stat.isFile() || stat.isSymbolicLink()) continue;
|
|
3660
|
+
const before = readRegularFile(absolutePath);
|
|
3661
|
+
let after = before;
|
|
3662
|
+
for (const migration of pendingMigrations) {
|
|
3663
|
+
const result = migration.transform(after);
|
|
3664
|
+
after = result.source;
|
|
3665
|
+
if (result.replacements > 0) {
|
|
3666
|
+
const planned = perMigration.get(migration.id) ?? {
|
|
3667
|
+
id: migration.id,
|
|
3668
|
+
description: migration.description,
|
|
3669
|
+
files: [],
|
|
3670
|
+
replacements: 0
|
|
3671
|
+
};
|
|
3672
|
+
planned.files.push(repoPath);
|
|
3673
|
+
planned.replacements += result.replacements;
|
|
3674
|
+
perMigration.set(migration.id, planned);
|
|
3675
|
+
}
|
|
3676
|
+
blockers.push(...migration.findBlockers(after, repoPath));
|
|
3677
|
+
}
|
|
3678
|
+
if (after !== before) edits.push({ path: repoPath, before, after });
|
|
3679
|
+
}
|
|
3680
|
+
const pending = pendingMigrations.map((migration) => {
|
|
3681
|
+
const planned = perMigration.get(migration.id);
|
|
3682
|
+
return planned ? { ...planned, files: [...new Set(planned.files)].sort() } : { id: migration.id, description: migration.description, files: [], replacements: 0 };
|
|
3683
|
+
});
|
|
3684
|
+
if (pending.length > 0 && blockers.length === 0) {
|
|
3685
|
+
const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
|
|
3686
|
+
const absoluteManifestPath = join14(repo, manifestPath);
|
|
3687
|
+
const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
|
|
3688
|
+
const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
|
|
3689
|
+
`;
|
|
3690
|
+
if (before !== after) edits.push({ path: manifestPath, before, after });
|
|
3691
|
+
}
|
|
3692
|
+
return {
|
|
3693
|
+
pending,
|
|
3694
|
+
changedFiles: edits.map((edit) => edit.path).sort(),
|
|
3695
|
+
blockers,
|
|
3696
|
+
edits
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
function applyAppMigrationPlan(appDir, plan) {
|
|
3700
|
+
if (plan.blockers.length > 0) {
|
|
3701
|
+
throw new Error("Cannot apply an app migration plan with unresolved blockers.");
|
|
3702
|
+
}
|
|
3703
|
+
const repo = repoToplevel(appDir);
|
|
3704
|
+
for (const edit of plan.edits) {
|
|
3705
|
+
const absolutePath = join14(repo, edit.path);
|
|
3706
|
+
const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
|
|
3707
|
+
if (current !== edit.before) {
|
|
3708
|
+
throw new Error(`Migration input changed after planning: ${edit.path}`);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
for (const edit of plan.edits) writeRegularFile(join14(repo, edit.path), edit.before, edit.after);
|
|
3712
|
+
}
|
|
3713
|
+
function readRegularFile(path) {
|
|
3714
|
+
const before = lstatSync(path);
|
|
3715
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
3716
|
+
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
3717
|
+
}
|
|
3718
|
+
const fd = openSync(path, constants.O_RDONLY);
|
|
3719
|
+
try {
|
|
3720
|
+
const opened = fstatSync(fd);
|
|
3721
|
+
const after = lstatSync(path);
|
|
3722
|
+
if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
3723
|
+
throw new Error(`Migration input changed while opening: ${path}`);
|
|
3724
|
+
}
|
|
3725
|
+
return readFileSync14(fd, "utf-8");
|
|
3726
|
+
} finally {
|
|
3727
|
+
closeSync(fd);
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
function writeRegularFile(path, before, after) {
|
|
3731
|
+
if (before === null) {
|
|
3732
|
+
const fd2 = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
|
|
3733
|
+
try {
|
|
3734
|
+
writeSync(fd2, after, 0, "utf-8");
|
|
3735
|
+
} finally {
|
|
3736
|
+
closeSync(fd2);
|
|
3737
|
+
}
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
const pathStat = lstatSync(path);
|
|
3741
|
+
if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
|
|
3742
|
+
throw new Error(`Migration input must be a regular file, not a symlink: ${path}`);
|
|
3743
|
+
}
|
|
3744
|
+
const fd = openSync(path, constants.O_RDWR);
|
|
3745
|
+
try {
|
|
3746
|
+
const opened = fstatSync(fd);
|
|
3747
|
+
const currentPath = lstatSync(path);
|
|
3748
|
+
if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
|
|
3749
|
+
throw new Error(`Migration input changed while opening: ${path}`);
|
|
3750
|
+
}
|
|
3751
|
+
if (readFileSync14(fd, "utf-8") !== before) {
|
|
3752
|
+
throw new Error(`Migration input changed after planning: ${path}`);
|
|
3753
|
+
}
|
|
3754
|
+
ftruncateSync(fd, 0);
|
|
3755
|
+
writeSync(fd, after, 0, "utf-8");
|
|
3756
|
+
} finally {
|
|
3757
|
+
closeSync(fd);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
function isSourcePath(repoPath, appPrefix) {
|
|
3761
|
+
if (appPrefix && !repoPath.startsWith(appPrefix)) return false;
|
|
3762
|
+
return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
|
|
3763
|
+
}
|
|
3764
|
+
function migrateCanonicalIdentityWire(source) {
|
|
3765
|
+
let replacements = 0;
|
|
3766
|
+
const lines = source.split("\n");
|
|
3767
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3768
|
+
let line = lines[index];
|
|
3769
|
+
line = line.replace(
|
|
3770
|
+
/(\.set\(\s*)(['"])x-app-name\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
|
|
3771
|
+
(_match, open, quote, separator, receiver, close) => {
|
|
3772
|
+
replacements++;
|
|
3773
|
+
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
3774
|
+
}
|
|
3775
|
+
);
|
|
3776
|
+
line = line.replace(
|
|
3777
|
+
/(['"])x-app-name\1(\s*:\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
3778
|
+
(_match, quote, separator, receiver) => {
|
|
3779
|
+
replacements++;
|
|
3780
|
+
return `${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
3781
|
+
}
|
|
3782
|
+
);
|
|
3783
|
+
line = line.replace(
|
|
3784
|
+
/(\[\s*)(['"])x-app-name\2(\s*\]\s*=\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
|
|
3785
|
+
(_match, open, quote, separator, receiver) => {
|
|
3786
|
+
replacements++;
|
|
3787
|
+
return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
|
|
3788
|
+
}
|
|
3789
|
+
);
|
|
3790
|
+
line = line.replace(
|
|
3791
|
+
/(forwardedParams\.set\(\s*)(['"])appName\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
|
|
3792
|
+
(_match, open, quote, separator, receiver, close) => {
|
|
3793
|
+
replacements++;
|
|
3794
|
+
return `${open}${quote}appId${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
|
|
3795
|
+
}
|
|
3796
|
+
);
|
|
3797
|
+
lines[index] = line;
|
|
3798
|
+
}
|
|
3799
|
+
if (replacements === 0) return { source, replacements };
|
|
3800
|
+
let migrated = lines.join("\n");
|
|
3801
|
+
if (/forwardedParams\.set\(\s*['"]appId['"]/.test(migrated)) {
|
|
3802
|
+
migrated = migrated.replaceAll("injectAppName", "injectAppId").replaceAll("`?appName=...`", "`?appId=...`").replaceAll("Inject appName into the query string", "Inject appId into the query string").replaceAll("caller-supplied appName", "caller-supplied appId").replaceAll(
|
|
3803
|
+
"/_deepspace/subscriptions/plans?appName=",
|
|
3804
|
+
"/_deepspace/subscriptions/plans?appId="
|
|
3805
|
+
);
|
|
3806
|
+
}
|
|
3807
|
+
migrated = migrated.replaceAll(
|
|
3808
|
+
"APP_IDENTITY_TOKEN + APP_NAME",
|
|
3809
|
+
"APP_IDENTITY_TOKEN + DEEPSPACE_APP_ID"
|
|
3810
|
+
);
|
|
3811
|
+
return {
|
|
3812
|
+
source: ensureCanonicalIdentityProperties(migrated),
|
|
3813
|
+
replacements
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
function ensureCanonicalIdentityProperties(source) {
|
|
3817
|
+
const lines = source.split("\n");
|
|
3818
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
3819
|
+
const match = /^(\s*)(?:readonly\s+)?APP_NAME\??\s*:\s*string\s*([;,]?)\s*$/.exec(lines[index]);
|
|
3820
|
+
if (!match || blockAlreadyDeclaresCanonicalId(lines, index, match[1].length)) continue;
|
|
3821
|
+
const suffix = match[2] ?? "";
|
|
3822
|
+
lines.splice(index + 1, 0, `${match[1]}DEEPSPACE_APP_ID: string${suffix}`);
|
|
3823
|
+
}
|
|
3824
|
+
return lines.join("\n");
|
|
3825
|
+
}
|
|
3826
|
+
function blockAlreadyDeclaresCanonicalId(lines, propertyIndex, propertyIndent) {
|
|
3827
|
+
for (const direction of [-1, 1]) {
|
|
3828
|
+
for (let index = propertyIndex + direction; index >= 0 && index < lines.length; index += direction) {
|
|
3829
|
+
const line = lines[index];
|
|
3830
|
+
if (/^\s*(?:readonly\s+)?DEEPSPACE_APP_ID\??\s*:/.test(line)) return true;
|
|
3831
|
+
const trimmed = line.trimStart();
|
|
3832
|
+
if (!trimmed) continue;
|
|
3833
|
+
const indent = line.length - trimmed.length;
|
|
3834
|
+
if (indent < propertyIndent && (trimmed.includes("{") || trimmed.startsWith("}"))) break;
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
return false;
|
|
3838
|
+
}
|
|
3839
|
+
function findLegacyIdentityWireBlockers(source, file) {
|
|
3840
|
+
const blockers = [];
|
|
3841
|
+
for (const [index, line] of source.split("\n").entries()) {
|
|
3842
|
+
const trimmed = line.trimStart();
|
|
3843
|
+
if (!/['"]x-app-name['"]/.test(line) || /\.delete\(\s*['"]x-app-name['"]/.test(line) || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
3844
|
+
continue;
|
|
3845
|
+
}
|
|
3846
|
+
blockers.push({
|
|
3847
|
+
migrationId: CANONICAL_APP_IDENTITY_MIGRATION_ID,
|
|
3848
|
+
file,
|
|
3849
|
+
line: index + 1,
|
|
3850
|
+
message: "This app still sends the removed x-app-name identity header in a form the safe migration cannot rewrite."
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3853
|
+
return blockers;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
// src/cli/commands/deploy/build.ts
|
|
3599
3857
|
var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
|
|
3600
3858
|
"/api/*",
|
|
3601
3859
|
"/ws/*",
|
|
@@ -3631,19 +3889,19 @@ async function buildDeployBundle(options) {
|
|
|
3631
3889
|
preparedWranglerConfig?.cleanup();
|
|
3632
3890
|
}
|
|
3633
3891
|
spinner2.stop("Built");
|
|
3634
|
-
const deployConfigPath =
|
|
3635
|
-
if (!
|
|
3892
|
+
const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
|
|
3893
|
+
if (!existsSync14(deployConfigPath)) {
|
|
3636
3894
|
output.die(
|
|
3637
3895
|
"Build output config not found at .wrangler/deploy/config.json",
|
|
3638
3896
|
"build_output_missing"
|
|
3639
3897
|
);
|
|
3640
3898
|
}
|
|
3641
|
-
const deployConfig = JSON.parse(
|
|
3899
|
+
const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
|
|
3642
3900
|
const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
|
|
3643
|
-
if (!
|
|
3901
|
+
if (!existsSync14(outputWranglerPath)) {
|
|
3644
3902
|
output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
|
|
3645
3903
|
}
|
|
3646
|
-
const outputConfig = JSON.parse(
|
|
3904
|
+
const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
|
|
3647
3905
|
if (outputConfig.name && outputConfig.name !== appName) {
|
|
3648
3906
|
const envHint = envName ? ` (--env ${envName})` : "";
|
|
3649
3907
|
output.die(
|
|
@@ -3652,18 +3910,18 @@ async function buildDeployBundle(options) {
|
|
|
3652
3910
|
);
|
|
3653
3911
|
}
|
|
3654
3912
|
const workerDir = dirname5(outputWranglerPath);
|
|
3655
|
-
const workerBundlePath =
|
|
3913
|
+
const workerBundlePath = join15(workerDir, outputConfig.main);
|
|
3656
3914
|
const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
|
|
3657
|
-
if (!
|
|
3915
|
+
if (!existsSync14(workerBundlePath)) {
|
|
3658
3916
|
output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
|
|
3659
3917
|
}
|
|
3660
|
-
if (!clientDir || !
|
|
3918
|
+
if (!clientDir || !existsSync14(clientDir)) {
|
|
3661
3919
|
return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
|
|
3662
3920
|
}
|
|
3663
3921
|
spinner2.start("Collecting assets...");
|
|
3664
3922
|
const assets = collectAssets(clientDir);
|
|
3665
3923
|
spinner2.stop(`Collected ${assets.length} assets`);
|
|
3666
|
-
const workerJs =
|
|
3924
|
+
const workerJs = readFileSync15(workerBundlePath, "utf-8");
|
|
3667
3925
|
const doBindings = outputConfig.durable_objects?.bindings;
|
|
3668
3926
|
const sqliteClasses = new Set(
|
|
3669
3927
|
outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
|
|
@@ -3689,9 +3947,16 @@ async function buildDeployBundle(options) {
|
|
|
3689
3947
|
);
|
|
3690
3948
|
}
|
|
3691
3949
|
const extraRoutes = extractRunWorkerFirst(wranglerConfig);
|
|
3950
|
+
let appMigrations = [];
|
|
3951
|
+
try {
|
|
3952
|
+
appMigrations = readAppliedAppMigrations(appDir);
|
|
3953
|
+
} catch (error) {
|
|
3954
|
+
output.die(errorMessage(error), "invalid_migration_manifest");
|
|
3955
|
+
}
|
|
3692
3956
|
return {
|
|
3693
3957
|
assets,
|
|
3694
3958
|
workerJs,
|
|
3959
|
+
appMigrations,
|
|
3695
3960
|
doManifest,
|
|
3696
3961
|
customBindings,
|
|
3697
3962
|
extraRoutes
|
|
@@ -3716,14 +3981,14 @@ function collectAssets(dir) {
|
|
|
3716
3981
|
const walk = (currentDir, prefix) => {
|
|
3717
3982
|
for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
|
|
3718
3983
|
if (entry.name === ".assetsignore") continue;
|
|
3719
|
-
const fullPath =
|
|
3984
|
+
const fullPath = join15(currentDir, entry.name);
|
|
3720
3985
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
3721
3986
|
if (entry.isDirectory()) {
|
|
3722
3987
|
walk(fullPath, relativePath);
|
|
3723
3988
|
} else {
|
|
3724
3989
|
assets.push({
|
|
3725
3990
|
path: `/${relativePath}`,
|
|
3726
|
-
contentBase64:
|
|
3991
|
+
contentBase64: readFileSync15(fullPath).toString("base64")
|
|
3727
3992
|
});
|
|
3728
3993
|
}
|
|
3729
3994
|
}
|
|
@@ -3737,18 +4002,18 @@ function errorMessage(error) {
|
|
|
3737
4002
|
|
|
3738
4003
|
// src/cli/commands/deploy/commerce.ts
|
|
3739
4004
|
import * as p7 from "@clack/prompts";
|
|
3740
|
-
import { existsSync as
|
|
3741
|
-
import { join as
|
|
4005
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4006
|
+
import { join as join16 } from "path";
|
|
3742
4007
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3743
4008
|
var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
3744
4009
|
function errorMessage2(error) {
|
|
3745
4010
|
return error instanceof Error ? error.message : String(error);
|
|
3746
4011
|
}
|
|
3747
4012
|
async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
|
|
3748
|
-
const sourcePath =
|
|
3749
|
-
if (!
|
|
4013
|
+
const sourcePath = join16(appDir, "src", sourceFile);
|
|
4014
|
+
if (!existsSync15(sourcePath)) return void 0;
|
|
3750
4015
|
const esbuild = await import("esbuild");
|
|
3751
|
-
const outputPath =
|
|
4016
|
+
const outputPath = join16(appDir, ".wrangler", "deploy", outputFile);
|
|
3752
4017
|
await esbuild.build({
|
|
3753
4018
|
entryPoints: [sourcePath],
|
|
3754
4019
|
bundle: true,
|
|
@@ -3841,7 +4106,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
|
|
|
3841
4106
|
}
|
|
3842
4107
|
}
|
|
3843
4108
|
async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
3844
|
-
if (!
|
|
4109
|
+
if (!existsSync15(join16(appDir, "src", "subscriptions.ts"))) return;
|
|
3845
4110
|
let plans;
|
|
3846
4111
|
try {
|
|
3847
4112
|
plans = await loadDeclaration(
|
|
@@ -3896,7 +4161,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
|
|
|
3896
4161
|
}
|
|
3897
4162
|
}
|
|
3898
4163
|
async function syncOneTimeProducts(appDir, appName, token) {
|
|
3899
|
-
if (!
|
|
4164
|
+
if (!existsSync15(join16(appDir, "src", "products.ts"))) return;
|
|
3900
4165
|
let products;
|
|
3901
4166
|
try {
|
|
3902
4167
|
products = await loadDeclaration(
|
|
@@ -4188,7 +4453,7 @@ function repoApi(deployUrl, token, appId) {
|
|
|
4188
4453
|
}
|
|
4189
4454
|
|
|
4190
4455
|
// src/cli/lib/vc-remote.ts
|
|
4191
|
-
import { existsSync as
|
|
4456
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4192
4457
|
import { tmpdir } from "os";
|
|
4193
4458
|
import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
|
|
4194
4459
|
var SPACE_REMOTE = "space";
|
|
@@ -4229,7 +4494,7 @@ function entryIsTransient(cwd) {
|
|
|
4229
4494
|
}
|
|
4230
4495
|
function helperEntryUsable(command) {
|
|
4231
4496
|
const match = /^!'([^']*)' '([^']*)' git-credential$/.exec(command);
|
|
4232
|
-
return match !== null &&
|
|
4497
|
+
return match !== null && existsSync16(match[1]) && existsSync16(match[2]);
|
|
4233
4498
|
}
|
|
4234
4499
|
function credentialHelperCommand() {
|
|
4235
4500
|
const entry = process.argv[1];
|
|
@@ -4347,7 +4612,7 @@ function classifyPushTransportFailure(error) {
|
|
|
4347
4612
|
if (/(?:HTTP |error: )422\b/i.test(message)) {
|
|
4348
4613
|
return {
|
|
4349
4614
|
code: "source_managed_by_github",
|
|
4350
|
-
error: "This app uses GitHub source.
|
|
4615
|
+
error: "This app uses GitHub source. Manage commits with normal Git/GitHub; `deepspace deploy` ships the local working tree without changing Git."
|
|
4351
4616
|
};
|
|
4352
4617
|
}
|
|
4353
4618
|
if (/(?:HTTP |error: )429\b/i.test(message)) {
|
|
@@ -4527,7 +4792,10 @@ async function syncDeployRepository(options) {
|
|
|
4527
4792
|
let source = sourceState.source;
|
|
4528
4793
|
let sourceRevision = sourceState.revision;
|
|
4529
4794
|
const baseReleaseId = null;
|
|
4530
|
-
if (
|
|
4795
|
+
if (source?.provider === "github") {
|
|
4796
|
+
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
4797
|
+
}
|
|
4798
|
+
if (!push) {
|
|
4531
4799
|
try {
|
|
4532
4800
|
assertSyncableRepo(appDir);
|
|
4533
4801
|
commitOid = resolveCommit(appDir, "HEAD");
|
|
@@ -4537,23 +4805,6 @@ async function syncDeployRepository(options) {
|
|
|
4537
4805
|
}
|
|
4538
4806
|
try {
|
|
4539
4807
|
assertSyncableRepo(appDir);
|
|
4540
|
-
if (source?.provider === "github") {
|
|
4541
|
-
const github = syncGitHubDeploy({
|
|
4542
|
-
deployUrl,
|
|
4543
|
-
appDir,
|
|
4544
|
-
appId,
|
|
4545
|
-
token,
|
|
4546
|
-
source,
|
|
4547
|
-
ignoreStale,
|
|
4548
|
-
output
|
|
4549
|
-
});
|
|
4550
|
-
return await github.then((result) => ({
|
|
4551
|
-
...result,
|
|
4552
|
-
deployKey,
|
|
4553
|
-
source,
|
|
4554
|
-
sourceRevision
|
|
4555
|
-
}));
|
|
4556
|
-
}
|
|
4557
4808
|
if (!source && listGitHubRemotes(appDir).length > 0) {
|
|
4558
4809
|
output.die(
|
|
4559
4810
|
"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.",
|
|
@@ -4662,71 +4913,6 @@ async function syncDeployRepository(options) {
|
|
|
4662
4913
|
}
|
|
4663
4914
|
return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
|
|
4664
4915
|
}
|
|
4665
|
-
async function syncGitHubDeploy(options) {
|
|
4666
|
-
const { deployUrl, appDir, appId, token, source, ignoreStale, output } = options;
|
|
4667
|
-
if (!isWorkTreeClean(appDir)) {
|
|
4668
|
-
const refusal = dirtyWorktreeRefusal(currentBranch(appDir));
|
|
4669
|
-
output.die(refusal.error, refusal.code);
|
|
4670
|
-
}
|
|
4671
|
-
const branch = currentBranch(appDir);
|
|
4672
|
-
if (!branch) {
|
|
4673
|
-
const refusal = detachedHeadRefusal();
|
|
4674
|
-
output.die(refusal.error, refusal.code);
|
|
4675
|
-
throw new Error(refusal.error);
|
|
4676
|
-
}
|
|
4677
|
-
const tip = resolveCommit(appDir, "HEAD");
|
|
4678
|
-
if (!tip) {
|
|
4679
|
-
output.die("The repository has no commit to deploy.", "no_commits");
|
|
4680
|
-
throw new Error("The repository has no commit to deploy.");
|
|
4681
|
-
}
|
|
4682
|
-
const remote = selectGitHubRemote(appDir, { repository: source.repository });
|
|
4683
|
-
if (!remote) {
|
|
4684
|
-
output.die(
|
|
4685
|
-
`No local GitHub remote points at ${source.repository}. Add one, then deploy again.`,
|
|
4686
|
-
"github_remote_required"
|
|
4687
|
-
);
|
|
4688
|
-
throw new Error(`No local GitHub remote points at ${source.repository}.`);
|
|
4689
|
-
}
|
|
4690
|
-
const secretFiles = trackedSecretFiles(appDir, tip);
|
|
4691
|
-
if (secretFiles.length > 0) {
|
|
4692
|
-
output.die(
|
|
4693
|
-
`Refusing to deploy a GitHub commit that tracks secret file(s): ${secretFiles.join(", ")}. Remove them from Git history, push the repaired branch, then deploy.`,
|
|
4694
|
-
"secret_in_history"
|
|
4695
|
-
);
|
|
4696
|
-
}
|
|
4697
|
-
if (remoteBranchOid(appDir, remote.name, branch) !== tip) {
|
|
4698
|
-
output.die(
|
|
4699
|
-
`GitHub ${source.repository} does not have ${branch} at ${tip.slice(0, 10)}. DeepSpace never pushes GitHub source; publish it manually, then deploy again.`,
|
|
4700
|
-
"github_push_required",
|
|
4701
|
-
{
|
|
4702
|
-
actionRequired: true,
|
|
4703
|
-
action: { cwd: appDir, argv: ["git", "push", remote.name, branch] }
|
|
4704
|
-
}
|
|
4705
|
-
);
|
|
4706
|
-
}
|
|
4707
|
-
const latest = await repoApi(deployUrl, token, appId).latestRelease().then((result) => result.release);
|
|
4708
|
-
if (latest?.commitOid && latest.commitOid !== tip) {
|
|
4709
|
-
const known = resolveCommit(appDir, latest.commitOid) !== null;
|
|
4710
|
-
const containsLive = known && isAncestor(appDir, latest.commitOid, tip);
|
|
4711
|
-
if ((!known || !containsLive) && !ignoreStale) {
|
|
4712
|
-
output.die(
|
|
4713
|
-
known ? `This GitHub branch does not contain live release #${latest.seq} (${latest.commitOid.slice(0, 10)}). Integrate the published GitHub history, then redeploy.` : `The live release commit ${latest.commitOid.slice(0, 10)} is not in this checkout. Fetch ${source.repository}, integrate it, then redeploy.`,
|
|
4714
|
-
"stale_base",
|
|
4715
|
-
{
|
|
4716
|
-
actionRequired: true,
|
|
4717
|
-
action: { cwd: appDir, argv: ["git", "fetch", remote.name] }
|
|
4718
|
-
}
|
|
4719
|
-
);
|
|
4720
|
-
}
|
|
4721
|
-
if (!containsLive) {
|
|
4722
|
-
p9.log.warn(
|
|
4723
|
-
"Deploying GitHub history that does not contain the live release (--ignore-stale)."
|
|
4724
|
-
);
|
|
4725
|
-
}
|
|
4726
|
-
}
|
|
4727
|
-
p9.log.info(`Verified GitHub ${source.repository}:${branch} at ${tip.slice(0, 10)} (no push).`);
|
|
4728
|
-
return { commitOid: tip, recoverable: true, baseReleaseId: latest?.id ?? null };
|
|
4729
|
-
}
|
|
4730
4916
|
function classifyRemoteState(appDir, token, branch, localTip) {
|
|
4731
4917
|
let remoteTip = null;
|
|
4732
4918
|
try {
|
|
@@ -4882,6 +5068,7 @@ async function deployBuiltBundle(options) {
|
|
|
4882
5068
|
);
|
|
4883
5069
|
form.append("uploadId", uploadId);
|
|
4884
5070
|
form.append("totalGroups", String(totalGroups));
|
|
5071
|
+
form.append("appMigrations", JSON.stringify(bundle.appMigrations));
|
|
4885
5072
|
if (bundle.doManifest) form.append("doManifest", JSON.stringify(bundle.doManifest));
|
|
4886
5073
|
if (bundle.customBindings.length) {
|
|
4887
5074
|
form.append("bindingManifest", JSON.stringify(bundle.customBindings));
|
|
@@ -5047,7 +5234,7 @@ async function postWithRetry(url, makeInit, {
|
|
|
5047
5234
|
|
|
5048
5235
|
// src/cli/commands/deploy/secrets.ts
|
|
5049
5236
|
import * as p11 from "@clack/prompts";
|
|
5050
|
-
import { existsSync as
|
|
5237
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
5051
5238
|
function classifyDevVarsSecrets(options) {
|
|
5052
5239
|
const store = new Set(options.storeSecretNames);
|
|
5053
5240
|
const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
|
|
@@ -5145,8 +5332,8 @@ function prepareDeploySecrets(options) {
|
|
|
5145
5332
|
return { values, names };
|
|
5146
5333
|
}
|
|
5147
5334
|
function readCustomDevVars(path, output) {
|
|
5148
|
-
if (!
|
|
5149
|
-
const section = extractCustomDevVars(stripGeneratedSecretsCache(
|
|
5335
|
+
if (!existsSync17(path)) return {};
|
|
5336
|
+
const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync16(path, "utf-8")));
|
|
5150
5337
|
if (!section) return {};
|
|
5151
5338
|
try {
|
|
5152
5339
|
return parseDevVars(section);
|
|
@@ -5158,6 +5345,46 @@ function errorMessage5(error) {
|
|
|
5158
5345
|
return error instanceof Error ? error.message : String(error);
|
|
5159
5346
|
}
|
|
5160
5347
|
|
|
5348
|
+
// src/cli/commands/migrate/legacy-app-id.ts
|
|
5349
|
+
var LEGACY_APP_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
5350
|
+
function readLegacyAppId(cwd = process.cwd(), wranglerEnv) {
|
|
5351
|
+
const cfg = readWranglerIdentityConfig(cwd);
|
|
5352
|
+
if (!cfg) return null;
|
|
5353
|
+
const slot = wranglerEnv ? cfg.env?.[wranglerEnv] : cfg;
|
|
5354
|
+
if (!slot) return null;
|
|
5355
|
+
const declaredId = slot.vars?.DEEPSPACE_APP_ID;
|
|
5356
|
+
if (declaredId !== void 0) {
|
|
5357
|
+
if (typeof declaredId === "string" && LEGACY_APP_ID_RE.test(declaredId)) return declaredId;
|
|
5358
|
+
throw new InputError(
|
|
5359
|
+
"DEEPSPACE_APP_ID is present but invalid; correct it before migrating.",
|
|
5360
|
+
"invalid_app_id"
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
const appName = typeof slot.vars?.APP_NAME === "string" ? slot.vars.APP_NAME : null;
|
|
5364
|
+
const workerName = typeof slot.name === "string" ? slot.name : null;
|
|
5365
|
+
if (!appName) return null;
|
|
5366
|
+
if (!workerName) {
|
|
5367
|
+
throw new InputError(
|
|
5368
|
+
"Legacy migration requires both Worker name and APP_NAME to be present and identical.",
|
|
5369
|
+
"ambiguous_legacy_app_id"
|
|
5370
|
+
);
|
|
5371
|
+
}
|
|
5372
|
+
if (appName !== workerName) {
|
|
5373
|
+
throw new InputError(
|
|
5374
|
+
`Legacy APP_NAME "${appName}" does not match Worker name "${workerName}"; migration cannot choose safely.`,
|
|
5375
|
+
"ambiguous_legacy_app_id"
|
|
5376
|
+
);
|
|
5377
|
+
}
|
|
5378
|
+
return !APP_ID_RE.test(appName) && LEGACY_APP_ID_RE.test(appName) ? appName : null;
|
|
5379
|
+
}
|
|
5380
|
+
function legacyDeployRefusal(appDir, wranglerEnv) {
|
|
5381
|
+
const legacyAppId = readLegacyAppId(appDir, wranglerEnv);
|
|
5382
|
+
return legacyAppId ? {
|
|
5383
|
+
error: `This checkout still uses legacy app identity ${legacyAppId}. Run \`deepspace app migrate --dry-run\`, then complete \`deepspace app migrate\` before deploying.`,
|
|
5384
|
+
code: "legacy_app_migration_required"
|
|
5385
|
+
} : null;
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5161
5388
|
// src/cli/commands/deploy.ts
|
|
5162
5389
|
var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
|
|
5163
5390
|
var deploy_default = defineCommand3({
|
|
@@ -5190,7 +5417,7 @@ var deploy_default = defineCommand3({
|
|
|
5190
5417
|
// Citty maps --no-push to the negation of an argument named `push`.
|
|
5191
5418
|
push: {
|
|
5192
5419
|
type: "boolean",
|
|
5193
|
-
description: "Sync DeepSpace source before deploying.
|
|
5420
|
+
description: "Sync DeepSpace source before deploying. GitHub source always deploys the local working tree without Git operations; --no-push opts DeepSpace source out of sync.",
|
|
5194
5421
|
default: true
|
|
5195
5422
|
},
|
|
5196
5423
|
"ignore-stale": {
|
|
@@ -5224,7 +5451,7 @@ var deploy_default = defineCommand3({
|
|
|
5224
5451
|
}
|
|
5225
5452
|
p12.intro("Deploying DeepSpace app");
|
|
5226
5453
|
output.showIntro();
|
|
5227
|
-
const wranglerConfig =
|
|
5454
|
+
const wranglerConfig = readWranglerConfig(appDir);
|
|
5228
5455
|
const nameResult = resolveAppNameForEnv(wranglerConfig, envName);
|
|
5229
5456
|
if (!nameResult.ok) return output.die(nameResult.reason, "invalid_app_name");
|
|
5230
5457
|
const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
|
|
@@ -5238,13 +5465,8 @@ var deploy_default = defineCommand3({
|
|
|
5238
5465
|
const appName = nameResult.name;
|
|
5239
5466
|
let appId = readAppId(appDir, envName);
|
|
5240
5467
|
if (!appId) {
|
|
5241
|
-
const
|
|
5242
|
-
if (
|
|
5243
|
-
output.die(
|
|
5244
|
-
`This checkout still uses legacy app identity ${legacyAppId}. Run \`deepspace app migrate --dry-run\`, then complete \`deepspace app migrate\` before deploying.`,
|
|
5245
|
-
"legacy_app_migration_required"
|
|
5246
|
-
);
|
|
5247
|
-
}
|
|
5468
|
+
const refusal = legacyDeployRefusal(appDir, envName);
|
|
5469
|
+
if (refusal) output.die(refusal.error, refusal.code);
|
|
5248
5470
|
}
|
|
5249
5471
|
p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
|
|
5250
5472
|
const { token, ownerId } = await authenticate(appDir, output);
|
|
@@ -6026,20 +6248,20 @@ var usage_default = defineDeepspaceCommand({
|
|
|
6026
6248
|
});
|
|
6027
6249
|
|
|
6028
6250
|
// src/cli/commands/add.ts
|
|
6029
|
-
import { existsSync as
|
|
6030
|
-
import { dirname as dirname7, join as
|
|
6251
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, realpathSync as realpathSync3 } from "fs";
|
|
6252
|
+
import { dirname as dirname7, join as join18, resolve as resolve14 } from "path";
|
|
6031
6253
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6032
6254
|
|
|
6033
6255
|
// src/cli/commands/feature-installer.ts
|
|
6034
6256
|
import {
|
|
6035
|
-
closeSync,
|
|
6257
|
+
closeSync as closeSync2,
|
|
6036
6258
|
copyFileSync,
|
|
6037
|
-
existsSync as
|
|
6038
|
-
fstatSync,
|
|
6259
|
+
existsSync as existsSync18,
|
|
6260
|
+
fstatSync as fstatSync2,
|
|
6039
6261
|
mkdirSync as mkdirSync6,
|
|
6040
6262
|
mkdtempSync,
|
|
6041
|
-
openSync,
|
|
6042
|
-
readFileSync as
|
|
6263
|
+
openSync as openSync2,
|
|
6264
|
+
readFileSync as readFileSync17,
|
|
6043
6265
|
readSync,
|
|
6044
6266
|
readdirSync as readdirSync5,
|
|
6045
6267
|
rmSync as rmSync4,
|
|
@@ -6047,16 +6269,16 @@ import {
|
|
|
6047
6269
|
} from "fs";
|
|
6048
6270
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6049
6271
|
import { tmpdir as tmpdir2 } from "os";
|
|
6050
|
-
import { dirname as dirname6, isAbsolute as isAbsolute4, join as
|
|
6272
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join17, resolve as resolve13 } from "path";
|
|
6051
6273
|
import { fileURLToPath } from "url";
|
|
6052
6274
|
var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
|
|
6053
6275
|
function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
6054
6276
|
let dir = dirname6(fileURLToPath(moduleUrl));
|
|
6055
6277
|
for (; ; ) {
|
|
6056
|
-
const packagePath =
|
|
6057
|
-
if (
|
|
6278
|
+
const packagePath = join17(dir, "package.json");
|
|
6279
|
+
if (existsSync18(packagePath)) {
|
|
6058
6280
|
try {
|
|
6059
|
-
const pkg2 = JSON.parse(
|
|
6281
|
+
const pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
|
|
6060
6282
|
if (pkg2.name === "deepspace") return dir;
|
|
6061
6283
|
} catch {
|
|
6062
6284
|
}
|
|
@@ -6067,13 +6289,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
|
|
|
6067
6289
|
}
|
|
6068
6290
|
}
|
|
6069
6291
|
function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
|
|
6070
|
-
return
|
|
6292
|
+
return existsSync18(join17(packageRoot, "features"));
|
|
6071
6293
|
}
|
|
6072
6294
|
function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
6073
|
-
const configPath =
|
|
6074
|
-
if (!
|
|
6295
|
+
const configPath = join17(packageRoot, "features", id, "feature.json");
|
|
6296
|
+
if (!existsSync18(configPath)) return null;
|
|
6075
6297
|
try {
|
|
6076
|
-
const config = JSON.parse(
|
|
6298
|
+
const config = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
6077
6299
|
if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
|
|
6078
6300
|
return null;
|
|
6079
6301
|
}
|
|
@@ -6083,8 +6305,8 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
|
|
|
6083
6305
|
}
|
|
6084
6306
|
}
|
|
6085
6307
|
function listFeatureConfigs(packageRoot = featurePackageRoot()) {
|
|
6086
|
-
const featuresDir =
|
|
6087
|
-
if (!
|
|
6308
|
+
const featuresDir = join17(packageRoot, "features");
|
|
6309
|
+
if (!existsSync18(featuresDir)) return [];
|
|
6088
6310
|
return readdirSync5(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));
|
|
6089
6311
|
}
|
|
6090
6312
|
function featureCatalog(packageRoot = featurePackageRoot()) {
|
|
@@ -6138,7 +6360,7 @@ function copyFile(src, dest) {
|
|
|
6138
6360
|
function copyFileWithImportShift(src, dest, levels) {
|
|
6139
6361
|
mkdirSync6(dirname6(dest), { recursive: true });
|
|
6140
6362
|
const prefix = "../".repeat(levels);
|
|
6141
|
-
const shifted =
|
|
6363
|
+
const shifted = readFileSync17(src, "utf-8").replace(
|
|
6142
6364
|
/(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
|
|
6143
6365
|
`$1$2${prefix}$3`
|
|
6144
6366
|
);
|
|
@@ -6151,12 +6373,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
|
|
|
6151
6373
|
const preexisting = [];
|
|
6152
6374
|
const missing = [];
|
|
6153
6375
|
for (const file of config.files) {
|
|
6154
|
-
const srcPath =
|
|
6376
|
+
const srcPath = join17(packageRoot, "features", config.id, file.src);
|
|
6155
6377
|
const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
|
|
6156
|
-
const destPath =
|
|
6157
|
-
const existed =
|
|
6378
|
+
const destPath = join17(targetDir, resolvedDest);
|
|
6379
|
+
const existed = existsSync18(destPath);
|
|
6158
6380
|
if (existed) preexisting.push(resolvedDest);
|
|
6159
|
-
if (!
|
|
6381
|
+
if (!existsSync18(srcPath)) {
|
|
6160
6382
|
emit(` Warning: source not found: ${file.src}`, "stderr");
|
|
6161
6383
|
if (!existed) missing.push(resolvedDest);
|
|
6162
6384
|
continue;
|
|
@@ -6193,11 +6415,11 @@ function unresolvedSchema(schema, reason, emit) {
|
|
|
6193
6415
|
}
|
|
6194
6416
|
function integrateSchema(config, targetDir, emit) {
|
|
6195
6417
|
if (!config.schema) return [];
|
|
6196
|
-
const schemasPath =
|
|
6197
|
-
if (!
|
|
6418
|
+
const schemasPath = join17(targetDir, "src", "schemas.ts");
|
|
6419
|
+
if (!existsSync18(schemasPath)) {
|
|
6198
6420
|
return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
|
|
6199
6421
|
}
|
|
6200
|
-
let content =
|
|
6422
|
+
let content = readFileSync17(schemasPath, "utf-8");
|
|
6201
6423
|
const { exportName, importPath, spreadOperator } = config.schema;
|
|
6202
6424
|
const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
|
|
6203
6425
|
if (importPattern.test(content)) {
|
|
@@ -6250,11 +6472,11 @@ function unresolvedActions(actions, reason, emit) {
|
|
|
6250
6472
|
}
|
|
6251
6473
|
function integrateActions(config, targetDir, emit) {
|
|
6252
6474
|
if (!config.actions?.length) return [];
|
|
6253
|
-
const actionsPath =
|
|
6254
|
-
if (!
|
|
6475
|
+
const actionsPath = join17(targetDir, "src", "actions", "index.ts");
|
|
6476
|
+
if (!existsSync18(actionsPath)) {
|
|
6255
6477
|
return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
|
|
6256
6478
|
}
|
|
6257
|
-
let content =
|
|
6479
|
+
let content = readFileSync17(actionsPath, "utf-8");
|
|
6258
6480
|
const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
|
|
6259
6481
|
if (!registryPattern.test(content)) {
|
|
6260
6482
|
const reason = "src/actions/index.ts has an unexpected structure.";
|
|
@@ -6299,8 +6521,8 @@ ${registryEntry}`);
|
|
|
6299
6521
|
}
|
|
6300
6522
|
function integrateCss(packageRoot, config, targetDir, emit) {
|
|
6301
6523
|
if (!config.css?.length) return [];
|
|
6302
|
-
const stylesPath =
|
|
6303
|
-
if (!
|
|
6524
|
+
const stylesPath = join17(targetDir, "src", "styles.css");
|
|
6525
|
+
if (!existsSync18(stylesPath)) {
|
|
6304
6526
|
emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
|
|
6305
6527
|
return [
|
|
6306
6528
|
{
|
|
@@ -6312,12 +6534,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
6312
6534
|
}
|
|
6313
6535
|
];
|
|
6314
6536
|
}
|
|
6315
|
-
let styles =
|
|
6537
|
+
let styles = readFileSync17(stylesPath, "utf-8");
|
|
6316
6538
|
let integrated = 0;
|
|
6317
6539
|
const unresolved = [];
|
|
6318
6540
|
for (const cssFile of config.css) {
|
|
6319
|
-
const srcPath =
|
|
6320
|
-
if (!
|
|
6541
|
+
const srcPath = join17(packageRoot, "features", config.id, cssFile);
|
|
6542
|
+
if (!existsSync18(srcPath)) {
|
|
6321
6543
|
emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
|
|
6322
6544
|
unresolved.push({
|
|
6323
6545
|
area: "css",
|
|
@@ -6326,7 +6548,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
|
|
|
6326
6548
|
});
|
|
6327
6549
|
continue;
|
|
6328
6550
|
}
|
|
6329
|
-
const css =
|
|
6551
|
+
const css = readFileSync17(srcPath, "utf-8");
|
|
6330
6552
|
const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
|
|
6331
6553
|
const marker = fingerprint?.[1] ?? fingerprint?.[2];
|
|
6332
6554
|
if (marker && styles.includes(marker)) {
|
|
@@ -6353,11 +6575,11 @@ function unresolvedRoute(route, config, reason, emit) {
|
|
|
6353
6575
|
}
|
|
6354
6576
|
function integrateRoute(config, targetDir, emit) {
|
|
6355
6577
|
if (!config.route) return [];
|
|
6356
|
-
const navPath =
|
|
6357
|
-
if (!
|
|
6578
|
+
const navPath = join17(targetDir, "src", "nav.ts");
|
|
6579
|
+
if (!existsSync18(navPath)) {
|
|
6358
6580
|
return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
|
|
6359
6581
|
}
|
|
6360
|
-
let content =
|
|
6582
|
+
let content = readFileSync17(navPath, "utf-8");
|
|
6361
6583
|
const routePath = config.route.path;
|
|
6362
6584
|
if (content.includes(`'${routePath}'`)) {
|
|
6363
6585
|
emit(` Nav already present: ${routePath}`);
|
|
@@ -6385,7 +6607,7 @@ ${entry}`);
|
|
|
6385
6607
|
}
|
|
6386
6608
|
var INSTALL_DIAGNOSTIC_BYTES = 16 * 1024;
|
|
6387
6609
|
function readDiagnosticTail(outputFd) {
|
|
6388
|
-
const size =
|
|
6610
|
+
const size = fstatSync2(outputFd).size;
|
|
6389
6611
|
if (size === 0) return void 0;
|
|
6390
6612
|
const length = Math.min(size, INSTALL_DIAGNOSTIC_BYTES);
|
|
6391
6613
|
const buffer = Buffer.alloc(length);
|
|
@@ -6404,10 +6626,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
6404
6626
|
const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
|
|
6405
6627
|
return { error: child.error, status: child.status };
|
|
6406
6628
|
}
|
|
6407
|
-
const captureDir = mkdtempSync(
|
|
6629
|
+
const captureDir = mkdtempSync(join17(tmpdir2(), "deepspace-add-install-"));
|
|
6408
6630
|
let outputFd;
|
|
6409
6631
|
try {
|
|
6410
|
-
outputFd =
|
|
6632
|
+
outputFd = openSync2(join17(captureDir, "output.log"), "w+");
|
|
6411
6633
|
const child = spawnSync6(packageManager, ["install"], {
|
|
6412
6634
|
...options,
|
|
6413
6635
|
stdio: ["ignore", outputFd, outputFd]
|
|
@@ -6418,7 +6640,7 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
|
|
|
6418
6640
|
diagnosticTail: readDiagnosticTail(outputFd)
|
|
6419
6641
|
};
|
|
6420
6642
|
} finally {
|
|
6421
|
-
if (outputFd !== void 0)
|
|
6643
|
+
if (outputFd !== void 0) closeSync2(outputFd);
|
|
6422
6644
|
rmSync4(captureDir, { recursive: true, force: true });
|
|
6423
6645
|
}
|
|
6424
6646
|
}
|
|
@@ -6431,14 +6653,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
|
|
|
6431
6653
|
if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
|
|
6432
6654
|
return { added: [], installRan: false, packageManager };
|
|
6433
6655
|
}
|
|
6434
|
-
const packagePath =
|
|
6435
|
-
if (!
|
|
6656
|
+
const packagePath = join17(targetDir, "package.json");
|
|
6657
|
+
if (!existsSync18(packagePath)) {
|
|
6436
6658
|
emit(" Could not patch package.json (file not found)");
|
|
6437
6659
|
return { added: [], installRan: false, packageManager };
|
|
6438
6660
|
}
|
|
6439
6661
|
let pkg2;
|
|
6440
6662
|
try {
|
|
6441
|
-
pkg2 = JSON.parse(
|
|
6663
|
+
pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
|
|
6442
6664
|
} catch (error) {
|
|
6443
6665
|
emit(
|
|
6444
6666
|
` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
|
|
@@ -6498,7 +6720,7 @@ ${child.diagnosticTail}` : "";
|
|
|
6498
6720
|
}
|
|
6499
6721
|
function dependencySnapshot(targetDir) {
|
|
6500
6722
|
try {
|
|
6501
|
-
const pkg2 = JSON.parse(
|
|
6723
|
+
const pkg2 = JSON.parse(readFileSync17(join17(targetDir, "package.json"), "utf-8"));
|
|
6502
6724
|
return {
|
|
6503
6725
|
dependencies: pkg2.dependencies ?? {},
|
|
6504
6726
|
devDependencies: pkg2.devDependencies ?? {}
|
|
@@ -6510,7 +6732,7 @@ function dependencySnapshot(targetDir) {
|
|
|
6510
6732
|
function installedNavFeatures(packageRoot, targetDir) {
|
|
6511
6733
|
return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
|
|
6512
6734
|
const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
|
|
6513
|
-
return
|
|
6735
|
+
return existsSync18(join17(targetDir, signature));
|
|
6514
6736
|
}).map((feature) => feature.id);
|
|
6515
6737
|
}
|
|
6516
6738
|
function warnNavConflicts(packageRoot, config, targetDir, emit) {
|
|
@@ -6739,11 +6961,11 @@ function renderFeatureInfo(config) {
|
|
|
6739
6961
|
return lines.join("\n");
|
|
6740
6962
|
}
|
|
6741
6963
|
function isDeepSpaceApp(dir) {
|
|
6742
|
-
if (
|
|
6743
|
-
const packagePath =
|
|
6744
|
-
if (!
|
|
6964
|
+
if (existsSync19(join18(dir, "wrangler.toml"))) return true;
|
|
6965
|
+
const packagePath = join18(dir, "package.json");
|
|
6966
|
+
if (!existsSync19(packagePath)) return false;
|
|
6745
6967
|
try {
|
|
6746
|
-
const pkg2 = JSON.parse(
|
|
6968
|
+
const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
6747
6969
|
return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
|
|
6748
6970
|
} catch {
|
|
6749
6971
|
return false;
|
|
@@ -6752,8 +6974,8 @@ function isDeepSpaceApp(dir) {
|
|
|
6752
6974
|
function installedPackageRoot(fromDir) {
|
|
6753
6975
|
let dir = fromDir;
|
|
6754
6976
|
for (; ; ) {
|
|
6755
|
-
const candidate =
|
|
6756
|
-
if (
|
|
6977
|
+
const candidate = join18(dir, "node_modules", "deepspace");
|
|
6978
|
+
if (existsSync19(join18(candidate, "package.json"))) return candidate;
|
|
6757
6979
|
const parent = dirname7(dir);
|
|
6758
6980
|
if (parent === dir) return null;
|
|
6759
6981
|
dir = parent;
|
|
@@ -6770,9 +6992,9 @@ async function targetInstaller(appDir) {
|
|
|
6770
6992
|
if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
|
|
6771
6993
|
return { installFeature };
|
|
6772
6994
|
}
|
|
6773
|
-
const entry =
|
|
6774
|
-
const catalog =
|
|
6775
|
-
if (!
|
|
6995
|
+
const entry = join18(installedRoot, "dist", "feature-installer.js");
|
|
6996
|
+
const catalog = join18(installedRoot, "features");
|
|
6997
|
+
if (!existsSync19(entry) || !existsSync19(catalog)) {
|
|
6776
6998
|
throw new Refusal(
|
|
6777
6999
|
`The app's installed deepspace package does not contain the typed feature installer.
|
|
6778
7000
|
Expected: ${entry}
|
|
@@ -6865,7 +7087,7 @@ var add_default = defineDeepspaceCommand({
|
|
|
6865
7087
|
return { data: { features: featureCatalog(root) } };
|
|
6866
7088
|
}
|
|
6867
7089
|
const appDir = resolve14(args.dir ?? ".");
|
|
6868
|
-
if (!
|
|
7090
|
+
if (!existsSync19(appDir)) {
|
|
6869
7091
|
throw new Refusal(
|
|
6870
7092
|
`Target directory not found: ${appDir}
|
|
6871
7093
|
Create a new app first: deepspace app create <name>`,
|
|
@@ -7526,7 +7748,7 @@ var transfer_default = defineCommand7({
|
|
|
7526
7748
|
import { defineCommand as defineCommand8 } from "citty";
|
|
7527
7749
|
|
|
7528
7750
|
// src/cli/commands/_invoke-impl.ts
|
|
7529
|
-
import { readFileSync as
|
|
7751
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
7530
7752
|
import * as p15 from "@clack/prompts";
|
|
7531
7753
|
var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
7532
7754
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -7567,7 +7789,7 @@ function resolveBody(opts) {
|
|
|
7567
7789
|
if (opts.body != null) {
|
|
7568
7790
|
raw = opts.body;
|
|
7569
7791
|
} else if (opts.bodyFile != null) {
|
|
7570
|
-
raw = opts.bodyFile === "-" ?
|
|
7792
|
+
raw = opts.bodyFile === "-" ? readFileSync19(0, "utf-8") : readFileSync19(opts.bodyFile, "utf-8");
|
|
7571
7793
|
} else {
|
|
7572
7794
|
return "{}";
|
|
7573
7795
|
}
|
|
@@ -7846,8 +8068,8 @@ var integrations_default = defineCommand8({
|
|
|
7846
8068
|
|
|
7847
8069
|
// src/cli/commands/library.ts
|
|
7848
8070
|
import { defineCommand as defineCommand9 } from "citty";
|
|
7849
|
-
import { existsSync as
|
|
7850
|
-
import { join as
|
|
8071
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
|
|
8072
|
+
import { join as join19, resolve as resolve15 } from "path";
|
|
7851
8073
|
import { parse as parseToml4 } from "smol-toml";
|
|
7852
8074
|
import * as p16 from "@clack/prompts";
|
|
7853
8075
|
var DEFAULT_LIBRARY_APP = "deepdotspace-site";
|
|
@@ -7918,14 +8140,14 @@ var publish = defineDeepspaceCommand({
|
|
|
7918
8140
|
let appName = str(args.app)?.trim();
|
|
7919
8141
|
if (!appName) {
|
|
7920
8142
|
const appDir = resolve15(str(args.dir) ?? ".");
|
|
7921
|
-
const wranglerPath =
|
|
7922
|
-
if (!
|
|
8143
|
+
const wranglerPath = join19(appDir, "wrangler.toml");
|
|
8144
|
+
if (!existsSync20(wranglerPath)) {
|
|
7923
8145
|
throw new Refusal(
|
|
7924
8146
|
"No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.",
|
|
7925
8147
|
"not_in_app_repo"
|
|
7926
8148
|
);
|
|
7927
8149
|
}
|
|
7928
|
-
const cfg = parseToml4(
|
|
8150
|
+
const cfg = parseToml4(readFileSync20(wranglerPath, "utf-8"));
|
|
7929
8151
|
const nameRes = resolveAppName(cfg.name);
|
|
7930
8152
|
if (!nameRes.ok) throw new Refusal(`wrangler.toml: ${nameRes.reason}`, "invalid_config");
|
|
7931
8153
|
appName = nameRes.name;
|
|
@@ -8063,8 +8285,8 @@ var library_default = defineCommand9({
|
|
|
8063
8285
|
|
|
8064
8286
|
// src/cli/commands/feedback.ts
|
|
8065
8287
|
import * as p17 from "@clack/prompts";
|
|
8066
|
-
import { existsSync as
|
|
8067
|
-
import { dirname as dirname8, join as
|
|
8288
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
8289
|
+
import { dirname as dirname8, join as join20 } from "path";
|
|
8068
8290
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8069
8291
|
import { release } from "os";
|
|
8070
8292
|
var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
|
|
@@ -8087,12 +8309,12 @@ function readCliVersion() {
|
|
|
8087
8309
|
try {
|
|
8088
8310
|
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
8089
8311
|
const candidates = [
|
|
8090
|
-
|
|
8091
|
-
|
|
8312
|
+
join20(here, "..", "package.json"),
|
|
8313
|
+
join20(here, "..", "..", "..", "package.json")
|
|
8092
8314
|
];
|
|
8093
8315
|
for (const path of candidates) {
|
|
8094
|
-
if (!
|
|
8095
|
-
const pkg2 = JSON.parse(
|
|
8316
|
+
if (!existsSync21(path)) continue;
|
|
8317
|
+
const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
|
|
8096
8318
|
if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
|
|
8097
8319
|
}
|
|
8098
8320
|
} catch {
|
|
@@ -8103,7 +8325,7 @@ function detectAppName2() {
|
|
|
8103
8325
|
try {
|
|
8104
8326
|
const cwd = process.cwd();
|
|
8105
8327
|
if (!hasWranglerConfig(cwd)) return void 0;
|
|
8106
|
-
const config =
|
|
8328
|
+
const config = readWranglerConfig(cwd);
|
|
8107
8329
|
return typeof config.name === "string" ? config.name : void 0;
|
|
8108
8330
|
} catch {
|
|
8109
8331
|
return void 0;
|
|
@@ -8288,7 +8510,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
|
|
|
8288
8510
|
|
|
8289
8511
|
// src/cli/commands/secrets.ts
|
|
8290
8512
|
import { defineCommand as defineCommand10 } from "citty";
|
|
8291
|
-
import { readFileSync as
|
|
8513
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
8292
8514
|
|
|
8293
8515
|
// src/cli/lib/citty-args.ts
|
|
8294
8516
|
function dedupePositionals(bound, rest) {
|
|
@@ -8558,7 +8780,7 @@ var upload = defineCommand10({
|
|
|
8558
8780
|
async run({ args }) {
|
|
8559
8781
|
try {
|
|
8560
8782
|
const t = await resolveTarget(args);
|
|
8561
|
-
const content = args.file === "-" ?
|
|
8783
|
+
const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
|
|
8562
8784
|
const secrets = parseSecretsUpload(content);
|
|
8563
8785
|
if (Object.keys(secrets).length === 0)
|
|
8564
8786
|
fail(new InputError("No secrets found in the input.", "empty_input"));
|
|
@@ -8814,7 +9036,7 @@ var init_default = defineDeepspaceCommand({
|
|
|
8814
9036
|
// src/cli/commands/status.ts
|
|
8815
9037
|
import { defineCommand as defineCommand11 } from "citty";
|
|
8816
9038
|
import * as p18 from "@clack/prompts";
|
|
8817
|
-
import { existsSync as
|
|
9039
|
+
import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
|
|
8818
9040
|
function timeAgo(iso) {
|
|
8819
9041
|
const ms = Date.now() - Date.parse(iso);
|
|
8820
9042
|
if (!Number.isFinite(ms) || ms < 0) return iso;
|
|
@@ -8883,7 +9105,7 @@ var status_default = defineCommand11({
|
|
|
8883
9105
|
json.services = urls;
|
|
8884
9106
|
if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
|
|
8885
9107
|
}
|
|
8886
|
-
facts.loggedIn =
|
|
9108
|
+
facts.loggedIn = existsSync22(SESSION_PATH);
|
|
8887
9109
|
let userId = null;
|
|
8888
9110
|
if (!facts.loggedIn) {
|
|
8889
9111
|
lines.push(["Session", "not logged in"]);
|
|
@@ -8892,7 +9114,7 @@ var status_default = defineCommand11({
|
|
|
8892
9114
|
let who = "logged in";
|
|
8893
9115
|
try {
|
|
8894
9116
|
const payload = decodeJwtPayload(
|
|
8895
|
-
|
|
9117
|
+
readFileSync23(TOKEN_PATH, "utf-8").trim()
|
|
8896
9118
|
);
|
|
8897
9119
|
if (payload.email) who = payload.email;
|
|
8898
9120
|
userId = payload.sub ?? null;
|
|
@@ -9555,18 +9777,18 @@ var pull_default = defineDeepspaceCommand({
|
|
|
9555
9777
|
|
|
9556
9778
|
// src/cli/commands/clone.ts
|
|
9557
9779
|
import * as p21 from "@clack/prompts";
|
|
9558
|
-
import { existsSync as
|
|
9559
|
-
import { resolve as resolve16, basename as basename3, join as
|
|
9780
|
+
import { existsSync as existsSync23, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
|
|
9781
|
+
import { resolve as resolve16, basename as basename3, join as join21 } from "path";
|
|
9560
9782
|
function cleanFailedCloneDir(dir) {
|
|
9561
9783
|
let entries;
|
|
9562
9784
|
try {
|
|
9563
9785
|
entries = readdirSync6(dir);
|
|
9564
9786
|
} catch {
|
|
9565
|
-
return
|
|
9787
|
+
return existsSync23(dir) ? ["<unreadable>"] : [];
|
|
9566
9788
|
}
|
|
9567
9789
|
if (entries.length === 1 && entries[0] === ".git") {
|
|
9568
9790
|
try {
|
|
9569
|
-
rmSync5(
|
|
9791
|
+
rmSync5(join21(dir, ".git"), { recursive: true, force: true });
|
|
9570
9792
|
return [];
|
|
9571
9793
|
} catch {
|
|
9572
9794
|
return entries;
|
|
@@ -9575,7 +9797,7 @@ function cleanFailedCloneDir(dir) {
|
|
|
9575
9797
|
return entries;
|
|
9576
9798
|
}
|
|
9577
9799
|
function validateCloneTarget(dir) {
|
|
9578
|
-
if (!
|
|
9800
|
+
if (!existsSync23(dir)) return false;
|
|
9579
9801
|
let entries;
|
|
9580
9802
|
try {
|
|
9581
9803
|
entries = readdirSync6(dir);
|
|
@@ -9989,8 +10211,8 @@ function printOverlaps(overlaps, json) {
|
|
|
9989
10211
|
|
|
9990
10212
|
// src/cli/commands/workspace/local.ts
|
|
9991
10213
|
import * as p25 from "@clack/prompts";
|
|
9992
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
9993
|
-
import { dirname as dirname9, isAbsolute as isAbsolute5, join as
|
|
10214
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync24, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
|
|
10215
|
+
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
|
|
9994
10216
|
var WORKSPACE_MARKER = "deepspace-workspace.json";
|
|
9995
10217
|
function excludeWorktreeDir(appDir, dir) {
|
|
9996
10218
|
try {
|
|
@@ -10011,7 +10233,7 @@ function worktreeMarkerPath(worktreeRoot) {
|
|
|
10011
10233
|
const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
|
|
10012
10234
|
if (result.status !== 0) return null;
|
|
10013
10235
|
const gitDir = result.stdout.toString("utf-8").trim();
|
|
10014
|
-
return isAbsolute5(gitDir) ?
|
|
10236
|
+
return isAbsolute5(gitDir) ? join22(gitDir, WORKSPACE_MARKER) : null;
|
|
10015
10237
|
} catch {
|
|
10016
10238
|
return null;
|
|
10017
10239
|
}
|
|
@@ -10047,7 +10269,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
10047
10269
|
const markerPath = worktreeMarkerPath(worktreeRoot);
|
|
10048
10270
|
if (!markerPath) return false;
|
|
10049
10271
|
try {
|
|
10050
|
-
const marker = JSON.parse(
|
|
10272
|
+
const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
|
|
10051
10273
|
return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
|
|
10052
10274
|
} catch {
|
|
10053
10275
|
return false;
|
|
@@ -10056,11 +10278,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
|
|
|
10056
10278
|
function defaultWorkspaceRoot(appDir, workspaceId) {
|
|
10057
10279
|
const primary = listWorktrees(appDir)[0];
|
|
10058
10280
|
if (!primary) throw new Error("Git did not report a primary worktree");
|
|
10059
|
-
return
|
|
10281
|
+
return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
|
|
10060
10282
|
}
|
|
10061
10283
|
function appDirInWorktree(appDir, worktreeRoot) {
|
|
10062
10284
|
const appRelative = relative2(realpathSync5(repoToplevel(appDir)), realpathSync5(appDir));
|
|
10063
|
-
return appRelative ?
|
|
10285
|
+
return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
|
|
10064
10286
|
}
|
|
10065
10287
|
function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
|
|
10066
10288
|
const createdOid = resolveCommit(appDir, startPoint);
|
|
@@ -10078,7 +10300,7 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
|
|
|
10078
10300
|
function inOwnLinkedWorktree(appDir, id) {
|
|
10079
10301
|
if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
|
|
10080
10302
|
try {
|
|
10081
|
-
return statSync(
|
|
10303
|
+
return statSync(join22(repoToplevel(appDir), ".git")).isFile();
|
|
10082
10304
|
} catch {
|
|
10083
10305
|
return false;
|
|
10084
10306
|
}
|
|
@@ -10409,7 +10631,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10409
10631
|
|
|
10410
10632
|
// src/cli/commands/workspace/attach.ts
|
|
10411
10633
|
import * as p27 from "@clack/prompts";
|
|
10412
|
-
import { existsSync as
|
|
10634
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
|
|
10413
10635
|
import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
|
|
10414
10636
|
function finishedWorkspaceMessage(id, view) {
|
|
10415
10637
|
const workspace = view.workspace;
|
|
@@ -10419,7 +10641,7 @@ function finishedWorkspaceMessage(id, view) {
|
|
|
10419
10641
|
return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
|
|
10420
10642
|
}
|
|
10421
10643
|
function cleanFailedFreshAttachDir(dir) {
|
|
10422
|
-
if (!
|
|
10644
|
+
if (!existsSync24(dir)) return [];
|
|
10423
10645
|
let entries;
|
|
10424
10646
|
try {
|
|
10425
10647
|
entries = readdirSync7(dir);
|
|
@@ -10537,7 +10759,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
|
|
|
10537
10759
|
process.cwd(),
|
|
10538
10760
|
(typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
|
|
10539
10761
|
);
|
|
10540
|
-
if (
|
|
10762
|
+
if (existsSync24(dir)) {
|
|
10541
10763
|
spinner2?.stop("Target exists.");
|
|
10542
10764
|
throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
|
|
10543
10765
|
}
|
|
@@ -10854,13 +11076,13 @@ import * as p31 from "@clack/prompts";
|
|
|
10854
11076
|
|
|
10855
11077
|
// src/cli/lib/validation.ts
|
|
10856
11078
|
import { sync as spawnSync7 } from "cross-spawn";
|
|
10857
|
-
import { readFileSync as
|
|
10858
|
-
import { join as
|
|
11079
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
11080
|
+
import { join as join23 } from "path";
|
|
10859
11081
|
var MAX_SUMMARY_CHARS = 4e3;
|
|
10860
11082
|
var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
|
|
10861
11083
|
function configuredCommand(appDir) {
|
|
10862
11084
|
try {
|
|
10863
|
-
const pkg2 = JSON.parse(
|
|
11085
|
+
const pkg2 = JSON.parse(readFileSync25(join23(appDir, "package.json"), "utf-8"));
|
|
10864
11086
|
if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
|
|
10865
11087
|
} catch {
|
|
10866
11088
|
}
|
|
@@ -11783,7 +12005,7 @@ function sameRefs(left, right) {
|
|
|
11783
12005
|
return left.every((ref) => expected.get(ref.name) === ref.oid);
|
|
11784
12006
|
}
|
|
11785
12007
|
|
|
11786
|
-
// src/cli/
|
|
12008
|
+
// src/cli/commands/migrate/identity-api.ts
|
|
11787
12009
|
async function getIdentityMigration(deployUrl, token, appId) {
|
|
11788
12010
|
try {
|
|
11789
12011
|
const result = await apiFetch(
|
|
@@ -11832,11 +12054,11 @@ async function cancelIdentityMigration(deployUrl, token, appId) {
|
|
|
11832
12054
|
{ method: "DELETE" }
|
|
11833
12055
|
);
|
|
11834
12056
|
}
|
|
11835
|
-
async function
|
|
12057
|
+
async function verifyIdentityMigration(deployUrl, token, appId) {
|
|
11836
12058
|
const result = await apiFetch(
|
|
11837
12059
|
deployUrl,
|
|
11838
12060
|
token,
|
|
11839
|
-
`/api/apps/${encodeURIComponent(appId)}/identity-migration/
|
|
12061
|
+
`/api/apps/${encodeURIComponent(appId)}/identity-migration/verify`,
|
|
11840
12062
|
{ method: "POST" }
|
|
11841
12063
|
);
|
|
11842
12064
|
return result.migration;
|
|
@@ -11847,7 +12069,7 @@ var STRICT_APP_ID_RE2 = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
|
11847
12069
|
var migrate_default = defineDeepspaceCommand({
|
|
11848
12070
|
meta: {
|
|
11849
12071
|
name: "migrate",
|
|
11850
|
-
description: "
|
|
12072
|
+
description: "Apply pending DeepSpace app migrations without moving retained data"
|
|
11851
12073
|
},
|
|
11852
12074
|
args: {
|
|
11853
12075
|
remote: {
|
|
@@ -11870,21 +12092,13 @@ var migrate_default = defineDeepspaceCommand({
|
|
|
11870
12092
|
type: "boolean",
|
|
11871
12093
|
description: "Cancel a prepared migration after restoring and publishing the legacy id",
|
|
11872
12094
|
default: false
|
|
11873
|
-
},
|
|
11874
|
-
rollback: {
|
|
11875
|
-
type: "boolean",
|
|
11876
|
-
description: "Roll back a committed migration before any canonical deploy starts",
|
|
11877
|
-
default: false
|
|
11878
12095
|
}
|
|
11879
12096
|
},
|
|
11880
12097
|
async run({ args }) {
|
|
11881
|
-
const recoveryMode = args.cancel === true ? "cancel" :
|
|
11882
|
-
const exclusiveModes = [args["dry-run"], args.cancel
|
|
12098
|
+
const recoveryMode = args.cancel === true ? "cancel" : null;
|
|
12099
|
+
const exclusiveModes = [args["dry-run"], args.cancel].filter(Boolean).length;
|
|
11883
12100
|
if (exclusiveModes > 1) {
|
|
11884
|
-
throw new Refusal(
|
|
11885
|
-
"Use only one of --dry-run, --cancel, or --rollback.",
|
|
11886
|
-
"conflicting_migration_mode"
|
|
11887
|
-
);
|
|
12101
|
+
throw new Refusal("Use only one of --dry-run or --cancel.", "conflicting_migration_mode");
|
|
11888
12102
|
}
|
|
11889
12103
|
const appDir = findAppDir();
|
|
11890
12104
|
if (!appDir) {
|
|
@@ -11920,14 +12134,60 @@ var migrate_default = defineDeepspaceCommand({
|
|
|
11920
12134
|
json: args.json
|
|
11921
12135
|
});
|
|
11922
12136
|
}
|
|
12137
|
+
const sourcePlan = planAppMigrations(appDir);
|
|
12138
|
+
if (sourcePlan.blockers.length > 0) {
|
|
12139
|
+
const first = sourcePlan.blockers[0];
|
|
12140
|
+
throw new Refusal(
|
|
12141
|
+
`Migration ${first.migrationId} needs a manual source update at ${first.file}:${first.line}. ${first.message}`,
|
|
12142
|
+
"source_migration_blocked",
|
|
12143
|
+
{ extra: { blockers: sourcePlan.blockers } }
|
|
12144
|
+
);
|
|
12145
|
+
}
|
|
11923
12146
|
if (STRICT_APP_ID_RE2.test(currentAppId)) {
|
|
11924
12147
|
if (!existingMigration) {
|
|
11925
|
-
|
|
11926
|
-
|
|
12148
|
+
if (args["dry-run"] && sourcePlan.pending.length > 0) {
|
|
12149
|
+
reportSourcePlan(sourcePlan, args.json);
|
|
12150
|
+
return {
|
|
12151
|
+
data: {
|
|
12152
|
+
status: "ready",
|
|
12153
|
+
appId: currentAppId,
|
|
12154
|
+
sourceMigrations: sourcePlan.pending
|
|
12155
|
+
}
|
|
12156
|
+
};
|
|
12157
|
+
}
|
|
12158
|
+
if (sourcePlan.pending.length > 0) {
|
|
12159
|
+
requireSourceMigrationWorktree(appDir);
|
|
12160
|
+
applyAppMigrationPlan(appDir, sourcePlan);
|
|
12161
|
+
throw sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
12162
|
+
}
|
|
12163
|
+
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
12164
|
+
envName,
|
|
12165
|
+
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
12166
|
+
});
|
|
12167
|
+
reportUpToDate(currentAppId, args.json);
|
|
12168
|
+
return { data: { status: "up_to_date", appId: currentAppId } };
|
|
12169
|
+
}
|
|
12170
|
+
if (sourcePlan.pending.length === 0 && existingMigration.status === "verified") {
|
|
12171
|
+
await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token, {
|
|
12172
|
+
envName,
|
|
12173
|
+
remoteName: typeof args.remote === "string" ? args.remote : void 0
|
|
12174
|
+
});
|
|
11927
12175
|
}
|
|
11928
12176
|
if (args["dry-run"]) {
|
|
11929
12177
|
reportMigration(existingMigration, args.json);
|
|
11930
|
-
|
|
12178
|
+
reportSourcePlan(sourcePlan, args.json);
|
|
12179
|
+
return {
|
|
12180
|
+
data: {
|
|
12181
|
+
status: existingMigration.status,
|
|
12182
|
+
migration: existingMigration,
|
|
12183
|
+
sourceMigrations: sourcePlan.pending
|
|
12184
|
+
}
|
|
12185
|
+
};
|
|
12186
|
+
}
|
|
12187
|
+
if (sourcePlan.pending.length > 0) {
|
|
12188
|
+
requireSourceMigrationWorktree(appDir, existingMigration.status === "prepared");
|
|
12189
|
+
applyAppMigrationPlan(appDir, sourcePlan);
|
|
12190
|
+
throw existingMigration.status === "prepared" ? commitRequired(appDir, existingMigration, envName, sourcePlan.changedFiles) : sourceMigrationCommitRequired(appDir, sourcePlan);
|
|
11931
12191
|
}
|
|
11932
12192
|
return continuePreparedMigration({
|
|
11933
12193
|
appDir,
|
|
@@ -11978,6 +12238,7 @@ var migrate_default = defineDeepspaceCommand({
|
|
|
11978
12238
|
);
|
|
11979
12239
|
console.log("No changes made.");
|
|
11980
12240
|
}
|
|
12241
|
+
reportSourcePlan(sourcePlan, args.json);
|
|
11981
12242
|
return {
|
|
11982
12243
|
data: {
|
|
11983
12244
|
status: "ready",
|
|
@@ -11988,6 +12249,7 @@ var migrate_default = defineDeepspaceCommand({
|
|
|
11988
12249
|
branch: head.branch,
|
|
11989
12250
|
commitOid: head.oid,
|
|
11990
12251
|
sourceClaimRequired: inventory.app.sourceClaimRequired,
|
|
12252
|
+
sourceMigrations: sourcePlan.pending,
|
|
11991
12253
|
inventory
|
|
11992
12254
|
}
|
|
11993
12255
|
};
|
|
@@ -11997,19 +12259,13 @@ var migrate_default = defineDeepspaceCommand({
|
|
|
11997
12259
|
repository: remote.repository,
|
|
11998
12260
|
expectedSourceRevision: inventory.app.sourceRevision
|
|
11999
12261
|
});
|
|
12262
|
+
applyAppMigrationPlan(appDir, sourcePlan);
|
|
12000
12263
|
writeAppId(appDir, migration.appId, { wranglerEnv: envName, force: true });
|
|
12001
|
-
throw commitRequired(appDir, migration, envName);
|
|
12264
|
+
throw commitRequired(appDir, migration, envName, sourcePlan.changedFiles);
|
|
12002
12265
|
}
|
|
12003
12266
|
});
|
|
12004
12267
|
async function recoverMigration(input) {
|
|
12005
|
-
const {
|
|
12006
|
-
if (migration.status === "verified" || migration.deployStartedAt) {
|
|
12007
|
-
throw new Refusal(
|
|
12008
|
-
"A canonical deployment has started. Registry rollback is no longer safe; use the forward-recovery procedure to repair or redeploy the canonical app.",
|
|
12009
|
-
"forward_recovery_required",
|
|
12010
|
-
{ extra: { migration } }
|
|
12011
|
-
);
|
|
12012
|
-
}
|
|
12268
|
+
const { appDir, envName, remoteName, currentAppId, token, deployUrl, migration, json } = input;
|
|
12013
12269
|
if (migration.status === "rolled_back") {
|
|
12014
12270
|
if (currentAppId !== migration.legacyAppId) {
|
|
12015
12271
|
writeAppId(appDir, migration.legacyAppId, { wranglerEnv: envName, force: true });
|
|
@@ -12018,17 +12274,10 @@ async function recoverMigration(input) {
|
|
|
12018
12274
|
reportMigration(migration, json);
|
|
12019
12275
|
return { data: { status: "rolled_back", migration } };
|
|
12020
12276
|
}
|
|
12021
|
-
if (
|
|
12277
|
+
if (migration.status !== "prepared") {
|
|
12022
12278
|
throw new Refusal(
|
|
12023
|
-
"This migration is committed.
|
|
12024
|
-
"
|
|
12025
|
-
{ extra: { migration } }
|
|
12026
|
-
);
|
|
12027
|
-
}
|
|
12028
|
-
if (mode === "rollback" && migration.status !== "committed") {
|
|
12029
|
-
throw new Refusal(
|
|
12030
|
-
"This migration is only prepared. Use --cancel instead.",
|
|
12031
|
-
"migration_cancel_required",
|
|
12279
|
+
"This migration is already committed. Complete it by deploying the canonical app.",
|
|
12280
|
+
"forward_recovery_required",
|
|
12032
12281
|
{ extra: { migration } }
|
|
12033
12282
|
);
|
|
12034
12283
|
}
|
|
@@ -12038,14 +12287,9 @@ async function recoverMigration(input) {
|
|
|
12038
12287
|
}
|
|
12039
12288
|
const remote = requireGitHubRemote2(appDir, remoteName, migration.repository);
|
|
12040
12289
|
requirePublishedHead2(appDir, remote);
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
return { data: { status: "cancelled", legacyAppId: migration.legacyAppId } };
|
|
12045
|
-
}
|
|
12046
|
-
const rolledBack = await rollbackIdentityMigration(deployUrl, token, migration.appId);
|
|
12047
|
-
reportMigration(rolledBack, json);
|
|
12048
|
-
return { data: { status: "rolled_back", migration: rolledBack } };
|
|
12290
|
+
await cancelIdentityMigration(deployUrl, token, migration.legacyAppId);
|
|
12291
|
+
if (!json) console.log(`Canceled ${migration.legacyAppId} \u2192 ${migration.appId}.`);
|
|
12292
|
+
return { data: { status: "cancelled", legacyAppId: migration.legacyAppId } };
|
|
12049
12293
|
}
|
|
12050
12294
|
async function continuePreparedMigration(input) {
|
|
12051
12295
|
const { appDir, envName, remoteName, token, deployUrl, migration, json } = input;
|
|
@@ -12060,7 +12304,14 @@ async function continuePreparedMigration(input) {
|
|
|
12060
12304
|
return { data: { status: "verified", migration } };
|
|
12061
12305
|
}
|
|
12062
12306
|
if (migration.status === "committed") {
|
|
12063
|
-
|
|
12307
|
+
await requireSourceMigrationDeploy(appDir, migration.appId, deployUrl, token, {
|
|
12308
|
+
envName,
|
|
12309
|
+
remoteName,
|
|
12310
|
+
after: migration.committedAt ?? void 0
|
|
12311
|
+
});
|
|
12312
|
+
const verified = await verifyIdentityMigration(deployUrl, token, migration.appId);
|
|
12313
|
+
reportMigration(verified, json);
|
|
12314
|
+
return { data: { status: "verified", migration: verified } };
|
|
12064
12315
|
}
|
|
12065
12316
|
if (!isWorkTreeClean(appDir)) {
|
|
12066
12317
|
const wranglerPath = wranglerRepoPath(appDir);
|
|
@@ -12128,10 +12379,11 @@ function requirePublishedHead2(appDir, remote) {
|
|
|
12128
12379
|
}
|
|
12129
12380
|
return head;
|
|
12130
12381
|
}
|
|
12131
|
-
function commitRequired(appDir, migration, envName) {
|
|
12382
|
+
function commitRequired(appDir, migration, envName, additionalPaths = []) {
|
|
12132
12383
|
const wranglerPath = wranglerRepoPath(appDir);
|
|
12384
|
+
const migrationPaths = [.../* @__PURE__ */ new Set([wranglerPath, ...additionalPaths])].sort();
|
|
12133
12385
|
return new Refusal(
|
|
12134
|
-
`Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic
|
|
12386
|
+
`Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic app migration changes, then rerun.`,
|
|
12135
12387
|
"migration_commit_required",
|
|
12136
12388
|
{
|
|
12137
12389
|
actionRequired: true,
|
|
@@ -12144,13 +12396,78 @@ function commitRequired(appDir, migration, envName) {
|
|
|
12144
12396
|
"--message",
|
|
12145
12397
|
`Migrate DeepSpace app identity to ${migration.appId}`,
|
|
12146
12398
|
"--",
|
|
12147
|
-
|
|
12399
|
+
...migrationPaths
|
|
12148
12400
|
]
|
|
12149
12401
|
},
|
|
12150
12402
|
extra: { migration, env: envName ?? null }
|
|
12151
12403
|
}
|
|
12152
12404
|
);
|
|
12153
12405
|
}
|
|
12406
|
+
function sourceMigrationCommitRequired(appDir, plan) {
|
|
12407
|
+
const migrationIds = plan.pending.map((migration) => migration.id);
|
|
12408
|
+
return new Refusal(
|
|
12409
|
+
`Applied ${migrationIds.join(", ")}. Commit the deterministic app migration changes, then rerun.`,
|
|
12410
|
+
"migration_commit_required",
|
|
12411
|
+
{
|
|
12412
|
+
actionRequired: true,
|
|
12413
|
+
action: {
|
|
12414
|
+
cwd: repoToplevel(appDir),
|
|
12415
|
+
argv: [
|
|
12416
|
+
"git",
|
|
12417
|
+
"commit",
|
|
12418
|
+
"--only",
|
|
12419
|
+
"--message",
|
|
12420
|
+
"Apply DeepSpace app migrations",
|
|
12421
|
+
"--",
|
|
12422
|
+
...plan.changedFiles
|
|
12423
|
+
]
|
|
12424
|
+
},
|
|
12425
|
+
extra: { sourceMigrations: plan.pending }
|
|
12426
|
+
}
|
|
12427
|
+
);
|
|
12428
|
+
}
|
|
12429
|
+
async function requireSourceMigrationDeploy(appDir, appId, deployUrl, token, options = {}) {
|
|
12430
|
+
const { release: release2 } = await repoApi(deployUrl, token, appId).latestRelease();
|
|
12431
|
+
const deployedIds = releaseMigrationIds(release2?.config);
|
|
12432
|
+
const pendingIds = APP_MIGRATION_DEFINITIONS.map(({ id }) => id).filter(
|
|
12433
|
+
(id) => !deployedIds.has(id)
|
|
12434
|
+
);
|
|
12435
|
+
const releaseAfterCutover = options.after === void 0 || typeof release2?.createdAt === "string" && Number.isFinite(Date.parse(release2.createdAt)) && Date.parse(release2.createdAt) > Date.parse(options.after);
|
|
12436
|
+
if (pendingIds.length === 0 && releaseAfterCutover) return;
|
|
12437
|
+
const sourceState = await getAppSource(deployUrl, token, appId);
|
|
12438
|
+
if (sourceState.source?.provider === "github") {
|
|
12439
|
+
const remote = requireGitHubRemote2(appDir, options.remoteName, sourceState.source.repository);
|
|
12440
|
+
requirePublishedHead2(appDir, remote);
|
|
12441
|
+
}
|
|
12442
|
+
throw new Refusal(
|
|
12443
|
+
pendingIds.length > 0 ? `App migrations ${pendingIds.join(", ")} are not in the live release. Deploy them, then rerun.` : "The canonical app has not been deployed since its identity cutover. Deploy it, then rerun.",
|
|
12444
|
+
"migration_deploy_required",
|
|
12445
|
+
{
|
|
12446
|
+
actionRequired: true,
|
|
12447
|
+
action: {
|
|
12448
|
+
cwd: appDir,
|
|
12449
|
+
argv: ["deepspace", "deploy", ...options.envName ? ["--env", options.envName] : []]
|
|
12450
|
+
},
|
|
12451
|
+
extra: { appId, sourceMigrations: pendingIds, after: options.after ?? null }
|
|
12452
|
+
}
|
|
12453
|
+
);
|
|
12454
|
+
}
|
|
12455
|
+
function releaseMigrationIds(config) {
|
|
12456
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return /* @__PURE__ */ new Set();
|
|
12457
|
+
const validation = validateAppMigrationIds(
|
|
12458
|
+
config.appMigrations ?? []
|
|
12459
|
+
);
|
|
12460
|
+
return new Set(validation.valid ? validation.ids : []);
|
|
12461
|
+
}
|
|
12462
|
+
function requireSourceMigrationWorktree(appDir, allowDirtyWrangler = false) {
|
|
12463
|
+
if (isWorkTreeClean(appDir)) return;
|
|
12464
|
+
const changed = statusFiles(repoToplevel(appDir));
|
|
12465
|
+
if (allowDirtyWrangler && changed.every((path) => path === wranglerRepoPath(appDir))) return;
|
|
12466
|
+
throw new Refusal(
|
|
12467
|
+
"Commit or discard unrelated working-tree changes before applying app migrations.",
|
|
12468
|
+
"dirty_worktree"
|
|
12469
|
+
);
|
|
12470
|
+
}
|
|
12154
12471
|
function restoreLegacyCommitRequired(appDir, migration, envName) {
|
|
12155
12472
|
const wranglerPath = wranglerRepoPath(appDir);
|
|
12156
12473
|
return new Refusal(
|
|
@@ -12200,8 +12517,16 @@ function deployRequired(appDir, migration, envName) {
|
|
|
12200
12517
|
}
|
|
12201
12518
|
);
|
|
12202
12519
|
}
|
|
12203
|
-
function
|
|
12204
|
-
if (!json) console.log(
|
|
12520
|
+
function reportUpToDate(appId, json) {
|
|
12521
|
+
if (!json) console.log(`No pending DeepSpace app migrations for ${appId}.`);
|
|
12522
|
+
}
|
|
12523
|
+
function reportSourcePlan(plan, json) {
|
|
12524
|
+
if (json || plan.pending.length === 0) return;
|
|
12525
|
+
for (const migration of plan.pending) {
|
|
12526
|
+
console.log(
|
|
12527
|
+
`Pending source migration: ${migration.id} (${migration.files.length} file${migration.files.length === 1 ? "" : "s"})`
|
|
12528
|
+
);
|
|
12529
|
+
}
|
|
12205
12530
|
}
|
|
12206
12531
|
function reportMigration(migration, json) {
|
|
12207
12532
|
if (json) return;
|
|
@@ -12213,8 +12538,8 @@ function reportMigration(migration, json) {
|
|
|
12213
12538
|
|
|
12214
12539
|
// src/cli/cli.ts
|
|
12215
12540
|
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
12216
|
-
var pkg = JSON.parse(
|
|
12217
|
-
if (process.platform === "linux" && !process.env.SSL_CERT_FILE &&
|
|
12541
|
+
var pkg = JSON.parse(readFileSync26(join24(__dirname, "..", "package.json"), "utf-8"));
|
|
12542
|
+
if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
|
|
12218
12543
|
process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
|
|
12219
12544
|
}
|
|
12220
12545
|
var create2 = defineCommand14({
|