@specific.dev/cli 0.1.43 → 0.1.45

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 (66) 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 +4 -4
  4. package/dist/admin/__next._full.txt +24 -17
  5. package/dist/admin/__next._head.txt +4 -4
  6. package/dist/admin/__next._index.txt +10 -4
  7. package/dist/admin/__next._tree.txt +3 -2
  8. package/dist/admin/_next/static/chunks/0cb2c4291ba35581.css +4 -0
  9. package/dist/admin/_next/static/chunks/63473a6cb811ed42.js +5 -0
  10. package/dist/admin/_next/static/chunks/65ce370ab86b6df6.js +1 -0
  11. package/dist/admin/_next/static/chunks/6877369fbd84f127.js +1 -0
  12. package/dist/admin/_next/static/chunks/721087f8fbaa34b3.js +1 -0
  13. package/dist/admin/_next/static/chunks/9f53491ced2668ee.js +1 -0
  14. package/dist/admin/_next/static/chunks/a308451471d4cb39.js +1 -0
  15. package/dist/admin/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
  16. package/dist/admin/_next/static/chunks/{ff1a16fafef87110.js → b2b4aada246f4749.js} +1 -1
  17. package/dist/admin/_next/static/chunks/b4fa9a08d6dc37c1.js +2 -0
  18. package/dist/admin/_next/static/chunks/db72df2e613a023e.js +5 -0
  19. package/dist/admin/_next/static/chunks/e3f73cf77dd1ede1.js +1 -0
  20. package/dist/admin/_next/static/chunks/turbopack-ebee0930f5a58b67.js +4 -0
  21. package/dist/admin/_next/static/media/1bffadaabf893a1e-s.7cd81963.woff2 +0 -0
  22. package/dist/admin/_next/static/media/2bbe8d2671613f1f-s.76dcb0b2.woff2 +0 -0
  23. package/dist/admin/_next/static/media/2c55a0e60120577a-s.2a48534a.woff2 +0 -0
  24. package/dist/admin/_next/static/media/5476f68d60460930-s.c995e352.woff2 +0 -0
  25. package/dist/admin/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2 +0 -0
  26. package/dist/admin/_next/static/media/9c72aa0f40e4eef8-s.18a48cbc.woff2 +0 -0
  27. package/dist/admin/_next/static/media/ad66f9afd8947f86-s.7a40eb73.woff2 +0 -0
  28. package/dist/admin/_next/static/media/icon.456b8582.svg +12 -0
  29. package/dist/admin/_not-found/__next._full.txt +19 -13
  30. package/dist/admin/_not-found/__next._head.txt +4 -4
  31. package/dist/admin/_not-found/__next._index.txt +10 -4
  32. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +2 -2
  33. package/dist/admin/_not-found/__next._not-found.txt +3 -3
  34. package/dist/admin/_not-found/__next._tree.txt +2 -2
  35. package/dist/admin/_not-found/index.html +1 -1
  36. package/dist/admin/_not-found/index.txt +19 -13
  37. package/dist/admin/databases/__next._full.txt +24 -17
  38. package/dist/admin/databases/__next._head.txt +4 -4
  39. package/dist/admin/databases/__next._index.txt +10 -4
  40. package/dist/admin/databases/__next._tree.txt +3 -2
  41. package/dist/admin/databases/__next.databases.__PAGE__.txt +4 -4
  42. package/dist/admin/databases/__next.databases.txt +3 -3
  43. package/dist/admin/databases/index.html +1 -1
  44. package/dist/admin/databases/index.txt +24 -17
  45. package/dist/admin/icon.svg +12 -0
  46. package/dist/admin/index.html +1 -1
  47. package/dist/admin/index.txt +24 -17
  48. package/dist/cli.js +308 -497
  49. package/dist/docs/integrations/nextjs.md +18 -0
  50. package/dist/docs/migrations/supabase.md +18 -0
  51. package/dist/docs/secrets-config.md +52 -8
  52. package/package.json +12 -4
  53. package/dist/admin/_next/static/chunks/1a608619ba3183f8.js +0 -5
  54. package/dist/admin/_next/static/chunks/1da818a0086b4acf.css +0 -3
  55. package/dist/admin/_next/static/chunks/42730c0491633b9d.js +0 -5
  56. package/dist/admin/_next/static/chunks/465f799faf41e6df.js +0 -1
  57. package/dist/admin/_next/static/chunks/806bdb8e4a6a9b95.js +0 -4
  58. package/dist/admin/_next/static/chunks/9054c84ba21a4c14.js +0 -2
  59. package/dist/admin/_next/static/chunks/b71388016463cab2.js +0 -1
  60. package/dist/admin/_next/static/chunks/cba5c081fc9dc612.js +0 -2
  61. package/dist/admin/_next/static/chunks/d2be314c3ece3fbe.js +0 -1
  62. package/dist/admin/_next/static/chunks/dde2c8e6322d1671.js +0 -1
  63. package/dist/admin/_next/static/chunks/turbopack-22b7312525502d51.js +0 -4
  64. /package/dist/admin/_next/static/{3dGrdrrH7RVk5ixe6lgRQ → WUdRH_Qksuvhq_ji1IkHb}/_buildManifest.js +0 -0
  65. /package/dist/admin/_next/static/{3dGrdrrH7RVk5ixe6lgRQ → WUdRH_Qksuvhq_ji1IkHb}/_clientMiddlewareManifest.json +0 -0
  66. /package/dist/admin/_next/static/{3dGrdrrH7RVk5ixe6lgRQ → WUdRH_Qksuvhq_ji1IkHb}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  mod
39
39
  ));
40
40
 
41
- // ../../node_modules/is-docker/index.js
41
+ // node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
42
42
  import fs4 from "node:fs";
43
43
  function hasDockerEnv() {
44
44
  try {
@@ -63,11 +63,11 @@ function isDocker() {
63
63
  }
64
64
  var isDockerCached;
65
65
  var init_is_docker = __esm({
66
- "../../node_modules/is-docker/index.js"() {
66
+ "node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js"() {
67
67
  }
68
68
  });
69
69
 
70
- // ../../node_modules/is-inside-container/index.js
70
+ // node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
71
71
  import fs5 from "node:fs";
72
72
  function isInsideContainer() {
73
73
  if (cachedResult === void 0) {
@@ -77,7 +77,7 @@ function isInsideContainer() {
77
77
  }
78
78
  var cachedResult, hasContainerEnv;
79
79
  var init_is_inside_container = __esm({
80
- "../../node_modules/is-inside-container/index.js"() {
80
+ "node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js"() {
81
81
  init_is_docker();
82
82
  hasContainerEnv = () => {
83
83
  try {
@@ -90,13 +90,13 @@ var init_is_inside_container = __esm({
90
90
  }
91
91
  });
92
92
 
93
- // ../../node_modules/is-wsl/index.js
93
+ // node_modules/.pnpm/is-wsl@3.1.0/node_modules/is-wsl/index.js
94
94
  import process2 from "node:process";
95
95
  import os3 from "node:os";
96
96
  import fs6 from "node:fs";
97
97
  var isWsl, is_wsl_default;
98
98
  var init_is_wsl = __esm({
99
- "../../node_modules/is-wsl/index.js"() {
99
+ "node_modules/.pnpm/is-wsl@3.1.0/node_modules/is-wsl/index.js"() {
100
100
  init_is_inside_container();
101
101
  isWsl = () => {
102
102
  if (process2.platform !== "linux") {
@@ -118,14 +118,14 @@ var init_is_wsl = __esm({
118
118
  }
119
119
  });
120
120
 
121
- // ../../node_modules/powershell-utils/index.js
121
+ // node_modules/.pnpm/powershell-utils@0.1.0/node_modules/powershell-utils/index.js
122
122
  import process3 from "node:process";
123
123
  import { Buffer as Buffer2 } from "node:buffer";
124
124
  import { promisify } from "node:util";
125
125
  import childProcess from "node:child_process";
126
126
  var execFile, powerShellPath, executePowerShell;
127
127
  var init_powershell_utils = __esm({
128
- "../../node_modules/powershell-utils/index.js"() {
128
+ "node_modules/.pnpm/powershell-utils@0.1.0/node_modules/powershell-utils/index.js"() {
129
129
  execFile = promisify(childProcess.execFile);
130
130
  powerShellPath = () => `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
131
131
  executePowerShell = async (command, options2 = {}) => {
@@ -158,7 +158,7 @@ var init_powershell_utils = __esm({
158
158
  }
159
159
  });
160
160
 
161
- // ../../node_modules/wsl-utils/utilities.js
161
+ // node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/utilities.js
162
162
  function parseMountPointFromConfig(content) {
163
163
  for (const line of content.split("\n")) {
164
164
  if (/^\s*#/.test(line)) {
@@ -172,17 +172,17 @@ function parseMountPointFromConfig(content) {
172
172
  }
173
173
  }
174
174
  var init_utilities = __esm({
175
- "../../node_modules/wsl-utils/utilities.js"() {
175
+ "node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/utilities.js"() {
176
176
  }
177
177
  });
178
178
 
179
- // ../../node_modules/wsl-utils/index.js
179
+ // node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js
180
180
  import { promisify as promisify2 } from "node:util";
181
181
  import childProcess2 from "node:child_process";
182
182
  import fs7, { constants as fsConstants } from "node:fs/promises";
183
183
  var execFile2, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath2, canAccessPowerShellPromise, canAccessPowerShell, wslDefaultBrowser, convertWslPathToWindows;
184
184
  var init_wsl_utils = __esm({
185
- "../../node_modules/wsl-utils/index.js"() {
185
+ "node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js"() {
186
186
  init_is_wsl();
187
187
  init_powershell_utils();
188
188
  init_utilities();
@@ -252,7 +252,7 @@ var init_wsl_utils = __esm({
252
252
  }
253
253
  });
254
254
 
255
- // ../../node_modules/define-lazy-prop/index.js
255
+ // node_modules/.pnpm/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js
256
256
  function defineLazyProperty(object, propertyName, valueGetter) {
257
257
  const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
258
258
  Object.defineProperty(object, propertyName, {
@@ -270,11 +270,11 @@ function defineLazyProperty(object, propertyName, valueGetter) {
270
270
  return object;
271
271
  }
272
272
  var init_define_lazy_prop = __esm({
273
- "../../node_modules/define-lazy-prop/index.js"() {
273
+ "node_modules/.pnpm/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js"() {
274
274
  }
275
275
  });
276
276
 
277
- // ../../node_modules/default-browser-id/index.js
277
+ // node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
278
278
  import { promisify as promisify3 } from "node:util";
279
279
  import process4 from "node:process";
280
280
  import { execFile as execFile3 } from "node:child_process";
@@ -292,12 +292,12 @@ async function defaultBrowserId() {
292
292
  }
293
293
  var execFileAsync;
294
294
  var init_default_browser_id = __esm({
295
- "../../node_modules/default-browser-id/index.js"() {
295
+ "node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js"() {
296
296
  execFileAsync = promisify3(execFile3);
297
297
  }
298
298
  });
299
299
 
300
- // ../../node_modules/run-applescript/index.js
300
+ // node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js
301
301
  import process5 from "node:process";
302
302
  import { promisify as promisify4 } from "node:util";
303
303
  import { execFile as execFile4, execFileSync } from "node:child_process";
@@ -315,23 +315,23 @@ async function runAppleScript(script, { humanReadableOutput = true, signal } = {
315
315
  }
316
316
  var execFileAsync2;
317
317
  var init_run_applescript = __esm({
318
- "../../node_modules/run-applescript/index.js"() {
318
+ "node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js"() {
319
319
  execFileAsync2 = promisify4(execFile4);
320
320
  }
321
321
  });
322
322
 
323
- // ../../node_modules/bundle-name/index.js
323
+ // node_modules/.pnpm/bundle-name@4.1.0/node_modules/bundle-name/index.js
324
324
  async function bundleName(bundleId) {
325
325
  return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
326
326
  tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
327
327
  }
328
328
  var init_bundle_name = __esm({
329
- "../../node_modules/bundle-name/index.js"() {
329
+ "node_modules/.pnpm/bundle-name@4.1.0/node_modules/bundle-name/index.js"() {
330
330
  init_run_applescript();
331
331
  }
332
332
  });
333
333
 
334
- // ../../node_modules/default-browser/windows.js
334
+ // node_modules/.pnpm/default-browser@5.4.0/node_modules/default-browser/windows.js
335
335
  import { promisify as promisify5 } from "node:util";
336
336
  import { execFile as execFile5 } from "node:child_process";
337
337
  async function defaultBrowser(_execFileAsync = execFileAsync3) {
@@ -354,7 +354,7 @@ async function defaultBrowser(_execFileAsync = execFileAsync3) {
354
354
  }
355
355
  var execFileAsync3, windowsBrowserProgIds, _windowsBrowserProgIdMap, UnknownBrowserError;
356
356
  var init_windows = __esm({
357
- "../../node_modules/default-browser/windows.js"() {
357
+ "node_modules/.pnpm/default-browser@5.4.0/node_modules/default-browser/windows.js"() {
358
358
  execFileAsync3 = promisify5(execFile5);
359
359
  windowsBrowserProgIds = {
360
360
  MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
@@ -381,7 +381,7 @@ var init_windows = __esm({
381
381
  }
382
382
  });
383
383
 
384
- // ../../node_modules/default-browser/index.js
384
+ // node_modules/.pnpm/default-browser@5.4.0/node_modules/default-browser/index.js
385
385
  import { promisify as promisify6 } from "node:util";
386
386
  import process6 from "node:process";
387
387
  import { execFile as execFile6 } from "node:child_process";
@@ -404,7 +404,7 @@ async function defaultBrowser2() {
404
404
  }
405
405
  var execFileAsync4, titleize;
406
406
  var init_default_browser = __esm({
407
- "../../node_modules/default-browser/index.js"() {
407
+ "node_modules/.pnpm/default-browser@5.4.0/node_modules/default-browser/index.js"() {
408
408
  init_default_browser_id();
409
409
  init_bundle_name();
410
410
  init_windows();
@@ -414,17 +414,17 @@ var init_default_browser = __esm({
414
414
  }
415
415
  });
416
416
 
417
- // ../../node_modules/is-in-ssh/index.js
417
+ // node_modules/.pnpm/is-in-ssh@1.0.0/node_modules/is-in-ssh/index.js
418
418
  import process7 from "node:process";
419
419
  var isInSsh, is_in_ssh_default;
420
420
  var init_is_in_ssh = __esm({
421
- "../../node_modules/is-in-ssh/index.js"() {
421
+ "node_modules/.pnpm/is-in-ssh@1.0.0/node_modules/is-in-ssh/index.js"() {
422
422
  isInSsh = Boolean(process7.env.SSH_CONNECTION || process7.env.SSH_CLIENT || process7.env.SSH_TTY);
423
423
  is_in_ssh_default = isInSsh;
424
424
  }
425
425
  });
426
426
 
427
- // ../../node_modules/open/index.js
427
+ // node_modules/.pnpm/open@11.0.0/node_modules/open/index.js
428
428
  var open_exports = {};
429
429
  __export(open_exports, {
430
430
  apps: () => apps,
@@ -457,7 +457,7 @@ function detectPlatformBinary({ [platform2]: platformBinary }, { wsl } = {}) {
457
457
  }
458
458
  var fallbackAttemptSymbol, __dirname, localXdgOpenPath, platform2, arch, tryEachApp, baseOpen, open, openApp, apps, open_default;
459
459
  var init_open = __esm({
460
- "../../node_modules/open/index.js"() {
460
+ "node_modules/.pnpm/open@11.0.0/node_modules/open/index.js"() {
461
461
  init_wsl_utils();
462
462
  init_powershell_utils();
463
463
  init_define_lazy_prop();
@@ -744,9 +744,9 @@ var init_open = __esm({
744
744
  }
745
745
  });
746
746
 
747
- // ../../node_modules/hcl2-json-parser/dist/index.js
747
+ // node_modules/.pnpm/hcl2-json-parser@1.0.1/node_modules/hcl2-json-parser/dist/index.js
748
748
  var require_dist = __commonJS({
749
- "../../node_modules/hcl2-json-parser/dist/index.js"(exports, module) {
749
+ "node_modules/.pnpm/hcl2-json-parser@1.0.1/node_modules/hcl2-json-parser/dist/index.js"(exports, module) {
750
750
  "use strict";
751
751
  (function() {
752
752
  var $goVersion = "go1.18.10";
@@ -754,8 +754,8 @@ var require_dist = __commonJS({
754
754
  var $global, $module, $NaN = NaN;
755
755
  if ("undefined" != typeof window ? $global = window : "undefined" != typeof self ? $global = self : "undefined" != typeof global ? ($global = global).require = __require : $global = this, void 0 === $global || void 0 === $global.Array) throw new Error("no global object found");
756
756
  if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
757
- var fs25 = $global.require("fs");
758
- "object" == typeof fs25 && null !== fs25 && 0 !== Object.keys(fs25).length && ($global.fs = fs25);
757
+ var fs24 = $global.require("fs");
758
+ "object" == typeof fs24 && null !== fs24 && 0 !== Object.keys(fs24).length && ($global.fs = fs24);
759
759
  } catch (e) {
760
760
  }
761
761
  if (!$global.fs) {
@@ -183843,7 +183843,7 @@ function trackEvent(event, properties) {
183843
183843
  event,
183844
183844
  properties: {
183845
183845
  ...properties,
183846
- cli_version: "0.1.43",
183846
+ cli_version: "0.1.45",
183847
183847
  platform: process.platform,
183848
183848
  node_version: process.version,
183849
183849
  project_id: getProjectId(),
@@ -183911,7 +183911,7 @@ function appendOrCreateFile(filePath, content) {
183911
183911
  }
183912
183912
  function addToGitignore() {
183913
183913
  const gitignorePath = path6.join(process.cwd(), ".gitignore");
183914
- const entries = [".specific", "specific.secrets"];
183914
+ const entries = [".specific", "specific.local"];
183915
183915
  if (fs10.existsSync(gitignorePath)) {
183916
183916
  const existing = fs10.readFileSync(gitignorePath, "utf-8");
183917
183917
  const lines = existing.split("\n").map((l) => l.trim());
@@ -184172,7 +184172,7 @@ import Spinner3 from "ink-spinner";
184172
184172
  import * as fs11 from "fs";
184173
184173
  import * as path7 from "path";
184174
184174
 
184175
- // ../config/dist/parser.js
184175
+ // node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
184176
184176
  var import_hcl2_json_parser = __toESM(require_dist(), 1);
184177
184177
  var { parseToObject } = import_hcl2_json_parser.default;
184178
184178
  function parseReference(value) {
@@ -184740,13 +184740,13 @@ import Spinner4 from "ink-spinner";
184740
184740
  import * as fs19 from "fs";
184741
184741
  import * as path16 from "path";
184742
184742
 
184743
- // node_modules/chokidar/index.js
184743
+ // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
184744
184744
  import { EventEmitter } from "node:events";
184745
184745
  import { stat as statcb, Stats } from "node:fs";
184746
184746
  import { readdir as readdir2, stat as stat3 } from "node:fs/promises";
184747
184747
  import * as sp2 from "node:path";
184748
184748
 
184749
- // node_modules/readdirp/index.js
184749
+ // node_modules/.pnpm/readdirp@5.0.0/node_modules/readdirp/index.js
184750
184750
  import { lstat, readdir, realpath, stat } from "node:fs/promises";
184751
184751
  import { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from "node:path";
184752
184752
  import { Readable } from "node:stream";
@@ -184979,7 +184979,7 @@ function readdirp(root, options2 = {}) {
184979
184979
  return new ReaddirpStream(options2);
184980
184980
  }
184981
184981
 
184982
- // node_modules/chokidar/handler.js
184982
+ // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/handler.js
184983
184983
  import { watch as fs_watch, unwatchFile, watchFile } from "node:fs";
184984
184984
  import { realpath as fsrealpath, lstat as lstat2, open as open2, stat as stat2 } from "node:fs/promises";
184985
184985
  import { type as osType } from "node:os";
@@ -185738,7 +185738,7 @@ var NodeFsHandler = class {
185738
185738
  }
185739
185739
  };
185740
185740
 
185741
- // node_modules/chokidar/index.js
185741
+ // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
185742
185742
  var SLASH = "/";
185743
185743
  var SLASH_SLASH = "//";
185744
185744
  var ONE_DOT = ".";
@@ -187103,224 +187103,107 @@ function sleep2(ms) {
187103
187103
  // src/lib/dev/service-runner.ts
187104
187104
  import { spawn as spawn2 } from "child_process";
187105
187105
 
187106
- // src/lib/secrets/parser.ts
187106
+ // src/lib/local/parser.ts
187107
187107
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
187108
- import { readFile, writeFile, mkdir } from "fs/promises";
187108
+ import { readFile, writeFile } from "fs/promises";
187109
187109
  import { existsSync as existsSync10 } from "fs";
187110
- import * as path11 from "path";
187111
- import * as crypto2 from "crypto";
187112
187110
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
187113
- var SECRETS_FILE = "specific.secrets";
187114
- var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
187115
- async function parseSecretsFile(content) {
187111
+ var LOCAL_FILE = "specific.local";
187112
+ var HEADER_COMMENT = `# Local secrets and configuration
187113
+ # Do not commit this file
187114
+
187115
+ `;
187116
+ async function parseLocalFile(content) {
187116
187117
  const secrets = /* @__PURE__ */ new Map();
187118
+ const configs = /* @__PURE__ */ new Map();
187117
187119
  if (!content.trim()) {
187118
- return secrets;
187120
+ return { secrets, configs };
187119
187121
  }
187120
187122
  const parsed = await parseToObject2(content);
187121
- for (const [key, value] of Object.entries(parsed)) {
187122
- if (typeof value === "string") {
187123
- secrets.set(key, value);
187124
- }
187125
- }
187126
- return secrets;
187127
- }
187128
- async function loadSecrets() {
187129
- if (!existsSync10(SECRETS_FILE)) {
187130
- return /* @__PURE__ */ new Map();
187131
- }
187132
- const content = await readFile(SECRETS_FILE, "utf-8");
187133
- return await parseSecretsFile(content);
187134
- }
187135
- function generateRandomString(length = 64) {
187136
- const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
187137
- const bytes = crypto2.randomBytes(length);
187138
- let result = "";
187139
- for (let i = 0; i < length; i++) {
187140
- result += chars[bytes[i] % chars.length];
187141
- }
187142
- return result;
187143
- }
187144
- async function loadGeneratedSecrets() {
187145
- if (!existsSync10(GENERATED_SECRETS_FILE)) {
187146
- return /* @__PURE__ */ new Map();
187147
- }
187148
- const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
187149
- const parsed = JSON.parse(content);
187150
- return new Map(Object.entries(parsed));
187151
- }
187152
- async function saveGeneratedSecret(name, value) {
187153
- let secrets = {};
187154
- if (existsSync10(GENERATED_SECRETS_FILE)) {
187155
- const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
187156
- secrets = JSON.parse(content);
187157
- }
187158
- secrets[name] = value;
187159
- const dir = path11.dirname(GENERATED_SECRETS_FILE);
187160
- if (!existsSync10(dir)) {
187161
- await mkdir(dir, { recursive: true });
187162
- }
187163
- await writeFile(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
187164
- }
187165
- async function prepareSecrets(secretsConfig, isDevMode = false) {
187166
- const userSecrets = await loadSecrets();
187167
- const generatedSecrets = await loadGeneratedSecrets();
187168
- const finalSecrets = /* @__PURE__ */ new Map();
187169
- for (const secretDef of secretsConfig) {
187170
- const userValue = userSecrets.get(secretDef.name);
187171
- if (userValue !== void 0) {
187172
- finalSecrets.set(secretDef.name, userValue);
187173
- continue;
187174
- }
187175
- if (secretDef.generated) {
187176
- let generatedValue = generatedSecrets.get(secretDef.name);
187177
- if (generatedValue === void 0) {
187178
- generatedValue = generateRandomString(secretDef.length ?? 64);
187179
- await saveGeneratedSecret(secretDef.name, generatedValue);
187123
+ if (parsed.secrets) {
187124
+ const secretsBlocks = Array.isArray(parsed.secrets) ? parsed.secrets : [parsed.secrets];
187125
+ for (const block of secretsBlocks) {
187126
+ if (block && typeof block === "object") {
187127
+ for (const [key, value] of Object.entries(block)) {
187128
+ if (typeof value === "string") {
187129
+ secrets.set(key, value);
187130
+ }
187131
+ }
187180
187132
  }
187181
- finalSecrets.set(secretDef.name, generatedValue);
187182
- continue;
187183
- }
187184
- if (isDevMode && secretDef.dev?.required === false) {
187185
- finalSecrets.set(secretDef.name, "");
187186
- continue;
187187
187133
  }
187188
187134
  }
187189
- return finalSecrets;
187190
- }
187191
- var HEADER_COMMENT = "# Do not commit this file - it contains secrets\n\n";
187192
- async function saveSecret(name, value) {
187193
- let content = "";
187194
- if (existsSync10(SECRETS_FILE)) {
187195
- content = await readFile(SECRETS_FILE, "utf-8");
187196
- } else {
187197
- content = HEADER_COMMENT;
187198
- }
187199
- const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
187200
- const secretPattern = new RegExp(`^${name}\\s*=\\s*"[^"]*"\\s*$`, "m");
187201
- if (secretPattern.test(content)) {
187202
- content = content.replace(secretPattern, `${name} = "${escapedValue}"`);
187203
- } else {
187204
- content = content.trimEnd() + `
187205
- ${name} = "${escapedValue}"
187206
- `;
187207
- }
187208
- await writeFile(SECRETS_FILE, content);
187209
- }
187210
- function findUsedSecrets(services, isDevMode) {
187211
- const used = /* @__PURE__ */ new Set();
187212
- for (const service of services) {
187213
- const mergedEnv = isDevMode ? { ...service.env, ...service.dev?.env } : service.env;
187214
- for (const value of Object.values(mergedEnv ?? {})) {
187215
- if (typeof value === "object" && value.type === "secret") {
187216
- used.add(value.name);
187135
+ if (parsed.config) {
187136
+ const configBlocks = Array.isArray(parsed.config) ? parsed.config : [parsed.config];
187137
+ for (const block of configBlocks) {
187138
+ if (block && typeof block === "object") {
187139
+ for (const [key, value] of Object.entries(block)) {
187140
+ if (typeof value === "string") {
187141
+ configs.set(key, value);
187142
+ }
187143
+ }
187217
187144
  }
187218
187145
  }
187219
187146
  }
187220
- return used;
187221
- }
187222
- function findMissingSecrets(secretsDef, preparedSecrets, usedSecrets) {
187223
- const missing = [];
187224
- for (const secret of secretsDef) {
187225
- if (secret.generated) continue;
187226
- if (usedSecrets && !usedSecrets.has(secret.name)) continue;
187227
- if (!preparedSecrets.has(secret.name)) {
187228
- missing.push(secret.name);
187229
- }
187230
- }
187231
- return missing;
187147
+ return { secrets, configs };
187232
187148
  }
187233
-
187234
- // src/lib/config/parser.ts
187235
- var import_hcl2_json_parser3 = __toESM(require_dist(), 1);
187236
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
187237
- import { existsSync as existsSync11 } from "fs";
187238
- var { parseToObject: parseToObject3 } = import_hcl2_json_parser3.default;
187239
- var CONFIG_FILE = "specific.config";
187240
- async function parseConfigFile(content) {
187241
- const configs = /* @__PURE__ */ new Map();
187242
- if (!content.trim()) {
187243
- return configs;
187244
- }
187245
- const parsed = await parseToObject3(content);
187246
- for (const [key, value] of Object.entries(parsed)) {
187247
- if (typeof value === "string") {
187248
- configs.set(key, value);
187249
- }
187149
+ async function loadLocal() {
187150
+ if (!existsSync10(LOCAL_FILE)) {
187151
+ return { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
187250
187152
  }
187251
- return configs;
187153
+ const content = await readFile(LOCAL_FILE, "utf-8");
187154
+ return await parseLocalFile(content);
187252
187155
  }
187253
- async function loadConfigs() {
187254
- if (!existsSync11(CONFIG_FILE)) {
187255
- return /* @__PURE__ */ new Map();
187256
- }
187257
- const content = await readFile2(CONFIG_FILE, "utf-8");
187258
- return await parseConfigFile(content);
187156
+ function escapeHclValue(value) {
187157
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
187259
187158
  }
187260
- async function saveConfig(name, value) {
187261
- let content = "";
187262
- if (existsSync11(CONFIG_FILE)) {
187263
- content = await readFile2(CONFIG_FILE, "utf-8");
187264
- } else {
187265
- content = `# Configuration values for this project
187266
- # These values override defaults defined in specific.hcl
187267
-
187159
+ function updateBlockValue(content, blockName, key, value) {
187160
+ const escapedValue = escapeHclValue(value);
187161
+ const newLine = ` ${key} = "${escapedValue}"`;
187162
+ const blockRegex = new RegExp(`(${blockName}\\s*\\{)([^}]*)(\\})`, "s");
187163
+ const blockMatch = content.match(blockRegex);
187164
+ if (blockMatch) {
187165
+ const blockContent = blockMatch[2];
187166
+ const keyRegex = new RegExp(`^(\\s*)${key}\\s*=\\s*"[^"]*"\\s*$`, "m");
187167
+ const keyMatch = blockContent.match(keyRegex);
187168
+ if (keyMatch) {
187169
+ const updatedBlockContent = blockContent.replace(keyRegex, newLine);
187170
+ return content.replace(blockRegex, `$1${updatedBlockContent}$3`);
187171
+ } else {
187172
+ const trimmedContent = blockContent.trimEnd();
187173
+ const newBlockContent = trimmedContent ? `${trimmedContent}
187174
+ ${newLine}
187175
+ ` : `
187176
+ ${newLine}
187268
187177
  `;
187269
- }
187270
- const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
187271
- const configPattern = new RegExp(`^${name}\\s*=\\s*"[^"]*"\\s*$`, "m");
187272
- if (configPattern.test(content)) {
187273
- content = content.replace(configPattern, `${name} = "${escapedValue}"`);
187178
+ return content.replace(blockRegex, `$1${newBlockContent}$3`);
187179
+ }
187274
187180
  } else {
187275
- content = content.trimEnd() + `
187276
- ${name} = "${escapedValue}"
187277
- `;
187278
- }
187279
- await writeFile2(CONFIG_FILE, content);
187181
+ const newBlock = `${blockName} {
187182
+ ${newLine}
187280
187183
  }
187281
- async function prepareConfigs(configsDef, environmentOverrides, isDevMode = false) {
187282
- const userConfigs = await loadConfigs();
187283
- const finalConfigs = /* @__PURE__ */ new Map();
187284
- for (const configDef of configsDef) {
187285
- const userValue = userConfigs.get(configDef.name);
187286
- if (userValue !== void 0) {
187287
- finalConfigs.set(configDef.name, userValue);
187288
- continue;
187289
- }
187290
- const envOverride = environmentOverrides?.[configDef.name];
187291
- if (envOverride !== void 0) {
187292
- finalConfigs.set(configDef.name, envOverride);
187293
- continue;
187294
- }
187295
- const effectiveDefault = isDevMode && configDef.dev?.default !== void 0 ? configDef.dev.default : configDef.default;
187296
- if (effectiveDefault !== void 0) {
187297
- finalConfigs.set(configDef.name, effectiveDefault);
187298
- continue;
187299
- }
187184
+ `;
187185
+ return content.trimEnd() + "\n\n" + newBlock;
187300
187186
  }
187301
- return finalConfigs;
187302
187187
  }
187303
- function findUsedConfigs(services, isDevMode) {
187304
- const used = /* @__PURE__ */ new Set();
187305
- for (const service of services) {
187306
- const mergedEnv = isDevMode ? { ...service.env, ...service.dev?.env } : service.env;
187307
- for (const value of Object.values(mergedEnv ?? {})) {
187308
- if (typeof value === "object" && value.type === "config") {
187309
- used.add(value.name);
187310
- }
187311
- }
187188
+ async function saveLocalSecret(name, value) {
187189
+ let content = "";
187190
+ if (existsSync10(LOCAL_FILE)) {
187191
+ content = await readFile(LOCAL_FILE, "utf-8");
187192
+ } else {
187193
+ content = HEADER_COMMENT;
187312
187194
  }
187313
- return used;
187195
+ content = updateBlockValue(content, "secrets", name, value);
187196
+ await writeFile(LOCAL_FILE, content);
187314
187197
  }
187315
- function findMissingConfigs(configsDef, preparedConfigs, usedConfigs) {
187316
- const missing = [];
187317
- for (const config of configsDef) {
187318
- if (usedConfigs && !usedConfigs.has(config.name)) continue;
187319
- if (!preparedConfigs.has(config.name)) {
187320
- missing.push(config.name);
187321
- }
187198
+ async function saveLocalConfig(name, value) {
187199
+ let content = "";
187200
+ if (existsSync10(LOCAL_FILE)) {
187201
+ content = await readFile(LOCAL_FILE, "utf-8");
187202
+ } else {
187203
+ content = HEADER_COMMENT;
187322
187204
  }
187323
- return missing;
187205
+ content = updateBlockValue(content, "config", name, value);
187206
+ await writeFile(LOCAL_FILE, content);
187324
187207
  }
187325
187208
 
187326
187209
  // src/lib/dev/env-resolver.ts
@@ -187329,9 +187212,12 @@ var MissingSecretError = class extends Error {
187329
187212
  super(
187330
187213
  `Missing secret '${secretName}'
187331
187214
 
187332
- This secret is declared in specific.hcl but has no value in ${SECRETS_FILE}.
187215
+ This secret is declared in specific.hcl but has no value in ${LOCAL_FILE}.
187333
187216
 
187334
- Run: specific secrets set ${secretName}`
187217
+ Add it to the secrets block in ${LOCAL_FILE}:
187218
+ secrets {
187219
+ ${secretName} = "your-value"
187220
+ }`
187335
187221
  );
187336
187222
  this.secretName = secretName;
187337
187223
  this.name = "MissingSecretError";
@@ -187342,9 +187228,12 @@ var MissingConfigError = class extends Error {
187342
187228
  super(
187343
187229
  `Missing config '${configName}'
187344
187230
 
187345
- This config is declared in specific.hcl but has no default value, environment override, or value in ${CONFIG_FILE}.
187231
+ This config is declared in specific.hcl but has no default value, environment override, or value in ${LOCAL_FILE}.
187346
187232
 
187347
- Run: specific config set ${configName} <value>`
187233
+ Add it to the config block in ${LOCAL_FILE}:
187234
+ config {
187235
+ ${configName} = "your-value"
187236
+ }`
187348
187237
  );
187349
187238
  this.configName = configName;
187350
187239
  this.name = "MissingConfigError";
@@ -187608,7 +187497,7 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187608
187497
 
187609
187498
  // src/lib/dev/instance-state.ts
187610
187499
  import * as fs15 from "fs";
187611
- import * as path12 from "path";
187500
+ import * as path11 from "path";
187612
187501
  var InstanceStateManager = class {
187613
187502
  stateDir;
187614
187503
  statePath;
@@ -187617,9 +187506,9 @@ var InstanceStateManager = class {
187617
187506
  key;
187618
187507
  constructor(projectRoot, key = "default") {
187619
187508
  this.key = key;
187620
- this.stateDir = path12.join(projectRoot, ".specific", "keys", key);
187621
- this.statePath = path12.join(this.stateDir, "state.json");
187622
- this.lockPath = path12.join(this.stateDir, "state.lock");
187509
+ this.stateDir = path11.join(projectRoot, ".specific", "keys", key);
187510
+ this.statePath = path11.join(this.stateDir, "state.json");
187511
+ this.lockPath = path11.join(this.stateDir, "state.lock");
187623
187512
  }
187624
187513
  getKey() {
187625
187514
  return this.key;
@@ -187821,11 +187710,11 @@ var InstanceStateManager = class {
187821
187710
  import * as http from "http";
187822
187711
  import * as https from "https";
187823
187712
  import * as fs16 from "fs";
187824
- import * as path13 from "path";
187713
+ import * as path12 from "path";
187825
187714
  import { fileURLToPath as fileURLToPath3 } from "url";
187826
187715
  import httpProxy from "http-proxy";
187827
- var __dirname3 = path13.dirname(fileURLToPath3(import.meta.url));
187828
- var adminDir = path13.join(__dirname3, "admin");
187716
+ var __dirname3 = path12.dirname(fileURLToPath3(import.meta.url));
187717
+ var adminDir = path12.join(__dirname3, "admin");
187829
187718
  var HTTP_PORT = 80;
187830
187719
  var HTTPS_PORT = 443;
187831
187720
  var DOMAIN_SUFFIX = ".local.spcf.app";
@@ -188111,9 +188000,9 @@ function serveStaticFile(res, pathname) {
188111
188000
  filePath = filePath + "index.html";
188112
188001
  }
188113
188002
  const relativePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
188114
- const fullPath = path13.join(adminDir, relativePath);
188115
- const resolvedPath = path13.resolve(fullPath);
188116
- const resolvedAdminDir = path13.resolve(adminDir);
188003
+ const fullPath = path12.join(adminDir, relativePath);
188004
+ const resolvedPath = path12.resolve(fullPath);
188005
+ const resolvedAdminDir = path12.resolve(adminDir);
188117
188006
  if (!resolvedPath.startsWith(resolvedAdminDir)) {
188118
188007
  res.writeHead(403, { "Content-Type": "text/html" });
188119
188008
  res.end("<h1>Forbidden</h1>");
@@ -188124,11 +188013,11 @@ function serveStaticFile(res, pathname) {
188124
188013
  if (fs16.existsSync(htmlPath)) {
188125
188014
  return serveFile(res, htmlPath);
188126
188015
  }
188127
- const indexPath = path13.join(resolvedPath, "index.html");
188016
+ const indexPath = path12.join(resolvedPath, "index.html");
188128
188017
  if (fs16.existsSync(indexPath)) {
188129
188018
  return serveFile(res, indexPath);
188130
188019
  }
188131
- const notFoundPath = path13.join(adminDir, "404.html");
188020
+ const notFoundPath = path12.join(adminDir, "404.html");
188132
188021
  if (fs16.existsSync(notFoundPath)) {
188133
188022
  return serveFileContent(res, notFoundPath, "text/html", 404);
188134
188023
  }
@@ -188139,7 +188028,7 @@ function serveStaticFile(res, pathname) {
188139
188028
  serveFile(res, resolvedPath);
188140
188029
  }
188141
188030
  function serveFile(res, filePath) {
188142
- const ext = path13.extname(filePath).toLowerCase();
188031
+ const ext = path12.extname(filePath).toLowerCase();
188143
188032
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
188144
188033
  serveFileContent(res, filePath, contentType, 200);
188145
188034
  }
@@ -188202,6 +188091,101 @@ async function startAdminServer(getState) {
188202
188091
  // src/lib/dev/electric-manager.ts
188203
188092
  import * as net2 from "net";
188204
188093
  import { spawn as spawn3 } from "child_process";
188094
+
188095
+ // src/lib/secrets/parser.ts
188096
+ import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
188097
+ import { existsSync as existsSync13 } from "fs";
188098
+ import * as path13 from "path";
188099
+ import * as crypto2 from "crypto";
188100
+ var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
188101
+ async function loadSecrets() {
188102
+ const { secrets } = await loadLocal();
188103
+ return secrets;
188104
+ }
188105
+ function generateRandomString(length = 64) {
188106
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
188107
+ const bytes = crypto2.randomBytes(length);
188108
+ let result = "";
188109
+ for (let i = 0; i < length; i++) {
188110
+ result += chars[bytes[i] % chars.length];
188111
+ }
188112
+ return result;
188113
+ }
188114
+ async function loadGeneratedSecrets() {
188115
+ if (!existsSync13(GENERATED_SECRETS_FILE)) {
188116
+ return /* @__PURE__ */ new Map();
188117
+ }
188118
+ const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
188119
+ const parsed = JSON.parse(content);
188120
+ return new Map(Object.entries(parsed));
188121
+ }
188122
+ async function saveGeneratedSecret(name, value) {
188123
+ let secrets = {};
188124
+ if (existsSync13(GENERATED_SECRETS_FILE)) {
188125
+ const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
188126
+ secrets = JSON.parse(content);
188127
+ }
188128
+ secrets[name] = value;
188129
+ const dir = path13.dirname(GENERATED_SECRETS_FILE);
188130
+ if (!existsSync13(dir)) {
188131
+ await mkdir(dir, { recursive: true });
188132
+ }
188133
+ await writeFile2(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
188134
+ }
188135
+ async function prepareSecrets(secretsConfig, isDevMode = false) {
188136
+ const userSecrets = await loadSecrets();
188137
+ const generatedSecrets = await loadGeneratedSecrets();
188138
+ const finalSecrets = /* @__PURE__ */ new Map();
188139
+ for (const secretDef of secretsConfig) {
188140
+ const userValue = userSecrets.get(secretDef.name);
188141
+ if (userValue !== void 0) {
188142
+ finalSecrets.set(secretDef.name, userValue);
188143
+ continue;
188144
+ }
188145
+ if (secretDef.generated) {
188146
+ let generatedValue = generatedSecrets.get(secretDef.name);
188147
+ if (generatedValue === void 0) {
188148
+ generatedValue = generateRandomString(secretDef.length ?? 64);
188149
+ await saveGeneratedSecret(secretDef.name, generatedValue);
188150
+ }
188151
+ finalSecrets.set(secretDef.name, generatedValue);
188152
+ continue;
188153
+ }
188154
+ if (isDevMode && secretDef.dev?.required === false) {
188155
+ finalSecrets.set(secretDef.name, "");
188156
+ continue;
188157
+ }
188158
+ }
188159
+ return finalSecrets;
188160
+ }
188161
+ async function saveSecret(name, value) {
188162
+ await saveLocalSecret(name, value);
188163
+ }
188164
+ function findUsedSecrets(services, isDevMode) {
188165
+ const used = /* @__PURE__ */ new Set();
188166
+ for (const service of services) {
188167
+ const mergedEnv = isDevMode ? { ...service.env, ...service.dev?.env } : service.env;
188168
+ for (const value of Object.values(mergedEnv ?? {})) {
188169
+ if (typeof value === "object" && value.type === "secret") {
188170
+ used.add(value.name);
188171
+ }
188172
+ }
188173
+ }
188174
+ return used;
188175
+ }
188176
+ function findMissingSecrets(secretsDef, preparedSecrets, usedSecrets) {
188177
+ const missing = [];
188178
+ for (const secret of secretsDef) {
188179
+ if (secret.generated) continue;
188180
+ if (usedSecrets && !usedSecrets.has(secret.name)) continue;
188181
+ if (!preparedSecrets.has(secret.name)) {
188182
+ missing.push(secret.name);
188183
+ }
188184
+ }
188185
+ return missing;
188186
+ }
188187
+
188188
+ // src/lib/dev/electric-manager.ts
188205
188189
  async function startElectric(postgres, port, options2) {
188206
188190
  if (postgres.type !== "postgres") {
188207
188191
  throw new Error(
@@ -188926,6 +188910,59 @@ var ProxyRegistryManager = class {
188926
188910
  }
188927
188911
  };
188928
188912
 
188913
+ // src/lib/config/parser.ts
188914
+ async function loadConfigs() {
188915
+ const { configs } = await loadLocal();
188916
+ return configs;
188917
+ }
188918
+ async function saveConfig(name, value) {
188919
+ await saveLocalConfig(name, value);
188920
+ }
188921
+ async function prepareConfigs(configsDef, environmentOverrides, isDevMode = false) {
188922
+ const userConfigs = await loadConfigs();
188923
+ const finalConfigs = /* @__PURE__ */ new Map();
188924
+ for (const configDef of configsDef) {
188925
+ const userValue = userConfigs.get(configDef.name);
188926
+ if (userValue !== void 0) {
188927
+ finalConfigs.set(configDef.name, userValue);
188928
+ continue;
188929
+ }
188930
+ const envOverride = environmentOverrides?.[configDef.name];
188931
+ if (envOverride !== void 0) {
188932
+ finalConfigs.set(configDef.name, envOverride);
188933
+ continue;
188934
+ }
188935
+ const effectiveDefault = isDevMode && configDef.dev?.default !== void 0 ? configDef.dev.default : configDef.default;
188936
+ if (effectiveDefault !== void 0) {
188937
+ finalConfigs.set(configDef.name, effectiveDefault);
188938
+ continue;
188939
+ }
188940
+ }
188941
+ return finalConfigs;
188942
+ }
188943
+ function findUsedConfigs(services, isDevMode) {
188944
+ const used = /* @__PURE__ */ new Set();
188945
+ for (const service of services) {
188946
+ const mergedEnv = isDevMode ? { ...service.env, ...service.dev?.env } : service.env;
188947
+ for (const value of Object.values(mergedEnv ?? {})) {
188948
+ if (typeof value === "object" && value.type === "config") {
188949
+ used.add(value.name);
188950
+ }
188951
+ }
188952
+ }
188953
+ return used;
188954
+ }
188955
+ function findMissingConfigs(configsDef, preparedConfigs, usedConfigs) {
188956
+ const missing = [];
188957
+ for (const config of configsDef) {
188958
+ if (usedConfigs && !usedConfigs.has(config.name)) continue;
188959
+ if (!preparedConfigs.has(config.name)) {
188960
+ missing.push(config.name);
188961
+ }
188962
+ }
188963
+ return missing;
188964
+ }
188965
+
188929
188966
  // src/lib/ui/SecretInput.tsx
188930
188967
  import React4, { useState as useState3 } from "react";
188931
188968
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
@@ -189450,11 +189487,11 @@ function DevUI({ instanceKey }) {
189450
189487
  const errorParts = [];
189451
189488
  if (missingSecrets.length > 0) {
189452
189489
  errorParts.push(`Missing secrets: ${missingSecrets.join(", ")}
189453
- Add them to specific.secrets`);
189490
+ Add them to the secrets block in specific.local`);
189454
189491
  }
189455
189492
  if (missingConfigs.length > 0) {
189456
189493
  errorParts.push(`Missing configs: ${missingConfigs.join(", ")}
189457
- Add them to specific.config`);
189494
+ Add them to the config block in specific.local`);
189458
189495
  }
189459
189496
  setState((s) => ({
189460
189497
  ...s,
@@ -189579,6 +189616,7 @@ Add them to specific.config`);
189579
189616
  for (const s of services2) {
189580
189617
  runningServicePorts.set(s.name, s.ports.get("default"));
189581
189618
  }
189619
+ const projectId = hasProjectId() ? readProjectId() : void 0;
189582
189620
  const getState = () => ({
189583
189621
  status: "running",
189584
189622
  services: config2.services.filter((svc) => runningServicePorts.has(svc.name) || svc.serve).map((svc) => ({
@@ -189593,7 +189631,8 @@ Add them to specific.config`);
189593
189631
  port: r.port,
189594
189632
  host: r.host,
189595
189633
  syncEnabled: r.type === "postgres" && syncDatabases.has(name)
189596
- }))
189634
+ })),
189635
+ projectId
189597
189636
  });
189598
189637
  const adminServer = await startAdminServer(getState);
189599
189638
  adminServerRef.current = adminServer;
@@ -189982,7 +190021,7 @@ import * as path19 from "path";
189982
190021
  // src/lib/deploy/build-tester.ts
189983
190022
  import { spawn as spawn5 } from "child_process";
189984
190023
  import { existsSync as existsSync17 } from "fs";
189985
- import { join as join18, resolve as resolve5 } from "path";
190024
+ import { join as join18 } from "path";
189986
190025
  function getDependencyInstallCommand(build, projectDir) {
189987
190026
  switch (build.base) {
189988
190027
  case "node":
@@ -190071,12 +190110,12 @@ function runCommand2(command, projectDir, buildName) {
190071
190110
  async function testBuild(build, projectDir) {
190072
190111
  const startTime = Date.now();
190073
190112
  const outputs = [];
190074
- const contextDir = resolve5(projectDir, build.context || ".");
190075
- writeLog("build-test", `Starting test for build "${build.name}" (base: ${build.base}, context: ${contextDir})`);
190076
- const depsCommand = getDependencyInstallCommand(build, contextDir);
190113
+ const workDir = projectDir;
190114
+ writeLog("build-test", `Starting test for build "${build.name}" (base: ${build.base}, workDir: ${workDir})`);
190115
+ const depsCommand = getDependencyInstallCommand(build, workDir);
190077
190116
  if (depsCommand) {
190078
190117
  writeLog("build-test", `[${build.name}] Installing dependencies...`);
190079
- const depsResult = await runCommand2(depsCommand, contextDir, build.name);
190118
+ const depsResult = await runCommand2(depsCommand, workDir, build.name);
190080
190119
  outputs.push(`[${depsCommand}]
190081
190120
  ${depsResult.output}`);
190082
190121
  if (!depsResult.success) {
@@ -190095,7 +190134,7 @@ ${depsResult.output}`);
190095
190134
  }
190096
190135
  if (build.command) {
190097
190136
  writeLog("build-test", `[${build.name}] Running build command...`);
190098
- const buildResult = await runCommand2(build.command, contextDir, build.name);
190137
+ const buildResult = await runCommand2(build.command, workDir, build.name);
190099
190138
  outputs.push(`[${build.command}]
190100
190139
  ${buildResult.output}`);
190101
190140
  if (!buildResult.success) {
@@ -191405,232 +191444,6 @@ function cleanCommand(instanceKey) {
191405
191444
  }
191406
191445
  }
191407
191446
 
191408
- // src/commands/secrets.tsx
191409
- import React9, { useState as useState8, useEffect as useEffect6 } from "react";
191410
- import { render as render7, Text as Text9, Box as Box9, useInput as useInput6, useApp as useApp4 } from "ink";
191411
- function SetSecretUI({ secretName }) {
191412
- const { exit } = useApp4();
191413
- const [value, setValue] = useState8("");
191414
- const [done, setDone] = useState8(false);
191415
- const [saving, setSaving] = useState8(false);
191416
- const [error, setError] = useState8(null);
191417
- useInput6((input, key) => {
191418
- if (done || saving) return;
191419
- if (key.return) {
191420
- if (value.trim() === "") {
191421
- setError("Secret value cannot be empty");
191422
- return;
191423
- }
191424
- setSaving(true);
191425
- saveSecret(secretName, value).then(() => {
191426
- setDone(true);
191427
- }).catch((err) => {
191428
- setError(err instanceof Error ? err.message : String(err));
191429
- setSaving(false);
191430
- });
191431
- } else if (key.backspace || key.delete) {
191432
- setValue((prev) => prev.slice(0, -1));
191433
- } else if (key.escape) {
191434
- exit();
191435
- } else if (!key.ctrl && !key.meta && input) {
191436
- setValue((prev) => prev + input);
191437
- }
191438
- });
191439
- useEffect6(() => {
191440
- if (done) {
191441
- const timer = setTimeout(() => exit(), 50);
191442
- return () => clearTimeout(timer);
191443
- }
191444
- }, [done, exit]);
191445
- if (error) {
191446
- return /* @__PURE__ */ React9.createElement(Text9, { color: "red" }, "Error: ", error);
191447
- }
191448
- if (done) {
191449
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { color: "green" }, "Secret '", secretName, "' saved to ", SECRETS_FILE));
191450
- }
191451
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, null, "Enter value for secret '", secretName, "':"), /* @__PURE__ */ React9.createElement(Box9, null, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "> "), /* @__PURE__ */ React9.createElement(Text9, null, value.length > 0 ? "*".repeat(value.length) : ""), /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "|")), /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
191452
- }
191453
- async function secretsSetCommand(secretName) {
191454
- if (!secretName) {
191455
- console.error("Error: Secret name required");
191456
- console.error("Usage: specific secrets set <name>");
191457
- process.exit(1);
191458
- }
191459
- render7(/* @__PURE__ */ React9.createElement(SetSecretUI, { secretName }));
191460
- }
191461
- async function secretsCommand(action, secretName) {
191462
- if (!action) {
191463
- console.error("Usage: specific secrets <command>");
191464
- console.error("");
191465
- console.error("Commands:");
191466
- console.error(" set <name> Set a secret value");
191467
- process.exit(1);
191468
- }
191469
- if (action === "set") {
191470
- if (!secretName) {
191471
- console.error("Error: Secret name required");
191472
- console.error("Usage: specific secrets set <name>");
191473
- process.exit(1);
191474
- }
191475
- await secretsSetCommand(secretName);
191476
- } else {
191477
- console.error(`Unknown command: ${action}`);
191478
- console.error("Usage: specific secrets set <name>");
191479
- process.exit(1);
191480
- }
191481
- }
191482
-
191483
- // src/commands/config.tsx
191484
- import React10, { useState as useState9, useEffect as useEffect7 } from "react";
191485
- import { render as render8, Text as Text10, Box as Box10, useInput as useInput7, useApp as useApp5 } from "ink";
191486
- import * as fs24 from "fs";
191487
- var HEADER_COMMENT2 = "# Configuration values for this project\n# These values override defaults defined in specific.hcl\n";
191488
- function SetConfigUI({ configName, initialValue }) {
191489
- const { exit } = useApp5();
191490
- const [value, setValue] = useState9(initialValue ?? "");
191491
- const [done, setDone] = useState9(false);
191492
- const [error, setError] = useState9(null);
191493
- useInput7((input, key) => {
191494
- if (done) return;
191495
- if (key.return) {
191496
- if (value.trim() === "") {
191497
- setError("Config value cannot be empty");
191498
- return;
191499
- }
191500
- try {
191501
- const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
191502
- const hclLine = `${configName} = "${escapedValue}"`;
191503
- let content = "";
191504
- let hasHeader = false;
191505
- if (fs24.existsSync(CONFIG_FILE)) {
191506
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
191507
- hasHeader = content.startsWith("#");
191508
- const lines = content.split("\n");
191509
- const newLines = [];
191510
- let found = false;
191511
- const pattern = new RegExp(`^${configName}\\s*=\\s*"`);
191512
- for (const line of lines) {
191513
- if (pattern.test(line.trim())) {
191514
- newLines.push(hclLine);
191515
- found = true;
191516
- } else {
191517
- newLines.push(line);
191518
- }
191519
- }
191520
- if (found) {
191521
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
191522
- setDone(true);
191523
- return;
191524
- }
191525
- }
191526
- let newContent = "";
191527
- if (!hasHeader && !content) {
191528
- newContent = HEADER_COMMENT2 + "\n";
191529
- } else if (content) {
191530
- newContent = content.endsWith("\n") ? content : content + "\n";
191531
- }
191532
- newContent += `${hclLine}
191533
- `;
191534
- fs24.writeFileSync(CONFIG_FILE, newContent);
191535
- setDone(true);
191536
- } catch (err) {
191537
- setError(err instanceof Error ? err.message : String(err));
191538
- }
191539
- } else if (key.backspace || key.delete) {
191540
- setValue((prev) => prev.slice(0, -1));
191541
- } else if (key.escape) {
191542
- exit();
191543
- } else if (!key.ctrl && !key.meta && input) {
191544
- setValue((prev) => prev + input);
191545
- }
191546
- });
191547
- useEffect7(() => {
191548
- if (done) {
191549
- const timer = setTimeout(() => exit(), 50);
191550
- return () => clearTimeout(timer);
191551
- }
191552
- }, [done, exit]);
191553
- if (error) {
191554
- return /* @__PURE__ */ React10.createElement(Text10, { color: "red" }, "Error: ", error);
191555
- }
191556
- if (done) {
191557
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, { color: "green" }, "Config '", configName, "' saved to ", CONFIG_FILE));
191558
- }
191559
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, null, "Enter value for config '", configName, "':"), /* @__PURE__ */ React10.createElement(Box10, null, /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, "> "), /* @__PURE__ */ React10.createElement(Text10, null, value), /* @__PURE__ */ React10.createElement(Text10, { color: "gray" }, "|")), /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
191560
- }
191561
- async function configSetCommand(configName, configValue) {
191562
- if (!configName) {
191563
- console.error("Error: Config name required");
191564
- console.error("Usage: specific config set <name> [value]");
191565
- process.exit(1);
191566
- }
191567
- if (configValue !== void 0) {
191568
- try {
191569
- const escapedValue = configValue.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
191570
- const hclLine = `${configName} = "${escapedValue}"`;
191571
- let content = "";
191572
- let hasHeader = false;
191573
- if (fs24.existsSync(CONFIG_FILE)) {
191574
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
191575
- hasHeader = content.startsWith("#");
191576
- const lines = content.split("\n");
191577
- const newLines = [];
191578
- let found = false;
191579
- const pattern = new RegExp(`^${configName}\\s*=\\s*"`);
191580
- for (const line of lines) {
191581
- if (pattern.test(line.trim())) {
191582
- newLines.push(hclLine);
191583
- found = true;
191584
- } else {
191585
- newLines.push(line);
191586
- }
191587
- }
191588
- if (found) {
191589
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
191590
- console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
191591
- return;
191592
- }
191593
- }
191594
- let newContent = "";
191595
- if (!hasHeader && !content) {
191596
- newContent = HEADER_COMMENT2 + "\n";
191597
- } else if (content) {
191598
- newContent = content.endsWith("\n") ? content : content + "\n";
191599
- }
191600
- newContent += `${hclLine}
191601
- `;
191602
- fs24.writeFileSync(CONFIG_FILE, newContent);
191603
- console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
191604
- } catch (err) {
191605
- console.error("Error:", err instanceof Error ? err.message : String(err));
191606
- process.exit(1);
191607
- }
191608
- return;
191609
- }
191610
- render8(/* @__PURE__ */ React10.createElement(SetConfigUI, { configName }));
191611
- }
191612
- async function configCommand(action, configName, configValue) {
191613
- if (!action) {
191614
- console.error("Usage: specific config <command>");
191615
- console.error("");
191616
- console.error("Commands:");
191617
- console.error(" set <name> [value] Set a config value");
191618
- process.exit(1);
191619
- }
191620
- if (action === "set") {
191621
- if (!configName) {
191622
- console.error("Error: Config name required");
191623
- console.error("Usage: specific config set <name> [value]");
191624
- process.exit(1);
191625
- }
191626
- await configSetCommand(configName, configValue);
191627
- } else {
191628
- console.error(`Unknown command: ${action}`);
191629
- console.error("Usage: specific config set <name> [value]");
191630
- process.exit(1);
191631
- }
191632
- }
191633
-
191634
191447
  // src/commands/login.tsx
191635
191448
  async function loginCommand() {
191636
191449
  if (isLoggedIn()) {
@@ -191644,14 +191457,14 @@ async function loginCommand() {
191644
191457
  }
191645
191458
 
191646
191459
  // src/commands/logout.tsx
191647
- import React11, { useState as useState10, useEffect as useEffect8 } from "react";
191648
- import { render as render9, Text as Text11, useApp as useApp6 } from "ink";
191460
+ import React9, { useState as useState8, useEffect as useEffect6 } from "react";
191461
+ import { render as render7, Text as Text9, useApp as useApp4 } from "ink";
191649
191462
  function LogoutUI() {
191650
- const { exit } = useApp6();
191651
- const [state, setState] = useState10({
191463
+ const { exit } = useApp4();
191464
+ const [state, setState] = useState8({
191652
191465
  phase: "checking"
191653
191466
  });
191654
- useEffect8(() => {
191467
+ useEffect6(() => {
191655
191468
  if (state.phase !== "checking") return;
191656
191469
  if (!isLoggedIn()) {
191657
191470
  setState({ phase: "not-logged-in" });
@@ -191660,29 +191473,29 @@ function LogoutUI() {
191660
191473
  clearUserCredentials();
191661
191474
  setState({ phase: "done" });
191662
191475
  }, [state.phase]);
191663
- useEffect8(() => {
191476
+ useEffect6(() => {
191664
191477
  if (state.phase === "done" || state.phase === "not-logged-in") {
191665
191478
  const timer = setTimeout(() => exit(), 100);
191666
191479
  return () => clearTimeout(timer);
191667
191480
  }
191668
191481
  }, [state.phase, exit]);
191669
191482
  if (state.phase === "not-logged-in") {
191670
- return /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Not logged in.");
191483
+ return /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Not logged in.");
191671
191484
  }
191672
191485
  if (state.phase === "done") {
191673
- return /* @__PURE__ */ React11.createElement(Text11, { color: "green" }, "Logged out successfully.");
191486
+ return /* @__PURE__ */ React9.createElement(Text9, { color: "green" }, "Logged out successfully.");
191674
191487
  }
191675
- return /* @__PURE__ */ React11.createElement(Text11, null, "Logging out...");
191488
+ return /* @__PURE__ */ React9.createElement(Text9, null, "Logging out...");
191676
191489
  }
191677
191490
  function logoutCommand() {
191678
- render9(/* @__PURE__ */ React11.createElement(LogoutUI, null));
191491
+ render7(/* @__PURE__ */ React9.createElement(LogoutUI, null));
191679
191492
  }
191680
191493
 
191681
191494
  // src/cli.tsx
191682
191495
  var program = new Command();
191683
191496
  var env = "production";
191684
191497
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
191685
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.43").enablePositionalOptions();
191498
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.45").enablePositionalOptions();
191686
191499
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
191687
191500
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
191688
191501
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -191705,8 +191518,6 @@ program.command("psql [database]").description("Connect to a running Postgres da
191705
191518
  program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
191706
191519
  cleanCommand(options2.key);
191707
191520
  });
191708
- program.command("secrets [action] [name]").description("Manage secrets").action(secretsCommand);
191709
- program.command("config [action] [name] [value]").description("Manage configuration values").action(configCommand);
191710
191521
  program.command("login").description("Log in to Specific").action(loginCommand);
191711
191522
  program.command("logout").description("Log out of Specific").action(logoutCommand);
191712
191523
  var commandName = process.argv[2] || "help";