@specific.dev/cli 0.1.181 → 0.1.184

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 (99) 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/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +1 -1
  18. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
  19. package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/crons/__next._full.txt +1 -1
  21. package/dist/admin/crons/__next._head.txt +1 -1
  22. package/dist/admin/crons/__next._index.txt +1 -1
  23. package/dist/admin/crons/__next._tree.txt +1 -1
  24. package/dist/admin/crons/index.html +1 -1
  25. package/dist/admin/crons/index.txt +1 -1
  26. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  27. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  28. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  29. package/dist/admin/databases/__next._full.txt +1 -1
  30. package/dist/admin/databases/__next._head.txt +1 -1
  31. package/dist/admin/databases/__next._index.txt +1 -1
  32. package/dist/admin/databases/__next._tree.txt +1 -1
  33. package/dist/admin/databases/index.html +1 -1
  34. package/dist/admin/databases/index.txt +1 -1
  35. package/dist/admin/fullscreen/__next._full.txt +1 -1
  36. package/dist/admin/fullscreen/__next._head.txt +1 -1
  37. package/dist/admin/fullscreen/__next._index.txt +1 -1
  38. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  39. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  40. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  41. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  42. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  43. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  44. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  45. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  46. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  47. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  48. package/dist/admin/fullscreen/databases/index.html +1 -1
  49. package/dist/admin/fullscreen/databases/index.txt +1 -1
  50. package/dist/admin/fullscreen/index.html +1 -1
  51. package/dist/admin/fullscreen/index.txt +1 -1
  52. package/dist/admin/index.html +1 -1
  53. package/dist/admin/index.txt +1 -1
  54. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  55. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  56. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  57. package/dist/admin/mail/__next._full.txt +1 -1
  58. package/dist/admin/mail/__next._head.txt +1 -1
  59. package/dist/admin/mail/__next._index.txt +1 -1
  60. package/dist/admin/mail/__next._tree.txt +1 -1
  61. package/dist/admin/mail/index.html +1 -1
  62. package/dist/admin/mail/index.txt +1 -1
  63. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
  64. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
  65. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
  66. package/dist/admin/services/__next._full.txt +1 -1
  67. package/dist/admin/services/__next._head.txt +1 -1
  68. package/dist/admin/services/__next._index.txt +1 -1
  69. package/dist/admin/services/__next._tree.txt +1 -1
  70. package/dist/admin/services/index.html +1 -1
  71. package/dist/admin/services/index.txt +1 -1
  72. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
  73. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
  74. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
  75. package/dist/admin/storage/__next._full.txt +1 -1
  76. package/dist/admin/storage/__next._head.txt +1 -1
  77. package/dist/admin/storage/__next._index.txt +1 -1
  78. package/dist/admin/storage/__next._tree.txt +1 -1
  79. package/dist/admin/storage/index.html +1 -1
  80. package/dist/admin/storage/index.txt +1 -1
  81. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  82. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  83. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  84. package/dist/admin/workflows/__next._full.txt +1 -1
  85. package/dist/admin/workflows/__next._head.txt +1 -1
  86. package/dist/admin/workflows/__next._index.txt +1 -1
  87. package/dist/admin/workflows/__next._tree.txt +1 -1
  88. package/dist/admin/workflows/index.html +1 -1
  89. package/dist/admin/workflows/index.txt +1 -1
  90. package/dist/cli.js +465 -57
  91. package/dist/docs/deployments.md +79 -0
  92. package/dist/docs/index.md +4 -1
  93. package/dist/docs/observability.md +2 -1
  94. package/dist/docs/previews.md +12 -0
  95. package/dist/docs/secrets-config.md +6 -0
  96. package/package.json +1 -1
  97. /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_buildManifest.js +0 -0
  98. /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_clientMiddlewareManifest.json +0 -0
  99. /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -368079,8 +368079,13 @@ function toDeployment(response) {
368079
368079
  error: response.error,
368080
368080
  changes: response.changes,
368081
368081
  pendingActions: response.pendingActions,
368082
+ builds: response.builds,
368082
368083
  publicUrls: response.publicUrls,
368084
+ triggeredBy: response.triggeredBy,
368085
+ gitCommitSha: response.gitCommitSha,
368083
368086
  createdAt: response.createdAt,
368087
+ startedAt: response.startedAt,
368088
+ completedAt: response.completedAt,
368084
368089
  uploadUrl: response.uploadUrl,
368085
368090
  maxTarballBytes: response.maxTarballBytes
368086
368091
  };
@@ -368220,6 +368225,14 @@ async function createTarball(config, projectDir) {
368220
368225
  return await createProjectTarball(projectDir, widestContext);
368221
368226
  }
368222
368227
  var import_hcl2_json_parser = __toESM2(require_dist(), 1);
368228
+ function extractEnvReferences(value) {
368229
+ if (typeof value === "string")
368230
+ return [];
368231
+ if (value.type === "interpolated") {
368232
+ return value.parts.filter((p) => p.type === "ref").map((p) => p.ref);
368233
+ }
368234
+ return [value];
368235
+ }
368223
368236
  var { parseToObject } = import_hcl2_json_parser.default;
368224
368237
  function parseReference(value) {
368225
368238
  if (typeof value !== "string") {
@@ -368991,19 +369004,11 @@ async function parseConfig(hcl) {
368991
369004
  environments: parseEnvironments(json.environment)
368992
369005
  };
368993
369006
  }
368994
- function envValueReferences(value) {
368995
- if (typeof value === "string")
368996
- return [];
368997
- if (value.type === "interpolated") {
368998
- return value.parts.filter((p) => p.type === "ref").map((p) => p.ref);
368999
- }
369000
- return [value];
369001
- }
369002
369007
  function hasPortReference(env2) {
369003
369008
  if (!env2)
369004
369009
  return false;
369005
369010
  for (const value of Object.values(env2)) {
369006
- for (const ref of envValueReferences(value)) {
369011
+ for (const ref of extractEnvReferences(value)) {
369007
369012
  if (ref.type === "port")
369008
369013
  return true;
369009
369014
  }
@@ -369020,7 +369025,7 @@ function validateEndpointReferences(config) {
369020
369025
  if (!service.env)
369021
369026
  continue;
369022
369027
  for (const [key, value] of Object.entries(service.env)) {
369023
- const refs = envValueReferences(value);
369028
+ const refs = extractEnvReferences(value);
369024
369029
  for (const ref of refs) {
369025
369030
  if (service.endpoints.length > 1 && ref.type === "port") {
369026
369031
  errors.push({
@@ -369099,6 +369104,82 @@ function validateEndpointReferences(config) {
369099
369104
  }
369100
369105
  return errors;
369101
369106
  }
369107
+ var UnknownServiceError = class extends Error {
369108
+ unknownNames;
369109
+ availableServices;
369110
+ cronNames;
369111
+ constructor(unknownNames, availableServices, cronNames) {
369112
+ const plural = unknownNames.length > 1 ? "services" : "service";
369113
+ const names = unknownNames.map((n) => `"${n}"`).join(", ");
369114
+ const available = availableServices.length > 0 ? ` Available services: ${availableServices.join(", ")}` : " No services are defined in specific.hcl.";
369115
+ super(`Unknown ${plural} ${names}.${available}`);
369116
+ this.unknownNames = unknownNames;
369117
+ this.availableServices = availableServices;
369118
+ this.cronNames = cronNames;
369119
+ this.name = "UnknownServiceError";
369120
+ }
369121
+ };
369122
+ function resolveServiceClosure(config, roots, options2 = {}) {
369123
+ const devMode = options2.devMode ?? true;
369124
+ const byName = new Map(config.services.map((s) => [s.name, s]));
369125
+ const dedupedRoots = [...new Set(roots)];
369126
+ const unknown = dedupedRoots.filter((name) => !byName.has(name));
369127
+ if (unknown.length > 0) {
369128
+ throw new UnknownServiceError(unknown, [...byName.keys()], config.crons.map((c) => c.name));
369129
+ }
369130
+ const visited = new Set(dedupedRoots);
369131
+ const queue = [...dedupedRoots];
369132
+ const postgres = /* @__PURE__ */ new Set();
369133
+ const redis = /* @__PURE__ */ new Set();
369134
+ const storage = /* @__PURE__ */ new Set();
369135
+ const temporal = /* @__PURE__ */ new Set();
369136
+ const mail = /* @__PURE__ */ new Set();
369137
+ const syncPostgres = /* @__PURE__ */ new Set();
369138
+ const effectiveEnv = (service) => devMode ? { ...service.env, ...service.dev?.env } : { ...service.env };
369139
+ while (queue.length > 0) {
369140
+ const service = byName.get(queue.shift());
369141
+ for (const value of Object.values(effectiveEnv(service))) {
369142
+ for (const ref of extractEnvReferences(value)) {
369143
+ switch (ref.type) {
369144
+ case "service":
369145
+ if (byName.has(ref.serviceName) && !visited.has(ref.serviceName)) {
369146
+ visited.add(ref.serviceName);
369147
+ queue.push(ref.serviceName);
369148
+ }
369149
+ break;
369150
+ case "postgres":
369151
+ postgres.add(ref.name);
369152
+ if (ref.attribute === "sync.url" || ref.attribute === "sync.secret") {
369153
+ syncPostgres.add(ref.name);
369154
+ }
369155
+ break;
369156
+ case "redis":
369157
+ redis.add(ref.name);
369158
+ break;
369159
+ case "storage":
369160
+ storage.add(ref.name);
369161
+ break;
369162
+ case "temporal":
369163
+ temporal.add(ref.name);
369164
+ break;
369165
+ case "mail":
369166
+ mail.add(ref.name);
369167
+ break;
369168
+ }
369169
+ }
369170
+ }
369171
+ }
369172
+ const names = (items, set) => items.filter((item) => set.has(item.name)).map((item) => item.name);
369173
+ return {
369174
+ services: names(config.services, visited),
369175
+ postgres: names(config.postgres, postgres),
369176
+ redis: names(config.redis, redis),
369177
+ storage: names(config.storage, storage),
369178
+ temporal: names(config.temporal, temporal),
369179
+ mail: names(config.mail, mail),
369180
+ syncPostgres: names(config.postgres, syncPostgres)
369181
+ };
369182
+ }
369102
369183
  function parseHclErrors(rawError) {
369103
369184
  const raw = rawError.trim();
369104
369185
  if (!raw.startsWith("parse config: [")) {
@@ -372583,7 +372664,7 @@ async function startResources(options2) {
372583
372664
  });
372584
372665
  }
372585
372666
  if (shouldStartElectric) {
372586
- const syncDatabases = detectSyncDatabases(config);
372667
+ const syncDatabases = options2.syncDatabases ?? detectSyncDatabases(config);
372587
372668
  for (const pgName of syncDatabases) {
372588
372669
  if (signal?.cancelled) {
372589
372670
  return { resources, electric, mail: mailServers, startedResources, startedElectric, cancelled: true };
@@ -373082,9 +373163,11 @@ var DevEnvironment = class extends TypedEventEmitter {
373082
373163
  tunnelEnabled;
373083
373164
  preSuppliedSecrets;
373084
373165
  preSuppliedConfigs;
373166
+ servicesFilter;
373085
373167
  // Status
373086
373168
  _status = "idle";
373087
373169
  _config;
373170
+ _selection;
373088
373171
  // Guards
373089
373172
  started = false;
373090
373173
  shuttingDown = false;
@@ -373128,6 +373211,7 @@ var DevEnvironment = class extends TypedEventEmitter {
373128
373211
  this.tunnelEnabled = options2?.tunnel ?? false;
373129
373212
  this.preSuppliedSecrets = options2?.secrets ?? {};
373130
373213
  this.preSuppliedConfigs = options2?.configs ?? {};
373214
+ this.servicesFilter = options2?.services ?? [];
373131
373215
  }
373132
373216
  // ── Public getters ─────────────────────────────────────────────────────────
373133
373217
  get status() {
@@ -373136,6 +373220,10 @@ var DevEnvironment = class extends TypedEventEmitter {
373136
373220
  get config() {
373137
373221
  return this._config;
373138
373222
  }
373223
+ /** The resolved service selection when a services filter is active. */
373224
+ get selection() {
373225
+ return this._selection;
373226
+ }
373139
373227
  // ── Status helper ──────────────────────────────────────────────────────────
373140
373228
  setStatus(status) {
373141
373229
  this._status = status;
@@ -373510,6 +373598,31 @@ var DevEnvironment = class extends TypedEventEmitter {
373510
373598
  }
373511
373599
  if (this.cancelled) return;
373512
373600
  this._config = config;
373601
+ let closure;
373602
+ if (this.servicesFilter.length > 0) {
373603
+ try {
373604
+ closure = resolveServiceClosure(config, this.servicesFilter);
373605
+ } catch (err) {
373606
+ let message = err instanceof Error ? err.message : String(err);
373607
+ if (err instanceof UnknownServiceError) {
373608
+ const crons = err.unknownNames.filter(
373609
+ (n) => err.cronNames.includes(n)
373610
+ );
373611
+ for (const cron of crons) {
373612
+ message += `
373613
+ "${cron}" is a cron \u2014 crons don't run in specific dev. Trigger it with: specific exec ${cron}`;
373614
+ }
373615
+ }
373616
+ this.setStatus("error");
373617
+ this.emit("error", new Error(message));
373618
+ return;
373619
+ }
373620
+ this.systemLog(
373621
+ `Starting ${closure.services.length} of ${config.services.length} services: ${closure.services.join(", ")}`
373622
+ );
373623
+ }
373624
+ this._selection = closure;
373625
+ const servicesToRun = closure ? config.services.filter((s) => closure.services.includes(s.name)) : config.services;
373513
373626
  this.colorMap = /* @__PURE__ */ new Map();
373514
373627
  let colorIndex = 0;
373515
373628
  for (const pg of config.postgres) {
@@ -373547,13 +373660,21 @@ var DevEnvironment = class extends TypedEventEmitter {
373547
373660
  this.setStatus("starting");
373548
373661
  const portAllocator = await BlockPortAllocator.create();
373549
373662
  this.systemLog(`Using port block ${portAllocator.getBasePort()}\u2013${portAllocator.getBasePort() + 99}`);
373550
- const syncDatabases = detectSyncDatabases(config);
373663
+ const syncDatabases = closure ? new Set(closure.syncPostgres) : detectSyncDatabases(config);
373551
373664
  let resources;
373552
373665
  const startedElectric = [];
373553
373666
  try {
373554
373667
  const result = await startResources({
373555
373668
  config,
373556
- selection: { mode: "all" },
373669
+ selection: closure ? {
373670
+ mode: "required",
373671
+ postgres: closure.postgres,
373672
+ redis: closure.redis,
373673
+ storage: closure.storage,
373674
+ temporal: closure.temporal,
373675
+ mail: closure.mail
373676
+ } : { mode: "all" },
373677
+ syncDatabases,
373557
373678
  stateManager,
373558
373679
  dataDir: `.specific/keys/${this.instanceKey}/data`,
373559
373680
  portAllocator,
@@ -373715,8 +373836,8 @@ var DevEnvironment = class extends TypedEventEmitter {
373715
373836
  if (this.cancelled) return;
373716
373837
  const secrets = await prepareSecrets(config.secrets, true);
373717
373838
  const configs = await prepareConfigs(config.configs, void 0, true);
373718
- const usedSecrets = findUsedSecrets(config.services, true);
373719
- const usedConfigs = findUsedConfigs(config.services, true);
373839
+ const usedSecrets = findUsedSecrets(servicesToRun, true);
373840
+ const usedConfigs = findUsedConfigs(servicesToRun, true);
373720
373841
  let missingSecrets = findMissingSecrets(
373721
373842
  config.secrets,
373722
373843
  secrets,
@@ -373800,7 +373921,7 @@ Add them to the config block in specific.local`
373800
373921
  }
373801
373922
  const serviceEndpoints = /* @__PURE__ */ new Map();
373802
373923
  const serviceEndpointPorts = /* @__PURE__ */ new Map();
373803
- for (const service of config.services) {
373924
+ for (const service of servicesToRun) {
373804
373925
  if (!service.command && !service.dev?.command) continue;
373805
373926
  const endpointPorts = /* @__PURE__ */ new Map();
373806
373927
  const endpointInfos = [];
@@ -373827,7 +373948,7 @@ Add them to the config block in specific.local`
373827
373948
  serviceEndpoints.set(service.name, endpointInfos);
373828
373949
  }
373829
373950
  const exposedServices = [];
373830
- for (const service of config.services) {
373951
+ for (const service of servicesToRun) {
373831
373952
  const endpointInfos = serviceEndpoints.get(service.name) || [];
373832
373953
  for (const info of endpointInfos) {
373833
373954
  const endpointConfig = service.endpoints.find(
@@ -373873,7 +373994,7 @@ Add them to the config block in specific.local`
373873
373994
  }
373874
373995
  return this.projectDir;
373875
373996
  };
373876
- for (const service of config.services) {
373997
+ for (const service of servicesToRun) {
373877
373998
  if (this.cancelled) break;
373878
373999
  if (!service.command && !service.dev?.command) {
373879
374000
  continue;
@@ -373942,7 +374063,7 @@ Add them to the config block in specific.local`
373942
374063
  this.systemLog("Restarting services (migration changed)");
373943
374064
  await Promise.all(this.services.map((s) => s.stop()));
373944
374065
  const newServices = [];
373945
- for (const service of config.services) {
374066
+ for (const service of servicesToRun) {
373946
374067
  if (!service.command && !service.dev?.command) continue;
373947
374068
  try {
373948
374069
  const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
@@ -374765,7 +374886,7 @@ function trackEvent(event, properties) {
374765
374886
  event,
374766
374887
  properties: {
374767
374888
  ...properties,
374768
- cli_version: "0.1.181",
374889
+ cli_version: "0.1.184",
374769
374890
  platform: process.platform,
374770
374891
  node_version: process.version,
374771
374892
  project_id: getProjectId()
@@ -376233,7 +376354,18 @@ function ConfigInput({ configName, defaultValue, onSubmit, onCancel }) {
376233
376354
 
376234
376355
  // src/commands/dev.tsx
376235
376356
  var COLORS2 = ["cyan", "yellow", "green", "magenta", "blue"];
376236
- function DevUI({ instanceKey, tunnelEnabled }) {
376357
+ function filterConfigToSelection(config, selection) {
376358
+ return {
376359
+ ...config,
376360
+ services: config.services.filter((s) => selection.services.includes(s.name)),
376361
+ postgres: config.postgres.filter((p) => selection.postgres.includes(p.name)),
376362
+ redis: config.redis.filter((r) => selection.redis.includes(r.name)),
376363
+ storage: config.storage.filter((s) => selection.storage.includes(s.name)),
376364
+ temporal: config.temporal.filter((t) => selection.temporal.includes(t.name)),
376365
+ mail: config.mail.filter((m) => selection.mail.includes(m.name))
376366
+ };
376367
+ }
376368
+ function DevUI({ instanceKey, tunnelEnabled, services: serviceFilter }) {
376237
376369
  const { exit } = useApp2();
376238
376370
  const [state, setState] = useState5({
376239
376371
  status: "loading",
@@ -376276,7 +376408,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376276
376408
  const devEnv = new DevEnvironment({
376277
376409
  projectDir: process.cwd(),
376278
376410
  instanceKey,
376279
- tunnel: tunnelEnabled
376411
+ tunnel: tunnelEnabled,
376412
+ services: serviceFilter
376280
376413
  });
376281
376414
  devEnvRef.current = devEnv;
376282
376415
  let currentColorMap = /* @__PURE__ */ new Map();
@@ -376286,12 +376419,18 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376286
376419
  } else if (status === "starting") {
376287
376420
  const config2 = devEnv.config;
376288
376421
  if (config2) {
376289
- currentColorMap = buildColorMap(config2);
376422
+ const selection = devEnv.selection;
376423
+ const displayConfig = selection ? filterConfigToSelection(config2, selection) : config2;
376424
+ currentColorMap = buildColorMap(displayConfig);
376290
376425
  setState((s) => ({
376291
376426
  ...s,
376292
376427
  status: "starting",
376293
- config: config2,
376294
- colorMap: currentColorMap
376428
+ config: displayConfig,
376429
+ colorMap: currentColorMap,
376430
+ filterInfo: selection ? {
376431
+ selected: selection.services.length,
376432
+ total: config2.services.length
376433
+ } : void 0
376295
376434
  }));
376296
376435
  } else {
376297
376436
  setState((s) => ({ ...s, status: "starting" }));
@@ -376408,7 +376547,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376408
376547
  devEnv.removeAllListeners();
376409
376548
  devEnvRef.current = null;
376410
376549
  };
376411
- }, [instanceKey, tunnelEnabled]);
376550
+ }, [instanceKey, tunnelEnabled, serviceFilter.join(",")]);
376412
376551
  useEffect5(() => {
376413
376552
  const handleSignal = () => {
376414
376553
  const devEnv = devEnvRef.current;
@@ -376555,7 +376694,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376555
376694
  return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, /* @__PURE__ */ React6.createElement(Spinner3, { type: "dots" })));
376556
376695
  }
376557
376696
  if (state.status === "starting") {
376558
- return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)")), /* @__PURE__ */ React6.createElement(Text6, null, " "), (config.postgres.length > 0 || config.redis.length > 0 || config.storage.length > 0 || config.temporal.length > 0) && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Resources:"), config.postgres.map((pg) => {
376697
+ return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), state.filterInfo && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", "[", state.filterInfo.selected, " of ", state.filterInfo.total, " services]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)")), /* @__PURE__ */ React6.createElement(Text6, null, " "), (config.postgres.length > 0 || config.redis.length > 0 || config.storage.length > 0 || config.temporal.length > 0) && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Resources:"), config.postgres.map((pg) => {
376559
376698
  const instance = resources.get(pg.name);
376560
376699
  const resStatus = state.resourceStatus.get(pg.name);
376561
376700
  const isReady = !!instance;
@@ -376620,7 +376759,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376620
376759
  const staticItems = [
376621
376760
  {
376622
376761
  key: "title",
376623
- content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
376762
+ content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), state.filterInfo && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", "[", state.filterInfo.selected, " of ", state.filterInfo.total, " services]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
376624
376763
  },
376625
376764
  { key: "space1", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") },
376626
376765
  // Show admin UI URL
@@ -376730,10 +376869,20 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376730
376869
  ];
376731
376870
  return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React6.createElement(Box6, { key: item.key }, item.content)));
376732
376871
  }
376733
- function devCommand(instanceKey, tunnelEnabled = false) {
376872
+ function devCommand(instanceKey, tunnelEnabled = false, services = []) {
376734
376873
  const options2 = isInteractive() ? {} : { stdin: new Readable2({ read() {
376735
376874
  } }) };
376736
- render4(/* @__PURE__ */ React6.createElement(DevUI, { instanceKey, tunnelEnabled }), options2);
376875
+ render4(
376876
+ /* @__PURE__ */ React6.createElement(
376877
+ DevUI,
376878
+ {
376879
+ instanceKey,
376880
+ tunnelEnabled,
376881
+ services
376882
+ }
376883
+ ),
376884
+ options2
376885
+ );
376737
376886
  }
376738
376887
 
376739
376888
  // src/commands/deploy.tsx
@@ -376743,6 +376892,22 @@ import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useIn
376743
376892
  import Spinner4 from "ink-spinner";
376744
376893
  import * as fs23 from "fs";
376745
376894
  import * as path21 from "path";
376895
+
376896
+ // src/lib/deployments/format.ts
376897
+ function formatErrorCode(code) {
376898
+ const labels = {
376899
+ build_failed: "Build failed",
376900
+ pre_deploy_failed: "Pre-deploy hook failed",
376901
+ deploy_failed: "Service failed to deploy",
376902
+ health_check_failed: "Health check failed",
376903
+ post_deploy_failed: "Post-deploy hook failed",
376904
+ volume_reset_failed: "Volume reset failed",
376905
+ internal_error: "Internal error"
376906
+ };
376907
+ return labels[code] || code;
376908
+ }
376909
+
376910
+ // src/commands/deploy.tsx
376746
376911
  import { execFileSync as execFileSync2 } from "child_process";
376747
376912
  function getGitInfo(cwd) {
376748
376913
  try {
@@ -376944,17 +377109,6 @@ function getFailedBuild(pendingActions) {
376944
377109
  if (!pendingActions) return void 0;
376945
377110
  return pendingActions.find((a) => a.type === "build_failed");
376946
377111
  }
376947
- function formatErrorCode(code) {
376948
- const labels = {
376949
- pre_deploy_failed: "Pre-deploy hook failed",
376950
- deploy_failed: "Service failed to deploy",
376951
- health_check_failed: "Health check failed",
376952
- post_deploy_failed: "Post-deploy hook failed",
376953
- volume_reset_failed: "Volume reset failed",
376954
- internal_error: "Internal error"
376955
- };
376956
- return labels[code] || code;
376957
- }
376958
377112
  function StructuredError({ error }) {
376959
377113
  return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, formatErrorCode(error.code), ": ", error.message), error.resource && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Resource: ", error.resource), error.output && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Output:"), /* @__PURE__ */ React7.createElement(Text7, null, error.output)));
376960
377114
  }
@@ -379147,7 +379301,7 @@ function compareVersions(a, b) {
379147
379301
  return 0;
379148
379302
  }
379149
379303
  async function checkForUpdate() {
379150
- const currentVersion = "0.1.181";
379304
+ const currentVersion = "0.1.184";
379151
379305
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
379152
379306
  if (!response.ok) {
379153
379307
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -379434,13 +379588,35 @@ function PreviewBlock({ env: env2 }) {
379434
379588
  const urls = managedUrls(env2.domains);
379435
379589
  return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), env2.prNumber !== null && /* @__PURE__ */ React12.createElement(Text12, null, " ", "PR #", env2.prNumber, env2.prTitle ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " \u2014 ", env2.prTitle) : null), urls.length > 0 && /* @__PURE__ */ React12.createElement(Text12, null, " ", "URL: ", urls.join(", ")), env2.expiresAt && /* @__PURE__ */ React12.createElement(Text12, null, " ", "Expires: ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, new Date(env2.expiresAt).toLocaleString())));
379436
379590
  }
379591
+ function PreviewConfigurationBlock({
379592
+ config,
379593
+ activePreviewCount,
379594
+ showDetails
379595
+ }) {
379596
+ const configured = config.github;
379597
+ const enabledConfigs = configured.filter((c) => c.prPreviewsEnabled);
379598
+ const labelGated = enabledConfigs.filter((c) => c.previewsRequireLabel);
379599
+ const githubSummary = enabledConfigs.length > 0 ? `enabled for ${enabledConfigs.length} environment${enabledConfigs.length === 1 ? "" : "s"}` : configured.length > 0 ? "configured but disabled" : "not configured";
379600
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview configuration"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, null, "PR previews: ", config.enabledForOrganization ? "available" : "unavailable", "; GitHub ", githubSummary, ";", " ", labelGated.length > 0 ? `requires ${config.requiredLabel}` : "no label required", "; active ", activePreviewCount), !showDetails && configured.length > 0 && /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " ", "Run `specific status --previews` for GitHub preview details."), showDetails && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, "Required label: ", config.requiredLabel), configured.length === 0 ? /* @__PURE__ */ React12.createElement(Text12, null, "GitHub PR automation: not configured") : configured.map((githubConfig) => /* @__PURE__ */ React12.createElement(Box11, { key: githubConfig.environmentId, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, githubConfig.environmentName), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", githubConfig.environmentId, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "PR previews: ", githubConfig.prPreviewsEnabled ? "enabled" : "disabled", "; ", "requires label: ", githubConfig.previewsRequireLabel ? "yes" : "no"), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Repository: ", githubConfig.repositoryFullName, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", githubConfig.branch, ", ", githubConfig.hclPath, ")")))))));
379601
+ }
379437
379602
  function EnvironmentBlock({ env: env2 }) {
379438
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), env2.domains && Object.keys(env2.domains).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Domains:"), Object.entries(env2.domains).map(([serviceName, urls]) => {
379603
+ const latest = env2.latestDeployment;
379604
+ const latestIsShownElsewhere = !latest || latest.id === env2.activeDeployment?.id || latest.id === env2.inProgressDeployment?.id;
379605
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), !latestIsShownElsewhere && /* @__PURE__ */ React12.createElement(Text12, null, " ", "Last deployment:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, latest.id), " ", /* @__PURE__ */ React12.createElement(
379606
+ Text12,
379607
+ {
379608
+ ...latest.state === "failed" ? { color: "red", bold: true } : {}
379609
+ },
379610
+ "(",
379611
+ latest.state,
379612
+ latest.triggeredBy ? ` via ${latest.triggeredBy}` : "",
379613
+ ")"
379614
+ )), !latestIsShownElsewhere && latest.state === "failed" && /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " ", "Run `specific deployment show ", latest.id, "` for details."), env2.domains && Object.keys(env2.domains).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Domains:"), Object.entries(env2.domains).map(([serviceName, urls]) => {
379439
379615
  const displayUrls = urls.custom.length > 0 ? urls.custom : urls.managed ? [urls.managed] : [];
379440
379616
  return /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", serviceName, displayUrls.length > 0 ? `: ${displayUrls.join(", ")}` : null);
379441
379617
  })));
379442
379618
  }
379443
- function StatusUI() {
379619
+ function StatusUI({ previews = false }) {
379444
379620
  const [state, setState] = useState11({ phase: "loading" });
379445
379621
  useEffect11(() => {
379446
379622
  (async () => {
@@ -379468,14 +379644,21 @@ function StatusUI() {
379468
379644
  return /* @__PURE__ */ React12.createElement(Text12, { color: "red" }, "Failed to get status: ", state.error);
379469
379645
  }
379470
379646
  const status = state.status;
379471
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Environments"), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.")) : /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.environments.map((env2) => /* @__PURE__ */ React12.createElement(EnvironmentBlock, { key: env2.id, env: env2 })))), (status.previewEnvironments ?? []).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview environments"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.previewEnvironments.map((env2) => /* @__PURE__ */ React12.createElement(PreviewBlock, { key: env2.id, env: env2 })))));
379647
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Environments"), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.")) : /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.environments.map((env2) => /* @__PURE__ */ React12.createElement(EnvironmentBlock, { key: env2.id, env: env2 })))), /* @__PURE__ */ React12.createElement(
379648
+ PreviewConfigurationBlock,
379649
+ {
379650
+ config: status.previewEnvironmentConfig,
379651
+ activePreviewCount: (status.previewEnvironments ?? []).length,
379652
+ showDetails: previews
379653
+ }
379654
+ ), (status.previewEnvironments ?? []).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview environments"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.previewEnvironments.map((env2) => /* @__PURE__ */ React12.createElement(PreviewBlock, { key: env2.id, env: env2 })))));
379472
379655
  }
379473
- function statusCommand() {
379474
- render10(/* @__PURE__ */ React12.createElement(StatusUI, null));
379656
+ function statusCommand(options2 = {}) {
379657
+ render10(/* @__PURE__ */ React12.createElement(StatusUI, { previews: options2.previews ?? false }));
379475
379658
  }
379476
379659
 
379477
379660
  // src/lib/project/resolve-env.ts
379478
- async function resolveEnvironmentId(client2, projectId, envFlag) {
379661
+ async function resolveEnvironmentStatus(client2, projectId, envFlag) {
379479
379662
  const status = await client2.getProjectStatus(projectId);
379480
379663
  const environments = status.environments;
379481
379664
  const previewEnvironments = status.previewEnvironments ?? [];
@@ -379490,16 +379673,19 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
379490
379673
  `Environment "${envFlag}" not found (matched against name and ID). Available: ${available}`
379491
379674
  );
379492
379675
  }
379493
- return match.id;
379676
+ return match;
379494
379677
  }
379495
379678
  if (hasEnvironmentId()) {
379496
379679
  const savedId = readEnvironmentId();
379497
- if (environments.some((e) => e.id === savedId)) {
379498
- return savedId;
379680
+ const saved = environments.find(
379681
+ (e) => e.id === savedId
379682
+ );
379683
+ if (saved) {
379684
+ return saved;
379499
379685
  }
379500
379686
  }
379501
379687
  if (environments.length === 1) {
379502
- return environments[0].id;
379688
+ return environments[0];
379503
379689
  }
379504
379690
  if (environments.length === 0) {
379505
379691
  throw new Error("This project has no environments yet.");
@@ -379509,6 +379695,10 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
379509
379695
  `Project has multiple environments (${names}). Pass --environment <name|id>.`
379510
379696
  );
379511
379697
  }
379698
+ async function resolveEnvironmentId(client2, projectId, envFlag) {
379699
+ const match = await resolveEnvironmentStatus(client2, projectId, envFlag);
379700
+ return match.id;
379701
+ }
379512
379702
 
379513
379703
  // src/lib/query/render-table.ts
379514
379704
  var MAX_COL_WIDTH = 60;
@@ -379750,6 +379940,204 @@ function friendlyErrorMessage(raw) {
379750
379940
  return raw;
379751
379941
  }
379752
379942
 
379943
+ // src/commands/deployment.tsx
379944
+ var ANSI2 = {
379945
+ dim: "\x1B[2m",
379946
+ red: "\x1B[31m",
379947
+ green: "\x1B[32m",
379948
+ yellow: "\x1B[33m",
379949
+ bold: "\x1B[1m",
379950
+ reset: "\x1B[0m"
379951
+ };
379952
+ async function deploymentShowCommand(idArg, options2) {
379953
+ try {
379954
+ const format = resolveOutputFormat2(options2.format);
379955
+ const includeOutput = options2.output ?? false;
379956
+ const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
379957
+ let deploymentId = idArg;
379958
+ if (!deploymentId) {
379959
+ const projectId = readProjectId();
379960
+ const env2 = await resolveEnvironmentStatus(
379961
+ client2,
379962
+ projectId,
379963
+ options2.environment
379964
+ );
379965
+ const latest = env2.latestDeployment ?? env2.inProgressDeployment ?? env2.activeDeployment;
379966
+ if (!latest) {
379967
+ throw new Error(
379968
+ `No deployments found for environment "${env2.name}".`
379969
+ );
379970
+ }
379971
+ deploymentId = latest.id;
379972
+ }
379973
+ const deployment = await client2.getDeployment(deploymentId);
379974
+ if (format === "json") {
379975
+ const payload = includeOutput ? deployment : stripOutput(deployment);
379976
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
379977
+ return;
379978
+ }
379979
+ process.stdout.write(renderDeployment(deployment, includeOutput));
379980
+ } catch (err) {
379981
+ const message = err instanceof Error ? err.message : "Unknown error occurred";
379982
+ console.error(`Error: ${message}`);
379983
+ process.exit(1);
379984
+ }
379985
+ }
379986
+ function resolveOutputFormat2(format) {
379987
+ if (!format) {
379988
+ return isInteractive() ? "text" : "json";
379989
+ }
379990
+ if (format === "text" || format === "json") {
379991
+ return format;
379992
+ }
379993
+ throw new Error(`Invalid format "${format}". Expected one of: text, json.`);
379994
+ }
379995
+ function omitOutput(value) {
379996
+ const copy = { ...value };
379997
+ delete copy.output;
379998
+ return copy;
379999
+ }
380000
+ function stripOutput(deployment) {
380001
+ const stripped = { ...deployment };
380002
+ if (stripped.error) {
380003
+ stripped.error = omitOutput(stripped.error);
380004
+ }
380005
+ if (stripped.builds) {
380006
+ stripped.builds = stripped.builds.map(omitOutput);
380007
+ }
380008
+ if (stripped.pendingActions) {
380009
+ stripped.pendingActions = stripped.pendingActions.map(
380010
+ (a) => a.type === "build_failed" ? omitOutput(a) : a
380011
+ );
380012
+ }
380013
+ return stripped;
380014
+ }
380015
+ function stateColor(state) {
380016
+ switch (state) {
380017
+ case "active":
380018
+ case "completed":
380019
+ return ANSI2.green;
380020
+ case "failed":
380021
+ case "cancelled":
380022
+ return ANSI2.red;
380023
+ case "superseded":
380024
+ return ANSI2.dim;
380025
+ default:
380026
+ return ANSI2.yellow;
380027
+ }
380028
+ }
380029
+ function colored(color, text) {
380030
+ return `${color}${text}${ANSI2.reset}`;
380031
+ }
380032
+ function formatDuration(startIso, endIso) {
380033
+ const ms = new Date(endIso).getTime() - new Date(startIso).getTime();
380034
+ if (!Number.isFinite(ms) || ms < 0) return null;
380035
+ const totalSeconds = Math.round(ms / 1e3);
380036
+ const minutes = Math.floor(totalSeconds / 60);
380037
+ const seconds = totalSeconds % 60;
380038
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
380039
+ }
380040
+ function formatBytes3(bytes) {
380041
+ if (bytes < 1024) return `${bytes} B`;
380042
+ return `${(bytes / 1024).toFixed(1)} KB`;
380043
+ }
380044
+ function renderDeployment(deployment, includeOutput) {
380045
+ const lines = [];
380046
+ lines.push(
380047
+ `${ANSI2.bold}Deployment ${deployment.id}${ANSI2.reset} ${ANSI2.dim}(${deployment.environment})${ANSI2.reset}`
380048
+ );
380049
+ lines.push(
380050
+ ` State: ${colored(stateColor(deployment.state), deployment.state)}`
380051
+ );
380052
+ if (deployment.stateMessage) {
380053
+ lines.push(` Message: ${deployment.stateMessage}`);
380054
+ }
380055
+ if (deployment.triggeredBy) {
380056
+ lines.push(` Triggered: ${deployment.triggeredBy}`);
380057
+ }
380058
+ if (deployment.gitCommitSha) {
380059
+ lines.push(` Commit: ${deployment.gitCommitSha}`);
380060
+ }
380061
+ lines.push(` Created: ${deployment.createdAt}`);
380062
+ if (deployment.completedAt) {
380063
+ const duration = deployment.startedAt ? formatDuration(deployment.startedAt, deployment.completedAt) : null;
380064
+ lines.push(
380065
+ ` Completed: ${deployment.completedAt}${duration ? ` ${ANSI2.dim}(took ${duration})${ANSI2.reset}` : ""}`
380066
+ );
380067
+ }
380068
+ const builds = deployment.builds ?? [];
380069
+ if (builds.length > 0) {
380070
+ lines.push("");
380071
+ lines.push(`${ANSI2.bold}Builds${ANSI2.reset}`);
380072
+ const nameWidth = Math.max(...builds.map((b) => b.name.length));
380073
+ for (const build of builds) {
380074
+ const marker = build.state === "failed" ? "\u2716" : build.state === "completed" ? "\u2714" : "\u2026";
380075
+ let line = ` ${colored(stateColor(build.state), marker)} ${build.name.padEnd(nameWidth)} ${colored(stateColor(build.state), build.state)}`;
380076
+ if (build.state === "failed" && build.error) {
380077
+ line += ` ${ANSI2.dim}\u2014 ${build.error}${ANSI2.reset}`;
380078
+ }
380079
+ lines.push(line);
380080
+ }
380081
+ }
380082
+ const pendingActions = deployment.pendingActions ?? [];
380083
+ const missingSecrets = pendingActions.flatMap(
380084
+ (a) => a.type === "missing_secrets" ? a.secrets : []
380085
+ );
380086
+ const missingConfigs = pendingActions.flatMap(
380087
+ (a) => a.type === "missing_configs" ? a.configs : []
380088
+ );
380089
+ if (missingSecrets.length > 0 || missingConfigs.length > 0) {
380090
+ lines.push("");
380091
+ lines.push(colored(ANSI2.yellow, `${ANSI2.bold}Action required${ANSI2.reset}`));
380092
+ if (missingSecrets.length > 0) {
380093
+ lines.push(` Missing secrets: ${missingSecrets.join(", ")}`);
380094
+ }
380095
+ if (missingConfigs.length > 0) {
380096
+ lines.push(` Missing configs: ${missingConfigs.join(", ")}`);
380097
+ }
380098
+ }
380099
+ if (deployment.error) {
380100
+ lines.push("");
380101
+ lines.push(
380102
+ colored(
380103
+ ANSI2.red,
380104
+ `${ANSI2.bold}${formatErrorCode(deployment.error.code)}:${ANSI2.reset}${ANSI2.red} ${deployment.error.message}`
380105
+ )
380106
+ );
380107
+ if (deployment.error.resource) {
380108
+ lines.push(`${ANSI2.dim}Resource: ${deployment.error.resource}${ANSI2.reset}`);
380109
+ }
380110
+ }
380111
+ const buildsWithOutput = builds.filter(
380112
+ (b) => typeof b.output === "string" && b.output.length > 0
380113
+ );
380114
+ const extraErrorOutput = deployment.error?.output && !buildsWithOutput.some((b) => b.output === deployment.error?.output) ? deployment.error.output : null;
380115
+ if (includeOutput) {
380116
+ const ordered = [...buildsWithOutput].sort(
380117
+ (a, b) => Number(b.state === "failed") - Number(a.state === "failed")
380118
+ );
380119
+ for (const build of ordered) {
380120
+ lines.push("");
380121
+ lines.push(`${ANSI2.bold}Output (${build.name})${ANSI2.reset}`);
380122
+ lines.push(build.output.trimEnd());
380123
+ }
380124
+ if (extraErrorOutput) {
380125
+ lines.push("");
380126
+ lines.push(`${ANSI2.bold}Output${ANSI2.reset}`);
380127
+ lines.push(extraErrorOutput.trimEnd());
380128
+ }
380129
+ } else {
380130
+ const totalBytes = buildsWithOutput.reduce((sum, b) => sum + b.output.length, 0) + (extraErrorOutput?.length ?? 0);
380131
+ if (totalBytes > 0) {
380132
+ lines.push("");
380133
+ lines.push(
380134
+ `${ANSI2.dim}Output hidden (${formatBytes3(totalBytes)}). Run \`specific deployment show ${deployment.id} --output\` to print it.${ANSI2.reset}`
380135
+ );
380136
+ }
380137
+ }
380138
+ return lines.join("\n") + "\n";
380139
+ }
380140
+
379753
380141
  // src/lib/domain/format-dns-instructions.ts
379754
380142
  function isApex(domain) {
379755
380143
  return domain.split(".").length === 2;
@@ -379830,7 +380218,7 @@ async function domainAddCommand(service, domain, options2) {
379830
380218
  var program = new Command();
379831
380219
  var env = "production";
379832
380220
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
379833
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.181").enablePositionalOptions();
380221
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.184").enablePositionalOptions();
379834
380222
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
379835
380223
  Examples:
379836
380224
  $ specific init
@@ -379843,13 +380231,14 @@ Examples:
379843
380231
  program.command("check").description("Validate specific.hcl configuration").addHelpText("after", `
379844
380232
  Examples:
379845
380233
  $ specific check`).action(checkCommand);
379846
- program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via public *.tunnel.spcf.app URLs").addHelpText("after", `
380234
+ program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via public *.tunnel.spcf.app URLs").option("--services <services...>", "Only start these services and everything they depend on").addHelpText("after", `
379847
380235
  Examples:
379848
380236
  $ specific dev
380237
+ $ specific dev --services api worker
379849
380238
  $ specific dev --tunnel
379850
380239
  $ specific dev --key feature-branch`).action((options2) => {
379851
380240
  const key = options2.key ?? getDefaultKey();
379852
- devCommand(key, options2.tunnel ?? false);
380241
+ devCommand(key, options2.tunnel ?? false, options2.services ?? []);
379853
380242
  });
379854
380243
  program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("-e, --environment <name|id>", "Target environment (auto-selected if only one exists)").option("--secret <key=value...>", "Secret values (repeatable)").option("--config <key=value...>", "Config values (repeatable)").option("--preview", "Deploy to an ephemeral preview environment").addHelpText("after", `
379855
380244
  Examples:
@@ -379912,9 +380301,28 @@ Examples:
379912
380301
  $ specific domain add web example.com --environment staging`).action((service, domain, options2) => {
379913
380302
  domainAddCommand(service, domain, options2);
379914
380303
  });
379915
- program.command("status").description("Show project, environments, and deployment status").addHelpText("after", `
380304
+ var deploymentCmd = program.command("deployment").description("Inspect deployments");
380305
+ deploymentCmd.command("show [id]").description(
380306
+ "Show a deployment's state, builds, and errors (defaults to the latest deployment)"
380307
+ ).option("-e, --environment <name|id>", "Target environment (defaults to the current one; ignored when an ID is given)").option(
380308
+ "--format <text|json>",
380309
+ "Output format (defaults to text on a TTY, json when piped)"
380310
+ ).option(
380311
+ "--output",
380312
+ "Include build output (logs); can be large, so it is hidden by default"
380313
+ ).addHelpText("after", `
380314
+ Examples:
380315
+ $ specific deployment show
380316
+ $ specific deployment show dep_0abc123
380317
+ $ specific deployment show dep_0abc123 --output
380318
+ $ specific deployment show --environment staging
380319
+ $ specific deployment show -e preview-a1b2c3d4 --format json`).action((id, options2) => {
380320
+ deploymentShowCommand(id, options2);
380321
+ });
380322
+ program.command("status").description("Show project, environments, and deployment status").option("--previews", "Show detailed preview environment and GitHub PR preview configuration").addHelpText("after", `
379916
380323
  Examples:
379917
- $ specific status`).action(statusCommand);
380324
+ $ specific status
380325
+ $ specific status --previews`).action((options2) => statusCommand(options2));
379918
380326
  program.command("query [sql]").description(
379919
380327
  "Run a SQL query against your environment's observability data (ClickHouse), or a Postgres database with --db"
379920
380328
  ).option("-e, --environment <name|id>", "Target environment (defaults to the current one)").option(