@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.
Files changed (39) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.__PAGE__.txt +2 -2
  4. package/dist/admin/__next._full.txt +9 -9
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +8 -8
  7. package/dist/admin/__next._tree.txt +2 -2
  8. package/dist/admin/_next/static/chunks/64efcb432fae8c98.js +1 -0
  9. package/dist/admin/_next/static/chunks/{b4fa9a08d6dc37c1.js → 75cb455f07e7651a.js} +1 -1
  10. package/dist/admin/_next/static/chunks/951210b423dc9315.css +4 -0
  11. package/dist/admin/_next/static/chunks/ce9a5f692b87aaa9.js +5 -0
  12. package/dist/admin/_next/static/chunks/d2b1f8ba26497c0b.js +1 -0
  13. package/dist/admin/_not-found/__next._full.txt +8 -8
  14. package/dist/admin/_not-found/__next._head.txt +1 -1
  15. package/dist/admin/_not-found/__next._index.txt +8 -8
  16. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  17. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  18. package/dist/admin/_not-found/__next._tree.txt +2 -2
  19. package/dist/admin/_not-found/index.html +1 -1
  20. package/dist/admin/_not-found/index.txt +8 -8
  21. package/dist/admin/databases/__next._full.txt +9 -9
  22. package/dist/admin/databases/__next._head.txt +1 -1
  23. package/dist/admin/databases/__next._index.txt +8 -8
  24. package/dist/admin/databases/__next._tree.txt +2 -2
  25. package/dist/admin/databases/__next.databases.__PAGE__.txt +2 -2
  26. package/dist/admin/databases/__next.databases.txt +1 -1
  27. package/dist/admin/databases/index.html +1 -1
  28. package/dist/admin/databases/index.txt +9 -9
  29. package/dist/admin/index.html +1 -1
  30. package/dist/admin/index.txt +9 -9
  31. package/dist/cli.js +346 -162
  32. package/package.json +5 -3
  33. package/dist/admin/_next/static/chunks/0cb2c4291ba35581.css +0 -4
  34. package/dist/admin/_next/static/chunks/721087f8fbaa34b3.js +0 -1
  35. package/dist/admin/_next/static/chunks/db72df2e613a023e.js +0 -5
  36. package/dist/admin/_next/static/chunks/e3f73cf77dd1ede1.js +0 -1
  37. /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → pcYHo7d7--ealoH3_ELSO}/_buildManifest.js +0 -0
  38. /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → pcYHo7d7--ealoH3_ELSO}/_clientMiddlewareManifest.json +0 -0
  39. /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.45",
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
- if (key in this.savedPorts) {
186550
- return this.savedPorts[key];
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 (arch3 === "x64" || arch3 === "x86_64") {
186650
+ if (archStr === "x64" || archStr === "x86_64") {
186619
186651
  mappedArch = "x64";
186620
- } else if (arch3 === "arm64" || arch3 === "aarch64") {
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.isProcessRunning(ownerFile2.owner.pid)) {
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.isProcessRunning(ownerFile.owner.pid)) {
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.isProcessRunning(ownerFile2.owner.pid)) {
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
- if (validationErrors.length > 0) {
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: validationErrors[0].message
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
- if (segments.every((s) => s[i] === segments[0][i])) {
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 = segments[0].slice(0, commonLength);
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
- "building",
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
- onSelect(projects[selectedIndex - 1]);
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: "building",
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: "building",
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", "Waiting for build to complete");
190809
- setState((s) => ({ ...s, phase: "building", deployment: deployment2 }));
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
- if ((state.phase === "building" || state.phase === "deploying") && state.deployment && state.missingSecrets === void 0 && state.secretValues === void 0 && state.missingConfigs === void 0 && state.configValues === void 0) {
190899
- const client2 = clientRef.current;
190900
- if (!client2) return;
190901
- const pollForCompletion = async () => {
190902
- try {
190903
- const status = await client2.getDeployment(state.deployment.id);
190904
- if (status.state === "failed") {
190905
- setState((s) => ({
190906
- ...s,
190907
- phase: "error",
190908
- deployment: status,
190909
- error: status.stateMessage || "Deployment failed"
190910
- }));
190911
- if (pollInterval) clearInterval(pollInterval);
190912
- return;
190913
- }
190914
- if (status.state === "active") {
190915
- setState((s) => ({ ...s, phase: "success", deployment: status }));
190916
- if (pollInterval) clearInterval(pollInterval);
190917
- return;
190918
- }
190919
- if (status.state === "awaiting_configs") {
190920
- if (pollInterval) clearInterval(pollInterval);
190921
- pollInterval = void 0;
190922
- const missingConfigs2 = status.missingConfigs || [];
190923
- writeLog("deploy", `Awaiting configs: ${missingConfigs2.join(", ")}`);
190924
- setState((s) => ({
190925
- ...s,
190926
- phase: "awaiting-configs",
190927
- deployment: status,
190928
- missingConfigs: missingConfigs2,
190929
- configValues: {},
190930
- currentConfigIndex: 0,
190931
- currentConfigInput: ""
190932
- }));
190933
- return;
190934
- }
190935
- if (status.state === "deploying") {
190936
- setState((s) => ({ ...s, phase: "deploying", deployment: status }));
190937
- } else if (status.state === "building") {
190938
- setState((s) => ({ ...s, phase: "building", deployment: status }));
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
- } catch {
191093
+ setState((s) => ({ ...s, phase: "success", deployment: status }));
191094
+ return;
190941
191095
  }
190942
- };
190943
- pollInterval = setInterval(pollForCompletion, 2e3);
190944
- pollForCompletion();
190945
- return () => {
190946
- if (pollInterval) clearInterval(pollInterval);
190947
- };
190948
- }
190949
- }, [state.phase, state.missingSecrets, state.secretValues, state.missingConfigs, state.configValues]);
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 displayPhase = phase === "awaiting-secrets" || phase === "awaiting-configs" ? "building" : phase;
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: "building",
191237
+ phase: "pending",
191054
191238
  currentPhase: displayPhase,
191055
- label: "Building"
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.45").enablePositionalOptions();
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);