@tdk-landscape/tdk-cli-core 1.3.140 → 1.3.142

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/CHANGELOG.md CHANGED
@@ -4,6 +4,7 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - TDK MCP tools now accept an absolute `projectPath` so a projectless MCP client can run doctor, inspect services and logs, and manage the intended project without depending on the server launch directory.
7
8
  - Shared platform stacks (postgres, traefik, proxy, messaging, infisical, monitoring, cdc, elk) now run in Compose projects named `<project>-<stack>` (for example `tdk-erp-system-database-management`) instead of the directory-named `database-management`, `tdk-out` and so on. Two TDK projects on one Docker daemon no longer share them.
8
9
 
9
10
  - Two TDK projects that each have a stack with the same name (for example `people`) no longer share Docker names. Compose projects are now `<project>-<stack>-autogenerated`, containers `<project>-<stack>-autogenerated-<service>-1` and images `<project>_<stack>_<service>:dev` (migrators `<project>_<stack>-db-migrator:dev`). Before, the second project's `tdk up` removed the first project's containers as orphans and overwrote its image. Containers from before this change keep the old names and are not removed by the new engine: stop them once, see [docs/layout.md](docs/layout.md).
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # TDK CLI — start services on your laptop
1
+ # TDK CLI — start services on your machine
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@tdk-landscape/tdk-cli-core.svg?style=flat&color=blue)](https://www.npmjs.com/package/@tdk-landscape/tdk-cli-core)
4
4
  [![unpacked size](https://img.shields.io/npm/unpacked-size/@tdk-landscape/tdk-cli-core)](https://packagephobia.com/result?p=@tdk-landscape/tdk-cli-core)
@@ -29,7 +29,7 @@ No vendor lock-in. No tedious plumbing. Run a single command to natively orchest
29
29
  * **Messaging & Gateway:** NATS, Traefik
30
30
 
31
31
 
32
- TDK CLI — start services on your laptop. It is not a deploy and not a Compose file: define each service in `service.json`, then run `tdk up`. No Kubernetes is needed on the machine.
32
+ TDK CLI — start services on your machine. It is not a deploy and not a Compose file: define each service in `service.json`, then run `tdk up`. No Kubernetes is needed on the machine.
33
33
 
34
34
  Stability: 1.x local dev. Write `service.json`; TDK generates the local runtime files. See [what TDK writes](docs/generated-files.md) and how to check for drift. Core CLI is MIT and needs no key. Premium is optional.
35
35
 
@@ -3,7 +3,7 @@
3
3
  import { spawn } from "node:child_process";
4
4
  import { closeSync, existsSync, openSync, readFileSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
- import { join } from "node:path";
6
+ import { isAbsolute, join } from "node:path";
7
7
  /** A service or stack name. A leading "-" is rejected so a value can never be read as a flag. */
8
8
  const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
9
9
  const SINCE = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
@@ -21,6 +21,7 @@ export function cliInvocation(execPath = process.execPath, script = process.argv
21
21
  export const runTdkCli = (args, options = {}) => new Promise((resolve, reject) => {
22
22
  const [command, ...prefix] = cliInvocation();
23
23
  const child = spawn(command, [...prefix, ...args], {
24
+ cwd: options.cwd,
24
25
  stdio: ["ignore", "pipe", "pipe"],
25
26
  env: { ...process.env, NO_COLOR: "1" },
26
27
  });
@@ -96,10 +97,11 @@ export function toToolResult(run) {
96
97
  }
97
98
  }
98
99
  export const defaultUpDeps = {
99
- spawnUp(args, logFile) {
100
+ spawnUp(args, logFile, cwd) {
100
101
  const fd = openSync(logFile, "a");
101
102
  const [command, ...prefix] = cliInvocation();
102
103
  const child = spawn(command, [...prefix, ...args], {
104
+ cwd,
103
105
  detached: true,
104
106
  stdio: ["ignore", fd, fd],
105
107
  env: { ...process.env, NO_COLOR: "1" },
@@ -140,7 +142,7 @@ export function buildUpArgs(args) {
140
142
  export async function startUp(args, deps = defaultUpDeps) {
141
143
  const argv = buildUpArgs(args);
142
144
  const logFile = deps.logFile();
143
- const child = deps.spawnUp(argv, logFile);
145
+ const child = deps.spawnUp(argv, logFile, projectPath(args.projectPath));
144
146
  let exited;
145
147
  child.once("exit", (code) => {
146
148
  exited = code;
@@ -203,13 +205,26 @@ const OBJECT = (properties) => ({
203
205
  properties,
204
206
  additionalProperties: false,
205
207
  });
208
+ const PROJECT_PATH = {
209
+ type: "string",
210
+ description: "Absolute path to the TDK project root; run this operation from that directory.",
211
+ };
212
+ function projectPath(value) {
213
+ if (value === undefined)
214
+ return undefined;
215
+ if (typeof value !== "string" || !isAbsolute(value)) {
216
+ throw new Error("projectPath must be an absolute path to a TDK project root");
217
+ }
218
+ return value;
219
+ }
206
220
  export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
207
- const viaCli = (build) => async (args) => toToolResult(await run(build(args)));
221
+ const viaCli = (build) => async (args) => toToolResult(await run(build(args), { cwd: projectPath(args.projectPath) }));
208
222
  return [
209
223
  {
210
224
  name: "doctor",
211
- description: "Check whether this machine can run the TDK stack. Returns data.ready and data.host.canUp; run it before `up`.",
225
+ description: "Check whether this machine can run the TDK stack. Optionally set projectPath to include checks for a specific TDK project. Returns data.ready and data.host.canUp; run it before `up`.",
212
226
  inputSchema: OBJECT({
227
+ projectPath: PROJECT_PATH,
213
228
  noPing: { type: "boolean", description: "Skip pinging running services' health endpoints" },
214
229
  }),
215
230
  handler: viaCli((args) => [
@@ -220,8 +235,9 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
220
235
  },
221
236
  {
222
237
  name: "up",
223
- description: "Start the stack (or part of it) detached and return without waiting for it to be ready. Poll `status` for data.tilt.readiness.ready. Fails fast on an unknown service, an unsupported host, or a Tilt already running.",
238
+ description: "Start the stack (or part of it) detached from the optional projectPath and return without waiting for it to be ready. Poll `status` for data.tilt.readiness.ready. Fails fast on an unknown service, an unsupported host, or a Tilt already running.",
224
239
  inputSchema: OBJECT({
240
+ projectPath: PROJECT_PATH,
225
241
  stack: { type: "string", description: "Only this stack" },
226
242
  only: {
227
243
  type: "array",
@@ -238,20 +254,21 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
238
254
  },
239
255
  {
240
256
  name: "down",
241
- description: "Stop the running stack.",
242
- inputSchema: OBJECT({}),
257
+ description: "Stop the running stack, optionally selecting its project with projectPath.",
258
+ inputSchema: OBJECT({ projectPath: PROJECT_PATH }),
243
259
  handler: viaCli(() => ["down", "--json"]),
244
260
  },
245
261
  {
246
262
  name: "status",
247
- description: "Stacks, resources, their ingress URLs and container ports, the stack-level ports, and Tilt readiness (data.tilt.readiness: ready, pending, failures, enabled; null when no Tilt answers).",
248
- inputSchema: OBJECT({}),
263
+ description: "Stacks, resources, their ingress URLs and container ports, the stack-level ports, and Tilt readiness (data.tilt.readiness: ready, pending, failures, enabled; null when no Tilt answers). Optionally select the project with projectPath.",
264
+ inputSchema: OBJECT({ projectPath: PROJECT_PATH }),
249
265
  handler: viaCli(() => ["status", "--json"]),
250
266
  },
251
267
  {
252
268
  name: "logs",
253
- description: "A bounded snapshot of recent logs from the running stack (default the last 200 lines, at most 10000). Not a stream.",
269
+ description: "A bounded snapshot of recent logs from the running stack (default the last 200 lines, at most 10000). Optionally select the project with projectPath. Not a stream.",
254
270
  inputSchema: OBJECT({
271
+ projectPath: PROJECT_PATH,
255
272
  services: { type: "array", items: { type: "string" }, description: "Only these services" },
256
273
  tail: { type: "integer", minimum: 1, maximum: 10000 },
257
274
  since: {
@@ -263,8 +280,11 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
263
280
  },
264
281
  {
265
282
  name: "resource_list",
266
- description: "List the project's services (resources) with their stack, type and port.",
267
- inputSchema: OBJECT({ stack: { type: "string", description: "Only this stack" } }),
283
+ description: "List the selected project's services (resources) with their stack, type and port. Set projectPath when the MCP server is not running from the project root.",
284
+ inputSchema: OBJECT({
285
+ projectPath: PROJECT_PATH,
286
+ stack: { type: "string", description: "Only this stack" },
287
+ }),
268
288
  handler: viaCli((args) => {
269
289
  const stack = optionalName(args.stack, "stack");
270
290
  return ["resources", "--json", ...(stack ? [`--stack=${stack}`] : [])];
@@ -112,6 +112,8 @@ function readPath(value, path) {
112
112
  if (current === null || current === undefined)
113
113
  return undefined;
114
114
  // Own properties only: a path like "__proto__" must not reach inherited values.
115
+ if (part === "__proto__" || part === "constructor" || part === "prototype")
116
+ return undefined;
115
117
  if (!Object.hasOwn(current, part))
116
118
  return undefined;
117
119
  current = current[part];
@@ -9,11 +9,11 @@ To create a stack, use:`},services:{singular:"service",command:"tdk resource <na
9
9
  To create a service:`},"stack-services":{singular:"service",command:"tdk resource <name> --stack <stack-name>",context:`
10
10
  To add services to this stack:`}};function Pu(e,t){let n=uo[e];console.log(t?f.yellow(`No ${n.singular}s found${t}.`):f.yellow(`No ${n.singular}s found.`)),console.log(f.gray(n.context)),console.log(f.gray(` ${n.command}`)),e==="stacks"&&(console.log(f.gray(`
11
11
  Or create a new resource with a stack:`)),console.log(f.gray(" tdk resource <name> --stack <stack-name>")))}function vu(e){console.log(f.yellow(e||"Cancelled."))}function wu(e){console.log(f.blue(`TDK ${e}
12
- `))}function $u(e,t){console.log(f.green(`All ${e} are ${t}!`))}var pt=62;function Ru(e="\u2500",t=pt){return e.repeat(t)}function In(e,t=pt-2){let n=Math.max(0,t-e.length),r=Math.floor(n/2),s=n-r;return" ".repeat(r)+e+" ".repeat(s)}function _u(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e.padEnd(t)}function Ou(e,t){return e.length<=t?e:`${e.slice(0,t-3)}...`}function Au(e){console.log(f.green(`\u2713 ${e}`))}function Du(e){console.log(f.blue(e))}function Cu(e,t=2){console.log(f.gray(`${" ".repeat(t)}${e}`))}function Nu(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function ju(e,t,n=pt){let r=n-2,s="\u2500".repeat(r);console.log(),console.log(f.cyan(`\u256D${s}\u256E`)),console.log(f.cyan("\u2502")+f.bold.white(In(e,r))+f.cyan("\u2502")),t&&(console.log(f.cyan(`\u251C${s}\u2524`)),console.log(f.cyan("\u2502")+f.gray(In(t,r))+f.cyan("\u2502"))),console.log(f.cyan(`\u2570${s}\u256F`))}import{existsSync as po}from"node:fs";import{dirname as fo,join as mo,resolve as Mn}from"node:path";import{cwd as ho}from"node:process";var Fn={name:"tdk-cli-internal",private:!0,version:"1.3.140",description:"TDK CLI \u2014 start services on your laptop.",keywords:["tdk","tilt","cli","local-dev","microservices"],homepage:"https://github.com/tdk-landscape/tdk-cli-core#readme",bugs:{url:"https://github.com/tdk-landscape/tdk-cli-core/issues"},repository:{type:"git",url:"git+https://github.com/tdk-landscape/tdk-cli-core.git",directory:"cli"},license:"MIT",author:"TDK Team",type:"module",main:"./dist/index.js",types:"./dist/index.d.ts",bin:{tdk:"bin/tdk.js","tdk-cli":"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",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",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",ajv:"^8.17.1",chalk:"^6.0.1",esbuild:"^0.28.2",ink:"^7.1.1",knip:"^6.38.0",react:"^19.3.0","react-devtools-core":"^8.0.0",typescript:"^7.0.2",vite:"^8.3.1",vitest:"^5.0.0"},engines:{node:">=22.12.0"}};function m(e=ho()){let t=Mn(e),n=Mn("/");for(;t!==n;){if(po(mo(t,".tdk",E)))return t;let r=fo(t);if(r===t)break;t=r}return null}var Ce=null;function go(){if(Ce)return Ce;let e=Fn;if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Invalid package.json: expected object");let t=e;return Ce={name:String(t.name??"@tdk-landscape/tdk-cli-core"),version:String(t.version??"0.0.0"),fullPackage:t},Ce}function Ln(){return go().version}import{readFileSync as ko}from"node:fs";import{join as yo}from"node:path";function Ne(){let e=m();if(e)try{let t=ko(yo(e,".tdk",E),"utf-8"),n=JSON.parse(t);if(n?.project?.name)return n.project.name}catch{}return"tdk-project"}function ft(e){let t=Ne(),n=process.env.TDK_SERVICE_BASE_URL??`http://${t}.localhost${e?`:${e}`:""}`;try{let r=new URL(n.includes("://")?n:`http://${n}`),s=r.hostname,o=r.port||(e?String(e):"");if(s==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(s))return{appBase:`${r.protocol}//${s}${o?`:${o}`:""}`,apiBase:`${r.protocol}//${s}${o?`:${o}`:""}`};let i=s.replace(/^(app|api)\./,"");return{appBase:`${r.protocol}//app.${i}${o?`:${o}`:""}`,apiBase:`${r.protocol}//api.${i}${o?`:${o}`:""}`}}catch{return{appBase:`http://app.${t}.localhost`,apiBase:`http://api.${t}.localhost`}}}function So(e){return`${e.replace(/\/+$/,"")}/health`}function mt(e){if(w(e.config?.appType)){let t=e.name.replace(/-api$/,"");return e.config?.apiPath??`/api/${t}`}return e.config?.basePath??`/${e.name}`}function Bn(e,t){let{appBase:n,apiBase:r}=ft(t);return e.filter(s=>s.config?.appType==="frontend"||w(s.config?.appType)).map(s=>{let o=w(s.config.appType)?r:n;return{name:s.name,appType:s.config.appType,url:`${o}${So(mt(s))}`}})}async function bo(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 Un(e,t){return Promise.all(e.map(n=>bo(n,t)))}import{existsSync as ee,readdirSync as lr,readFileSync as pr,statSync as xt}from"node:fs";import{dirname as oi,join as J}from"node:path";var he=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 Hn(e){let t=0;return{isValid(){return Date.now()-t<e},markUpdated(){t=Date.now()},reset(){t=0}}}import{execFileSync as _o,execSync as Oo}from"node:child_process";import{existsSync as vo}from"node:fs";import{delimiter as wo,extname as $o,join as Ro}from"node:path";import{delimiter as Zu,extname as To}from"node:path";function je(){return process.platform==="win32"}function xo(e){return e==="linux"?"linux":e==="darwin"?"darwin":e==="win32"?"windows":null}function Eo(e){return e==="x64"?"amd64":e==="arm64"?"arm64":null}function Po(e,t){let n=xo(e),r=Eo(t);if(!n||!r||n==="windows"&&r!=="amd64")return null;let s=`tdk-${n}-${r}`;return n==="windows"?`${s}.exe`:s}function Qu(){return Po(process.platform,process.arch)}function ed(){return je()?"tdk.exe":"tdk"}function Vn(e,t=je()){return t?To(e)?[e]:[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e]}function $(e){let t=process.env.PATH??process.env.Path??"",n=je(),r=Vn(e,n);if(n&&!$o(e))for(let s of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let o=s.toLowerCase();o&&!r.includes(`${e}${o}`)&&r.push(`${e}${o}`)}for(let s of t.split(wo))if(s)for(let o of r){let i=Ro(s,o);if(vo(i))return i}return null}var Gn=1e4;function Kn(){let e=!1;if(process.platform==="win32")try{return _o($("docker")??"docker",["ps"],{stdio:"ignore",timeout:Gn,windowsHide:!0}),"running"}catch(t){return t.code==="ETIMEDOUT"?"unresponsive":"missing"}for(let t of["docker ps","colima status","podman ps"])try{return Oo(t,{stdio:"ignore",timeout:Gn}),"running"}catch(n){n.code==="ETIMEDOUT"&&(e=!0)}return e?"unresponsive":"missing"}function Ie(e,t){return e[t]??0}function Ao(e,t){let n=Array.from({length:t.length+1},(r,s)=>s);for(let r=1;r<=e.length;r++){let s=[r];for(let o=1;o<=t.length;o++){let i=e[r-1]===t[o-1]?0:1;s[o]=Math.min(Ie(s,o-1)+1,Ie(n,o)+1,Ie(n,o-1)+i)}n=s}return Ie(n,t.length)}function Do(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 Wn(e,t){if(e.length<2||t.length===0||t.includes(e))return;let n=e.toLowerCase(),r,s=Number.POSITIVE_INFINITY;for(let o of t){if(o.length===0)continue;let i=o.toLowerCase(),a=Do(n,i)?1:Ao(n,i),c=Math.max(1,Math.min(2,Math.floor(Math.max(n.length,i.length)/3)));a>c||(a<s||a===s&&(r===void 0||o<r))&&(r=o,s=a)}return r}import{spawn as Co}from"node:child_process";import{join as No}from"node:path";function jo(e,t=[],n={}){return new Promise((r,s)=>{let o=[e,...t];n.verbose&&console.log(`Executing: tilt ${o.join(" ")}`);let i=Co($("tilt")??"tilt",o,{stdio:n.inheritStdio?"inherit":"pipe",shell:!1,windowsHide:process.platform==="win32",timeout:n.timeoutMs}),a="",c="";n.inheritStdio||(i.stdout?.on("data",u=>{a+=u.toString()}),i.stderr?.on("data",u=>{c+=u.toString()})),i.on("close",u=>{r({exitCode:u??1,stdout:a,stderr:c})}),i.on("error",u=>{s(new Error(`Failed to spawn tilt: ${u.message}`))})})}async function Jn(){try{return(await jo("version",[],{inheritStdio:!1,timeoutMs:1e4})).exitCode===0}catch{return!1}}function Io(){let e=m();if(!e)throw H.notInProject();return No(e,".tdk",".tdk-out","Tiltfile")}function qn(e){let t=Io();e.push("-f",t)}function hd(e,t={}){let n=[];return qn(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 gd(e={}){let t=[];return qn(t),e.force&&t.push("--force"),e.focusTargets&&e.focusTargets.length>0&&t.push("--",`--focus=${e.focusTargets.join(",")}`),t}function Yn(e){return e instanceof Error?e.message:String(e)}function Pd(e,t){if(process.env.TDK_VERBOSE){let n=t!==void 0?`: ${Yn(t)}`:"";console.warn(f.gray(`${e}${n}`))}}var R=class extends Error{suggestions;exitCode;constructor(t,n=[],r=1){super(t),this.name="TdkError",this.suggestions=n,this.exitCode=r}display(){Lo(this.message,void 0,this.suggestions)}exit(){this.display(),process.exit(this.exitCode)}};function ht(e,t,n=!1){let r=new Set;for(let s of e){let o=Wn(s,t);o&&r.add(n?`Did you mean "${o}" for "${s}"?`:`Did you mean "${o}"?`)}return[...r]}var H={tiltNotInstalled:()=>new R("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: ${ct}`]),dockerNotAvailable:()=>new R("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: ${ct}`]),dockerNotResponding:()=>new R("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 R(`Stack "${e}" not found`,[...ht([e],t),"Run `tdk stacks` to see available stacks","Run `tdk stack` to assign resources to a stack"]),resourceNotFound:(e,t=[])=>new R(`Resource "${e}" not found`,[...ht([e],t),"Run `tdk resources` to list all resources","Check the resource name spelling"]),unknownServices:(e,t)=>{let n=ht(e,t,e.length>1);return new R(`Unknown service ${e.join(", ")}. Valid names: ${t.join(", ")}`,n,2)},directoryExists:e=>new R(`Directory already exists: ${e}`,["Use `--path` to specify a different location","Remove the existing directory if no longer needed"]),invalidPath:e=>new R(`Invalid path: ${e}`,["Path must be within the project directory",'Path cannot contain special characters like <>:"|?*']),notInProject:()=>new R("Could not find project root (no .tdk/project.json found)",["Run this from within a TDK project","Run `tdk project --yes` to initialize a new project"])};function vd(){let e=m();return e||H.notInProject().exit()}function Fo(e){e instanceof R&&e.exit(),console.error(f.red(`Error: ${Yn(e)}`)),process.exit(1)}async function Mo(e,t){try{return await e()}catch(n){return t?.verbose&&n instanceof Error&&n.stack&&console.error(f.gray(n.stack)),Fo(n)}}async function wd(e,t){let n=[],r=Kn();return r==="unresponsive"?n.push(H.dockerNotResponding()):r==="missing"&&n.push(H.dockerNotAvailable()),await Jn()||n.push(H.tiltNotInstalled()),n.length>0&&(console.error(f.red(`
12
+ `))}function $u(e,t){console.log(f.green(`All ${e} are ${t}!`))}var pt=62;function Ru(e="\u2500",t=pt){return e.repeat(t)}function In(e,t=pt-2){let n=Math.max(0,t-e.length),r=Math.floor(n/2),s=n-r;return" ".repeat(r)+e+" ".repeat(s)}function _u(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e.padEnd(t)}function Ou(e,t){return e.length<=t?e:`${e.slice(0,t-3)}...`}function Au(e){console.log(f.green(`\u2713 ${e}`))}function Du(e){console.log(f.blue(e))}function Cu(e,t=2){console.log(f.gray(`${" ".repeat(t)}${e}`))}function Nu(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function ju(e,t,n=pt){let r=n-2,s="\u2500".repeat(r);console.log(),console.log(f.cyan(`\u256D${s}\u256E`)),console.log(f.cyan("\u2502")+f.bold.white(In(e,r))+f.cyan("\u2502")),t&&(console.log(f.cyan(`\u251C${s}\u2524`)),console.log(f.cyan("\u2502")+f.gray(In(t,r))+f.cyan("\u2502"))),console.log(f.cyan(`\u2570${s}\u256F`))}import{existsSync as po}from"node:fs";import{dirname as fo,join as mo,resolve as Mn}from"node:path";import{cwd as ho}from"node:process";var Fn={name:"tdk-cli-internal",private:!0,version:"1.3.142",description:"TDK CLI \u2014 start services on your laptop.",keywords:["tdk","tilt","cli","local-dev","microservices"],homepage:"https://github.com/tdk-landscape/tdk-cli-core#readme",bugs:{url:"https://github.com/tdk-landscape/tdk-cli-core/issues"},repository:{type:"git",url:"git+https://github.com/tdk-landscape/tdk-cli-core.git",directory:"cli"},license:"MIT",author:"TDK Team",type:"module",main:"./dist/index.js",types:"./dist/index.d.ts",bin:{tdk:"bin/tdk.js","tdk-cli":"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",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",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",ajv:"^8.17.1",chalk:"^6.0.1",esbuild:"^0.28.2",ink:"^7.1.1",knip:"^6.38.0",react:"^19.3.0","react-devtools-core":"^8.0.0",typescript:"^7.0.2",vite:"^8.3.1",vitest:"^5.0.0"},engines:{node:">=22.12.0"}};function m(e=ho()){let t=Mn(e),n=Mn("/");for(;t!==n;){if(po(mo(t,".tdk",E)))return t;let r=fo(t);if(r===t)break;t=r}return null}var Ce=null;function go(){if(Ce)return Ce;let e=Fn;if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Invalid package.json: expected object");let t=e;return Ce={name:String(t.name??"@tdk-landscape/tdk-cli-core"),version:String(t.version??"0.0.0"),fullPackage:t},Ce}function Ln(){return go().version}import{readFileSync as ko}from"node:fs";import{join as yo}from"node:path";function Ne(){let e=m();if(e)try{let t=ko(yo(e,".tdk",E),"utf-8"),n=JSON.parse(t);if(n?.project?.name)return n.project.name}catch{}return"tdk-project"}function ft(e){let t=Ne(),n=process.env.TDK_SERVICE_BASE_URL??`http://${t}.localhost${e?`:${e}`:""}`;try{let r=new URL(n.includes("://")?n:`http://${n}`),s=r.hostname,o=r.port||(e?String(e):"");if(s==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(s))return{appBase:`${r.protocol}//${s}${o?`:${o}`:""}`,apiBase:`${r.protocol}//${s}${o?`:${o}`:""}`};let i=s.replace(/^(app|api)\./,"");return{appBase:`${r.protocol}//app.${i}${o?`:${o}`:""}`,apiBase:`${r.protocol}//api.${i}${o?`:${o}`:""}`}}catch{return{appBase:`http://app.${t}.localhost`,apiBase:`http://api.${t}.localhost`}}}function So(e){return`${e.replace(/\/+$/,"")}/health`}function mt(e){if(w(e.config?.appType)){let t=e.name.replace(/-api$/,"");return e.config?.apiPath??`/api/${t}`}return e.config?.basePath??`/${e.name}`}function Bn(e,t){let{appBase:n,apiBase:r}=ft(t);return e.filter(s=>s.config?.appType==="frontend"||w(s.config?.appType)).map(s=>{let o=w(s.config.appType)?r:n;return{name:s.name,appType:s.config.appType,url:`${o}${So(mt(s))}`}})}async function bo(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 Un(e,t){return Promise.all(e.map(n=>bo(n,t)))}import{existsSync as ee,readdirSync as lr,readFileSync as pr,statSync as xt}from"node:fs";import{dirname as oi,join as J}from"node:path";var he=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 Hn(e){let t=0;return{isValid(){return Date.now()-t<e},markUpdated(){t=Date.now()},reset(){t=0}}}import{execFileSync as _o,execSync as Oo}from"node:child_process";import{existsSync as vo}from"node:fs";import{delimiter as wo,extname as $o,join as Ro}from"node:path";import{delimiter as Zu,extname as To}from"node:path";function je(){return process.platform==="win32"}function xo(e){return e==="linux"?"linux":e==="darwin"?"darwin":e==="win32"?"windows":null}function Eo(e){return e==="x64"?"amd64":e==="arm64"?"arm64":null}function Po(e,t){let n=xo(e),r=Eo(t);if(!n||!r||n==="windows"&&r!=="amd64")return null;let s=`tdk-${n}-${r}`;return n==="windows"?`${s}.exe`:s}function Qu(){return Po(process.platform,process.arch)}function ed(){return je()?"tdk.exe":"tdk"}function Vn(e,t=je()){return t?To(e)?[e]:[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e]}function $(e){let t=process.env.PATH??process.env.Path??"",n=je(),r=Vn(e,n);if(n&&!$o(e))for(let s of(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";")){let o=s.toLowerCase();o&&!r.includes(`${e}${o}`)&&r.push(`${e}${o}`)}for(let s of t.split(wo))if(s)for(let o of r){let i=Ro(s,o);if(vo(i))return i}return null}var Gn=1e4;function Kn(){let e=!1;if(process.platform==="win32")try{return _o($("docker")??"docker",["ps"],{stdio:"ignore",timeout:Gn,windowsHide:!0}),"running"}catch(t){return t.code==="ETIMEDOUT"?"unresponsive":"missing"}for(let t of["docker ps","colima status","podman ps"])try{return Oo(t,{stdio:"ignore",timeout:Gn}),"running"}catch(n){n.code==="ETIMEDOUT"&&(e=!0)}return e?"unresponsive":"missing"}function Ie(e,t){return e[t]??0}function Ao(e,t){let n=Array.from({length:t.length+1},(r,s)=>s);for(let r=1;r<=e.length;r++){let s=[r];for(let o=1;o<=t.length;o++){let i=e[r-1]===t[o-1]?0:1;s[o]=Math.min(Ie(s,o-1)+1,Ie(n,o)+1,Ie(n,o-1)+i)}n=s}return Ie(n,t.length)}function Do(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 Wn(e,t){if(e.length<2||t.length===0||t.includes(e))return;let n=e.toLowerCase(),r,s=Number.POSITIVE_INFINITY;for(let o of t){if(o.length===0)continue;let i=o.toLowerCase(),a=Do(n,i)?1:Ao(n,i),c=Math.max(1,Math.min(2,Math.floor(Math.max(n.length,i.length)/3)));a>c||(a<s||a===s&&(r===void 0||o<r))&&(r=o,s=a)}return r}import{spawn as Co}from"node:child_process";import{join as No}from"node:path";function jo(e,t=[],n={}){return new Promise((r,s)=>{let o=[e,...t];n.verbose&&console.log(`Executing: tilt ${o.join(" ")}`);let i=Co($("tilt")??"tilt",o,{stdio:n.inheritStdio?"inherit":"pipe",shell:!1,windowsHide:process.platform==="win32",timeout:n.timeoutMs}),a="",c="";n.inheritStdio||(i.stdout?.on("data",u=>{a+=u.toString()}),i.stderr?.on("data",u=>{c+=u.toString()})),i.on("close",u=>{r({exitCode:u??1,stdout:a,stderr:c})}),i.on("error",u=>{s(new Error(`Failed to spawn tilt: ${u.message}`))})})}async function Jn(){try{return(await jo("version",[],{inheritStdio:!1,timeoutMs:1e4})).exitCode===0}catch{return!1}}function Io(){let e=m();if(!e)throw H.notInProject();return No(e,".tdk",".tdk-out","Tiltfile")}function qn(e){let t=Io();e.push("-f",t)}function hd(e,t={}){let n=[];return qn(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 gd(e={}){let t=[];return qn(t),e.force&&t.push("--force"),e.focusTargets&&e.focusTargets.length>0&&t.push("--",`--focus=${e.focusTargets.join(",")}`),t}function Yn(e){return e instanceof Error?e.message:String(e)}function Pd(e,t){if(process.env.TDK_VERBOSE){let n=t!==void 0?`: ${Yn(t)}`:"";console.warn(f.gray(`${e}${n}`))}}var R=class extends Error{suggestions;exitCode;constructor(t,n=[],r=1){super(t),this.name="TdkError",this.suggestions=n,this.exitCode=r}display(){Lo(this.message,void 0,this.suggestions)}exit(){this.display(),process.exit(this.exitCode)}};function ht(e,t,n=!1){let r=new Set;for(let s of e){let o=Wn(s,t);o&&r.add(n?`Did you mean "${o}" for "${s}"?`:`Did you mean "${o}"?`)}return[...r]}var H={tiltNotInstalled:()=>new R("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: ${ct}`]),dockerNotAvailable:()=>new R("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: ${ct}`]),dockerNotResponding:()=>new R("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 R(`Stack "${e}" not found`,[...ht([e],t),"Run `tdk stacks` to see available stacks","Run `tdk stack` to assign resources to a stack"]),resourceNotFound:(e,t=[])=>new R(`Resource "${e}" not found`,[...ht([e],t),"Run `tdk resources` to list all resources","Check the resource name spelling"]),unknownServices:(e,t)=>{let n=ht(e,t,e.length>1);return new R(`Unknown service ${e.join(", ")}. Valid names: ${t.join(", ")}`,n,2)},directoryExists:e=>new R(`Directory already exists: ${e}`,["Use `--path` to specify a different location","Remove the existing directory if no longer needed"]),invalidPath:e=>new R(`Invalid path: ${e}`,["Path must be within the project directory",'Path cannot contain special characters like <>:"|?*']),notInProject:()=>new R("Could not find project root (no .tdk/project.json found)",["Run this from within a TDK project","Run `tdk project --yes` to initialize a new project"])};function vd(){let e=m();return e||H.notInProject().exit()}function Fo(e){e instanceof R&&e.exit(),console.error(f.red(`Error: ${Yn(e)}`)),process.exit(1)}async function Mo(e,t){try{return await e()}catch(n){return t?.verbose&&n instanceof Error&&n.stack&&console.error(f.gray(n.stack)),Fo(n)}}async function wd(e,t){let n=[],r=Kn();return r==="unresponsive"?n.push(H.dockerNotResponding()):r==="missing"&&n.push(H.dockerNotAvailable()),await Jn()||n.push(H.tiltNotInstalled()),n.length>0&&(console.error(f.red(`
13
13
  TDK can't start - missing prerequisites:
14
14
  `)),n.forEach((s,o)=>{o>0&&console.error(""),s.display()}),console.error(f.gray("\nRun `tdk doctor` for a full environment check.")),process.exit(1)),Mo(e,t)}function $d(e,t=1){console.error(f.red(`Error: ${e}`)),process.exit(t)}function Lo(e,t,n){if(console.error(f.red(`\u274C ${e}`)),t&&console.error(f.gray(` ${t}`)),n&&n.length>0){console.error(f.yellow(`
15
15
  \u{1F4A1} Suggestions:`));for(let r of n)console.error(f.cyan(` \u2192 ${r}`))}}function Rd(e,t){console.error(f.red(`
16
- tilt ${e} failed with exit code ${t}`)),process.exit(t)}import{existsSync as ir,readFileSync as ar}from"node:fs";import{copyFileSync as zn,mkdirSync as Bo,rmSync as Xn,writeFileSync as Zn}from"node:fs";import{join as Q}from"node:path";var Uo=60,Qn=1e3,er=1e4,tr=200,nr=64*1024,rr=new Set([404,502,503,504]),Ho=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]),Vo=new Set(["GET","HEAD","OPTIONS"]);function K(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Go(e,t){if(Vo.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"&&Ho.has(r)}function kt(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 s=`smoke.steps[${r}]`;if(!K(n)){t.push(`${s}: must be an object`);return}let o=n.path;(typeof o!="string"||!o.startsWith("/")||o.startsWith("//"))&&t.push(`${s}.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(`${s}.expect: must be an HTTP status between 100 and 599`),n.bodyContains!==void 0&&typeof n.bodyContains!="string"&&t.push(`${s}.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(`${s}.save: must map names to paths such as "$.id"`))}),t)}function jd(e,t){let{appBase:n,apiBase:r}=ft(t),s=[];for(let o of e){let i=o.config;if(!i?.smoke)continue;let a=i.appType,c=w(a)||a===B;if(!c&&a!=="frontend"||i.exposeViaProxy===!1)continue;let u=c?r:n,d=i.healthCheckPath||(c&&a!==B?"/health":void 0);s.push({name:o.name,baseUrl:`${u}${mt(o).replace(/\/+$/,"")}`,...d?.startsWith("/")?{readyPath:d}:{},smoke:i.smoke})}return s}function Ko(e,t){let n=t.replace(/^\$\.?/,"").match(/[^.[\]]+/g)??[],r=e;for(let s of n){if(r==null||!Object.hasOwn(r,s))return;r=r[s]}return r}function sr(e,t){return e.replace(/\{\{(\w+)\}\}/g,(n,r)=>t[r]??n)}function gt(e,t){return typeof e=="string"?sr(e,t):Array.isArray(e)?e.map(n=>gt(n,t)):K(e)?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,gt(r,t)])):e}function Fe(e){let t=e.replace(/\s+/g," ").trim();return t.length>tr?`${t.slice(0,tr)}...`:t}function or(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"step"}function Wo(e,t){let n=or(t),r=(e.get(n)??0)+1;return e.set(n,r),r===1?n:`${n}-${r}`}function Jo(e,t,n,r,s){try{let o=Q(e,or(n.service),t);Bo(o,{recursive:!0});let i=Q(o,"latest.json"),a=Q(o,"body.txt");return r===void 0?Xn(a,{force:!0}):Zn(a,r),Zn(i,`${JSON.stringify({...n,bodyPath:r===void 0?null:"body.txt"},null,2)}
16
+ tilt ${e} failed with exit code ${t}`)),process.exit(t)}import{existsSync as ir,readFileSync as ar}from"node:fs";import{copyFileSync as zn,mkdirSync as Bo,rmSync as Xn,writeFileSync as Zn}from"node:fs";import{join as Q}from"node:path";var Uo=60,Qn=1e3,er=1e4,tr=200,nr=64*1024,rr=new Set([404,502,503,504]),Ho=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]),Vo=new Set(["GET","HEAD","OPTIONS"]);function K(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Go(e,t){if(Vo.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"&&Ho.has(r)}function kt(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 s=`smoke.steps[${r}]`;if(!K(n)){t.push(`${s}: must be an object`);return}let o=n.path;(typeof o!="string"||!o.startsWith("/")||o.startsWith("//"))&&t.push(`${s}.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(`${s}.expect: must be an HTTP status between 100 and 599`),n.bodyContains!==void 0&&typeof n.bodyContains!="string"&&t.push(`${s}.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(`${s}.save: must map names to paths such as "$.id"`))}),t)}function jd(e,t){let{appBase:n,apiBase:r}=ft(t),s=[];for(let o of e){let i=o.config;if(!i?.smoke)continue;let a=i.appType,c=w(a)||a===B;if(!c&&a!=="frontend"||i.exposeViaProxy===!1)continue;let u=c?r:n,d=i.healthCheckPath||(c&&a!==B?"/health":void 0);s.push({name:o.name,baseUrl:`${u}${mt(o).replace(/\/+$/,"")}`,...d?.startsWith("/")?{readyPath:d}:{},smoke:i.smoke})}return s}function Ko(e,t){let n=t.replace(/^\$\.?/,"").match(/[^.[\]]+/g)??[],r=e;for(let s of n){if(r==null||s==="__proto__"||s==="constructor"||s==="prototype"||!Object.hasOwn(r,s))return;r=r[s]}return r}function sr(e,t){return e.replace(/\{\{(\w+)\}\}/g,(n,r)=>t[r]??n)}function gt(e,t){return typeof e=="string"?sr(e,t):Array.isArray(e)?e.map(n=>gt(n,t)):K(e)?Object.fromEntries(Object.entries(e).map(([n,r])=>[n,gt(r,t)])):e}function Fe(e){let t=e.replace(/\s+/g," ").trim();return t.length>tr?`${t.slice(0,tr)}...`:t}function or(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"step"}function Wo(e,t){let n=or(t),r=(e.get(n)??0)+1;return e.set(n,r),r===1?n:`${n}-${r}`}function Jo(e,t,n,r,s){try{let o=Q(e,or(n.service),t);Bo(o,{recursive:!0});let i=Q(o,"latest.json"),a=Q(o,"body.txt");return r===void 0?Xn(a,{force:!0}):Zn(a,r),Zn(i,`${JSON.stringify({...n,bodyPath:r===void 0?null:"body.txt"},null,2)}
17
17
  `),s&&(zn(i,Q(o,"last-success.json")),r===void 0?Xn(Q(o,"last-success-body.txt"),{force:!0}):zn(a,Q(o,"last-success-body.txt"))),i}catch{return}}function qo(e){let t=Buffer.from(e,"utf8");if(t.length<=nr)return{body:e,truncated:!1,bytes:t.length};let n=t.subarray(0,nr).toString("utf8").replace(/\uFFFD$/,"");return{body:n,truncated:!0,bytes:Buffer.byteLength(n,"utf8")}}async function Yo(e,t={}){let n=t.fetch??((d,l)=>fetch(d,l)),r=t.now??Date.now,s=t.sleep??(d=>new Promise(l=>setTimeout(l,d))),o=(e.smoke.timeoutSeconds??Uo)*1e3,i=r()+o,a={},c=kt(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 l=await n(d,{method:"GET",signal:AbortSignal.timeout(er)});if(await l.text(),!rr.has(l.status)&&l.status<500)break}catch{}if(r()>=i)break;await s(Qn)}}i=r()+o;let u=new Map;for(let[d,l]of e.smoke.steps.entries()){let h=l.name??`step ${d+1}`,k=(l.method??"GET").toUpperCase(),g=`${e.baseUrl}${sr(l.path,a)}`,p=l.expect??200,y=l.body!==void 0,v,j="",G,Ze;for(;;){G=void 0,Ze=void 0,v=void 0;let I=!1;try{let _=await n(g,{method:k,headers:y?{"content-type":"application/json"}:void 0,body:y?JSON.stringify(gt(l.body,a)):void 0,signal:AbortSignal.timeout(er)});v=_.status,j=await _.text()}catch(_){I=Go(_,k),G=_ instanceof Error?_.message:String(_);let Y=K(_)?_.code??(K(_.cause)?_.cause.code:void 0):void 0;Ze=typeof Y=="string"?Y:void 0}if(!(G!==void 0&&I||v!==void 0&&v!==p&&rr.has(v))||r()>=i)break;await s(Qn)}let ae=`${e.name}: ${h}: ${k} ${g}`,un;if(t.recordDir){let I=v===void 0?void 0:qo(j),ue=G===void 0&&v===p&&(l.bodyContains===void 0||j.includes(l.bodyContains));un=Jo(t.recordDir,Wo(u,h),{service:e.name,step:h,method:k,url:g,expectedStatus:p,status:v??null,...G===void 0?{}:{error:Ze??G},truncated:I?.truncated??!1,bodyBytes:I?.bytes??0},I?.body,ue)}let ce=I=>({name:e.name,ok:!1,failure:I,recordPath:un});if(G!==void 0)return ce(`${ae} never answered (${G})`);if(v!==p)return ce(`${ae} returned ${v}, expected ${p}${j?`: ${Fe(j)}`:""}`);if(l.bodyContains!==void 0&&!j.includes(l.bodyContains))return ce(`${ae} returned ${v} but the body does not contain "${l.bodyContains}": ${Fe(j)}`);if(l.save){let I;try{I=JSON.parse(j)}catch{return ce(`${ae} returned a body that is not JSON, cannot save: ${Fe(j)}`)}for(let[ue,_]of Object.entries(l.save)){let Y=Ko(I,_);if(Y==null||typeof Y=="object")return ce(`${ae} response has no ${_} to save as {{${ue}}}: ${Fe(j)}`);a[ue]=String(Y)}}}return{name:e.name,ok:!0}}function Id(e){return`Smoke check failed: ${e.failure}${e.recordPath?` (record: ${e.recordPath})`:""}`}async function Fd(e,t={}){return Promise.all(e.map(n=>Yo(n,t)))}var ge=1,Ud="https://tdk-landscape.github.io/schema.service.json",zo={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"},Xo=new Set(["$schema","apiBasePath","apiPath","appName","appType","backendName","basePath","build","buildContext","databaseName","dependencies","dev","dependsOn","dockerfile","enabled","exposeViaProxy","featuresEnabled","framework","healthCheckPath","image","language","name","nats","params","playwright","port","replicas","restart","runtime","sablier","schemaVersion","secrets","smoke","stack","syncs","traefik","type"]),Zo=[{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"}];function Me(e,t){let n=[],r=new Set;for(let{path:s,pattern:o,expected:i}of Zo){let a=e;for(let u of s)a=a&&typeof a=="object"&&!Array.isArray(a)?a[u]:void 0;if(a==null||a==="")continue;let c=`${t}.${s.join(".")}`;(typeof a!="string"||!o.test(a))&&r.add(c),typeof a!="string"?n.push(`${c}: expected a string`):o.test(a)||n.push(`${c}: must be ${i} (no spaces, line breaks or YAML syntax)`)}for(let[s,o]of Qo(e,t))r.has(s)||n.push(`${s}: ${o}`);return n}function yt(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 Qo(e,t){let n=[],r=Object.entries(e).filter(([s])=>s!=="smoke").map(([s,o])=>[o,`${t}.${s}`]);for(let s of Object.keys(e))yt(s)&&n.push([t,"a key contains a line break"]);for(;r.length>0;){let[s,o]=r.pop();if(typeof s=="string")yt(s)&&n.push([o,"must not contain line breaks or control characters"]);else if(Array.isArray(s))for(let[i,a]of s.entries())r.push([a,`${o}[${i}]`]);else if(s&&typeof s=="object")for(let[i,a]of Object.entries(s))yt(i)?n.push([o,"a key contains a line break"]):r.push([a,`${o}.${i}`])}return n.sort(([s],[o])=>s.localeCompare(o))}var ei=["apiPath","basePath"];function St(e){if(!e||typeof e!="object"||Array.isArray(e))return[];let t=e;return ei.filter(n=>n in t&&typeof t[n]!="string")}function bt(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{errors:[`${t}: expected a JSON object`],warnings:[]};let n=e,r=[];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!==ge&&r.push(`${t}.schemaVersion: unsupported version ${String(n.schemaVersion)} (supported: ${ge})`);for(let a of St(n))r.push(`${t}.${a}: expected a string`);let s=n.dev,o=s&&typeof s=="object"&&!Array.isArray(s)?s.liveReload:void 0;if(o!==void 0&&typeof o!="boolean"&&r.push(`${t}.dev.liveReload: expected true or false`),r.push(...Me(n,t)),"smoke"in n)for(let a of kt(n.smoke))r.push(`${t}.${a}`);let i=Object.keys(n).flatMap(a=>{let c=zo[a];return c?[`${t}.${a}: ${c}`]:Xo.has(a)?[]:[`${t}.${a}: unknown field is preserved`]});return o===!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 Hd(e,t){if(!ir(e))return[];try{let n=JSON.parse(ar(e,"utf-8"));return!n||typeof n!="object"||Array.isArray(n)?[]:Me(n,t)}catch{return[]}}function Vd(e,t){if(!ir(e))return{errors:[`${t}: file is missing`],warnings:[]};try{let n=JSON.parse(ar(e,"utf-8"));return bt(n,t)}catch(n){let r=n instanceof Error?n.message:String(n);return{errors:[`${t}: invalid JSON (${r})`],warnings:[]}}}function cr(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 ti=/^[a-z0-9-]+$/;function ur(e){return ti.test(e)}var ni="Use lowercase letters, numbers, and hyphens only";function dr(e,t){return`${e} "${t}" is not valid. ${ni}, e.g. "${e==="Resource name"?"my-service":"my-stack"}".`}function ri(e){return e.trim()?ur(e)?{valid:!0}:{valid:!1,error:dr("Resource name",e)}:{valid:!1,error:"Resource name is required"}}function si(e){return e.trim()?ur(e)?{valid:!0}:{valid:!1,error:dr("Stack name",e)}:{valid:!1,error:"Stack name is required"}}function Jd(e){let t=e==="resource"?ri:si;return n=>{let r=t(n);return r.valid?!0:r.error??"Invalid name"}}function qd(e){return Tt(at,e)?{valid:!0}:{valid:!1,error:`Invalid service. Must be one of: ${at.join(", ")}`}}function Yd(e){return Number.isInteger(e)&&e>0&&e<=65535}function zd(e){return!e.includes("\0")&&!/[<>:"|?*]/.test(e)}function Xd(e,t="_"){return e.replace(/[^a-zA-Z0-9-]/g,t).substring(0,100)}function Tt(e,t){return e.includes(t)}var ii=vn;function wt(e,t=5,n=0){let r=[];if(n>t)return r;let s=lr(e,{withFileTypes:!0});for(let o of s){let i=J(e,o.name);if(o.isDirectory()){if(ai(o.name))continue;r.push(...wt(i,t,n+1))}else o.isFile()&&o.name===ii&&r.push(i)}return r}function ul(e){return wt(e)}function ai(e){return Tt($n,e)||e.startsWith(".")}function ci(e){if(!e||typeof e!="object")return!1;let t=e,n=typeof t.runtime=="string"||typeof t.appType=="string"||typeof t.type=="string";return typeof t.appName=="string"&&n}var Le=new Set;var W=class extends Error{reason;constructor(t){super(t),this.reason=t,this.name="InvalidServiceJsonError"}};function Et(e){return e instanceof Error?e.message:String(e)}function ui(e){let t;try{t=pr(e,"utf-8")}catch(c){throw new W(`could not read file: ${Et(c)}`)}let n;try{n=JSON.parse(t)}catch(c){throw new W(`invalid JSON: ${Et(c)}`)}if(!ci(n))throw new W("missing required fields (appName, runtime or appType)");let r=St(n);if(r.length>0)throw new W(r.map(c=>`${c}: expected a string`).join("; "));let s=Me(n,e);if(s.length>0)throw new W(s.join("; "));let o=n;for(let c of bt(n,e).warnings)Le.has(c)||(Le.add(c),console.warn(`\u26A0\uFE0F ${c}`));let i={...o,appType:o.appType||o.type},a=oi(e);return{name:i.appName,path:a,configPath:e,config:i,stack:i.stack,port:i.port,type:i.appType}}function fr(e){let t=wt(e),n=[],r=[];for(let s of t)try{n.push(ui(s))}catch(o){r.push({path:s,reason:o instanceof W?o.reason:Et(o)})}return{resources:n,problems:r}}function di(e){for(let t of e){let n=`skipped ${t.path}: ${t.reason}`;Le.has(n)||(Le.add(n),console.warn(`\u26A0\uFE0F ${n}`))}}function b(e){let{resources:t,problems:n}=fr(e);return di(n),t}function ye(){let e=m();if(!e)throw H.notInProject();return b(e)}function dl(){let e=m();if(!e)throw H.notInProject();let{resources:t,problems:n}=fr(e);if(n.length>0)throw new Error(n.map(r=>`Invalid service.json at ${r.path}: ${r.reason}`).join(`
18
18
  `));return t}function ll(e){let t=b(e),n=new Set;for(let r of t)r.stack&&n.add(r.stack);return Array.from(n).sort()}function pl(e){let t=e||ye(),n=new Set;for(let r of t)r.stack&&n.add(r.stack);return Array.from(n).sort()}function fl(e=ye()){let t=new Map;for(let r of e)if(r.stack){let s=t.get(r.stack);s?s.push(r):t.set(r.stack,[r])}let n=[];for(let[r,s]of t)n.push({name:r,description:S(s.length,"resource"),resources:s,resourceCount:s.length});return n.sort((r,s)=>r.name.localeCompare(s.name))}function ml(e,t){return(t??ye()).some(r=>r.stack===e)}var $t=5e3,Pt=new he({ttlMs:$t}),vt=new he({ttlMs:$t}),ke=Hn($t);function hl(){Pt.clear(),vt.clear(),ke.reset()}function li(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===D)return"config";if(e==="schema.prisma"||e.includes("migration"))return"prisma";if(t){let n=t.split(`
19
19
  `).slice(0,10).join(`