@sakupa/mcp 0.7.44 → 0.7.45
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/dist/bin.js +72 -22
- package/dist/index.js +70 -20
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -380,7 +380,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
380
380
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
381
381
|
|
|
382
382
|
// ../core/dist/domain/version.js
|
|
383
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
383
|
+
var SAKUPA_MCP_VERSION = "0.7.45";
|
|
384
384
|
|
|
385
385
|
// ../core/dist/domain/errors.js
|
|
386
386
|
var HTTP_STATUS = {
|
|
@@ -810,8 +810,8 @@ var HttpApiClient = class {
|
|
|
810
810
|
body: body?.slice(0, 200)
|
|
811
811
|
});
|
|
812
812
|
}
|
|
813
|
-
async registerDevice() {
|
|
814
|
-
return this.call("POST", "/v1/devices");
|
|
813
|
+
async registerDevice(req) {
|
|
814
|
+
return this.call("POST", "/v1/devices", { body: req });
|
|
815
815
|
}
|
|
816
816
|
async listDeviceFreeSites(deviceId, credential) {
|
|
817
817
|
return this.call("GET", "/v1/devices/free-sites", {
|
|
@@ -984,7 +984,7 @@ var HttpApiClient = class {
|
|
|
984
984
|
};
|
|
985
985
|
|
|
986
986
|
// src/tools/definitions.ts
|
|
987
|
-
import { randomUUID as
|
|
987
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
988
988
|
import { promises as fs2 } from "node:fs";
|
|
989
989
|
import { join as join9, relative as relative3, resolve as resolve5, sep as sep4 } from "node:path";
|
|
990
990
|
import { z as z2 } from "zod";
|
|
@@ -2318,6 +2318,7 @@ import {
|
|
|
2318
2318
|
unlinkSync as unlinkSync2,
|
|
2319
2319
|
writeFileSync as writeFileSync4
|
|
2320
2320
|
} from "node:fs";
|
|
2321
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2321
2322
|
import { homedir as homedir3 } from "node:os";
|
|
2322
2323
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
2323
2324
|
var DEVICE_LOCK_STALE_MS = 3e4;
|
|
@@ -2327,10 +2328,22 @@ function deviceRegistryPath() {
|
|
|
2327
2328
|
return join6(base, ".sakupa", "devices.json");
|
|
2328
2329
|
}
|
|
2329
2330
|
var deviceLockPath = () => join6(dirname4(deviceRegistryPath()), "devices.lock");
|
|
2330
|
-
function
|
|
2331
|
+
function lockTokenAt(path) {
|
|
2332
|
+
try {
|
|
2333
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
2334
|
+
return typeof parsed.token === "string" ? parsed.token : null;
|
|
2335
|
+
} catch {
|
|
2336
|
+
return null;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
function ownsDeviceLock(lock) {
|
|
2340
|
+
return lockTokenAt(deviceLockPath()) === lock.token;
|
|
2341
|
+
}
|
|
2342
|
+
function releaseDeviceLock(lock) {
|
|
2331
2343
|
try {
|
|
2332
|
-
closeSync(
|
|
2344
|
+
closeSync(lock.fd);
|
|
2333
2345
|
} finally {
|
|
2346
|
+
if (!ownsDeviceLock(lock)) return;
|
|
2334
2347
|
try {
|
|
2335
2348
|
unlinkSync2(deviceLockPath());
|
|
2336
2349
|
} catch {
|
|
@@ -2345,9 +2358,13 @@ async function acquireDeviceLock(apiBaseUrl) {
|
|
|
2345
2358
|
const existing = loadDeviceBinding(apiBaseUrl);
|
|
2346
2359
|
if (existing) return existing;
|
|
2347
2360
|
try {
|
|
2361
|
+
const token = randomUUID3();
|
|
2348
2362
|
const fd2 = openSync(path, "wx", 384);
|
|
2349
|
-
writeFileSync4(
|
|
2350
|
-
|
|
2363
|
+
writeFileSync4(
|
|
2364
|
+
fd2,
|
|
2365
|
+
JSON.stringify({ token, pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
2366
|
+
);
|
|
2367
|
+
return { fd: fd2, token };
|
|
2351
2368
|
} catch {
|
|
2352
2369
|
try {
|
|
2353
2370
|
if (Date.now() - statSync2(path).mtimeMs >= DEVICE_LOCK_STALE_MS) {
|
|
@@ -2368,13 +2385,16 @@ async function acquireDeviceLock(apiBaseUrl) {
|
|
|
2368
2385
|
}
|
|
2369
2386
|
function readRegistry() {
|
|
2370
2387
|
const path = deviceRegistryPath();
|
|
2371
|
-
if (!existsSync5(path))
|
|
2388
|
+
if (!existsSync5(path)) {
|
|
2389
|
+
return { schemaVersion: 1, environments: {}, pendingRegistrations: {} };
|
|
2390
|
+
}
|
|
2372
2391
|
try {
|
|
2373
2392
|
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
2374
2393
|
if (parsed.schemaVersion !== 1 || !parsed.environments || typeof parsed.environments !== "object") {
|
|
2375
2394
|
throw new Error("unsupported device registry schema");
|
|
2376
2395
|
}
|
|
2377
|
-
|
|
2396
|
+
const pendingRegistrations = "pendingRegistrations" in parsed && parsed.pendingRegistrations && typeof parsed.pendingRegistrations === "object" ? parsed.pendingRegistrations : {};
|
|
2397
|
+
return { schemaVersion: 1, environments: parsed.environments, pendingRegistrations };
|
|
2378
2398
|
} catch (error) {
|
|
2379
2399
|
throw new Error(
|
|
2380
2400
|
`Sakupa device registry is unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}. Run help; do not search old project directories.`
|
|
@@ -2403,19 +2423,38 @@ async function ensureDeviceBinding(client, apiBaseUrl) {
|
|
|
2403
2423
|
const existing = loadDeviceBinding(apiBaseUrl);
|
|
2404
2424
|
if (existing) return existing;
|
|
2405
2425
|
const lock = await acquireDeviceLock(apiBaseUrl);
|
|
2406
|
-
if (
|
|
2426
|
+
if (!("fd" in lock)) return lock;
|
|
2407
2427
|
try {
|
|
2408
2428
|
const afterLock = loadDeviceBinding(apiBaseUrl);
|
|
2409
2429
|
if (afterLock) return afterLock;
|
|
2410
|
-
|
|
2411
|
-
|
|
2430
|
+
let registry = readRegistry();
|
|
2431
|
+
let pending = registry.pendingRegistrations[apiBaseUrl];
|
|
2432
|
+
if (!pending) {
|
|
2433
|
+
pending = {
|
|
2434
|
+
operationId: randomUUID3(),
|
|
2435
|
+
deviceId: randomUUID3(),
|
|
2436
|
+
credential: generateCredential(),
|
|
2437
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2438
|
+
};
|
|
2439
|
+
registry.pendingRegistrations[apiBaseUrl] = pending;
|
|
2440
|
+
writeRegistry(registry);
|
|
2441
|
+
}
|
|
2442
|
+
const created = await client.registerDevice({
|
|
2443
|
+
operationId: pending.operationId,
|
|
2444
|
+
deviceId: pending.deviceId,
|
|
2445
|
+
credential: pending.credential
|
|
2446
|
+
});
|
|
2412
2447
|
const binding = {
|
|
2413
2448
|
deviceId: created.deviceId,
|
|
2414
2449
|
credential: created.credential,
|
|
2415
2450
|
createdAt: created.createdAt
|
|
2416
2451
|
};
|
|
2417
|
-
|
|
2418
|
-
|
|
2452
|
+
if (ownsDeviceLock(lock)) {
|
|
2453
|
+
registry = readRegistry();
|
|
2454
|
+
registry.environments[apiBaseUrl] = binding;
|
|
2455
|
+
delete registry.pendingRegistrations[apiBaseUrl];
|
|
2456
|
+
writeRegistry(registry);
|
|
2457
|
+
}
|
|
2419
2458
|
return binding;
|
|
2420
2459
|
} finally {
|
|
2421
2460
|
releaseDeviceLock(lock);
|
|
@@ -2733,7 +2772,7 @@ import {
|
|
|
2733
2772
|
rmSync as rmSync2,
|
|
2734
2773
|
writeFileSync as writeFileSync6
|
|
2735
2774
|
} from "node:fs";
|
|
2736
|
-
import { randomUUID as
|
|
2775
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
2737
2776
|
import { join as join8 } from "node:path";
|
|
2738
2777
|
var ROTATION_FILE = "rotation.json";
|
|
2739
2778
|
function credentialRotationPath(projectDir) {
|
|
@@ -2794,7 +2833,7 @@ function writeCredentialRotation(projectDir, file) {
|
|
|
2794
2833
|
const directory = join8(projectDir, ".sakupa");
|
|
2795
2834
|
mkdirSync6(directory, { recursive: true, mode: 448 });
|
|
2796
2835
|
const target = credentialRotationPath(projectDir);
|
|
2797
|
-
const temporary = join8(directory, `.rotation-${
|
|
2836
|
+
const temporary = join8(directory, `.rotation-${randomUUID4()}.tmp`);
|
|
2798
2837
|
writeFileSync6(temporary, `${JSON.stringify(file, null, 2)}
|
|
2799
2838
|
`, {
|
|
2800
2839
|
encoding: "utf8",
|
|
@@ -3193,7 +3232,7 @@ function structuredToolResult(envelope) {
|
|
|
3193
3232
|
}
|
|
3194
3233
|
|
|
3195
3234
|
// src/tools/context.ts
|
|
3196
|
-
import { randomUUID as
|
|
3235
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
3197
3236
|
var LocalGuidanceError = class extends SakupaError {
|
|
3198
3237
|
constructor(code, message) {
|
|
3199
3238
|
super(code, message);
|
|
@@ -3272,7 +3311,7 @@ function reportAuthorizationStore(ctx) {
|
|
|
3272
3311
|
return store;
|
|
3273
3312
|
}
|
|
3274
3313
|
function issueReportAuthorization(ctx, failedTool) {
|
|
3275
|
-
const token =
|
|
3314
|
+
const token = randomUUID5();
|
|
3276
3315
|
reportAuthorizationStore(ctx).set(token, {
|
|
3277
3316
|
failedTool,
|
|
3278
3317
|
expiresAt: Date.now() + 10 * 60 * 1e3
|
|
@@ -3542,9 +3581,15 @@ function freeSiteCreationBarrier(sites, deployArguments) {
|
|
|
3542
3581
|
});
|
|
3543
3582
|
}
|
|
3544
3583
|
async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
3584
|
+
let cloudSites = (await client.listDeviceFreeSites(device.deviceId, device.credential)).sites;
|
|
3585
|
+
const alreadyOwned = new Set(cloudSites.map((site) => site.siteId));
|
|
3586
|
+
let claimedAny = false;
|
|
3545
3587
|
for (const record of listRecentCreations(Date.now(), apiBaseUrl)) {
|
|
3546
3588
|
const state = loadSiteFile(record.projectDir);
|
|
3547
3589
|
if (state.kind !== "ok" || state.file.siteId !== record.siteId) continue;
|
|
3590
|
+
if (state.file.apiBaseUrl !== "" && state.file.apiBaseUrl !== apiBaseUrl) continue;
|
|
3591
|
+
if (alreadyOwned.has(record.siteId)) continue;
|
|
3592
|
+
const environmentIsKnown = record.apiBaseUrl === apiBaseUrl || state.file.apiBaseUrl === apiBaseUrl;
|
|
3548
3593
|
try {
|
|
3549
3594
|
await client.claimDeviceFreeSite(
|
|
3550
3595
|
record.siteId,
|
|
@@ -3552,9 +3597,11 @@ async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
|
3552
3597
|
device.deviceId,
|
|
3553
3598
|
device.credential
|
|
3554
3599
|
);
|
|
3600
|
+
claimedAny = true;
|
|
3601
|
+
alreadyOwned.add(record.siteId);
|
|
3555
3602
|
} catch (error) {
|
|
3556
3603
|
if (isSakupaError(error) && ["not_found", "state_conflict"].includes(error.code)) {
|
|
3557
|
-
removeCreation(record.siteId);
|
|
3604
|
+
if (environmentIsKnown) removeCreation(record.siteId);
|
|
3558
3605
|
continue;
|
|
3559
3606
|
}
|
|
3560
3607
|
if (!isSakupaError(error) || error.code !== "unauthorized") {
|
|
@@ -3562,7 +3609,10 @@ async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
|
3562
3609
|
}
|
|
3563
3610
|
}
|
|
3564
3611
|
}
|
|
3565
|
-
|
|
3612
|
+
if (claimedAny) {
|
|
3613
|
+
cloudSites = (await client.listDeviceFreeSites(device.deviceId, device.credential)).sites;
|
|
3614
|
+
}
|
|
3615
|
+
return cloudSites;
|
|
3566
3616
|
}
|
|
3567
3617
|
function outputDirectoryChain(projectRoot, outputAbs) {
|
|
3568
3618
|
const rel = relative3(projectRoot, outputAbs);
|
|
@@ -4279,7 +4329,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4279
4329
|
{
|
|
4280
4330
|
siteId: site.siteId,
|
|
4281
4331
|
plan: args.plan,
|
|
4282
|
-
idempotencyKey:
|
|
4332
|
+
idempotencyKey: randomUUID6()
|
|
4283
4333
|
},
|
|
4284
4334
|
site.credential
|
|
4285
4335
|
);
|
package/dist/index.js
CHANGED
|
@@ -125,7 +125,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
125
125
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
126
126
|
|
|
127
127
|
// ../core/dist/domain/version.js
|
|
128
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
128
|
+
var SAKUPA_MCP_VERSION = "0.7.45";
|
|
129
129
|
|
|
130
130
|
// ../core/dist/domain/errors.js
|
|
131
131
|
var HTTP_STATUS = {
|
|
@@ -736,8 +736,8 @@ var HttpApiClient = class {
|
|
|
736
736
|
body: body?.slice(0, 200)
|
|
737
737
|
});
|
|
738
738
|
}
|
|
739
|
-
async registerDevice() {
|
|
740
|
-
return this.call("POST", "/v1/devices");
|
|
739
|
+
async registerDevice(req) {
|
|
740
|
+
return this.call("POST", "/v1/devices", { body: req });
|
|
741
741
|
}
|
|
742
742
|
async listDeviceFreeSites(deviceId, credential) {
|
|
743
743
|
return this.call("GET", "/v1/devices/free-sites", {
|
|
@@ -2211,7 +2211,7 @@ function toolError(e) {
|
|
|
2211
2211
|
}
|
|
2212
2212
|
|
|
2213
2213
|
// src/tools/definitions.ts
|
|
2214
|
-
import { randomUUID as
|
|
2214
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
2215
2215
|
import { promises as fs2 } from "node:fs";
|
|
2216
2216
|
import { join as join9, relative as relative3, resolve as resolve5, sep as sep4 } from "node:path";
|
|
2217
2217
|
import { z as z2 } from "zod";
|
|
@@ -2939,6 +2939,7 @@ import {
|
|
|
2939
2939
|
unlinkSync as unlinkSync2,
|
|
2940
2940
|
writeFileSync as writeFileSync4
|
|
2941
2941
|
} from "node:fs";
|
|
2942
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
2942
2943
|
import { homedir as homedir3 } from "node:os";
|
|
2943
2944
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
2944
2945
|
var DEVICE_LOCK_STALE_MS = 3e4;
|
|
@@ -2948,10 +2949,22 @@ function deviceRegistryPath() {
|
|
|
2948
2949
|
return join6(base, ".sakupa", "devices.json");
|
|
2949
2950
|
}
|
|
2950
2951
|
var deviceLockPath = () => join6(dirname4(deviceRegistryPath()), "devices.lock");
|
|
2951
|
-
function
|
|
2952
|
+
function lockTokenAt(path) {
|
|
2953
|
+
try {
|
|
2954
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
2955
|
+
return typeof parsed.token === "string" ? parsed.token : null;
|
|
2956
|
+
} catch {
|
|
2957
|
+
return null;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
function ownsDeviceLock(lock) {
|
|
2961
|
+
return lockTokenAt(deviceLockPath()) === lock.token;
|
|
2962
|
+
}
|
|
2963
|
+
function releaseDeviceLock(lock) {
|
|
2952
2964
|
try {
|
|
2953
|
-
closeSync(
|
|
2965
|
+
closeSync(lock.fd);
|
|
2954
2966
|
} finally {
|
|
2967
|
+
if (!ownsDeviceLock(lock)) return;
|
|
2955
2968
|
try {
|
|
2956
2969
|
unlinkSync2(deviceLockPath());
|
|
2957
2970
|
} catch {
|
|
@@ -2966,9 +2979,13 @@ async function acquireDeviceLock(apiBaseUrl) {
|
|
|
2966
2979
|
const existing = loadDeviceBinding(apiBaseUrl);
|
|
2967
2980
|
if (existing) return existing;
|
|
2968
2981
|
try {
|
|
2982
|
+
const token = randomUUID4();
|
|
2969
2983
|
const fd2 = openSync(path, "wx", 384);
|
|
2970
|
-
writeFileSync4(
|
|
2971
|
-
|
|
2984
|
+
writeFileSync4(
|
|
2985
|
+
fd2,
|
|
2986
|
+
JSON.stringify({ token, pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
2987
|
+
);
|
|
2988
|
+
return { fd: fd2, token };
|
|
2972
2989
|
} catch {
|
|
2973
2990
|
try {
|
|
2974
2991
|
if (Date.now() - statSync2(path).mtimeMs >= DEVICE_LOCK_STALE_MS) {
|
|
@@ -2989,13 +3006,16 @@ async function acquireDeviceLock(apiBaseUrl) {
|
|
|
2989
3006
|
}
|
|
2990
3007
|
function readRegistry() {
|
|
2991
3008
|
const path = deviceRegistryPath();
|
|
2992
|
-
if (!existsSync5(path))
|
|
3009
|
+
if (!existsSync5(path)) {
|
|
3010
|
+
return { schemaVersion: 1, environments: {}, pendingRegistrations: {} };
|
|
3011
|
+
}
|
|
2993
3012
|
try {
|
|
2994
3013
|
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
2995
3014
|
if (parsed.schemaVersion !== 1 || !parsed.environments || typeof parsed.environments !== "object") {
|
|
2996
3015
|
throw new Error("unsupported device registry schema");
|
|
2997
3016
|
}
|
|
2998
|
-
|
|
3017
|
+
const pendingRegistrations = "pendingRegistrations" in parsed && parsed.pendingRegistrations && typeof parsed.pendingRegistrations === "object" ? parsed.pendingRegistrations : {};
|
|
3018
|
+
return { schemaVersion: 1, environments: parsed.environments, pendingRegistrations };
|
|
2999
3019
|
} catch (error) {
|
|
3000
3020
|
throw new Error(
|
|
3001
3021
|
`Sakupa device registry is unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}. Run help; do not search old project directories.`
|
|
@@ -3024,19 +3044,38 @@ async function ensureDeviceBinding(client, apiBaseUrl) {
|
|
|
3024
3044
|
const existing = loadDeviceBinding(apiBaseUrl);
|
|
3025
3045
|
if (existing) return existing;
|
|
3026
3046
|
const lock = await acquireDeviceLock(apiBaseUrl);
|
|
3027
|
-
if (
|
|
3047
|
+
if (!("fd" in lock)) return lock;
|
|
3028
3048
|
try {
|
|
3029
3049
|
const afterLock = loadDeviceBinding(apiBaseUrl);
|
|
3030
3050
|
if (afterLock) return afterLock;
|
|
3031
|
-
|
|
3032
|
-
|
|
3051
|
+
let registry = readRegistry();
|
|
3052
|
+
let pending = registry.pendingRegistrations[apiBaseUrl];
|
|
3053
|
+
if (!pending) {
|
|
3054
|
+
pending = {
|
|
3055
|
+
operationId: randomUUID4(),
|
|
3056
|
+
deviceId: randomUUID4(),
|
|
3057
|
+
credential: generateCredential(),
|
|
3058
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3059
|
+
};
|
|
3060
|
+
registry.pendingRegistrations[apiBaseUrl] = pending;
|
|
3061
|
+
writeRegistry(registry);
|
|
3062
|
+
}
|
|
3063
|
+
const created = await client.registerDevice({
|
|
3064
|
+
operationId: pending.operationId,
|
|
3065
|
+
deviceId: pending.deviceId,
|
|
3066
|
+
credential: pending.credential
|
|
3067
|
+
});
|
|
3033
3068
|
const binding = {
|
|
3034
3069
|
deviceId: created.deviceId,
|
|
3035
3070
|
credential: created.credential,
|
|
3036
3071
|
createdAt: created.createdAt
|
|
3037
3072
|
};
|
|
3038
|
-
|
|
3039
|
-
|
|
3073
|
+
if (ownsDeviceLock(lock)) {
|
|
3074
|
+
registry = readRegistry();
|
|
3075
|
+
registry.environments[apiBaseUrl] = binding;
|
|
3076
|
+
delete registry.pendingRegistrations[apiBaseUrl];
|
|
3077
|
+
writeRegistry(registry);
|
|
3078
|
+
}
|
|
3040
3079
|
return binding;
|
|
3041
3080
|
} finally {
|
|
3042
3081
|
releaseDeviceLock(lock);
|
|
@@ -3321,7 +3360,7 @@ import {
|
|
|
3321
3360
|
rmSync as rmSync2,
|
|
3322
3361
|
writeFileSync as writeFileSync6
|
|
3323
3362
|
} from "node:fs";
|
|
3324
|
-
import { randomUUID as
|
|
3363
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
3325
3364
|
import { join as join8 } from "node:path";
|
|
3326
3365
|
var ROTATION_FILE = "rotation.json";
|
|
3327
3366
|
function credentialRotationPath(projectDir) {
|
|
@@ -3382,7 +3421,7 @@ function writeCredentialRotation(projectDir, file) {
|
|
|
3382
3421
|
const directory = join8(projectDir, ".sakupa");
|
|
3383
3422
|
mkdirSync6(directory, { recursive: true, mode: 448 });
|
|
3384
3423
|
const target = credentialRotationPath(projectDir);
|
|
3385
|
-
const temporary = join8(directory, `.rotation-${
|
|
3424
|
+
const temporary = join8(directory, `.rotation-${randomUUID5()}.tmp`);
|
|
3386
3425
|
writeFileSync6(temporary, `${JSON.stringify(file, null, 2)}
|
|
3387
3426
|
`, {
|
|
3388
3427
|
encoding: "utf8",
|
|
@@ -3658,9 +3697,15 @@ function freeSiteCreationBarrier(sites, deployArguments) {
|
|
|
3658
3697
|
});
|
|
3659
3698
|
}
|
|
3660
3699
|
async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
3700
|
+
let cloudSites = (await client.listDeviceFreeSites(device.deviceId, device.credential)).sites;
|
|
3701
|
+
const alreadyOwned = new Set(cloudSites.map((site) => site.siteId));
|
|
3702
|
+
let claimedAny = false;
|
|
3661
3703
|
for (const record of listRecentCreations(Date.now(), apiBaseUrl)) {
|
|
3662
3704
|
const state = loadSiteFile(record.projectDir);
|
|
3663
3705
|
if (state.kind !== "ok" || state.file.siteId !== record.siteId) continue;
|
|
3706
|
+
if (state.file.apiBaseUrl !== "" && state.file.apiBaseUrl !== apiBaseUrl) continue;
|
|
3707
|
+
if (alreadyOwned.has(record.siteId)) continue;
|
|
3708
|
+
const environmentIsKnown = record.apiBaseUrl === apiBaseUrl || state.file.apiBaseUrl === apiBaseUrl;
|
|
3664
3709
|
try {
|
|
3665
3710
|
await client.claimDeviceFreeSite(
|
|
3666
3711
|
record.siteId,
|
|
@@ -3668,9 +3713,11 @@ async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
|
3668
3713
|
device.deviceId,
|
|
3669
3714
|
device.credential
|
|
3670
3715
|
);
|
|
3716
|
+
claimedAny = true;
|
|
3717
|
+
alreadyOwned.add(record.siteId);
|
|
3671
3718
|
} catch (error) {
|
|
3672
3719
|
if (isSakupaError(error) && ["not_found", "state_conflict"].includes(error.code)) {
|
|
3673
|
-
removeCreation(record.siteId);
|
|
3720
|
+
if (environmentIsKnown) removeCreation(record.siteId);
|
|
3674
3721
|
continue;
|
|
3675
3722
|
}
|
|
3676
3723
|
if (!isSakupaError(error) || error.code !== "unauthorized") {
|
|
@@ -3678,7 +3725,10 @@ async function discoverDeviceFreeSites(client, apiBaseUrl, device) {
|
|
|
3678
3725
|
}
|
|
3679
3726
|
}
|
|
3680
3727
|
}
|
|
3681
|
-
|
|
3728
|
+
if (claimedAny) {
|
|
3729
|
+
cloudSites = (await client.listDeviceFreeSites(device.deviceId, device.credential)).sites;
|
|
3730
|
+
}
|
|
3731
|
+
return cloudSites;
|
|
3682
3732
|
}
|
|
3683
3733
|
function outputDirectoryChain(projectRoot, outputAbs) {
|
|
3684
3734
|
const rel = relative3(projectRoot, outputAbs);
|
|
@@ -4395,7 +4445,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4395
4445
|
{
|
|
4396
4446
|
siteId: site.siteId,
|
|
4397
4447
|
plan: args.plan,
|
|
4398
|
-
idempotencyKey:
|
|
4448
|
+
idempotencyKey: randomUUID6()
|
|
4399
4449
|
},
|
|
4400
4450
|
site.credential
|
|
4401
4451
|
);
|