@specific.dev/cli 0.1.38 → 0.1.40

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 (33) 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 +1 -1
  4. package/dist/admin/__next._full.txt +1 -1
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +1 -1
  7. package/dist/admin/__next._tree.txt +1 -1
  8. package/dist/admin/_next/static/chunks/{4b5ae5ebb2087f0d.js → 9054c84ba21a4c14.js} +1 -1
  9. package/dist/admin/_next/static/chunks/{0afa5e999b6c353a.js → dde2c8e6322d1671.js} +1 -1
  10. package/dist/admin/_next/static/chunks/{turbopack-e5185af8e7c716ca.js → turbopack-22b7312525502d51.js} +1 -1
  11. package/dist/admin/_not-found/__next._full.txt +1 -1
  12. package/dist/admin/_not-found/__next._head.txt +1 -1
  13. package/dist/admin/_not-found/__next._index.txt +1 -1
  14. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  15. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  16. package/dist/admin/_not-found/__next._tree.txt +1 -1
  17. package/dist/admin/_not-found/index.html +1 -1
  18. package/dist/admin/_not-found/index.txt +1 -1
  19. package/dist/admin/databases/__next._full.txt +1 -1
  20. package/dist/admin/databases/__next._head.txt +1 -1
  21. package/dist/admin/databases/__next._index.txt +1 -1
  22. package/dist/admin/databases/__next._tree.txt +1 -1
  23. package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
  24. package/dist/admin/databases/__next.databases.txt +1 -1
  25. package/dist/admin/databases/index.html +1 -1
  26. package/dist/admin/databases/index.txt +1 -1
  27. package/dist/admin/index.html +1 -1
  28. package/dist/admin/index.txt +1 -1
  29. package/dist/cli.js +480 -369
  30. package/package.json +3 -2
  31. /package/dist/admin/_next/static/{dtzWfchSIjRVrII5xoJl6 → WKOmsOUhTOkoASmeDtlS1}/_buildManifest.js +0 -0
  32. /package/dist/admin/_next/static/{dtzWfchSIjRVrII5xoJl6 → WKOmsOUhTOkoASmeDtlS1}/_clientMiddlewareManifest.json +0 -0
  33. /package/dist/admin/_next/static/{dtzWfchSIjRVrII5xoJl6 → WKOmsOUhTOkoASmeDtlS1}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -182236,8 +182236,8 @@ import { Command } from "commander";
182236
182236
  import React, { useState, useEffect } from "react";
182237
182237
  import { render, Text, Box, useInput, useApp } from "ink";
182238
182238
  import "ink-spinner";
182239
- import * as fs2 from "fs";
182240
- import * as path2 from "path";
182239
+ import * as fs3 from "fs";
182240
+ import * as path3 from "path";
182241
182241
 
182242
182242
  // src/lib/dev/local-ca.ts
182243
182243
  import * as fs from "fs";
@@ -182405,6 +182405,106 @@ function generateCertificate(domain, keys = []) {
182405
182405
  };
182406
182406
  }
182407
182407
 
182408
+ // src/lib/analytics/index.ts
182409
+ import { PostHog } from "posthog-node";
182410
+ import * as os2 from "os";
182411
+ import * as crypto from "crypto";
182412
+
182413
+ // src/lib/project/config.ts
182414
+ import * as fs2 from "fs";
182415
+ import * as path2 from "path";
182416
+ var PROJECT_ID_FILE = ".specific/project_id";
182417
+ var ProjectNotLinkedError = class extends Error {
182418
+ constructor() {
182419
+ super(
182420
+ `Project not linked to Specific.
182421
+
182422
+ The file ${PROJECT_ID_FILE} does not exist.
182423
+
182424
+ Run: specific deploy`
182425
+ );
182426
+ this.name = "ProjectNotLinkedError";
182427
+ }
182428
+ };
182429
+ function readProjectId(projectDir = process.cwd()) {
182430
+ const projectIdPath = path2.join(projectDir, PROJECT_ID_FILE);
182431
+ if (!fs2.existsSync(projectIdPath)) {
182432
+ throw new ProjectNotLinkedError();
182433
+ }
182434
+ const projectId = fs2.readFileSync(projectIdPath, "utf-8").trim();
182435
+ if (!projectId) {
182436
+ throw new Error(`${PROJECT_ID_FILE} is empty`);
182437
+ }
182438
+ return projectId;
182439
+ }
182440
+ function hasProjectId(projectDir = process.cwd()) {
182441
+ const projectIdPath = path2.join(projectDir, PROJECT_ID_FILE);
182442
+ return fs2.existsSync(projectIdPath);
182443
+ }
182444
+ function writeProjectId(projectId, projectDir = process.cwd()) {
182445
+ const specificDir = path2.join(projectDir, ".specific");
182446
+ if (!fs2.existsSync(specificDir)) {
182447
+ fs2.mkdirSync(specificDir, { recursive: true });
182448
+ }
182449
+ fs2.writeFileSync(path2.join(specificDir, "project_id"), projectId + "\n");
182450
+ }
182451
+
182452
+ // src/lib/analytics/index.ts
182453
+ var POSTHOG_HOST = "https://eu.i.posthog.com";
182454
+ var client = null;
182455
+ var anonymousId = null;
182456
+ function isEnabled() {
182457
+ return true;
182458
+ }
182459
+ function getAnonymousId() {
182460
+ if (anonymousId) return anonymousId;
182461
+ const machineId = `${os2.hostname()}-${os2.userInfo().username}`;
182462
+ anonymousId = crypto.createHash("sha256").update(machineId).digest("hex").slice(0, 16);
182463
+ return anonymousId;
182464
+ }
182465
+ function getProjectId() {
182466
+ try {
182467
+ if (hasProjectId()) {
182468
+ return readProjectId();
182469
+ }
182470
+ } catch {
182471
+ }
182472
+ return void 0;
182473
+ }
182474
+ function getClient() {
182475
+ if (!isEnabled()) return null;
182476
+ if (!client) {
182477
+ client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
182478
+ host: POSTHOG_HOST,
182479
+ flushAt: 1,
182480
+ // Flush immediately for CLI
182481
+ flushInterval: 0
182482
+ });
182483
+ }
182484
+ return client;
182485
+ }
182486
+ function trackEvent(event, properties) {
182487
+ const ph = getClient();
182488
+ if (!ph) return;
182489
+ ph.capture({
182490
+ distinctId: getAnonymousId(),
182491
+ event,
182492
+ properties: {
182493
+ ...properties,
182494
+ cli_version: "0.1.40",
182495
+ platform: process.platform,
182496
+ node_version: process.version,
182497
+ project_id: getProjectId()
182498
+ }
182499
+ });
182500
+ }
182501
+ async function shutdown() {
182502
+ if (client) {
182503
+ await client.shutdown();
182504
+ client = null;
182505
+ }
182506
+ }
182507
+
182408
182508
  // src/commands/init.tsx
182409
182509
  var SPECIFIC_INSTRUCTIONS = `Use Specific to handle all infrastructure and development environments: like deployments, databases, real-time sync, services, secrets, configuration and environment variables. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration.`;
182410
182510
  var CURSOR_MDC_CONTENT = `---
@@ -182422,67 +182522,67 @@ var options = [
182422
182522
  { id: "other", label: "Other / Manual" }
182423
182523
  ];
182424
182524
  function isGitProject() {
182425
- const gitPath = path2.join(process.cwd(), ".git");
182426
- return fs2.existsSync(gitPath);
182525
+ const gitPath = path3.join(process.cwd(), ".git");
182526
+ return fs3.existsSync(gitPath);
182427
182527
  }
182428
182528
  function detectExistingAgents() {
182429
182529
  const detected = {};
182430
- const cursorDir = path2.join(process.cwd(), ".cursor");
182431
- if (fs2.existsSync(cursorDir)) {
182530
+ const cursorDir = path3.join(process.cwd(), ".cursor");
182531
+ if (fs3.existsSync(cursorDir)) {
182432
182532
  detected["cursor"] = true;
182433
182533
  }
182434
- const claudeDir = path2.join(process.cwd(), ".claude");
182435
- const claudeMd = path2.join(process.cwd(), "CLAUDE.md");
182436
- if (fs2.existsSync(claudeDir) || fs2.existsSync(claudeMd)) {
182534
+ const claudeDir = path3.join(process.cwd(), ".claude");
182535
+ const claudeMd = path3.join(process.cwd(), "CLAUDE.md");
182536
+ if (fs3.existsSync(claudeDir) || fs3.existsSync(claudeMd)) {
182437
182537
  detected["claude"] = true;
182438
182538
  }
182439
- const agentsMd = path2.join(process.cwd(), "AGENTS.md");
182440
- if (fs2.existsSync(agentsMd)) {
182539
+ const agentsMd = path3.join(process.cwd(), "AGENTS.md");
182540
+ if (fs3.existsSync(agentsMd)) {
182441
182541
  detected["codex"] = true;
182442
182542
  }
182443
182543
  return detected;
182444
182544
  }
182445
182545
  function appendOrCreateFile(filePath, content) {
182446
- if (fs2.existsSync(filePath)) {
182447
- const existing = fs2.readFileSync(filePath, "utf-8");
182546
+ if (fs3.existsSync(filePath)) {
182547
+ const existing = fs3.readFileSync(filePath, "utf-8");
182448
182548
  if (existing.includes("specific docs") || existing.includes("specific check")) {
182449
182549
  return "unchanged";
182450
182550
  }
182451
182551
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
182452
- fs2.writeFileSync(filePath, existing + separator + content + "\n");
182552
+ fs3.writeFileSync(filePath, existing + separator + content + "\n");
182453
182553
  return "modified";
182454
182554
  } else {
182455
- fs2.writeFileSync(filePath, content + "\n");
182555
+ fs3.writeFileSync(filePath, content + "\n");
182456
182556
  return "created";
182457
182557
  }
182458
182558
  }
182459
182559
  function addToGitignore() {
182460
- const gitignorePath = path2.join(process.cwd(), ".gitignore");
182560
+ const gitignorePath = path3.join(process.cwd(), ".gitignore");
182461
182561
  const entries = [".specific", "specific.secrets"];
182462
- if (fs2.existsSync(gitignorePath)) {
182463
- const existing = fs2.readFileSync(gitignorePath, "utf-8");
182562
+ if (fs3.existsSync(gitignorePath)) {
182563
+ const existing = fs3.readFileSync(gitignorePath, "utf-8");
182464
182564
  const lines = existing.split("\n").map((l) => l.trim());
182465
182565
  const missingEntries = entries.filter((entry) => !lines.includes(entry));
182466
182566
  if (missingEntries.length === 0) {
182467
182567
  return "unchanged";
182468
182568
  }
182469
182569
  const separator = existing.endsWith("\n") ? "" : "\n";
182470
- fs2.writeFileSync(
182570
+ fs3.writeFileSync(
182471
182571
  gitignorePath,
182472
182572
  existing + separator + missingEntries.join("\n") + "\n"
182473
182573
  );
182474
182574
  return "modified";
182475
182575
  } else {
182476
- fs2.writeFileSync(gitignorePath, entries.join("\n") + "\n");
182576
+ fs3.writeFileSync(gitignorePath, entries.join("\n") + "\n");
182477
182577
  return "created";
182478
182578
  }
182479
182579
  }
182480
182580
  function configureClaudeCodePermissions() {
182481
- const claudeDir = path2.join(process.cwd(), ".claude");
182482
- const settingsPath = path2.join(claudeDir, "settings.local.json");
182581
+ const claudeDir = path3.join(process.cwd(), ".claude");
182582
+ const settingsPath = path3.join(claudeDir, "settings.local.json");
182483
182583
  const permissions = ["Bash(specific docs:*)", "Bash(specific check:*)"];
182484
- if (fs2.existsSync(settingsPath)) {
182485
- const existing = JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
182584
+ if (fs3.existsSync(settingsPath)) {
182585
+ const existing = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
182486
182586
  const allowList = existing?.permissions?.allow || [];
182487
182587
  const missingPermissions = permissions.filter(
182488
182588
  (p) => !allowList.includes(p)
@@ -182497,39 +182597,39 @@ function configureClaudeCodePermissions() {
182497
182597
  existing.permissions.allow = [];
182498
182598
  }
182499
182599
  existing.permissions.allow.push(...missingPermissions);
182500
- fs2.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
182600
+ fs3.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
182501
182601
  return "modified";
182502
182602
  }
182503
- if (!fs2.existsSync(claudeDir)) {
182504
- fs2.mkdirSync(claudeDir);
182603
+ if (!fs3.existsSync(claudeDir)) {
182604
+ fs3.mkdirSync(claudeDir);
182505
182605
  }
182506
182606
  const settings = {
182507
182607
  permissions: {
182508
182608
  allow: permissions
182509
182609
  }
182510
182610
  };
182511
- fs2.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
182611
+ fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
182512
182612
  return "created";
182513
182613
  }
182514
182614
  function createCursorRule() {
182515
- const cursorDir = path2.join(process.cwd(), ".cursor");
182516
- const rulesDir = path2.join(cursorDir, "rules");
182517
- const mdcPath = path2.join(rulesDir, "specific.mdc");
182518
- if (fs2.existsSync(mdcPath)) {
182519
- const existing = fs2.readFileSync(mdcPath, "utf-8");
182615
+ const cursorDir = path3.join(process.cwd(), ".cursor");
182616
+ const rulesDir = path3.join(cursorDir, "rules");
182617
+ const mdcPath = path3.join(rulesDir, "specific.mdc");
182618
+ if (fs3.existsSync(mdcPath)) {
182619
+ const existing = fs3.readFileSync(mdcPath, "utf-8");
182520
182620
  if (existing.includes("specific docs") || existing.includes("specific check")) {
182521
182621
  return "unchanged";
182522
182622
  }
182523
- fs2.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182623
+ fs3.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182524
182624
  return "modified";
182525
182625
  }
182526
- if (!fs2.existsSync(cursorDir)) {
182527
- fs2.mkdirSync(cursorDir);
182626
+ if (!fs3.existsSync(cursorDir)) {
182627
+ fs3.mkdirSync(cursorDir);
182528
182628
  }
182529
- if (!fs2.existsSync(rulesDir)) {
182530
- fs2.mkdirSync(rulesDir);
182629
+ if (!fs3.existsSync(rulesDir)) {
182630
+ fs3.mkdirSync(rulesDir);
182531
182631
  }
182532
- fs2.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182632
+ fs3.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182533
182633
  return "created";
182534
182634
  }
182535
182635
  function configureAgents(checked) {
@@ -182545,7 +182645,7 @@ function configureAgents(checked) {
182545
182645
  agents.filesModified.push(".cursor/rules/specific.mdc");
182546
182646
  }
182547
182647
  if (checked["claude"]) {
182548
- const claudeMdPath = path2.join(process.cwd(), "CLAUDE.md");
182648
+ const claudeMdPath = path3.join(process.cwd(), "CLAUDE.md");
182549
182649
  const status = appendOrCreateFile(claudeMdPath, SPECIFIC_INSTRUCTIONS);
182550
182650
  if (status === "created") agents.filesCreated.push("CLAUDE.md");
182551
182651
  else if (status === "modified") agents.filesModified.push("CLAUDE.md");
@@ -182556,7 +182656,7 @@ function configureAgents(checked) {
182556
182656
  agents.filesModified.push(".claude/settings.local.json");
182557
182657
  }
182558
182658
  if (checked["codex"]) {
182559
- const agentsMdPath = path2.join(process.cwd(), "AGENTS.md");
182659
+ const agentsMdPath = path3.join(process.cwd(), "AGENTS.md");
182560
182660
  const status = appendOrCreateFile(agentsMdPath, SPECIFIC_INSTRUCTIONS);
182561
182661
  if (status === "created") agents.filesCreated.push("AGENTS.md");
182562
182662
  else if (status === "modified") agents.filesModified.push("AGENTS.md");
@@ -182632,6 +182732,9 @@ function InitUI() {
182632
182732
  } else if ((key.return || input === " ") && isSubmitFocused) {
182633
182733
  const configResult = configureAgents(checked);
182634
182734
  setResult(configResult);
182735
+ trackEvent("project_initialized", {
182736
+ agents: Object.keys(checked).filter((k) => checked[k])
182737
+ });
182635
182738
  setPhase("done");
182636
182739
  }
182637
182740
  });
@@ -182675,11 +182778,11 @@ function initCommand() {
182675
182778
  }
182676
182779
 
182677
182780
  // src/commands/docs.tsx
182678
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
182679
- import { join as join3, dirname } from "path";
182781
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
182782
+ import { join as join4, dirname } from "path";
182680
182783
  import { fileURLToPath } from "url";
182681
182784
  var __dirname = dirname(fileURLToPath(import.meta.url));
182682
- var docsDir = join3(__dirname, "docs");
182785
+ var docsDir = join4(__dirname, "docs");
182683
182786
  function docsCommand(path22) {
182684
182787
  const docPath = resolveDocPath(path22);
182685
182788
  if (!docPath) {
@@ -182690,20 +182793,20 @@ Run 'specific docs' to see available topics.`
182690
182793
  );
182691
182794
  process.exit(1);
182692
182795
  }
182693
- const content = readFileSync3(docPath, "utf-8");
182796
+ const content = readFileSync4(docPath, "utf-8");
182694
182797
  console.log(content);
182695
182798
  }
182696
182799
  function resolveDocPath(path22) {
182697
182800
  if (!path22) {
182698
- const indexPath2 = join3(docsDir, "index.md");
182699
- return existsSync3(indexPath2) ? indexPath2 : null;
182801
+ const indexPath2 = join4(docsDir, "index.md");
182802
+ return existsSync4(indexPath2) ? indexPath2 : null;
182700
182803
  }
182701
- const directPath = join3(docsDir, `${path22}.md`);
182702
- if (existsSync3(directPath)) {
182804
+ const directPath = join4(docsDir, `${path22}.md`);
182805
+ if (existsSync4(directPath)) {
182703
182806
  return directPath;
182704
182807
  }
182705
- const indexPath = join3(docsDir, path22, "index.md");
182706
- if (existsSync3(indexPath)) {
182808
+ const indexPath = join4(docsDir, path22, "index.md");
182809
+ if (existsSync4(indexPath)) {
182707
182810
  return indexPath;
182708
182811
  }
182709
182812
  return null;
@@ -182713,8 +182816,8 @@ function resolveDocPath(path22) {
182713
182816
  import React2, { useState as useState2, useEffect as useEffect2 } from "react";
182714
182817
  import { render as render2, Text as Text2, Box as Box2 } from "ink";
182715
182818
  import Spinner2 from "ink-spinner";
182716
- import * as fs3 from "fs";
182717
- import * as path3 from "path";
182819
+ import * as fs4 from "fs";
182820
+ import * as path4 from "path";
182718
182821
 
182719
182822
  // ../config/dist/parser.js
182720
182823
  var import_hcl2_json_parser = __toESM(require_dist(), 1);
@@ -183196,8 +183299,8 @@ function CheckUI() {
183196
183299
  const [state, setState] = useState2({ status: "loading" });
183197
183300
  useEffect2(() => {
183198
183301
  async function load() {
183199
- const configPath = path3.join(process.cwd(), "specific.hcl");
183200
- if (!fs3.existsSync(configPath)) {
183302
+ const configPath = path4.join(process.cwd(), "specific.hcl");
183303
+ if (!fs4.existsSync(configPath)) {
183201
183304
  setState({
183202
183305
  status: "error",
183203
183306
  error: "No specific.hcl found in current directory"
@@ -183205,7 +183308,7 @@ function CheckUI() {
183205
183308
  return;
183206
183309
  }
183207
183310
  try {
183208
- const hcl = fs3.readFileSync(configPath, "utf-8");
183311
+ const hcl = fs4.readFileSync(configPath, "utf-8");
183209
183312
  const config2 = await parseConfig(hcl);
183210
183313
  setState({ status: "success", config: config2 });
183211
183314
  } catch (err) {
@@ -183237,8 +183340,8 @@ function checkCommand() {
183237
183340
  import React3, { useState as useState3, useEffect as useEffect3, useRef } from "react";
183238
183341
  import { render as render3, Text as Text3, Box as Box3, useApp as useApp2, Static } from "ink";
183239
183342
  import Spinner3 from "ink-spinner";
183240
- import * as fs12 from "fs";
183241
- import * as path13 from "path";
183343
+ import * as fs13 from "fs";
183344
+ import * as path14 from "path";
183242
183345
 
183243
183346
  // node_modules/chokidar/index.js
183244
183347
  import { EventEmitter } from "node:events";
@@ -184992,8 +185095,8 @@ var PortAllocator = class {
184992
185095
  };
184993
185096
 
184994
185097
  // src/lib/dev/stable-port-allocator.ts
184995
- import * as fs4 from "fs";
184996
- import * as path4 from "path";
185098
+ import * as fs5 from "fs";
185099
+ import * as path5 from "path";
184997
185100
  var PORT_RANGE_START2 = 4e4;
184998
185101
  var PORT_RANGE_END2 = 49999;
184999
185102
  var StablePortAllocator = class {
@@ -185002,16 +185105,16 @@ var StablePortAllocator = class {
185002
185105
  savedPorts = {};
185003
185106
  usedPorts = /* @__PURE__ */ new Set();
185004
185107
  constructor(projectRoot, key = "default") {
185005
- this.portsDir = path4.join(projectRoot, ".specific", "keys", key);
185006
- this.portsFilePath = path4.join(this.portsDir, "ports.json");
185108
+ this.portsDir = path5.join(projectRoot, ".specific", "keys", key);
185109
+ this.portsFilePath = path5.join(this.portsDir, "ports.json");
185007
185110
  this.loadPorts();
185008
185111
  }
185009
185112
  loadPorts() {
185010
- if (!fs4.existsSync(this.portsFilePath)) {
185113
+ if (!fs5.existsSync(this.portsFilePath)) {
185011
185114
  return;
185012
185115
  }
185013
185116
  try {
185014
- const content = fs4.readFileSync(this.portsFilePath, "utf-8");
185117
+ const content = fs5.readFileSync(this.portsFilePath, "utf-8");
185015
185118
  const data = JSON.parse(content);
185016
185119
  if (data.version === 1 && data.ports) {
185017
185120
  this.savedPorts = data.ports;
@@ -185024,14 +185127,14 @@ var StablePortAllocator = class {
185024
185127
  }
185025
185128
  }
185026
185129
  savePorts() {
185027
- if (!fs4.existsSync(this.portsDir)) {
185028
- fs4.mkdirSync(this.portsDir, { recursive: true });
185130
+ if (!fs5.existsSync(this.portsDir)) {
185131
+ fs5.mkdirSync(this.portsDir, { recursive: true });
185029
185132
  }
185030
185133
  const data = {
185031
185134
  version: 1,
185032
185135
  ports: this.savedPorts
185033
185136
  };
185034
- fs4.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
185137
+ fs5.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
185035
185138
  }
185036
185139
  allocateRandom() {
185037
185140
  const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
@@ -185057,10 +185160,10 @@ var StablePortAllocator = class {
185057
185160
  };
185058
185161
 
185059
185162
  // src/lib/dev/database-manager.ts
185060
- import * as fs7 from "fs";
185061
- import * as path7 from "path";
185163
+ import * as fs8 from "fs";
185164
+ import * as path8 from "path";
185062
185165
  import * as net from "net";
185063
- import * as os3 from "os";
185166
+ import * as os4 from "os";
185064
185167
  import { spawn } from "child_process";
185065
185168
 
185066
185169
  // src/lib/bin/types.ts
@@ -185098,17 +185201,17 @@ var ExtractionError = class extends Error {
185098
185201
  };
185099
185202
 
185100
185203
  // src/lib/bin/manager.ts
185101
- import * as fs5 from "fs";
185102
- import * as path5 from "path";
185103
- import * as os2 from "os";
185204
+ import * as fs6 from "fs";
185205
+ import * as path6 from "path";
185206
+ import * as os3 from "os";
185104
185207
  import { createReadStream } from "fs";
185105
185208
  import { createTarExtractor, extractTo } from "tar-vern";
185106
185209
  function getBinBaseDir() {
185107
- return path5.join(os2.homedir(), ".specific", "bin");
185210
+ return path6.join(os3.homedir(), ".specific", "bin");
185108
185211
  }
185109
185212
  function getPlatformInfo() {
185110
- const platform5 = os2.platform();
185111
- const arch3 = os2.arch();
185213
+ const platform5 = os3.platform();
185214
+ const arch3 = os3.arch();
185112
185215
  if (platform5 !== "darwin" && platform5 !== "linux") {
185113
185216
  throw new Error(
185114
185217
  `Unsupported platform: ${platform5}. Only macOS and Linux are supported.`
@@ -185127,7 +185230,7 @@ function getPlatformInfo() {
185127
185230
  return { platform: platform5, arch: mappedArch };
185128
185231
  }
185129
185232
  function getBinaryDir(definition, version, platformInfo) {
185130
- return path5.join(
185233
+ return path6.join(
185131
185234
  getBinBaseDir(),
185132
185235
  definition.name,
185133
185236
  version,
@@ -185137,8 +185240,8 @@ function getBinaryDir(definition, version, platformInfo) {
185137
185240
  function isBinaryInstalled(definition, version, platformInfo) {
185138
185241
  const binDir = getBinaryDir(definition, version, platformInfo);
185139
185242
  for (const execPath of definition.executables) {
185140
- const fullPath = path5.join(binDir, execPath);
185141
- if (!fs5.existsSync(fullPath)) {
185243
+ const fullPath = path6.join(binDir, execPath);
185244
+ if (!fs6.existsSync(fullPath)) {
185142
185245
  return false;
185143
185246
  }
185144
185247
  }
@@ -185166,12 +185269,12 @@ async function downloadFile(url, destPath, onProgress) {
185166
185269
  10
185167
185270
  );
185168
185271
  let bytesDownloaded = 0;
185169
- const parentDir = path5.dirname(destPath);
185170
- if (!fs5.existsSync(parentDir)) {
185171
- fs5.mkdirSync(parentDir, { recursive: true });
185272
+ const parentDir = path6.dirname(destPath);
185273
+ if (!fs6.existsSync(parentDir)) {
185274
+ fs6.mkdirSync(parentDir, { recursive: true });
185172
185275
  }
185173
185276
  const partPath = destPath + ".part";
185174
- const fileStream = fs5.createWriteStream(partPath);
185277
+ const fileStream = fs6.createWriteStream(partPath);
185175
185278
  try {
185176
185279
  const reader = response.body.getReader();
185177
185280
  while (true) {
@@ -185194,12 +185297,12 @@ async function downloadFile(url, destPath, onProgress) {
185194
185297
  else resolve5();
185195
185298
  });
185196
185299
  });
185197
- fs5.renameSync(partPath, destPath);
185300
+ fs6.renameSync(partPath, destPath);
185198
185301
  } catch (error) {
185199
185302
  try {
185200
185303
  fileStream.close();
185201
- if (fs5.existsSync(partPath)) {
185202
- fs5.unlinkSync(partPath);
185304
+ if (fs6.existsSync(partPath)) {
185305
+ fs6.unlinkSync(partPath);
185203
185306
  }
185204
185307
  } catch {
185205
185308
  }
@@ -185208,8 +185311,8 @@ async function downloadFile(url, destPath, onProgress) {
185208
185311
  }
185209
185312
  async function extractTarball(archivePath, destDir, definition, onProgress) {
185210
185313
  onProgress?.({ phase: "extracting" });
185211
- if (!fs5.existsSync(destDir)) {
185212
- fs5.mkdirSync(destDir, { recursive: true });
185314
+ if (!fs6.existsSync(destDir)) {
185315
+ fs6.mkdirSync(destDir, { recursive: true });
185213
185316
  }
185214
185317
  try {
185215
185318
  const fileStream = createReadStream(archivePath);
@@ -185217,9 +185320,9 @@ async function extractTarball(archivePath, destDir, definition, onProgress) {
185217
185320
  await extractTo(entries, destDir);
185218
185321
  onProgress?.({ phase: "finalizing" });
185219
185322
  for (const execPath of definition.executables) {
185220
- const fullPath = path5.join(destDir, execPath);
185221
- if (fs5.existsSync(fullPath)) {
185222
- fs5.chmodSync(fullPath, 493);
185323
+ const fullPath = path6.join(destDir, execPath);
185324
+ if (fs6.existsSync(fullPath)) {
185325
+ fs6.chmodSync(fullPath, 493);
185223
185326
  }
185224
185327
  }
185225
185328
  } catch (error) {
@@ -185243,24 +185346,24 @@ async function ensureBinary(definition, version, onProgress) {
185243
185346
  `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
185244
185347
  );
185245
185348
  }
185246
- if (!fs5.existsSync(binDir)) {
185247
- fs5.mkdirSync(binDir, { recursive: true });
185349
+ if (!fs6.existsSync(binDir)) {
185350
+ fs6.mkdirSync(binDir, { recursive: true });
185248
185351
  }
185249
- const execPath = path5.join(binDir, definition.executables[0]);
185352
+ const execPath = path6.join(binDir, definition.executables[0]);
185250
185353
  await downloadFile(url, execPath, onProgress);
185251
- fs5.chmodSync(execPath, 493);
185354
+ fs6.chmodSync(execPath, 493);
185252
185355
  onProgress?.({ phase: "finalizing" });
185253
185356
  } else {
185254
- const downloadDir = path5.join(getBinBaseDir(), "downloads");
185357
+ const downloadDir = path6.join(getBinBaseDir(), "downloads");
185255
185358
  const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
185256
- const archivePath = path5.join(downloadDir, archiveName);
185359
+ const archivePath = path6.join(downloadDir, archiveName);
185257
185360
  try {
185258
185361
  await downloadFile(url, archivePath, onProgress);
185259
185362
  await extractTarball(archivePath, binDir, definition, onProgress);
185260
185363
  } finally {
185261
185364
  try {
185262
- if (fs5.existsSync(archivePath)) {
185263
- fs5.unlinkSync(archivePath);
185365
+ if (fs6.existsSync(archivePath)) {
185366
+ fs6.unlinkSync(archivePath);
185264
185367
  }
185265
185368
  } catch {
185266
185369
  }
@@ -185269,10 +185372,10 @@ async function ensureBinary(definition, version, onProgress) {
185269
185372
  }
185270
185373
  const executables = {};
185271
185374
  for (const execPath of definition.executables) {
185272
- const name = path5.basename(execPath);
185273
- executables[name] = path5.join(binDir, execPath);
185375
+ const name = path6.basename(execPath);
185376
+ executables[name] = path6.join(binDir, execPath);
185274
185377
  }
185275
- const libraryPath = definition.libraryDir ? path5.join(binDir, definition.libraryDir) : void 0;
185378
+ const libraryPath = definition.libraryDir ? path6.join(binDir, definition.libraryDir) : void 0;
185276
185379
  return {
185277
185380
  rootDir: binDir,
185278
185381
  version: resolvedVersion,
@@ -185372,17 +185475,17 @@ var drizzleGatewayBinary = {
185372
185475
  };
185373
185476
 
185374
185477
  // src/lib/dev/debug-logger.ts
185375
- import * as fs6 from "fs";
185376
- import * as path6 from "path";
185478
+ import * as fs7 from "fs";
185479
+ import * as path7 from "path";
185377
185480
  var DEBUG_LOG_PATH = ".specific/debug.log";
185378
185481
  var logStream = null;
185379
185482
  function getLogStream() {
185380
185483
  if (logStream) {
185381
185484
  return logStream;
185382
185485
  }
185383
- const logPath = path6.join(process.cwd(), DEBUG_LOG_PATH);
185384
- fs6.mkdirSync(path6.dirname(logPath), { recursive: true });
185385
- logStream = fs6.createWriteStream(logPath, { flags: "a" });
185486
+ const logPath = path7.join(process.cwd(), DEBUG_LOG_PATH);
185487
+ fs7.mkdirSync(path7.dirname(logPath), { recursive: true });
185488
+ logStream = fs7.createWriteStream(logPath, { flags: "a" });
185386
185489
  return logStream;
185387
185490
  }
185388
185491
  function writeLog(source, message) {
@@ -185425,7 +185528,7 @@ function getLibraryEnv(binary) {
185425
185528
  if (!binary.libraryPath) {
185426
185529
  return {};
185427
185530
  }
185428
- const platform5 = os3.platform();
185531
+ const platform5 = os4.platform();
185429
185532
  if (platform5 === "darwin") {
185430
185533
  return { DYLD_LIBRARY_PATH: binary.libraryPath };
185431
185534
  } else if (platform5 === "linux") {
@@ -185435,15 +185538,15 @@ function getLibraryEnv(binary) {
185435
185538
  }
185436
185539
  async function startPostgres(pg, port, dataDir, onProgress) {
185437
185540
  const binary = await ensureBinary(postgresBinary, void 0, onProgress);
185438
- const dbDataPath = path7.join(process.cwd(), dataDir, pg.name);
185541
+ const dbDataPath = path8.join(process.cwd(), dataDir, pg.name);
185439
185542
  const host = "127.0.0.1";
185440
185543
  const user = "postgres";
185441
185544
  const password = "postgres";
185442
185545
  const libraryEnv = getLibraryEnv(binary);
185443
185546
  const env2 = { ...process.env, ...libraryEnv };
185444
- const dataExists = fs7.existsSync(dbDataPath);
185547
+ const dataExists = fs8.existsSync(dbDataPath);
185445
185548
  if (!dataExists) {
185446
- fs7.mkdirSync(dbDataPath, { recursive: true });
185549
+ fs8.mkdirSync(dbDataPath, { recursive: true });
185447
185550
  await runCommand(
185448
185551
  binary.executables["initdb"],
185449
185552
  ["-D", dbDataPath, "-U", user, "--auth=trust", "--no-locale", "-E", "UTF8"],
@@ -185519,9 +185622,9 @@ async function startRedis(redis, port, onProgress) {
185519
185622
  }
185520
185623
  async function startStorage(storage, port, dataDir) {
185521
185624
  const S3rver = (await import("s3rver")).default;
185522
- const storageDataPath = path7.join(process.cwd(), dataDir, storage.name);
185523
- if (!fs7.existsSync(storageDataPath)) {
185524
- fs7.mkdirSync(storageDataPath, { recursive: true });
185625
+ const storageDataPath = path8.join(process.cwd(), dataDir, storage.name);
185626
+ if (!fs8.existsSync(storageDataPath)) {
185627
+ fs8.mkdirSync(storageDataPath, { recursive: true });
185525
185628
  }
185526
185629
  const host = "127.0.0.1";
185527
185630
  const accessKey = "S3RVER";
@@ -185655,9 +185758,9 @@ import { spawn as spawn2 } from "child_process";
185655
185758
  // src/lib/secrets/parser.ts
185656
185759
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
185657
185760
  import { readFile, writeFile, mkdir } from "fs/promises";
185658
- import { existsSync as existsSync8 } from "fs";
185659
- import * as path8 from "path";
185660
- import * as crypto from "crypto";
185761
+ import { existsSync as existsSync9 } from "fs";
185762
+ import * as path9 from "path";
185763
+ import * as crypto2 from "crypto";
185661
185764
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
185662
185765
  var SECRETS_FILE = "specific.secrets";
185663
185766
  var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
@@ -185675,7 +185778,7 @@ async function parseSecretsFile(content) {
185675
185778
  return secrets;
185676
185779
  }
185677
185780
  async function loadSecrets() {
185678
- if (!existsSync8(SECRETS_FILE)) {
185781
+ if (!existsSync9(SECRETS_FILE)) {
185679
185782
  return /* @__PURE__ */ new Map();
185680
185783
  }
185681
185784
  const content = await readFile(SECRETS_FILE, "utf-8");
@@ -185683,7 +185786,7 @@ async function loadSecrets() {
185683
185786
  }
185684
185787
  function generateRandomString(length = 64) {
185685
185788
  const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
185686
- const bytes = crypto.randomBytes(length);
185789
+ const bytes = crypto2.randomBytes(length);
185687
185790
  let result = "";
185688
185791
  for (let i = 0; i < length; i++) {
185689
185792
  result += chars[bytes[i] % chars.length];
@@ -185691,7 +185794,7 @@ function generateRandomString(length = 64) {
185691
185794
  return result;
185692
185795
  }
185693
185796
  async function loadGeneratedSecrets() {
185694
- if (!existsSync8(GENERATED_SECRETS_FILE)) {
185797
+ if (!existsSync9(GENERATED_SECRETS_FILE)) {
185695
185798
  return /* @__PURE__ */ new Map();
185696
185799
  }
185697
185800
  const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
@@ -185700,13 +185803,13 @@ async function loadGeneratedSecrets() {
185700
185803
  }
185701
185804
  async function saveGeneratedSecret(name, value) {
185702
185805
  let secrets = {};
185703
- if (existsSync8(GENERATED_SECRETS_FILE)) {
185806
+ if (existsSync9(GENERATED_SECRETS_FILE)) {
185704
185807
  const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
185705
185808
  secrets = JSON.parse(content);
185706
185809
  }
185707
185810
  secrets[name] = value;
185708
- const dir = path8.dirname(GENERATED_SECRETS_FILE);
185709
- if (!existsSync8(dir)) {
185811
+ const dir = path9.dirname(GENERATED_SECRETS_FILE);
185812
+ if (!existsSync9(dir)) {
185710
185813
  await mkdir(dir, { recursive: true });
185711
185814
  }
185712
185815
  await writeFile(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
@@ -185736,7 +185839,7 @@ async function prepareSecrets(secretsConfig) {
185736
185839
  // src/lib/config/parser.ts
185737
185840
  var import_hcl2_json_parser3 = __toESM(require_dist(), 1);
185738
185841
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
185739
- import { existsSync as existsSync9 } from "fs";
185842
+ import { existsSync as existsSync10 } from "fs";
185740
185843
  var { parseToObject: parseToObject3 } = import_hcl2_json_parser3.default;
185741
185844
  var CONFIG_FILE = "specific.config";
185742
185845
  async function parseConfigFile(content) {
@@ -185753,7 +185856,7 @@ async function parseConfigFile(content) {
185753
185856
  return configs;
185754
185857
  }
185755
185858
  async function loadConfigs() {
185756
- if (!existsSync9(CONFIG_FILE)) {
185859
+ if (!existsSync10(CONFIG_FILE)) {
185757
185860
  return /* @__PURE__ */ new Map();
185758
185861
  }
185759
185862
  const content = await readFile2(CONFIG_FILE, "utf-8");
@@ -186065,8 +186168,8 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
186065
186168
  }
186066
186169
 
186067
186170
  // src/lib/dev/instance-state.ts
186068
- import * as fs8 from "fs";
186069
- import * as path9 from "path";
186171
+ import * as fs9 from "fs";
186172
+ import * as path10 from "path";
186070
186173
  var InstanceStateManager = class {
186071
186174
  stateDir;
186072
186175
  statePath;
@@ -186075,16 +186178,16 @@ var InstanceStateManager = class {
186075
186178
  key;
186076
186179
  constructor(projectRoot, key = "default") {
186077
186180
  this.key = key;
186078
- this.stateDir = path9.join(projectRoot, ".specific", "keys", key);
186079
- this.statePath = path9.join(this.stateDir, "state.json");
186080
- this.lockPath = path9.join(this.stateDir, "state.lock");
186181
+ this.stateDir = path10.join(projectRoot, ".specific", "keys", key);
186182
+ this.statePath = path10.join(this.stateDir, "state.json");
186183
+ this.lockPath = path10.join(this.stateDir, "state.lock");
186081
186184
  }
186082
186185
  getKey() {
186083
186186
  return this.key;
186084
186187
  }
186085
186188
  ensureStateDir() {
186086
- if (!fs8.existsSync(this.stateDir)) {
186087
- fs8.mkdirSync(this.stateDir, { recursive: true });
186189
+ if (!fs9.existsSync(this.stateDir)) {
186190
+ fs9.mkdirSync(this.stateDir, { recursive: true });
186088
186191
  }
186089
186192
  }
186090
186193
  isProcessRunning(pid) {
@@ -186101,15 +186204,15 @@ var InstanceStateManager = class {
186101
186204
  const startTime = Date.now();
186102
186205
  while (Date.now() - startTime < timeoutMs) {
186103
186206
  try {
186104
- const fd = fs8.openSync(
186207
+ const fd = fs9.openSync(
186105
186208
  this.lockPath,
186106
- fs8.constants.O_CREAT | fs8.constants.O_EXCL | fs8.constants.O_WRONLY
186209
+ fs9.constants.O_CREAT | fs9.constants.O_EXCL | fs9.constants.O_WRONLY
186107
186210
  );
186108
- fs8.writeSync(fd, String(process.pid));
186109
- fs8.closeSync(fd);
186211
+ fs9.writeSync(fd, String(process.pid));
186212
+ fs9.closeSync(fd);
186110
186213
  return () => {
186111
186214
  try {
186112
- fs8.unlinkSync(this.lockPath);
186215
+ fs9.unlinkSync(this.lockPath);
186113
186216
  } catch {
186114
186217
  }
186115
186218
  };
@@ -186118,16 +186221,16 @@ var InstanceStateManager = class {
186118
186221
  if (err.code === "EEXIST") {
186119
186222
  try {
186120
186223
  const lockPid = parseInt(
186121
- fs8.readFileSync(this.lockPath, "utf-8").trim(),
186224
+ fs9.readFileSync(this.lockPath, "utf-8").trim(),
186122
186225
  10
186123
186226
  );
186124
186227
  if (!this.isProcessRunning(lockPid)) {
186125
- fs8.unlinkSync(this.lockPath);
186228
+ fs9.unlinkSync(this.lockPath);
186126
186229
  continue;
186127
186230
  }
186128
186231
  } catch {
186129
186232
  try {
186130
- fs8.unlinkSync(this.lockPath);
186233
+ fs9.unlinkSync(this.lockPath);
186131
186234
  } catch {
186132
186235
  }
186133
186236
  continue;
@@ -186141,12 +186244,12 @@ var InstanceStateManager = class {
186141
186244
  throw new Error("Failed to acquire state lock (timeout)");
186142
186245
  }
186143
186246
  async getExistingInstances() {
186144
- if (!fs8.existsSync(this.statePath)) {
186247
+ if (!fs9.existsSync(this.statePath)) {
186145
186248
  return null;
186146
186249
  }
186147
186250
  const releaseLock = await this.acquireLock();
186148
186251
  try {
186149
- const content = fs8.readFileSync(this.statePath, "utf-8");
186252
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186150
186253
  const state = JSON.parse(content);
186151
186254
  if (!this.isProcessRunning(state.owner.pid)) {
186152
186255
  return null;
@@ -186159,21 +186262,21 @@ var InstanceStateManager = class {
186159
186262
  }
186160
186263
  }
186161
186264
  async cleanStaleState() {
186162
- if (!fs8.existsSync(this.statePath)) {
186265
+ if (!fs9.existsSync(this.statePath)) {
186163
186266
  return false;
186164
186267
  }
186165
186268
  const releaseLock = await this.acquireLock();
186166
186269
  try {
186167
- const content = fs8.readFileSync(this.statePath, "utf-8");
186270
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186168
186271
  const state = JSON.parse(content);
186169
186272
  if (!this.isProcessRunning(state.owner.pid)) {
186170
- fs8.unlinkSync(this.statePath);
186273
+ fs9.unlinkSync(this.statePath);
186171
186274
  return true;
186172
186275
  }
186173
186276
  return false;
186174
186277
  } catch {
186175
186278
  try {
186176
- fs8.unlinkSync(this.statePath);
186279
+ fs9.unlinkSync(this.statePath);
186177
186280
  return true;
186178
186281
  } catch {
186179
186282
  }
@@ -186185,8 +186288,8 @@ var InstanceStateManager = class {
186185
186288
  async claimOwnership(command) {
186186
186289
  const releaseLock = await this.acquireLock();
186187
186290
  try {
186188
- if (fs8.existsSync(this.statePath)) {
186189
- const content = fs8.readFileSync(this.statePath, "utf-8");
186291
+ if (fs9.existsSync(this.statePath)) {
186292
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186190
186293
  const state2 = JSON.parse(content);
186191
186294
  if (this.isProcessRunning(state2.owner.pid)) {
186192
186295
  throw new Error(`Instances already owned by PID ${state2.owner.pid}`);
@@ -186255,8 +186358,8 @@ var InstanceStateManager = class {
186255
186358
  }
186256
186359
  const releaseLock = await this.acquireLock();
186257
186360
  try {
186258
- if (fs8.existsSync(this.statePath)) {
186259
- fs8.unlinkSync(this.statePath);
186361
+ if (fs9.existsSync(this.statePath)) {
186362
+ fs9.unlinkSync(this.statePath);
186260
186363
  }
186261
186364
  this.ownsInstances = false;
186262
186365
  } finally {
@@ -186264,26 +186367,26 @@ var InstanceStateManager = class {
186264
186367
  }
186265
186368
  }
186266
186369
  readState() {
186267
- const content = fs8.readFileSync(this.statePath, "utf-8");
186370
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186268
186371
  return JSON.parse(content);
186269
186372
  }
186270
186373
  writeStateAtomic(state) {
186271
186374
  this.ensureStateDir();
186272
186375
  const tmpPath = this.statePath + ".tmp";
186273
- fs8.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
186274
- fs8.renameSync(tmpPath, this.statePath);
186376
+ fs9.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
186377
+ fs9.renameSync(tmpPath, this.statePath);
186275
186378
  }
186276
186379
  };
186277
186380
 
186278
186381
  // src/lib/dev/http-proxy.ts
186279
186382
  import * as http from "http";
186280
186383
  import * as https from "https";
186281
- import * as fs9 from "fs";
186282
- import * as path10 from "path";
186384
+ import * as fs10 from "fs";
186385
+ import * as path11 from "path";
186283
186386
  import { fileURLToPath as fileURLToPath2 } from "url";
186284
186387
  import httpProxy from "http-proxy";
186285
- var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
186286
- var adminDir = path10.join(__dirname2, "admin");
186388
+ var __dirname2 = path11.dirname(fileURLToPath2(import.meta.url));
186389
+ var adminDir = path11.join(__dirname2, "admin");
186287
186390
  var HTTP_PORT = 80;
186288
186391
  var HTTPS_PORT = 443;
186289
186392
  var DOMAIN_SUFFIX = ".local.spcf.app";
@@ -186340,7 +186443,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
186340
186443
  });
186341
186444
  const handleRequest = (req, res) => {
186342
186445
  const host = req.headers.host || "";
186343
- const hostname = host.split(":")[0];
186446
+ const hostname2 = host.split(":")[0];
186344
186447
  const drizzleKey = extractDrizzleGatewayKey(host);
186345
186448
  if (drizzleKey !== null) {
186346
186449
  const drizzlePort = drizzleGatewayPortMap.get(drizzleKey);
@@ -186366,7 +186469,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
186366
186469
  serveStaticFile(res, url.pathname);
186367
186470
  return;
186368
186471
  }
186369
- if (adminKey === "default" && hostname === ADMIN_DOMAIN) {
186472
+ if (adminKey === "default" && hostname2 === ADMIN_DOMAIN) {
186370
186473
  res.writeHead(503, { "Content-Type": "text/html" });
186371
186474
  res.end("<h1>Admin UI</h1><p>No dev instance running. Start <code>specific dev</code> first.</p>");
186372
186475
  return;
@@ -186487,11 +186590,11 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
186487
186590
  });
186488
186591
  }
186489
186592
  function extractServiceAndKey(host) {
186490
- const hostname = host.split(":")[0];
186491
- if (!hostname || !hostname.endsWith(DOMAIN_SUFFIX)) {
186593
+ const hostname2 = host.split(":")[0];
186594
+ if (!hostname2 || !hostname2.endsWith(DOMAIN_SUFFIX)) {
186492
186595
  return null;
186493
186596
  }
186494
- const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186597
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186495
186598
  if (!prefix) {
186496
186599
  return null;
186497
186600
  }
@@ -186504,11 +186607,11 @@ function extractServiceAndKey(host) {
186504
186607
  return null;
186505
186608
  }
186506
186609
  function extractDrizzleGatewayKey(host) {
186507
- const hostname = host.split(":")[0];
186508
- if (!hostname || !hostname.endsWith(DOMAIN_SUFFIX)) {
186610
+ const hostname2 = host.split(":")[0];
186611
+ if (!hostname2 || !hostname2.endsWith(DOMAIN_SUFFIX)) {
186509
186612
  return null;
186510
186613
  }
186511
- const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186614
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186512
186615
  if (!prefix) {
186513
186616
  return null;
186514
186617
  }
@@ -186521,17 +186624,17 @@ function extractDrizzleGatewayKey(host) {
186521
186624
  return null;
186522
186625
  }
186523
186626
  function extractAdminKey(host) {
186524
- const hostname = host.split(":")[0];
186525
- if (!hostname) {
186627
+ const hostname2 = host.split(":")[0];
186628
+ if (!hostname2) {
186526
186629
  return null;
186527
186630
  }
186528
- if (hostname === ADMIN_DOMAIN) {
186631
+ if (hostname2 === ADMIN_DOMAIN) {
186529
186632
  return "default";
186530
186633
  }
186531
- if (!hostname.endsWith(DOMAIN_SUFFIX)) {
186634
+ if (!hostname2.endsWith(DOMAIN_SUFFIX)) {
186532
186635
  return null;
186533
186636
  }
186534
- const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186637
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186535
186638
  if (!prefix) {
186536
186639
  return null;
186537
186640
  }
@@ -186569,25 +186672,25 @@ function serveStaticFile(res, pathname) {
186569
186672
  filePath = filePath + "index.html";
186570
186673
  }
186571
186674
  const relativePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
186572
- const fullPath = path10.join(adminDir, relativePath);
186573
- const resolvedPath = path10.resolve(fullPath);
186574
- const resolvedAdminDir = path10.resolve(adminDir);
186675
+ const fullPath = path11.join(adminDir, relativePath);
186676
+ const resolvedPath = path11.resolve(fullPath);
186677
+ const resolvedAdminDir = path11.resolve(adminDir);
186575
186678
  if (!resolvedPath.startsWith(resolvedAdminDir)) {
186576
186679
  res.writeHead(403, { "Content-Type": "text/html" });
186577
186680
  res.end("<h1>Forbidden</h1>");
186578
186681
  return;
186579
186682
  }
186580
- if (!fs9.existsSync(resolvedPath)) {
186683
+ if (!fs10.existsSync(resolvedPath)) {
186581
186684
  const htmlPath = resolvedPath + ".html";
186582
- if (fs9.existsSync(htmlPath)) {
186685
+ if (fs10.existsSync(htmlPath)) {
186583
186686
  return serveFile(res, htmlPath);
186584
186687
  }
186585
- const indexPath = path10.join(resolvedPath, "index.html");
186586
- if (fs9.existsSync(indexPath)) {
186688
+ const indexPath = path11.join(resolvedPath, "index.html");
186689
+ if (fs10.existsSync(indexPath)) {
186587
186690
  return serveFile(res, indexPath);
186588
186691
  }
186589
- const notFoundPath = path10.join(adminDir, "404.html");
186590
- if (fs9.existsSync(notFoundPath)) {
186692
+ const notFoundPath = path11.join(adminDir, "404.html");
186693
+ if (fs10.existsSync(notFoundPath)) {
186591
186694
  return serveFileContent(res, notFoundPath, "text/html", 404);
186592
186695
  }
186593
186696
  res.writeHead(404, { "Content-Type": "text/html" });
@@ -186597,13 +186700,13 @@ function serveStaticFile(res, pathname) {
186597
186700
  serveFile(res, resolvedPath);
186598
186701
  }
186599
186702
  function serveFile(res, filePath) {
186600
- const ext = path10.extname(filePath).toLowerCase();
186703
+ const ext = path11.extname(filePath).toLowerCase();
186601
186704
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
186602
186705
  serveFileContent(res, filePath, contentType, 200);
186603
186706
  }
186604
186707
  function serveFileContent(res, filePath, contentType, statusCode = 200) {
186605
186708
  try {
186606
- const content = fs9.readFileSync(filePath);
186709
+ const content = fs10.readFileSync(filePath);
186607
186710
  res.writeHead(statusCode, { "Content-Type": contentType });
186608
186711
  res.end(content);
186609
186712
  } catch (err) {
@@ -186755,8 +186858,8 @@ function sleep2(ms) {
186755
186858
 
186756
186859
  // src/lib/dev/drizzle-gateway-manager.ts
186757
186860
  import * as net3 from "net";
186758
- import * as fs10 from "fs";
186759
- import * as path11 from "path";
186861
+ import * as fs11 from "fs";
186862
+ import * as path12 from "path";
186760
186863
  import { spawn as spawn4 } from "child_process";
186761
186864
  import { randomUUID } from "crypto";
186762
186865
  function generateStoreJson(postgresInstances) {
@@ -186788,13 +186891,13 @@ async function startDrizzleGateway(postgresInstances, port, configDir, options2)
186788
186891
  options2?.onProgress
186789
186892
  );
186790
186893
  const host = "127.0.0.1";
186791
- const drizzleConfigDir = path11.join(configDir, "drizzle-gateway");
186792
- if (!fs10.existsSync(drizzleConfigDir)) {
186793
- fs10.mkdirSync(drizzleConfigDir, { recursive: true });
186894
+ const drizzleConfigDir = path12.join(configDir, "drizzle-gateway");
186895
+ if (!fs11.existsSync(drizzleConfigDir)) {
186896
+ fs11.mkdirSync(drizzleConfigDir, { recursive: true });
186794
186897
  }
186795
186898
  const storeJson = generateStoreJson(postgresInstances);
186796
- const storeJsonPath = path11.join(drizzleConfigDir, "store.json");
186797
- fs10.writeFileSync(storeJsonPath, JSON.stringify(storeJson, null, 2));
186899
+ const storeJsonPath = path12.join(drizzleConfigDir, "store.json");
186900
+ fs11.writeFileSync(storeJsonPath, JSON.stringify(storeJson, null, 2));
186798
186901
  writeLog("drizzle-gateway", `Starting Drizzle Gateway`);
186799
186902
  writeLog("drizzle-gateway", `STORE_PATH: ${drizzleConfigDir}`);
186800
186903
  writeLog("drizzle-gateway", `PORT: ${port}`);
@@ -187063,9 +187166,9 @@ function watchConfigFile(configPath, debounceMs, onChange) {
187063
187166
  }
187064
187167
 
187065
187168
  // src/lib/dev/proxy-registry.ts
187066
- import * as fs11 from "fs";
187067
- import * as path12 from "path";
187068
- import * as os4 from "os";
187169
+ import * as fs12 from "fs";
187170
+ import * as path13 from "path";
187171
+ import * as os5 from "os";
187069
187172
  var ProxyRegistryManager = class {
187070
187173
  proxyDir;
187071
187174
  ownerPath;
@@ -187074,14 +187177,14 @@ var ProxyRegistryManager = class {
187074
187177
  isOwner = false;
187075
187178
  registryWatcher = null;
187076
187179
  constructor() {
187077
- this.proxyDir = path12.join(os4.homedir(), ".specific", "proxy");
187078
- this.ownerPath = path12.join(this.proxyDir, "owner.json");
187079
- this.registryPath = path12.join(this.proxyDir, "registry.json");
187080
- this.lockPath = path12.join(this.proxyDir, "registry.lock");
187180
+ this.proxyDir = path13.join(os5.homedir(), ".specific", "proxy");
187181
+ this.ownerPath = path13.join(this.proxyDir, "owner.json");
187182
+ this.registryPath = path13.join(this.proxyDir, "registry.json");
187183
+ this.lockPath = path13.join(this.proxyDir, "registry.lock");
187081
187184
  }
187082
187185
  ensureProxyDir() {
187083
- if (!fs11.existsSync(this.proxyDir)) {
187084
- fs11.mkdirSync(this.proxyDir, { recursive: true });
187186
+ if (!fs12.existsSync(this.proxyDir)) {
187187
+ fs12.mkdirSync(this.proxyDir, { recursive: true });
187085
187188
  }
187086
187189
  }
187087
187190
  isProcessRunning(pid) {
@@ -187098,15 +187201,15 @@ var ProxyRegistryManager = class {
187098
187201
  const startTime = Date.now();
187099
187202
  while (Date.now() - startTime < timeoutMs) {
187100
187203
  try {
187101
- const fd = fs11.openSync(
187204
+ const fd = fs12.openSync(
187102
187205
  this.lockPath,
187103
- fs11.constants.O_CREAT | fs11.constants.O_EXCL | fs11.constants.O_WRONLY
187206
+ fs12.constants.O_CREAT | fs12.constants.O_EXCL | fs12.constants.O_WRONLY
187104
187207
  );
187105
- fs11.writeSync(fd, String(process.pid));
187106
- fs11.closeSync(fd);
187208
+ fs12.writeSync(fd, String(process.pid));
187209
+ fs12.closeSync(fd);
187107
187210
  return () => {
187108
187211
  try {
187109
- fs11.unlinkSync(this.lockPath);
187212
+ fs12.unlinkSync(this.lockPath);
187110
187213
  } catch {
187111
187214
  }
187112
187215
  };
@@ -187115,16 +187218,16 @@ var ProxyRegistryManager = class {
187115
187218
  if (err.code === "EEXIST") {
187116
187219
  try {
187117
187220
  const lockPid = parseInt(
187118
- fs11.readFileSync(this.lockPath, "utf-8").trim(),
187221
+ fs12.readFileSync(this.lockPath, "utf-8").trim(),
187119
187222
  10
187120
187223
  );
187121
187224
  if (!this.isProcessRunning(lockPid)) {
187122
- fs11.unlinkSync(this.lockPath);
187225
+ fs12.unlinkSync(this.lockPath);
187123
187226
  continue;
187124
187227
  }
187125
187228
  } catch {
187126
187229
  try {
187127
- fs11.unlinkSync(this.lockPath);
187230
+ fs12.unlinkSync(this.lockPath);
187128
187231
  } catch {
187129
187232
  }
187130
187233
  continue;
@@ -187144,8 +187247,8 @@ var ProxyRegistryManager = class {
187144
187247
  async claimProxyOwnership(key) {
187145
187248
  const releaseLock = await this.acquireLock();
187146
187249
  try {
187147
- if (fs11.existsSync(this.ownerPath)) {
187148
- const content = fs11.readFileSync(this.ownerPath, "utf-8");
187250
+ if (fs12.existsSync(this.ownerPath)) {
187251
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187149
187252
  const ownerFile2 = JSON.parse(content);
187150
187253
  if (this.isProcessRunning(ownerFile2.owner.pid)) {
187151
187254
  return false;
@@ -187175,11 +187278,11 @@ var ProxyRegistryManager = class {
187175
187278
  }
187176
187279
  const releaseLock = await this.acquireLock();
187177
187280
  try {
187178
- if (fs11.existsSync(this.ownerPath)) {
187179
- const content = fs11.readFileSync(this.ownerPath, "utf-8");
187281
+ if (fs12.existsSync(this.ownerPath)) {
187282
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187180
187283
  const ownerFile = JSON.parse(content);
187181
187284
  if (ownerFile.owner.pid === process.pid) {
187182
- fs11.unlinkSync(this.ownerPath);
187285
+ fs12.unlinkSync(this.ownerPath);
187183
187286
  }
187184
187287
  }
187185
187288
  this.isOwner = false;
@@ -187191,12 +187294,12 @@ var ProxyRegistryManager = class {
187191
187294
  * Get the current proxy owner.
187192
187295
  */
187193
187296
  async getProxyOwner() {
187194
- if (!fs11.existsSync(this.ownerPath)) {
187297
+ if (!fs12.existsSync(this.ownerPath)) {
187195
187298
  return null;
187196
187299
  }
187197
187300
  const releaseLock = await this.acquireLock();
187198
187301
  try {
187199
- const content = fs11.readFileSync(this.ownerPath, "utf-8");
187302
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187200
187303
  const ownerFile = JSON.parse(content);
187201
187304
  if (!this.isProcessRunning(ownerFile.owner.pid)) {
187202
187305
  return null;
@@ -187290,7 +187393,7 @@ var ProxyRegistryManager = class {
187290
187393
  */
187291
187394
  watchRegistry(onChange) {
187292
187395
  this.ensureProxyDir();
187293
- if (!fs11.existsSync(this.registryPath)) {
187396
+ if (!fs12.existsSync(this.registryPath)) {
187294
187397
  const emptyRegistry = {
187295
187398
  version: 1,
187296
187399
  keys: {},
@@ -187334,13 +187437,13 @@ var ProxyRegistryManager = class {
187334
187437
  async attemptElection(key) {
187335
187438
  const releaseLock = await this.acquireLock();
187336
187439
  try {
187337
- if (fs11.existsSync(this.ownerPath)) {
187338
- const content = fs11.readFileSync(this.ownerPath, "utf-8");
187440
+ if (fs12.existsSync(this.ownerPath)) {
187441
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187339
187442
  const ownerFile2 = JSON.parse(content);
187340
187443
  if (this.isProcessRunning(ownerFile2.owner.pid)) {
187341
187444
  return false;
187342
187445
  }
187343
- fs11.unlinkSync(this.ownerPath);
187446
+ fs12.unlinkSync(this.ownerPath);
187344
187447
  }
187345
187448
  const ownerFile = {
187346
187449
  version: 1,
@@ -187358,7 +187461,7 @@ var ProxyRegistryManager = class {
187358
187461
  }
187359
187462
  }
187360
187463
  readRegistry() {
187361
- if (!fs11.existsSync(this.registryPath)) {
187464
+ if (!fs12.existsSync(this.registryPath)) {
187362
187465
  return {
187363
187466
  version: 1,
187364
187467
  keys: {},
@@ -187366,7 +187469,7 @@ var ProxyRegistryManager = class {
187366
187469
  };
187367
187470
  }
187368
187471
  try {
187369
- const content = fs11.readFileSync(this.registryPath, "utf-8");
187472
+ const content = fs12.readFileSync(this.registryPath, "utf-8");
187370
187473
  return JSON.parse(content);
187371
187474
  } catch {
187372
187475
  return {
@@ -187379,8 +187482,8 @@ var ProxyRegistryManager = class {
187379
187482
  writeFileAtomic(filePath, data) {
187380
187483
  this.ensureProxyDir();
187381
187484
  const tmpPath = filePath + ".tmp";
187382
- fs11.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
187383
- fs11.renameSync(tmpPath, filePath);
187485
+ fs12.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
187486
+ fs12.renameSync(tmpPath, filePath);
187384
187487
  }
187385
187488
  };
187386
187489
 
@@ -187423,6 +187526,7 @@ function DevUI({ instanceKey }) {
187423
187526
  }
187424
187527
  }
187425
187528
  const shuttingDown = useRef(false);
187529
+ const startTimeRef = useRef(null);
187426
187530
  const stateManagerRef = useRef(null);
187427
187531
  const proxyRegistryRef = useRef(null);
187428
187532
  const electricInstancesRef = useRef([]);
@@ -187435,9 +187539,11 @@ function DevUI({ instanceKey }) {
187435
187539
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
187436
187540
  const [reloadTrigger, setReloadTrigger] = useState3(0);
187437
187541
  const [readyToStart, setReadyToStart] = useState3(() => caFilesExist());
187438
- const shutdown = async () => {
187542
+ const shutdown2 = async () => {
187439
187543
  if (shuttingDown.current) return;
187440
187544
  shuttingDown.current = true;
187545
+ const duration = startTimeRef.current ? Math.round((Date.now() - startTimeRef.current) / 1e3) : void 0;
187546
+ trackEvent("dev_stopped", { duration_seconds: duration });
187441
187547
  writeLog("system", "Shutting down");
187442
187548
  setState((s) => ({ ...s, status: "stopping" }));
187443
187549
  if (electionIntervalRef.current) {
@@ -187544,7 +187650,7 @@ function DevUI({ instanceKey }) {
187544
187650
  }
187545
187651
  process.exit(1);
187546
187652
  }
187547
- shutdown();
187653
+ shutdown2();
187548
187654
  };
187549
187655
  process.on("SIGINT", handleSignal);
187550
187656
  process.on("SIGTERM", handleSignal);
@@ -187553,12 +187659,18 @@ function DevUI({ instanceKey }) {
187553
187659
  process.off("SIGTERM", handleSignal);
187554
187660
  };
187555
187661
  }, []);
187662
+ useEffect3(() => {
187663
+ if (state.status === "running" && !startTimeRef.current) {
187664
+ startTimeRef.current = Date.now();
187665
+ trackEvent("dev_started");
187666
+ }
187667
+ }, [state.status]);
187556
187668
  useEffect3(() => {
187557
187669
  if (state.status !== "running") return;
187558
- const configPath = path13.join(process.cwd(), "specific.hcl");
187670
+ const configPath = path14.join(process.cwd(), "specific.hcl");
187559
187671
  const watcher = watchConfigFile(configPath, 1e3, () => {
187560
187672
  try {
187561
- const hcl = fs12.readFileSync(configPath, "utf-8");
187673
+ const hcl = fs13.readFileSync(configPath, "utf-8");
187562
187674
  parseConfig(hcl).then(() => {
187563
187675
  triggerReload();
187564
187676
  }).catch((err) => {
@@ -187624,8 +187736,8 @@ function DevUI({ instanceKey }) {
187624
187736
  }));
187625
187737
  return;
187626
187738
  }
187627
- const configPath = path13.join(process.cwd(), "specific.hcl");
187628
- if (!fs12.existsSync(configPath)) {
187739
+ const configPath = path14.join(process.cwd(), "specific.hcl");
187740
+ if (!fs13.existsSync(configPath)) {
187629
187741
  writeLog("system", "Waiting for specific.hcl to appear");
187630
187742
  setState((s) => ({
187631
187743
  ...s,
@@ -187644,7 +187756,7 @@ function DevUI({ instanceKey }) {
187644
187756
  }
187645
187757
  let config2;
187646
187758
  try {
187647
- const hcl = fs12.readFileSync(configPath, "utf-8");
187759
+ const hcl = fs13.readFileSync(configPath, "utf-8");
187648
187760
  config2 = await parseConfig(hcl);
187649
187761
  } catch (err) {
187650
187762
  setState((s) => ({
@@ -187750,7 +187862,7 @@ function DevUI({ instanceKey }) {
187750
187862
  const drizzleGateway = await startDrizzleGateway(
187751
187863
  postgresResources,
187752
187864
  drizzlePort,
187753
- path13.join(process.cwd(), ".specific", "keys", instanceKey)
187865
+ path14.join(process.cwd(), ".specific", "keys", instanceKey)
187754
187866
  );
187755
187867
  startedDrizzleGateway = drizzleGateway;
187756
187868
  drizzleGatewayRef.current = drizzleGateway;
@@ -188211,7 +188323,7 @@ function devCommand(instanceKey) {
188211
188323
 
188212
188324
  // src/lib/dev/git-worktree.ts
188213
188325
  import { execSync as execSync2 } from "child_process";
188214
- import * as path14 from "path";
188326
+ import * as path15 from "path";
188215
188327
  function isInWorktree() {
188216
188328
  try {
188217
188329
  const commonDir = execSync2("git rev-parse --git-common-dir", {
@@ -188222,8 +188334,8 @@ function isInWorktree() {
188222
188334
  encoding: "utf-8",
188223
188335
  stdio: ["pipe", "pipe", "pipe"]
188224
188336
  }).trim();
188225
- const resolvedCommonDir = path14.resolve(commonDir);
188226
- const resolvedGitDir = path14.resolve(gitDir);
188337
+ const resolvedCommonDir = path15.resolve(commonDir);
188338
+ const resolvedGitDir = path15.resolve(gitDir);
188227
188339
  return resolvedCommonDir !== resolvedGitDir;
188228
188340
  } catch {
188229
188341
  return false;
@@ -188238,7 +188350,7 @@ function getWorktreeName() {
188238
188350
  encoding: "utf-8",
188239
188351
  stdio: ["pipe", "pipe", "pipe"]
188240
188352
  }).trim();
188241
- return path14.basename(gitDir);
188353
+ return path15.basename(gitDir);
188242
188354
  } catch {
188243
188355
  return null;
188244
188356
  }
@@ -188255,30 +188367,30 @@ import Spinner5 from "ink-spinner";
188255
188367
 
188256
188368
  // ../../node_modules/open/index.js
188257
188369
  import process8 from "node:process";
188258
- import path15 from "node:path";
188370
+ import path16 from "node:path";
188259
188371
  import { fileURLToPath as fileURLToPath3 } from "node:url";
188260
188372
  import childProcess3 from "node:child_process";
188261
- import fs17, { constants as fsConstants2 } from "node:fs/promises";
188373
+ import fs18, { constants as fsConstants2 } from "node:fs/promises";
188262
188374
 
188263
188375
  // ../../node_modules/wsl-utils/index.js
188264
188376
  import { promisify as promisify2 } from "node:util";
188265
188377
  import childProcess2 from "node:child_process";
188266
- import fs16, { constants as fsConstants } from "node:fs/promises";
188378
+ import fs17, { constants as fsConstants } from "node:fs/promises";
188267
188379
 
188268
188380
  // ../../node_modules/is-wsl/index.js
188269
188381
  import process2 from "node:process";
188270
- import os5 from "node:os";
188271
- import fs15 from "node:fs";
188382
+ import os6 from "node:os";
188383
+ import fs16 from "node:fs";
188272
188384
 
188273
188385
  // ../../node_modules/is-inside-container/index.js
188274
- import fs14 from "node:fs";
188386
+ import fs15 from "node:fs";
188275
188387
 
188276
188388
  // ../../node_modules/is-docker/index.js
188277
- import fs13 from "node:fs";
188389
+ import fs14 from "node:fs";
188278
188390
  var isDockerCached;
188279
188391
  function hasDockerEnv() {
188280
188392
  try {
188281
- fs13.statSync("/.dockerenv");
188393
+ fs14.statSync("/.dockerenv");
188282
188394
  return true;
188283
188395
  } catch {
188284
188396
  return false;
@@ -188286,7 +188398,7 @@ function hasDockerEnv() {
188286
188398
  }
188287
188399
  function hasDockerCGroup() {
188288
188400
  try {
188289
- return fs13.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
188401
+ return fs14.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
188290
188402
  } catch {
188291
188403
  return false;
188292
188404
  }
@@ -188302,7 +188414,7 @@ function isDocker() {
188302
188414
  var cachedResult;
188303
188415
  var hasContainerEnv = () => {
188304
188416
  try {
188305
- fs14.statSync("/run/.containerenv");
188417
+ fs15.statSync("/run/.containerenv");
188306
188418
  return true;
188307
188419
  } catch {
188308
188420
  return false;
@@ -188320,14 +188432,14 @@ var isWsl = () => {
188320
188432
  if (process2.platform !== "linux") {
188321
188433
  return false;
188322
188434
  }
188323
- if (os5.release().toLowerCase().includes("microsoft")) {
188435
+ if (os6.release().toLowerCase().includes("microsoft")) {
188324
188436
  if (isInsideContainer()) {
188325
188437
  return false;
188326
188438
  }
188327
188439
  return true;
188328
188440
  }
188329
188441
  try {
188330
- return fs15.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
188442
+ return fs16.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
188331
188443
  } catch {
188332
188444
  return false;
188333
188445
  }
@@ -188395,14 +188507,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
188395
188507
  const configFilePath = "/etc/wsl.conf";
188396
188508
  let isConfigFileExists = false;
188397
188509
  try {
188398
- await fs16.access(configFilePath, fsConstants.F_OK);
188510
+ await fs17.access(configFilePath, fsConstants.F_OK);
188399
188511
  isConfigFileExists = true;
188400
188512
  } catch {
188401
188513
  }
188402
188514
  if (!isConfigFileExists) {
188403
188515
  return defaultMountPoint;
188404
188516
  }
188405
- const configContent = await fs16.readFile(configFilePath, { encoding: "utf8" });
188517
+ const configContent = await fs17.readFile(configFilePath, { encoding: "utf8" });
188406
188518
  const parsedMountPoint = parseMountPointFromConfig(configContent);
188407
188519
  if (parsedMountPoint === void 0) {
188408
188520
  return defaultMountPoint;
@@ -188422,7 +188534,7 @@ var canAccessPowerShell = async () => {
188422
188534
  canAccessPowerShellPromise ??= (async () => {
188423
188535
  try {
188424
188536
  const psPath = await powerShellPath2();
188425
- await fs16.access(psPath, fsConstants.X_OK);
188537
+ await fs17.access(psPath, fsConstants.X_OK);
188426
188538
  return true;
188427
188539
  } catch {
188428
188540
  return false;
@@ -188586,8 +188698,8 @@ var is_in_ssh_default = isInSsh;
188586
188698
 
188587
188699
  // ../../node_modules/open/index.js
188588
188700
  var fallbackAttemptSymbol = Symbol("fallbackAttempt");
188589
- var __dirname3 = import.meta.url ? path15.dirname(fileURLToPath3(import.meta.url)) : "";
188590
- var localXdgOpenPath = path15.join(__dirname3, "xdg-open");
188701
+ var __dirname3 = import.meta.url ? path16.dirname(fileURLToPath3(import.meta.url)) : "";
188702
+ var localXdgOpenPath = path16.join(__dirname3, "xdg-open");
188591
188703
  var { platform: platform4, arch: arch2 } = process8;
188592
188704
  var tryEachApp = async (apps2, opener) => {
188593
188705
  if (apps2.length === 0) {
@@ -188735,7 +188847,7 @@ var baseOpen = async (options2) => {
188735
188847
  const isBundled = !__dirname3 || __dirname3 === "/";
188736
188848
  let exeLocalXdgOpen = false;
188737
188849
  try {
188738
- await fs17.access(localXdgOpenPath, fsConstants2.X_OK);
188850
+ await fs18.access(localXdgOpenPath, fsConstants2.X_OK);
188739
188851
  exeLocalXdgOpen = true;
188740
188852
  } catch {
188741
188853
  }
@@ -188872,30 +188984,30 @@ import * as path19 from "path";
188872
188984
 
188873
188985
  // src/lib/deploy/build-tester.ts
188874
188986
  import { spawn as spawn5 } from "child_process";
188875
- import { existsSync as existsSync15 } from "fs";
188876
- import { join as join16 } from "path";
188987
+ import { existsSync as existsSync16 } from "fs";
188988
+ import { join as join17 } from "path";
188877
188989
  function getDependencyInstallCommand(build, projectDir) {
188878
188990
  switch (build.base) {
188879
188991
  case "node":
188880
- if (existsSync15(join16(projectDir, "pnpm-lock.yaml"))) {
188992
+ if (existsSync16(join17(projectDir, "pnpm-lock.yaml"))) {
188881
188993
  return "pnpm install --frozen-lockfile";
188882
- } else if (existsSync15(join16(projectDir, "yarn.lock"))) {
188994
+ } else if (existsSync16(join17(projectDir, "yarn.lock"))) {
188883
188995
  return "yarn install --frozen-lockfile";
188884
- } else if (existsSync15(join16(projectDir, "package-lock.json"))) {
188996
+ } else if (existsSync16(join17(projectDir, "package-lock.json"))) {
188885
188997
  return "npm ci";
188886
188998
  } else {
188887
188999
  return "npm install";
188888
189000
  }
188889
189001
  case "python":
188890
- if (existsSync15(join16(projectDir, "poetry.lock"))) {
189002
+ if (existsSync16(join17(projectDir, "poetry.lock"))) {
188891
189003
  return "poetry install --no-interaction";
188892
- } else if (existsSync15(join16(projectDir, "Pipfile.lock"))) {
189004
+ } else if (existsSync16(join17(projectDir, "Pipfile.lock"))) {
188893
189005
  return "pipenv install --deploy";
188894
- } else if (existsSync15(join16(projectDir, "Pipfile"))) {
189006
+ } else if (existsSync16(join17(projectDir, "Pipfile"))) {
188895
189007
  return "pipenv install";
188896
- } else if (existsSync15(join16(projectDir, "pyproject.toml"))) {
189008
+ } else if (existsSync16(join17(projectDir, "pyproject.toml"))) {
188897
189009
  return "pip install .";
188898
- } else if (existsSync15(join16(projectDir, "requirements.txt"))) {
189010
+ } else if (existsSync16(join17(projectDir, "requirements.txt"))) {
188899
189011
  return "pip install -r requirements.txt";
188900
189012
  }
188901
189013
  return null;
@@ -189287,12 +189399,40 @@ var ApiClient = class {
189287
189399
  }
189288
189400
  return response.json();
189289
189401
  }
189402
+ async getMe(signal) {
189403
+ const url = `${this.baseUrl}/users/me`;
189404
+ writeLog("api", `GET ${url}`);
189405
+ const response = await fetch(url, {
189406
+ headers: this.authHeaders(),
189407
+ signal
189408
+ });
189409
+ writeLog("api", `Response: ${response.status} ${response.statusText}`);
189410
+ if (!response.ok) {
189411
+ let errorBody;
189412
+ try {
189413
+ const error = await response.json();
189414
+ errorBody = JSON.stringify(error);
189415
+ writeLog("api:error", `API error: ${error.error} (${error.code})`);
189416
+ writeLog("api:error", `Request was: GET ${url}`);
189417
+ writeLog("api:error", `Response body: ${errorBody}`);
189418
+ throw new Error(`Failed to get user: ${error.error} (${error.code})`);
189419
+ } catch (e) {
189420
+ if (e instanceof Error && e.message.startsWith("Failed to get user")) {
189421
+ throw e;
189422
+ }
189423
+ errorBody = await response.text();
189424
+ writeLog("api:error", `Failed to parse error response: ${errorBody}`);
189425
+ throw new Error(`Failed to get user: ${response.statusText}`);
189426
+ }
189427
+ }
189428
+ return response.json();
189429
+ }
189290
189430
  };
189291
189431
 
189292
189432
  // src/lib/tarball/create.ts
189293
189433
  import { execSync as execSync3 } from "child_process";
189294
- import * as fs18 from "fs";
189295
- import * as path16 from "path";
189434
+ import * as fs19 from "fs";
189435
+ import * as path17 from "path";
189296
189436
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
189297
189437
  function isInsideGitRepository(dir) {
189298
189438
  try {
@@ -189338,10 +189478,10 @@ async function createGitArchive(projectDir) {
189338
189478
  var EXCLUDED_DIRS = [".specific"];
189339
189479
  async function collectPaths(baseDir, currentDir, exclude) {
189340
189480
  const results = [];
189341
- const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
189481
+ const entries = await fs19.promises.readdir(currentDir, { withFileTypes: true });
189342
189482
  for (const entry of entries) {
189343
- const fullPath = path16.join(currentDir, entry.name);
189344
- const relativePath = path16.relative(baseDir, fullPath);
189483
+ const fullPath = path17.join(currentDir, entry.name);
189484
+ const relativePath = path17.relative(baseDir, fullPath);
189345
189485
  if (entry.isDirectory()) {
189346
189486
  if (!exclude.includes(entry.name)) {
189347
189487
  results.push(relativePath);
@@ -189356,8 +189496,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
189356
189496
  }
189357
189497
  async function createTarArchive(projectDir) {
189358
189498
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
189359
- const configPath = path16.join(projectDir, "specific.hcl");
189360
- if (!fs18.existsSync(configPath)) {
189499
+ const configPath = path17.join(projectDir, "specific.hcl");
189500
+ if (!fs19.existsSync(configPath)) {
189361
189501
  throw new Error("specific.hcl not found in project directory");
189362
189502
  }
189363
189503
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -189379,49 +189519,10 @@ async function createProjectTarball(projectDir) {
189379
189519
  return createTarArchive(projectDir);
189380
189520
  }
189381
189521
 
189382
- // src/lib/project/config.ts
189383
- import * as fs19 from "fs";
189384
- import * as path17 from "path";
189385
- var PROJECT_ID_FILE = ".specific/project_id";
189386
- var ProjectNotLinkedError = class extends Error {
189387
- constructor() {
189388
- super(
189389
- `Project not linked to Specific.
189390
-
189391
- The file ${PROJECT_ID_FILE} does not exist.
189392
-
189393
- Run: specific deploy`
189394
- );
189395
- this.name = "ProjectNotLinkedError";
189396
- }
189397
- };
189398
- function readProjectId(projectDir = process.cwd()) {
189399
- const projectIdPath = path17.join(projectDir, PROJECT_ID_FILE);
189400
- if (!fs19.existsSync(projectIdPath)) {
189401
- throw new ProjectNotLinkedError();
189402
- }
189403
- const projectId = fs19.readFileSync(projectIdPath, "utf-8").trim();
189404
- if (!projectId) {
189405
- throw new Error(`${PROJECT_ID_FILE} is empty`);
189406
- }
189407
- return projectId;
189408
- }
189409
- function hasProjectId(projectDir = process.cwd()) {
189410
- const projectIdPath = path17.join(projectDir, PROJECT_ID_FILE);
189411
- return fs19.existsSync(projectIdPath);
189412
- }
189413
- function writeProjectId(projectId, projectDir = process.cwd()) {
189414
- const specificDir = path17.join(projectDir, ".specific");
189415
- if (!fs19.existsSync(specificDir)) {
189416
- fs19.mkdirSync(specificDir, { recursive: true });
189417
- }
189418
- fs19.writeFileSync(path17.join(specificDir, "project_id"), projectId + "\n");
189419
- }
189420
-
189421
189522
  // src/lib/auth/credentials.ts
189422
189523
  import * as fs20 from "fs";
189423
189524
  import * as path18 from "path";
189424
- import * as os6 from "os";
189525
+ import * as os7 from "os";
189425
189526
 
189426
189527
  // src/lib/auth/errors.ts
189427
189528
  var RefreshTokenExpiredError = class extends Error {
@@ -189560,10 +189661,14 @@ function LoginFlow({ isReauthentication, onComplete }) {
189560
189661
  }
189561
189662
  } else {
189562
189663
  const successResponse = response;
189664
+ const expiresAt2 = Date.now() + successResponse.expires_in * 1e3;
189665
+ const client2 = new ApiClient(successResponse.access_token);
189666
+ const user = await client2.getMe();
189563
189667
  writeUserCredentials({
189564
189668
  accessToken: successResponse.access_token,
189565
189669
  refreshToken: successResponse.refresh_token,
189566
- expiresAt: Date.now() + successResponse.expires_in * 1e3
189670
+ expiresAt: expiresAt2,
189671
+ userId: user.id
189567
189672
  });
189568
189673
  setUserEmail(successResponse.user.email);
189569
189674
  setPhase("success");
@@ -189612,7 +189717,7 @@ function LoginFlow({ isReauthentication, onComplete }) {
189612
189717
 
189613
189718
  // src/lib/auth/credentials.ts
189614
189719
  function getUserCredentialsDir() {
189615
- return path18.join(os6.homedir(), ".specific");
189720
+ return path18.join(os7.homedir(), ".specific");
189616
189721
  }
189617
189722
  function getCredentialsPath() {
189618
189723
  return path18.join(getUserCredentialsDir(), "credentials.json");
@@ -189899,8 +190004,8 @@ function DeployUI({ environment, config, skipBuildTest }) {
189899
190004
  async function loadProjects() {
189900
190005
  try {
189901
190006
  const token = await getValidAccessToken();
189902
- const client = new ApiClient(token);
189903
- const { projects: projects2 } = await client.listProjects();
190007
+ const client2 = new ApiClient(token);
190008
+ const { projects: projects2 } = await client2.listProjects();
189904
190009
  if (cancelled) return;
189905
190010
  setState({
189906
190011
  phase: "selecting-project",
@@ -189946,8 +190051,8 @@ function DeployUI({ environment, config, skipBuildTest }) {
189946
190051
  async function createProject() {
189947
190052
  try {
189948
190053
  const token = await getValidAccessToken();
189949
- const client = new ApiClient(token);
189950
- const project = await client.createProject(state.newProjectName);
190054
+ const client2 = new ApiClient(token);
190055
+ const project = await client2.createProject(state.newProjectName);
189951
190056
  if (cancelled) return;
189952
190057
  writeProjectId(project.id);
189953
190058
  setState({
@@ -190040,15 +190145,15 @@ function DeployUI({ environment, config, skipBuildTest }) {
190040
190145
  (secret) => secretValues[secret]
190041
190146
  );
190042
190147
  if (!allSecretsCollected) return;
190043
- const client = clientRef.current;
190044
- if (!client) return;
190148
+ const client2 = clientRef.current;
190149
+ if (!client2) return;
190045
190150
  (async () => {
190046
190151
  try {
190047
190152
  writeLog(
190048
190153
  "deploy",
190049
190154
  `Submitting secrets: ${Object.keys(secretValues).join(", ")}`
190050
190155
  );
190051
- await client.submitSecrets(deployment2.id, secretValues);
190156
+ await client2.submitSecrets(deployment2.id, secretValues);
190052
190157
  writeLog("deploy", "Secrets submitted successfully");
190053
190158
  setState((s) => ({
190054
190159
  ...s,
@@ -190082,15 +190187,15 @@ function DeployUI({ environment, config, skipBuildTest }) {
190082
190187
  (config2) => configValues[config2]
190083
190188
  );
190084
190189
  if (!allConfigsCollected) return;
190085
- const client = clientRef.current;
190086
- if (!client) return;
190190
+ const client2 = clientRef.current;
190191
+ if (!client2) return;
190087
190192
  (async () => {
190088
190193
  try {
190089
190194
  writeLog(
190090
190195
  "deploy",
190091
190196
  `Submitting configs: ${Object.keys(configValues).join(", ")}`
190092
190197
  );
190093
- await client.submitConfigs(deployment2.id, configValues);
190198
+ await client2.submitConfigs(deployment2.id, configValues);
190094
190199
  writeLog("deploy", "Configs submitted successfully");
190095
190200
  setState((s) => ({
190096
190201
  ...s,
@@ -190112,58 +190217,47 @@ function DeployUI({ environment, config, skipBuildTest }) {
190112
190217
  useEffect5(() => {
190113
190218
  if (state.phase !== "testing-builds" || !state.projectId) return;
190114
190219
  let cancelled = false;
190115
- async function runBuildTests() {
190220
+ let pollInterval;
190221
+ async function runBuildTestsAndDeploy() {
190116
190222
  const projectDir = process.cwd();
190117
190223
  const builds = config.builds || [];
190118
- if (skipBuildTest || builds.length === 0) {
190119
- writeLog("deploy", skipBuildTest ? "Skipping build tests (--skip-build-test)" : "No builds to test");
190120
- setState((s) => ({ ...s, phase: "creating-tarball" }));
190121
- return;
190122
- }
190123
- writeLog("deploy", `Testing ${builds.length} build(s) locally`);
190124
- try {
190125
- const results = await testAllBuilds(builds, projectDir);
190126
- if (cancelled) return;
190127
- if (!results.allPassed) {
190128
- const failures = results.results.filter((r) => !r.success);
190129
- const errorMsg = failures.map((f) => `Build "${f.buildName}" failed:
190224
+ if (!skipBuildTest && builds.length > 0) {
190225
+ writeLog("deploy", `Testing ${builds.length} build(s) locally`);
190226
+ try {
190227
+ const results = await testAllBuilds(builds, projectDir);
190228
+ if (cancelled) return;
190229
+ if (!results.allPassed) {
190230
+ const failures = results.results.filter((r) => !r.success);
190231
+ const errorMsg = failures.map((f) => `Build "${f.buildName}" failed:
190130
190232
  ${f.output}`).join("\n\n");
190233
+ writeLog("deploy:error", errorMsg);
190234
+ setState({
190235
+ phase: "error",
190236
+ error: `Build test failed:
190237
+ ${errorMsg}`
190238
+ });
190239
+ return;
190240
+ }
190241
+ writeLog("deploy", "All builds passed local testing");
190242
+ } catch (err) {
190243
+ if (cancelled) return;
190244
+ const errorMsg = `Build test failed: ${err instanceof Error ? err.message : String(err)}`;
190131
190245
  writeLog("deploy:error", errorMsg);
190132
190246
  setState({
190133
190247
  phase: "error",
190134
- error: `Build test failed:
190135
- ${errorMsg}`
190248
+ error: errorMsg
190136
190249
  });
190137
190250
  return;
190138
190251
  }
190139
- writeLog("deploy", "All builds passed local testing");
190140
- setState((s) => ({ ...s, phase: "creating-tarball" }));
190141
- } catch (err) {
190142
- if (cancelled) return;
190143
- const errorMsg = `Build test failed: ${err instanceof Error ? err.message : String(err)}`;
190144
- writeLog("deploy:error", errorMsg);
190145
- setState({
190146
- phase: "error",
190147
- error: errorMsg
190148
- });
190252
+ } else {
190253
+ writeLog("deploy", skipBuildTest ? "Skipping build tests (--skip-build-test)" : "No builds to test");
190149
190254
  }
190150
- }
190151
- runBuildTests();
190152
- return () => {
190153
- cancelled = true;
190154
- };
190155
- }, [state.phase, state.projectId, config.builds, skipBuildTest]);
190156
- useEffect5(() => {
190157
- if (state.phase !== "creating-tarball" || !state.projectId) return;
190158
- let cancelled = false;
190159
- let pollInterval;
190160
- async function deploy() {
190161
- const projectDir = process.cwd();
190255
+ setState((s) => ({ ...s, phase: "creating-tarball" }));
190162
190256
  writeLog("deploy", `Starting deployment to "${environment}"`);
190163
190257
  writeLog("deploy", `Project directory: ${projectDir}`);
190164
190258
  const authToken = await getValidAccessToken();
190165
- const client = new ApiClient(authToken);
190166
- clientRef.current = client;
190259
+ const client2 = new ApiClient(authToken);
190260
+ clientRef.current = client2;
190167
190261
  if (cancelled) return;
190168
190262
  let tarball;
190169
190263
  try {
@@ -190188,7 +190282,7 @@ ${errorMsg}`
190188
190282
  let deployment2;
190189
190283
  try {
190190
190284
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
190191
- deployment2 = await client.createDeployment(state.projectId, environment);
190285
+ deployment2 = await client2.createDeployment(state.projectId, environment);
190192
190286
  writeLog("deploy", `Deployment created: ${deployment2.id}`);
190193
190287
  } catch (err) {
190194
190288
  const errorMsg = `Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`;
@@ -190203,7 +190297,7 @@ ${errorMsg}`
190203
190297
  setState((s) => ({ ...s, phase: "uploading", deployment: deployment2 }));
190204
190298
  try {
190205
190299
  writeLog("deploy", `Uploading tarball (${tarball.length} bytes)`);
190206
- deployment2 = await client.uploadTarball(deployment2.id, tarball);
190300
+ deployment2 = await client2.uploadTarball(deployment2.id, tarball);
190207
190301
  writeLog("deploy", "Tarball uploaded successfully");
190208
190302
  } catch (err) {
190209
190303
  const errorMsg = `Failed to upload tarball: ${err instanceof Error ? err.message : String(err)}`;
@@ -190222,7 +190316,7 @@ ${errorMsg}`
190222
190316
  let lastState;
190223
190317
  const pollForCompletion = async () => {
190224
190318
  try {
190225
- const status = await client.getDeployment(deployment2.id);
190319
+ const status = await client2.getDeployment(deployment2.id);
190226
190320
  if (cancelled) return;
190227
190321
  if (status.state !== lastState) {
190228
190322
  writeLog(
@@ -190299,20 +190393,20 @@ ${errorMsg}`
190299
190393
  pollInterval = setInterval(pollForCompletion, 2e3);
190300
190394
  pollForCompletion();
190301
190395
  }
190302
- deploy();
190396
+ runBuildTestsAndDeploy();
190303
190397
  return () => {
190304
190398
  cancelled = true;
190305
190399
  if (pollInterval) clearInterval(pollInterval);
190306
190400
  };
190307
- }, [state.projectId, environment]);
190401
+ }, [state.projectId, environment, config.builds, skipBuildTest]);
190308
190402
  useEffect5(() => {
190309
190403
  let pollInterval;
190310
190404
  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) {
190311
- const client = clientRef.current;
190312
- if (!client) return;
190405
+ const client2 = clientRef.current;
190406
+ if (!client2) return;
190313
190407
  const pollForCompletion = async () => {
190314
190408
  try {
190315
- const status = await client.getDeployment(state.deployment.id);
190409
+ const status = await client2.getDeployment(state.deployment.id);
190316
190410
  if (status.state === "failed") {
190317
190411
  setState((s) => ({
190318
190412
  ...s,
@@ -190344,12 +190438,24 @@ ${errorMsg}`
190344
190438
  }
190345
190439
  }, [state.phase, state.missingSecrets, state.secretValues, state.missingConfigs, state.configValues]);
190346
190440
  useEffect5(() => {
190347
- if (state.phase === "success" || state.phase === "error") {
190441
+ if (state.phase === "testing-builds") {
190442
+ trackEvent("deploy_started", { environment });
190443
+ }
190444
+ }, [state.phase, environment]);
190445
+ useEffect5(() => {
190446
+ if (state.phase === "success") {
190447
+ trackEvent("deploy_succeeded", { environment });
190348
190448
  closeDebugLog();
190349
190449
  const timer = setTimeout(() => exit(), 100);
190350
190450
  return () => clearTimeout(timer);
190351
190451
  }
190352
- }, [state.phase, exit]);
190452
+ if (state.phase === "error") {
190453
+ trackEvent("deploy_failed", { environment });
190454
+ closeDebugLog();
190455
+ const timer = setTimeout(() => exit(), 100);
190456
+ return () => clearTimeout(timer);
190457
+ }
190458
+ }, [state.phase, exit, environment]);
190353
190459
  const {
190354
190460
  phase,
190355
190461
  deployment,
@@ -191174,7 +191280,7 @@ function logoutCommand() {
191174
191280
  var program = new Command();
191175
191281
  var env = "production";
191176
191282
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
191177
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.38").enablePositionalOptions();
191283
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.40").enablePositionalOptions();
191178
191284
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
191179
191285
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
191180
191286
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -191201,6 +191307,11 @@ program.command("secrets [action] [name]").description("Manage secrets").action(
191201
191307
  program.command("config [action] [name] [value]").description("Manage configuration values").action(configCommand);
191202
191308
  program.command("login").description("Log in to Specific").action(loginCommand);
191203
191309
  program.command("logout").description("Log out of Specific").action(logoutCommand);
191310
+ var commandName = process.argv[2] || "help";
191311
+ trackEvent("cli_command_invoked", { command: commandName });
191312
+ process.on("beforeExit", async () => {
191313
+ await shutdown();
191314
+ });
191204
191315
  program.parse();
191205
191316
  /*! Bundled license information:
191206
191317