@flisk-sh/flisk 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +2 -2
  2. package/cli/dist/agent-runtimes/custom.js +94 -13
  3. package/cli/dist/commands/agent.js +144 -123
  4. package/cli/dist/commands/doctor.js +12 -0
  5. package/cli/dist/commands/logs.js +71 -4
  6. package/cli/dist/commands/project.js +16 -0
  7. package/cli/dist/commands/resource.js +3 -1
  8. package/cli/dist/commands/status.js +43 -24
  9. package/cli/dist/commands/ui.js +41 -2
  10. package/cli/dist/commands/up.js +54 -11
  11. package/cli/dist/components/DetailPanel.js +23 -3
  12. package/cli/dist/mcp/tools.js +9 -6
  13. package/cli/dist/types/index.js +0 -2
  14. package/cli/dist/utils/agent-format.js +18 -0
  15. package/cli/dist/utils/agent-sessions.js +122 -0
  16. package/cli/dist/utils/agent-skills.js +64 -0
  17. package/cli/dist/utils/cold-preflight.js +3 -0
  18. package/cli/dist/utils/doctor-fix-first.js +93 -0
  19. package/cli/dist/utils/doctor-report.js +3 -0
  20. package/cli/dist/utils/native-runtime.js +3 -0
  21. package/cli/dist/utils/native-service.js +2 -2
  22. package/cli/dist/utils/service-manifest.js +8 -15
  23. package/cli/dist/utils/services.js +7 -10
  24. package/cli/dist/utils/startup-report.js +45 -3
  25. package/cli/dist/utils/status-groups.js +23 -0
  26. package/cli/dist/utils/up-hints.js +42 -0
  27. package/cli/dist/utils/up-progress.js +117 -0
  28. package/cli/dist/utils/up-readiness.js +5 -0
  29. package/cli/dist-bundle/{chunk-U5FCQF42.js → chunk-6OW2GVQV.js} +1 -1
  30. package/cli/dist-bundle/chunk-B2JOM3HN.js +35 -0
  31. package/cli/dist-bundle/{chunk-R7DWXISM.js → chunk-F2DLIM75.js} +1 -1
  32. package/cli/dist-bundle/{chunk-ETXZ5LUG.js → chunk-L6O7TT3K.js} +10 -10
  33. package/cli/dist-bundle/chunk-MFY7RZ76.js +130 -0
  34. package/cli/dist-bundle/{chunk-MUDE4DZD.js → chunk-MYAYJF4Q.js} +1 -1
  35. package/cli/dist-bundle/{chunk-DPQK5TIS.js → chunk-P7GZ6NGM.js} +1 -1
  36. package/cli/dist-bundle/{chunk-IDNES5PE.js → chunk-T44DEEL6.js} +1 -1
  37. package/cli/dist-bundle/{chunk-C4UFBXRD.js → chunk-TIPSERWO.js} +1 -1
  38. package/cli/dist-bundle/{chunk-BIVAAWBX.js → chunk-VYPB7MY5.js} +1 -1
  39. package/cli/dist-bundle/{chunk-WCGBVETR.js → chunk-XHENT4KO.js} +1 -1
  40. package/cli/dist-bundle/{chunk-CAEMWNPF.js → chunk-YTBTCAWY.js} +1 -1
  41. package/cli/dist-bundle/cli.js +462 -377
  42. package/cli/dist-bundle/{cold-preflight-KUWOL2Q2.js → cold-preflight-DQVJUGBB.js} +1 -1
  43. package/cli/dist-bundle/{demo-stage-QCC4IWXR.js → demo-stage-F4YA7UL2.js} +1 -1
  44. package/cli/dist-bundle/{open-AZNX4AC5.js → open-ZUSSVPD3.js} +1 -1
  45. package/cli/dist-bundle/project-JFCTPRP5.js +2 -0
  46. package/cli/dist-bundle/{services-EY5WKPHQ.js → services-DOHDHOCT.js} +1 -1
  47. package/cli/dist-bundle/{tilt-port-owner-UBU6JWOA.js → tilt-port-owner-XODQHXHX.js} +1 -1
  48. package/cli/package.json +1 -1
  49. package/cli/templates/agent-skills/flisk-agent-ops/SKILL.md +39 -0
  50. package/cli/templates/agent-skills/flisk-build/SKILL.md +85 -0
  51. package/cli/templates/agent-skills/flisk-doctor/SKILL.md +28 -0
  52. package/cli/templates/agent-skills/flisk-recipe-ai-agent/SKILL.md +45 -0
  53. package/cli/templates/agent-skills/flisk-recipe-blog/SKILL.md +36 -0
  54. package/cli/templates/agent-skills/flisk-recipe-saas/SKILL.md +39 -0
  55. package/cli/templates/agent-skills/flisk-recipe-shop/SKILL.md +41 -0
  56. package/cli/templates/agent-skills/flisk-recipe-starters/SKILL.md +46 -0
  57. package/cli/templates/agent-skills/flisk-resource/SKILL.md +27 -0
  58. package/cli/templates/agent-skills/flisk-status/SKILL.md +25 -0
  59. package/cli/templates/agent-skills/flisk-troubleshoot/SKILL.md +227 -0
  60. package/cli/templates/agent-skills/flisk-up/SKILL.md +37 -0
  61. package/package.json +1 -1
  62. package/runtime/engine/schemas/service-schema.json +3 -15
  63. package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +7 -1
  64. package/cli/dist-bundle/chunk-CDKEHFW6.js +0 -35
  65. package/cli/dist-bundle/chunk-GT76VMUL.js +0 -130
  66. package/cli/dist-bundle/project-5SWIGCNP.js +0 -2
@@ -0,0 +1,117 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { effectiveRuntimeStatus } from "./tilt-resource-state.js";
4
+ import { isAdvisoryResource } from "./up-readiness.js";
5
+ /**
6
+ * The state of each resource `flisk up` waits for, from `tilt get uiresources -o json`. Disabled, deferred, advisory and
7
+ * the `(Tiltfile)` resource are left out, so the count matches what readiness gates on.
8
+ */
9
+ export function classifyUpProgress(jsonText, deferred) {
10
+ const items = JSON.parse(jsonText).items ?? [];
11
+ const services = [];
12
+ for (const item of items) {
13
+ const name = item.metadata?.name;
14
+ if (!name || name === "(Tiltfile)" || deferred.has(name) || isAdvisoryResource(name))
15
+ continue;
16
+ if (item.status?.disableStatus?.state === "Disabled")
17
+ continue;
18
+ const rawUpdate = item.status?.updateStatus ?? "";
19
+ // A serve-only local resource has nothing to build.
20
+ const update = rawUpdate === "not_applicable" ? "ok" : rawUpdate;
21
+ const runtime = effectiveRuntimeStatus(item.status);
22
+ let state;
23
+ if (update === "error" || runtime === "error")
24
+ state = "failed";
25
+ else if (update === "in_progress")
26
+ state = "building";
27
+ else if (update === "ok")
28
+ state = runtime === "pending" || runtime === "none" ? "starting" : "ready";
29
+ else
30
+ state = "waiting";
31
+ services.push({ name, state });
32
+ }
33
+ return services.sort((a, b) => a.name.localeCompare(b.name));
34
+ }
35
+ /** `5/9 ready`, then the services in each unfinished state. */
36
+ export function formatUpProgressLine(services) {
37
+ const ready = services.filter((service) => service.state === "ready").length;
38
+ const parts = [`${ready}/${services.length} ready`];
39
+ for (const state of ["failed", "building", "starting", "waiting"]) {
40
+ const names = services.filter((service) => service.state === state).map((s) => s.name);
41
+ if (names.length > 0)
42
+ parts.push(`${state}: ${names.join(", ")}`);
43
+ }
44
+ return parts.join(" · ");
45
+ }
46
+ export function formatServiceFailedLine(name) {
47
+ return `✗ ${name} failed. See its logs: flisk logs -s ${name}`;
48
+ }
49
+ /**
50
+ * Redraw one line in place only on an interactive terminal. `--quiet`, a pipe, CI and `TERM=dumb` get plain lines with
51
+ * no cursor control.
52
+ */
53
+ export function shouldRedrawUpProgress(input) {
54
+ if (!input.isTTY || input.quiet)
55
+ return false;
56
+ if (input.env.CI && input.env.CI !== "false" && input.env.CI !== "0")
57
+ return false;
58
+ return input.env.TERM?.toLowerCase().startsWith("dumb") !== true;
59
+ }
60
+ /**
61
+ * Shows per-service progress while `flisk up` waits for Tilt. Redraw mode keeps one line current and prints a failure as
62
+ * a line of its own; plain mode prints one line per state change.
63
+ */
64
+ export function createUpProgressReporter(options) {
65
+ const previous = new Map();
66
+ let lineShown = false;
67
+ const clearLine = () => {
68
+ if (lineShown)
69
+ options.write("\r\x1b[2K");
70
+ lineShown = false;
71
+ };
72
+ return {
73
+ update(jsonText) {
74
+ let services;
75
+ try {
76
+ services = classifyUpProgress(jsonText, options.deferred);
77
+ }
78
+ catch {
79
+ return; // Tilt can answer with partial output while it starts.
80
+ }
81
+ if (services.length === 0)
82
+ return;
83
+ const ready = services.filter((service) => service.state === "ready").length;
84
+ const changed = services.filter((service) => previous.get(service.name) !== service.state);
85
+ for (const service of services)
86
+ previous.set(service.name, service.state);
87
+ if (options.redraw) {
88
+ const failed = changed.filter((service) => service.state === "failed");
89
+ if (failed.length > 0)
90
+ clearLine();
91
+ for (const service of failed)
92
+ options.write(`${formatServiceFailedLine(service.name)}\n`);
93
+ const width = Math.max(20, (options.columns ?? 80) - 1);
94
+ const line = `⏳ ${formatUpProgressLine(services)}`;
95
+ options.write(`\r\x1b[2K${line.length > width ? `${line.slice(0, width - 1)}…` : line}`);
96
+ lineShown = true;
97
+ return;
98
+ }
99
+ for (const service of changed) {
100
+ const count = `(${ready}/${services.length} ready)`;
101
+ options.write(service.state === "failed"
102
+ ? `${formatServiceFailedLine(service.name)} ${count}\n`
103
+ : `${service.name}: ${service.state} ${count}\n`);
104
+ }
105
+ },
106
+ finish: clearLine,
107
+ };
108
+ }
109
+ /** Wraps the readiness poll so every answer from Tilt also updates the progress. */
110
+ export function fetchWithProgress(fetchJson, progress) {
111
+ return async (port) => {
112
+ const text = await fetchJson(port);
113
+ if (text)
114
+ progress.update(text);
115
+ return text;
116
+ };
117
+ }
@@ -8,6 +8,11 @@ export function tiltGetUiResources(port) {
8
8
  execFile(findOnPath("tilt") ?? "tilt", ["get", "uiresources", "-o", "json", "--port", String(port)], { timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => resolve(error ? null : stdout));
9
9
  });
10
10
  }
11
+ export function tiltResourceLogs(port, name) {
12
+ return new Promise((resolve) => {
13
+ execFile(findOnPath("tilt") ?? "tilt", ["logs", name, "--port", String(port)], { timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => resolve(error ? null : stdout));
14
+ });
15
+ }
11
16
  /**
12
17
  * Tilt lists resources outside the current focus (the other release phases, or everything but a `--only` selection) with
13
18
  * `disableStatus.state: Disabled` and both statuses `none`, which would read as pending forever. A resource whose update
@@ -1,5 +1,5 @@
1
1
  import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
- import{b}from"./chunk-MINZ25AC.js";import{g as m,r as T}from"./chunk-BIVAAWBX.js";import{a as S}from"./chunk-WCGBVETR.js";import{K as k,M as $,ka as x,p as y,q as w}from"./chunk-ETXZ5LUG.js";import{b as n,f as d,m as g,x as h}from"./chunk-C4UFBXRD.js";import{Command as R}from"commander";import{readFileSync as N}from"node:fs";import{join as O}from"node:path";function F(o){let e=o.config;return e?Array.isArray(e.featuresEnabled)&&e.featuresEnabled.includes(d)?!0:(Array.isArray(e.dependsOn)?e.dependsOn:[]).some(a=>S.includes(a)):!1}function J(o,e){let s=o.replace(/-/g,"_"),a=[];for(let t of[...e].sort((r,c)=>r.name.localeCompare(c.name))){let r=t.resources.filter(F).map(c=>c.name);r.length!==0&&a.push({name:`${s}_${t.name.replace(/-/g,"_")}`,stack:t.name,services:r.sort()})}return a.length===0?null:{name:"postgres",databases:a}}function E(o){try{let e=JSON.parse(N(O(o,".flisk",g),"utf-8"));return typeof e?.project?.name=="string"?e.project.name:null}catch{return null}}var P=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function A(o){let e=process.stderr,s=e.isTTY===!0,a=0,t=o,r,c=()=>e.write(`\r\x1B[2K${n.cyan(P[a])} ${t}`);s?(c(),r=setInterval(()=>{a=(a+1)%P.length,c()},80)):e.write(`- ${t}
2
+ import{b}from"./chunk-MINZ25AC.js";import{g as m,r as T}from"./chunk-VYPB7MY5.js";import{a as S}from"./chunk-XHENT4KO.js";import{K as k,M as $,ka as x,p as y,q as w}from"./chunk-L6O7TT3K.js";import{b as n,f as d,m as g,x as h}from"./chunk-TIPSERWO.js";import{Command as R}from"commander";import{readFileSync as N}from"node:fs";import{join as O}from"node:path";function F(o){let e=o.config;return e?Array.isArray(e.featuresEnabled)&&e.featuresEnabled.includes(d)?!0:(Array.isArray(e.dependsOn)?e.dependsOn:[]).some(a=>S.includes(a)):!1}function J(o,e){let s=o.replace(/-/g,"_"),a=[];for(let t of[...e].sort((r,c)=>r.name.localeCompare(c.name))){let r=t.resources.filter(F).map(c=>c.name);r.length!==0&&a.push({name:`${s}_${t.name.replace(/-/g,"_")}`,stack:t.name,services:r.sort()})}return a.length===0?null:{name:"postgres",databases:a}}function E(o){try{let e=JSON.parse(N(O(o,".flisk",g),"utf-8"));return typeof e?.project?.name=="string"?e.project.name:null}catch{return null}}var P=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function A(o){let e=process.stderr,s=e.isTTY===!0,a=0,t=o,r,c=()=>e.write(`\r\x1B[2K${n.cyan(P[a])} ${t}`);s?(c(),r=setInterval(()=>{a=(a+1)%P.length,c()},80)):e.write(`- ${t}
3
3
  `);let l=(i,u)=>{r&&clearInterval(r),e.write(`${s?"\r\x1B[2K":""}${i} ${u??t}
4
4
  `)};return{get text(){return t},set text(i){t=i,s||e.write(`- ${t}
5
5
  `)},succeed:i=>l(n.green("\u2714"),i),fail:i=>l(n.red("\u2716"),i),warn:i=>l(n.yellow("\u26A0"),i)}}async function j(o){let{resources:e}=x(o),s=await m(o,{inspectDocker:!1}),{appBase:a}=k(s?.ingressHttp,o);return e.filter(t=>t.config?.appType==="frontend").map(t=>({name:t.name,url:`${a}${$(t)}`}))}async function v(o,e=2e3){try{return(await fetch(o,{signal:AbortSignal.timeout(e)})).status<500}catch{return!1}}async function D(o,e=3e4,s){if(o.length===0)return;let a=Date.now();for(;;){for(let r of o)if(await v(r.url))return r;let t=Date.now()-a;if(t>=e)return;s?.(t),await new Promise(r=>setTimeout(r,2e3))}}async function te(o,e=0,s){let a=await j(o);return D(a,e,s)}async function C(o){let e=o??h();if(!e)return console.log(n.yellow("Not in a Flisk project, so there is no frontend to open.")),console.log(n.gray("Run `flisk project --yes` first, or `flisk open` inside a project.")),null;let s=await j(e);if(s.length===0)return console.log(n.yellow("This project has no frontend service to open.")),console.log(n.gray("Start the stack with `flisk up` and add a frontend with `flisk resource`.")),null;let a=E(e);y(`Opening the first frontend of ${a??"this project"}`);for(let l of s)console.log(n.gray(` - ${l.name}: ${l.url}`));let t=A("Waiting for the frontend to answer..."),r=await D(s,12e4,l=>{t.text=`Waiting for the frontend to answer... ${Math.round(l/1e3)}s`});if(!r){t.fail("No frontend answered yet.");let i=(await m(e,{inspectDocker:!1}))?.ingressHttp,u=i?await T(i)==="in-use":!1;if(i&&!u){console.log(n.yellow(`The stack is running, but this machine cannot reach port ${i}.`)),console.log(n.gray(" Docker is not publishing the port to the host."));let{checkColima:f}=await import("./colima-GBURKN54.js"),p=f();p.isColima&&!p.running?(console.log(n.gray(` ${p.problem??"The Colima VM is not running"}`)),console.log(n.gray(` Fix: ${p.fix??"colima start"}`))):console.log(n.gray(" Restart the Docker runtime (Docker Desktop or Colima), then run `flisk open` again."))}else console.log(n.yellow("The stack is not serving the frontend yet.")),console.log(n.gray(" - Start the stack in another terminal: flisk up")),console.log(n.gray(" - Then run this again: flisk open"));for(let f of s)console.log(n.gray(` - ${f.name}: ${f.url}`));return null}t.succeed(`The frontend is up: ${r.name}`);let c=await b(r.url);return console.log(c?n.green(`
@@ -0,0 +1,35 @@
1
+ import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
+ import{a as T}from"./chunk-QEBN6ZVQ.js";import{b as C,c as V,d as Y,m as me,p as L,q as H,v as J,w as I,y as O}from"./chunk-MYAYJF4Q.js";import{C as re,D as K,E as W,fa as ae,h as D,i as B,ka as ce,la as le,na as fe,o as g,p as v,pa as de,q as o,qa as pe,ra as ue}from"./chunk-L6O7TT3K.js";import{N as U,P as z,R as A,b as m,f as E,m as R,o as P,x as N}from"./chunk-TIPSERWO.js";import{execFileSync as Fe}from"node:child_process";import{cpSync as Re,existsSync as b,readdirSync as Ne,readFileSync as Q,writeFileSync as De}from"node:fs";import{basename as be,dirname as Le,isAbsolute as Je,join as j,resolve as q}from"node:path";import{cwd as je}from"node:process";import{fileURLToPath as Ie}from"node:url";import{Command as Ge}from"commander";import{existsSync as ge,mkdirSync as _e,writeFileSync as he}from"node:fs";import G from"node:path";var xe=["flisk-build","flisk-recipe-starters","flisk-recipe-shop","flisk-recipe-saas","flisk-recipe-ai-agent","flisk-recipe-blog","flisk-doctor","flisk-up","flisk-resource","flisk-status","flisk-troubleshoot","flisk-agent-ops"],Ee=[".claude/skills",".agents/skills"],Pe="https://github.com/flisk-sh/flisk/blob/main/docs/",Ae="# AGENTS.md\n\nThis project runs on [Flisk](https://github.com/flisk-sh/flisk). To build or run something here, follow the Flisk skills in `.agents/skills` (Claude Code: `.claude/skills`), starting with `flisk-build`.\n\n- Add a service: `flisk resource <name> --type <frontend|backend|worker|agent|mcp> --stack <stack> --yes`\n- Start it: `flisk up <stack>`, then check every URL from `flisk networks`\n- When something fails: `flisk doctor`, then `flisk logs -s <service>`\n- Do not hand-write Dockerfiles, Compose files or Tiltfiles, and do not edit `.autogenerated/`: change `service.json` instead.\n";function Ce(e){return e.replace(/\]\((?:\.\.\/)+docs\//g,`](${Pe}`)}function ke(e){let t=[];for(let a of xe){let f=Ce(me(`agent-skills/${a}/SKILL.md`));for(let d of Ee){let i=G.join(d,a,"SKILL.md"),n=G.join(e,i);ge(n)||(_e(G.dirname(n),{recursive:!0}),he(n,f),t.push(i))}}let c=G.join(e,"AGENTS.md");return ge(c)||(he(c,Ae),t.push("AGENTS.md")),t}var Oe="-y, --yes",Te="Skip prompts and confirmations; use defaults";function Se(e,t=Te){return e.option(Oe,t,!1)}async function $e(e,t=!0){let c=await I({message:e,initial:t});return c||D(),c}function Ze(e,t=1){e.valid||A(e.error??"Validation failed",t)}function et(e,t,c,a){return e.dryRun?(a?a():(console.log(`Dry run - ${t}`),console.log(`Would run: ${c}`)),!0):!1}async function tt(e,t){return await $e(e,!0)?!0:(t&&t(),!1)}var M=new Map;function ye(e){e!==void 0?M.delete(e):M.clear()}function st(e=!1){let t=N(),c=t??"";if(!e){let p=M.get(c);if(p)return p}let{resources:a,problems:f}=t?ce(t):{resources:fe(),problems:[]},d=ue(a),i=pe(a);le(f);let n=a.filter(p=>!p.stack),u=new Map;for(let p of a)if(p.stack){let l=u.get(p.stack)||[];l.push(p),u.set(p.stack,l)}let h={resources:a,problems:f,stacks:d,stackNames:i,unassignedResources:n,resourcesByStack:u};return M.set(c,h),h}var $={proxy:{name:"proxy",description:"Traefik reverse proxy and API gateway for routing all services",category:"core",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]},verdaccio:{name:"verdaccio",description:"Verdaccio npm registry for package management and local publishing (Premium - requires FLISK_LICENSE_KEY)",category:"premium",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[]},[E]:{name:E,description:"PostgreSQL database for data persistence and SQL operations",category:"core",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]},monitoring:{name:"monitoring",description:"SigNoz or SkyWalking observability platform for monitoring and tracing",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[E]},elk:{name:"elk",description:"Elasticsearch, Logstash, Kibana stack for log aggregation and analysis",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[]},debezium:{name:"debezium",description:"Change Data Capture for real-time data streaming from databases",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[E]},"golden-image":{name:"golden-image",description:"Pre-built Docker image layer for faster builds and smaller deployments",category:"optional",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]}};function X(e,t){try{return de(e,t)}catch{return[]}}function Me(e){return new Set([...e.phases.pre_alpha.enabledStacks,...e.phases.alpha.enabledStacks,...e.phases.beta.enabledStacks,...e.phases.out_of_scope.enabledStacks])}function Z(e,t){let c=Me(e),a=t.filter(f=>!c.has(f));return a.length===0?(e.phases.pre_alpha.enabledStacks=Array.from(new Set(e.phases.pre_alpha.enabledStacks)),[]):(e.phases.pre_alpha.enabledStacks=Array.from(new Set([...e.phases.pre_alpha.enabledStacks,...a])),a)}function Tt(e,t){let c=j(e,".flisk",R);if(!b(c))return[];let a=L(e),f=Z(a,X(e,t));return f.length>0&&C(c,a),f}function qe(){return Object.values($).filter(e=>e.enabled_by_default&&e.category==="core").map(e=>e.name)}function Ue(e,t){return t||(T[e]?.repo.split("/").pop()??e).replace(/\.git$/,"")}function ve(e){return e.split(`
3
+ `).map(c=>c.trim()).filter(Boolean).pop()??"unknown error"}async function ze(e,t,c,a={}){let f=T[e];if(!f){console.log(m.red(`
4
+ \u274C Unknown template: "${e}"`)),console.log(m.yellow(`
5
+ Available templates:`));for(let[n,u]of Object.entries(T))console.log(` ${m.cyan(n)} ${m.gray(`- ${u.description}`)}`);console.log(m.gray("\nOr run `flisk project` with no template for a blank project.")),process.exit(1)}let d=Ue(e,t);ae(d)||U.invalidPath(d).exit();let i=Je(d)?d:q(je(),d);b(i)&&Ne(i).length>0&&U.directoryExists(i).exit(),a.quiet||(B(`Cloning template: ${e}`),o(`${f.description}
6
+ `,0),o(`Source: ${f.bundledPath?"bundled Flisk example":f.repo}`,0),o(`Destination: ${i}
7
+ `,0));try{if(f.bundledPath){let n=q(Le(Ie(new URL("../dist/commands/project.js",import.meta.url).href)),"../../../");Re(q(n,f.bundledPath),i,{recursive:!0});let u=be(i).replace(/-/g,"_");for(let h of["services/platform/messaging/docker-compose.yml","services/shop/orders-api/service.json","services/shop/orders-api/src/index.ts","services/shop/orders-worker/service.json","services/shop/orders-worker/src/index.ts"]){let p=j(i,h),l=Q(p,"utf-8");De(p,l.replaceAll("flisk_example",u))}}else Fe("git",["clone",f.repo,i],{stdio:["ignore","pipe","pipe"],encoding:"utf-8"})}catch(n){let u=n instanceof Error?n.message:String(n),h=f.bundledPath?`Bundled example copy failed (${ve(u)}). Reinstall the Flisk CLI package and try again.`:`git clone failed: ${ve(u)}. Check you have git installed and network access.`;if(a.quiet)throw new Error(h);A(h)}if(ye(),!a.quiet){console.log(m.green(`
8
+ \u2705 Cloned "${e}" into ${d}/`)),o(`
9
+ Next steps:`,0),o(`1. cd ${d}`),o("2. flisk doctor # check Docker/Tilt are ready"),o("3. flisk up # start the stack"),o(`
10
+ Runtime readiness:`,0);for(let n of c)o(n,0)}}var _={version:"1.0",project:{name:"",version:"1.0.0"},phases:{pre_alpha:{name:"Pre-Alpha",description:"Core infrastructure and MVP services",enabledStacks:qe()},alpha:{name:"Alpha",description:"Essential business services",enabledStacks:[]},beta:{name:"Beta",description:"Extended features",enabledStacks:[]},out_of_scope:{name:"Out of Scope",description:"Future releases",enabledStacks:[]}},optional_infra:{monitoring:!1,elk:!1,debezium:!1,golden_image:!0,verdaccio:!1},discovery:{paths:["services/*/*"]},overrides:{}};function $t(e){let t=JSON.parse(JSON.stringify(_));t.project.name=be(e)||"my-project",V(j(e,".flisk")),C(j(e,".flisk",R),t)}var Ft=Se(new Ge("project")).alias("init").description("Initialize or validate project-level master configuration").argument("[template]",`Clone a starter example instead of a blank project (${Object.keys(T).join(", ")})`).option("--path <dir>","Directory to clone the template into (default: the template's repo name)").option("--check","Check if master configs exist and are in sync").option("--force","Overwrite existing configuration (dangerous)").option("--config-file <path>","Load project config from existing JSON file").option("-q, --quiet","Only print what fails; used by `flisk demo`",!1).option("--no-agent-skills","Do not write the Flisk agent skills (.claude/skills, .agents/skills) and AGENTS.md").action(async(e,t)=>{let{assertMachineReadyOrExit:c,detectRuntimePrerequisites:a,formatRuntimeNextSteps:f}=await import("./cold-preflight-DQVJUGBB.js");await c({scope:"scaffold"});let d=f(a()),i=!!t.quiet;if(e){await z(async()=>ze(e,t.path,d,{quiet:i}));return}await z(async()=>{let n=N();n||(n=je(),i||(v(`\u{1F680} Initializing new Flisk project...
11
+ `),o(`Location: ${n}
12
+ `,0)));let u=j(n,".flisk"),h=j(u,R);if(t.check){let r=P.every(S=>b(j(n,".flisk",".flisk-out",S))),k=b(h);if(r&&k){let S=L(n);g("Project configuration is valid"),o(`Project: ${S.project.name}`,3),o(`Phases: ${Object.keys(S.phases).join(", ")}`,3),process.exit(0)}else{if(console.log(m.yellow("\u26A0\uFE0F Project configuration incomplete:")),k||o(".flisk/project.json (not found)",3),!r)for(let S of P.filter(w=>!b(j(n,".flisk/.flisk-out",w))))o(`.flisk/.flisk-out/${S} (not found)`,3);o("Run `flisk project` to create them."),process.exit(1)}}i||(B("Project Configuration"),o(`Project root: ${n}
13
+ `,0)),b(u)||(V(u),i||g("Created: .flisk/ directory"));let p=b(h);if(p&&!t.force){i||g(".flisk/project.json exists");let r=JSON.parse(Q(h,"utf-8")),k=L(n),S=X(n),w=Z(k,S);if((w.length>0||!r.phases)&&C(h,k),w.length>0&&!i&&o(`Auto-enabled discovered service stacks: ${w.join(", ")}`,0),i||v(`
14
+ \u{1F4CB} Regenerating master configuration files...
15
+ `),await H(n,{quiet:i}),W(n))i||g("Created: .env (the defaults work as-is)");else{let y=K(n);y.length>0&&!i&&g(`Updated: .env (added ${y.join(", ")}; existing values unchanged)`)}let F=Y(n);if(F.length>0&&!i&&g(`Added to .gitignore: ${F.join(", ")}`),!i){console.log(m.green(`
16
+ \u2705 Project configuration regenerated!`)),o(`
17
+ Generated in .flisk/.flisk-out/:`,0);for(let y of P)o(`- ${y}`);o(`
18
+ Runtime readiness:`,0);for(let y of d)o(y,0)}return}if(p&&t.force&&!t.yes&&(console.log(m.red(`
19
+ \u26A0\uFE0F WARNING: --force will overwrite .flisk/project.json!`)),!await I({message:"This will reset your project configuration. Continue?",initial:!1}))){D();return}let l,ee=X(n);if(t.configFile){let r=q(t.configFile);b(r)||A(`Config file not found: ${r}`);let k=Q(r,"utf-8");l=JSON.parse(k),i||g(`Loaded config from: ${r}`)}else if(t.yes)l=JSON.parse(JSON.stringify(_)),l.project.name=n.split("/").pop()||"my-project",i||console.log(m.gray("Using default configuration (non-interactive mode)"));else{v(`\u{1F4DD} Project Setup Wizard
20
+ `);let r=await J({message:"Project name:",initial:n.split("/").pop()||"my-project",validate:s=>s.trim()!==""||"Project name is required"}),k=await J({message:"Project version:",initial:"1.0.0-alpha"}),S=Object.values($).filter(s=>s.category==="core"&&s.phase==="pre_alpha"),w=new Set(S.map(s=>s.name)),ne=await O({message:"Select Pre-Alpha enabled stacks (core infrastructure):",choices:[...S.map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default})),...ee.filter(s=>!w.has(s)).map(s=>({title:`${s} (discovered service stack)`,value:s,selected:!0}))]}),F=await O({message:"Select Alpha enabled stacks (core business):",choices:[{title:"api (Backend API)",value:"api"},{title:"app (Frontend app)",value:"app"}]}),y=await O({message:"Select Beta enabled stacks (extended features):",choices:[{title:"worker (Background jobs)",value:"worker"},{title:"migrator (Database migrations)",value:"migrator"}]}),x=await O({message:"Enable optional infrastructure (high resource):",choices:Object.values($).filter(s=>s.category==="optional").concat(Object.values($).filter(s=>s.category==="premium")).map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default}))});v(`
21
+ \u{1F4C2} Service discovery`),o("What: folder patterns Flisk scans for a service.json in each match."),o("Where: type one or more globs, comma-separated."),o("How: saved as discovery.paths in .flisk/project.json - editable later."),o(`Example: ${_.discovery.paths.join(", ")}
22
+ `);let we=await J({message:"Folders to scan:",initial:_.discovery.paths.join(", ")});l=JSON.parse(JSON.stringify(_)),l.project.name=r,l.project.version=k,l.phases.pre_alpha.enabledStacks=Array.from(new Set(ne)),l.phases.alpha.enabledStacks=Array.from(new Set(F)),l.phases.beta.enabledStacks=Array.from(new Set(y)),l.optional_infra.monitoring=x.includes("monitoring"),l.optional_infra.elk=x.includes("elk"),l.optional_infra.debezium=x.includes("debezium"),l.optional_infra.golden_image=x.includes("golden_image"),l.optional_infra.verdaccio=x.includes("verdaccio");let se=we.split(",").map(s=>s.trim()).filter(s=>s.length>0);l.discovery.paths=se.length>0?se:_.discovery.paths}let te=Z(l,ee);if(te.length>0&&!i&&o(`Auto-enabled discovered service stacks: ${te.join(", ")}`,0),i||v(`
23
+ \u{1F4CB} Creating project configuration...
24
+ `),C(h,l),i||(g("Created: .flisk/project.json"),o(`\u2192 Project: ${l.project.name}`),v(`
25
+ \u{1F4CB} Generating master configuration files...
26
+ `)),await H(n,{quiet:i}),i||v(`
27
+ \u{1F527} Setting up environment variables...
28
+ `),W(n))i||g("Created: .env (the defaults work as-is)");else{let r=K(n);r.length>0?i||g(`Updated: .env (added ${r.join(", ")}; existing values unchanged)`):i||g("Found: .env (environment already configured)")}let oe=re(n);if(oe.missing.length>0){console.log(m.yellow(`
29
+ \u26A0\uFE0F Missing required environment variables:`));for(let r of oe.missing)o(`- ${r}`,1);o("\u2192 Edit .env and fill in these values before running `flisk up`",1)}let ie=Y(n);if(ie.length>0&&!i&&g(`Added to .gitignore: ${ie.join(", ")}`),t.agentSkills!==!1)try{ke(n).length>0&&!i&&g("Added agent skills: .claude/skills, .agents/skills and AGENTS.md (start with flisk-build)")}catch(r){let k=r instanceof Error?r.message:String(r);console.log(m.yellow(`\u26A0\uFE0F Could not add the agent skills: ${k}`))}if(!i){console.log(m.green(`
30
+ \u2705 Project configuration complete!`)),o(`
31
+ Generated files in .flisk/.flisk-out/:`,0);for(let r of P)o(`- ${r}`);o(`
32
+ Environment configuration:`,0),o("- .env (contains all required variables)"),o(`
33
+ Source file:`,0),o("- .flisk/project.json (edit this to change project structure)"),o(`
34
+ Next steps:`,0),o("1. flisk resource api --type backend --stack app # scaffold a service"),o("2. flisk up app # start it"),o(`
35
+ Runtime readiness:`,0);for(let r of d)o(r,0);o("\nAfter editing .flisk/project.json, run `flisk config regenerate`.",0)}})});export{ye as a,st as b,Se as c,Ze as d,et as e,tt as f,Tt as g,Ue as h,ze as i,$t as j,Ft as k};
@@ -1,2 +1,2 @@
1
1
  import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
- import{r as c}from"./chunk-IDNES5PE.js";import{execFileSync as u}from"node:child_process";import{realpathSync as l}from"node:fs";import{resolve as p}from"node:path";var m=(o,t)=>u(o,t,{encoding:"utf-8",stdio:"pipe",timeout:5e3,windowsHide:!0});function a(o,t=process.platform,e=m){if(t==="win32")return null;try{let s=e("lsof",["-nP",`-iTCP:${o}`,"-sTCP:LISTEN","-t"]).split(/\r?\n/).map(r=>r.trim()).filter(Boolean);for(let r of s){let n=e("ps",["-p",r,"-o","command="]).trim(),[i]=c(`${r} ${n}`);if(i)return i}}catch{}return null}function f(o){try{return l(o)}catch{return p(o)}}function d(o,t){return o!==null&&f(o.root)!==f(t)}async function P(o,t,e,s=a){let r={running:[],foreign:[]};for(let n of o){if(!await e(n))continue;let i=s(n);i&&d(i,t)?r.foreign.push({port:n,root:i.root}):r.running.push(n)}return r}export{a,d as b,P as c};
2
+ import{r as c}from"./chunk-T44DEEL6.js";import{execFileSync as u}from"node:child_process";import{realpathSync as l}from"node:fs";import{resolve as p}from"node:path";var m=(o,t)=>u(o,t,{encoding:"utf-8",stdio:"pipe",timeout:5e3,windowsHide:!0});function a(o,t=process.platform,e=m){if(t==="win32")return null;try{let s=e("lsof",["-nP",`-iTCP:${o}`,"-sTCP:LISTEN","-t"]).split(/\r?\n/).map(r=>r.trim()).filter(Boolean);for(let r of s){let n=e("ps",["-p",r,"-o","command="]).trim(),[i]=c(`${r} ${n}`);if(i)return i}}catch{}return null}function f(o){try{return l(o)}catch{return p(o)}}function d(o,t){return o!==null&&f(o.root)!==f(t)}async function P(o,t,e,s=a){let r={running:[],foreign:[]};for(let n of o){if(!await e(n))continue;let i=s(n);i&&d(i,t)?r.foreign.push({port:n,root:i.root}):r.running.push(n)}return r}export{a,d as b,P as c};
@@ -1,18 +1,18 @@
1
1
  import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
- import{N as Y,b as p,e as P,h as ke,i as be,m as we,n as Ae,r as J,u as Te,v as Re,x as R}from"./chunk-C4UFBXRD.js";import ct,{existsSync as C,readdirSync as cn,readFileSync as un,statSync as pe}from"node:fs";import{dirname as fn,join as b}from"node:path";var I=class{data;options;constructor(t){this.data=new Map,this.options=t}has(t){let n=this.data.get(t);return n?Date.now()-n.timestamp>this.options.ttlMs?(this.data.delete(t),!1):!0:!1}get(t){if(this.has(t))return this.data.get(t)?.value}set(t,n){this.data.set(t,{value:n,timestamp:Date.now()})}delete(t){this.data.delete(t)}clear(){this.data.clear()}keys(){let t=[];for(let n of this.data.keys())this.has(n)&&t.push(n);return t}size(){return this.keys().length}};function _e(e){let t=0;return{isValid(){return Date.now()-t<e},markUpdated(){t=Date.now()},reset(){t=0}}}function pt(e,t,n){return e===1?t:n||`${t}s`}function Ce(e,t,n){return`${e} ${pt(e,t,n)}`}var dt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"};function vn(e){return new Date(e).toLocaleString("en-US",dt)}function kn(e){return new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric"})}var Z={success:{color:"green",icon:"\u2713",chalkFn:p.green},error:{color:"red",icon:"\u2717",chalkFn:p.red},warning:{color:"yellow",icon:"\u25CB",chalkFn:p.yellow},unknown:{color:"gray",icon:"?",chalkFn:p.gray}};function Q(e){if(!e)return"unknown";let t=e.toLowerCase();return t==="ready"||t==="healthy"||t==="active"||t==="running"?"success":t==="error"||t==="failed"||t==="critical"||t==="stopped"?"error":t==="pending"||t==="starting"||t==="building"||t==="degraded"?"warning":"unknown"}function bn(e){return Z[Q(e)].color}function wn(e){return Z[Q(e)].icon}function An(e,t){return Z[Q(t)].chalkFn(e)}var lt={resources:{singular:"resource",command:"flisk resource <name>",context:`
2
+ import{N as Z,b as p,e as H,h as ke,i as be,m as we,n as Ae,r as J,u as Te,v as Re,x as R}from"./chunk-TIPSERWO.js";import ct,{existsSync as C,readdirSync as cn,readFileSync as un,statSync as pe}from"node:fs";import{dirname as fn,join as b}from"node:path";var P=class{data;options;constructor(t){this.data=new Map,this.options=t}has(t){let n=this.data.get(t);return n?Date.now()-n.timestamp>this.options.ttlMs?(this.data.delete(t),!1):!0:!1}get(t){if(this.has(t))return this.data.get(t)?.value}set(t,n){this.data.set(t,{value:n,timestamp:Date.now()})}delete(t){this.data.delete(t)}clear(){this.data.clear()}keys(){let t=[];for(let n of this.data.keys())this.has(n)&&t.push(n);return t}size(){return this.keys().length}};function _e(e){let t=0;return{isValid(){return Date.now()-t<e},markUpdated(){t=Date.now()},reset(){t=0}}}function pt(e,t,n){return e===1?t:n||`${t}s`}function Ce(e,t,n){return`${e} ${pt(e,t,n)}`}var dt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"};function vn(e){return new Date(e).toLocaleString("en-US",dt)}function kn(e){return new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric"})}var Y={success:{color:"green",icon:"\u2713",chalkFn:p.green},error:{color:"red",icon:"\u2717",chalkFn:p.red},warning:{color:"yellow",icon:"\u25CB",chalkFn:p.yellow},unknown:{color:"gray",icon:"?",chalkFn:p.gray}};function Q(e){if(!e)return"unknown";let t=e.toLowerCase();return t==="ready"||t==="healthy"||t==="active"||t==="running"?"success":t==="error"||t==="failed"||t==="critical"||t==="stopped"?"error":t==="pending"||t==="starting"||t==="building"||t==="degraded"?"warning":"unknown"}function bn(e){return Y[Q(e)].color}function wn(e){return Y[Q(e)].icon}function An(e,t){return Y[Q(t)].chalkFn(e)}var lt={resources:{singular:"resource",command:"flisk resource <name>",context:`
3
3
  To create a resource:`},stacks:{singular:"stack",command:"flisk stack <stack-name>",context:`
4
4
  To create a stack, use:`},services:{singular:"service",command:"flisk resource <name>",context:`
5
5
  To create a service:`},"stack-services":{singular:"service",command:"flisk resource <name> --stack <stack-name>",context:`
6
6
  To add services to this stack:`}};function Tn(e,t){let n=lt[e];console.log(t?p.yellow(`No ${n.singular}s found${t}.`):p.yellow(`No ${n.singular}s found.`)),console.log(p.gray(n.context)),console.log(p.gray(` ${n.command}`)),e==="stacks"&&(console.log(p.gray(`
7
7
  Or create a new resource with a stack:`)),console.log(p.gray(" flisk resource <name> --stack <stack-name>")))}function Rn(e){console.log(p.yellow(e||"Cancelled."))}function _n(e){console.log(p.blue(`Flisk ${e}
8
- `))}function On(e,t){console.log(p.green(`All ${e} are ${t}!`))}var X=62;function Cn(e="\u2500",t=X){return e.repeat(t)}function Oe(e,t=X-2){let n=Math.max(0,t-e.length),r=Math.floor(n/2),o=n-r;return" ".repeat(r)+e+" ".repeat(o)}function Dn(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e.padEnd(t)}function Nn(e,t){return e.length<=t?e:`${e.slice(0,t-3)}...`}function Fn(e){console.log(p.green(`\u2713 ${e}`))}function jn(e){console.log(p.blue(e))}function Pn(e,t=2){console.log(p.gray(`${" ".repeat(t)}${e}`))}function In(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function Mn(e,t,n=X){let r=n-2,o="\u2500".repeat(r);console.log(),console.log(p.cyan(`\u256D${o}\u256E`)),console.log(p.cyan("\u2502")+p.bold.white(Oe(e,r))+p.cyan("\u2502")),t&&(console.log(p.cyan(`\u251C${o}\u2524`)),console.log(p.cyan("\u2502")+p.gray(Oe(t,r))+p.cyan("\u2502"))),console.log(p.cyan(`\u2570${o}\u256F`))}var mt=["image","dockerfile"];function v(e){return typeof e?.command=="string"&&e.command.trim()!==""}function ee(e){return v(e.config)}function U(e){let t=ht(e),n=[];if("command"in e&&!v(e)&&n.push(`service ${t}: command must be a non-empty string`),!v(e))return n;let r=mt.filter(s=>e[s]!==void 0&&e[s]!=="");r.length>0&&n.push(`service ${t}: declares both command and ${r.join(" and ")}; a service has exactly one runtime source (remove ${r.length===1?"it":"them"} to run the command locally, or remove command to run in Docker)`);let o=e.env;if(o!==void 0){let s=o!==null&&typeof o=="object"&&!Array.isArray(o),i=s?Object.entries(o).filter(([a,u])=>!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)||typeof u!="string").map(([a])=>a):[];s?i.length>0&&n.push(`service ${t}: env ${i.join(", ")}: names must be letters, digits or _ (not starting with a digit) and values must be strings`):n.push(`service ${t}: env must be an object of string values`)}return n}function ht(e){let t=typeof e.appName=="string"?e.appName:e.name;return typeof t=="string"&&t?`"${t}"`:"(unnamed)"}function Un(e,t=e){let n=t.filter(ee).map(r=>r.name);return e.length>0&&e.every(ee)?{mode:"native",mixed:[]}:{mode:"container",mixed:n}}function Ln(e,t){return[`This run mixes services that run as local processes (${e.join(", ")}) with services that run in Docker (${t.join(", ")}).`,"Flisk cannot start both kinds in one project yet.","Give every service a `command` to run without Docker, or remove `command` from these services to run them in Docker."].join(`
9
- `)}function Vn(e){return e.length>0&&e.every(ee)}function Wn(e){let t=e.trim().split(/\s+/);for(let n of t)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(n)&&!(n==="exec"||n==="env"))return/^[A-Za-z0-9_./@+-]+$/.test(n)?n:null;return null}import{existsSync as rt,readFileSync as ot}from"node:fs";import{copyFileSync as Ge,mkdirSync as Vt,rmSync as qe,writeFileSync as Ke}from"node:fs";import{join as O}from"node:path";function A(e){return e==="backend"||e==="mcp"||e==="agent"}import{existsSync as Dt,readFileSync as Nt}from"node:fs";import{join as Ft}from"node:path";import{randomBytes as Ne}from"node:crypto";import{existsSync as ne,readFileSync as Fe,writeFileSync as je}from"node:fs";import{join as re}from"node:path";import{randomBytes as gt}from"node:crypto";import{closeSync as yt,fchmodSync as St,fsyncSync as $t,openSync as xt,renameSync as Et,rmSync as vt,statSync as kt,writeFileSync as bt}from"node:fs";import{basename as wt,dirname as At,join as Tt}from"node:path";function De(e,t){let n=Tt(At(e),`.${wt(e)}.${process.pid}.${gt(4).toString("hex")}.tmp`),r;try{r=kt(e).mode&511}catch{r=void 0}try{let o=xt(n,"wx");try{r!==void 0&&St(o,r),bt(o,t,"utf-8"),$t(o)}finally{yt(o)}Et(n,e)}catch(o){throw vt(n,{force:!0}),o}}var T=[{name:"VERDACCIO_URL_DOCKER",description:"Docker-internal URL of the private npm registry (Verdaccio, a paid feature)",required:!1,default:"http://verdaccio:4873",example:"http://verdaccio:4873"},{name:"VERDACCIO_URL",description:"Local URL of the private npm registry (Verdaccio, a paid feature)",required:!1,default:"http://localhost:4873"},{name:"TILT_ENV",description:"Tilt environment name",required:!0,default:"dev",example:"dev",complete:!0},{name:"DATABASE_URL",description:"PostgreSQL connection string (uses the generated DB_PASSWORD below)",required:!1},{name:"DB_PASSWORD",description:"PostgreSQL superuser password (docker-compose POSTGRES_PASSWORD)",required:!0,example:"postgres",complete:!0},{name:"JWT_SECRET",description:"Secret that signs local-jwt tokens (generated once per project; never shared between projects)",required:!1,complete:!0}],Pe=()=>Ne(16).toString("hex"),Rt=()=>Ne(32).toString("hex"),er=T.filter(e=>e.complete).map(e=>e.name);function te(e,t){return e==="DB_PASSWORD"?t:e==="JWT_SECRET"?Rt():e==="DATABASE_URL"?`postgresql://postgres:${t}@postgres:5432/app_dev`:T.find(n=>n.name===e)?.default??""}function _t(e){let t=e.trim(),n=t[0];if(n==='"'||n==="'"){let o=!1;for(let s=1;s<t.length;s+=1){let i=t[s];if(o){o=!1;continue}if(i==="\\"){o=!0;continue}if(i===n){let a=t.slice(1,s);if(n==='"'){let u={n:`
10
- `,r:"\r",t:" ",'"':'"',"\\":"\\",$:"$"};return a.replace(/\\([nrt"\\$])/g,(f,d)=>u[d]??d)}return a.replace(/\\'/g,"'")}}return t}let r=e.search(/\s#/);return(r===-1?e:e.slice(0,r)).trim()}function _(e){let t=new Map,n=e.startsWith("\uFEFF")?e.slice(1):e;for(let r of n.split(/\r?\n/)){let o=r.trim();if(!o||o.startsWith("#"))continue;let s=o.startsWith("export ")?o.slice(7).trim():o,i=s.indexOf("=");if(i<=0)continue;let a=s.slice(0,i).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)&&t.set(a,_t(s.slice(i+1)))}return t}function Ie(e){let t=[`# ${e.description}`];return e.required&&t.push("# Required (the value below is a working default)"),e.example&&t.push(`# Example: ${e.example}`),t}function Me(){let e=Pe(),t=["# \u{1F527} Flisk Environment Configuration","# Generated by `flisk project`. The defaults work as-is; edit only to change them.","# Keep this file out of git: it holds a generated database password and JWT secret.","#","# After editing, run: flisk config regenerate && flisk up",""];for(let n of T)t.push(...Ie(n)),t.push(`${n.name}=${te(n.name,e)}`),t.push("");return t.join(`
11
- `)}function Ot(e){let t=e.match(/^[a-z][a-z0-9+.-]*:\/\/[^:/@\s]+:([^@\s]*)@/i);if(!(!t||t[1].includes("$")))return t[1]}function tr(e){let t=re(e,".env"),n={missing:[],invalid:[],warnings:[]};if(!ne(t))return{missing:T.filter(i=>i.required).map(i=>i.name),invalid:[],warnings:[".env file not found - will be auto-generated"]};let r=_(Fe(t,"utf-8"));for(let i of T)i.required&&!r.has(i.name)&&n.missing.push(i.name),r.has(i.name)&&!(r.get(i.name)??"").trim()&&n.invalid.push(`${i.name} is set but empty`);let o=r.get("DATABASE_URL"),s=r.get("DB_PASSWORD");if(o&&s){let i=Ot(o);i!==void 0&&i!==s&&n.warnings.push("DATABASE_URL carries a different password than DB_PASSWORD; the database container uses DB_PASSWORD")}return n}function Ct(e,t,n){if(!_(e).has(t))return e;let o=e.indexOf("=");if(o<0)return e;let s=e.slice(o+1),i=s.search(/\s#/),a=i<0?"":s.slice(i);return e.slice(0,o+1)+n+a}function nr(e){let t=re(e,".env");if(!ne(t))return je(t,Me(),{mode:384}),T.map(c=>c.name);let n=Fe(t,"utf-8"),r=_(n),o=T.filter(c=>c.complete?r.has(c.name)?!(r.get(c.name)??"").trim():!0:!1);if(o.length===0)return[];let s=o.filter(c=>!r.has(c.name)),i=o.filter(c=>r.has(c.name)),a=r.get("DB_PASSWORD"),u=a?.trim()?a:Pe(),f=n.split(/(\r\n|\n|\r)/);for(let c of i){let h=-1;for(let l=0;l<f.length;l+=2)_(f[l]).has(c.name)&&(h=l);if(h<0)throw new Error(`Could not locate empty .env assignment for ${c.name}`);f[h]=Ct(f[h],c.name,te(c.name,u))}let d=f.join("");if(s.length>0){let c=["","# Added by Flisk: keys this version expects. Existing values above were not changed.",""];for(let l of s)c.push(...Ie(l)),c.push(`${l.name}=${te(l.name,u)}`),c.push("");let h=d.endsWith(`
8
+ `))}function On(e,t){console.log(p.green(`All ${e} are ${t}!`))}var X=62;function Cn(e="\u2500",t=X){return e.repeat(t)}function Oe(e,t=X-2){let n=Math.max(0,t-e.length),r=Math.floor(n/2),o=n-r;return" ".repeat(r)+e+" ".repeat(o)}function Dn(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e.padEnd(t)}function Fn(e,t){return e.length<=t?e:`${e.slice(0,t-3)}...`}function Nn(e){console.log(p.green(`\u2713 ${e}`))}function jn(e){console.log(p.blue(e))}function Pn(e,t=2){console.log(p.gray(`${" ".repeat(t)}${e}`))}function In(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function Mn(e,t,n=X){let r=n-2,o="\u2500".repeat(r);console.log(),console.log(p.cyan(`\u256D${o}\u256E`)),console.log(p.cyan("\u2502")+p.bold.white(Oe(e,r))+p.cyan("\u2502")),t&&(console.log(p.cyan(`\u251C${o}\u2524`)),console.log(p.cyan("\u2502")+p.gray(Oe(t,r))+p.cyan("\u2502"))),console.log(p.cyan(`\u2570${o}\u256F`))}var mt=["image","dockerfile"];function v(e){return typeof e?.command=="string"&&e.command.trim()!==""}function ee(e){return v(e.config)}function B(e){let t=ht(e),n=[];if("command"in e&&!v(e)&&n.push(`service ${t}: command must be a non-empty string`),!v(e))return n;let r=mt.filter(s=>e[s]!==void 0&&e[s]!=="");r.length>0&&n.push(`service ${t}: declares both command and ${r.join(" and ")}; a service has exactly one runtime source (remove ${r.length===1?"it":"them"} to run the command locally, or remove command to run in Docker)`);let o=e.env;if(o!==void 0){let s=o!==null&&typeof o=="object"&&!Array.isArray(o),i=s?Object.entries(o).filter(([a,c])=>!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)||typeof c!="string").map(([a])=>a):[];s?i.length>0&&n.push(`service ${t}: env ${i.join(", ")}: names must be letters, digits or _ (not starting with a digit) and values must be strings`):n.push(`service ${t}: env must be an object of string values`)}return n}function ht(e){let t=e.appName;return typeof t=="string"&&t?`"${t}"`:"(unnamed)"}function Un(e,t=e){let n=t.filter(ee).map(r=>r.name);return e.length>0&&e.every(ee)?{mode:"native",mixed:[]}:{mode:"container",mixed:n}}function Ln(e,t){return[`This run mixes services that run as local processes (${e.join(", ")}) with services that run in Docker (${t.join(", ")}).`,"Flisk cannot start both kinds in one project yet.","Give every service a `command` to run without Docker, or remove `command` from these services to run them in Docker."].join(`
9
+ `)}function Vn(e){return e.length>0&&e.every(ee)}function Wn(e){let t=e.trim().split(/\s+/);for(let n of t)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(n)&&!(n==="exec"||n==="env"))return/^[A-Za-z0-9_./@+-]+$/.test(n)?n:null;return null}import{existsSync as rt,readFileSync as ot}from"node:fs";import{copyFileSync as Ge,mkdirSync as Vt,rmSync as qe,writeFileSync as Ke}from"node:fs";import{join as O}from"node:path";function A(e){return e==="backend"||e==="mcp"||e==="agent"}import{existsSync as Dt,readFileSync as Ft}from"node:fs";import{join as Nt}from"node:path";import{randomBytes as Fe}from"node:crypto";import{existsSync as ne,readFileSync as Ne,writeFileSync as je}from"node:fs";import{join as re}from"node:path";import{randomBytes as gt}from"node:crypto";import{closeSync as yt,fchmodSync as St,fsyncSync as $t,openSync as xt,renameSync as Et,rmSync as vt,statSync as kt,writeFileSync as bt}from"node:fs";import{basename as wt,dirname as At,join as Tt}from"node:path";function De(e,t){let n=Tt(At(e),`.${wt(e)}.${process.pid}.${gt(4).toString("hex")}.tmp`),r;try{r=kt(e).mode&511}catch{r=void 0}try{let o=xt(n,"wx");try{r!==void 0&&St(o,r),bt(o,t,"utf-8"),$t(o)}finally{yt(o)}Et(n,e)}catch(o){throw vt(n,{force:!0}),o}}var T=[{name:"VERDACCIO_URL_DOCKER",description:"Docker-internal URL of the private npm registry (Verdaccio, a paid feature)",required:!1,default:"http://verdaccio:4873",example:"http://verdaccio:4873"},{name:"VERDACCIO_URL",description:"Local URL of the private npm registry (Verdaccio, a paid feature)",required:!1,default:"http://localhost:4873"},{name:"TILT_ENV",description:"Tilt environment name",required:!0,default:"dev",example:"dev",complete:!0},{name:"DATABASE_URL",description:"PostgreSQL connection string (uses the generated DB_PASSWORD below)",required:!1},{name:"DB_PASSWORD",description:"PostgreSQL superuser password (docker-compose POSTGRES_PASSWORD)",required:!0,example:"postgres",complete:!0},{name:"JWT_SECRET",description:"Secret that signs local-jwt tokens (generated once per project; never shared between projects)",required:!1,complete:!0}],Pe=()=>Fe(16).toString("hex"),Rt=()=>Fe(32).toString("hex"),er=T.filter(e=>e.complete).map(e=>e.name);function te(e,t){return e==="DB_PASSWORD"?t:e==="JWT_SECRET"?Rt():e==="DATABASE_URL"?`postgresql://postgres:${t}@postgres:5432/app_dev`:T.find(n=>n.name===e)?.default??""}function _t(e){let t=e.trim(),n=t[0];if(n==='"'||n==="'"){let o=!1;for(let s=1;s<t.length;s+=1){let i=t[s];if(o){o=!1;continue}if(i==="\\"){o=!0;continue}if(i===n){let a=t.slice(1,s);if(n==='"'){let c={n:`
10
+ `,r:"\r",t:" ",'"':'"',"\\":"\\",$:"$"};return a.replace(/\\([nrt"\\$])/g,(f,d)=>c[d]??d)}return a.replace(/\\'/g,"'")}}return t}let r=e.search(/\s#/);return(r===-1?e:e.slice(0,r)).trim()}function _(e){let t=new Map,n=e.startsWith("\uFEFF")?e.slice(1):e;for(let r of n.split(/\r?\n/)){let o=r.trim();if(!o||o.startsWith("#"))continue;let s=o.startsWith("export ")?o.slice(7).trim():o,i=s.indexOf("=");if(i<=0)continue;let a=s.slice(0,i).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)&&t.set(a,_t(s.slice(i+1)))}return t}function Ie(e){let t=[`# ${e.description}`];return e.required&&t.push("# Required (the value below is a working default)"),e.example&&t.push(`# Example: ${e.example}`),t}function Me(){let e=Pe(),t=["# \u{1F527} Flisk Environment Configuration","# Generated by `flisk project`. The defaults work as-is; edit only to change them.","# Keep this file out of git: it holds a generated database password and JWT secret.","#","# After editing, run: flisk config regenerate && flisk up",""];for(let n of T)t.push(...Ie(n)),t.push(`${n.name}=${te(n.name,e)}`),t.push("");return t.join(`
11
+ `)}function Ot(e){let t=e.match(/^[a-z][a-z0-9+.-]*:\/\/[^:/@\s]+:([^@\s]*)@/i);if(!(!t||t[1].includes("$")))return t[1]}function tr(e){let t=re(e,".env"),n={missing:[],invalid:[],warnings:[]};if(!ne(t))return{missing:T.filter(i=>i.required).map(i=>i.name),invalid:[],warnings:[".env file not found - will be auto-generated"]};let r=_(Ne(t,"utf-8"));for(let i of T)i.required&&!r.has(i.name)&&n.missing.push(i.name),r.has(i.name)&&!(r.get(i.name)??"").trim()&&n.invalid.push(`${i.name} is set but empty`);let o=r.get("DATABASE_URL"),s=r.get("DB_PASSWORD");if(o&&s){let i=Ot(o);i!==void 0&&i!==s&&n.warnings.push("DATABASE_URL carries a different password than DB_PASSWORD; the database container uses DB_PASSWORD")}return n}function Ct(e,t,n){if(!_(e).has(t))return e;let o=e.indexOf("=");if(o<0)return e;let s=e.slice(o+1),i=s.search(/\s#/),a=i<0?"":s.slice(i);return e.slice(0,o+1)+n+a}function nr(e){let t=re(e,".env");if(!ne(t))return je(t,Me(),{mode:384}),T.map(u=>u.name);let n=Ne(t,"utf-8"),r=_(n),o=T.filter(u=>u.complete?r.has(u.name)?!(r.get(u.name)??"").trim():!0:!1);if(o.length===0)return[];let s=o.filter(u=>!r.has(u.name)),i=o.filter(u=>r.has(u.name)),a=r.get("DB_PASSWORD"),c=a?.trim()?a:Pe(),f=n.split(/(\r\n|\n|\r)/);for(let u of i){let h=-1;for(let l=0;l<f.length;l+=2)_(f[l]).has(u.name)&&(h=l);if(h<0)throw new Error(`Could not locate empty .env assignment for ${u.name}`);f[h]=Ct(f[h],u.name,te(u.name,c))}let d=f.join("");if(s.length>0){let u=["","# Added by Flisk: keys this version expects. Existing values above were not changed.",""];for(let l of s)u.push(...Ie(l)),u.push(`${l.name}=${te(l.name,c)}`),u.push("");let h=d.endsWith(`
12
12
  `)||d.endsWith("\r")?"":`
13
- `;d+=h+c.join(`
14
- `)}return De(t,d),o.map(c=>c.name)}function rr(e){let t=re(e,".env");return ne(t)?!1:(je(t,Me(),{mode:384}),!0)}var jt="[REDACTED]",oe=class extends Error{path;constructor(t,n){super(`Could not read ${t}: ${n}`),this.path=t,this.name="EnvUnreadableError"}},Pt=8;function It(e,t=[]){let n=[...Be(Ft(e,".env")),...t.flatMap(r=>Be(r))];return[...new Set(n)].sort((r,o)=>o.length-r.length)}function Be(e){if(!Dt(e))return[];let t;try{t=Nt(e,"utf-8")}catch(n){throw new oe(e,n instanceof Error?n.message:String(n))}return[..._(t).values()].filter(n=>n.length>=Pt)}function Le(e,t){let n=e;for(let r of t)n=n.split(r).join(jt);return n}function Ue(e,t){return t.length===0?e:typeof e=="string"?Le(e,t):Array.isArray(e)?e.map(n=>Ue(n,t)):e!==null&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,Ue(r,t)])):e}function Ve(e,t=[]){let n;try{n=It(e,t)}catch(r){return()=>{throw r}}return r=>Le(r,n)}import{readFileSync as Mt}from"node:fs";import{join as Bt}from"node:path";function mr(){return We(R())}function We(e){if(e)try{let t=Mt(Bt(e,".flisk",we),"utf-8"),n=JSON.parse(t);if(n?.project?.name)return n.project.name}catch{}return"flisk-project"}function L(e,t){let n=We(t??R()),r=process.env.FLISK_SERVICE_BASE_URL??`http://${n}.localhost${e?`:${e}`:""}`;try{let o=new URL(r.includes("://")?r:`http://${r}`),s=o.hostname,i=o.protocol==="https:"?"443":"80",a=o.port||(e?String(e):""),u=a===i?"":a;if(s==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(s))return{appBase:`${o.protocol}//${s}${u?`:${u}`:""}`,apiBase:`${o.protocol}//${s}${u?`:${u}`:""}`};let f=s.replace(/^(app|api)\./,"");return{appBase:`${o.protocol}//app.${f}${u?`:${u}`:""}`,apiBase:`${o.protocol}//api.${f}${u?`:${u}`:""}`}}catch{return{appBase:`http://app.${n}.localhost`,apiBase:`http://api.${n}.localhost`}}}function Ut(e){return`${e.replace(/\/+$/,"")}/health`}function V(e){if(A(e.config?.appType)){let t=e.name.replace(/-api$/,"");return e.config?.apiPath??`/api/${t}`}return e.config?.basePath??`/${e.name}`}function hr(e,t){let n=e.config?.appType;if(n!=="frontend"&&!A(n))return;let{appBase:r,apiBase:o}=L(t);return`${n==="frontend"?r:o}${V(e)}`}function gr(e,t){return ze(e,t,n=>Ut(n))}function yr(e,t){return ze(e,t,n=>n)}function ze(e,t,n){let{appBase:r,apiBase:o}=L(t);return e.filter(s=>s.config?.appType==="frontend"||A(s.config?.appType)).map(s=>{let i=A(s.config.appType)?o:r;return{name:s.name,appType:s.config.appType,url:`${i}${n(V(s))}`}})}async function Lt(e,t){try{let n=await fetch(e.url,{method:"GET",redirect:"manual",signal:AbortSignal.timeout(t)});return{...e,ok:n.ok,status:n.status}}catch(n){let r=n instanceof Error&&n.name==="TimeoutError"?`no response within ${t}ms`:n instanceof Error?n.message:String(n);return{...e,ok:!1,error:r}}}async function Sr(e,t){return Promise.all(e.map(n=>Lt(n,t)))}var Xe=60,He=1e3,Je=1e4,Ye=200,Ze=64*1024,Qe=new Set([404,502,503,504]),Wt=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]),zt=new Set(["GET","HEAD","OPTIONS"]);function k(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gt(e,t){if(zt.has(t))return!0;if(!k(e))return!1;let n=k(e.cause)?e.cause:void 0,r=e.code??n?.code;return typeof r=="string"&&Wt.has(r)}function ie(e){if(!k(e))return['smoke: must be an object with "via" and "steps"'];let t=[];return e.via!=="proxy"&&t.push('smoke.via: only "proxy" is supported (the check must go through the public URL)'),e.timeoutSeconds!==void 0&&!(typeof e.timeoutSeconds=="number"&&e.timeoutSeconds>0)&&t.push("smoke.timeoutSeconds: must be a number greater than 0"),!Array.isArray(e.steps)||e.steps.length===0?(t.push("smoke.steps: needs at least one step"),t):(e.steps.forEach((n,r)=>{let o=`smoke.steps[${r}]`;if(!k(n)){t.push(`${o}: must be an object`);return}let s=n.path;(typeof s!="string"||!s.startsWith("/")||s.startsWith("//"))&&t.push(`${o}.path: must start with "/" and is appended to the public URL; a full URL or relative path would skip the proxy`),n.expect!==void 0&&!(typeof n.expect=="number"&&Number.isInteger(n.expect)&&n.expect>=100&&n.expect<=599)&&t.push(`${o}.expect: must be an HTTP status between 100 and 599`),n.bodyContains!==void 0&&typeof n.bodyContains!="string"&&t.push(`${o}.bodyContains: must be a string`),n.save!==void 0&&(k(n.save)&&Object.values(n.save).every(a=>typeof a=="string"&&a.startsWith("$."))||t.push(`${o}.save: must map names to paths such as "$.id"`))}),t)}function Ar(e,t){let{appBase:n,apiBase:r}=L(t),o=[];for(let s of e){let i=s.config;if(!i?.smoke)continue;let a=i.appType,u=A(a)||a===P;if(!u&&a!=="frontend"||i.exposeViaProxy===!1)continue;let f=u?r:n,d=i.healthCheckPath||(u&&a!==P?"/health":void 0);o.push({name:s.name,baseUrl:`${f}${V(s).replace(/\/+$/,"")}`,...d?.startsWith("/")?{readyPath:d}:{},smoke:i.smoke})}return o}var qt=10;function Tr(e,t={}){let n=t.timeoutSeconds??qt;return e.flatMap(r=>{let o=r.smoke.steps.filter(i=>(i.method??"GET").toUpperCase()==="GET"&&i.body===void 0&&!i.path.includes("{{")).map(i=>({...i,bodyContains:void 0}));if(o.length===0)return[];let s=Math.min(r.smoke.timeoutSeconds??Xe,n);return[{name:r.name,baseUrl:r.baseUrl,smoke:{via:r.smoke.via,timeoutSeconds:s,steps:o}}]})}function Kt(e,t){let n=t.replace(/^\$\.?/,"").match(/[^.[\]]+/g)??[],r=e;for(let o of n){if(r==null||o==="__proto__"||o==="constructor"||o==="prototype"||!Object.hasOwn(r,o))return;let s=Object.getOwnPropertyDescriptor(r,o);if(!s||!("value"in s))return;r=s.value}return r}function et(e,t){return e.replace(/\{\{(\w+)\}\}/g,(n,r)=>t[r]??n)}function se(e,t){return typeof e=="string"?et(e,t):Array.isArray(e)?e.map(n=>se(n,t)):k(e)?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,se(r,t)])):e}function W(e){let t=e.replace(/\s+/g," ").trim();return t.length>Ye?`${t.slice(0,Ye)}...`:t}function tt(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"step"}function Ht(e,t){let n=tt(t),r=(e.get(n)??0)+1;return e.set(n,r),r===1?n:`${n}-${r}`}function Jt(e,t,n,r,o){try{let s=O(e,tt(n.service),t);Vt(s,{recursive:!0});let i=O(s,"latest.json"),a=O(s,"body.txt");return r===void 0?qe(a,{force:!0}):Ke(a,r),Ke(i,`${JSON.stringify({...n,bodyPath:r===void 0?null:"body.txt"},null,2)}
15
- `),o&&(Ge(i,O(s,"last-success.json")),r===void 0?qe(O(s,"last-success-body.txt"),{force:!0}):Ge(a,O(s,"last-success-body.txt"))),i}catch{return}}function Yt(e){let t=Buffer.from(e,"utf8");if(t.length<=Ze)return{body:e,truncated:!1,bytes:t.length};let n=t.subarray(0,Ze).toString("utf8").replace(/\uFFFD$/,"");return{body:n,truncated:!0,bytes:Buffer.byteLength(n,"utf8")}}async function Zt(e,t={}){let n=t.fetch??((d,c)=>fetch(d,c)),r=t.now??Date.now,o=t.sleep??(d=>new Promise(c=>setTimeout(c,d))),s=(e.smoke.timeoutSeconds??Xe)*1e3,i=r()+s,a={},u=ie(e.smoke);if(u.length>0)return{name:e.name,ok:!1,failure:`${e.name}: invalid smoke block: ${u.join("; ")}`};if(e.readyPath){let d=`${e.baseUrl}${e.readyPath}`;for(;;){try{let c=await n(d,{method:"GET",signal:AbortSignal.timeout(Je)});if(await c.text(),!Qe.has(c.status)&&c.status<500)break}catch{}if(r()>=i)break;await o(He)}}i=r()+s;let f=new Map;for(let[d,c]of e.smoke.steps.entries()){let h=c.name??`step ${d+1}`,l=(c.method??"GET").toUpperCase(),ye=`${e.baseUrl}${et(c.path,a)}`,D=c.expect??200,Se=c.body!==void 0,y,N="",E,q;for(;;){E=void 0,q=void 0,y=void 0;let g=!1;try{let m=await n(ye,{method:l,headers:Se?{"content-type":"application/json"}:void 0,body:Se?JSON.stringify(se(c.body,a)):void 0,signal:AbortSignal.timeout(Je)});y=m.status,N=await m.text()}catch(m){g=Gt(m,l),E=m instanceof Error?m.message:String(m);let w=k(m)?m.code??(k(m.cause)?m.cause.code:void 0):void 0;q=typeof w=="string"?w:void 0}if(!(E!==void 0&&g||y!==void 0&&y!==D&&Qe.has(y))||r()>=i)break;await o(He)}let $e=!1,K=g=>{if(!$e){if(!t.redact)return g;try{return t.redact(g)}catch($){$e=!0;let m=$ instanceof Error?$.message:String($);t.warn?.(`${e.name}: ${h}: the response text was neither shown nor stored: ${m}`);return}}},H=K(ye),S=y===void 0?void 0:K(N),xe=E===void 0?void 0:K(E),Ee=E===void 0?void 0:q??xe,F=`${e.name}: ${h}: ${l} ${H??"(url withheld)"}`,ve;if(t.recordDir){let g=S===void 0?void 0:Yt(S),$=E===void 0&&y===D&&(c.bodyContains===void 0||N.includes(c.bodyContains));ve=Jt(t.recordDir,Ht(f,h),{service:e.name,step:h,method:l,...H===void 0?{}:{url:H},expectedStatus:D,status:y??null,...Ee===void 0?{}:{error:Ee},truncated:g?.truncated??!1,bodyBytes:g?.bytes??0},g?.body,$)}let j=g=>({name:e.name,ok:!1,failure:g,recordPath:ve});if(E!==void 0)return j(`${F} never answered (${xe??"the error was withheld"})`);if(y!==D)return j(`${F} returned ${y}, expected ${D}${S?`: ${W(S)}`:""}`);if(c.bodyContains!==void 0&&!N.includes(c.bodyContains))return j(`${F} returned ${y} but the body does not contain "${c.bodyContains}"${S?`: ${W(S)}`:""}`);if(c.save){let g;try{g=JSON.parse(N)}catch{return j(`${F} returned a body that is not JSON, cannot save${S?`: ${W(S)}`:""}`)}for(let[$,m]of Object.entries(c.save)){let w=Kt(g,m);if(w==null||typeof w=="object")return j(`${F} response has no ${m} to save as {{${$}}}${S?`: ${W(S)}`:""}`);a[$]=String(w)}}}return{name:e.name,ok:!0}}function Rr(e){return`Smoke check failed: ${e.failure}${e.recordPath?` (record: ${e.recordPath})`:""}`}function _r(e,t,n={}){return{redact:Ve(e,n.envFiles??[]),...t?{warn:t}:{}}}async function Or(e,t={}){return Promise.all(e.map(n=>Zt(n,t)))}var nt=1,jr="https://flisk.sh/schema.service.json",Qt={dependencies:"deprecated, use dependsOn (still read for now)",envVars:"deprecated, use params (still read when params is absent)",jwtSecret:"ignored. A secret must not be committed: remove it and set JWT_SECRET in the project .env"},Xt=new Set(["$schema","agent","apiBasePath","apiPath","appName","appType","backendName","basePath","build","buildContext","command","databaseName","dependencies","dev","dependsOn","dockerfile","enabled","env","exposeViaProxy","featuresEnabled","framework","healthCheckPath","image","language","name","nats","params","playwright","port","replicas","restart","runtime","sablier","schemaVersion","secrets","smoke","stack","syncs","traefik","type"]),en=[{path:["healthCheckPath"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /health"},{path:["traefik","host"],pattern:/^[A-Za-z0-9.-]+$/,expected:"a hostname such as shop.backend.myproject.local"},{path:["traefik","pathPrefix"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /api/v1/shop-management"},{path:["traefik","healthCheck"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /health"},{path:["nats","queueGroup"],pattern:/^[A-Za-z0-9_.-]+$/,expected:"letters, digits, '_', '.' or '-'"},{path:["databaseName"],pattern:/^[A-Za-z0-9_-]+$/,expected:"letters, digits, '_' or '-'"},{path:["stack"],pattern:/^[A-Za-z0-9_-]+$/,expected:"letters, digits, '_' or '-'"},{path:["image"],pattern:/^[A-Za-z0-9._/:@-]+$/,expected:"an image reference such as ghcr.io/acme/app:1.0"},{path:["basePath"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /storefront"}];function z(e,t){let n=[],r=new Set;for(let{path:o,pattern:s,expected:i}of en){let a=e;for(let f of o)a=a&&typeof a=="object"&&!Array.isArray(a)?a[f]:void 0;if(a==null||a==="")continue;let u=`${t}.${o.join(".")}`;(typeof a!="string"||!s.test(a))&&r.add(u),typeof a!="string"?n.push(`${u}: expected a string`):s.test(a)||n.push(`${u}: must be ${i} (no spaces, line breaks or YAML syntax)`)}for(let[o,s]of tn(e,t))r.has(o)||n.push(`${o}: ${s}`);return n}function ae(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127||n===133||n===8232||n===8233)return!0}return!1}function tn(e,t){let n=[],r=Object.entries(e).filter(([o])=>o!=="smoke").map(([o,s])=>[s,`${t}.${o}`]);for(let o of Object.keys(e))ae(o)&&n.push([t,"a key contains a line break"]);for(;r.length>0;){let[o,s]=r.pop();if(typeof o=="string")ae(o)&&n.push([s,"must not contain line breaks or control characters"]);else if(Array.isArray(o))for(let[i,a]of o.entries())r.push([a,`${s}[${i}]`]);else if(o&&typeof o=="object")for(let[i,a]of Object.entries(o))ae(i)?n.push([s,"a key contains a line break"]):r.push([a,`${s}.${i}`])}return n.sort(([o],[s])=>o.localeCompare(s))}var nn=["apiPath","basePath"];function ce(e){if(!e||typeof e!="object"||Array.isArray(e))return[];let t=e;return nn.filter(n=>n in t&&typeof t[n]!="string")}function ue(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{errors:[`${t}: expected a JSON object`],warnings:[]};let n=e,r=[];if(v(n))!("appName"in n)&&typeof n.name!="string"&&r.push(`${t}.name: required field is missing`),"schemaVersion"in n||r.push(`${t}.schemaVersion: required field is missing`);else for(let a of["appName","appType","stack","schemaVersion"])a in n||r.push(`${t}.${a}: required field is missing`);for(let a of["appName","appType","stack"])a in n&&typeof n[a]!="string"&&r.push(`${t}.${a}: expected a string`);"schemaVersion"in n&&n.schemaVersion!==nt&&r.push(`${t}.schemaVersion: unsupported version ${String(n.schemaVersion)} (supported: ${nt})`);for(let a of ce(n))r.push(`${t}.${a}: expected a string`);let o=n.dev,s=o&&typeof o=="object"&&!Array.isArray(o)?o.liveReload:void 0;if(s!==void 0&&typeof s!="boolean"&&r.push(`${t}.dev.liveReload: expected true or false`),r.push(...z(n,t)),r.push(...U(n).map(a=>`${t}: ${a}`)),"smoke"in n)for(let a of ie(n.smoke))r.push(`${t}.${a}`);let i=Object.keys(n).flatMap(a=>{let u=Qt[a];return u?[`${t}.${a}: ${u}`]:Xt.has(a)?[]:[`${t}.${a}: unknown field is preserved`]});return s===!0&&n.language!=="go"&&i.push(`${t}.dev.liveReload: only applies to Go services (language "go"); it is ignored here`),{errors:r,warnings:i,manifest:n}}function Pr(e,t){if(!rt(e))return[];try{let n=JSON.parse(ot(e,"utf-8"));return!n||typeof n!="object"||Array.isArray(n)?[]:z(n,t)}catch{return[]}}function Ir(e,t){if(!rt(e))return{errors:[`${t}: file is missing`],warnings:[]};try{let n=JSON.parse(ot(e,"utf-8"));return ue(n,t)}catch(n){let r=n instanceof Error?n.message:String(n);return{errors:[`${t}: invalid JSON (${r})`],warnings:[]}}}function st(e){if(e.length===0||e.every(n=>n==="unknown"))return"unknown";let t=e.filter(n=>n==="ready").length/e.length;return t>.9?"healthy":t>.5?"degraded":"error"}var rn=/^[a-z0-9-]+$/;function it(e){return rn.test(e)}var on="Use lowercase letters, numbers, and hyphens only";function at(e,t){return`${e} "${t}" is not valid. ${on}, e.g. "${e==="Resource name"?"my-service":"my-stack"}".`}function sn(e){return e.trim()?it(e)?{valid:!0}:{valid:!1,error:at("Resource name",e)}:{valid:!1,error:"Resource name is required"}}function an(e){return e.trim()?it(e)?{valid:!0}:{valid:!1,error:at("Stack name",e)}:{valid:!1,error:"Stack name is required"}}function Lr(e){let t=e==="resource"?sn:an;return n=>{let r=t(n);return r.valid?!0:r.error??"Invalid name"}}function Vr(e){return fe(J,e)?{valid:!0}:{valid:!1,error:`Invalid service. Must be one of: ${J.join(", ")}`}}function Wr(e){return Number.isInteger(e)&&e>0&&e<=65535}function zr(e){return!e.includes("\0")&&!/[<>:"|?*]/.test(e)}function Gr(e,t="_"){return e.replace(/[^a-zA-Z0-9-]/g,t).substring(0,100)}function fe(e,t){return e.includes(t)}var pn=ke;function he(e,t=5,n=0){let r=[];if(n>t)return r;let o=ct.readdirSync(e,{withFileTypes:!0});for(let s of o){let i=b(e,s.name);if(s.isDirectory()){if(dn(s.name))continue;r.push(...he(i,t,n+1))}else s.isFile()&&s.name===pn&&r.push(i)}return r}function oo(e){return he(e)}function dn(e){return fe(Te,e)||e.startsWith(".")}function ln(e){if(!e||typeof e!="object")return!1;let t=e;if(v(t))return typeof t.appName=="string"||typeof t.name=="string";let n=typeof t.runtime=="string"||typeof t.appType=="string"||typeof t.type=="string";return typeof t.appName=="string"&&n}var M=new Set;function so(){M.clear()}var x=class extends Error{reason;constructor(t){super(t),this.reason=t,this.name="InvalidServiceJsonError"}};function de(e){return e instanceof Error?e.message:String(e)}function mn(e){let t;try{t=ct.readFileSync(e,"utf-8")}catch(f){throw new x(`could not read file: ${de(f)}`)}let n;try{n=JSON.parse(t)}catch(f){throw new x(`invalid JSON: ${de(f)}`)}if(!ln(n))throw new x("missing required fields (appName, runtime or appType)");let r=U(n);if(r.length>0)throw new x(r.join("; "));let o=ce(n);if(o.length>0)throw new x(o.map(f=>`${f}: expected a string`).join("; "));let s=z(n,e);if(s.length>0)throw new x(s.join("; "));let i=n;for(let f of ue(n,e).warnings)M.has(f)||(M.add(f),console.warn(`\u26A0\uFE0F ${f}`));let a={...i,appName:i.appName??String(i.name),appType:i.appType||i.type||(v(i)?P:void 0)},u=fn(e);return{name:a.appName,path:u,configPath:e,config:a,stack:a.stack,port:a.port,type:a.appType}}function ut(e){let t=he(e),n=[],r=[];for(let o of t)try{n.push(mn(o))}catch(s){r.push({path:o,reason:s instanceof x?s.reason:de(s)})}return{resources:n,problems:r}}function hn(e){for(let t of e){let n=`skipped ${t.path}: ${t.reason}`;M.has(n)||(M.add(n),console.warn(`\u26A0\uFE0F ${n}`))}}function ft(e){let{resources:t,problems:n}=ut(e);return hn(n),t}function G(){let e=R();if(!e)throw Y.notInProject();return ft(e)}function io(e){let t=e;if(!t){let n=R();if(!n)throw Y.notInProject();t=ut(n)}if(t.problems.length>0)throw new Error(t.problems.map(n=>`Invalid service.json at ${n.path}: ${n.reason}`).join(`
16
- `));return t.resources}function ao(e,t){let n=t??ft(e);return gn(n)}function gn(e){let t=e||G(),n=new Set;for(let r of t)r.stack&&n.add(r.stack);return Array.from(n).sort()}function co(e=G()){let t=new Map;for(let r of e)if(r.stack){let o=t.get(r.stack);o?o.push(r):t.set(r.stack,[r])}let n=[];for(let[r,o]of t)n.push({name:r,description:Ce(o.length,"resource"),resources:o,resourceCount:o.length});return n.sort((r,o)=>r.name.localeCompare(o.name))}function uo(e,t){return(t??G()).filter(r=>r.stack===e)}function fo(e,t){return(t??G()).some(r=>r.stack===e)}var ge=5e3,le=new I({ttlMs:ge}),me=new I({ttlMs:ge}),B=_e(ge);function po(){le.clear(),me.clear(),B.reset()}function yn(e,t){if(e==="Dockerfile"||e.startsWith("docker-compose"))return"docker";if(e==="Tiltfile"||e.endsWith(".star"))return"tilt";if(e==="vite.config.ts"||e==="tsconfig.json"||e===Ae)return"config";if(e==="schema.prisma"||e.includes("migration"))return"prisma";if(t){let n=t.split(`
13
+ `;d+=h+u.join(`
14
+ `)}return De(t,d),o.map(u=>u.name)}function rr(e){let t=re(e,".env");return ne(t)?!1:(je(t,Me(),{mode:384}),!0)}var jt="[REDACTED]",oe=class extends Error{path;constructor(t,n){super(`Could not read ${t}: ${n}`),this.path=t,this.name="EnvUnreadableError"}},Pt=8;function It(e,t=[]){let n=[...Be(Nt(e,".env")),...t.flatMap(r=>Be(r))];return[...new Set(n)].sort((r,o)=>o.length-r.length)}function Be(e){if(!Dt(e))return[];let t;try{t=Ft(e,"utf-8")}catch(n){throw new oe(e,n instanceof Error?n.message:String(n))}return[..._(t).values()].filter(n=>n.length>=Pt)}function Le(e,t){let n=e;for(let r of t)n=n.split(r).join(jt);return n}function Ue(e,t){return t.length===0?e:typeof e=="string"?Le(e,t):Array.isArray(e)?e.map(n=>Ue(n,t)):e!==null&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,Ue(r,t)])):e}function Ve(e,t=[]){let n;try{n=It(e,t)}catch(r){return()=>{throw r}}return r=>Le(r,n)}import{readFileSync as Mt}from"node:fs";import{join as Bt}from"node:path";function mr(){return We(R())}function We(e){if(e)try{let t=Mt(Bt(e,".flisk",we),"utf-8"),n=JSON.parse(t);if(n?.project?.name)return n.project.name}catch{}return"flisk-project"}function U(e,t){let n=We(t??R()),r=process.env.FLISK_SERVICE_BASE_URL??`http://${n}.localhost${e?`:${e}`:""}`;try{let o=new URL(r.includes("://")?r:`http://${r}`),s=o.hostname,i=o.protocol==="https:"?"443":"80",a=o.port||(e?String(e):""),c=a===i?"":a;if(s==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(s))return{appBase:`${o.protocol}//${s}${c?`:${c}`:""}`,apiBase:`${o.protocol}//${s}${c?`:${c}`:""}`};let f=s.replace(/^(app|api)\./,"");return{appBase:`${o.protocol}//app.${f}${c?`:${c}`:""}`,apiBase:`${o.protocol}//api.${f}${c?`:${c}`:""}`}}catch{return{appBase:`http://app.${n}.localhost`,apiBase:`http://api.${n}.localhost`}}}function Ut(e){return`${e.replace(/\/+$/,"")}/health`}function L(e){if(A(e.config?.appType)){let t=e.name.replace(/-api$/,"");return e.config?.apiPath??`/api/${t}`}return e.config?.basePath??`/${e.name}`}function hr(e,t){let n=e.config?.appType;if(n!=="frontend"&&!A(n))return;let{appBase:r,apiBase:o}=U(t);return`${n==="frontend"?r:o}${L(e)}`}function gr(e,t){return ze(e,t,n=>Ut(n))}function yr(e,t){return ze(e,t,n=>n)}function ze(e,t,n){let{appBase:r,apiBase:o}=U(t);return e.filter(s=>s.config?.appType==="frontend"||A(s.config?.appType)).map(s=>{let i=A(s.config.appType)?o:r;return{name:s.name,appType:s.config.appType,url:`${i}${n(L(s))}`}})}async function Lt(e,t){try{let n=await fetch(e.url,{method:"GET",redirect:"manual",signal:AbortSignal.timeout(t)});return{...e,ok:n.ok,status:n.status}}catch(n){let r=n instanceof Error&&n.name==="TimeoutError"?`no response within ${t}ms`:n instanceof Error?n.message:String(n);return{...e,ok:!1,error:r}}}async function Sr(e,t){return Promise.all(e.map(n=>Lt(n,t)))}var Xe=60,He=1e3,Je=1e4,Ze=200,Ye=64*1024,Qe=new Set([404,502,503,504]),Wt=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]),zt=new Set(["GET","HEAD","OPTIONS"]);function k(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gt(e,t){if(zt.has(t))return!0;if(!k(e))return!1;let n=k(e.cause)?e.cause:void 0,r=e.code??n?.code;return typeof r=="string"&&Wt.has(r)}function ie(e){if(!k(e))return['smoke: must be an object with "via" and "steps"'];let t=[];return e.via!=="proxy"&&t.push('smoke.via: only "proxy" is supported (the check must go through the public URL)'),e.timeoutSeconds!==void 0&&!(typeof e.timeoutSeconds=="number"&&e.timeoutSeconds>0)&&t.push("smoke.timeoutSeconds: must be a number greater than 0"),!Array.isArray(e.steps)||e.steps.length===0?(t.push("smoke.steps: needs at least one step"),t):(e.steps.forEach((n,r)=>{let o=`smoke.steps[${r}]`;if(!k(n)){t.push(`${o}: must be an object`);return}let s=n.path;(typeof s!="string"||!s.startsWith("/")||s.startsWith("//"))&&t.push(`${o}.path: must start with "/" and is appended to the public URL; a full URL or relative path would skip the proxy`),n.expect!==void 0&&!(typeof n.expect=="number"&&Number.isInteger(n.expect)&&n.expect>=100&&n.expect<=599)&&t.push(`${o}.expect: must be an HTTP status between 100 and 599`),n.bodyContains!==void 0&&typeof n.bodyContains!="string"&&t.push(`${o}.bodyContains: must be a string`),n.save!==void 0&&(k(n.save)&&Object.values(n.save).every(a=>typeof a=="string"&&a.startsWith("$."))||t.push(`${o}.save: must map names to paths such as "$.id"`))}),t)}function Ar(e,t){let{appBase:n,apiBase:r}=U(t),o=[];for(let s of e){let i=s.config;if(!i?.smoke)continue;let a=i.appType,c=A(a)||a===H;if(!c&&a!=="frontend"||i.exposeViaProxy===!1)continue;let f=c?r:n,d=i.healthCheckPath||(c&&a!==H?"/health":void 0);o.push({name:s.name,baseUrl:`${f}${L(s).replace(/\/+$/,"")}`,...d?.startsWith("/")?{readyPath:d}:{},smoke:i.smoke})}return o}var qt=10;function Tr(e,t={}){let n=t.timeoutSeconds??qt;return e.flatMap(r=>{let o=r.smoke.steps.filter(i=>(i.method??"GET").toUpperCase()==="GET"&&i.body===void 0&&!i.path.includes("{{")).map(i=>({...i,bodyContains:void 0}));if(o.length===0)return[];let s=Math.min(r.smoke.timeoutSeconds??Xe,n);return[{name:r.name,baseUrl:r.baseUrl,smoke:{via:r.smoke.via,timeoutSeconds:s,steps:o}}]})}function Kt(e,t){let n=t.replace(/^\$\.?/,"").match(/[^.[\]]+/g)??[],r=e;for(let o of n){if(r==null||o==="__proto__"||o==="constructor"||o==="prototype"||!Object.hasOwn(r,o))return;let s=Object.getOwnPropertyDescriptor(r,o);if(!s||!("value"in s))return;r=s.value}return r}function et(e,t){return e.replace(/\{\{(\w+)\}\}/g,(n,r)=>t[r]??n)}function se(e,t){return typeof e=="string"?et(e,t):Array.isArray(e)?e.map(n=>se(n,t)):k(e)?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,se(r,t)])):e}function V(e){let t=e.replace(/\s+/g," ").trim();return t.length>Ze?`${t.slice(0,Ze)}...`:t}function tt(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"step"}function Ht(e,t){let n=tt(t),r=(e.get(n)??0)+1;return e.set(n,r),r===1?n:`${n}-${r}`}function Jt(e,t,n,r,o){try{let s=O(e,tt(n.service),t);Vt(s,{recursive:!0});let i=O(s,"latest.json"),a=O(s,"body.txt");return r===void 0?qe(a,{force:!0}):Ke(a,r),Ke(i,`${JSON.stringify({...n,bodyPath:r===void 0?null:"body.txt"},null,2)}
15
+ `),o&&(Ge(i,O(s,"last-success.json")),r===void 0?qe(O(s,"last-success-body.txt"),{force:!0}):Ge(a,O(s,"last-success-body.txt"))),i}catch{return}}function Zt(e){let t=Buffer.from(e,"utf8");if(t.length<=Ye)return{body:e,truncated:!1,bytes:t.length};let n=t.subarray(0,Ye).toString("utf8").replace(/\uFFFD$/,"");return{body:n,truncated:!0,bytes:Buffer.byteLength(n,"utf8")}}async function Yt(e,t={}){let n=t.fetch??((d,u)=>fetch(d,u)),r=t.now??Date.now,o=t.sleep??(d=>new Promise(u=>setTimeout(u,d))),s=(e.smoke.timeoutSeconds??Xe)*1e3,i=r()+s,a={},c=ie(e.smoke);if(c.length>0)return{name:e.name,ok:!1,failure:`${e.name}: invalid smoke block: ${c.join("; ")}`};if(e.readyPath){let d=`${e.baseUrl}${e.readyPath}`;for(;;){try{let u=await n(d,{method:"GET",signal:AbortSignal.timeout(Je)});if(await u.text(),!Qe.has(u.status)&&u.status<500)break}catch{}if(r()>=i)break;await o(He)}}i=r()+s;let f=new Map;for(let[d,u]of e.smoke.steps.entries()){let h=u.name??`step ${d+1}`,l=(u.method??"GET").toUpperCase(),ye=`${e.baseUrl}${et(u.path,a)}`,D=u.expect??200,Se=u.body!==void 0,y,F="",E,G;for(;;){E=void 0,G=void 0,y=void 0;let g=!1;try{let m=await n(ye,{method:l,headers:Se?{"content-type":"application/json"}:void 0,body:Se?JSON.stringify(se(u.body,a)):void 0,signal:AbortSignal.timeout(Je)});y=m.status,F=await m.text()}catch(m){g=Gt(m,l),E=m instanceof Error?m.message:String(m);let w=k(m)?m.code??(k(m.cause)?m.cause.code:void 0):void 0;G=typeof w=="string"?w:void 0}if(!(E!==void 0&&g||y!==void 0&&y!==D&&Qe.has(y))||r()>=i)break;await o(He)}let $e=!1,q=g=>{if(!$e){if(!t.redact)return g;try{return t.redact(g)}catch($){$e=!0;let m=$ instanceof Error?$.message:String($);t.warn?.(`${e.name}: ${h}: the response text was neither shown nor stored: ${m}`);return}}},K=q(ye),S=y===void 0?void 0:q(F),xe=E===void 0?void 0:q(E),Ee=E===void 0?void 0:G??xe,N=`${e.name}: ${h}: ${l} ${K??"(url withheld)"}`,ve;if(t.recordDir){let g=S===void 0?void 0:Zt(S),$=E===void 0&&y===D&&(u.bodyContains===void 0||F.includes(u.bodyContains));ve=Jt(t.recordDir,Ht(f,h),{service:e.name,step:h,method:l,...K===void 0?{}:{url:K},expectedStatus:D,status:y??null,...Ee===void 0?{}:{error:Ee},truncated:g?.truncated??!1,bodyBytes:g?.bytes??0},g?.body,$)}let j=g=>({name:e.name,ok:!1,failure:g,recordPath:ve});if(E!==void 0)return j(`${N} never answered (${xe??"the error was withheld"})`);if(y!==D)return j(`${N} returned ${y}, expected ${D}${S?`: ${V(S)}`:""}`);if(u.bodyContains!==void 0&&!F.includes(u.bodyContains))return j(`${N} returned ${y} but the body does not contain "${u.bodyContains}"${S?`: ${V(S)}`:""}`);if(u.save){let g;try{g=JSON.parse(F)}catch{return j(`${N} returned a body that is not JSON, cannot save${S?`: ${V(S)}`:""}`)}for(let[$,m]of Object.entries(u.save)){let w=Kt(g,m);if(w==null||typeof w=="object")return j(`${N} response has no ${m} to save as {{${$}}}${S?`: ${V(S)}`:""}`);a[$]=String(w)}}}return{name:e.name,ok:!0}}function Rr(e){return`Smoke check failed: ${e.failure}${e.recordPath?` (record: ${e.recordPath})`:""}`}function _r(e,t,n={}){return{redact:Ve(e,n.envFiles??[]),...t?{warn:t}:{}}}async function Or(e,t={}){return Promise.all(e.map(n=>Yt(n,t)))}var nt=1,jr="https://flisk.sh/schema.service.json",Qt={dependencies:"deprecated, use dependsOn (still read for now)",envVars:"deprecated, use params (still read when params is absent)",jwtSecret:"ignored. A secret must not be committed: remove it and set JWT_SECRET in the project .env"},Xt=new Set(["$schema","agent","apiBasePath","apiPath","appName","appType","backendName","basePath","build","buildContext","command","databaseName","dependencies","dev","dependsOn","dockerfile","enabled","env","exposeViaProxy","featuresEnabled","framework","healthCheckPath","image","language","name","nats","params","playwright","port","replicas","restart","runtime","sablier","schemaVersion","secrets","smoke","stack","syncs","traefik","type"]),en=[{path:["healthCheckPath"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /health"},{path:["traefik","host"],pattern:/^[A-Za-z0-9.-]+$/,expected:"a hostname such as shop.backend.myproject.local"},{path:["traefik","pathPrefix"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /api/v1/shop-management"},{path:["traefik","healthCheck"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /health"},{path:["nats","queueGroup"],pattern:/^[A-Za-z0-9_.-]+$/,expected:"letters, digits, '_', '.' or '-'"},{path:["databaseName"],pattern:/^[A-Za-z0-9_-]+$/,expected:"letters, digits, '_' or '-'"},{path:["stack"],pattern:/^[A-Za-z0-9_-]+$/,expected:"letters, digits, '_' or '-'"},{path:["image"],pattern:/^[A-Za-z0-9._/:@-]+$/,expected:"an image reference such as ghcr.io/acme/app:1.0"},{path:["basePath"],pattern:/^\/[a-z0-9/-]*$/,expected:"a path such as /storefront"}];function W(e,t){let n=[],r=new Set;for(let{path:o,pattern:s,expected:i}of en){let a=e;for(let f of o)a=a&&typeof a=="object"&&!Array.isArray(a)?a[f]:void 0;if(a==null||a==="")continue;let c=`${t}.${o.join(".")}`;(typeof a!="string"||!s.test(a))&&r.add(c),typeof a!="string"?n.push(`${c}: expected a string`):s.test(a)||n.push(`${c}: must be ${i} (no spaces, line breaks or YAML syntax)`)}for(let[o,s]of tn(e,t))r.has(o)||n.push(`${o}: ${s}`);return n}function ae(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127||n===133||n===8232||n===8233)return!0}return!1}function tn(e,t){let n=[],r=Object.entries(e).filter(([o])=>o!=="smoke").map(([o,s])=>[s,`${t}.${o}`]);for(let o of Object.keys(e))ae(o)&&n.push([t,"a key contains a line break"]);for(;r.length>0;){let[o,s]=r.pop();if(typeof o=="string")ae(o)&&n.push([s,"must not contain line breaks or control characters"]);else if(Array.isArray(o))for(let[i,a]of o.entries())r.push([a,`${s}[${i}]`]);else if(o&&typeof o=="object")for(let[i,a]of Object.entries(o))ae(i)?n.push([s,"a key contains a line break"]):r.push([a,`${s}.${i}`])}return n.sort(([o],[s])=>o.localeCompare(s))}var nn=["apiPath","basePath"];function ce(e){if(!e||typeof e!="object"||Array.isArray(e))return[];let t=e;return nn.filter(n=>n in t&&typeof t[n]!="string")}function ue(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{errors:[`${t}: expected a JSON object`],warnings:[]};let n=e,r=[],o=v(n)?["appName","appType","schemaVersion"]:["appName","appType","stack","schemaVersion"];for(let c of o)c in n||r.push(`${t}.${c}: required field is missing`);for(let c of["appName","appType","stack"])c in n&&typeof n[c]!="string"&&r.push(`${t}.${c}: expected a string`);"schemaVersion"in n&&n.schemaVersion!==nt&&r.push(`${t}.schemaVersion: unsupported version ${String(n.schemaVersion)} (supported: ${nt})`);for(let c of ce(n))r.push(`${t}.${c}: expected a string`);let s=n.dev,i=s&&typeof s=="object"&&!Array.isArray(s)?s.liveReload:void 0;if(i!==void 0&&typeof i!="boolean"&&r.push(`${t}.dev.liveReload: expected true or false`),r.push(...W(n,t)),r.push(...B(n).map(c=>`${t}: ${c}`)),"smoke"in n)for(let c of ie(n.smoke))r.push(`${t}.${c}`);let a=Object.keys(n).flatMap(c=>{let f=Qt[c];return f?[`${t}.${c}: ${f}`]:Xt.has(c)?[]:[`${t}.${c}: unknown field is preserved`]});return i===!0&&n.language!=="go"&&a.push(`${t}.dev.liveReload: only applies to Go services (language "go"); it is ignored here`),{errors:r,warnings:a,manifest:n}}function Pr(e,t){if(!rt(e))return[];try{let n=JSON.parse(ot(e,"utf-8"));return!n||typeof n!="object"||Array.isArray(n)?[]:W(n,t)}catch{return[]}}function Ir(e,t){if(!rt(e))return{errors:[`${t}: file is missing`],warnings:[]};try{let n=JSON.parse(ot(e,"utf-8"));return ue(n,t)}catch(n){let r=n instanceof Error?n.message:String(n);return{errors:[`${t}: invalid JSON (${r})`],warnings:[]}}}function st(e){if(e.length===0||e.every(n=>n==="unknown"))return"unknown";let t=e.filter(n=>n==="ready").length/e.length;return t>.9?"healthy":t>.5?"degraded":"error"}var rn=/^[a-z0-9-]+$/;function it(e){return rn.test(e)}var on="Use lowercase letters, numbers, and hyphens only";function at(e,t){return`${e} "${t}" is not valid. ${on}, e.g. "${e==="Resource name"?"my-service":"my-stack"}".`}function sn(e){return e.trim()?it(e)?{valid:!0}:{valid:!1,error:at("Resource name",e)}:{valid:!1,error:"Resource name is required"}}function an(e){return e.trim()?it(e)?{valid:!0}:{valid:!1,error:at("Stack name",e)}:{valid:!1,error:"Stack name is required"}}function Lr(e){let t=e==="resource"?sn:an;return n=>{let r=t(n);return r.valid?!0:r.error??"Invalid name"}}function Vr(e){return fe(J,e)?{valid:!0}:{valid:!1,error:`Invalid service. Must be one of: ${J.join(", ")}`}}function Wr(e){return Number.isInteger(e)&&e>0&&e<=65535}function zr(e){return!e.includes("\0")&&!/[<>:"|?*]/.test(e)}function Gr(e,t="_"){return e.replace(/[^a-zA-Z0-9-]/g,t).substring(0,100)}function fe(e,t){return e.includes(t)}var pn=ke;function he(e,t=5,n=0){let r=[];if(n>t)return r;let o=ct.readdirSync(e,{withFileTypes:!0});for(let s of o){let i=b(e,s.name);if(s.isDirectory()){if(dn(s.name))continue;r.push(...he(i,t,n+1))}else s.isFile()&&s.name===pn&&r.push(i)}return r}function oo(e){return he(e)}function dn(e){return fe(Te,e)||e.startsWith(".")}function ln(e){if(!e||typeof e!="object")return!1;let t=e;if(v(t))return typeof t.appName=="string"&&typeof t.appType=="string";let n=typeof t.runtime=="string"||typeof t.appType=="string"||typeof t.type=="string";return typeof t.appName=="string"&&n}var I=new Set;function so(){I.clear()}var x=class extends Error{reason;constructor(t){super(t),this.reason=t,this.name="InvalidServiceJsonError"}};function de(e){return e instanceof Error?e.message:String(e)}function mn(e){let t;try{t=ct.readFileSync(e,"utf-8")}catch(f){throw new x(`could not read file: ${de(f)}`)}let n;try{n=JSON.parse(t)}catch(f){throw new x(`invalid JSON: ${de(f)}`)}if(!ln(n))throw new x(v(n)?"missing required fields (appName and appType)":"missing required fields (appName, runtime or appType)");let r=B(n);if(r.length>0)throw new x(r.join("; "));let o=ce(n);if(o.length>0)throw new x(o.map(f=>`${f}: expected a string`).join("; "));let s=W(n,e);if(s.length>0)throw new x(s.join("; "));let i=n;for(let f of ue(n,e).warnings)I.has(f)||(I.add(f),console.warn(`\u26A0\uFE0F ${f}`));let a={...i,appType:i.appType||i.type},c=fn(e);return{name:a.appName,path:c,configPath:e,config:a,stack:a.stack,port:a.port,type:a.appType}}function ut(e){let t=he(e),n=[],r=[];for(let o of t)try{n.push(mn(o))}catch(s){r.push({path:o,reason:s instanceof x?s.reason:de(s)})}return{resources:n,problems:r}}function hn(e){for(let t of e){let n=`skipped ${t.path}: ${t.reason}`;I.has(n)||(I.add(n),console.warn(`\u26A0\uFE0F ${n}`))}}function ft(e){let{resources:t,problems:n}=ut(e);return hn(n),t}function z(){let e=R();if(!e)throw Z.notInProject();return ft(e)}function io(e){let t=e;if(!t){let n=R();if(!n)throw Z.notInProject();t=ut(n)}if(t.problems.length>0)throw new Error(t.problems.map(n=>`Invalid service.json at ${n.path}: ${n.reason}`).join(`
16
+ `));return t.resources}function ao(e,t){let n=t??ft(e);return gn(n)}function gn(e){let t=e||z(),n=new Set;for(let r of t)r.stack&&n.add(r.stack);return Array.from(n).sort()}function co(e=z()){let t=new Map;for(let r of e)if(r.stack){let o=t.get(r.stack);o?o.push(r):t.set(r.stack,[r])}let n=[];for(let[r,o]of t)n.push({name:r,description:Ce(o.length,"resource"),resources:o,resourceCount:o.length});return n.sort((r,o)=>r.name.localeCompare(o.name))}function uo(e,t){return(t??z()).filter(r=>r.stack===e)}function fo(e,t){return(t??z()).some(r=>r.stack===e)}var ge=5e3,le=new P({ttlMs:ge}),me=new P({ttlMs:ge}),M=_e(ge);function po(){le.clear(),me.clear(),M.reset()}function yn(e,t){if(e==="Dockerfile"||e.startsWith("docker-compose"))return"docker";if(e==="Tiltfile"||e.endsWith(".star"))return"tilt";if(e==="vite.config.ts"||e==="tsconfig.json"||e===Ae)return"config";if(e==="schema.prisma"||e.includes("migration"))return"prisma";if(t){let n=t.split(`
17
17
  `).slice(0,10).join(`
18
- `);if(n.includes("GENERATED BY Flisk")||n.includes("AUTOGENERATED"))return"generated"}return"unknown"}function Sn(e){let t=e.configPath;if(B.isValid()){let l=le.get(t);if(l)return l}let n=e.path,r=pe(e.configPath),o=r.mtime.toISOString(),s=r.mtime.toISOString(),i=$n(n),a=C(b(n,"Dockerfile")),u=C(b(n,"Tiltfile")),f=C(b(n,be))||C(b(n,"docker-compose.yaml")),d=e.config?.appType,c=d||"backend";d||(e.name.includes("frontend")?c="frontend":(e.name.includes("sdk")||e.name.includes("lib")||e.name.includes("infra"))&&(c="library"));let h={name:e.name,stack:e.stack,type:c,port:e.config?.port,createdAt:o,lastModified:s,dependencies:e.config?.dependsOn??[],hasDockerfile:a,hasTiltfile:u,hasDockerCompose:f,autogeneratedFiles:i,status:"unknown"};return le.set(t,h),B.markUpdated(),h}function $n(e){let t=[];for(let r of Re){let o=b(e,r);if(C(o)){let s=pe(o),i=un(o,"utf-8");t.push({name:r,path:o,type:yn(r,i),size:s.size,lastModified:s.mtime.toISOString()})}}let n=b(e,"prisma");if(C(n)){let r=cn(n);for(let o of r)if(o==="schema.prisma"||o.includes("migration")){let s=b(n,o),i=pe(s);t.push({name:`prisma/${o}`,path:s,type:"prisma",size:i.size,lastModified:i.mtime.toISOString()})}}return t.sort((r,o)=>r.name.localeCompare(o.name))}function lo(e){let t=e.name;if(B.isValid()){let u=me.get(t);if(u)return u}let n=e.resources.map(u=>Sn(u)),r=n.map(u=>new Date(u.createdAt).getTime()),o=Math.min(...r),s=Math.max(...r),i=st(n.map(u=>u.status)),a={name:e.name,resourceCount:n.length,createdAt:new Date(o).toISOString(),lastModified:new Date(s).toISOString(),resources:n,overallStatus:i};return me.set(t,a),B.markUpdated(),a}export{Ce as a,vn as b,kn as c,bn as d,wn as e,An as f,Tn as g,Rn as h,_n as i,On as j,X as k,Cn as l,Dn as m,Nn as n,Fn as o,jn as p,Pn as q,In as r,Mn as s,ee as t,Un as u,Ln as v,Vn as w,Wn as x,A as y,De as z,er as A,_ as B,tr as C,nr as D,rr as E,oe as F,It as G,Le as H,Ue as I,mr as J,L as K,Ut as L,V as M,hr as N,gr as O,yr as P,Sr as Q,Ar as R,Tr as S,Rr as T,_r as U,Or as V,nt as W,jr as X,ue as Y,Pr as Z,Ir as _,st as $,sn as aa,an as ba,Lr as ca,Vr as da,Wr as ea,zr as fa,Gr as ga,fe as ha,oo as ia,so as ja,ut as ka,hn as la,ft as ma,G as na,io as oa,ao as pa,gn as qa,co as ra,uo as sa,fo as ta,po as ua,Sn as va,lo as wa};
18
+ `);if(n.includes("GENERATED BY Flisk")||n.includes("AUTOGENERATED"))return"generated"}return"unknown"}function Sn(e){let t=e.configPath;if(M.isValid()){let l=le.get(t);if(l)return l}let n=e.path,r=pe(e.configPath),o=r.mtime.toISOString(),s=r.mtime.toISOString(),i=$n(n),a=C(b(n,"Dockerfile")),c=C(b(n,"Tiltfile")),f=C(b(n,be))||C(b(n,"docker-compose.yaml")),d=e.config?.appType,u=d||"backend";d||(e.name.includes("frontend")?u="frontend":(e.name.includes("sdk")||e.name.includes("lib")||e.name.includes("infra"))&&(u="library"));let h={name:e.name,stack:e.stack,type:u,port:e.config?.port,createdAt:o,lastModified:s,dependencies:e.config?.dependsOn??[],hasDockerfile:a,hasTiltfile:c,hasDockerCompose:f,autogeneratedFiles:i,status:"unknown"};return le.set(t,h),M.markUpdated(),h}function $n(e){let t=[];for(let r of Re){let o=b(e,r);if(C(o)){let s=pe(o),i=un(o,"utf-8");t.push({name:r,path:o,type:yn(r,i),size:s.size,lastModified:s.mtime.toISOString()})}}let n=b(e,"prisma");if(C(n)){let r=cn(n);for(let o of r)if(o==="schema.prisma"||o.includes("migration")){let s=b(n,o),i=pe(s);t.push({name:`prisma/${o}`,path:s,type:"prisma",size:i.size,lastModified:i.mtime.toISOString()})}}return t.sort((r,o)=>r.name.localeCompare(o.name))}function lo(e){let t=e.name;if(M.isValid()){let c=me.get(t);if(c)return c}let n=e.resources.map(c=>Sn(c)),r=n.map(c=>new Date(c.createdAt).getTime()),o=Math.min(...r),s=Math.max(...r),i=st(n.map(c=>c.status)),a={name:e.name,resourceCount:n.length,createdAt:new Date(o).toISOString(),lastModified:new Date(s).toISOString(),resources:n,overallStatus:i};return me.set(t,a),M.markUpdated(),a}export{Ce as a,vn as b,kn as c,bn as d,wn as e,An as f,Tn as g,Rn as h,_n as i,On as j,X as k,Cn as l,Dn as m,Fn as n,Nn as o,jn as p,Pn as q,In as r,Mn as s,ee as t,Un as u,Ln as v,Vn as w,Wn as x,A as y,De as z,er as A,_ as B,tr as C,nr as D,rr as E,oe as F,It as G,Le as H,Ue as I,mr as J,U as K,Ut as L,L as M,hr as N,gr as O,yr as P,Sr as Q,Ar as R,Tr as S,Rr as T,_r as U,Or as V,nt as W,jr as X,ue as Y,Pr as Z,Ir as _,st as $,sn as aa,an as ba,Lr as ca,Vr as da,Wr as ea,zr as fa,Gr as ga,fe as ha,oo as ia,so as ja,ut as ka,hn as la,ft as ma,z as na,io as oa,ao as pa,gn as qa,co as ra,uo as sa,fo as ta,po as ua,Sn as va,lo as wa};