@flisk-sh/flisk 1.3.198 → 1.3.200
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.
- package/cli/dist/cli.js +63 -15
- package/cli/dist/commands/open.js +8 -0
- package/cli/dist/utils/one-command.js +80 -0
- package/cli/dist/utils/project-templates.js +14 -0
- package/cli/dist/utils/stack-progress.js +64 -0
- package/cli/dist-bundle/{chunk-KBGQ4NJE.js → chunk-DS47LHYF.js} +1 -1
- package/cli/dist-bundle/chunk-EICMGZXL.js +5 -0
- package/cli/dist-bundle/chunk-GEXOZCEQ.js +2 -0
- package/cli/dist-bundle/chunk-H446NBVS.js +71 -0
- package/cli/dist-bundle/chunk-IYZENTVI.js +34 -0
- package/cli/dist-bundle/{chunk-US4D5GJJ.js → chunk-LMHKXU2Q.js} +1 -1
- package/cli/dist-bundle/chunk-RMZQU74X.js +366 -0
- package/cli/dist-bundle/chunk-RWNIFXXG.js +4 -0
- package/cli/dist-bundle/chunk-WUO7C6WO.js +2 -0
- package/cli/dist-bundle/cli.js +266 -266
- package/cli/dist-bundle/cold-preflight-LPH7WF3B.js +2 -0
- package/cli/dist-bundle/one-command-YP6L6MMM.js +5 -0
- package/cli/dist-bundle/open-PF6BGKCR.js +2 -0
- package/cli/dist-bundle/project-2TVWT57J.js +2 -0
- package/cli/dist-bundle/stack-progress-UVREHNNB.js +2 -0
- package/cli/dist-bundle/tilt-port-owner-7O5FOW5C.js +2 -0
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/cli/dist-bundle/chunk-DWHA5YKT.js +0 -435
- package/cli/dist-bundle/chunk-KGKKDD4K.js +0 -2
- package/cli/dist-bundle/chunk-SB5WAMSU.js +0 -5
- package/cli/dist-bundle/chunk-WPQOTHB5.js +0 -4
- package/cli/dist-bundle/chunk-YUARXGF4.js +0 -34
- package/cli/dist-bundle/cold-preflight-5TVSNLIV.js +0 -2
- package/cli/dist-bundle/one-command-TKXMWXCK.js +0 -4
- package/cli/dist-bundle/open-5SUD6LKB.js +0 -2
- package/cli/dist-bundle/project-5GTJYFAE.js +0 -2
- package/cli/dist-bundle/project-templates-BHURA3LT.js +0 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{b as N,c as pe,d as H,i as D,j as q,n as J,o as I,q as C}from"./chunk-DS47LHYF.js";import{H as B,J as V,L as A,Na as ne,Ra as ae,Sa as se,Ua as ie,V as F,W as z,Wa as ce,Xa as le,Ya as de,aa as f,ba as v,ca as t,d as P,ja as re,k as U,ka as G,la as W,m as E,s as T}from"./chunk-LMHKXU2Q.js";import{b as p}from"./chunk-CF46CD6R.js";import{execFileSync as ve}from"node:child_process";import{cpSync as ye,existsSync as y,readdirSync as Se,readFileSync as Y,writeFileSync as je}from"node:fs";import{basename as _e,dirname as we,join as _,resolve as M}from"node:path";import{cwd as ue}from"node:process";import{fileURLToPath as xe}from"node:url";import{Command as Pe}from"commander";var he="-y, --yes",ke="Skip prompts and confirmations; use defaults";function fe(e,o=ke){return e.option(he,o,!1)}async function be(e,o=!0){let c=await I({message:e,initial:o});return c||F(),c}function Ne(e,o=1){e.valid||A(e.error??"Validation failed",o)}function De(e,o,c,a){return e.dryRun?(a?a():(console.log(`Dry run - ${o}`),console.log(`Would run: ${c}`)),!0):!1}async function Je(e,o){return await be(e,!0)?!0:(o&&o(),!1)}var L=new Map;function me(e){e!==void 0?L.delete(e):L.clear()}function Ue(e=!1){let o=T(),c=o??"";if(!e){let n=L.get(c);if(n)return n}let{resources:a,problems:l}=o?ae(o):{resources:ie(),problems:[]},d=de(a),r=le(a);se(l);let g=a.filter(n=>!n.stack),m=new Map;for(let n of a)if(n.stack){let w=m.get(n.stack)||[];w.push(n),m.set(n.stack,w)}let k={resources:a,problems:l,stacks:d,stackNames:r,unassignedResources:g,resourcesByStack:m};return L.set(c,k),k}var O={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 TDK_LICENSE_KEY)",category:"premium",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[]},[P]:{name:P,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:[P]},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:[P]},"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:[]}};var j={demo:{repo:"https://github.com/flisk-sh/tdk-restaurant-example.git",description:"The demo stack: restaurant ops with reservations, kitchen and floor control"},restaurant:{repo:"https://github.com/flisk-sh/tdk-restaurant-example.git",description:"Restaurant ops: reservations, kitchen, floor control (Hono + Vite + Bun)"},saas:{repo:"https://github.com/flisk-sh/tdk-saas-starter.git",description:"SaaS starter: account dashboard and a working checkout button (Hono + Vite + Bun)"},erp:{repo:"https://github.com/flisk-sh/tdk-erp-system.git",description:"Enterprise ERP: 100 microservices across 7 business domains"},"user-management":{repo:"https://github.com/flisk-sh/tdk-user-management.git",description:"User/auth management services"},ecommerce:{repo:"https://github.com/flisk-sh/tdk-ecommerce-example.git",description:"Vue 3 storefront and Hono catalog API (needs Flisk 1.3.75+ for --framework vue)"},example:{repo:"https://github.com/flisk-sh/tdk-example.git",bundledPath:"examples/tdk-example",description:"Hono + Postgres + NATS worker + Vite example with a routed write path"},"agent-stack":{repo:"https://github.com/flisk-sh/tdk-agent-stack.git",description:"AI agent stack: Bun agent + MCP tool server + Postgres + NATS"}};function Ge(e){let o=e[2];if(!(!o||o.startsWith("-")))return o in j?o:void 0}function K(e,o){try{return ce(e,o)}catch{return[]}}function Ee(e){return new Set([...e.phases.pre_alpha.enabledStacks,...e.phases.alpha.enabledStacks,...e.phases.beta.enabledStacks,...e.phases.out_of_scope.enabledStacks])}function Q(e,o){let c=Ee(e),a=o.filter(l=>!c.has(l));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 ut(e,o){let c=_(e,".tdk",U);if(!y(c))return[];let a=D(e),l=Q(a,K(e,o));return l.length>0&&N(c,a),l}function Ae(){return Object.values(O).filter(e=>e.enabled_by_default&&e.category==="core").map(e=>e.name)}function Ce(e,o){return o||(j[e]?.repo.split("/").pop()??e).replace(/\.git$/,"")}async function Oe(e,o,c){let a=j[e];if(!a){console.log(p.red(`
|
|
3
|
+
\u274C Unknown template: "${e}"`)),console.log(p.yellow(`
|
|
4
|
+
Available templates:`));for(let[r,g]of Object.entries(j))console.log(` ${p.cyan(r)} ${p.gray(`- ${g.description}`)}`);console.log(p.gray("\nOr run `flisk project` with no template for a blank project.")),process.exit(1)}let l=Ce(e,o);ne(l)||B.invalidPath(l).exit();let d=M(ue(),l);y(d)&&Se(d).length>0&&B.directoryExists(d).exit(),z(`Cloning template: ${e}`),t(`${a.description}
|
|
5
|
+
`,0),t(`Source: ${a.bundledPath?"bundled Flisk example":a.repo}`,0),t(`Destination: ${d}
|
|
6
|
+
`,0);try{if(a.bundledPath){let r=M(we(xe(new URL("../dist/commands/project.js",import.meta.url).href)),"../../../");ye(M(r,a.bundledPath),d,{recursive:!0});let g=_e(d).replace(/-/g,"_");for(let m 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 k=_(d,m),n=Y(k,"utf-8");je(k,n.replaceAll("tdk_example",g))}}else ve("git",["clone",a.repo,d],{stdio:"inherit"})}catch{A(a.bundledPath?"Bundled example copy failed. Reinstall the Flisk CLI package and try again.":"git clone failed. Check you have git installed and network access.")}me(),console.log(p.green(`
|
|
7
|
+
\u2705 Cloned "${e}" into ${l}/`)),t(`
|
|
8
|
+
Next steps:`,0),t(`1. cd ${l}`),t("2. flisk doctor # check Docker/Tilt are ready"),t("3. flisk up # start the stack"),t(`
|
|
9
|
+
Runtime readiness:`,0);for(let r of c)t(r,0)}var R={version:"1.0",project:{name:"",version:"1.0.0"},phases:{pre_alpha:{name:"Pre-Alpha",description:"Core infrastructure and MVP services",enabledStacks:Ae()},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:{}},gt=fe(new Pe("project")).description("Initialize or validate project-level master configuration").argument("[template]",`Clone a starter example instead of a blank project (${Object.keys(j).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").action(async(e,o)=>{let{assertMachineReadyOrExit:c,detectRuntimePrerequisites:a,formatRuntimeNextSteps:l}=await import("./cold-preflight-LPH7WF3B.js");await c({scope:"scaffold"});let d=l(a());if(e){await V(async()=>Oe(e,o.path,d));return}await V(async()=>{let r=T();r||(r=ue(),v(`\u{1F680} Initializing new Flisk project...
|
|
10
|
+
`),t(`Location: ${r}
|
|
11
|
+
`,0));let g=_(r,".tdk"),m=_(g,U);if(o.check){let i=E.every(u=>y(_(r,".tdk",".tdk-out",u))),h=y(m);if(i&&h){let u=D(r);f("Project configuration is valid"),t(`Project: ${u.project.name}`,3),t(`Phases: ${Object.keys(u.phases).join(", ")}`,3),process.exit(0)}else{if(console.log(p.yellow("\u26A0\uFE0F Project configuration incomplete:")),h||t(".tdk/project.json (not found)",3),!i)for(let u of E.filter(S=>!y(_(r,".tdk/.tdk-out",S))))t(`.tdk/.tdk-out/${u} (not found)`,3);t("Run `flisk project` to create them."),process.exit(1)}}z("Project Configuration"),t(`Project root: ${r}
|
|
12
|
+
`,0),y(g)||(pe(g),f("Created: .tdk/ directory"));let k=y(m);if(k&&!o.force){f(".tdk/project.json exists");let i=JSON.parse(Y(m,"utf-8")),h=D(r),u=K(r),S=Q(h,u);if((S.length>0||!i.phases)&&N(m,h),S.length>0&&t(`Auto-enabled discovered service stacks: ${S.join(", ")}`,0),v(`
|
|
13
|
+
\u{1F4CB} Regenerating master configuration files...
|
|
14
|
+
`),await q(r),W(r))f("Created: .env (the defaults work as-is)");else{let b=G(r);b.length>0&&f(`Updated: .env (added ${b.join(", ")}; existing values unchanged)`)}let $=H(r);$.length>0&&f(`Added to .gitignore: ${$.join(", ")}`),console.log(p.green(`
|
|
15
|
+
\u2705 Project configuration regenerated!`)),t(`
|
|
16
|
+
Generated in .tdk/.tdk-out/:`,0);for(let b of E)t(`- ${b}`);t(`
|
|
17
|
+
Runtime readiness:`,0);for(let b of d)t(b,0);return}if(k&&o.force&&!o.yes&&(console.log(p.red(`
|
|
18
|
+
\u26A0\uFE0F WARNING: --force will overwrite .tdk/project.json!`)),!await I({message:"This will reset your project configuration. Continue?",initial:!1}))){F();return}let n,w=K(r);if(o.configFile){let i=M(o.configFile);y(i)||A(`Config file not found: ${i}`);let h=Y(i,"utf-8");n=JSON.parse(h),f(`Loaded config from: ${i}`)}else if(o.yes)n=JSON.parse(JSON.stringify(R)),n.project.name=r.split("/").pop()||"my-project",console.log(p.gray("Using default configuration (non-interactive mode)"));else{v(`\u{1F4DD} Project Setup Wizard
|
|
19
|
+
`);let i=await J({message:"Project name:",initial:r.split("/").pop()||"my-project",validate:s=>s.trim()!==""||"Project name is required"}),h=await J({message:"Project version:",initial:"1.0.0-alpha"}),u=Object.values(O).filter(s=>s.category==="core"&&s.phase==="pre_alpha"),S=new Set(u.map(s=>s.name)),te=await C({message:"Select Pre-Alpha enabled stacks (core infrastructure):",choices:[...u.map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default})),...w.filter(s=>!S.has(s)).map(s=>({title:`${s} (discovered service stack)`,value:s,selected:!0}))]}),$=await C({message:"Select Alpha enabled stacks (core business):",choices:[{title:"api (Backend API)",value:"api"},{title:"app (Frontend app)",value:"app"}]}),b=await C({message:"Select Beta enabled stacks (extended features):",choices:[{title:"worker (Background jobs)",value:"worker"},{title:"migrator (Database migrations)",value:"migrator"}]}),x=await C({message:"Enable optional infrastructure (high resource):",choices:Object.values(O).filter(s=>s.category==="optional").concat(Object.values(O).filter(s=>s.category==="premium")).map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default}))});v(`
|
|
20
|
+
\u{1F4C2} Service discovery`),t("What: folder patterns Flisk scans for a service.json in each match."),t("Where: type one or more globs, comma-separated."),t("How: saved as discovery.paths in .tdk/project.json - editable later."),t(`Example: ${R.discovery.paths.join(", ")}
|
|
21
|
+
`);let ge=await J({message:"Folders to scan:",initial:R.discovery.paths.join(", ")});n=JSON.parse(JSON.stringify(R)),n.project.name=i,n.project.version=h,n.phases.pre_alpha.enabledStacks=Array.from(new Set(te)),n.phases.alpha.enabledStacks=Array.from(new Set($)),n.phases.beta.enabledStacks=Array.from(new Set(b)),n.optional_infra.monitoring=x.includes("monitoring"),n.optional_infra.elk=x.includes("elk"),n.optional_infra.debezium=x.includes("debezium"),n.optional_infra.golden_image=x.includes("golden_image"),n.optional_infra.verdaccio=x.includes("verdaccio");let oe=ge.split(",").map(s=>s.trim()).filter(s=>s.length>0);n.discovery.paths=oe.length>0?oe:R.discovery.paths}let X=Q(n,w);if(X.length>0&&t(`Auto-enabled discovered service stacks: ${X.join(", ")}`,0),v(`
|
|
22
|
+
\u{1F4CB} Creating project configuration...
|
|
23
|
+
`),N(m,n),f("Created: .tdk/project.json"),t(`\u2192 Project: ${n.project.name}`),v(`
|
|
24
|
+
\u{1F4CB} Generating master configuration files...
|
|
25
|
+
`),await q(r),v(`
|
|
26
|
+
\u{1F527} Setting up environment variables...
|
|
27
|
+
`),W(r))f("Created: .env (the defaults work as-is)");else{let i=G(r);i.length>0?f(`Updated: .env (added ${i.join(", ")}; existing values unchanged)`):f("Found: .env (environment already configured)")}let Z=re(r);if(Z.missing.length>0){console.log(p.yellow(`
|
|
28
|
+
\u26A0\uFE0F Missing required environment variables:`));for(let i of Z.missing)t(`- ${i}`,1);t("\u2192 Edit .env and fill in these values before running `flisk up`",1)}let ee=H(r);ee.length>0&&f(`Added to .gitignore: ${ee.join(", ")}`),console.log(p.green(`
|
|
29
|
+
\u2705 Project configuration complete!`)),t(`
|
|
30
|
+
Generated files in .tdk/.tdk-out/:`,0);for(let i of E)t(`- ${i}`);t(`
|
|
31
|
+
Environment configuration:`,0),t("- .env (contains all required variables)"),t(`
|
|
32
|
+
Source file:`,0),t("- .tdk/project.json (edit this to change project structure)"),t(`
|
|
33
|
+
Next steps:`,0),t("1. flisk resource api --type backend --stack app # scaffold a service"),t("2. flisk up app # start it"),t(`
|
|
34
|
+
Runtime readiness:`,0);for(let i of d)t(i,0);t("\nAfter editing .tdk/project.json, run `flisk config regenerate`.",0)})});export{me as a,Ue as b,fe as c,Ne as d,De as e,Je as f,Ge as g,ut as h,Ce as i,Oe as j,gt as k};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
-
import{b as f}from"./chunk-CF46CD6R.js";var ar="Container Runtime",_="bring-your-own";var De="database-management",cr=".tiltignore",Ie="service.json",Q="docker-compose.yml",ur=".autogenerated",Ct="TILT_TECH_STACK.star",Dt="TILT_RESOURCE_DEFAULTS.star",B="project.json",je="package.json",fr=[Ct,Dt,"spec.master"],pr=["backend","frontend","library","sdk","worker","migrator","mcp","agent",_],lr={backend:["dev","build","start"],worker:["dev","build","start"],migrator:["dev","build","start"],mcp:["dev","build","start"],agent:["dev","build","start"],frontend:["dev","build"],library:["dev","build"],sdk:["dev","build"],[_]:[]};var Z=["monitoring","elk","debezium","golden_image","verdaccio"],dr={frontend:{base:3e3,min:3e3,max:3999,range:"3000-3999"},backend:{base:4e3,min:4e3,max:4999,range:"4000-4999"},mcp:{base:4e3,min:4e3,max:5999,range:"4000-5999"},agent:{base:4e3,min:4e3,max:5999,range:"4000-5999"},[_]:{base:4e3,min:4e3,max:5999,range:"4000-5999"},worker:{base:6e3,min:6e3,max:6999,range:"6000-6999"},health:{base:5e3,min:5e3,max:5999,range:"5000-5999"},migrator:{base:7e3,min:7e3,max:7999,range:"7000-7999"}},mr={tiltUi:10350,traefik:8080},ee="https://flisk.sh/docs/quickstart/";var Ne=["node_modules",".git",".tilt",".tilt-engine","dist","build",".prisma",".turbo","coverage","tmp","temp","__tests__","test","tests",".github",".vscode","docs"],Pe=["Dockerfile",Q,"docker-compose.yaml","Tiltfile","vite.config.ts","tsconfig.json","tsconfig.build.json"],It={infra:"\u{1F3D7}\uFE0F",core:"\u{1F527}",api:"\u26A1",app:"\u{1F3A8}",orchestrator:"\u2699\uFE0F"};function hr(e){let t=e.toLowerCase();for(let[n,r]of Object.entries(It))if(t.includes(n))return r;return"\u{1F4E6}"}import{existsSync as Nt}from"node:fs";import{dirname as Pt,join as Ft,resolve as Ue}from"node:path";import{cwd as Ut}from"node:process";var Fe={name:"tdk-cli-internal",private:!0,version:"1.3.198",description:"Flisk CLI \u2014 start services on your machine.",keywords:["flisk","tilt","cli","local-dev","microservices"],homepage:"https://github.com/flisk-sh/flisk#readme",bugs:{url:"https://github.com/flisk-sh/flisk/issues"},repository:{type:"git",url:"git+https://github.com/flisk-sh/flisk.git",directory:"cli"},license:"MIT",author:"Flisk Team",type:"module",main:"./dist/index.js",types:"./dist/index.d.ts",bin:{flisk:"bin/tdk.js"},files:["dist","dist-bundle","bin","templates","README.md","Tiltfile","engine","discovery","specs","ext","!ext/ide-components/code_executor","shared-platform-engineering/docker-templates"],scripts:{build:"tsc && node scripts/bundle.mjs","build:watch":"tsc --watch","check:dist":"npm run build && ../scripts/check-cli-dist.sh",lint:"biome check .","lint:fix":"biome check . --write",typecheck:"tsc --project tsconfig.typecheck.json",clean:"rm -rf dist",dev:"bun run build:watch",test:"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage --coverage.reportOnFailure --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary --coverage.reporter=json --coverage.reportsDirectory=coverage --reporter=default --reporter=json --outputFile.json=test-results/vitest.json",prepack:"../scripts/stage-npm-engine.sh && npm run build"},dependencies:{commander:"^15.0.0",handlebars:"^4.7.8"},devDependencies:{"@biomejs/biome":"^2.5.13","@types/node":"^22.0.0","@types/react":"^19.3.0","@vitest/coverage-v8":"^5.0.2",ajv:"^8.17.1",chalk:"^6.0.1",esbuild:"^0.28.2",ink:"^8.0.0",knip:"^6.38.0",react:"^19.3.0","react-devtools-core":"^8.0.0",typescript:"^7.0.2",vite:"^8.3.3",vitest:"^5.0.0"},engines:{node:">=22.12.0"}};function E(e=Ut()){let t=Ue(e),n=Ue("/");for(;t!==n;){if(Nt(Ft(t,".tdk",B)))return t;let r=Pt(t);if(r===t)break;t=r}return null}var V=null;function Mt(){if(V)return V;let e=Fe;if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Invalid package.json: expected object");let t=e;return V={name:String(t.name??"@flisk-sh/flisk"),version:String(t.version??"0.0.0"),fullPackage:t},V}function wr(){return Mt().version}import{existsSync as Me}from"node:fs";import{delimiter as Le,extname as Be,join as Ve}from"node:path";import{delimiter as br,extname as Lt}from"node:path";function U(){return process.platform==="win32"}function Bt(e){return e==="linux"?"linux":e==="darwin"?"darwin":e==="win32"?"windows":null}function Vt(e){return e==="x64"?"amd64":e==="arm64"?"arm64":null}function Gt(e,t){let n=Bt(e),r=Vt(t);if(!n||!r||n==="windows"&&r!=="amd64")return null;let o=`tdk-${n}-${r}`;return n==="windows"?`${o}.exe`:o}function Ar(){return Gt(process.platform,process.arch)}function Rr(){return U()?"tdk.exe":"flisk"}function te(e,t=U()){return t?Lt(e)?[e]:[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e]}function G(e){let t=process.env.PATH??process.env.Path??"",n=U(),r=te(e,n);if(n&&!Be(e))for(let o of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let s=o.toLowerCase();s&&!r.includes(`${e}${s}`)&&r.push(`${e}${s}`)}for(let o of t.split(Le))if(o)for(let s of r){let i=Ve(o,s);if(Me(i))return i}return null}function Ir(e){let t=process.env.PATH??process.env.Path??"",n=U(),r=te(e,n);if(n&&!Be(e))for(let i of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let a=i.toLowerCase();a&&!r.includes(`${e}${a}`)&&r.push(`${e}${a}`)}let o=[],s=new Set;for(let i of t.split(Le))if(i)for(let a of r){let c=Ve(i,a);if(!Me(c))continue;let p=n?c.toLowerCase():c;s.has(p)||(s.add(p),o.push(c))}return o}import{execFileSync as Wt,execSync as Kt}from"node:child_process";var Ge=1e4;function We(){let e=!1;if(process.platform==="win32")try{return Wt(G("docker")??"docker",["ps"],{stdio:"ignore",timeout:Ge,windowsHide:!0}),"running"}catch(t){return t.code==="ETIMEDOUT"?"unresponsive":"missing"}for(let t of["docker ps","colima status","podman ps"])try{return Kt(t,{stdio:"ignore",timeout:Ge}),"running"}catch(n){n.code==="ETIMEDOUT"&&(e=!0)}return e?"unresponsive":"missing"}function W(e,t){return e[t]??0}function Ht(e,t){let n=Array.from({length:t.length+1},(r,o)=>o);for(let r=1;r<=e.length;r++){let o=[r];for(let s=1;s<=t.length;s++){let i=e[r-1]===t[s-1]?0:1;o[s]=Math.min(W(o,s-1)+1,W(n,s)+1,W(n,s-1)+i)}n=o}return W(n,t.length)}function zt(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length-1;n++)if(e[n]!==t[n]&&e[n]===t[n+1]&&e[n+1]===t[n]&&e.slice(0,n)===t.slice(0,n)&&e.slice(n+2)===t.slice(n+2))return!0;return!1}function Ke(e,t){if(e.length<2||t.length===0||t.includes(e))return;let n=e.toLowerCase(),r,o=Number.POSITIVE_INFINITY;for(let s of t){if(s.length===0)continue;let i=s.toLowerCase(),a=zt(n,i)?1:Ht(n,i),c=Math.max(1,Math.min(2,Math.floor(Math.max(n.length,i.length)/3)));a>c||(a<o||a===o&&(r===void 0||s<r))&&(r=s,o=a)}return r}import{spawn as Jt}from"node:child_process";import{join as qt}from"node:path";function Yt(e,t=[],n={}){return new Promise((r,o)=>{let s=[e,...t];n.verbose&&console.log(`Executing: tilt ${s.join(" ")}`);let i=Jt(G("tilt")??"tilt",s,{stdio:n.inheritStdio?"inherit":"pipe",shell:!1,windowsHide:process.platform==="win32",timeout:n.timeoutMs}),a="",c="";n.inheritStdio||(i.stdout?.on("data",p=>{a+=p.toString()}),i.stderr?.on("data",p=>{c+=p.toString()})),i.on("close",p=>{r({exitCode:p??1,stdout:a,stderr:c})}),i.on("error",p=>{o(new Error(`Failed to spawn tilt: ${p.message}`))})})}async function He(){try{return(await Yt("version",[],{inheritStdio:!1,timeoutMs:1e4})).exitCode===0}catch{return!1}}function Xt(){let e=E();if(!e)throw $.notInProject();return qt(e,".tdk",".tdk-out","Tiltfile")}function ze(e){let t=Xt();e.push("-f",t)}function Wr(e,t={}){let n=[];return ze(n),t.focusTargets&&t.focusTargets.length>0&&n.push("--",`--focus=${t.focusTargets.join(",")}`),n.push(...e),t.verbose&&!t.quiet&&n.push("--verbose"),t.watch&&n.push("--watch"),n}function Kr(e={}){let t=[];return ze(t),e.focusTargets&&e.focusTargets.length>0&&t.push("--",`--focus=${e.focusTargets.join(",")}`),t}function Je(e){return e instanceof Error?e.message:String(e)}function Zr(e,t){if(process.env.TDK_VERBOSE){let n=t!==void 0?`: ${Je(t)}`:"";console.warn(f.gray(`${e}${n}`))}}var S=class extends Error{suggestions;exitCode;constructor(t,n=[],r=1){super(t),this.name="TdkError",this.suggestions=n,this.exitCode=r}display(){oe(this.message,void 0,this.suggestions)}exit(){this.display(),process.exit(this.exitCode)}},re=class extends Error{code;constructor(t=1){super(`process.exit(${t})`),this.name="ProcessExitError",this.code=t}};function ne(e,t,n=!1){let r=new Set;for(let o of e){let s=Ke(o,t);s&&r.add(n?`Did you mean "${s}" for "${o}"?`:`Did you mean "${s}"?`)}return[...r]}var $={tiltNotInstalled:()=>new S("Tilt CLI is not installed",["Install Tilt: `brew install tilt` (macOS)","Or download from: https://docs.tilt.dev/install.html","Verify with: `tilt version`",`Setup guide: ${ee}`]),dockerNotAvailable:()=>new S("Docker (or a compatible container runtime) is not running",["Start Docker Desktop, then verify with: `docker ps`","Or start Colima: `colima start`","Or start the Docker service on Linux: `sudo systemctl start docker`",`Setup guide: ${ee}`]),dockerNotResponding:()=>new S("Docker is running but not responding (`docker ps` hung for 10s)",["Quit and reopen Docker Desktop, or run `colima restart`","Then verify with: `docker ps`"]),stackNotFound:(e,t=[])=>new S(`Stack "${e}" not found`,[...ne([e],t),"Run `flisk stacks` to see available stacks","Run `flisk stack` to assign resources to a stack"]),resourceNotFound:(e,t=[])=>new S(`Resource "${e}" not found`,[...ne([e],t),"Run `flisk resources` to list all resources","Check the resource name spelling"]),unknownServices:(e,t)=>{let n=ne(e,t,e.length>1);return new S(`Unknown service ${e.join(", ")}. Valid names: ${t.join(", ")}`,n,2)},directoryExists:e=>new S(`Directory already exists: ${e}`,["Use `--path` to specify a different location","Remove the existing directory if no longer needed"]),invalidPath:e=>new S(`Invalid path: ${e}`,["Path must be within the project directory",'Path cannot contain special characters like <>:"|?*']),notInProject:()=>new S("Could not find project root (no .tdk/project.json found)",["Run this from within a Flisk project","Run `flisk project --yes` to initialize a new project"])};function eo(){let e=E();return e||$.notInProject().exit()}function Qt(e){e instanceof S&&e.exit(),oe(Je(e)),process.exit(1)}async function Zt(e,t){try{return await e()}catch(n){if(n instanceof re)throw n;return t?.verbose&&n instanceof Error&&n.stack&&console.error(f.gray(n.stack)),Qt(n)}}async function to(e,t){let n=[],r=We();return r==="unresponsive"?n.push($.dockerNotResponding()):r==="missing"&&n.push($.dockerNotAvailable()),await He()||n.push($.tiltNotInstalled()),n.length>0&&(console.error(f.red(`
|
|
2
|
+
import{b as f}from"./chunk-CF46CD6R.js";var ar="Container Runtime",_="bring-your-own";var De="database-management",cr=".tiltignore",Ie="service.json",Q="docker-compose.yml",ur=".autogenerated",Ct="TILT_TECH_STACK.star",Dt="TILT_RESOURCE_DEFAULTS.star",B="project.json",je="package.json",fr=[Ct,Dt,"spec.master"],pr=["backend","frontend","library","sdk","worker","migrator","mcp","agent",_],lr={backend:["dev","build","start"],worker:["dev","build","start"],migrator:["dev","build","start"],mcp:["dev","build","start"],agent:["dev","build","start"],frontend:["dev","build"],library:["dev","build"],sdk:["dev","build"],[_]:[]};var Z=["monitoring","elk","debezium","golden_image","verdaccio"],dr={frontend:{base:3e3,min:3e3,max:3999,range:"3000-3999"},backend:{base:4e3,min:4e3,max:4999,range:"4000-4999"},mcp:{base:4e3,min:4e3,max:5999,range:"4000-5999"},agent:{base:4e3,min:4e3,max:5999,range:"4000-5999"},[_]:{base:4e3,min:4e3,max:5999,range:"4000-5999"},worker:{base:6e3,min:6e3,max:6999,range:"6000-6999"},health:{base:5e3,min:5e3,max:5999,range:"5000-5999"},migrator:{base:7e3,min:7e3,max:7999,range:"7000-7999"}},mr={tiltUi:10350,traefik:8080},ee="https://flisk.sh/docs/quickstart/";var Ne=["node_modules",".git",".tilt",".tilt-engine","dist","build",".prisma",".turbo","coverage","tmp","temp","__tests__","test","tests",".github",".vscode","docs"],Pe=["Dockerfile",Q,"docker-compose.yaml","Tiltfile","vite.config.ts","tsconfig.json","tsconfig.build.json"],It={infra:"\u{1F3D7}\uFE0F",core:"\u{1F527}",api:"\u26A1",app:"\u{1F3A8}",orchestrator:"\u2699\uFE0F"};function hr(e){let t=e.toLowerCase();for(let[n,r]of Object.entries(It))if(t.includes(n))return r;return"\u{1F4E6}"}import{existsSync as Nt}from"node:fs";import{dirname as Pt,join as Ft,resolve as Ue}from"node:path";import{cwd as Ut}from"node:process";var Fe={name:"tdk-cli-internal",private:!0,version:"1.3.200",description:"Flisk CLI \u2014 start services on your machine.",keywords:["flisk","tilt","cli","local-dev","microservices"],homepage:"https://github.com/flisk-sh/flisk#readme",bugs:{url:"https://github.com/flisk-sh/flisk/issues"},repository:{type:"git",url:"git+https://github.com/flisk-sh/flisk.git",directory:"cli"},license:"MIT",author:"Flisk Team",type:"module",main:"./dist/index.js",types:"./dist/index.d.ts",bin:{flisk:"bin/tdk.js"},files:["dist","dist-bundle","bin","templates","README.md","Tiltfile","engine","discovery","specs","ext","!ext/ide-components/code_executor","shared-platform-engineering/docker-templates"],scripts:{build:"tsc && node scripts/bundle.mjs","build:watch":"tsc --watch","check:dist":"npm run build && ../scripts/check-cli-dist.sh",lint:"biome check .","lint:fix":"biome check . --write",typecheck:"tsc --project tsconfig.typecheck.json",clean:"rm -rf dist",dev:"bun run build:watch",test:"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage --coverage.reportOnFailure --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary --coverage.reporter=json --coverage.reportsDirectory=coverage --reporter=default --reporter=json --outputFile.json=test-results/vitest.json",prepack:"../scripts/stage-npm-engine.sh && npm run build"},dependencies:{commander:"^15.0.0",handlebars:"^4.7.8"},devDependencies:{"@biomejs/biome":"^2.5.13","@types/node":"^22.0.0","@types/react":"^19.3.0","@vitest/coverage-v8":"^5.0.2",ajv:"^8.17.1",chalk:"^6.0.1",esbuild:"^0.28.2",ink:"^8.0.0",knip:"^6.38.0",react:"^19.3.0","react-devtools-core":"^8.0.0",typescript:"^7.0.2",vite:"^8.3.3",vitest:"^5.0.0"},engines:{node:">=22.12.0"}};function E(e=Ut()){let t=Ue(e),n=Ue("/");for(;t!==n;){if(Nt(Ft(t,".tdk",B)))return t;let r=Pt(t);if(r===t)break;t=r}return null}var V=null;function Mt(){if(V)return V;let e=Fe;if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Invalid package.json: expected object");let t=e;return V={name:String(t.name??"@flisk-sh/flisk"),version:String(t.version??"0.0.0"),fullPackage:t},V}function wr(){return Mt().version}import{existsSync as Me}from"node:fs";import{delimiter as Le,extname as Be,join as Ve}from"node:path";import{delimiter as br,extname as Lt}from"node:path";function U(){return process.platform==="win32"}function Bt(e){return e==="linux"?"linux":e==="darwin"?"darwin":e==="win32"?"windows":null}function Vt(e){return e==="x64"?"amd64":e==="arm64"?"arm64":null}function Gt(e,t){let n=Bt(e),r=Vt(t);if(!n||!r||n==="windows"&&r!=="amd64")return null;let o=`tdk-${n}-${r}`;return n==="windows"?`${o}.exe`:o}function Ar(){return Gt(process.platform,process.arch)}function Rr(){return U()?"tdk.exe":"flisk"}function te(e,t=U()){return t?Lt(e)?[e]:[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e]}function G(e){let t=process.env.PATH??process.env.Path??"",n=U(),r=te(e,n);if(n&&!Be(e))for(let o of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let s=o.toLowerCase();s&&!r.includes(`${e}${s}`)&&r.push(`${e}${s}`)}for(let o of t.split(Le))if(o)for(let s of r){let i=Ve(o,s);if(Me(i))return i}return null}function Ir(e){let t=process.env.PATH??process.env.Path??"",n=U(),r=te(e,n);if(n&&!Be(e))for(let i of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let a=i.toLowerCase();a&&!r.includes(`${e}${a}`)&&r.push(`${e}${a}`)}let o=[],s=new Set;for(let i of t.split(Le))if(i)for(let a of r){let c=Ve(i,a);if(!Me(c))continue;let p=n?c.toLowerCase():c;s.has(p)||(s.add(p),o.push(c))}return o}import{execFileSync as Wt,execSync as Kt}from"node:child_process";var Ge=1e4;function We(){let e=!1;if(process.platform==="win32")try{return Wt(G("docker")??"docker",["ps"],{stdio:"ignore",timeout:Ge,windowsHide:!0}),"running"}catch(t){return t.code==="ETIMEDOUT"?"unresponsive":"missing"}for(let t of["docker ps","colima status","podman ps"])try{return Kt(t,{stdio:"ignore",timeout:Ge}),"running"}catch(n){n.code==="ETIMEDOUT"&&(e=!0)}return e?"unresponsive":"missing"}function W(e,t){return e[t]??0}function Ht(e,t){let n=Array.from({length:t.length+1},(r,o)=>o);for(let r=1;r<=e.length;r++){let o=[r];for(let s=1;s<=t.length;s++){let i=e[r-1]===t[s-1]?0:1;o[s]=Math.min(W(o,s-1)+1,W(n,s)+1,W(n,s-1)+i)}n=o}return W(n,t.length)}function zt(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length-1;n++)if(e[n]!==t[n]&&e[n]===t[n+1]&&e[n+1]===t[n]&&e.slice(0,n)===t.slice(0,n)&&e.slice(n+2)===t.slice(n+2))return!0;return!1}function Ke(e,t){if(e.length<2||t.length===0||t.includes(e))return;let n=e.toLowerCase(),r,o=Number.POSITIVE_INFINITY;for(let s of t){if(s.length===0)continue;let i=s.toLowerCase(),a=zt(n,i)?1:Ht(n,i),c=Math.max(1,Math.min(2,Math.floor(Math.max(n.length,i.length)/3)));a>c||(a<o||a===o&&(r===void 0||s<r))&&(r=s,o=a)}return r}import{spawn as Jt}from"node:child_process";import{join as qt}from"node:path";function Yt(e,t=[],n={}){return new Promise((r,o)=>{let s=[e,...t];n.verbose&&console.log(`Executing: tilt ${s.join(" ")}`);let i=Jt(G("tilt")??"tilt",s,{stdio:n.inheritStdio?"inherit":"pipe",shell:!1,windowsHide:process.platform==="win32",timeout:n.timeoutMs}),a="",c="";n.inheritStdio||(i.stdout?.on("data",p=>{a+=p.toString()}),i.stderr?.on("data",p=>{c+=p.toString()})),i.on("close",p=>{r({exitCode:p??1,stdout:a,stderr:c})}),i.on("error",p=>{o(new Error(`Failed to spawn tilt: ${p.message}`))})})}async function He(){try{return(await Yt("version",[],{inheritStdio:!1,timeoutMs:1e4})).exitCode===0}catch{return!1}}function Xt(){let e=E();if(!e)throw $.notInProject();return qt(e,".tdk",".tdk-out","Tiltfile")}function ze(e){let t=Xt();e.push("-f",t)}function Wr(e,t={}){let n=[];return ze(n),t.focusTargets&&t.focusTargets.length>0&&n.push("--",`--focus=${t.focusTargets.join(",")}`),n.push(...e),t.verbose&&!t.quiet&&n.push("--verbose"),t.watch&&n.push("--watch"),n}function Kr(e={}){let t=[];return ze(t),e.focusTargets&&e.focusTargets.length>0&&t.push("--",`--focus=${e.focusTargets.join(",")}`),t}function Je(e){return e instanceof Error?e.message:String(e)}function Zr(e,t){if(process.env.TDK_VERBOSE){let n=t!==void 0?`: ${Je(t)}`:"";console.warn(f.gray(`${e}${n}`))}}var S=class extends Error{suggestions;exitCode;constructor(t,n=[],r=1){super(t),this.name="TdkError",this.suggestions=n,this.exitCode=r}display(){oe(this.message,void 0,this.suggestions)}exit(){this.display(),process.exit(this.exitCode)}},re=class extends Error{code;constructor(t=1){super(`process.exit(${t})`),this.name="ProcessExitError",this.code=t}};function ne(e,t,n=!1){let r=new Set;for(let o of e){let s=Ke(o,t);s&&r.add(n?`Did you mean "${s}" for "${o}"?`:`Did you mean "${s}"?`)}return[...r]}var $={tiltNotInstalled:()=>new S("Tilt CLI is not installed",["Install Tilt: `brew install tilt` (macOS)","Or download from: https://docs.tilt.dev/install.html","Verify with: `tilt version`",`Setup guide: ${ee}`]),dockerNotAvailable:()=>new S("Docker (or a compatible container runtime) is not running",["Start Docker Desktop, then verify with: `docker ps`","Or start Colima: `colima start`","Or start the Docker service on Linux: `sudo systemctl start docker`",`Setup guide: ${ee}`]),dockerNotResponding:()=>new S("Docker is running but not responding (`docker ps` hung for 10s)",["Quit and reopen Docker Desktop, or run `colima restart`","Then verify with: `docker ps`"]),stackNotFound:(e,t=[])=>new S(`Stack "${e}" not found`,[...ne([e],t),"Run `flisk stacks` to see available stacks","Run `flisk stack` to assign resources to a stack"]),resourceNotFound:(e,t=[])=>new S(`Resource "${e}" not found`,[...ne([e],t),"Run `flisk resources` to list all resources","Check the resource name spelling"]),unknownServices:(e,t)=>{let n=ne(e,t,e.length>1);return new S(`Unknown service ${e.join(", ")}. Valid names: ${t.join(", ")}`,n,2)},directoryExists:e=>new S(`Directory already exists: ${e}`,["Use `--path` to specify a different location","Remove the existing directory if no longer needed"]),invalidPath:e=>new S(`Invalid path: ${e}`,["Path must be within the project directory",'Path cannot contain special characters like <>:"|?*']),notInProject:()=>new S("Could not find project root (no .tdk/project.json found)",["Run this from within a Flisk project","Run `flisk project --yes` to initialize a new project"])};function eo(){let e=E();return e||$.notInProject().exit()}function Qt(e){e instanceof S&&e.exit(),oe(Je(e)),process.exit(1)}async function Zt(e,t){try{return await e()}catch(n){if(n instanceof re)throw n;return t?.verbose&&n instanceof Error&&n.stack&&console.error(f.gray(n.stack)),Qt(n)}}async function to(e,t){let n=[],r=We();return r==="unresponsive"?n.push($.dockerNotResponding()):r==="missing"&&n.push($.dockerNotAvailable()),await He()||n.push($.tiltNotInstalled()),n.length>0&&(console.error(f.red(`
|
|
3
3
|
Flisk can't start - missing prerequisites:
|
|
4
4
|
`)),n.forEach((o,s)=>{s>0&&console.error(""),o.display()}),console.error(f.gray("\nRun `flisk doctor` for a full environment check.")),process.exit(1)),Zt(e,t)}function no(e,t=1,n){oe(e,void 0,n),process.exit(t)}function oe(e,t,n){if(console.error(f.red(`\u274C ${e}`)),t&&console.error(f.gray(` ${t}`)),n&&n.length>0){console.error(f.yellow(`
|
|
5
5
|
\u{1F4A1} Suggestions:`));for(let r of n)console.error(f.cyan(` \u2192 ${r}`))}}function ro(e,t){console.error(f.red(`
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{b as N}from"./chunk-GEXOZCEQ.js";import{G as O,O as m,Ta as f,c as $,d as C,fa as T,g as U,h as E,k as F,l as P,s as l,x as M}from"./chunk-LMHKXU2Q.js";import{execFileSync as de,execSync as ue}from"node:child_process";import{existsSync as R,readdirSync as V,readFileSync as y}from"node:fs";import{basename as le,dirname as me,join as u,relative as k}from"node:path";function Oe(t){return I(t,!1)}function Ne(t){return I(t,!0)}function I(t,e){return`import { Hono } from 'hono';
|
|
3
|
+
${e?`import { sql } from 'drizzle-orm';
|
|
4
|
+
import { db } from './db.js';
|
|
5
|
+
`:""}
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
9
|
+
app.get('/health', (c) => {
|
|
10
|
+
return c.json({ status: 'ok', service: '${t}' });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
app.get('/health/live', (c) => {
|
|
14
|
+
return c.json({ status: 'alive', timestamp: Date.now() });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.get('/health/ready', async (c) => {
|
|
18
|
+
const dependencies: Record<string, string> = {};
|
|
19
|
+
let allReady = true;
|
|
20
|
+
|
|
21
|
+
// Add dependency checks here (database, cache, etc.)
|
|
22
|
+
// Mark allReady = false if any dependency is unhealthy
|
|
23
|
+
|
|
24
|
+
const status = allReady ? 'ready' : 'not_ready';
|
|
25
|
+
return c.json({ status, dependencies }, allReady ? 200 : 503);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
${e?`// Database check: runs a trivial query through the Drizzle client (src/db.ts).
|
|
29
|
+
app.get('/health/db', async (c) => {
|
|
30
|
+
try {
|
|
31
|
+
await db.execute(sql\`select 1\`);
|
|
32
|
+
return c.json({ status: 'ok', database: 'up' });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Database health check failed:', error);
|
|
35
|
+
return c.json({ status: 'error', database: 'down' }, 503);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
`:""}app.get('/', (c) => {
|
|
40
|
+
return c.json({
|
|
41
|
+
service: '${t}',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
endpoints: ['/health', '/health/live', '/health/ready'${e?", '/health/db'":""}]
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const port = process.env.PORT || 3000;
|
|
48
|
+
console.log('\\n\u{1F680} ${t} running on http://localhost:' + port);
|
|
49
|
+
console.log('\u{1F4CA} Health check: http://localhost:' + port + '/health\\n');
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
port,
|
|
53
|
+
fetch: app.fetch,
|
|
54
|
+
};
|
|
55
|
+
`}var G={id:"bun",label:"Bun + Hono",installHint:"bun install"};var se="go run .",L={id:"go",label:"Go 1.23 (net/http)",devCommand:se,watch:["**/*.go","go.mod","go.sum"],installHint:"go mod tidy # optional: Docker builds the image for you",createFiles(t){return[{filename:"go.mod",content:`module ${t}
|
|
56
|
+
|
|
57
|
+
go 1.23
|
|
58
|
+
`,description:"Generating go.mod",emoji:"\u{1F4E6}"},{filename:"main.go",content:`package main
|
|
59
|
+
|
|
60
|
+
import (
|
|
61
|
+
"encoding/json"
|
|
62
|
+
"log"
|
|
63
|
+
"net/http"
|
|
64
|
+
"os"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
func writeJSON(w http.ResponseWriter, status int, body any) {
|
|
68
|
+
w.Header().Set("Content-Type", "application/json")
|
|
69
|
+
w.WriteHeader(status)
|
|
70
|
+
_ = json.NewEncoder(w).Encode(body)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func newMux() *http.ServeMux {
|
|
74
|
+
mux := http.NewServeMux()
|
|
75
|
+
|
|
76
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
77
|
+
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
|
78
|
+
writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "service": "${t}"})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
|
82
|
+
writeJSON(w, http.StatusOK, map[string]any{
|
|
83
|
+
"service": "${t}",
|
|
84
|
+
"version": "1.0.0",
|
|
85
|
+
"endpoints": []string{"/health"},
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return mux
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func main() {
|
|
93
|
+
port := os.Getenv("PORT")
|
|
94
|
+
if port == "" {
|
|
95
|
+
port = "3000"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ":" + port listens on every interface, which Traefik needs inside the container network.
|
|
99
|
+
log.Printf("${t} listening on :%s", port)
|
|
100
|
+
log.Fatal(http.ListenAndServe(":"+port, newMux()))
|
|
101
|
+
}
|
|
102
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"main_test.go",content:`package main
|
|
103
|
+
|
|
104
|
+
import (
|
|
105
|
+
"encoding/json"
|
|
106
|
+
"net/http"
|
|
107
|
+
"net/http/httptest"
|
|
108
|
+
"testing"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
func TestHealth(t *testing.T) {
|
|
112
|
+
rec := httptest.NewRecorder()
|
|
113
|
+
newMux().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/health", nil))
|
|
114
|
+
|
|
115
|
+
if rec.Code != http.StatusOK {
|
|
116
|
+
t.Fatalf("GET /health = %d, want %d", rec.Code, http.StatusOK)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func TestRoot(t *testing.T) {
|
|
121
|
+
rec := httptest.NewRecorder()
|
|
122
|
+
newMux().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
123
|
+
|
|
124
|
+
if rec.Code != http.StatusOK {
|
|
125
|
+
t.Fatalf("GET / = %d, want %d", rec.Code, http.StatusOK)
|
|
126
|
+
}
|
|
127
|
+
if got := rec.Header().Get("Content-Type"); got != "application/json" {
|
|
128
|
+
t.Fatalf("GET / Content-Type = %q, want %q", got, "application/json")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var body map[string]any
|
|
132
|
+
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
|
|
133
|
+
t.Fatalf("GET / body is not JSON: %v", err)
|
|
134
|
+
}
|
|
135
|
+
if body["service"] != "${t}" {
|
|
136
|
+
t.Errorf("service = %v, want %q", body["service"], "${t}")
|
|
137
|
+
}
|
|
138
|
+
if body["version"] != "1.0.0" {
|
|
139
|
+
t.Errorf("version = %v, want %q", body["version"], "1.0.0")
|
|
140
|
+
}
|
|
141
|
+
endpoints, ok := body["endpoints"].([]any)
|
|
142
|
+
if !ok || len(endpoints) != 1 || endpoints[0] != "/health" {
|
|
143
|
+
t.Errorf("endpoints = %v, want [/health]", body["endpoints"])
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`,description:"Generating smoke test",emoji:"\u{1F9EA}"}]}};var ne="node --watch src/index.js",B={id:"node",label:"Node.js 22",devCommand:ne,watch:["src/**/*"],installHint:"npm install # optional: Docker builds the image for you",createFiles(t){return[{filename:"package.json",content:`{
|
|
147
|
+
"name": "${t}",
|
|
148
|
+
"version": "1.0.0",
|
|
149
|
+
"type": "module",
|
|
150
|
+
"scripts": {
|
|
151
|
+
"dev": "node --watch src/index.js",
|
|
152
|
+
"start": "node src/index.js",
|
|
153
|
+
"test": "node --test"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`,description:"Generating package.json",emoji:"\u{1F4E6}"},{filename:"package-lock.json",content:`{
|
|
157
|
+
"name": "${t}",
|
|
158
|
+
"version": "1.0.0",
|
|
159
|
+
"lockfileVersion": 3,
|
|
160
|
+
"requires": true,
|
|
161
|
+
"packages": {
|
|
162
|
+
"": {
|
|
163
|
+
"name": "${t}",
|
|
164
|
+
"version": "1.0.0"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
`,description:"Generating package-lock.json",emoji:"\u{1F512}"},{filename:"src/index.js",content:`import http from "node:http";
|
|
169
|
+
|
|
170
|
+
const name = "${t}";
|
|
171
|
+
const port = Number(process.env.PORT) || 3000;
|
|
172
|
+
|
|
173
|
+
function sendJson(res, status, data) {
|
|
174
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
175
|
+
res.end(JSON.stringify(data));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const server = http.createServer((req, res) => {
|
|
179
|
+
const url = new URL(req.url, \`http://\${req.headers.host || "localhost"}\`);
|
|
180
|
+
|
|
181
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
182
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
183
|
+
return sendJson(res, 200, { status: "ok", service: name });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
187
|
+
return sendJson(res, 200, {
|
|
188
|
+
service: name,
|
|
189
|
+
version: "1.0.0",
|
|
190
|
+
endpoints: ["/health"],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return sendJson(res, 404, { error: "Not Found" });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (process.env.NODE_ENV !== "test") {
|
|
198
|
+
// "0.0.0.0" listens on every interface, which Traefik needs inside the container network.
|
|
199
|
+
server.listen(port, "0.0.0.0", () => {
|
|
200
|
+
console.log(\`\\n\u{1F680} \${name} running on http://localhost:\${port}\`);
|
|
201
|
+
console.log(\`\u{1F4CA} Health check: http://localhost:\${port}/health\\n\`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"tests/health.test.js",content:`import assert from "node:assert/strict";
|
|
205
|
+
import { after, before, describe, it } from "node:test";
|
|
206
|
+
import { server } from "../src/index.js";
|
|
207
|
+
|
|
208
|
+
describe("health check", () => {
|
|
209
|
+
let baseUrl;
|
|
210
|
+
|
|
211
|
+
before((_, done) => {
|
|
212
|
+
server.listen(0, "127.0.0.1", () => {
|
|
213
|
+
const address = server.address();
|
|
214
|
+
baseUrl = \`http://127.0.0.1:\${address.port}\`;
|
|
215
|
+
done();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
after((_, done) => {
|
|
220
|
+
server.close(done);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("GET /health returns 200 with ok status", async () => {
|
|
224
|
+
const res = await fetch(\`\${baseUrl}/health\`);
|
|
225
|
+
assert.equal(res.status, 200);
|
|
226
|
+
const body = await res.json();
|
|
227
|
+
assert.deepEqual(body, { status: "ok", service: "${t}" });
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
`,description:"Generating smoke test",emoji:"\u{1F9EA}"}]}};var re="uvicorn main:app --app-dir src --host 0.0.0.0 --port ${PORT:-4000} --reload --reload-dir src",q={id:"python",label:"Python 3.12 + FastAPI",devCommand:re,watch:["src/**/*"],installHint:'pip install -e ".[dev]" # optional: Docker builds the image for you',createFiles(t){return[{filename:"pyproject.toml",content:`[project]
|
|
231
|
+
name = "${t}"
|
|
232
|
+
version = "0.0.1"
|
|
233
|
+
requires-python = ">=3.12"
|
|
234
|
+
dependencies = [
|
|
235
|
+
"fastapi==0.115.6",
|
|
236
|
+
"uvicorn[standard]==0.34.0",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
[project.optional-dependencies]
|
|
240
|
+
dev = [
|
|
241
|
+
"pytest==9.0.3",
|
|
242
|
+
"httpx==0.28.1",
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
[build-system]
|
|
246
|
+
requires = ["setuptools>=68"]
|
|
247
|
+
build-backend = "setuptools.build_meta"
|
|
248
|
+
|
|
249
|
+
# Only the dependencies are installed into the image; the source is synced for live reload.
|
|
250
|
+
[tool.setuptools]
|
|
251
|
+
py-modules = []
|
|
252
|
+
|
|
253
|
+
[tool.pytest.ini_options]
|
|
254
|
+
pythonpath = ["src"]
|
|
255
|
+
testpaths = ["tests"]
|
|
256
|
+
`,description:"Generating pyproject.toml",emoji:"\u{1F4E6}"},{filename:"src/main.py",content:`import os
|
|
257
|
+
|
|
258
|
+
from fastapi import FastAPI
|
|
259
|
+
|
|
260
|
+
app = FastAPI(title="${t}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.get("/health")
|
|
264
|
+
def health() -> dict[str, str]:
|
|
265
|
+
return {"status": "ok", "service": "${t}"}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@app.get("/")
|
|
269
|
+
def root() -> dict[str, object]:
|
|
270
|
+
return {"service": "${t}", "version": "1.0.0", "endpoints": ["/health"]}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
import uvicorn
|
|
275
|
+
|
|
276
|
+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "3000")))
|
|
277
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"tests/test_health.py",content:`from fastapi.testclient import TestClient
|
|
278
|
+
|
|
279
|
+
from main import app
|
|
280
|
+
|
|
281
|
+
client = TestClient(app)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_health() -> None:
|
|
285
|
+
response = client.get("/health")
|
|
286
|
+
assert response.status_code == 200
|
|
287
|
+
assert response.json() == {"status": "ok", "service": "${t}"}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_root() -> None:
|
|
291
|
+
response = client.get("/")
|
|
292
|
+
assert response.status_code == 200
|
|
293
|
+
assert response.json() == {"service": "${t}", "version": "1.0.0", "endpoints": ["/health"]}
|
|
294
|
+
`,description:"Generating test file",emoji:"\u{1F9EA}"}]}};var oe="cargo run",H={id:"rust",label:"Rust (axum)",devCommand:oe,watch:["src/**/*.rs","Cargo.toml","Cargo.lock"],installHint:"cargo build # optional: Docker builds the image for you",createFiles(t){return[{filename:"Cargo.toml",content:`[package]
|
|
295
|
+
name = "${t}"
|
|
296
|
+
version = "0.1.0"
|
|
297
|
+
edition = "2021"
|
|
298
|
+
|
|
299
|
+
# The image copies the release binary by this fixed name.
|
|
300
|
+
[[bin]]
|
|
301
|
+
name = "app"
|
|
302
|
+
path = "src/main.rs"
|
|
303
|
+
|
|
304
|
+
[dependencies]
|
|
305
|
+
axum = "0.8"
|
|
306
|
+
serde_json = "1"
|
|
307
|
+
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
|
|
308
|
+
`,description:"Generating Cargo.toml",emoji:"\u{1F4E6}"},{filename:"src/main.rs",content:`use axum::{routing::get, Json, Router};
|
|
309
|
+
use serde_json::{json, Value};
|
|
310
|
+
|
|
311
|
+
fn app() -> Router {
|
|
312
|
+
Router::new()
|
|
313
|
+
.route("/", get(root))
|
|
314
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
315
|
+
.route("/health", get(health))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async fn root() -> Json<Value> {
|
|
319
|
+
Json(json!({ "service": "${t}", "version": "1.0.0", "endpoints": ["/health"] }))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async fn health() -> Json<Value> {
|
|
323
|
+
Json(json!({ "status": "ok", "service": "${t}" }))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#[tokio::main]
|
|
327
|
+
async fn main() {
|
|
328
|
+
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
|
329
|
+
|
|
330
|
+
// 0.0.0.0 listens on every interface, which Traefik needs inside the container network.
|
|
331
|
+
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
|
|
332
|
+
.await
|
|
333
|
+
.expect("failed to bind the port");
|
|
334
|
+
println!("${t} listening on 0.0.0.0:{port}");
|
|
335
|
+
axum::serve(listener, app()).await.expect("server error");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[cfg(test)]
|
|
339
|
+
mod tests {
|
|
340
|
+
use super::*;
|
|
341
|
+
|
|
342
|
+
#[tokio::test]
|
|
343
|
+
async fn health_reports_ok() {
|
|
344
|
+
let Json(body) = health().await;
|
|
345
|
+
assert_eq!(body["status"], "ok");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
`,description:"Generating backend source and smoke test",emoji:"\u{1F4BB}"}]}};var J="bun",h={bun:G,python:q,go:L,rust:H,node:B};function ae(t){let e=(t??J).trim().toLowerCase(),n=Object.hasOwn(h,e)?h[e]:void 0;if(!n){let s=Object.keys(h).join(", ");throw new O(`Unknown backend language "${t}". Supported languages: ${s}.`,[`Use one of: ${s}`,`Omit --language to use ${J}`])}return n}function qe(t,e){if(t!=="backend"){if(e!==void 0)throw new O("--language can only be used with --type backend.",["Add --type backend, or drop --language for this resource type"]);return}return ae(e)}import{exec as ie,execFile as ce}from"node:child_process";import{promisify as W}from"node:util";var pe=W(ie),z=async(t,e)=>{if(process.platform==="win32"){let s=t.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)??[],[o,...r]=s.map(c=>c.startsWith('"')||c.startsWith("'")?c.slice(1,-1):c);if(!o)throw new Error("Command is empty");let a=M(o)??o,{stdout:i}=await W(ce)(a,r,{encoding:"utf-8",timeout:e,windowsHide:!0,shell:!1});return i}let{stdout:n}=await pe(t,{encoding:"utf-8",timeout:e});return n};function Ke(t){let e=t;return e?.code==="ETIMEDOUT"||e?.killed===!0}var x=1e4,fe=/\.(?:[cm]?[jt]sx?|vue)$/,he=/(?:\.test\.|\.spec\.|\/tests?\/|\/__tests__\/)/,ge=7,ke=new Set(["node_modules",E,"dist","tests","__tests__"]);function Y(t){try{let e=JSON.parse(y(u(t,".tdk",F),"utf-8"));return typeof e?.project?.name=="string"?e.project.name:void 0}catch{return}}function ye(t){try{let e=JSON.parse(y(t,"utf-8")).params;return e&&typeof e=="object"?e:{}}catch{return{}}}function A(t){try{let e=JSON.parse(y(u(t,P),"utf-8"));return e&&typeof e=="object"?e:{}}catch{return{}}}function K(t){if(typeof t!="string")return;let e=t.match(/(?:^|[^0-9])(\d+)(?:\.|$)/);return e?Number(e[1]):void 0}function be(t){let e=S(t);if(e===void 0)return["missing prisma/schema.prisma"];let n=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,""),s=[],o=n.match(/datasource\s+\w+\s*\{([^}]*)\}/)?.[1]??"";return/provider\s*=\s*["']postgresql["']/.test(o)||s.push("schema provider is not postgresql"),/\b(?:url|directUrl)\s*=/.test(n)&&s.push("schema still declares url/directUrl"),s}function we(t){let e=u(t,E,"prisma.config.autogenerated.ts"),s=[u(t,"prisma.config.ts"),e].find(a=>R(a));if(!s)return["missing prisma.config.ts or generated Prisma config"];let r=(S(s)??"").match(/datasource\s*:\s*\{([^}]*)\}/)?.[1]??"";return/url\s*:\s*(?:env\(["']DATABASE_URL["']\)|process\.env\.DATABASE_URL)\s*,?/.test(r)?[]:['Prisma config datasource.url is not env("DATABASE_URL")']}function it(t=l()??process.cwd(),e=f(t)){let n=e.filter(r=>r.config?.featuresEnabled?.includes("prisma")),s=[],o=e.filter(r=>r.config?.appType==="migrator");for(let r of e){let a=r.config?.features;Array.isArray(a)&&a.includes("prisma")&&!r.config?.featuresEnabled?.includes("prisma")&&s.push(`${r.name}: use featuresEnabled, not features, for Prisma`)}if(n.length===0)return s.length===0?{name:"Prisma 7 consistency",didPass:!0,isSkipped:!0,message:"No resources enable Prisma; Prisma consistency checks were skipped"}:{name:"Prisma 7 consistency",didPass:!1,message:`${m(s.length,"Prisma consistency error")}:
|
|
349
|
+
${s.join(`
|
|
350
|
+
`)}`,fix:"Use featuresEnabled to opt into the validated Prisma 7 shape"};for(let r of n){let a=A(r.path).dependencies,c={...A(r.path).devDependencies??{},...a??{}},d=K(c.prisma),p=K(c["@prisma/client"]);if(d===void 0||p===void 0?s.push(`${r.name}: prisma and @prisma/client are required`):(d!==p||d!==ge)&&s.push(`${r.name}: prisma packages must both be major 7`),s.push(...be(u(r.path,"prisma","schema.prisma")).map(g=>`${r.name}: ${g}`)),s.push(...we(r.path).map(g=>`${r.name}: ${g}`)),(r.config?.appType??r.type)==="migrator")r.config?.dependsOn?.some(w=>w==="postgres"||w===C)||s.push(`${r.name}: migrator must depend on postgres or database-management`);else{r.config?.dependsOn?.some(_=>o.some(v=>v.name===_))||s.push(`${r.name}: API must depend on its Prisma migrator, which depends on postgres or database-management`);let w=A(r.path).scripts;Object.entries(w??{}).some(([_,v])=>ee.has(_)&&typeof v=="string"&&/prisma\s+migrate/.test(v))&&s.push(`${r.name}: API start script must not run prisma migrate`);let te=u(r.path,r.config?.dockerfile??"Dockerfile");j(te,!0)&&s.push(`${r.name}: API image/startup must not run Prisma migrations`)}}return n.length>0&&!N(t,e).willStart&&s.push("shared platform Postgres will not start for the Prisma resources"),s.length===0?{name:"Prisma 7 consistency",didPass:!0,message:"Prisma resources use the validated Prisma 7 shape"}:{name:"Prisma 7 consistency",didPass:!1,message:`${m(s.length,"Prisma consistency error")}:
|
|
351
|
+
${s.join(`
|
|
352
|
+
`)}`,fix:"Use Prisma 7 packages and config, put migrations in a migrator, and make the API depend on that migrator"}}function X(t){let e=new Map;for(let n of t){let s=n.config?.port;T(n.config?.appType)&&typeof s=="number"&&e.set(n.name,s)}return e}function ve(t){let e=t.config?.language;return typeof e=="string"&&Object.hasOwn(h,e)&&h[e].createFiles!==void 0}function $e(t){return(t.config?.appType??t.type)===$}function ct(t=l()??process.cwd()){let e=f(t).filter(n=>!$e(n)&&!ve(n)&&!R(u(n.path,P))).map(n=>n.name);return e.length===0?{name:"Resource package.json",didPass:!0,message:"Every resource has a package.json"}:{name:"Resource package.json",didPass:!1,message:`${m(e.length,"resource")} without a package.json: ${e.join(", ")}. The image build stops at "COPY .../package.json: not found"`,fix:"Add a package.json next to each service.json (copy one from a working resource), or delete the folder's service.json if it is not a service"}}function pt(t=l()??process.cwd(),e=f(t)){let n=new Map;for(let r of e){let a=n.get(r.name)??[];a.push(k(t,r.configPath)||r.configPath),n.set(r.name,a)}let s=[...n].filter(([,r])=>r.length>1);if(s.length===0)return{name:"Duplicate resource names",didPass:!0,message:"Every resource has a unique appName"};let o=s.map(([r,a])=>`"${r}" in ${a.join(" and ")}`);return{name:"Duplicate resource names",didPass:!1,message:`${m(s.length,"appName")} used by more than one resource:
|
|
353
|
+
${o.join(`
|
|
354
|
+
`)}`,fix:'Give each resource its own "appName" in its service.json. The name becomes the Compose service and the Traefik router, so two resources with one name collide'}}function Pe(t){let e=t.config?.appType;return T(e)?!0:e===$&&t.config?.exposeViaProxy!==!1}function dt(t=l()??process.cwd()){let e=new Map;for(let o of f(t)){let r=o.config?.port;if(typeof r!="number"||!Pe(o))continue;let a=e.get(r)??[];a.push(o.name),e.set(r,a)}let n=[...e].filter(([,o])=>o.length>1);if(n.length===0)return{name:"Duplicate resource ports",didPass:!0,message:"No two routed resources use the same port"};let s=n.map(([o,r])=>`port ${o}: ${r.join(", ")}`);return{name:"Duplicate resource ports",didPass:!1,isWarning:!0,message:`${m(n.length,"port")} used by more than one routed resource:
|
|
355
|
+
${s.join(`
|
|
356
|
+
`)}`,fix:'Usually a copied service.json. Each container can listen on the same port, but pick distinct "port" values (flisk resource assigns free ones) so URLs like http://<resource>:<port> and localhost checks stay unambiguous'}}var Te=/https?:\/\/([a-z][a-z0-9-]*):(\d+)/g;function ut(t=l()??process.cwd()){let e=f(t),n=X(e),s=new Map([...n].map(([r,a])=>[a,r])),o=[];for(let r of e)for(let[a,i]of Object.entries(ye(r.configPath)))for(let[,c,d]of String(i).matchAll(Te)){let p=Number(d),b=n.get(c);b!==void 0&&b!==p?o.push(`${r.name} param ${a}: ${c}:${p}, but ${c} listens on ${b}`):(c==="localhost"||c==="127.0.0.1")&&s.has(p)&&s.get(p)!==r.name&&o.push(`${r.name} param ${a}: localhost:${p} is ${r.name} itself inside its container; ${s.get(p)} is reached as ${s.get(p)}:${p}`)}return o.length===0?{name:"Service URL ports",didPass:!0,message:"Service URLs in params point at the ports the services listen on"}:{name:"Service URL ports",didPass:!1,message:`${m(o.length,"service URL")} in params that cannot connect:
|
|
357
|
+
${o.join(`
|
|
358
|
+
`)}`,fix:'Flisk runs each backend on the "port" in its service.json, and containers reach each other by resource name (http://<resource>:<port>). Use that port in the URL'}}function D(t){let e=[],n;try{n=V(t,{withFileTypes:!0})}catch{return e}for(let s of n){let o=u(t,s.name);s.isDirectory()?ke.has(s.name)||e.push(...D(o)):fe.test(s.name)&&!he.test(o)&&e.push(o)}return e}var je=/(?:localhost|127\.0\.0\.1):(\d{2,5})\b/g,Q=/^\s*(?:\/\/|\/\*|\*|<!--|#)/;function lt(t=l()??process.cwd()){let e=new Map;for(let s of f(t)){let o=s.config;if(!(T(o?.appType)||o?.appType===$&&o.exposeViaProxy!==!1))continue;let a=me(s.path),i=o?.stack??le(a),c=`${a}\0${i}`,d=e.get(c)??{stack:i,resources:[]};d.resources.push(s),e.set(c,d)}let n=[];for(let{stack:s,resources:o}of e.values()){if(o.length<2)continue;let r=i=>!!(i.config?.traefik?.host||i.config?.traefik?.pathPrefix),a=o.filter(i=>!r(i)).map(i=>i.name);a.length>0&&n.push(`stack "${s}": ${a.sort().join(", ")} lose the shared /api/${s}-management route because ${o.length} backends share the stack; they are routed at /api/<name> only`);for(let i of["host","pathPrefix"]){let c=new Map;for(let d of o){let p=d.config?.traefik?.[i];p&&c.set(p,[...c.get(p)??[],d.name])}for(let[d,p]of c)p.length>1&&n.push(`stack "${s}": ${p.sort().join(" and ")} both set traefik.${i} "${d}", so Traefik cannot tell them apart`)}}return n.length===0?{name:"Stack API routes",didPass:!0,message:"No two backends in one service directory compete for one stack route"}:{name:"Stack API routes",didPass:!1,isWarning:!0,message:n.join(`
|
|
359
|
+
`),fix:'Call each backend at /api/<name>, move one backend to its own stack, or give each its own "traefik.pathPrefix" in service.json'}}function mt(t=l()??process.cwd()){let e=f(t),n=new Map([...X(e)].map(([r,a])=>[a,r])),s=[];for(let r of e.filter(a=>a.config?.appType==="frontend"))for(let a of D(u(r.path,"src")))y(a,"utf-8").split(`
|
|
360
|
+
`).forEach((i,c)=>{if(!Q.test(i))for(let[,d]of i.matchAll(je)){let p=n.get(Number(d));p&&s.push(`${k(t,a)}:${c+1} calls localhost:${d} (${p})`)}});if(s.length===0)return{name:"Frontend backend URLs",didPass:!0,message:"No frontend calls a backend through a localhost port"};let o=Y(t)??"<project>";return{name:"Frontend backend URLs",didPass:!1,message:`${m(s.length,"frontend call")} to a backend port that Flisk does not publish on localhost:
|
|
361
|
+
${s.join(`
|
|
362
|
+
`)}`,fix:`Backends are only reachable through Traefik: http://api.${o}.localhost/api/<name without -api>/... (for example catalog-api is /api/catalog). Use that URL, or the generated .autogenerated/env.ts`}}var Z=/\b(?:prisma\s+(?:migrate\s+(?:deploy|dev|resolve)|db\s+push)|drizzle-kit\s+(?:migrate|push)|knex\s+migrate:\w+|typeorm\s+migration:run|mikro-orm\s+migration:up|sequelize(?:-cli)?\s+db:migrate|dbmate\s+(?:up|migrate)|atlas\s+migrate\s+apply|alembic\s+upgrade)\b/,ee=new Set(["start","dev","serve","prestart","predev","preserve"]),xe=/\.(?:sh|bash)$/,Se=/^\s*(?:CMD|ENTRYPOINT)\b/i;function S(t){try{return y(t,"utf-8")}catch{return}}function _e(t){let e=S(u(t,P));if(e===void 0)return[];let n={};try{n=JSON.parse(e)?.scripts??{}}catch{return[]}return Object.entries(n).filter(([s,o])=>ee.has(s)&&typeof o=="string"&&Z.test(o)).map(([s])=>`package.json "${s}" script`)}function j(t,e){let n=S(t);return n===void 0?!1:n.split(`
|
|
363
|
+
`).some(s=>Z.test(s.replace(/["'`,[\]]+/g," "))&&!Q.test(s)&&(!e||Se.test(s)))}function ft(t=l()??process.cwd()){let e=[];for(let n of f(t)){let s=n.config?.appType??n.type;if(s!=="backend"&&s!=="worker")continue;let o=n.path;for(let i of _e(o))e.push(`${n.name}: ${i}`);let r=u(o,n.config?.dockerfile??"Dockerfile");j(r,!0)&&e.push(`${n.name}: ${k(t,r)} CMD or ENTRYPOINT`);let a=[];try{a=V(o,{withFileTypes:!0})}catch{}for(let i of a){let c=u(o,i.name);i.isFile()&&xe.test(i.name)&&j(c,!1)&&e.push(`${n.name}: ${k(t,c)}`)}for(let i of D(u(o,"src")))j(i,!1)&&e.push(`${n.name}: ${k(t,i)}`)}return e.length===0?{name:"Migrations outside the API",didPass:!0,message:"No backend or worker runs a migration command from its own start-up"}:{name:"Migrations outside the API",didPass:!1,isWarning:!0,message:`${m(e.length,"migration command")} run from a backend or worker's own start-up:
|
|
364
|
+
${e.join(`
|
|
365
|
+
`)}`,fix:"Move the migration into a `migrator` resource (appType migrator) that the service depends on, so it runs once before the service starts instead of on every start and restart"}}function ht(t=l()??process.cwd()){let e=f(t).filter(s=>s.config?.featuresEnabled?.includes("nats")).map(s=>s.name);if(e.length===0)return{name:"NATS broker",didPass:!0,message:"No resource enables the nats feature",isSkipped:!0};if(R(u(t,"services","platform","messaging",U)))return{name:"NATS broker",didPass:!0,message:`${m(e.length,"resource")} enable nats and services/platform/messaging/docker-compose.yml is present`};let n=(Y(t)??"project").replace(/-/g,"_");return{name:"NATS broker",didPass:!1,message:`${e.join(", ")} enable the nats feature, but no NATS server will start: Flisk only starts one when services/platform/messaging/docker-compose.yml exists, and it does not generate that file`,fix:`Create it with a "nats" service (image nats with -js, container_name ${n}_nats, network ${n}_backend, external) and a "redis" service, which Flisk registers alongside it`}}function gt(t=l()??process.cwd()){let e=N(t),n=" (project resource set; flisk up may select a subset and not start Postgres)",s=e.focusWouldEnableDatabaseManagement?" Warning: generated Tiltfile default focus expands CORE_INFRA to database-management while project.json has it off; default flisk up takes the feature path.":"";if(e.unknownDependsOnNames.length>0){let r=e.unknownDependsOnNames.map(({resource:i,name:c})=>`"${c}" (from ${i})`).join(", "),a=e.willStart?` Postgres will start because ${e.reason==="feature"?"database-management is enabled":`resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`}${n}`:" Postgres will not start for this project.json/generated Tiltfile state.";return{name:"Shared platform Postgres",didPass:!1,message:`${m(e.unknownDependsOnNames.length,"unknown dependsOn name")}: ${r}. These are not known services or stacks, and are not the shared platform database names (postgres, database-management).${a}${s}`,fix:'Use "postgres" or "database-management" in dependsOn for the shared platform database, or fix the name to match an existing service'}}if(!e.willStart)return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,isSkipped:!s,message:s?s.trim():"Shared platform Postgres will not start: database-management is off and no resource depends on postgres or database-management."};if(e.reason==="feature"&&e.dependsOnUsers.length===0){let r=e.featureOnFromTiltfile||e.featureOnFromSpecMaster?" (generated Tiltfile/spec.master)":"";return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,message:`Shared platform Postgres will start because the database-management feature is enabled${r} (project-wide; not a flisk up selection).${s}`}}let o=e.reason==="feature"?`the database-management feature is enabled and resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`:`resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`;return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,message:`Shared platform Postgres will start because ${o}${n}${s}`}}function Ee(t){let e=[];for(let n of t.split(`
|
|
366
|
+
`)){let s=n.match(/^\s*(\d+)\s+.*\btilt up\b.*?\s-f\s+(\S+)/i);s&&e.push({pid:Number(s[1]),root:s[2].split(/[\\/]\.tdk[\\/]/i)[0]})}return e}function kt(t=l()??process.cwd(),e=ue){let n;try{n=process.platform==="win32"?de("powershell.exe",["-NoProfile","-Command","Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'tilt.exe' } | ForEach-Object { '{0} {1}' -f $_.ProcessId, $_.CommandLine }"],{encoding:"utf-8",timeout:x,windowsHide:!0}):e("ps -Ao pid=,command=",{encoding:"utf-8",timeout:x})}catch{return{name:"Tilt processes",didPass:!0,isSkipped:!0,message:"Could not list processes"}}let s=Ee(n),o=s.filter(a=>a.root===t),r=s.filter(a=>a.root!==t);return o.length>1?{name:"Tilt processes",didPass:!1,message:`${o.length} Tilt processes are running for this project (pids ${o.map(a=>a.pid).join(", ")}). They fight over ports and containers`,fix:`Stop them all, then start one: flisk down && kill ${o.map(a=>a.pid).join(" ")} && flisk up`}:r.length>0?{name:"Tilt processes",didPass:!0,isSkipped:!0,message:`Another project's Tilt is running: ${r.map(i=>`${i.root} (pid ${i.pid})`).join(", ")}`,fix:"flisk up will use the next free Tilt UI port (10351, ...). If it is a leftover, run flisk down in that project"}:{name:"Tilt processes",didPass:!0,message:"No stray Tilt processes"}}async function yt(t=z){let e=`tdk_doctor_probe_${process.pid}`;try{await t(`docker network create ${e}`,x)}catch(n){let s=String(n.stderr??n);return/address pools/i.test(s)?{name:"Docker networks",didPass:!1,message:"Docker cannot create another network: all predefined address pools are in use. Each Flisk project needs several",fix:"run flisk down --prune-networks in projects you are not using, or docker network prune (removes all unused networks)"}:{name:"Docker networks",didPass:!0,isSkipped:!0,message:"Could not test network creation"}}try{await t(`docker network rm ${e}`,x)}catch{}return{name:"Docker networks",didPass:!0,message:"Docker can create networks"}}export{Oe as a,Ne as b,h as c,ae as d,qe as e,z as f,Ke as g,it as h,ct as i,pt as j,dt as k,ut as l,lt as m,mt as n,ft as o,ht as p,gt as q,Ee as r,kt as s,yt as t};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{a as x}from"./chunk-DTIHDMED.js";import{g as w}from"./chunk-EICMGZXL.js";import{a as y}from"./chunk-GEXOZCEQ.js";import{Ra as h,ba as d,ca as u,d as l,k as f,ra as m,s as p,ta as g}from"./chunk-LMHKXU2Q.js";import{b as a}from"./chunk-CF46CD6R.js";import{Command as O}from"commander";import{readFileSync as $}from"node:fs";import{join as j}from"node:path";function A(n){let e=n.config;return e?Array.isArray(e.featuresEnabled)&&e.featuresEnabled.includes(l)?!0:(Array.isArray(e.dependsOn)?e.dependsOn:[]).some(t=>y.includes(t)):!1}function H(n,e){let i=n.replace(/-/g,"_"),t=[];for(let o of[...e].sort((r,s)=>r.name.localeCompare(s.name))){let r=o.resources.filter(A).map(s=>s.name);r.length!==0&&t.push({name:`${i}_${o.name.replace(/-/g,"_")}`,stack:o.name,services:r.sort()})}return t.length===0?null:{name:"postgres",databases:t}}function k(n){try{let e=JSON.parse($(j(n,".tdk",f),"utf-8"));return typeof e?.project?.name=="string"?e.project.name:null}catch{return null}}import{spawn as P}from"node:child_process";function N(n){try{let e=new URL(n);return e.protocol==="http:"||e.protocol==="https:"}catch{return!1}}function S(n,e=process.platform,i=P){if(!N(n))return Promise.resolve(!1);let t,o;return e==="darwin"?(t="open",o=[n]):e==="win32"?(t="rundll32.exe",o=["url.dll,FileProtocolHandler",n]):(t="xdg-open",o=[n]),new Promise(r=>{try{let s=i(t,o,{stdio:"ignore",detached:!0,windowsHide:!0});s.once("error",()=>r(!1)),s.once("spawn",()=>{typeof s.unref=="function"&&s.unref(),r(!0)})}catch{r(!1)}})}function B(n,e=process.platform,i=P){return new Promise(t=>{let o,r;e==="darwin"?(o="pbcopy",r=[]):e==="win32"?(o="clip",r=[]):(o="xclip",r=["-selection","clipboard"]);try{let s=i(o,r,{stdio:["pipe","ignore","ignore"],windowsHide:!0});s.on("error",()=>{t(!1)}),s.on("close",c=>{t(c===0)}),s.stdin?(s.stdin.on("error",()=>{t(!1)}),s.stdin.write(n,"utf-8"),s.stdin.end()):t(!1)}catch{t(!1)}})}async function E(n){let{resources:e}=h(n),i=await w(n,{inspectDocker:!1}),{appBase:t}=m(i?.ingressHttp,n);return e.filter(o=>o.config?.appType==="frontend").map(o=>({name:o.name,url:`${t}${g(o)}`}))}async function b(n,e=2e3){try{return(await fetch(n,{signal:AbortSignal.timeout(e)})).status<500}catch{return!1}}async function T(n,e=3e4,i){if(n.length===0)return;let t=Date.now(),o=t+e;for(;;){for(let s of n)if(await b(s.url))return s;let r=Date.now()-t;if(r>=e)return;i?.(r),await new Promise(s=>setTimeout(s,2e3))}}async function Q(n,e=0,i){let t=await E(n);return T(t,e,i)}async function F(n){let e=n??p();if(!e)return console.log(a.yellow("Not in a Flisk project, so there is no frontend to open.")),console.log(a.gray("Run `flisk project --yes` first, or `flisk open` inside a project.")),null;let i=await E(e);if(i.length===0)return console.log(a.yellow("This project has no frontend service to open.")),console.log(a.gray("Start the stack with `flisk up` and add a frontend with `flisk resource`.")),null;let t=k(e);d(`Opening the first frontend of ${t??"this project"}`);for(let c of i)console.log(a.gray(` - ${c.name}: ${c.url}`));let o=x("Waiting for the frontend to answer..."),r=await T(i,12e4,c=>{o.text=`Waiting for the frontend to answer... ${Math.round(c/1e3)}s`});if(!r){o.fail("No frontend answered yet."),console.log(a.yellow("The stack is not serving the frontend yet.")),console.log(a.gray(" - Start the stack in another terminal: flisk up")),console.log(a.gray(" - Then run this again: flisk open"));for(let c of i)console.log(a.gray(` - ${c.name}: ${c.url}`));return null}o.succeed(`The frontend is up: ${r.name}`);let s=await S(r.url);return console.log(s?a.green(`
|
|
3
|
+
Opened ${r.url}`):a.yellow(`
|
|
4
|
+
Could not open a browser. Open ${r.url} yourself.`)),u("Run `flisk open` again to open the next frontend."),r.url}var X=new O("open").description("Open the first frontend of the project in your browser").action(async()=>{await F()});export{H as a,k as b,S as c,B as d,E as e,b as f,T as g,Q as h,F as i,X as j};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{r as c}from"./chunk-RMZQU74X.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};
|