@specific.dev/cli 0.1.159 → 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 +58 -11
  83. package/dist/docs/services.md +35 -0
  84. package/package.json +1 -1
  85. /package/dist/admin/_next/static/{tQIwPrBmfmqJ2i8TYKMQV → e6-YvpGd9Q3oQIstxRX3d}/_buildManifest.js +0 -0
  86. /package/dist/admin/_next/static/{tQIwPrBmfmqJ2i8TYKMQV → e6-YvpGd9Q3oQIstxRX3d}/_clientMiddlewareManifest.json +0 -0
  87. /package/dist/admin/_next/static/{tQIwPrBmfmqJ2i8TYKMQV → 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":"tQIwPrBmfmqJ2i8TYKMQV","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
@@ -368402,7 +368402,26 @@ function parseBuildRef(build) {
368402
368402
  }
368403
368403
  return void 0;
368404
368404
  }
368405
- 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) {
368406
368425
  const endpoints = [];
368407
368426
  const endpointData = fieldObj.endpoint;
368408
368427
  if (!endpointData) {
@@ -368416,9 +368435,11 @@ function parseEndpoints(fieldObj) {
368416
368435
  if (ep && typeof ep === "object" && !Array.isArray(ep)) {
368417
368436
  const epObj = ep;
368418
368437
  const publicValue = Array.isArray(epObj.public) ? epObj.public[0] : epObj.public;
368438
+ const healthCheck = parseHealthCheckBlock(serviceName, "default", epObj.health_check);
368419
368439
  endpoints.push({
368420
368440
  name: "default",
368421
- public: publicValue === true
368441
+ public: publicValue === true,
368442
+ ...healthCheck ? { health_check: healthCheck } : {}
368422
368443
  });
368423
368444
  }
368424
368445
  }
@@ -368428,9 +368449,11 @@ function parseEndpoints(fieldObj) {
368428
368449
  if (fields && typeof fields === "object" && !Array.isArray(fields)) {
368429
368450
  const fieldObj2 = fields;
368430
368451
  const publicValue = Array.isArray(fieldObj2.public) ? fieldObj2.public[0] : fieldObj2.public;
368452
+ const healthCheck = parseHealthCheckBlock(serviceName, name, fieldObj2.health_check);
368431
368453
  endpoints.push({
368432
368454
  name,
368433
- public: publicValue === true
368455
+ public: publicValue === true,
368456
+ ...healthCheck ? { health_check: healthCheck } : {}
368434
368457
  });
368435
368458
  } else {
368436
368459
  endpoints.push({
@@ -368492,7 +368515,7 @@ function parseServices(serviceData) {
368492
368515
  if (fieldObj.root) {
368493
368516
  service.root = String(fieldObj.root);
368494
368517
  }
368495
- const endpoints = parseEndpoints(fieldObj);
368518
+ const endpoints = parseEndpoints(name, fieldObj);
368496
368519
  const volumes = parseVolumes(fieldObj);
368497
368520
  if (volumes.length > 0) {
368498
368521
  service.volumes = volumes;
@@ -374493,7 +374516,7 @@ function trackEvent(event, properties) {
374493
374516
  event,
374494
374517
  properties: {
374495
374518
  ...properties,
374496
- cli_version: "0.1.159",
374519
+ cli_version: "0.1.160",
374497
374520
  platform: process.platform,
374498
374521
  node_version: process.version,
374499
374522
  project_id: getProjectId()
@@ -375282,7 +375305,26 @@ function parseBuildRef2(build) {
375282
375305
  }
375283
375306
  return void 0;
375284
375307
  }
375285
- 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) {
375286
375328
  const endpoints = [];
375287
375329
  const endpointData = fieldObj.endpoint;
375288
375330
  if (!endpointData) {
@@ -375296,9 +375338,11 @@ function parseEndpoints2(fieldObj) {
375296
375338
  if (ep && typeof ep === "object" && !Array.isArray(ep)) {
375297
375339
  const epObj = ep;
375298
375340
  const publicValue = Array.isArray(epObj.public) ? epObj.public[0] : epObj.public;
375341
+ const healthCheck = parseHealthCheckBlock2(serviceName, "default", epObj.health_check);
375299
375342
  endpoints.push({
375300
375343
  name: "default",
375301
- public: publicValue === true
375344
+ public: publicValue === true,
375345
+ ...healthCheck ? { health_check: healthCheck } : {}
375302
375346
  });
375303
375347
  }
375304
375348
  }
@@ -375308,9 +375352,11 @@ function parseEndpoints2(fieldObj) {
375308
375352
  if (fields && typeof fields === "object" && !Array.isArray(fields)) {
375309
375353
  const fieldObj2 = fields;
375310
375354
  const publicValue = Array.isArray(fieldObj2.public) ? fieldObj2.public[0] : fieldObj2.public;
375355
+ const healthCheck = parseHealthCheckBlock2(serviceName, name, fieldObj2.health_check);
375311
375356
  endpoints.push({
375312
375357
  name,
375313
- public: publicValue === true
375358
+ public: publicValue === true,
375359
+ ...healthCheck ? { health_check: healthCheck } : {}
375314
375360
  });
375315
375361
  } else {
375316
375362
  endpoints.push({
@@ -375372,7 +375418,7 @@ function parseServices2(serviceData) {
375372
375418
  if (fieldObj.root) {
375373
375419
  service.root = String(fieldObj.root);
375374
375420
  }
375375
- const endpoints = parseEndpoints2(fieldObj);
375421
+ const endpoints = parseEndpoints2(name, fieldObj);
375376
375422
  const volumes = parseVolumes2(fieldObj);
375377
375423
  if (volumes.length > 0) {
375378
375424
  service.volumes = volumes;
@@ -376517,6 +376563,7 @@ function formatErrorCode(code) {
376517
376563
  const labels = {
376518
376564
  pre_deploy_failed: "Pre-deploy hook failed",
376519
376565
  deploy_failed: "Service failed to deploy",
376566
+ health_check_failed: "Health check failed",
376520
376567
  post_deploy_failed: "Post-deploy hook failed",
376521
376568
  internal_error: "Internal error"
376522
376569
  };
@@ -378677,7 +378724,7 @@ function compareVersions(a, b) {
378677
378724
  return 0;
378678
378725
  }
378679
378726
  async function checkForUpdate() {
378680
- const currentVersion = "0.1.159";
378727
+ const currentVersion = "0.1.160";
378681
378728
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
378682
378729
  if (!response.ok) {
378683
378730
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -379266,7 +379313,7 @@ async function domainAddCommand(service, domain, options2) {
379266
379313
  var program = new Command();
379267
379314
  var env = "production";
379268
379315
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
379269
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.159").enablePositionalOptions();
379316
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.160").enablePositionalOptions();
379270
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", `
379271
379318
  Examples:
379272
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.159",
3
+ "version": "0.1.160",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",