@specific.dev/cli 0.1.174 → 0.1.176

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 (102) 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 +3 -3
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +2 -2
  8. package/dist/admin/__next._tree.txt +2 -2
  9. package/dist/admin/_next/static/chunks/{411d73f9ad4901a2.js → 076d0b19f149e961.js} +2 -2
  10. package/dist/admin/_next/static/chunks/{f11bf31415d76903.css → 25ac6c65cd7c74e2.css} +1 -1
  11. package/dist/admin/_not-found/__next._full.txt +2 -2
  12. package/dist/admin/_not-found/__next._head.txt +1 -1
  13. package/dist/admin/_not-found/__next._index.txt +2 -2
  14. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  15. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  16. package/dist/admin/_not-found/__next._tree.txt +2 -2
  17. package/dist/admin/_not-found/index.html +1 -1
  18. package/dist/admin/_not-found/index.txt +2 -2
  19. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +2 -2
  20. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
  21. package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
  22. package/dist/admin/crons/__next._full.txt +3 -3
  23. package/dist/admin/crons/__next._head.txt +1 -1
  24. package/dist/admin/crons/__next._index.txt +2 -2
  25. package/dist/admin/crons/__next._tree.txt +2 -2
  26. package/dist/admin/crons/index.html +1 -1
  27. package/dist/admin/crons/index.txt +3 -3
  28. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  29. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  30. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  31. package/dist/admin/databases/__next._full.txt +2 -2
  32. package/dist/admin/databases/__next._head.txt +1 -1
  33. package/dist/admin/databases/__next._index.txt +2 -2
  34. package/dist/admin/databases/__next._tree.txt +2 -2
  35. package/dist/admin/databases/index.html +1 -1
  36. package/dist/admin/databases/index.txt +2 -2
  37. package/dist/admin/fullscreen/__next._full.txt +3 -3
  38. package/dist/admin/fullscreen/__next._head.txt +1 -1
  39. package/dist/admin/fullscreen/__next._index.txt +2 -2
  40. package/dist/admin/fullscreen/__next._tree.txt +2 -2
  41. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +2 -2
  42. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  43. package/dist/admin/fullscreen/databases/__next._full.txt +2 -2
  44. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  45. package/dist/admin/fullscreen/databases/__next._index.txt +2 -2
  46. package/dist/admin/fullscreen/databases/__next._tree.txt +2 -2
  47. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  48. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  49. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  50. package/dist/admin/fullscreen/databases/index.html +1 -1
  51. package/dist/admin/fullscreen/databases/index.txt +2 -2
  52. package/dist/admin/fullscreen/index.html +1 -1
  53. package/dist/admin/fullscreen/index.txt +3 -3
  54. package/dist/admin/index.html +1 -1
  55. package/dist/admin/index.txt +3 -3
  56. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  57. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  58. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  59. package/dist/admin/mail/__next._full.txt +2 -2
  60. package/dist/admin/mail/__next._head.txt +1 -1
  61. package/dist/admin/mail/__next._index.txt +2 -2
  62. package/dist/admin/mail/__next._tree.txt +2 -2
  63. package/dist/admin/mail/index.html +1 -1
  64. package/dist/admin/mail/index.txt +2 -2
  65. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
  66. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
  67. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
  68. package/dist/admin/services/__next._full.txt +2 -2
  69. package/dist/admin/services/__next._head.txt +1 -1
  70. package/dist/admin/services/__next._index.txt +2 -2
  71. package/dist/admin/services/__next._tree.txt +2 -2
  72. package/dist/admin/services/index.html +1 -1
  73. package/dist/admin/services/index.txt +2 -2
  74. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
  75. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
  76. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
  77. package/dist/admin/storage/__next._full.txt +2 -2
  78. package/dist/admin/storage/__next._head.txt +1 -1
  79. package/dist/admin/storage/__next._index.txt +2 -2
  80. package/dist/admin/storage/__next._tree.txt +2 -2
  81. package/dist/admin/storage/index.html +1 -1
  82. package/dist/admin/storage/index.txt +2 -2
  83. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  84. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  85. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  86. package/dist/admin/workflows/__next._full.txt +2 -2
  87. package/dist/admin/workflows/__next._head.txt +1 -1
  88. package/dist/admin/workflows/__next._index.txt +2 -2
  89. package/dist/admin/workflows/__next._tree.txt +2 -2
  90. package/dist/admin/workflows/index.html +1 -1
  91. package/dist/admin/workflows/index.txt +2 -2
  92. package/dist/cli.js +43 -10
  93. package/dist/docs/domains.md +1 -3
  94. package/dist/docs/index.md +1 -0
  95. package/dist/docs/observability.md +28 -1
  96. package/dist/docs/previews.md +57 -0
  97. package/dist/docs/services.md +2 -0
  98. package/dist/docs/volumes.md +27 -0
  99. package/package.json +1 -1
  100. /package/dist/admin/_next/static/{OcHvpXQ_2VlEpc9dZIpdf → 6iTWj5ZOTS6kNmEbXOCUs}/_buildManifest.js +0 -0
  101. /package/dist/admin/_next/static/{OcHvpXQ_2VlEpc9dZIpdf → 6iTWj5ZOTS6kNmEbXOCUs}/_clientMiddlewareManifest.json +0 -0
  102. /package/dist/admin/_next/static/{OcHvpXQ_2VlEpc9dZIpdf → 6iTWj5ZOTS6kNmEbXOCUs}/_ssgManifest.js +0 -0
@@ -14,11 +14,11 @@ f:"$Sreact.suspense"
14
14
  11:I[15478,["/_next/static/chunks/b2b4aada246f4749.js","/_next/static/chunks/7c2bbd09e9922443.js"],"ViewportBoundary"]
15
15
  13:I[15478,["/_next/static/chunks/b2b4aada246f4749.js","/_next/static/chunks/7c2bbd09e9922443.js"],"MetadataBoundary"]
16
16
  15:I[47541,[],"default"]
17
- :HL["/_next/static/chunks/f11bf31415d76903.css","style"]
17
+ :HL["/_next/static/chunks/25ac6c65cd7c74e2.css","style"]
18
18
  :HL["/_next/static/media/797e433ab948586e-s.p.29207c2f.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":"OcHvpXQ_2VlEpc9dZIpdf","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/f11bf31415d76903.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/21d31e8f6bca9c4d.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/65e3cacf9bc29c63.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":"6iTWj5ZOTS6kNmEbXOCUs","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/25ac6c65cd7c74e2.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/21d31e8f6bca9c4d.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/65e3cacf9bc29c63.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
@@ -368588,8 +368588,19 @@ function parseVolumes(fieldObj) {
368588
368588
  return volumes;
368589
368589
  }
368590
368590
  if (!Array.isArray(volumeData)) {
368591
- for (const [name] of Object.entries(volumeData)) {
368592
- volumes.push({ name });
368591
+ for (const [name, fieldsArray] of Object.entries(volumeData)) {
368592
+ const volume = { name };
368593
+ const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
368594
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
368595
+ const resetCommand = fields.reset_command;
368596
+ if (resetCommand !== void 0) {
368597
+ if (typeof resetCommand !== "string" || resetCommand.trim() === "") {
368598
+ throw new Error(`Volume "${name}" has an invalid "reset_command" value. Must be a non-empty string.`);
368599
+ }
368600
+ volume.reset_command = resetCommand;
368601
+ }
368602
+ }
368603
+ volumes.push(volume);
368593
368604
  }
368594
368605
  }
368595
368606
  return volumes;
@@ -374729,7 +374740,7 @@ function trackEvent(event, properties) {
374729
374740
  event,
374730
374741
  properties: {
374731
374742
  ...properties,
374732
- cli_version: "0.1.174",
374743
+ cli_version: "0.1.176",
374733
374744
  platform: process.platform,
374734
374745
  node_version: process.version,
374735
374746
  project_id: getProjectId()
@@ -375591,8 +375602,19 @@ function parseVolumes2(fieldObj) {
375591
375602
  return volumes;
375592
375603
  }
375593
375604
  if (!Array.isArray(volumeData)) {
375594
- for (const [name] of Object.entries(volumeData)) {
375595
- volumes.push({ name });
375605
+ for (const [name, fieldsArray] of Object.entries(volumeData)) {
375606
+ const volume = { name };
375607
+ const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
375608
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
375609
+ const resetCommand = fields.reset_command;
375610
+ if (resetCommand !== void 0) {
375611
+ if (typeof resetCommand !== "string" || resetCommand.trim() === "") {
375612
+ throw new Error(`Volume "${name}" has an invalid "reset_command" value. Must be a non-empty string.`);
375613
+ }
375614
+ volume.reset_command = resetCommand;
375615
+ }
375616
+ }
375617
+ volumes.push(volume);
375596
375618
  }
375597
375619
  }
375598
375620
  return volumes;
@@ -376898,6 +376920,7 @@ function formatErrorCode(code) {
376898
376920
  deploy_failed: "Service failed to deploy",
376899
376921
  health_check_failed: "Health check failed",
376900
376922
  post_deploy_failed: "Post-deploy hook failed",
376923
+ volume_reset_failed: "Volume reset failed",
376901
376924
  internal_error: "Internal error"
376902
376925
  };
376903
376926
  return labels[code] || code;
@@ -377897,6 +377920,7 @@ async function runDeployPipeline(options2) {
377897
377920
  } else if (!hasProjectId(projectDir)) {
377898
377921
  writeProjectId(projectId);
377899
377922
  }
377923
+ let previewEnvironmentId;
377900
377924
  if (options2.preview) {
377901
377925
  const previousPreviewId = readPreviewEnvironmentId(projectDir);
377902
377926
  if (previousPreviewId) {
@@ -377910,7 +377934,7 @@ async function runDeployPipeline(options2) {
377910
377934
  const preview = await client2.createPreviewEnvironment(projectId);
377911
377935
  writePreviewEnvironmentId(preview.id, projectDir);
377912
377936
  console.log(`Preview environment "${preview.name}" created (expires: ${new Date(preview.expiresAt).toLocaleString()})`);
377913
- options2.environment = preview.name;
377937
+ previewEnvironmentId = preview.id;
377914
377938
  }
377915
377939
  const projects = await client2.listProjects();
377916
377940
  const project = projects.find((p) => p.id === projectId);
@@ -377929,7 +377953,10 @@ async function runDeployPipeline(options2) {
377929
377953
  }
377930
377954
  const environments = project.environments;
377931
377955
  let environmentId;
377932
- if (options2.environment) {
377956
+ if (previewEnvironmentId) {
377957
+ environmentId = previewEnvironmentId;
377958
+ writeEnvironmentId(previewEnvironmentId);
377959
+ } else if (options2.environment) {
377933
377960
  const match = findEnvironmentByNameOrId(environments, options2.environment);
377934
377961
  if (!match) {
377935
377962
  console.error(
@@ -379088,7 +379115,7 @@ function compareVersions(a, b) {
379088
379115
  return 0;
379089
379116
  }
379090
379117
  async function checkForUpdate() {
379091
- const currentVersion = "0.1.174";
379118
+ const currentVersion = "0.1.176";
379092
379119
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
379093
379120
  if (!response.ok) {
379094
379121
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -379375,6 +379402,12 @@ function PreviewBlock({ env: env2 }) {
379375
379402
  const urls = managedUrls(env2.domains);
379376
379403
  return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), env2.prNumber !== null && /* @__PURE__ */ React12.createElement(Text12, null, " ", "PR #", env2.prNumber, env2.prTitle ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " \u2014 ", env2.prTitle) : null), urls.length > 0 && /* @__PURE__ */ React12.createElement(Text12, null, " ", "URL: ", urls.join(", ")), env2.expiresAt && /* @__PURE__ */ React12.createElement(Text12, null, " ", "Expires: ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, new Date(env2.expiresAt).toLocaleString())));
379377
379404
  }
379405
+ function EnvironmentBlock({ env: env2 }) {
379406
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), env2.domains && Object.keys(env2.domains).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Domains:"), Object.entries(env2.domains).map(([serviceName, urls]) => {
379407
+ const displayUrls = urls.custom.length > 0 ? urls.custom : urls.managed ? [urls.managed] : [];
379408
+ return /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", serviceName, displayUrls.length > 0 ? `: ${displayUrls.join(", ")}` : null);
379409
+ })));
379410
+ }
379378
379411
  function StatusUI() {
379379
379412
  const [state, setState] = useState11({ phase: "loading" });
379380
379413
  useEffect11(() => {
@@ -379403,7 +379436,7 @@ function StatusUI() {
379403
379436
  return /* @__PURE__ */ React12.createElement(Text12, { color: "red" }, "Failed to get status: ", state.error);
379404
379437
  }
379405
379438
  const status = state.status;
379406
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.") : status.environments.map((env2) => /* @__PURE__ */ React12.createElement(Box11, { key: env2.id, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), env2.domains && Object.keys(env2.domains).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Domains:"), Object.entries(env2.domains).map(([serviceName, urls]) => /* @__PURE__ */ React12.createElement(Box11, { key: serviceName, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " - ", serviceName, urls.managed ? `: ${urls.managed}` : null), urls.custom.map((url) => /* @__PURE__ */ React12.createElement(Text12, { key: url }, " ", url))))))), (status.previewEnvironments ?? []).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview environments"), status.previewEnvironments.map((env2) => /* @__PURE__ */ React12.createElement(PreviewBlock, { key: env2.id, env: env2 }))));
379439
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Environments"), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.")) : /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.environments.map((env2) => /* @__PURE__ */ React12.createElement(EnvironmentBlock, { key: env2.id, env: env2 })))), (status.previewEnvironments ?? []).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview environments"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.previewEnvironments.map((env2) => /* @__PURE__ */ React12.createElement(PreviewBlock, { key: env2.id, env: env2 })))));
379407
379440
  }
379408
379441
  function statusCommand() {
379409
379442
  render10(/* @__PURE__ */ React12.createElement(StatusUI, null));
@@ -379762,7 +379795,7 @@ async function domainAddCommand(service, domain, options2) {
379762
379795
  var program = new Command();
379763
379796
  var env = "production";
379764
379797
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
379765
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.174").enablePositionalOptions();
379798
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.176").enablePositionalOptions();
379766
379799
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
379767
379800
  Examples:
379768
379801
  $ specific init
@@ -35,9 +35,7 @@ TLS certificates are issued automatically via Let's Encrypt once DNS resolves to
35
35
 
36
36
  ## Confirming the domain
37
37
 
38
- After `specific domain add`, run `specific status` to confirm the domain is registered. Custom domains appear under the corresponding service alongside its managed `.spcf.app` URL.
39
-
40
- Note that `specific status` only confirms the domain is registered with Specific — it does not verify that the user has configured DNS correctly or that TLS has been issued. To verify end-to-end, the user can `curl https://<domain>` once they've configured DNS.
38
+ Adding a domain registers it with Specific, but that only confirms it on Specific's side it does not verify that the user has configured DNS correctly or that TLS has been issued. To verify end-to-end, the user can `curl https://<domain>` once they've configured DNS.
41
39
 
42
40
  ## Managing domains in the dashboard
43
41
 
@@ -28,6 +28,7 @@ After deploying, the user can manage their project from the Specific Dashboard a
28
28
  - [Volumes](/volumes): define persistent storage volumes for services to store files in.
29
29
  - [Temporal](/temporal): managed durable workflow engine for background tasks, AI agents and more.
30
30
  - [Observability](/observability): query logs and metrics from running environments using `specific query` for debugging and analytics.
31
+ - [Preview environments](/previews): ephemeral copies of an environment created per pull request, with the parent's databases branched, object storage forked, and volumes cloned. Covers resetting cloned volume state for stateful services.
31
32
  <!-- beta:mail -->
32
33
  - [Mail](/mail): managed email sending via SMTP for transactional emails.
33
34
  <!-- /beta:mail -->
@@ -22,7 +22,7 @@ specific query --format csv "SELECT ServiceName, count() FROM observability.logs
22
22
 
23
23
  Flags:
24
24
 
25
- - `-e, --environment <name|id>` — target environment (defaults to the current one).
25
+ - `-e, --environment <name|id>` — target environment by name or ID (defaults to the current one). Run `specific status` to list every environment and its ID, including preview environments (see "Choosing an environment" below).
26
26
  - `--format <table|json|jsonl|csv>` — output format. When stdout is a terminal, the default is `table`. When stdout is piped or redirected, the default is `jsonl`.
27
27
 
28
28
  Queries are automatically scoped to the selected environment. You cannot see data from other environments and you do not need to filter on environment yourself.
@@ -31,6 +31,32 @@ Queries are read-only. `INSERT`, `UPDATE`, `DELETE`, and DDL are rejected. Each
31
31
 
32
32
  The table format is for quick human inspection and truncates wide values to keep terminal output readable. Agents and scripts should use `jsonl`, `json`, or `csv` to get complete, untruncated cell values. Lookback notices are printed to stderr so stdout remains parseable.
33
33
 
34
+ ## Choosing an environment
35
+
36
+ `specific query` runs against exactly one environment. With no `-e/--environment` flag it targets the current environment (the one saved in `.specific/`); otherwise pass an environment **name or ID**.
37
+
38
+ Run `specific status` to discover every environment in the project and its ID. The output has two sections:
39
+
40
+ - **Environments** — the long-lived environments such as `production` and `staging`.
41
+ - **Preview environments** — ephemeral environments created for pull requests (plus any manually created previews). Each line shows the environment name, its `env_…` ID, the PR number and title (for pull-request previews), the deployed URL, and any expiry time.
42
+
43
+ Preview environments stream logs and metrics exactly like long-lived ones, so a preview deploy is queryable with the same SQL — you just have to target it. Copy the preview's name or ID from `specific status` and pass it to `--environment`:
44
+
45
+ ```sh
46
+ # List environments (standard + preview) and their IDs, then copy the one you want
47
+ specific status
48
+
49
+ # Query a preview environment by name (or by its env_… ID)
50
+ specific query --environment preview-a1b2c3d4 \
51
+ "SELECT Timestamp, ServiceName, Body
52
+ FROM observability.logs
53
+ WHERE SeverityNumber >= 17
54
+ ORDER BY Timestamp DESC
55
+ LIMIT 50"
56
+ ```
57
+
58
+ This is the only way to reach a preview environment's data: there is no environment column to filter on (queries are auto-scoped), so to inspect a specific preview you must select it with `--environment`.
59
+
34
60
  ## Schema
35
61
 
36
62
  Two views are exposed in the `observability` database.
@@ -197,5 +223,6 @@ The store is ClickHouse, so the full ClickHouse SQL dialect is available. A few
197
223
 
198
224
  Related topics:
199
225
 
226
+ - Run `specific status` to list environments (standard and preview) and their IDs before querying a specific one
200
227
  - Run `specific docs services` for how services are defined
201
228
  - Run `specific docs postgres` for connecting to your application database (use `specific psql` for application data, not `specific query`)
@@ -0,0 +1,57 @@
1
+ # Preview Environments
2
+
3
+ A preview environment is a temporary, fully isolated copy of an existing environment. It runs the same services with their own URLs and their own network namespace, on top of its own copy of the parent's stateful data — so a change can be tested end to end without touching the parent.
4
+
5
+ ## How previews are created
6
+
7
+ - **Per pull request** — with the GitHub integration connected, Specific creates a preview environment for each open pull request, keeps it up to date as new commits are pushed, and posts its URL back on the PR.
8
+ - **Manually** — previews can also be created on demand.
9
+
10
+ Preview environments are ephemeral: pull-request previews are removed when the PR is closed or merged, and a preview can be given an expiry, after which Specific cleans it up automatically.
11
+
12
+ Run `specific status` to list every environment and its `env_…` ID, including preview environments. Target a preview with `--environment` on commands that take one (see `specific docs observability`).
13
+
14
+ ## What's copied from the parent
15
+
16
+ A preview branches off a parent environment and starts from its data. Each resource is forked in the way that's fastest for that service:
17
+
18
+ - **Postgres** databases are branched (an instant, copy-on-write branch).
19
+ - **Object storage** buckets are forked.
20
+ - **Volumes** are cloned — a byte-for-byte copy of the parent volume's contents.
21
+
22
+ The preview gets its own isolated database, buckets, and volumes; writing to them never affects the parent.
23
+
24
+ ## Volumes in previews
25
+
26
+ Because a volume is copied byte for byte, a preview's service starts on an exact replica of the parent's on-disk data. For most volumes (uploads, caches, plain database files) that is exactly what you want, and no extra configuration is needed.
27
+
28
+ But some stateful services store their **node or cluster identity inside the volume**, and a cloned instance that inherits the parent's identity can fail to start. The clearest example is **Typesense**, which keeps its raft consensus state — its node identity and peer list — under `state/` in the data directory. On a clone, the new node finds a cluster configuration that belongs to the parent and can't elect itself, so it crash-loops.
29
+
30
+ For services like this, set `reset_command` on the volume. Specific runs it **once** against the freshly cloned volume — before the service starts, in the service's image, with the volume mounted at `/volumes/<name>` — so the service can scrub state that must not be inherited:
31
+
32
+ ```hcl
33
+ service "search" {
34
+ image = "typesense/typesense:30.1"
35
+ command = "typesense-server --data-dir=/volumes/data --api-port=$PORT --api-address=0.0.0.0"
36
+
37
+ volume "data" {
38
+ reset_command = "rm -rf /volumes/data/state"
39
+ }
40
+
41
+ env = {
42
+ PORT = port
43
+ }
44
+ }
45
+ ```
46
+
47
+ - It runs **only** on cloned volumes in preview environments — never in production, and never on a freshly provisioned volume.
48
+ - A non-zero exit fails the deploy, so a botched reset never starts the service on bad data.
49
+ - Most services need no `reset_command` — reach for it only when a service stores node or cluster identity inside its volume.
50
+
51
+ ---
52
+
53
+ Related topics:
54
+
55
+ - Run `specific docs volumes` for declaring persistent volumes
56
+ - Run `specific docs services` for service configuration
57
+ - Run `specific docs observability` for querying a preview's logs and metrics
@@ -560,3 +560,5 @@ Related topics:
560
560
  - Run `specific docs crons` for scheduled jobs (a service that runs on a schedule)
561
561
  - Run `specific docs exec` for running one-off commands during development
562
562
  - Run `specific docs postgres` for database configuration
563
+ - Run `specific docs volumes` for persistent storage volumes
564
+ - Run `specific docs previews` for preview environments (and resetting cloned volume state)
@@ -61,3 +61,30 @@ fs.writeFileSync(path.join(uploadsDir, "myfile.txt"), "Hello, world!");
61
61
  // Read it back (persists across restarts)
62
62
  const content = fs.readFileSync(path.join(uploadsDir, "myfile.txt"), "utf8");
63
63
  ```
64
+
65
+ ## Preview environments
66
+
67
+ Preview environments clone each volume's contents byte for byte from the parent environment. For most volumes (uploads, caches, plain database files) that's exactly what you want, and no extra configuration is needed.
68
+
69
+ Some stateful services store node or cluster identity inside the volume and crash-loop when a clone inherits the parent's identity — for example Typesense keeps its raft consensus state under `state/`. For those, set `reset_command` on the volume to scrub that state once, on the freshly cloned volume, before the service starts in a preview:
70
+
71
+ ```hcl
72
+ service "search" {
73
+ image = "typesense/typesense:30.1"
74
+ command = "typesense-server --data-dir=/volumes/data --api-port=$PORT --api-address=0.0.0.0"
75
+
76
+ volume "data" {
77
+ reset_command = "rm -rf /volumes/data/state"
78
+ }
79
+
80
+ env = {
81
+ PORT = port
82
+ }
83
+ }
84
+ ```
85
+
86
+ - It runs **only** on cloned volumes in preview environments — never in production, and never on a freshly provisioned volume.
87
+ - A non-zero exit fails the deploy.
88
+ - Most services need no `reset_command` — reach for it only when a service stores node or cluster identity inside its volume.
89
+
90
+ See `specific docs previews` for how preview environments work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.174",
3
+ "version": "0.1.176",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",