@specific.dev/cli 0.1.45 → 0.1.47
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/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.__PAGE__.txt +2 -2
- package/dist/admin/__next._full.txt +9 -9
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +8 -8
- package/dist/admin/__next._tree.txt +2 -2
- package/dist/admin/_next/static/chunks/64efcb432fae8c98.js +1 -0
- package/dist/admin/_next/static/chunks/{b4fa9a08d6dc37c1.js → 75cb455f07e7651a.js} +1 -1
- package/dist/admin/_next/static/chunks/951210b423dc9315.css +4 -0
- package/dist/admin/_next/static/chunks/ce9a5f692b87aaa9.js +5 -0
- package/dist/admin/_next/static/chunks/d2b1f8ba26497c0b.js +1 -0
- package/dist/admin/_not-found/__next._full.txt +8 -8
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +8 -8
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +2 -2
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +8 -8
- package/dist/admin/databases/__next._full.txt +9 -9
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +8 -8
- package/dist/admin/databases/__next._tree.txt +2 -2
- package/dist/admin/databases/__next.databases.__PAGE__.txt +2 -2
- package/dist/admin/databases/__next.databases.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +9 -9
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +9 -9
- package/dist/cli.js +346 -162
- package/package.json +5 -3
- package/dist/admin/_next/static/chunks/0cb2c4291ba35581.css +0 -4
- package/dist/admin/_next/static/chunks/721087f8fbaa34b3.js +0 -1
- package/dist/admin/_next/static/chunks/db72df2e613a023e.js +0 -5
- package/dist/admin/_next/static/chunks/e3f73cf77dd1ede1.js +0 -1
- /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → pcYHo7d7--ealoH3_ELSO}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → pcYHo7d7--ealoH3_ELSO}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → pcYHo7d7--ealoH3_ELSO}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -183398,6 +183398,36 @@ var ApiClient = class {
|
|
|
183398
183398
|
}
|
|
183399
183399
|
return response.json();
|
|
183400
183400
|
}
|
|
183401
|
+
async startDeployment(deploymentId) {
|
|
183402
|
+
const url = `${this.baseUrl}/deployments/${deploymentId}/start`;
|
|
183403
|
+
writeLog("api", `POST ${url}`);
|
|
183404
|
+
const response = await fetch(url, {
|
|
183405
|
+
method: "POST",
|
|
183406
|
+
headers: this.authHeaders()
|
|
183407
|
+
});
|
|
183408
|
+
writeLog("api", `Response: ${response.status} ${response.statusText}`);
|
|
183409
|
+
if (!response.ok) {
|
|
183410
|
+
let errorBody;
|
|
183411
|
+
try {
|
|
183412
|
+
const error = await response.json();
|
|
183413
|
+
errorBody = JSON.stringify(error);
|
|
183414
|
+
writeLog("api:error", `API error: ${error.error} (${error.code})`);
|
|
183415
|
+
writeLog("api:error", `Request was: POST ${url}`);
|
|
183416
|
+
writeLog("api:error", `Response body: ${errorBody}`);
|
|
183417
|
+
throw new Error(
|
|
183418
|
+
`Failed to start deployment: ${error.error} (${error.code})`
|
|
183419
|
+
);
|
|
183420
|
+
} catch (e) {
|
|
183421
|
+
if (e instanceof Error && e.message.startsWith("Failed to start")) {
|
|
183422
|
+
throw e;
|
|
183423
|
+
}
|
|
183424
|
+
errorBody = await response.text();
|
|
183425
|
+
writeLog("api:error", `Failed to parse error response: ${errorBody}`);
|
|
183426
|
+
throw new Error(`Failed to start deployment: ${response.statusText}`);
|
|
183427
|
+
}
|
|
183428
|
+
}
|
|
183429
|
+
return response.json();
|
|
183430
|
+
}
|
|
183401
183431
|
async submitSecrets(deploymentId, secrets) {
|
|
183402
183432
|
const secretKeys = Object.keys(secrets);
|
|
183403
183433
|
const url = `${this.baseUrl}/deployments/${deploymentId}/secrets`;
|
|
@@ -183539,7 +183569,7 @@ var ApiClient = class {
|
|
|
183539
183569
|
writeLog("api", `GET ${url}`);
|
|
183540
183570
|
const response = await fetch(url, {
|
|
183541
183571
|
headers: this.authHeaders(),
|
|
183542
|
-
signal
|
|
183572
|
+
...signal ? { signal } : {}
|
|
183543
183573
|
});
|
|
183544
183574
|
writeLog("api", `Response: ${response.status} ${response.statusText}`);
|
|
183545
183575
|
if (!response.ok) {
|
|
@@ -183843,7 +183873,7 @@ function trackEvent(event, properties) {
|
|
|
183843
183873
|
event,
|
|
183844
183874
|
properties: {
|
|
183845
183875
|
...properties,
|
|
183846
|
-
cli_version: "0.1.
|
|
183876
|
+
cli_version: "0.1.47",
|
|
183847
183877
|
platform: process.platform,
|
|
183848
183878
|
node_version: process.version,
|
|
183849
183879
|
project_id: getProjectId(),
|
|
@@ -186546,8 +186576,9 @@ var StablePortAllocator = class {
|
|
|
186546
186576
|
return port;
|
|
186547
186577
|
}
|
|
186548
186578
|
allocate(key) {
|
|
186549
|
-
|
|
186550
|
-
|
|
186579
|
+
const savedPort = this.savedPorts[key];
|
|
186580
|
+
if (savedPort !== void 0) {
|
|
186581
|
+
return savedPort;
|
|
186551
186582
|
}
|
|
186552
186583
|
const port = this.allocateRandom();
|
|
186553
186584
|
this.savedPorts[key] = port;
|
|
@@ -186614,10 +186645,11 @@ function getPlatformInfo() {
|
|
|
186614
186645
|
`Unsupported platform: ${platform5}. Only macOS and Linux are supported.`
|
|
186615
186646
|
);
|
|
186616
186647
|
}
|
|
186648
|
+
const archStr = arch3;
|
|
186617
186649
|
let mappedArch;
|
|
186618
|
-
if (
|
|
186650
|
+
if (archStr === "x64" || archStr === "x86_64") {
|
|
186619
186651
|
mappedArch = "x64";
|
|
186620
|
-
} else if (
|
|
186652
|
+
} else if (archStr === "arm64" || archStr === "aarch64") {
|
|
186621
186653
|
mappedArch = "arm64";
|
|
186622
186654
|
} else {
|
|
186623
186655
|
throw new Error(
|
|
@@ -187318,7 +187350,7 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187318
187350
|
}
|
|
187319
187351
|
return pg.syncSecret;
|
|
187320
187352
|
default:
|
|
187321
|
-
throw new Error(`Unknown postgres attribute: ${value.attribute}`);
|
|
187353
|
+
throw new Error(`Unknown postgres attribute: ${String(value.attribute)}`);
|
|
187322
187354
|
}
|
|
187323
187355
|
}
|
|
187324
187356
|
case "redis": {
|
|
@@ -187336,7 +187368,7 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187336
187368
|
case "password":
|
|
187337
187369
|
return redis.password;
|
|
187338
187370
|
default:
|
|
187339
|
-
throw new Error(`Unknown redis attribute: ${value.attribute}`);
|
|
187371
|
+
throw new Error(`Unknown redis attribute: ${String(value.attribute)}`);
|
|
187340
187372
|
}
|
|
187341
187373
|
}
|
|
187342
187374
|
case "storage": {
|
|
@@ -187366,7 +187398,7 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187366
187398
|
}
|
|
187367
187399
|
return storage.bucket;
|
|
187368
187400
|
default:
|
|
187369
|
-
throw new Error(`Unknown storage attribute: ${value.attribute}`);
|
|
187401
|
+
throw new Error(`Unknown storage attribute: ${String(value.attribute)}`);
|
|
187370
187402
|
}
|
|
187371
187403
|
}
|
|
187372
187404
|
case "config": {
|
|
@@ -187927,9 +187959,9 @@ function extractServiceAndKey(host) {
|
|
|
187927
187959
|
return null;
|
|
187928
187960
|
}
|
|
187929
187961
|
const parts = prefix.split(".");
|
|
187930
|
-
if (parts.length === 1) {
|
|
187962
|
+
if (parts.length === 1 && parts[0]) {
|
|
187931
187963
|
return { serviceName: parts[0], key: "default" };
|
|
187932
|
-
} else if (parts.length === 2) {
|
|
187964
|
+
} else if (parts.length === 2 && parts[0] && parts[1]) {
|
|
187933
187965
|
return { serviceName: parts[0], key: parts[1] };
|
|
187934
187966
|
}
|
|
187935
187967
|
return null;
|
|
@@ -187946,7 +187978,7 @@ function extractDrizzleGatewayKey(host) {
|
|
|
187946
187978
|
const parts = prefix.split(".");
|
|
187947
187979
|
if (parts.length === 1 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
|
|
187948
187980
|
return "default";
|
|
187949
|
-
} else if (parts.length === 2 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
|
|
187981
|
+
} else if (parts.length === 2 && parts[0] === DRIZZLE_GATEWAY_PREFIX && parts[1]) {
|
|
187950
187982
|
return parts[1];
|
|
187951
187983
|
}
|
|
187952
187984
|
return null;
|
|
@@ -187967,7 +187999,7 @@ function extractAdminKey(host) {
|
|
|
187967
187999
|
return null;
|
|
187968
188000
|
}
|
|
187969
188001
|
const parts = prefix.split(".");
|
|
187970
|
-
if (parts.length === 1) {
|
|
188002
|
+
if (parts.length === 1 && parts[0]) {
|
|
187971
188003
|
return parts[0];
|
|
187972
188004
|
}
|
|
187973
188005
|
return null;
|
|
@@ -188592,6 +188624,7 @@ function watchConfigFile(configPath, debounceMs, onChange) {
|
|
|
188592
188624
|
import * as fs18 from "fs";
|
|
188593
188625
|
import * as path15 from "path";
|
|
188594
188626
|
import * as os8 from "os";
|
|
188627
|
+
import * as net4 from "net";
|
|
188595
188628
|
var ProxyRegistryManager = class {
|
|
188596
188629
|
proxyDir;
|
|
188597
188630
|
ownerPath;
|
|
@@ -188619,6 +188652,46 @@ var ProxyRegistryManager = class {
|
|
|
188619
188652
|
return err.code !== "ESRCH";
|
|
188620
188653
|
}
|
|
188621
188654
|
}
|
|
188655
|
+
/**
|
|
188656
|
+
* Check if the proxy is actually listening on its expected port.
|
|
188657
|
+
* This catches cases where the owner process is alive but the proxy has crashed.
|
|
188658
|
+
*/
|
|
188659
|
+
isProxyListening(port, timeoutMs = 1e3) {
|
|
188660
|
+
return new Promise((resolve7) => {
|
|
188661
|
+
const socket = new net4.Socket();
|
|
188662
|
+
let resolved = false;
|
|
188663
|
+
const cleanup = () => {
|
|
188664
|
+
if (!resolved) {
|
|
188665
|
+
resolved = true;
|
|
188666
|
+
socket.destroy();
|
|
188667
|
+
}
|
|
188668
|
+
};
|
|
188669
|
+
socket.setTimeout(timeoutMs);
|
|
188670
|
+
socket.on("connect", () => {
|
|
188671
|
+
cleanup();
|
|
188672
|
+
resolve7(true);
|
|
188673
|
+
});
|
|
188674
|
+
socket.on("timeout", () => {
|
|
188675
|
+
cleanup();
|
|
188676
|
+
resolve7(false);
|
|
188677
|
+
});
|
|
188678
|
+
socket.on("error", () => {
|
|
188679
|
+
cleanup();
|
|
188680
|
+
resolve7(false);
|
|
188681
|
+
});
|
|
188682
|
+
socket.connect(port, "127.0.0.1");
|
|
188683
|
+
});
|
|
188684
|
+
}
|
|
188685
|
+
/**
|
|
188686
|
+
* Check if the proxy owner is healthy (process running AND proxy listening).
|
|
188687
|
+
*/
|
|
188688
|
+
async isProxyOwnerHealthy(pid) {
|
|
188689
|
+
if (!this.isProcessRunning(pid)) {
|
|
188690
|
+
return false;
|
|
188691
|
+
}
|
|
188692
|
+
const isListening = await this.isProxyListening(443);
|
|
188693
|
+
return isListening;
|
|
188694
|
+
}
|
|
188622
188695
|
async acquireLock(timeoutMs = 5e3) {
|
|
188623
188696
|
this.ensureProxyDir();
|
|
188624
188697
|
const startTime = Date.now();
|
|
@@ -188673,7 +188746,7 @@ var ProxyRegistryManager = class {
|
|
|
188673
188746
|
if (fs18.existsSync(this.ownerPath)) {
|
|
188674
188747
|
const content = fs18.readFileSync(this.ownerPath, "utf-8");
|
|
188675
188748
|
const ownerFile2 = JSON.parse(content);
|
|
188676
|
-
if (this.
|
|
188749
|
+
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188677
188750
|
return false;
|
|
188678
188751
|
}
|
|
188679
188752
|
}
|
|
@@ -188724,7 +188797,7 @@ var ProxyRegistryManager = class {
|
|
|
188724
188797
|
try {
|
|
188725
188798
|
const content = fs18.readFileSync(this.ownerPath, "utf-8");
|
|
188726
188799
|
const ownerFile = JSON.parse(content);
|
|
188727
|
-
if (!this.
|
|
188800
|
+
if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
|
|
188728
188801
|
return null;
|
|
188729
188802
|
}
|
|
188730
188803
|
return ownerFile.owner;
|
|
@@ -188863,7 +188936,7 @@ var ProxyRegistryManager = class {
|
|
|
188863
188936
|
if (fs18.existsSync(this.ownerPath)) {
|
|
188864
188937
|
const content = fs18.readFileSync(this.ownerPath, "utf-8");
|
|
188865
188938
|
const ownerFile2 = JSON.parse(content);
|
|
188866
|
-
if (this.
|
|
188939
|
+
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188867
188940
|
return false;
|
|
188868
188941
|
}
|
|
188869
188942
|
fs18.unlinkSync(this.ownerPath);
|
|
@@ -189502,14 +189575,15 @@ Add them to the config block in specific.local`);
|
|
|
189502
189575
|
}
|
|
189503
189576
|
}
|
|
189504
189577
|
const validationErrors = validateEndpointReferences(config2);
|
|
189505
|
-
|
|
189578
|
+
const firstError = validationErrors[0];
|
|
189579
|
+
if (firstError) {
|
|
189506
189580
|
for (const error of validationErrors) {
|
|
189507
189581
|
writeLog("system:error", error.message);
|
|
189508
189582
|
}
|
|
189509
189583
|
setState((s) => ({
|
|
189510
189584
|
...s,
|
|
189511
189585
|
status: "error",
|
|
189512
|
-
error:
|
|
189586
|
+
error: firstError.message
|
|
189513
189587
|
}));
|
|
189514
189588
|
return;
|
|
189515
189589
|
}
|
|
@@ -190285,16 +190359,19 @@ function findWidestContext(projectDir, contexts) {
|
|
|
190285
190359
|
if (contexts.length === 0) return ".";
|
|
190286
190360
|
const absolute = contexts.map((c) => path18.resolve(projectDir, c));
|
|
190287
190361
|
const segments = absolute.map((p) => p.split(path18.sep).filter(Boolean));
|
|
190362
|
+
const firstSegments = segments[0];
|
|
190363
|
+
if (!firstSegments) return ".";
|
|
190288
190364
|
const minLen = Math.min(...segments.map((s) => s.length));
|
|
190289
190365
|
let commonLength = 0;
|
|
190290
190366
|
for (let i = 0; i < minLen; i++) {
|
|
190291
|
-
|
|
190367
|
+
const firstSeg = firstSegments[i];
|
|
190368
|
+
if (firstSeg !== void 0 && segments.every((s) => s[i] === firstSeg)) {
|
|
190292
190369
|
commonLength = i + 1;
|
|
190293
190370
|
} else {
|
|
190294
190371
|
break;
|
|
190295
190372
|
}
|
|
190296
190373
|
}
|
|
190297
|
-
const ancestorSegments =
|
|
190374
|
+
const ancestorSegments = firstSegments.slice(0, commonLength);
|
|
190298
190375
|
const ancestor = path18.sep + ancestorSegments.join(path18.sep);
|
|
190299
190376
|
return path18.relative(projectDir, ancestor) || ".";
|
|
190300
190377
|
}
|
|
@@ -190328,7 +190405,7 @@ function PhaseIndicator({
|
|
|
190328
190405
|
"creating-tarball",
|
|
190329
190406
|
"creating-deployment",
|
|
190330
190407
|
"uploading",
|
|
190331
|
-
"
|
|
190408
|
+
"pending",
|
|
190332
190409
|
"deploying",
|
|
190333
190410
|
"success"
|
|
190334
190411
|
];
|
|
@@ -190366,7 +190443,10 @@ function ProjectSelector({
|
|
|
190366
190443
|
if (selectedIndex === 0) {
|
|
190367
190444
|
onSelect("new");
|
|
190368
190445
|
} else {
|
|
190369
|
-
|
|
190446
|
+
const project = projects[selectedIndex - 1];
|
|
190447
|
+
if (project) {
|
|
190448
|
+
onSelect(project);
|
|
190449
|
+
}
|
|
190370
190450
|
}
|
|
190371
190451
|
} else if (key.upArrow) {
|
|
190372
190452
|
onUp();
|
|
@@ -190397,6 +190477,30 @@ function NameInput({ onSubmit, onCancel }) {
|
|
|
190397
190477
|
});
|
|
190398
190478
|
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Create new project"), /* @__PURE__ */ React7.createElement(Text7, null, "Enter project name:"), /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, "> "), /* @__PURE__ */ React7.createElement(Text7, null, value), /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "|")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Press Enter to create, Esc to go back"));
|
|
190399
190479
|
}
|
|
190480
|
+
function getMissingSecrets(pendingActions) {
|
|
190481
|
+
if (!pendingActions) return [];
|
|
190482
|
+
const secretAction = pendingActions.find((a) => a.type === "missing_secrets");
|
|
190483
|
+
if (secretAction && secretAction.type === "missing_secrets") {
|
|
190484
|
+
return secretAction.secrets;
|
|
190485
|
+
}
|
|
190486
|
+
return [];
|
|
190487
|
+
}
|
|
190488
|
+
function getMissingConfigs(pendingActions) {
|
|
190489
|
+
if (!pendingActions) return [];
|
|
190490
|
+
const configAction = pendingActions.find((a) => a.type === "missing_configs");
|
|
190491
|
+
if (configAction && configAction.type === "missing_configs") {
|
|
190492
|
+
return configAction.configs;
|
|
190493
|
+
}
|
|
190494
|
+
return [];
|
|
190495
|
+
}
|
|
190496
|
+
function hasBuildsInProgress(pendingActions) {
|
|
190497
|
+
if (!pendingActions) return false;
|
|
190498
|
+
return pendingActions.some((a) => a.type === "build_in_progress");
|
|
190499
|
+
}
|
|
190500
|
+
function getFailedBuild(pendingActions) {
|
|
190501
|
+
if (!pendingActions) return void 0;
|
|
190502
|
+
return pendingActions.find((a) => a.type === "build_failed");
|
|
190503
|
+
}
|
|
190400
190504
|
function DeployUI({ environment, config, skipBuildTest }) {
|
|
190401
190505
|
const { exit } = useApp3();
|
|
190402
190506
|
const [state, setState] = useState6({ phase: "checking-auth" });
|
|
@@ -190565,6 +190669,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
|
|
|
190565
190669
|
setState((s) => {
|
|
190566
190670
|
if (!s.missingSecrets || s.currentSecretIndex === void 0) return s;
|
|
190567
190671
|
const currentSecret2 = s.missingSecrets[s.currentSecretIndex];
|
|
190672
|
+
if (!currentSecret2) return s;
|
|
190568
190673
|
const newSecretValues = { ...s.secretValues, [currentSecret2]: value };
|
|
190569
190674
|
const nextIndex = s.currentSecretIndex + 1;
|
|
190570
190675
|
if (nextIndex < s.missingSecrets.length) {
|
|
@@ -190594,6 +190699,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
|
|
|
190594
190699
|
setState((s) => {
|
|
190595
190700
|
if (!s.missingConfigs || s.currentConfigIndex === void 0) return s;
|
|
190596
190701
|
const currentConfig2 = s.missingConfigs[s.currentConfigIndex];
|
|
190702
|
+
if (!currentConfig2) return s;
|
|
190597
190703
|
const newConfigValues = { ...s.configValues, [currentConfig2]: value };
|
|
190598
190704
|
const nextIndex = s.currentConfigIndex + 1;
|
|
190599
190705
|
if (nextIndex < s.missingConfigs.length) {
|
|
@@ -190646,7 +190752,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
|
|
|
190646
190752
|
writeLog("deploy", "Secrets submitted successfully");
|
|
190647
190753
|
setState((s) => ({
|
|
190648
190754
|
...s,
|
|
190649
|
-
phase: "
|
|
190755
|
+
phase: "pending",
|
|
190650
190756
|
missingSecrets: void 0,
|
|
190651
190757
|
secretValues: void 0
|
|
190652
190758
|
}));
|
|
@@ -190688,7 +190794,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
|
|
|
190688
190794
|
writeLog("deploy", "Configs submitted successfully");
|
|
190689
190795
|
setState((s) => ({
|
|
190690
190796
|
...s,
|
|
190691
|
-
phase: "
|
|
190797
|
+
phase: "pending",
|
|
190692
190798
|
missingConfigs: void 0,
|
|
190693
190799
|
configValues: void 0
|
|
190694
190800
|
}));
|
|
@@ -190706,7 +190812,6 @@ function DeployUI({ environment, config, skipBuildTest }) {
|
|
|
190706
190812
|
useEffect4(() => {
|
|
190707
190813
|
if (state.phase !== "testing-builds" || !state.projectId) return;
|
|
190708
190814
|
let cancelled = false;
|
|
190709
|
-
let pollInterval;
|
|
190710
190815
|
async function runBuildTestsAndDeploy() {
|
|
190711
190816
|
const projectDir = process.cwd();
|
|
190712
190817
|
const builds = config.builds || [];
|
|
@@ -190805,148 +190910,205 @@ ${errorMsg}`
|
|
|
190805
190910
|
return;
|
|
190806
190911
|
}
|
|
190807
190912
|
if (cancelled) return;
|
|
190808
|
-
writeLog("deploy", "
|
|
190809
|
-
setState((s) => ({ ...s, phase: "
|
|
190810
|
-
let lastState;
|
|
190811
|
-
const pollForCompletion = async () => {
|
|
190812
|
-
try {
|
|
190813
|
-
const status = await client2.getDeployment(deployment2.id);
|
|
190814
|
-
if (cancelled) return;
|
|
190815
|
-
if (status.state !== lastState) {
|
|
190816
|
-
writeLog(
|
|
190817
|
-
"deploy",
|
|
190818
|
-
`Deployment state: ${status.state}${status.stateMessage ? ` - ${status.stateMessage}` : ""}`
|
|
190819
|
-
);
|
|
190820
|
-
lastState = status.state;
|
|
190821
|
-
}
|
|
190822
|
-
if (status.state === "failed") {
|
|
190823
|
-
writeLog(
|
|
190824
|
-
"deploy:error",
|
|
190825
|
-
`Deployment failed: ${status.stateMessage || "Unknown error"}`
|
|
190826
|
-
);
|
|
190827
|
-
setState((s) => ({
|
|
190828
|
-
...s,
|
|
190829
|
-
phase: "error",
|
|
190830
|
-
deployment: status,
|
|
190831
|
-
error: status.stateMessage || "Deployment failed"
|
|
190832
|
-
}));
|
|
190833
|
-
if (pollInterval) clearInterval(pollInterval);
|
|
190834
|
-
return;
|
|
190835
|
-
}
|
|
190836
|
-
if (status.state === "active") {
|
|
190837
|
-
writeLog("deploy", "Deployment successful");
|
|
190838
|
-
if (status.publicUrls) {
|
|
190839
|
-
for (const [name, url] of Object.entries(status.publicUrls)) {
|
|
190840
|
-
writeLog("deploy", `Public URL: ${name} -> ${url}`);
|
|
190841
|
-
}
|
|
190842
|
-
}
|
|
190843
|
-
setState((s) => ({ ...s, phase: "success", deployment: status }));
|
|
190844
|
-
if (pollInterval) clearInterval(pollInterval);
|
|
190845
|
-
return;
|
|
190846
|
-
}
|
|
190847
|
-
if (status.state === "awaiting_secrets") {
|
|
190848
|
-
if (pollInterval) clearInterval(pollInterval);
|
|
190849
|
-
pollInterval = void 0;
|
|
190850
|
-
const missingSecrets2 = status.missingSecrets || [];
|
|
190851
|
-
writeLog("deploy", `Awaiting secrets: ${missingSecrets2.join(", ")}`);
|
|
190852
|
-
setState((s) => ({
|
|
190853
|
-
...s,
|
|
190854
|
-
phase: "awaiting-secrets",
|
|
190855
|
-
deployment: status,
|
|
190856
|
-
missingSecrets: missingSecrets2,
|
|
190857
|
-
secretValues: {},
|
|
190858
|
-
currentSecretIndex: 0,
|
|
190859
|
-
currentSecretInput: ""
|
|
190860
|
-
}));
|
|
190861
|
-
return;
|
|
190862
|
-
}
|
|
190863
|
-
if (status.state === "awaiting_configs") {
|
|
190864
|
-
if (pollInterval) clearInterval(pollInterval);
|
|
190865
|
-
pollInterval = void 0;
|
|
190866
|
-
const missingConfigs2 = status.missingConfigs || [];
|
|
190867
|
-
writeLog("deploy", `Awaiting configs: ${missingConfigs2.join(", ")}`);
|
|
190868
|
-
setState((s) => ({
|
|
190869
|
-
...s,
|
|
190870
|
-
phase: "awaiting-configs",
|
|
190871
|
-
deployment: status,
|
|
190872
|
-
missingConfigs: missingConfigs2,
|
|
190873
|
-
configValues: {},
|
|
190874
|
-
currentConfigIndex: 0,
|
|
190875
|
-
currentConfigInput: ""
|
|
190876
|
-
}));
|
|
190877
|
-
return;
|
|
190878
|
-
}
|
|
190879
|
-
if (status.state === "deploying") {
|
|
190880
|
-
setState((s) => ({ ...s, phase: "deploying", deployment: status }));
|
|
190881
|
-
} else if (status.state === "building") {
|
|
190882
|
-
setState((s) => ({ ...s, phase: "building", deployment: status }));
|
|
190883
|
-
}
|
|
190884
|
-
} catch {
|
|
190885
|
-
}
|
|
190886
|
-
};
|
|
190887
|
-
pollInterval = setInterval(pollForCompletion, 2e3);
|
|
190888
|
-
pollForCompletion();
|
|
190913
|
+
writeLog("deploy", "Deployment in pending state, waiting for builds to complete");
|
|
190914
|
+
setState((s) => ({ ...s, phase: "pending", deployment: deployment2 }));
|
|
190889
190915
|
}
|
|
190890
190916
|
runBuildTestsAndDeploy();
|
|
190891
190917
|
return () => {
|
|
190892
190918
|
cancelled = true;
|
|
190893
|
-
if (pollInterval) clearInterval(pollInterval);
|
|
190894
190919
|
};
|
|
190895
190920
|
}, [state.projectId, environment, config.builds, skipBuildTest]);
|
|
190896
190921
|
useEffect4(() => {
|
|
190922
|
+
if (state.phase !== "pending" || !state.deployment) return;
|
|
190897
190923
|
let pollInterval;
|
|
190898
|
-
|
|
190899
|
-
|
|
190900
|
-
|
|
190901
|
-
|
|
190902
|
-
|
|
190903
|
-
|
|
190904
|
-
|
|
190905
|
-
|
|
190906
|
-
|
|
190907
|
-
|
|
190908
|
-
|
|
190909
|
-
|
|
190910
|
-
|
|
190911
|
-
|
|
190912
|
-
|
|
190913
|
-
|
|
190914
|
-
|
|
190915
|
-
|
|
190916
|
-
|
|
190917
|
-
|
|
190918
|
-
}
|
|
190919
|
-
|
|
190920
|
-
|
|
190921
|
-
|
|
190922
|
-
|
|
190923
|
-
|
|
190924
|
-
|
|
190925
|
-
|
|
190926
|
-
|
|
190927
|
-
|
|
190928
|
-
|
|
190929
|
-
|
|
190930
|
-
|
|
190931
|
-
|
|
190932
|
-
|
|
190933
|
-
|
|
190934
|
-
|
|
190935
|
-
if (
|
|
190936
|
-
|
|
190937
|
-
|
|
190938
|
-
|
|
190924
|
+
let cancelled = false;
|
|
190925
|
+
const client2 = clientRef.current;
|
|
190926
|
+
if (!client2) return;
|
|
190927
|
+
const pollForPendingActions = async () => {
|
|
190928
|
+
if (cancelled) return;
|
|
190929
|
+
try {
|
|
190930
|
+
const status = await client2.getDeployment(state.deployment.id);
|
|
190931
|
+
if (cancelled) return;
|
|
190932
|
+
writeLog(
|
|
190933
|
+
"deploy",
|
|
190934
|
+
`Deployment state: ${status.state}, pending actions: ${JSON.stringify(status.pendingActions || [])}`
|
|
190935
|
+
);
|
|
190936
|
+
if (status.state === "failed") {
|
|
190937
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
190938
|
+
writeLog("deploy:error", `Deployment failed: ${status.stateMessage || "Unknown error"}`);
|
|
190939
|
+
setState((s) => ({
|
|
190940
|
+
...s,
|
|
190941
|
+
phase: "error",
|
|
190942
|
+
deployment: status,
|
|
190943
|
+
error: status.stateMessage || "Deployment failed"
|
|
190944
|
+
}));
|
|
190945
|
+
return;
|
|
190946
|
+
}
|
|
190947
|
+
const failedBuild = getFailedBuild(status.pendingActions);
|
|
190948
|
+
if (failedBuild && failedBuild.type === "build_failed") {
|
|
190949
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
190950
|
+
writeLog("deploy:error", `Build failed: ${failedBuild.error}`);
|
|
190951
|
+
setState((s) => ({
|
|
190952
|
+
...s,
|
|
190953
|
+
phase: "error",
|
|
190954
|
+
deployment: status,
|
|
190955
|
+
error: `Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`
|
|
190956
|
+
}));
|
|
190957
|
+
return;
|
|
190958
|
+
}
|
|
190959
|
+
const missingSecrets2 = getMissingSecrets(status.pendingActions);
|
|
190960
|
+
if (missingSecrets2.length > 0) {
|
|
190961
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
190962
|
+
pollInterval = void 0;
|
|
190963
|
+
writeLog("deploy", `Awaiting secrets: ${missingSecrets2.join(", ")}`);
|
|
190964
|
+
setState((s) => ({
|
|
190965
|
+
...s,
|
|
190966
|
+
phase: "awaiting-secrets",
|
|
190967
|
+
deployment: status,
|
|
190968
|
+
pendingActions: status.pendingActions,
|
|
190969
|
+
missingSecrets: missingSecrets2,
|
|
190970
|
+
secretValues: {},
|
|
190971
|
+
currentSecretIndex: 0,
|
|
190972
|
+
currentSecretInput: ""
|
|
190973
|
+
}));
|
|
190974
|
+
return;
|
|
190975
|
+
}
|
|
190976
|
+
const missingConfigs2 = getMissingConfigs(status.pendingActions);
|
|
190977
|
+
if (missingConfigs2.length > 0) {
|
|
190978
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
190979
|
+
pollInterval = void 0;
|
|
190980
|
+
writeLog("deploy", `Awaiting configs: ${missingConfigs2.join(", ")}`);
|
|
190981
|
+
setState((s) => ({
|
|
190982
|
+
...s,
|
|
190983
|
+
phase: "awaiting-configs",
|
|
190984
|
+
deployment: status,
|
|
190985
|
+
pendingActions: status.pendingActions,
|
|
190986
|
+
missingConfigs: missingConfigs2,
|
|
190987
|
+
configValues: {},
|
|
190988
|
+
currentConfigIndex: 0,
|
|
190989
|
+
currentConfigInput: ""
|
|
190990
|
+
}));
|
|
190991
|
+
return;
|
|
190992
|
+
}
|
|
190993
|
+
if (hasBuildsInProgress(status.pendingActions)) {
|
|
190994
|
+
setState((s) => ({
|
|
190995
|
+
...s,
|
|
190996
|
+
deployment: status,
|
|
190997
|
+
pendingActions: status.pendingActions
|
|
190998
|
+
}));
|
|
190999
|
+
return;
|
|
191000
|
+
}
|
|
191001
|
+
if (!status.pendingActions || status.pendingActions.length === 0) {
|
|
191002
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
191003
|
+
pollInterval = void 0;
|
|
191004
|
+
writeLog("deploy", "All pending actions resolved, starting deployment");
|
|
191005
|
+
setState((s) => ({
|
|
191006
|
+
...s,
|
|
191007
|
+
phase: "starting",
|
|
191008
|
+
deployment: status,
|
|
191009
|
+
pendingActions: []
|
|
191010
|
+
}));
|
|
191011
|
+
return;
|
|
191012
|
+
}
|
|
191013
|
+
setState((s) => ({
|
|
191014
|
+
...s,
|
|
191015
|
+
deployment: status,
|
|
191016
|
+
pendingActions: status.pendingActions
|
|
191017
|
+
}));
|
|
191018
|
+
} catch (err) {
|
|
191019
|
+
writeLog("deploy", `Poll error: ${err instanceof Error ? err.message : String(err)}`);
|
|
191020
|
+
}
|
|
191021
|
+
};
|
|
191022
|
+
pollInterval = setInterval(pollForPendingActions, 2e3);
|
|
191023
|
+
pollForPendingActions();
|
|
191024
|
+
return () => {
|
|
191025
|
+
cancelled = true;
|
|
191026
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
191027
|
+
};
|
|
191028
|
+
}, [state.phase, state.deployment?.id]);
|
|
191029
|
+
useEffect4(() => {
|
|
191030
|
+
if (state.phase !== "starting" || !state.deployment) return;
|
|
191031
|
+
let cancelled = false;
|
|
191032
|
+
const client2 = clientRef.current;
|
|
191033
|
+
if (!client2) return;
|
|
191034
|
+
(async () => {
|
|
191035
|
+
try {
|
|
191036
|
+
writeLog("deploy", "Calling /start endpoint");
|
|
191037
|
+
await client2.startDeployment(state.deployment.id);
|
|
191038
|
+
writeLog("deploy", "Deployment started successfully");
|
|
191039
|
+
if (cancelled) return;
|
|
191040
|
+
setState((s) => ({
|
|
191041
|
+
...s,
|
|
191042
|
+
phase: "queued"
|
|
191043
|
+
}));
|
|
191044
|
+
} catch (err) {
|
|
191045
|
+
if (cancelled) return;
|
|
191046
|
+
const errorMsg = `Failed to start deployment: ${err instanceof Error ? err.message : String(err)}`;
|
|
191047
|
+
writeLog("deploy:error", errorMsg);
|
|
191048
|
+
setState((s) => ({
|
|
191049
|
+
...s,
|
|
191050
|
+
phase: "error",
|
|
191051
|
+
error: errorMsg
|
|
191052
|
+
}));
|
|
191053
|
+
}
|
|
191054
|
+
})();
|
|
191055
|
+
return () => {
|
|
191056
|
+
cancelled = true;
|
|
191057
|
+
};
|
|
191058
|
+
}, [state.phase, state.deployment?.id]);
|
|
191059
|
+
useEffect4(() => {
|
|
191060
|
+
if (state.phase !== "queued" && state.phase !== "deploying" || !state.deployment) return;
|
|
191061
|
+
let pollInterval;
|
|
191062
|
+
let cancelled = false;
|
|
191063
|
+
const client2 = clientRef.current;
|
|
191064
|
+
if (!client2) return;
|
|
191065
|
+
const pollForCompletion = async () => {
|
|
191066
|
+
if (cancelled) return;
|
|
191067
|
+
try {
|
|
191068
|
+
const status = await client2.getDeployment(state.deployment.id);
|
|
191069
|
+
if (cancelled) return;
|
|
191070
|
+
writeLog(
|
|
191071
|
+
"deploy",
|
|
191072
|
+
`Deployment state: ${status.state}${status.stateMessage ? ` - ${status.stateMessage}` : ""}`
|
|
191073
|
+
);
|
|
191074
|
+
if (status.state === "failed") {
|
|
191075
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
191076
|
+
writeLog("deploy:error", `Deployment failed: ${status.stateMessage || "Unknown error"}`);
|
|
191077
|
+
setState((s) => ({
|
|
191078
|
+
...s,
|
|
191079
|
+
phase: "error",
|
|
191080
|
+
deployment: status,
|
|
191081
|
+
error: status.stateMessage || "Deployment failed"
|
|
191082
|
+
}));
|
|
191083
|
+
return;
|
|
191084
|
+
}
|
|
191085
|
+
if (status.state === "active") {
|
|
191086
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
191087
|
+
writeLog("deploy", "Deployment successful");
|
|
191088
|
+
if (status.publicUrls) {
|
|
191089
|
+
for (const [name, url] of Object.entries(status.publicUrls)) {
|
|
191090
|
+
writeLog("deploy", `Public URL: ${name} -> ${url}`);
|
|
191091
|
+
}
|
|
190939
191092
|
}
|
|
190940
|
-
|
|
191093
|
+
setState((s) => ({ ...s, phase: "success", deployment: status }));
|
|
191094
|
+
return;
|
|
190941
191095
|
}
|
|
190942
|
-
|
|
190943
|
-
|
|
190944
|
-
|
|
190945
|
-
|
|
190946
|
-
|
|
190947
|
-
}
|
|
190948
|
-
|
|
190949
|
-
|
|
191096
|
+
if (status.state === "deploying") {
|
|
191097
|
+
setState((s) => ({ ...s, phase: "deploying", deployment: status }));
|
|
191098
|
+
} else if (status.state === "queued") {
|
|
191099
|
+
setState((s) => ({ ...s, phase: "queued", deployment: status }));
|
|
191100
|
+
}
|
|
191101
|
+
} catch (err) {
|
|
191102
|
+
writeLog("deploy", `Poll error: ${err instanceof Error ? err.message : String(err)}`);
|
|
191103
|
+
}
|
|
191104
|
+
};
|
|
191105
|
+
pollInterval = setInterval(pollForCompletion, 2e3);
|
|
191106
|
+
pollForCompletion();
|
|
191107
|
+
return () => {
|
|
191108
|
+
cancelled = true;
|
|
191109
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
191110
|
+
};
|
|
191111
|
+
}, [state.phase, state.deployment?.id]);
|
|
190950
191112
|
useEffect4(() => {
|
|
190951
191113
|
if (state.phase === "testing-builds") {
|
|
190952
191114
|
trackEvent("deploy_started", { environment });
|
|
@@ -190971,6 +191133,7 @@ ${errorMsg}`
|
|
|
190971
191133
|
deployment,
|
|
190972
191134
|
error,
|
|
190973
191135
|
tarballSize,
|
|
191136
|
+
pendingActions,
|
|
190974
191137
|
missingSecrets,
|
|
190975
191138
|
currentSecretIndex,
|
|
190976
191139
|
missingConfigs,
|
|
@@ -191018,7 +191181,28 @@ ${errorMsg}`
|
|
|
191018
191181
|
}
|
|
191019
191182
|
const currentSecret = missingSecrets && currentSecretIndex !== void 0 ? missingSecrets[currentSecretIndex] : void 0;
|
|
191020
191183
|
const currentConfig = missingConfigs && currentConfigIndex !== void 0 ? missingConfigs[currentConfigIndex] : void 0;
|
|
191021
|
-
const
|
|
191184
|
+
const getDisplayPhase = () => {
|
|
191185
|
+
if (phase === "awaiting-secrets" || phase === "awaiting-configs" || phase === "starting") {
|
|
191186
|
+
return "pending";
|
|
191187
|
+
}
|
|
191188
|
+
if (phase === "queued") {
|
|
191189
|
+
return "deploying";
|
|
191190
|
+
}
|
|
191191
|
+
return phase;
|
|
191192
|
+
};
|
|
191193
|
+
const displayPhase = getDisplayPhase();
|
|
191194
|
+
const getPendingLabel = () => {
|
|
191195
|
+
if (hasBuildsInProgress(pendingActions)) {
|
|
191196
|
+
return "Building images";
|
|
191197
|
+
}
|
|
191198
|
+
if (phase === "awaiting-secrets" || phase === "awaiting-configs") {
|
|
191199
|
+
return "Waiting for input";
|
|
191200
|
+
}
|
|
191201
|
+
if (phase === "starting") {
|
|
191202
|
+
return "Starting deployment";
|
|
191203
|
+
}
|
|
191204
|
+
return "Preparing";
|
|
191205
|
+
};
|
|
191022
191206
|
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "Deploying to ", environment), deployment && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " (", deployment.id, ")")), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(
|
|
191023
191207
|
PhaseIndicator,
|
|
191024
191208
|
{
|
|
@@ -191050,16 +191234,16 @@ ${errorMsg}`
|
|
|
191050
191234
|
), /* @__PURE__ */ React7.createElement(
|
|
191051
191235
|
PhaseIndicator,
|
|
191052
191236
|
{
|
|
191053
|
-
phase: "
|
|
191237
|
+
phase: "pending",
|
|
191054
191238
|
currentPhase: displayPhase,
|
|
191055
|
-
label:
|
|
191239
|
+
label: getPendingLabel()
|
|
191056
191240
|
}
|
|
191057
191241
|
), /* @__PURE__ */ React7.createElement(
|
|
191058
191242
|
PhaseIndicator,
|
|
191059
191243
|
{
|
|
191060
191244
|
phase: "deploying",
|
|
191061
191245
|
currentPhase: displayPhase,
|
|
191062
|
-
label: "Deploying"
|
|
191246
|
+
label: phase === "queued" ? "Waiting in queue" : "Deploying"
|
|
191063
191247
|
}
|
|
191064
191248
|
)), phase === "awaiting-secrets" && currentSecret && /* @__PURE__ */ React7.createElement(
|
|
191065
191249
|
SecretInput,
|
|
@@ -191495,7 +191679,7 @@ function logoutCommand() {
|
|
|
191495
191679
|
var program = new Command();
|
|
191496
191680
|
var env = "production";
|
|
191497
191681
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
191498
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
191682
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.47").enablePositionalOptions();
|
|
191499
191683
|
program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
|
|
191500
191684
|
program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
|
|
191501
191685
|
program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
|