@specific.dev/cli 0.1.45 → 0.1.46

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/951210b423dc9315.css +4 -0
  10. package/dist/admin/_next/static/chunks/ce9a5f692b87aaa9.js +5 -0
  11. package/dist/admin/_next/static/chunks/d2b1f8ba26497c0b.js +1 -0
  12. package/dist/admin/_next/static/chunks/{b4fa9a08d6dc37c1.js → e13659c7ad8234ce.js} +1 -1
  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 +302 -159
  32. package/package.json +1 -1
  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 → uXSe8Dmoqn0jmhvY6Iln0}/_buildManifest.js +0 -0
  38. /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → uXSe8Dmoqn0jmhvY6Iln0}/_clientMiddlewareManifest.json +0 -0
  39. /package/dist/admin/_next/static/{WUdRH_Qksuvhq_ji1IkHb → uXSe8Dmoqn0jmhvY6Iln0}/_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.46",
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;
@@ -189502,14 +189534,15 @@ Add them to the config block in specific.local`);
189502
189534
  }
189503
189535
  }
189504
189536
  const validationErrors = validateEndpointReferences(config2);
189505
- if (validationErrors.length > 0) {
189537
+ const firstError = validationErrors[0];
189538
+ if (firstError) {
189506
189539
  for (const error of validationErrors) {
189507
189540
  writeLog("system:error", error.message);
189508
189541
  }
189509
189542
  setState((s) => ({
189510
189543
  ...s,
189511
189544
  status: "error",
189512
- error: validationErrors[0].message
189545
+ error: firstError.message
189513
189546
  }));
189514
189547
  return;
189515
189548
  }
@@ -190285,16 +190318,19 @@ function findWidestContext(projectDir, contexts) {
190285
190318
  if (contexts.length === 0) return ".";
190286
190319
  const absolute = contexts.map((c) => path18.resolve(projectDir, c));
190287
190320
  const segments = absolute.map((p) => p.split(path18.sep).filter(Boolean));
190321
+ const firstSegments = segments[0];
190322
+ if (!firstSegments) return ".";
190288
190323
  const minLen = Math.min(...segments.map((s) => s.length));
190289
190324
  let commonLength = 0;
190290
190325
  for (let i = 0; i < minLen; i++) {
190291
- if (segments.every((s) => s[i] === segments[0][i])) {
190326
+ const firstSeg = firstSegments[i];
190327
+ if (firstSeg !== void 0 && segments.every((s) => s[i] === firstSeg)) {
190292
190328
  commonLength = i + 1;
190293
190329
  } else {
190294
190330
  break;
190295
190331
  }
190296
190332
  }
190297
- const ancestorSegments = segments[0].slice(0, commonLength);
190333
+ const ancestorSegments = firstSegments.slice(0, commonLength);
190298
190334
  const ancestor = path18.sep + ancestorSegments.join(path18.sep);
190299
190335
  return path18.relative(projectDir, ancestor) || ".";
190300
190336
  }
@@ -190328,7 +190364,7 @@ function PhaseIndicator({
190328
190364
  "creating-tarball",
190329
190365
  "creating-deployment",
190330
190366
  "uploading",
190331
- "building",
190367
+ "pending",
190332
190368
  "deploying",
190333
190369
  "success"
190334
190370
  ];
@@ -190366,7 +190402,10 @@ function ProjectSelector({
190366
190402
  if (selectedIndex === 0) {
190367
190403
  onSelect("new");
190368
190404
  } else {
190369
- onSelect(projects[selectedIndex - 1]);
190405
+ const project = projects[selectedIndex - 1];
190406
+ if (project) {
190407
+ onSelect(project);
190408
+ }
190370
190409
  }
190371
190410
  } else if (key.upArrow) {
190372
190411
  onUp();
@@ -190397,6 +190436,30 @@ function NameInput({ onSubmit, onCancel }) {
190397
190436
  });
190398
190437
  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
190438
  }
190439
+ function getMissingSecrets(pendingActions) {
190440
+ if (!pendingActions) return [];
190441
+ const secretAction = pendingActions.find((a) => a.type === "missing_secrets");
190442
+ if (secretAction && secretAction.type === "missing_secrets") {
190443
+ return secretAction.secrets;
190444
+ }
190445
+ return [];
190446
+ }
190447
+ function getMissingConfigs(pendingActions) {
190448
+ if (!pendingActions) return [];
190449
+ const configAction = pendingActions.find((a) => a.type === "missing_configs");
190450
+ if (configAction && configAction.type === "missing_configs") {
190451
+ return configAction.configs;
190452
+ }
190453
+ return [];
190454
+ }
190455
+ function hasBuildsInProgress(pendingActions) {
190456
+ if (!pendingActions) return false;
190457
+ return pendingActions.some((a) => a.type === "build_in_progress");
190458
+ }
190459
+ function getFailedBuild(pendingActions) {
190460
+ if (!pendingActions) return void 0;
190461
+ return pendingActions.find((a) => a.type === "build_failed");
190462
+ }
190400
190463
  function DeployUI({ environment, config, skipBuildTest }) {
190401
190464
  const { exit } = useApp3();
190402
190465
  const [state, setState] = useState6({ phase: "checking-auth" });
@@ -190565,6 +190628,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
190565
190628
  setState((s) => {
190566
190629
  if (!s.missingSecrets || s.currentSecretIndex === void 0) return s;
190567
190630
  const currentSecret2 = s.missingSecrets[s.currentSecretIndex];
190631
+ if (!currentSecret2) return s;
190568
190632
  const newSecretValues = { ...s.secretValues, [currentSecret2]: value };
190569
190633
  const nextIndex = s.currentSecretIndex + 1;
190570
190634
  if (nextIndex < s.missingSecrets.length) {
@@ -190594,6 +190658,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
190594
190658
  setState((s) => {
190595
190659
  if (!s.missingConfigs || s.currentConfigIndex === void 0) return s;
190596
190660
  const currentConfig2 = s.missingConfigs[s.currentConfigIndex];
190661
+ if (!currentConfig2) return s;
190597
190662
  const newConfigValues = { ...s.configValues, [currentConfig2]: value };
190598
190663
  const nextIndex = s.currentConfigIndex + 1;
190599
190664
  if (nextIndex < s.missingConfigs.length) {
@@ -190646,7 +190711,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
190646
190711
  writeLog("deploy", "Secrets submitted successfully");
190647
190712
  setState((s) => ({
190648
190713
  ...s,
190649
- phase: "building",
190714
+ phase: "pending",
190650
190715
  missingSecrets: void 0,
190651
190716
  secretValues: void 0
190652
190717
  }));
@@ -190688,7 +190753,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
190688
190753
  writeLog("deploy", "Configs submitted successfully");
190689
190754
  setState((s) => ({
190690
190755
  ...s,
190691
- phase: "building",
190756
+ phase: "pending",
190692
190757
  missingConfigs: void 0,
190693
190758
  configValues: void 0
190694
190759
  }));
@@ -190706,7 +190771,6 @@ function DeployUI({ environment, config, skipBuildTest }) {
190706
190771
  useEffect4(() => {
190707
190772
  if (state.phase !== "testing-builds" || !state.projectId) return;
190708
190773
  let cancelled = false;
190709
- let pollInterval;
190710
190774
  async function runBuildTestsAndDeploy() {
190711
190775
  const projectDir = process.cwd();
190712
190776
  const builds = config.builds || [];
@@ -190805,148 +190869,205 @@ ${errorMsg}`
190805
190869
  return;
190806
190870
  }
190807
190871
  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();
190872
+ writeLog("deploy", "Deployment in pending state, waiting for builds to complete");
190873
+ setState((s) => ({ ...s, phase: "pending", deployment: deployment2 }));
190889
190874
  }
190890
190875
  runBuildTestsAndDeploy();
190891
190876
  return () => {
190892
190877
  cancelled = true;
190893
- if (pollInterval) clearInterval(pollInterval);
190894
190878
  };
190895
190879
  }, [state.projectId, environment, config.builds, skipBuildTest]);
190896
190880
  useEffect4(() => {
190881
+ if (state.phase !== "pending" || !state.deployment) return;
190897
190882
  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 }));
190883
+ let cancelled = false;
190884
+ const client2 = clientRef.current;
190885
+ if (!client2) return;
190886
+ const pollForPendingActions = async () => {
190887
+ if (cancelled) return;
190888
+ try {
190889
+ const status = await client2.getDeployment(state.deployment.id);
190890
+ if (cancelled) return;
190891
+ writeLog(
190892
+ "deploy",
190893
+ `Deployment state: ${status.state}, pending actions: ${JSON.stringify(status.pendingActions || [])}`
190894
+ );
190895
+ if (status.state === "failed") {
190896
+ if (pollInterval) clearInterval(pollInterval);
190897
+ writeLog("deploy:error", `Deployment failed: ${status.stateMessage || "Unknown error"}`);
190898
+ setState((s) => ({
190899
+ ...s,
190900
+ phase: "error",
190901
+ deployment: status,
190902
+ error: status.stateMessage || "Deployment failed"
190903
+ }));
190904
+ return;
190905
+ }
190906
+ const failedBuild = getFailedBuild(status.pendingActions);
190907
+ if (failedBuild && failedBuild.type === "build_failed") {
190908
+ if (pollInterval) clearInterval(pollInterval);
190909
+ writeLog("deploy:error", `Build failed: ${failedBuild.error}`);
190910
+ setState((s) => ({
190911
+ ...s,
190912
+ phase: "error",
190913
+ deployment: status,
190914
+ error: `Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`
190915
+ }));
190916
+ return;
190917
+ }
190918
+ const missingSecrets2 = getMissingSecrets(status.pendingActions);
190919
+ if (missingSecrets2.length > 0) {
190920
+ if (pollInterval) clearInterval(pollInterval);
190921
+ pollInterval = void 0;
190922
+ writeLog("deploy", `Awaiting secrets: ${missingSecrets2.join(", ")}`);
190923
+ setState((s) => ({
190924
+ ...s,
190925
+ phase: "awaiting-secrets",
190926
+ deployment: status,
190927
+ pendingActions: status.pendingActions,
190928
+ missingSecrets: missingSecrets2,
190929
+ secretValues: {},
190930
+ currentSecretIndex: 0,
190931
+ currentSecretInput: ""
190932
+ }));
190933
+ return;
190934
+ }
190935
+ const missingConfigs2 = getMissingConfigs(status.pendingActions);
190936
+ if (missingConfigs2.length > 0) {
190937
+ if (pollInterval) clearInterval(pollInterval);
190938
+ pollInterval = void 0;
190939
+ writeLog("deploy", `Awaiting configs: ${missingConfigs2.join(", ")}`);
190940
+ setState((s) => ({
190941
+ ...s,
190942
+ phase: "awaiting-configs",
190943
+ deployment: status,
190944
+ pendingActions: status.pendingActions,
190945
+ missingConfigs: missingConfigs2,
190946
+ configValues: {},
190947
+ currentConfigIndex: 0,
190948
+ currentConfigInput: ""
190949
+ }));
190950
+ return;
190951
+ }
190952
+ if (hasBuildsInProgress(status.pendingActions)) {
190953
+ setState((s) => ({
190954
+ ...s,
190955
+ deployment: status,
190956
+ pendingActions: status.pendingActions
190957
+ }));
190958
+ return;
190959
+ }
190960
+ if (!status.pendingActions || status.pendingActions.length === 0) {
190961
+ if (pollInterval) clearInterval(pollInterval);
190962
+ pollInterval = void 0;
190963
+ writeLog("deploy", "All pending actions resolved, starting deployment");
190964
+ setState((s) => ({
190965
+ ...s,
190966
+ phase: "starting",
190967
+ deployment: status,
190968
+ pendingActions: []
190969
+ }));
190970
+ return;
190971
+ }
190972
+ setState((s) => ({
190973
+ ...s,
190974
+ deployment: status,
190975
+ pendingActions: status.pendingActions
190976
+ }));
190977
+ } catch (err) {
190978
+ writeLog("deploy", `Poll error: ${err instanceof Error ? err.message : String(err)}`);
190979
+ }
190980
+ };
190981
+ pollInterval = setInterval(pollForPendingActions, 2e3);
190982
+ pollForPendingActions();
190983
+ return () => {
190984
+ cancelled = true;
190985
+ if (pollInterval) clearInterval(pollInterval);
190986
+ };
190987
+ }, [state.phase, state.deployment?.id]);
190988
+ useEffect4(() => {
190989
+ if (state.phase !== "starting" || !state.deployment) return;
190990
+ let cancelled = false;
190991
+ const client2 = clientRef.current;
190992
+ if (!client2) return;
190993
+ (async () => {
190994
+ try {
190995
+ writeLog("deploy", "Calling /start endpoint");
190996
+ await client2.startDeployment(state.deployment.id);
190997
+ writeLog("deploy", "Deployment started successfully");
190998
+ if (cancelled) return;
190999
+ setState((s) => ({
191000
+ ...s,
191001
+ phase: "queued"
191002
+ }));
191003
+ } catch (err) {
191004
+ if (cancelled) return;
191005
+ const errorMsg = `Failed to start deployment: ${err instanceof Error ? err.message : String(err)}`;
191006
+ writeLog("deploy:error", errorMsg);
191007
+ setState((s) => ({
191008
+ ...s,
191009
+ phase: "error",
191010
+ error: errorMsg
191011
+ }));
191012
+ }
191013
+ })();
191014
+ return () => {
191015
+ cancelled = true;
191016
+ };
191017
+ }, [state.phase, state.deployment?.id]);
191018
+ useEffect4(() => {
191019
+ if (state.phase !== "queued" && state.phase !== "deploying" || !state.deployment) return;
191020
+ let pollInterval;
191021
+ let cancelled = false;
191022
+ const client2 = clientRef.current;
191023
+ if (!client2) return;
191024
+ const pollForCompletion = async () => {
191025
+ if (cancelled) return;
191026
+ try {
191027
+ const status = await client2.getDeployment(state.deployment.id);
191028
+ if (cancelled) return;
191029
+ writeLog(
191030
+ "deploy",
191031
+ `Deployment state: ${status.state}${status.stateMessage ? ` - ${status.stateMessage}` : ""}`
191032
+ );
191033
+ if (status.state === "failed") {
191034
+ if (pollInterval) clearInterval(pollInterval);
191035
+ writeLog("deploy:error", `Deployment failed: ${status.stateMessage || "Unknown error"}`);
191036
+ setState((s) => ({
191037
+ ...s,
191038
+ phase: "error",
191039
+ deployment: status,
191040
+ error: status.stateMessage || "Deployment failed"
191041
+ }));
191042
+ return;
191043
+ }
191044
+ if (status.state === "active") {
191045
+ if (pollInterval) clearInterval(pollInterval);
191046
+ writeLog("deploy", "Deployment successful");
191047
+ if (status.publicUrls) {
191048
+ for (const [name, url] of Object.entries(status.publicUrls)) {
191049
+ writeLog("deploy", `Public URL: ${name} -> ${url}`);
191050
+ }
190939
191051
  }
190940
- } catch {
191052
+ setState((s) => ({ ...s, phase: "success", deployment: status }));
191053
+ return;
190941
191054
  }
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]);
191055
+ if (status.state === "deploying") {
191056
+ setState((s) => ({ ...s, phase: "deploying", deployment: status }));
191057
+ } else if (status.state === "queued") {
191058
+ setState((s) => ({ ...s, phase: "queued", deployment: status }));
191059
+ }
191060
+ } catch (err) {
191061
+ writeLog("deploy", `Poll error: ${err instanceof Error ? err.message : String(err)}`);
191062
+ }
191063
+ };
191064
+ pollInterval = setInterval(pollForCompletion, 2e3);
191065
+ pollForCompletion();
191066
+ return () => {
191067
+ cancelled = true;
191068
+ if (pollInterval) clearInterval(pollInterval);
191069
+ };
191070
+ }, [state.phase, state.deployment?.id]);
190950
191071
  useEffect4(() => {
190951
191072
  if (state.phase === "testing-builds") {
190952
191073
  trackEvent("deploy_started", { environment });
@@ -190971,6 +191092,7 @@ ${errorMsg}`
190971
191092
  deployment,
190972
191093
  error,
190973
191094
  tarballSize,
191095
+ pendingActions,
190974
191096
  missingSecrets,
190975
191097
  currentSecretIndex,
190976
191098
  missingConfigs,
@@ -191018,7 +191140,28 @@ ${errorMsg}`
191018
191140
  }
191019
191141
  const currentSecret = missingSecrets && currentSecretIndex !== void 0 ? missingSecrets[currentSecretIndex] : void 0;
191020
191142
  const currentConfig = missingConfigs && currentConfigIndex !== void 0 ? missingConfigs[currentConfigIndex] : void 0;
191021
- const displayPhase = phase === "awaiting-secrets" || phase === "awaiting-configs" ? "building" : phase;
191143
+ const getDisplayPhase = () => {
191144
+ if (phase === "awaiting-secrets" || phase === "awaiting-configs" || phase === "starting") {
191145
+ return "pending";
191146
+ }
191147
+ if (phase === "queued") {
191148
+ return "deploying";
191149
+ }
191150
+ return phase;
191151
+ };
191152
+ const displayPhase = getDisplayPhase();
191153
+ const getPendingLabel = () => {
191154
+ if (hasBuildsInProgress(pendingActions)) {
191155
+ return "Building images";
191156
+ }
191157
+ if (phase === "awaiting-secrets" || phase === "awaiting-configs") {
191158
+ return "Waiting for input";
191159
+ }
191160
+ if (phase === "starting") {
191161
+ return "Starting deployment";
191162
+ }
191163
+ return "Preparing";
191164
+ };
191022
191165
  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
191166
  PhaseIndicator,
191024
191167
  {
@@ -191050,16 +191193,16 @@ ${errorMsg}`
191050
191193
  ), /* @__PURE__ */ React7.createElement(
191051
191194
  PhaseIndicator,
191052
191195
  {
191053
- phase: "building",
191196
+ phase: "pending",
191054
191197
  currentPhase: displayPhase,
191055
- label: "Building"
191198
+ label: getPendingLabel()
191056
191199
  }
191057
191200
  ), /* @__PURE__ */ React7.createElement(
191058
191201
  PhaseIndicator,
191059
191202
  {
191060
191203
  phase: "deploying",
191061
191204
  currentPhase: displayPhase,
191062
- label: "Deploying"
191205
+ label: phase === "queued" ? "Waiting in queue" : "Deploying"
191063
191206
  }
191064
191207
  )), phase === "awaiting-secrets" && currentSecret && /* @__PURE__ */ React7.createElement(
191065
191208
  SecretInput,
@@ -191495,7 +191638,7 @@ function logoutCommand() {
191495
191638
  var program = new Command();
191496
191639
  var env = "production";
191497
191640
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
191498
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.45").enablePositionalOptions();
191641
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.46").enablePositionalOptions();
191499
191642
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
191500
191643
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
191501
191644
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",