@specific.dev/cli 0.1.114 → 0.1.115

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 (67) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/databases/__next._full.txt +1 -1
  21. package/dist/admin/databases/__next._head.txt +1 -1
  22. package/dist/admin/databases/__next._index.txt +1 -1
  23. package/dist/admin/databases/__next._tree.txt +1 -1
  24. package/dist/admin/databases/index.html +1 -1
  25. package/dist/admin/databases/index.txt +1 -1
  26. package/dist/admin/fullscreen/__next._full.txt +1 -1
  27. package/dist/admin/fullscreen/__next._head.txt +1 -1
  28. package/dist/admin/fullscreen/__next._index.txt +1 -1
  29. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  30. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  32. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  39. package/dist/admin/fullscreen/databases/index.html +1 -1
  40. package/dist/admin/fullscreen/databases/index.txt +1 -1
  41. package/dist/admin/fullscreen/index.html +1 -1
  42. package/dist/admin/fullscreen/index.txt +1 -1
  43. package/dist/admin/index.html +1 -1
  44. package/dist/admin/index.txt +1 -1
  45. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  48. package/dist/admin/mail/__next._full.txt +1 -1
  49. package/dist/admin/mail/__next._head.txt +1 -1
  50. package/dist/admin/mail/__next._index.txt +1 -1
  51. package/dist/admin/mail/__next._tree.txt +1 -1
  52. package/dist/admin/mail/index.html +1 -1
  53. package/dist/admin/mail/index.txt +1 -1
  54. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  55. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  56. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  57. package/dist/admin/workflows/__next._full.txt +1 -1
  58. package/dist/admin/workflows/__next._head.txt +1 -1
  59. package/dist/admin/workflows/__next._index.txt +1 -1
  60. package/dist/admin/workflows/__next._tree.txt +1 -1
  61. package/dist/admin/workflows/index.html +1 -1
  62. package/dist/admin/workflows/index.txt +1 -1
  63. package/dist/cli.js +186 -103
  64. package/package.json +1 -1
  65. /package/dist/admin/_next/static/{bgRGrZnID0BykUNK10yK_ → 3wOysvI433uU7Czi4vdl0}/_buildManifest.js +0 -0
  66. /package/dist/admin/_next/static/{bgRGrZnID0BykUNK10yK_ → 3wOysvI433uU7Czi4vdl0}/_clientMiddlewareManifest.json +0 -0
  67. /package/dist/admin/_next/static/{bgRGrZnID0BykUNK10yK_ → 3wOysvI433uU7Czi4vdl0}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -367514,8 +367514,14 @@ var ApiClient = class {
367514
367514
  Authorization: `Bearer ${token}`
367515
367515
  };
367516
367516
  }
367517
- async createDeployment(projectId, environment) {
367518
- const requestBody = { projectId, environment };
367517
+ async createDeployment(projectId, environment, options2) {
367518
+ const requestBody = {
367519
+ projectId,
367520
+ environment,
367521
+ ...options2?.triggeredBy && { triggeredBy: options2.triggeredBy },
367522
+ ...options2?.gitCommitSha && { gitCommitSha: options2.gitCommitSha },
367523
+ ...options2?.gitBranch && { gitBranch: options2.gitBranch }
367524
+ };
367519
367525
  writeLog("api", `POST ${this.baseUrl}/deployments`);
367520
367526
  writeLog("api", `Request body: ${JSON.stringify(requestBody)}`);
367521
367527
  const response = await fetch(`${this.baseUrl}/deployments`, {
@@ -367862,9 +367868,9 @@ var SpecificClient = class {
367862
367868
  return await this.client.getMe();
367863
367869
  }
367864
367870
  // --- Deployments ---
367865
- async createDeployment(projectId, environment) {
367871
+ async createDeployment(projectId, environment, options2) {
367866
367872
  return toDeployment(
367867
- await this.client.createDeployment(projectId, environment)
367873
+ await this.client.createDeployment(projectId, environment, options2)
367868
367874
  );
367869
367875
  }
367870
367876
  async uploadTarball(deploymentId, tarball, appPath) {
@@ -369458,7 +369464,7 @@ async function parseLocalFile(content) {
369458
369464
  if (block && typeof block === "object") {
369459
369465
  for (const [key, value] of Object.entries(block)) {
369460
369466
  if (typeof value === "string") {
369461
- secrets.set(key, value);
369467
+ secrets.set(key, value.trimEnd());
369462
369468
  }
369463
369469
  }
369464
369470
  }
@@ -369470,7 +369476,7 @@ async function parseLocalFile(content) {
369470
369476
  if (block && typeof block === "object") {
369471
369477
  for (const [key, value] of Object.entries(block)) {
369472
369478
  if (typeof value === "string") {
369473
- configs.set(key, value);
369479
+ configs.set(key, value.trimEnd());
369474
369480
  }
369475
369481
  }
369476
369482
  }
@@ -369485,57 +369491,42 @@ async function loadLocal() {
369485
369491
  const content = await readFile(LOCAL_FILE, "utf-8");
369486
369492
  return await parseLocalFile(content);
369487
369493
  }
369488
- function escapeHclValue(value) {
369489
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
369494
+ function formatHclValue(value) {
369495
+ if (value.includes("\n")) {
369496
+ return `<<-EOT
369497
+ ${value}
369498
+ EOT`;
369499
+ }
369500
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
369501
+ return `"${escaped}"`;
369490
369502
  }
369491
- function updateBlockValue(content, blockName, key, value) {
369492
- const escapedValue = escapeHclValue(value);
369493
- const newLine = ` ${key} = "${escapedValue}"`;
369494
- const blockRegex = new RegExp(`(${blockName}\\s*\\{)([^}]*)(\\})`, "s");
369495
- const blockMatch = content.match(blockRegex);
369496
- if (blockMatch) {
369497
- const blockContent = blockMatch[2];
369498
- const keyRegex = new RegExp(`^(\\s*)${key}\\s*=\\s*"[^"]*"\\s*$`, "m");
369499
- const keyMatch = blockContent.match(keyRegex);
369500
- if (keyMatch) {
369501
- const updatedBlockContent = blockContent.replace(keyRegex, newLine);
369502
- return content.replace(blockRegex, `$1${updatedBlockContent}$3`);
369503
- } else {
369504
- const trimmedContent = blockContent.trimEnd();
369505
- const newBlockContent = trimmedContent ? `${trimmedContent}
369506
- ${newLine}
369507
- ` : `
369508
- ${newLine}
369509
- `;
369510
- return content.replace(blockRegex, `$1${newBlockContent}$3`);
369511
- }
369512
- } else {
369513
- const newBlock = `${blockName} {
369514
- ${newLine}
369503
+ function serializeBlock(blockName, entries) {
369504
+ const lines = [`${blockName} {`];
369505
+ for (const [key, value] of entries) {
369506
+ lines.push(` ${key} = ${formatHclValue(value)}`);
369507
+ }
369508
+ lines.push("}");
369509
+ return lines.join("\n");
369515
369510
  }
369516
- `;
369517
- return content.trimEnd() + "\n\n" + newBlock;
369511
+ function serializeLocalFile(secrets, configs) {
369512
+ const blocks = [];
369513
+ if (secrets.size > 0) {
369514
+ blocks.push(serializeBlock("secrets", secrets));
369515
+ }
369516
+ if (configs.size > 0) {
369517
+ blocks.push(serializeBlock("config", configs));
369518
369518
  }
369519
+ return HEADER_COMMENT + blocks.join("\n\n") + "\n";
369519
369520
  }
369520
369521
  async function saveLocalSecret(name, value) {
369521
- let content = "";
369522
- if (existsSync5(LOCAL_FILE)) {
369523
- content = await readFile(LOCAL_FILE, "utf-8");
369524
- } else {
369525
- content = HEADER_COMMENT;
369526
- }
369527
- content = updateBlockValue(content, "secrets", name, value);
369528
- await writeFile(LOCAL_FILE, content);
369522
+ const { secrets, configs } = existsSync5(LOCAL_FILE) ? await parseLocalFile(await readFile(LOCAL_FILE, "utf-8")) : { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
369523
+ secrets.set(name, value);
369524
+ await writeFile(LOCAL_FILE, serializeLocalFile(secrets, configs));
369529
369525
  }
369530
369526
  async function saveLocalConfig(name, value) {
369531
- let content = "";
369532
- if (existsSync5(LOCAL_FILE)) {
369533
- content = await readFile(LOCAL_FILE, "utf-8");
369534
- } else {
369535
- content = HEADER_COMMENT;
369536
- }
369537
- content = updateBlockValue(content, "config", name, value);
369538
- await writeFile(LOCAL_FILE, content);
369527
+ const { secrets, configs } = existsSync5(LOCAL_FILE) ? await parseLocalFile(await readFile(LOCAL_FILE, "utf-8")) : { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
369528
+ configs.set(name, value);
369529
+ await writeFile(LOCAL_FILE, serializeLocalFile(secrets, configs));
369539
369530
  }
369540
369531
  function appendSearchPathToUrl(baseUrl, searchPath) {
369541
369532
  const url = new URL(baseUrl);
@@ -373287,7 +373278,7 @@ function trackEvent(event, properties) {
373287
373278
  event,
373288
373279
  properties: {
373289
373280
  ...properties,
373290
- cli_version: "0.1.114",
373281
+ cli_version: "0.1.115",
373291
373282
  platform: process.platform,
373292
373283
  node_version: process.version,
373293
373284
  project_id: getProjectId()
@@ -374520,53 +374511,113 @@ function checkCommand() {
374520
374511
  }
374521
374512
 
374522
374513
  // src/commands/dev.tsx
374523
- import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
374514
+ import React6, { useState as useState5, useEffect as useEffect5, useRef as useRef3 } from "react";
374524
374515
  import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
374525
374516
  import Spinner3 from "ink-spinner";
374526
374517
  import { Readable as Readable2 } from "stream";
374527
374518
 
374528
374519
  // src/lib/ui/SecretInput.tsx
374529
- import React4, { useState as useState3 } from "react";
374520
+ import React4, { useState as useState3, useRef, useCallback, useEffect as useEffect3 } from "react";
374530
374521
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
374522
+ var PASTE_DETECT_DELAY_MS = 100;
374531
374523
  function SecretInput({ secretName, onSubmit, onCancel }) {
374532
374524
  const [value, setValue] = useState3("");
374525
+ const valueRef = useRef("");
374526
+ const submitTimer = useRef(null);
374527
+ useEffect3(() => {
374528
+ valueRef.current = value;
374529
+ }, [value]);
374530
+ const scheduleSubmit = useCallback(() => {
374531
+ if (submitTimer.current) {
374532
+ clearTimeout(submitTimer.current);
374533
+ }
374534
+ submitTimer.current = setTimeout(() => {
374535
+ submitTimer.current = null;
374536
+ if (valueRef.current.trim() !== "") {
374537
+ onSubmit(valueRef.current);
374538
+ }
374539
+ }, PASTE_DETECT_DELAY_MS);
374540
+ }, [onSubmit]);
374541
+ const cancelSubmit = useCallback(() => {
374542
+ if (submitTimer.current) {
374543
+ clearTimeout(submitTimer.current);
374544
+ submitTimer.current = null;
374545
+ setValue((prev) => prev + "\n");
374546
+ }
374547
+ }, []);
374548
+ useEffect3(() => {
374549
+ return () => {
374550
+ if (submitTimer.current) clearTimeout(submitTimer.current);
374551
+ };
374552
+ }, []);
374533
374553
  useInput2((input, key) => {
374534
374554
  if (key.return) {
374535
- if (value.trim() !== "") {
374536
- onSubmit(value);
374537
- }
374555
+ scheduleSubmit();
374538
374556
  } else if (key.escape) {
374539
374557
  onCancel();
374540
374558
  } else if (key.backspace || key.delete) {
374559
+ cancelSubmit();
374541
374560
  setValue((prev) => prev.slice(0, -1));
374542
374561
  } else if (!key.ctrl && !key.meta && input) {
374562
+ cancelSubmit();
374543
374563
  setValue((prev) => prev + input);
374544
374564
  }
374545
374565
  });
374546
- return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, null, "Enter value for secret ", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, secretName), ":"), /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "> "), /* @__PURE__ */ React4.createElement(Text4, null, value.length > 0 ? "*".repeat(value.length) : ""), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "|")), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
374566
+ const lineCount = value.split("\n").length;
374567
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, null, "Enter value for secret ", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, secretName), ":"), /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "> "), /* @__PURE__ */ React4.createElement(Text4, null, value.length > 0 ? lineCount > 1 ? `[${lineCount} lines, ${value.length} chars]` : "*".repeat(value.length) : ""), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "|")), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
374547
374568
  }
374548
374569
 
374549
374570
  // src/lib/ui/ConfigInput.tsx
374550
- import React5, { useState as useState4 } from "react";
374571
+ import React5, { useState as useState4, useRef as useRef2, useCallback as useCallback2, useEffect as useEffect4 } from "react";
374551
374572
  import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
374573
+ var PASTE_DETECT_DELAY_MS2 = 100;
374552
374574
  function ConfigInput({ configName, defaultValue, onSubmit, onCancel }) {
374553
374575
  const [value, setValue] = useState4("");
374554
- useInput3((input, key) => {
374555
- if (key.return) {
374556
- if (value.trim() !== "") {
374557
- onSubmit(value);
374576
+ const valueRef = useRef2("");
374577
+ const submitTimer = useRef2(null);
374578
+ useEffect4(() => {
374579
+ valueRef.current = value;
374580
+ }, [value]);
374581
+ const scheduleSubmit = useCallback2(() => {
374582
+ if (submitTimer.current) {
374583
+ clearTimeout(submitTimer.current);
374584
+ }
374585
+ submitTimer.current = setTimeout(() => {
374586
+ submitTimer.current = null;
374587
+ if (valueRef.current.trim() !== "") {
374588
+ onSubmit(valueRef.current);
374558
374589
  } else if (defaultValue !== void 0) {
374559
374590
  onSubmit(defaultValue);
374560
374591
  }
374592
+ }, PASTE_DETECT_DELAY_MS2);
374593
+ }, [onSubmit, defaultValue]);
374594
+ const cancelSubmit = useCallback2(() => {
374595
+ if (submitTimer.current) {
374596
+ clearTimeout(submitTimer.current);
374597
+ submitTimer.current = null;
374598
+ setValue((prev) => prev + "\n");
374599
+ }
374600
+ }, []);
374601
+ useEffect4(() => {
374602
+ return () => {
374603
+ if (submitTimer.current) clearTimeout(submitTimer.current);
374604
+ };
374605
+ }, []);
374606
+ useInput3((input, key) => {
374607
+ if (key.return) {
374608
+ scheduleSubmit();
374561
374609
  } else if (key.escape) {
374562
374610
  onCancel();
374563
374611
  } else if (key.backspace || key.delete) {
374612
+ cancelSubmit();
374564
374613
  setValue((prev) => prev.slice(0, -1));
374565
374614
  } else if (!key.ctrl && !key.meta && input) {
374615
+ cancelSubmit();
374566
374616
  setValue((prev) => prev + input);
374567
374617
  }
374568
374618
  });
374569
- return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, "Enter value for config ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, configName), defaultValue !== void 0 && /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " (default: ", defaultValue, ")"), ":"), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(Text5, null, value), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, "|")), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, defaultValue !== void 0 ? "(Press Enter to accept default, type to override, Esc to cancel)" : "(Press Enter to save, Esc to cancel)"));
374619
+ const lineCount = value.split("\n").length;
374620
+ return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, "Enter value for config ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, configName), defaultValue !== void 0 && /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " (default: ", defaultValue, ")"), ":"), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(Text5, null, lineCount > 1 ? `[${lineCount} lines]` : value), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, "|")), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, defaultValue !== void 0 ? "(Press Enter to accept default, type to override, Esc to cancel)" : "(Press Enter to save, Esc to cancel)"));
374570
374621
  }
374571
374622
 
374572
374623
  // src/commands/dev.tsx
@@ -374583,8 +374634,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
374583
374634
  tunnelUrls: /* @__PURE__ */ new Map(),
374584
374635
  tunnelStatus: /* @__PURE__ */ new Map()
374585
374636
  });
374586
- const devEnvRef = useRef(null);
374587
- const startTimeRef = useRef(null);
374637
+ const devEnvRef = useRef3(null);
374638
+ const startTimeRef = useRef3(null);
374588
374639
  const buildColorMap = (config2) => {
374589
374640
  const colorMap = /* @__PURE__ */ new Map();
374590
374641
  let colorIndex = 0;
@@ -374610,7 +374661,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
374610
374661
  }
374611
374662
  return colorMap;
374612
374663
  };
374613
- useEffect3(() => {
374664
+ useEffect5(() => {
374614
374665
  const devEnv = new DevEnvironment({
374615
374666
  projectDir: process.cwd(),
374616
374667
  instanceKey,
@@ -374747,7 +374798,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
374747
374798
  devEnvRef.current = null;
374748
374799
  };
374749
374800
  }, [instanceKey, tunnelEnabled]);
374750
- useEffect3(() => {
374801
+ useEffect5(() => {
374751
374802
  const handleSignal = () => {
374752
374803
  const devEnv = devEnvRef.current;
374753
374804
  if (!devEnv) return;
@@ -374778,7 +374829,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
374778
374829
  process.off("unhandledRejection", handleCrash);
374779
374830
  };
374780
374831
  }, []);
374781
- useEffect3(() => {
374832
+ useEffect5(() => {
374782
374833
  if (state.status === "running" && !startTimeRef.current) {
374783
374834
  startTimeRef.current = Date.now();
374784
374835
  trackEvent("dev_started");
@@ -375074,11 +375125,29 @@ function devCommand(instanceKey, tunnelEnabled = false) {
375074
375125
 
375075
375126
  // src/commands/deploy.tsx
375076
375127
  init_open();
375077
- import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
375128
+ import React7, { useState as useState6, useEffect as useEffect6, useCallback as useCallback3 } from "react";
375078
375129
  import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput4 } from "ink";
375079
375130
  import Spinner4 from "ink-spinner";
375080
375131
  import * as fs22 from "fs";
375081
375132
  import * as path20 from "path";
375133
+ import { execFileSync as execFileSync2 } from "child_process";
375134
+ function getGitInfo(cwd) {
375135
+ try {
375136
+ const commitSha = execFileSync2("git", ["rev-parse", "HEAD"], {
375137
+ cwd,
375138
+ encoding: "utf-8",
375139
+ stdio: ["pipe", "pipe", "pipe"]
375140
+ }).trim();
375141
+ const branch = execFileSync2(
375142
+ "git",
375143
+ ["rev-parse", "--abbrev-ref", "HEAD"],
375144
+ { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
375145
+ ).trim();
375146
+ return { commitSha, branch };
375147
+ } catch {
375148
+ return null;
375149
+ }
375150
+ }
375082
375151
  function formatBytes(bytes) {
375083
375152
  if (bytes < 1024) return `${bytes} B`;
375084
375153
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -375224,7 +375293,7 @@ function DeployUI({ environment, config }) {
375224
375293
  const { exit } = useApp3();
375225
375294
  const [state, setState] = useState6({ phase: "checking-auth" });
375226
375295
  const clientRef = React7.useRef(null);
375227
- useEffect4(() => {
375296
+ useEffect6(() => {
375228
375297
  if (state.phase !== "checking-auth") return;
375229
375298
  const projectDir = process.cwd();
375230
375299
  if (hasProjectId(projectDir)) {
@@ -375245,7 +375314,7 @@ function DeployUI({ environment, config }) {
375245
375314
  }
375246
375315
  setState({ phase: "logging-in" });
375247
375316
  }, [state.phase]);
375248
- useEffect4(() => {
375317
+ useEffect6(() => {
375249
375318
  if (state.phase !== "logging-in") return;
375250
375319
  let cancelled = false;
375251
375320
  async function startLogin() {
@@ -375306,7 +375375,7 @@ function DeployUI({ environment, config }) {
375306
375375
  cancelled = true;
375307
375376
  };
375308
375377
  }, [state.phase]);
375309
- useEffect4(() => {
375378
+ useEffect6(() => {
375310
375379
  if (state.phase !== "loading-projects") return;
375311
375380
  let cancelled = false;
375312
375381
  async function loadProjects() {
@@ -375339,7 +375408,7 @@ function DeployUI({ environment, config }) {
375339
375408
  cancelled = true;
375340
375409
  };
375341
375410
  }, [state.phase]);
375342
- const handleProjectSelect = useCallback(
375411
+ const handleProjectSelect = useCallback3(
375343
375412
  (project) => {
375344
375413
  if ("type" in project && project.type === "new") {
375345
375414
  setState((s) => ({ ...s, phase: "entering-name", selectedOrganizationId: project.orgId }));
@@ -375354,13 +375423,13 @@ function DeployUI({ environment, config }) {
375354
375423
  },
375355
375424
  []
375356
375425
  );
375357
- const handleNameSubmit = useCallback((name) => {
375426
+ const handleNameSubmit = useCallback3((name) => {
375358
375427
  setState((s) => ({ ...s, phase: "creating-project", newProjectName: name }));
375359
375428
  }, []);
375360
- const handleNameCancel = useCallback(() => {
375429
+ const handleNameCancel = useCallback3(() => {
375361
375430
  setState((s) => ({ ...s, phase: "selecting-project" }));
375362
375431
  }, []);
375363
- useEffect4(() => {
375432
+ useEffect6(() => {
375364
375433
  if (state.phase !== "creating-project" || !state.newProjectName) return;
375365
375434
  let cancelled = false;
375366
375435
  async function createProject() {
@@ -375387,7 +375456,7 @@ function DeployUI({ environment, config }) {
375387
375456
  cancelled = true;
375388
375457
  };
375389
375458
  }, [state.phase, state.newProjectName]);
375390
- const handleSecretSubmit = useCallback((value) => {
375459
+ const handleSecretSubmit = useCallback3((value) => {
375391
375460
  setState((s) => {
375392
375461
  if (!s.missingSecrets || s.currentSecretIndex === void 0) return s;
375393
375462
  const currentSecret2 = s.missingSecrets[s.currentSecretIndex];
@@ -375410,14 +375479,14 @@ function DeployUI({ environment, config }) {
375410
375479
  };
375411
375480
  });
375412
375481
  }, []);
375413
- const handleSecretCancel = useCallback(() => {
375482
+ const handleSecretCancel = useCallback3(() => {
375414
375483
  setState((s) => ({
375415
375484
  ...s,
375416
375485
  phase: "error",
375417
375486
  error: "Deployment cancelled - secrets not provided"
375418
375487
  }));
375419
375488
  }, []);
375420
- const handleConfigSubmit = useCallback((value) => {
375489
+ const handleConfigSubmit = useCallback3((value) => {
375421
375490
  setState((s) => {
375422
375491
  if (!s.missingConfigs || s.currentConfigIndex === void 0) return s;
375423
375492
  const currentConfig2 = s.missingConfigs[s.currentConfigIndex];
@@ -375440,14 +375509,14 @@ function DeployUI({ environment, config }) {
375440
375509
  };
375441
375510
  });
375442
375511
  }, []);
375443
- const handleConfigCancel = useCallback(() => {
375512
+ const handleConfigCancel = useCallback3(() => {
375444
375513
  setState((s) => ({
375445
375514
  ...s,
375446
375515
  phase: "error",
375447
375516
  error: "Deployment cancelled - configs not provided"
375448
375517
  }));
375449
375518
  }, []);
375450
- useEffect4(() => {
375519
+ useEffect6(() => {
375451
375520
  const {
375452
375521
  phase: phase2,
375453
375522
  deployment: deployment2,
@@ -375489,7 +375558,7 @@ function DeployUI({ environment, config }) {
375489
375558
  }
375490
375559
  })();
375491
375560
  }, [state]);
375492
- useEffect4(() => {
375561
+ useEffect6(() => {
375493
375562
  const {
375494
375563
  phase: phase2,
375495
375564
  deployment: deployment2,
@@ -375531,7 +375600,7 @@ function DeployUI({ environment, config }) {
375531
375600
  }
375532
375601
  })();
375533
375602
  }, [state]);
375534
- useEffect4(() => {
375603
+ useEffect6(() => {
375535
375604
  if (state.phase !== "creating-tarball" || !state.projectId) return;
375536
375605
  let cancelled = false;
375537
375606
  async function runDeploy() {
@@ -375567,7 +375636,14 @@ function DeployUI({ environment, config }) {
375567
375636
  let deployment2;
375568
375637
  try {
375569
375638
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
375570
- deployment2 = await client2.createDeployment(state.projectId, environment);
375639
+ const gitInfo = getGitInfo(projectDir);
375640
+ deployment2 = await client2.createDeployment(state.projectId, environment, {
375641
+ triggeredBy: "cli",
375642
+ ...gitInfo && {
375643
+ gitCommitSha: gitInfo.commitSha,
375644
+ gitBranch: gitInfo.branch
375645
+ }
375646
+ });
375571
375647
  writeLog("deploy", `Deployment created: ${deployment2.id}`);
375572
375648
  } catch (err) {
375573
375649
  const errorMsg = `Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`;
@@ -375604,7 +375680,7 @@ function DeployUI({ environment, config }) {
375604
375680
  cancelled = true;
375605
375681
  };
375606
375682
  }, [state.projectId, environment, config.builds]);
375607
- useEffect4(() => {
375683
+ useEffect6(() => {
375608
375684
  if (state.phase !== "pending" || !state.deployment) return;
375609
375685
  let pollInterval;
375610
375686
  let cancelled = false;
@@ -375713,7 +375789,7 @@ function DeployUI({ environment, config }) {
375713
375789
  if (pollInterval) clearInterval(pollInterval);
375714
375790
  };
375715
375791
  }, [state.phase, state.deployment?.id]);
375716
- useEffect4(() => {
375792
+ useEffect6(() => {
375717
375793
  if (state.phase !== "starting" || !state.deployment) return;
375718
375794
  let cancelled = false;
375719
375795
  const client2 = clientRef.current;
@@ -375743,7 +375819,7 @@ function DeployUI({ environment, config }) {
375743
375819
  cancelled = true;
375744
375820
  };
375745
375821
  }, [state.phase, state.deployment?.id]);
375746
- useEffect4(() => {
375822
+ useEffect6(() => {
375747
375823
  if (state.phase !== "queued" && state.phase !== "deploying" || !state.deployment) return;
375748
375824
  let pollInterval;
375749
375825
  let cancelled = false;
@@ -375796,12 +375872,12 @@ function DeployUI({ environment, config }) {
375796
375872
  if (pollInterval) clearInterval(pollInterval);
375797
375873
  };
375798
375874
  }, [state.phase, state.deployment?.id]);
375799
- useEffect4(() => {
375875
+ useEffect6(() => {
375800
375876
  if (state.phase === "creating-tarball") {
375801
375877
  trackEvent("deploy_started", { environment });
375802
375878
  }
375803
375879
  }, [state.phase, environment]);
375804
- useEffect4(() => {
375880
+ useEffect6(() => {
375805
375881
  if (state.phase === "success") {
375806
375882
  trackEvent("deploy_succeeded", { environment });
375807
375883
  closeDebugLog();
@@ -376008,7 +376084,14 @@ async function runDeployPipeline(options2) {
376008
376084
  console.log("Creating deployment...");
376009
376085
  let deployment;
376010
376086
  try {
376011
- deployment = await client2.createDeployment(projectId, "prod");
376087
+ const gitInfo = getGitInfo(projectDir);
376088
+ deployment = await client2.createDeployment(projectId, "prod", {
376089
+ triggeredBy: "cli",
376090
+ ...gitInfo && {
376091
+ gitCommitSha: gitInfo.commitSha,
376092
+ gitBranch: gitInfo.branch
376093
+ }
376094
+ });
376012
376095
  } catch (err) {
376013
376096
  console.error(`Error: Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
376014
376097
  process.exit(1);
@@ -376753,14 +376836,14 @@ async function reshapeCommand(action, databaseName, instanceKey = "default") {
376753
376836
  }
376754
376837
 
376755
376838
  // src/commands/clean.tsx
376756
- import React8, { useState as useState7, useEffect as useEffect5 } from "react";
376839
+ import React8, { useState as useState7, useEffect as useEffect7 } from "react";
376757
376840
  import { render as render6, Text as Text8, Box as Box8 } from "ink";
376758
376841
  import Spinner5 from "ink-spinner";
376759
376842
  import * as fs26 from "fs";
376760
376843
  import * as path24 from "path";
376761
376844
  function CleanUI({ instanceKey }) {
376762
376845
  const [state, setState] = useState7({ status: "checking" });
376763
- useEffect5(() => {
376846
+ useEffect7(() => {
376764
376847
  async function clean() {
376765
376848
  const projectRoot = process.cwd();
376766
376849
  const specificDir = path24.join(projectRoot, ".specific");
@@ -376888,14 +376971,14 @@ async function loginCommand(options2 = {}) {
376888
376971
  }
376889
376972
 
376890
376973
  // src/commands/logout.tsx
376891
- import React9, { useState as useState8, useEffect as useEffect6 } from "react";
376974
+ import React9, { useState as useState8, useEffect as useEffect8 } from "react";
376892
376975
  import { render as render7, Text as Text9, useApp as useApp4 } from "ink";
376893
376976
  function LogoutUI() {
376894
376977
  const { exit } = useApp4();
376895
376978
  const [state, setState] = useState8({
376896
376979
  phase: "checking"
376897
376980
  });
376898
- useEffect6(() => {
376981
+ useEffect8(() => {
376899
376982
  if (state.phase !== "checking") return;
376900
376983
  if (!isLoggedIn()) {
376901
376984
  setState({ phase: "not-logged-in" });
@@ -376904,7 +376987,7 @@ function LogoutUI() {
376904
376987
  clearUserCredentials();
376905
376988
  setState({ phase: "done" });
376906
376989
  }, [state.phase]);
376907
- useEffect6(() => {
376990
+ useEffect8(() => {
376908
376991
  if (state.phase === "done" || state.phase === "not-logged-in") {
376909
376992
  const timer = setTimeout(() => exit(), 100);
376910
376993
  return () => clearTimeout(timer);
@@ -376923,7 +377006,7 @@ function logoutCommand() {
376923
377006
  }
376924
377007
 
376925
377008
  // src/commands/beta.tsx
376926
- import React10, { useState as useState9, useEffect as useEffect7 } from "react";
377009
+ import React10, { useState as useState9, useEffect as useEffect9 } from "react";
376927
377010
  import { render as render8, Text as Text10, Box as Box9, useInput as useInput5, useApp as useApp5 } from "ink";
376928
377011
  function BetaToggleUI() {
376929
377012
  const { exit } = useApp5();
@@ -376961,7 +377044,7 @@ function BetaToggleUI() {
376961
377044
  setSaved(true);
376962
377045
  }
376963
377046
  });
376964
- useEffect7(() => {
377047
+ useEffect9(() => {
376965
377048
  if (saved) {
376966
377049
  const timer = setTimeout(() => exit(), 100);
376967
377050
  return () => clearTimeout(timer);
@@ -376999,7 +377082,7 @@ function betaCommand() {
376999
377082
  }
377000
377083
 
377001
377084
  // src/commands/update.tsx
377002
- import React11, { useState as useState10, useEffect as useEffect8 } from "react";
377085
+ import React11, { useState as useState10, useEffect as useEffect10 } from "react";
377003
377086
  import { render as render9, Text as Text11, Box as Box10, useApp as useApp6 } from "ink";
377004
377087
  import Spinner6 from "ink-spinner";
377005
377088
 
@@ -377019,7 +377102,7 @@ function compareVersions(a, b) {
377019
377102
  return 0;
377020
377103
  }
377021
377104
  async function checkForUpdate() {
377022
- const currentVersion = "0.1.114";
377105
+ const currentVersion = "0.1.115";
377023
377106
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
377024
377107
  if (!response.ok) {
377025
377108
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -377115,7 +377198,7 @@ function maybeStartBackgroundUpdate() {
377115
377198
  function UpdateUI() {
377116
377199
  const { exit } = useApp6();
377117
377200
  const [state, setState] = useState10({ phase: "checking" });
377118
- useEffect8(() => {
377201
+ useEffect10(() => {
377119
377202
  if (state.phase !== "checking") return;
377120
377203
  let cancelled = false;
377121
377204
  async function check() {
@@ -377144,7 +377227,7 @@ function UpdateUI() {
377144
377227
  cancelled = true;
377145
377228
  };
377146
377229
  }, [state.phase]);
377147
- useEffect8(() => {
377230
+ useEffect10(() => {
377148
377231
  if (state.phase !== "downloading" || !state.checkResult) return;
377149
377232
  let cancelled = false;
377150
377233
  async function download() {
@@ -377176,7 +377259,7 @@ function UpdateUI() {
377176
377259
  cancelled = true;
377177
377260
  };
377178
377261
  }, [state.phase, state.checkResult]);
377179
- useEffect8(() => {
377262
+ useEffect10(() => {
377180
377263
  if (state.phase === "up-to-date" || state.phase === "success" || state.phase === "error" || state.phase === "permission-error") {
377181
377264
  const timer = setTimeout(() => exit(), 100);
377182
377265
  return () => clearTimeout(timer);
@@ -377287,7 +377370,7 @@ async function projectListCommand() {
377287
377370
  var program = new Command();
377288
377371
  var env = "production";
377289
377372
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
377290
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.114").enablePositionalOptions();
377373
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.115").enablePositionalOptions();
377291
377374
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
377292
377375
  Examples:
377293
377376
  $ specific init
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.114",
3
+ "version": "0.1.115",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",