@specific.dev/cli 0.1.158 → 0.1.160

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 (87) 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 +2 -2
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +2 -2
  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/_next/static/chunks/{68598441248beee0.js → d5bf905a3d3f005b.js} +1 -1
  10. package/dist/admin/_not-found/__next._full.txt +1 -1
  11. package/dist/admin/_not-found/__next._head.txt +1 -1
  12. package/dist/admin/_not-found/__next._index.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  14. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  15. package/dist/admin/_not-found/__next._tree.txt +1 -1
  16. package/dist/admin/_not-found/index.html +1 -1
  17. package/dist/admin/_not-found/index.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  20. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  21. package/dist/admin/databases/__next._full.txt +1 -1
  22. package/dist/admin/databases/__next._head.txt +1 -1
  23. package/dist/admin/databases/__next._index.txt +1 -1
  24. package/dist/admin/databases/__next._tree.txt +1 -1
  25. package/dist/admin/databases/index.html +1 -1
  26. package/dist/admin/databases/index.txt +1 -1
  27. package/dist/admin/fullscreen/__next._full.txt +2 -2
  28. package/dist/admin/fullscreen/__next._head.txt +1 -1
  29. package/dist/admin/fullscreen/__next._index.txt +1 -1
  30. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +2 -2
  32. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  39. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  40. package/dist/admin/fullscreen/databases/index.html +1 -1
  41. package/dist/admin/fullscreen/databases/index.txt +1 -1
  42. package/dist/admin/fullscreen/index.html +1 -1
  43. package/dist/admin/fullscreen/index.txt +2 -2
  44. package/dist/admin/index.html +1 -1
  45. package/dist/admin/index.txt +2 -2
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  48. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  49. package/dist/admin/mail/__next._full.txt +1 -1
  50. package/dist/admin/mail/__next._head.txt +1 -1
  51. package/dist/admin/mail/__next._index.txt +1 -1
  52. package/dist/admin/mail/__next._tree.txt +1 -1
  53. package/dist/admin/mail/index.html +1 -1
  54. package/dist/admin/mail/index.txt +1 -1
  55. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
  56. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
  57. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
  58. package/dist/admin/services/__next._full.txt +1 -1
  59. package/dist/admin/services/__next._head.txt +1 -1
  60. package/dist/admin/services/__next._index.txt +1 -1
  61. package/dist/admin/services/__next._tree.txt +1 -1
  62. package/dist/admin/services/index.html +1 -1
  63. package/dist/admin/services/index.txt +1 -1
  64. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
  65. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
  66. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
  67. package/dist/admin/storage/__next._full.txt +1 -1
  68. package/dist/admin/storage/__next._head.txt +1 -1
  69. package/dist/admin/storage/__next._index.txt +1 -1
  70. package/dist/admin/storage/__next._tree.txt +1 -1
  71. package/dist/admin/storage/index.html +1 -1
  72. package/dist/admin/storage/index.txt +1 -1
  73. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  74. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  75. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  76. package/dist/admin/workflows/__next._full.txt +1 -1
  77. package/dist/admin/workflows/__next._head.txt +1 -1
  78. package/dist/admin/workflows/__next._index.txt +1 -1
  79. package/dist/admin/workflows/__next._tree.txt +1 -1
  80. package/dist/admin/workflows/index.html +1 -1
  81. package/dist/admin/workflows/index.txt +1 -1
  82. package/dist/cli.js +174 -22
  83. package/dist/docs/services.md +35 -0
  84. package/package.json +1 -1
  85. /package/dist/admin/_next/static/{SdGBMqd60o93Rm-No_m_B → e6-YvpGd9Q3oQIstxRX3d}/_buildManifest.js +0 -0
  86. /package/dist/admin/_next/static/{SdGBMqd60o93Rm-No_m_B → e6-YvpGd9Q3oQIstxRX3d}/_clientMiddlewareManifest.json +0 -0
  87. /package/dist/admin/_next/static/{SdGBMqd60o93Rm-No_m_B → e6-YvpGd9Q3oQIstxRX3d}/_ssgManifest.js +0 -0
@@ -18,7 +18,7 @@ f:"$Sreact.suspense"
18
18
  :HL["/_next/static/media/797e433ab948586e-s.p.479bea2b.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
19
19
  :HL["/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
20
20
  :HL["/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
21
- 0:{"P":null,"b":"SdGBMqd60o93Rm-No_m_B","c":["","workflows",""],"q":"","i":false,"f":[[["",{"children":["(default)",{"children":["workflows",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/4ae8172f753ceb22.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/c52b2ebc474a0535.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"inter_b2991b2-module__9mH_6q__variable","suppressHydrationWarning":true,"children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/_next/static/chunks/c7aaec1998a39e17.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/b44581875c51fcd8.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/_next/static/chunks/9835cc4e14c5aee3.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/_next/static/chunks/dc5c264e3262a84a.js","async":true,"nonce":"$undefined"}]],["$","$L6",null,{"children":[["$","$L7",null,{}],["$","$L8",null,{"className":"overflow-hidden","children":["$","$L9",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$La",null,{"Component":"$b","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@c","$@d"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/e3baf0c708c5b9ae.js","async":true,"nonce":"$undefined"}]],["$","$Le",null,{"children":["$","$f",null,{"name":"Next.MetadataOutlet","children":"$@10"}]}]]}],{},null,false,false]},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$L11",null,{"children":"$L12"}],["$","div",null,{"hidden":true,"children":["$","$L13",null,{"children":["$","$f",null,{"name":"Next.Metadata","children":"$L14"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$15",[]],"S":true}
21
+ 0:{"P":null,"b":"e6-YvpGd9Q3oQIstxRX3d","c":["","workflows",""],"q":"","i":false,"f":[[["",{"children":["(default)",{"children":["workflows",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/4ae8172f753ceb22.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/c52b2ebc474a0535.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"inter_b2991b2-module__9mH_6q__variable","suppressHydrationWarning":true,"children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/_next/static/chunks/c7aaec1998a39e17.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/b44581875c51fcd8.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/_next/static/chunks/9835cc4e14c5aee3.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/_next/static/chunks/dc5c264e3262a84a.js","async":true,"nonce":"$undefined"}]],["$","$L6",null,{"children":[["$","$L7",null,{}],["$","$L8",null,{"className":"overflow-hidden","children":["$","$L9",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$La",null,{"Component":"$b","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@c","$@d"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/e3baf0c708c5b9ae.js","async":true,"nonce":"$undefined"}]],["$","$Le",null,{"children":["$","$f",null,{"name":"Next.MetadataOutlet","children":"$@10"}]}]]}],{},null,false,false]},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$L11",null,{"children":"$L12"}],["$","div",null,{"hidden":true,"children":["$","$L13",null,{"children":["$","$f",null,{"name":"Next.Metadata","children":"$L14"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$15",[]],"S":true}
22
22
  c:{}
23
23
  d:"$0:f:0:1:1:children:1:children:1:children:0:props:children:0:props:serverProvidedParams:params"
24
24
  12:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
package/dist/cli.js CHANGED
@@ -367507,6 +367507,20 @@ function closeDebugLog() {
367507
367507
  logStream = null;
367508
367508
  }
367509
367509
  }
367510
+ var ApiError = class extends Error {
367511
+ code;
367512
+ httpStatus;
367513
+ apiError;
367514
+ pendingActions;
367515
+ constructor(args) {
367516
+ super(`${args.operation}: ${args.apiError} (${args.code})`);
367517
+ this.name = "ApiError";
367518
+ this.code = args.code;
367519
+ this.httpStatus = args.httpStatus;
367520
+ this.apiError = args.apiError;
367521
+ if (args.pendingActions) this.pendingActions = args.pendingActions;
367522
+ }
367523
+ };
367510
367524
  async function readApiError(response, operation, requestDescriptor) {
367511
367525
  const rawBody = await response.text().catch(() => "");
367512
367526
  writeLog("api:error", `Request was: ${requestDescriptor}`);
@@ -367519,7 +367533,13 @@ async function readApiError(response, operation, requestDescriptor) {
367519
367533
  const parsed = JSON.parse(rawBody);
367520
367534
  if (parsed && typeof parsed === "object" && typeof parsed.error === "string" && typeof parsed.code === "string") {
367521
367535
  const apiErr = parsed;
367522
- return new Error(`${operation}: ${apiErr.error} (${apiErr.code})`);
367536
+ return new ApiError({
367537
+ operation,
367538
+ code: apiErr.code,
367539
+ httpStatus: response.status,
367540
+ apiError: apiErr.error,
367541
+ ...apiErr.pendingActions && { pendingActions: apiErr.pendingActions }
367542
+ });
367523
367543
  }
367524
367544
  } catch {
367525
367545
  }
@@ -368382,7 +368402,26 @@ function parseBuildRef(build) {
368382
368402
  }
368383
368403
  return void 0;
368384
368404
  }
368385
- function parseEndpoints(fieldObj) {
368405
+ function parseHealthCheckBlock(serviceName, endpointName, healthCheck) {
368406
+ if (healthCheck === void 0) {
368407
+ return void 0;
368408
+ }
368409
+ const arr = Array.isArray(healthCheck) ? healthCheck : [healthCheck];
368410
+ const obj = arr[0];
368411
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
368412
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" has an invalid health_check block.`);
368413
+ }
368414
+ const fields = obj;
368415
+ const pathValue = Array.isArray(fields.path) ? fields.path[0] : fields.path;
368416
+ if (typeof pathValue !== "string" || pathValue.trim() === "") {
368417
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" health_check is missing required "path".`);
368418
+ }
368419
+ if (!pathValue.startsWith("/")) {
368420
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" health_check "path" must start with "/" (got "${pathValue}").`);
368421
+ }
368422
+ return { path: pathValue };
368423
+ }
368424
+ function parseEndpoints(serviceName, fieldObj) {
368386
368425
  const endpoints = [];
368387
368426
  const endpointData = fieldObj.endpoint;
368388
368427
  if (!endpointData) {
@@ -368396,9 +368435,11 @@ function parseEndpoints(fieldObj) {
368396
368435
  if (ep && typeof ep === "object" && !Array.isArray(ep)) {
368397
368436
  const epObj = ep;
368398
368437
  const publicValue = Array.isArray(epObj.public) ? epObj.public[0] : epObj.public;
368438
+ const healthCheck = parseHealthCheckBlock(serviceName, "default", epObj.health_check);
368399
368439
  endpoints.push({
368400
368440
  name: "default",
368401
- public: publicValue === true
368441
+ public: publicValue === true,
368442
+ ...healthCheck ? { health_check: healthCheck } : {}
368402
368443
  });
368403
368444
  }
368404
368445
  }
@@ -368408,9 +368449,11 @@ function parseEndpoints(fieldObj) {
368408
368449
  if (fields && typeof fields === "object" && !Array.isArray(fields)) {
368409
368450
  const fieldObj2 = fields;
368410
368451
  const publicValue = Array.isArray(fieldObj2.public) ? fieldObj2.public[0] : fieldObj2.public;
368452
+ const healthCheck = parseHealthCheckBlock(serviceName, name, fieldObj2.health_check);
368411
368453
  endpoints.push({
368412
368454
  name,
368413
- public: publicValue === true
368455
+ public: publicValue === true,
368456
+ ...healthCheck ? { health_check: healthCheck } : {}
368414
368457
  });
368415
368458
  } else {
368416
368459
  endpoints.push({
@@ -368472,7 +368515,7 @@ function parseServices(serviceData) {
368472
368515
  if (fieldObj.root) {
368473
368516
  service.root = String(fieldObj.root);
368474
368517
  }
368475
- const endpoints = parseEndpoints(fieldObj);
368518
+ const endpoints = parseEndpoints(name, fieldObj);
368476
368519
  const volumes = parseVolumes(fieldObj);
368477
368520
  if (volumes.length > 0) {
368478
368521
  service.volumes = volumes;
@@ -373932,6 +373975,12 @@ function writeProjectId(projectId, projectDir = process.cwd()) {
373932
373975
  }
373933
373976
  fs3.writeFileSync(path3.join(specificDir, "project_id"), projectId + "\n");
373934
373977
  }
373978
+ function clearProjectId(projectDir = process.cwd()) {
373979
+ const projectIdPath = path3.join(projectDir, PROJECT_ID_FILE);
373980
+ if (fs3.existsSync(projectIdPath)) {
373981
+ fs3.unlinkSync(projectIdPath);
373982
+ }
373983
+ }
373935
373984
  function readEnvironmentId(projectDir = process.cwd()) {
373936
373985
  const envIdPath = path3.join(projectDir, ENVIRONMENT_ID_FILE);
373937
373986
  if (!fs3.existsSync(envIdPath)) {
@@ -373954,6 +374003,12 @@ function writeEnvironmentId(environmentId, projectDir = process.cwd()) {
373954
374003
  }
373955
374004
  fs3.writeFileSync(path3.join(specificDir, "environment_id"), environmentId + "\n");
373956
374005
  }
374006
+ function clearEnvironmentId(projectDir = process.cwd()) {
374007
+ const envIdPath = path3.join(projectDir, ENVIRONMENT_ID_FILE);
374008
+ if (fs3.existsSync(envIdPath)) {
374009
+ fs3.unlinkSync(envIdPath);
374010
+ }
374011
+ }
373957
374012
  function readPreviewEnvironmentId(projectDir = process.cwd()) {
373958
374013
  const filePath = path3.join(projectDir, PREVIEW_ENVIRONMENT_ID_FILE);
373959
374014
  if (!fs3.existsSync(filePath)) {
@@ -374461,7 +374516,7 @@ function trackEvent(event, properties) {
374461
374516
  event,
374462
374517
  properties: {
374463
374518
  ...properties,
374464
- cli_version: "0.1.158",
374519
+ cli_version: "0.1.160",
374465
374520
  platform: process.platform,
374466
374521
  node_version: process.version,
374467
374522
  project_id: getProjectId()
@@ -375250,7 +375305,26 @@ function parseBuildRef2(build) {
375250
375305
  }
375251
375306
  return void 0;
375252
375307
  }
375253
- function parseEndpoints2(fieldObj) {
375308
+ function parseHealthCheckBlock2(serviceName, endpointName, healthCheck) {
375309
+ if (healthCheck === void 0) {
375310
+ return void 0;
375311
+ }
375312
+ const arr = Array.isArray(healthCheck) ? healthCheck : [healthCheck];
375313
+ const obj = arr[0];
375314
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
375315
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" has an invalid health_check block.`);
375316
+ }
375317
+ const fields = obj;
375318
+ const pathValue = Array.isArray(fields.path) ? fields.path[0] : fields.path;
375319
+ if (typeof pathValue !== "string" || pathValue.trim() === "") {
375320
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" health_check is missing required "path".`);
375321
+ }
375322
+ if (!pathValue.startsWith("/")) {
375323
+ throw new Error(`Service "${serviceName}" endpoint "${endpointName}" health_check "path" must start with "/" (got "${pathValue}").`);
375324
+ }
375325
+ return { path: pathValue };
375326
+ }
375327
+ function parseEndpoints2(serviceName, fieldObj) {
375254
375328
  const endpoints = [];
375255
375329
  const endpointData = fieldObj.endpoint;
375256
375330
  if (!endpointData) {
@@ -375264,9 +375338,11 @@ function parseEndpoints2(fieldObj) {
375264
375338
  if (ep && typeof ep === "object" && !Array.isArray(ep)) {
375265
375339
  const epObj = ep;
375266
375340
  const publicValue = Array.isArray(epObj.public) ? epObj.public[0] : epObj.public;
375341
+ const healthCheck = parseHealthCheckBlock2(serviceName, "default", epObj.health_check);
375267
375342
  endpoints.push({
375268
375343
  name: "default",
375269
- public: publicValue === true
375344
+ public: publicValue === true,
375345
+ ...healthCheck ? { health_check: healthCheck } : {}
375270
375346
  });
375271
375347
  }
375272
375348
  }
@@ -375276,9 +375352,11 @@ function parseEndpoints2(fieldObj) {
375276
375352
  if (fields && typeof fields === "object" && !Array.isArray(fields)) {
375277
375353
  const fieldObj2 = fields;
375278
375354
  const publicValue = Array.isArray(fieldObj2.public) ? fieldObj2.public[0] : fieldObj2.public;
375355
+ const healthCheck = parseHealthCheckBlock2(serviceName, name, fieldObj2.health_check);
375279
375356
  endpoints.push({
375280
375357
  name,
375281
- public: publicValue === true
375358
+ public: publicValue === true,
375359
+ ...healthCheck ? { health_check: healthCheck } : {}
375282
375360
  });
375283
375361
  } else {
375284
375362
  endpoints.push({
@@ -375340,7 +375418,7 @@ function parseServices2(serviceData) {
375340
375418
  if (fieldObj.root) {
375341
375419
  service.root = String(fieldObj.root);
375342
375420
  }
375343
- const endpoints = parseEndpoints2(fieldObj);
375421
+ const endpoints = parseEndpoints2(name, fieldObj);
375344
375422
  const volumes = parseVolumes2(fieldObj);
375345
375423
  if (volumes.length > 0) {
375346
375424
  service.volumes = volumes;
@@ -376485,6 +376563,7 @@ function formatErrorCode(code) {
376485
376563
  const labels = {
376486
376564
  pre_deploy_failed: "Pre-deploy hook failed",
376487
376565
  deploy_failed: "Service failed to deploy",
376566
+ health_check_failed: "Health check failed",
376488
376567
  post_deploy_failed: "Post-deploy hook failed",
376489
376568
  internal_error: "Internal error"
376490
376569
  };
@@ -376611,7 +376690,12 @@ function DeployUI({ envFlag, preview, config }) {
376611
376690
  const handleProjectSelect = useCallback3(
376612
376691
  (project) => {
376613
376692
  if ("type" in project && project.type === "new") {
376614
- setState((s) => ({ ...s, phase: "entering-name", selectedOrganizationId: project.orgId }));
376693
+ setState((s) => ({
376694
+ ...s,
376695
+ phase: "entering-name",
376696
+ selectedOrganizationId: project.orgId,
376697
+ error: void 0
376698
+ }));
376615
376699
  } else {
376616
376700
  const proj = project;
376617
376701
  writeProjectId(proj.id);
@@ -376667,7 +376751,17 @@ function DeployUI({ envFlag, preview, config }) {
376667
376751
  const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
376668
376752
  const projects2 = await client2.listProjects();
376669
376753
  const project = projects2.find((p) => p.id === state.projectId);
376670
- environments = project?.environments ?? [];
376754
+ if (!project) {
376755
+ if (cancelled) return;
376756
+ clearProjectId();
376757
+ clearEnvironmentId();
376758
+ setState({
376759
+ phase: "loading-projects",
376760
+ error: "The linked project no longer exists. Pick another to deploy to."
376761
+ });
376762
+ return;
376763
+ }
376764
+ environments = project.environments;
376671
376765
  }
376672
376766
  if (cancelled) return;
376673
376767
  if (environments.length === 0) {
@@ -376708,6 +376802,7 @@ function DeployUI({ envFlag, preview, config }) {
376708
376802
  return;
376709
376803
  }
376710
376804
  const projectDir = process.cwd();
376805
+ let staleEnvironmentNote;
376711
376806
  if (hasEnvironmentId(projectDir)) {
376712
376807
  const savedEnvId = readEnvironmentId(projectDir);
376713
376808
  const match = environments.find((e) => e.id === savedEnvId);
@@ -376721,6 +376816,8 @@ function DeployUI({ envFlag, preview, config }) {
376721
376816
  }));
376722
376817
  return;
376723
376818
  }
376819
+ clearEnvironmentId(projectDir);
376820
+ staleEnvironmentNote = "The linked environment no longer exists. Pick another.";
376724
376821
  }
376725
376822
  if (environments.length === 1) {
376726
376823
  writeEnvironmentId(environments[0].id);
@@ -376737,7 +376834,8 @@ function DeployUI({ envFlag, preview, config }) {
376737
376834
  ...s,
376738
376835
  phase: "selecting-environment",
376739
376836
  environments,
376740
- environmentSelectedIndex: 0
376837
+ environmentSelectedIndex: 0,
376838
+ ...staleEnvironmentNote && { error: staleEnvironmentNote }
376741
376839
  }));
376742
376840
  } catch (err) {
376743
376841
  if (cancelled) return;
@@ -376798,7 +376896,8 @@ function DeployUI({ envFlag, preview, config }) {
376798
376896
  ...s,
376799
376897
  phase: "creating-tarball",
376800
376898
  environmentId: env2.id,
376801
- environmentName: env2.name
376899
+ environmentName: env2.name,
376900
+ error: void 0
376802
376901
  }));
376803
376902
  },
376804
376903
  []
@@ -376994,6 +377093,28 @@ function DeployUI({ envFlag, preview, config }) {
376994
377093
  });
376995
377094
  writeLog("deploy", `Deployment created: ${deployment2.id}`);
376996
377095
  } catch (err) {
377096
+ if (err instanceof ApiError) {
377097
+ if (err.code === "PROJECT_DELETED" || err.code === "PROJECT_NOT_FOUND") {
377098
+ writeLog("deploy:error", `Linked project ${err.code} \u2014 re-linking`);
377099
+ clearProjectId();
377100
+ clearEnvironmentId();
377101
+ setState({
377102
+ phase: "loading-projects",
377103
+ error: err.code === "PROJECT_DELETED" ? "The linked project was deleted. Pick another to deploy to." : "The linked project no longer exists. Pick another to deploy to."
377104
+ });
377105
+ return;
377106
+ }
377107
+ if (err.code === "ENVIRONMENT_NOT_FOUND") {
377108
+ writeLog("deploy:error", "Linked environment ENVIRONMENT_NOT_FOUND \u2014 re-prompting");
377109
+ clearEnvironmentId();
377110
+ setState((s) => ({
377111
+ phase: "loading-environments",
377112
+ projectId: s.projectId,
377113
+ error: "The linked environment no longer exists. Pick another."
377114
+ }));
377115
+ return;
377116
+ }
377117
+ }
376997
377118
  const errorMsg = `Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`;
376998
377119
  writeLog("deploy:error", errorMsg);
376999
377120
  setState({
@@ -377262,12 +377383,12 @@ function DeployUI({ envFlag, preview, config }) {
377262
377383
  return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Log in to Specific"), userCode ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "Your authentication code:", " ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan", bold: true }, userCode)), /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React7.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React7.createElement(Text7, null, " Waiting for authentication in browser...")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "If the browser didn't open, visit: ", verificationUri)) : /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React7.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React7.createElement(Text7, null, " Initiating login...")));
377263
377384
  }
377264
377385
  if (phase === "loading-projects") {
377265
- return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React7.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React7.createElement(Text7, null, " Loading projects..."));
377386
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, state.error && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Note: ", state.error), /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React7.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React7.createElement(Text7, null, " Loading projects...")));
377266
377387
  }
377267
377388
  if (phase === "selecting-project" && projects && state.organizations && selectedIndex !== void 0) {
377268
377389
  const selectorItems = buildSelectorItems(projects, state.organizations);
377269
377390
  const selectableIndices = selectorItems.map((item, index) => item.type !== "header" ? index : -1).filter((i) => i >= 0);
377270
- return /* @__PURE__ */ React7.createElement(
377391
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, state.error && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Note: ", state.error), /* @__PURE__ */ React7.createElement(
377271
377392
  ProjectSelector,
377272
377393
  {
377273
377394
  projects,
@@ -377287,7 +377408,7 @@ function DeployUI({ envFlag, preview, config }) {
377287
377408
  return { ...s, selectedIndex: selectableIndices[nextPos] ?? idx };
377288
377409
  })
377289
377410
  }
377290
- );
377411
+ ));
377291
377412
  }
377292
377413
  if (phase === "entering-name") {
377293
377414
  return /* @__PURE__ */ React7.createElement(NameInput, { onSubmit: handleNameSubmit, onCancel: handleNameCancel });
@@ -377299,7 +377420,7 @@ function DeployUI({ envFlag, preview, config }) {
377299
377420
  return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React7.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React7.createElement(Text7, null, " Loading environments..."));
377300
377421
  }
377301
377422
  if (phase === "selecting-environment" && state.environments && state.environmentSelectedIndex !== void 0) {
377302
- return /* @__PURE__ */ React7.createElement(
377423
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, state.error && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Note: ", state.error), /* @__PURE__ */ React7.createElement(
377303
377424
  EnvironmentSelector,
377304
377425
  {
377305
377426
  environments: state.environments,
@@ -377317,7 +377438,7 @@ function DeployUI({ envFlag, preview, config }) {
377317
377438
  )
377318
377439
  }))
377319
377440
  }
377320
- );
377441
+ ));
377321
377442
  }
377322
377443
  const currentSecret = missingSecrets && currentSecretIndex !== void 0 ? missingSecrets[currentSecretIndex] : void 0;
377323
377444
  const currentConfig = missingConfigs && currentConfigIndex !== void 0 ? missingConfigs[currentConfigIndex] : void 0;
@@ -377419,9 +377540,11 @@ async function runDeployPipeline(options2) {
377419
377540
  }
377420
377541
  const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
377421
377542
  let projectId = options2.projectId;
377543
+ let projectIdFromLinkedFile = false;
377422
377544
  if (!projectId) {
377423
377545
  if (hasProjectId(projectDir)) {
377424
377546
  projectId = readProjectId(projectDir);
377547
+ projectIdFromLinkedFile = true;
377425
377548
  } else {
377426
377549
  console.error(
377427
377550
  "Error: No project linked.\n Create one first: specific project new <name> [--org <org-id>]\n Or pass one directly: specific deploy --project <project-id>"
@@ -377448,7 +377571,20 @@ async function runDeployPipeline(options2) {
377448
377571
  }
377449
377572
  const projects = await client2.listProjects();
377450
377573
  const project = projects.find((p) => p.id === projectId);
377451
- const environments = project?.environments ?? [];
377574
+ if (!project) {
377575
+ if (projectIdFromLinkedFile) {
377576
+ console.error(
377577
+ "Error: The linked project no longer exists or was deleted.\n Run `specific deploy` interactively to re-link, or remove `.specific/project_id` and try again."
377578
+ );
377579
+ } else {
377580
+ console.error(
377581
+ `Error: Project "${projectId}" not found or not accessible.
377582
+ Check the --project value, or list available projects: specific project list`
377583
+ );
377584
+ }
377585
+ process.exit(1);
377586
+ }
377587
+ const environments = project.environments;
377452
377588
  let environmentId;
377453
377589
  if (options2.environment) {
377454
377590
  const match = findEnvironmentByNameOrId(environments, options2.environment);
@@ -377537,6 +377673,22 @@ async function runDeployPipeline(options2) {
377537
377673
  }
377538
377674
  });
377539
377675
  } catch (err) {
377676
+ if (err instanceof ApiError) {
377677
+ if (err.code === "PROJECT_DELETED" || err.code === "PROJECT_NOT_FOUND") {
377678
+ const what = err.code === "PROJECT_DELETED" ? "The linked project was deleted." : "The linked project no longer exists.";
377679
+ console.error(
377680
+ `Error: ${what}
377681
+ Run \`specific deploy\` interactively to re-link, or remove \`.specific/project_id\` and try again.`
377682
+ );
377683
+ process.exit(1);
377684
+ }
377685
+ if (err.code === "ENVIRONMENT_NOT_FOUND") {
377686
+ console.error(
377687
+ "Error: The linked environment no longer exists.\n Run `specific deploy` interactively to re-link, or remove `.specific/environment_id` and try again."
377688
+ );
377689
+ process.exit(1);
377690
+ }
377691
+ }
377540
377692
  console.error(`Error: Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
377541
377693
  process.exit(1);
377542
377694
  }
@@ -378572,7 +378724,7 @@ function compareVersions(a, b) {
378572
378724
  return 0;
378573
378725
  }
378574
378726
  async function checkForUpdate() {
378575
- const currentVersion = "0.1.158";
378727
+ const currentVersion = "0.1.160";
378576
378728
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
378577
378729
  if (!response.ok) {
378578
378730
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -379161,7 +379313,7 @@ async function domainAddCommand(service, domain, options2) {
379161
379313
  var program = new Command();
379162
379314
  var env = "production";
379163
379315
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
379164
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.158").enablePositionalOptions();
379316
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.160").enablePositionalOptions();
379165
379317
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
379166
379318
  Examples:
379167
379319
  $ specific init
@@ -121,6 +121,41 @@ service "worker" {
121
121
  }
122
122
  ```
123
123
 
124
+ ### Health checks
125
+
126
+ Add a `health_check` block inside an `endpoint` to tell Specific how to verify the service is healthy. The platform sends an HTTP `GET` to the given path on the endpoint's port — if the response is `2xx`, the service is considered healthy.
127
+
128
+ ```hcl
129
+ service "api" {
130
+ build = build.api
131
+ command = "./api"
132
+
133
+ endpoint {
134
+ public = true
135
+ health_check {
136
+ path = "/healthz"
137
+ }
138
+ }
139
+
140
+ env = {
141
+ PORT = port
142
+ }
143
+ }
144
+ ```
145
+
146
+ - `path` - The HTTP path to probe (required, must start with `/`). The service must respond with a `2xx` status code when healthy.
147
+ - A health check should be a low-cost endpoint: don't query the database, call other services, or do real work. Just confirm the process is up and able to respond.
148
+ - Only one `health_check` per service is used. If multiple endpoints declare one, the first one wins.
149
+ - Background workers (services without endpoints) don't support `health_check` yet — only HTTP-serving endpoints do.
150
+
151
+ **Behavior in production:**
152
+
153
+ - During a deploy, the new pod doesn't receive traffic until its health check passes. The old version stays up until then, so users never hit a half-started process.
154
+ - If the health check starts failing after the deploy is live, the service is restarted automatically. Traffic is drained from the unhealthy instance well before the restart happens.
155
+ - If the health check never passes during a deploy (typically a misconfigured `path`), the deploy fails fast with a `health_check_failed` error pointing at the probe URL, rather than hanging until the deploy timeout.
156
+
157
+ A handler that returns `200 OK` with a constant body is sufficient. If the service already exposes a root path that always returns `2xx` (e.g. a static site), you can point `health_check` at `/` instead of adding a dedicated route.
158
+
124
159
  ## Private inter-service communication
125
160
 
126
161
  Services can reference other services' endpoints using `private_url` to communicate with each other over a private network:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.158",
3
+ "version": "0.1.160",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",