@flisk-sh/flisk 1.4.5 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +37 -14
  2. package/cli/dist/commands/doctor.js +50 -12
  3. package/cli/dist/commands/down.js +33 -1
  4. package/cli/dist/commands/logs.js +38 -1
  5. package/cli/dist/commands/project.js +11 -0
  6. package/cli/dist/commands/status.js +39 -10
  7. package/cli/dist/commands/up.js +119 -1
  8. package/cli/dist/commands/upgrade.js +2 -2
  9. package/cli/dist/utils/bun-requirement.js +7 -4
  10. package/cli/dist/utils/cold-preflight.js +52 -32
  11. package/cli/dist/utils/doctor-native.js +96 -0
  12. package/cli/dist/utils/native-project.js +41 -0
  13. package/cli/dist/utils/native-runtime.js +513 -0
  14. package/cli/dist/utils/native-service.js +95 -0
  15. package/cli/dist/utils/native-up.js +156 -0
  16. package/cli/dist/utils/platform.js +1 -1
  17. package/cli/dist/utils/project-templates.js +1 -1
  18. package/cli/dist/utils/service-manifest.js +19 -3
  19. package/cli/dist/utils/services.js +15 -2
  20. package/cli/dist/utils/tilt-logs.js +8 -0
  21. package/cli/dist-bundle/{chunk-V36PT2HF.js → chunk-BIVAAWBX.js} +1 -1
  22. package/cli/dist-bundle/{chunk-U42XZWXB.js → chunk-C4UFBXRD.js} +1 -1
  23. package/cli/dist-bundle/{chunk-CGTWXWBU.js → chunk-CAEMWNPF.js} +1 -1
  24. package/cli/dist-bundle/chunk-CDKEHFW6.js +35 -0
  25. package/cli/dist-bundle/{chunk-LUUJ4JB7.js → chunk-DPQK5TIS.js} +1 -1
  26. package/cli/dist-bundle/chunk-ETXZ5LUG.js +18 -0
  27. package/cli/dist-bundle/chunk-GT76VMUL.js +130 -0
  28. package/cli/dist-bundle/{chunk-4J2FLFUA.js → chunk-IDNES5PE.js} +1 -1
  29. package/cli/dist-bundle/{chunk-SFOFTVYM.js → chunk-MUDE4DZD.js} +1 -1
  30. package/cli/dist-bundle/{chunk-BP2M4OJ4.js → chunk-QEBN6ZVQ.js} +1 -1
  31. package/cli/dist-bundle/{chunk-VMVTECCK.js → chunk-R7DWXISM.js} +1 -1
  32. package/cli/dist-bundle/{chunk-Z5QEE4SW.js → chunk-U5FCQF42.js} +1 -1
  33. package/cli/dist-bundle/{chunk-TIYTGN5R.js → chunk-WCGBVETR.js} +1 -1
  34. package/cli/dist-bundle/cli.js +357 -352
  35. package/cli/dist-bundle/cold-preflight-KUWOL2Q2.js +2 -0
  36. package/cli/dist-bundle/{demo-stage-G4W23BXW.js → demo-stage-QCC4IWXR.js} +1 -1
  37. package/cli/dist-bundle/{open-HQFT6WDC.js → open-AZNX4AC5.js} +1 -1
  38. package/cli/dist-bundle/project-5SWIGCNP.js +2 -0
  39. package/cli/dist-bundle/{project-templates-IMJ3OX5I.js → project-templates-2VQPUBSQ.js} +1 -1
  40. package/cli/dist-bundle/{services-GLFKZBBZ.js → services-EY5WKPHQ.js} +1 -1
  41. package/cli/dist-bundle/{tilt-port-owner-PJN4MPYE.js → tilt-port-owner-UBU6JWOA.js} +1 -1
  42. package/cli/package.json +1 -1
  43. package/examples/{tdk-example → flisk-example}/README.md +2 -2
  44. package/package.json +2 -2
  45. package/runtime/discovery/AGENTS.md +1 -1
  46. package/runtime/discovery/TILT_DISCOVERY.star +1 -1
  47. package/runtime/discovery/deterministic.star +1 -1
  48. package/runtime/engine/AGENTS.md +3 -3
  49. package/runtime/engine/README.md +1 -1
  50. package/runtime/engine/REFACTORING_MANIFEST_SYSTEM.md +1 -1
  51. package/runtime/engine/assets/docker/wake-gateway/decide.ts +1 -1
  52. package/runtime/engine/assets/docker/wake-gateway/server.ts +2 -2
  53. package/runtime/engine/assets/scripts/mac-m1-resource-monitor.sh +6 -6
  54. package/runtime/engine/schemas/service-schema.json +99 -21
  55. package/runtime/engine/scripts/debug-container.sh +1 -1
  56. package/runtime/engine/scripts/generate-manifest.sh +2 -2
  57. package/runtime/engine/topologies/platform/docker/compose/traefik_standalone.star +19 -4
  58. package/runtime/engine/topologies/platform/docker/constants.star +2 -2
  59. package/runtime/engine/topologies/platform/docker/networking/traefik_static_routes.star +2 -2
  60. package/runtime/engine/topologies/tilt/manifest/mapper.star +1 -1
  61. package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +1 -1
  62. package/runtime/ext/ide-components/README.md +1 -1
  63. package/runtime/ext/ui-enhancements/extension.yaml +2 -2
  64. package/runtime/specs/specs/TILT_SERVICE_DEFAULTS.star +1 -1
  65. package/runtime/specs/specs/TILT_TECH_STACK.star +1 -1
  66. package/cli/dist-bundle/chunk-BMHPPX2Q.js +0 -35
  67. package/cli/dist-bundle/chunk-PEZQEZVV.js +0 -17
  68. package/cli/dist-bundle/chunk-ULMQVD7J.js +0 -122
  69. package/cli/dist-bundle/cold-preflight-3BNDOLGS.js +0 -2
  70. package/cli/dist-bundle/project-JZO2CPZZ.js +0 -2
  71. /package/examples/{tdk-example → flisk-example}/LICENSE +0 -0
  72. /package/examples/{tdk-example → flisk-example}/TILT_SERVICE_DEFAULTS.star +0 -0
  73. /package/examples/{tdk-example → flisk-example}/TILT_TECH_STACK.star +0 -0
  74. /package/examples/{tdk-example → flisk-example}/Tiltfile +0 -0
  75. /package/examples/{tdk-example → flisk-example}/services/platform/messaging/docker-compose.yml +0 -0
  76. /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/package.json +0 -0
  77. /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/service.json +0 -0
  78. /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/src/index.ts +0 -0
  79. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/index.html +0 -0
  80. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/package.json +0 -0
  81. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/public/.gitkeep +0 -0
  82. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/service.json +0 -0
  83. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/main.tsx +0 -0
  84. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/orders.tsx +0 -0
  85. /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/style.css +0 -0
  86. /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/package.json +0 -0
  87. /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/service.json +0 -0
  88. /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/src/index.ts +0 -0
package/README.md CHANGED
@@ -114,13 +114,13 @@ flisk resource shop-web --type frontend --stack shop --yes
114
114
  flisk up shop
115
115
  ```
116
116
 
117
- You get an orders API and a storefront behind one local gateway, with Postgres and live reload. You need Docker, [Tilt](https://docs.tilt.dev/install.html), Node.js 22.12+, and [Bun](https://bun.sh/) 1.2+; `flisk doctor` checks your machine. Prefer to answer questions instead? `npm init create-microservices my-project` asks what to create. See the [quick start](#quick-start) for every option.
117
+ You get an orders API and a storefront behind one local gateway, with Postgres and live reload. You need Docker, [Tilt](https://docs.tilt.dev/install.html), Node.js 22.12+, and [Bun](https://bun.sh/) 1.2+; `flisk doctor` checks your machine. A simpler app needs none of those: give each service a `command` and [run it as local processes](docs/run-without-docker.md). Prefer to answer questions instead? `npm init create-microservices my-project` asks what to create. See the [quick start](#quick-start) for every option.
118
118
 
119
119
  <div align="center">
120
120
 
121
121
  <p><strong><a href="#quick-start">Quick start</a> · <a href="#why-flisk-cli">Why Flisk</a> · <a href="#examples">Examples</a> · <a href="#installation">Installation</a> · <a href="#faq">FAQ</a> · <a href="#docs">Documentation</a></strong></p>
122
122
 
123
- [Website](https://flisk.sh/) · [Quickstart](https://flisk.sh/docs/quickstart/) · [Examples](https://flisk.sh/docs/examples/) · [Awesome Flisk](https://github.com/flisk-sh/awesome-tdk-framework) · [Demo](https://flisk-sh.github.io/flisk-demo-animation/) · [Report a bug](https://github.com/flisk-sh/flisk/issues)
123
+ [Website](https://flisk.sh/) · [Quickstart](https://flisk.sh/docs/quickstart/) · [Examples](https://flisk.sh/docs/examples/) · [Awesome Flisk](https://github.com/flisk-sh/awesome-flisk-framework) · [Demo](https://flisk-sh.github.io/flisk-demo-animation/) · [Report a bug](https://github.com/flisk-sh/flisk/issues)
124
124
 
125
125
  <a id="readme-languages"></a>
126
126
 
@@ -245,7 +245,7 @@ flisk up shop
245
245
  Agents get durable checkpoint volumes that survive `flisk down`, sticky session routing via `x-session-id`, graceful SIGTERM drain during hot reloads, a Docker health check on `/health`, and application-level readiness checks on `/ready`. See the [AI agents guide](docs/agents/ai-agents.md) and [honest comparison with agent frameworks](docs/compare-honest.md#ai-agent-frameworks-and-workflow-engines-libfx-langgraph-mastra-vercel-workflows-temporal).
246
246
 
247
247
 
248
- **What Flisk is:** a local development runtime. Define each service in `service.json` and run `flisk up`. Docker runs the containers, Tilt live-updates them while you code, and Flisk writes the configuration both use. No local Kubernetes is needed.
248
+ **What Flisk is:** a local development runtime. Define each service in `service.json` and run `flisk up`. A service with a `command` runs as a local process, with nothing else installed. When you need a database, one gateway URL, or the container you deploy, Docker runs the containers, Tilt live-updates them while you code, and Flisk writes the configuration both use. No local Kubernetes is needed.
249
249
 
250
250
  **What Flisk is not:** a deploy tool or a Compose file. Production deployment stays with Helm, Argo CD, or Kustomize.
251
251
 
@@ -337,19 +337,42 @@ Get a running local stack in **3 commands**. Works on macOS, Linux, and Windows
337
337
 
338
338
  ### Prerequisites (install once)
339
339
 
340
- | Tool | Version | Install | Install path |
341
- |------|---------|---------|--------------|
342
- | **Docker** | Engine 25+, Compose 2.20.2+ | [docker.com](https://www.docker.com/products/docker-desktop/) | All paths |
343
- | **Tilt** | 0.25+ | `curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh \| bash` | All paths |
344
- | **Node.js** | 22.12+ | [nodejs.org](https://nodejs.org/) | npm path (`npm install -g @flisk-sh/flisk`) |
345
- | **Bun** | 1.2+ | `curl -fsSL https://bun.sh/install \| bash` | Bun path (`bun add -g @flisk-sh/flisk`) |
340
+ | Tool | Version | Install | Needed for |
341
+ |------|---------|---------|------------|
342
+ | **Your app's runtime** | whatever your app uses | Node.js, Python, Go, Bun, ... | Every path: it runs your code |
343
+ | **Node.js** | 22.12+ | [nodejs.org](https://nodejs.org/) | npm install (`npm install -g @flisk-sh/flisk`) |
344
+ | **Docker** | Engine 25+, Compose 2.20.2+ | [docker.com](https://www.docker.com/products/docker-desktop/) | Docker + Tilt path only |
345
+ | **Tilt** | 0.25+ | `curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh \| bash` | Docker + Tilt path only |
346
+ | **Bun** | 1.2+ | `curl -fsSL https://bun.sh/install \| bash` | Bun install (`bun add -g @flisk-sh/flisk`), or generated JavaScript services |
346
347
 
347
- > **Conditional prerequisites**: Node.js is only required when you install Flisk via npm (`npm install -g @flisk-sh/flisk`). Bun is only required when you install via Bun or your project has generated JavaScript services. Docker and Tilt are required for every path.
348
+ > **Two paths.** A project whose services each declare a `command` runs them as local processes: you need only Flisk and your app's runtime ([simplest path](#simplest-path-no-docker)). Docker and Tilt are needed for the Docker + Tilt path: databases such as Postgres, one gateway URL for every service, and running the same container you deploy. `flisk doctor` checks only what your project uses.
348
349
 
349
350
  > **Windows?** Use WSL2 Ubuntu. See the [Windows & WSL2 guide](docs/wsl2.md) for the recommended setup.
350
351
 
351
352
  ---
352
353
 
354
+ ### Simplest path: no Docker
355
+
356
+ For an app made of services you can start with one command each (an API, a website, a worker), put a `service.json` next to each one:
357
+
358
+ ```json
359
+ { "schemaVersion": 1, "name": "api", "command": "node server.js", "port": 4000, "healthCheckPath": "/health" }
360
+ ```
361
+
362
+ ```json
363
+ { "schemaVersion": 1, "name": "web", "command": "node server.js", "port": 3000, "dependsOn": ["api"] }
364
+ ```
365
+
366
+ Then start everything from the project folder:
367
+
368
+ ```bash
369
+ flisk up
370
+ ```
371
+
372
+ Flisk starts the API, waits until it answers, starts the website, prints both URLs, prefixes each line of output with the service name, and restarts a service that crashes. Ctrl+C or `flisk down` stops them. No Docker, no Tilt, no generated files. See [Run your app without Docker](docs/run-without-docker.md) for every field, and for when to switch to the Docker + Tilt path.
373
+
374
+ ---
375
+
353
376
  ### Fastest path: one-command demo (no manual steps)
354
377
 
355
378
  ```bash
@@ -596,11 +619,11 @@ Runnable examples show Flisk on real stacks. Start with the smallest one:
596
619
  | [one-backend-helm](examples/one-backend-helm/README.md) | Working alongside Helm |
597
620
  | [byo](examples/byo/README.md) | Bringing your own existing resources |
598
621
  | [byo-jobs](examples/byo-jobs/README.md) | Bring-your-own jobs |
599
- | [tdk-example](examples/tdk-example/README.md) | A full multi-service stack |
622
+ | [flisk-example](examples/flisk-example/README.md) | A full multi-service stack |
600
623
 
601
624
  ## Use Flisk with your AI agent
602
625
 
603
- The repository ships five agent skills for common Flisk work under [`.agents/skills`](.agents/skills): `flisk-doctor` (environment checks), `flisk-up` (preview and start a stack), `flisk-resource` (create a service), `flisk-status` (health and URLs), and `flisk-troubleshoot` (fixing `flisk` and Tilt errors). Agents working in this repo also follow [AGENTS.md](AGENTS.md). Use these skills to let your agent check your setup and explain failures instead of guessing at Docker or Tilt configuration.
626
+ The repository ships agent skills for common Flisk work under [`.agents/skills`](.agents/skills): `flisk-build` (turn "build me a shop" into Flisk steps, check every URL, and report them), `flisk-recipe-starters` (start from a working restaurant, SaaS, store, AI agent or user-management app), recipes for a shop, a SaaS with login and billing, an AI agent with MCP tools and a database, and a blog (`flisk-recipe-shop`, `flisk-recipe-saas`, `flisk-recipe-ai-agent`, `flisk-recipe-blog`), `flisk-doctor` (environment checks), `flisk-up` (preview and start a stack), `flisk-resource` (create a service), `flisk-status` (health and URLs), and `flisk-troubleshoot` (fixing `flisk` and Tilt errors). More prompts to copy, by category (spreadsheets, recruiting, product management, shops, AI agents), are in the [prompt gallery](docs/recipes/build-with-your-agent.md). Agents working in this repo also follow [AGENTS.md](AGENTS.md). Use these skills to let your agent check your setup and explain failures instead of guessing at Docker or Tilt configuration.
604
627
 
605
628
  ## Installation
606
629
 
@@ -648,7 +671,7 @@ Set `FLISK_DEBUG=1` before running `flisk up --verbose` to include Starlark debu
648
671
  - [Generated service files and drift checks](docs/generated-files.md)
649
672
  - [Runnable one-backend example](examples/one-backend/README.md)
650
673
  - [Runnable Python backend example](examples/one-backend-python/README.md)
651
- - [Full multi-service example](examples/tdk-example/README.md)
674
+ - [Full multi-service example](examples/flisk-example/README.md)
652
675
  - [Features and license limits](docs/FEATURES.md)
653
676
  - [Tool comparison: docker-compose, Tilt, Skaffold, DevSpace, kind](docs/comparison.md)
654
677
  - [Honest comparison and known limits](docs/compare-honest.md)
@@ -686,7 +709,7 @@ On native Windows, `flisk --version`, `flisk doctor`, and `flisk up --dry-run` a
686
709
 
687
710
  Core stays free. Premium is a separate key for the extras above; no key required to run `flisk up`.
688
711
 
689
- **Ecosystem map:** examples, articles, related tools, and notes live in [awesome-tdk-framework](https://github.com/flisk-sh/awesome-tdk-framework). Star this repo (`flisk`) if the CLI is what you run; use the awesome list to browse the rest.
712
+ **Ecosystem map:** examples, articles, related tools, and notes live in [awesome-flisk-framework](https://github.com/flisk-sh/awesome-flisk-framework). Star this repo (`flisk`) if the CLI is what you run; use the awesome list to browse the rest.
690
713
 
691
714
  <p align="center">
692
715
 
@@ -15,6 +15,7 @@ import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.j
15
15
  import { checkCrashLoopingContainers } from "../utils/doctor-crashloop.js";
16
16
  import { applyDoctorFixes, checkDuplicateFliskInstalls, hasFailedActions, planDoctorFixes, projectTiltPids, } from "../utils/doctor-fix.js";
17
17
  import { checkDiskSpace, dockerDesktopStuck } from "../utils/doctor-host.js";
18
+ import { checkNativeCommands, checkNativeDependsOn, checkNativeRuntime, checkNativeServiceHealth, } from "../utils/doctor-native.js";
18
19
  import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
19
20
  import { checkHealthRoutes, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeAdvertisedProbes, summarizeServiceProbes, summarizeSmokeResults, } from "../utils/doctor-runtime.js";
20
21
  import { checkStarlarkLoadExports } from "../utils/doctor-starlark.js";
@@ -25,6 +26,7 @@ import { evaluateFliskVersionFloor } from "../utils/flisk-version.js";
25
26
  import { formatCount } from "../utils/formatting.js";
26
27
  import { getHostPortPlan, resolveRuntimeIngressPort } from "../utils/host-port-config.js";
27
28
  import { createHostPortPlan } from "../utils/host-port-plan.js";
29
+ import { findNativeProject } from "../utils/native-project.js";
28
30
  import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
29
31
  import { promptConfirm } from "../utils/prompt.js";
30
32
  import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
@@ -1285,8 +1287,13 @@ export const doctorCommand = new Command("doctor")
1285
1287
  process.exit(2);
1286
1288
  return;
1287
1289
  }
1290
+ // A project whose services all declare `command` runs as local processes: doctor checks only what they need (#2231).
1291
+ const nativeProject = findNativeProject();
1292
+ const nativeResources = nativeProject?.resources ?? null;
1288
1293
  if (!options.json) {
1289
- console.log("Flisk doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");
1294
+ console.log(nativeResources
1295
+ ? "Flisk doctor checks what this project's local processes need; its services declare commands, so Docker and Tilt are not checked."
1296
+ : "Flisk doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");
1290
1297
  }
1291
1298
  const host = detectHost();
1292
1299
  if (process.platform === "win32") {
@@ -1343,10 +1350,15 @@ export const doctorCommand = new Command("doctor")
1343
1350
  console.log(`\n${chalk.bold("🔍 Flisk Doctor")}\n`);
1344
1351
  console.log("Checking environment...\n");
1345
1352
  }
1346
- const projectRoot = findProjectRoot();
1353
+ // A directory of command services is a project for doctor even before `flisk up` writes its .flisk/project.json.
1354
+ const projectRoot = findProjectRoot() ?? nativeProject?.projectRoot ?? null;
1347
1355
  // Repairs run before the checks so the checks report the state after the fix.
1348
1356
  let fixActions;
1349
- if (options.fix) {
1357
+ if (options.fix && nativeResources) {
1358
+ // The repairs are for Docker networks and Tilt probes, which a project of local processes does not have.
1359
+ fixActions = [];
1360
+ }
1361
+ else if (options.fix) {
1350
1362
  try {
1351
1363
  let projectName;
1352
1364
  if (projectRoot) {
@@ -1388,16 +1400,19 @@ export const doctorCommand = new Command("doctor")
1388
1400
  if (!options.json)
1389
1401
  printFixActions(fixActions, options.dryRun);
1390
1402
  }
1403
+ // The host port plan places the gateway and Postgres containers; a project of local processes has neither.
1391
1404
  let hostPortPlan;
1392
1405
  let portPlanError;
1393
- try {
1394
- hostPortPlan = await getHostPortPlan(projectRoot ?? process.cwd());
1395
- }
1396
- catch (error) {
1397
- hostPortPlan = null;
1398
- portPlanError = error instanceof Error ? error.message : String(error);
1406
+ if (!nativeResources) {
1407
+ try {
1408
+ hostPortPlan = await getHostPortPlan(projectRoot ?? process.cwd());
1409
+ }
1410
+ catch (error) {
1411
+ hostPortPlan = null;
1412
+ portPlanError = error instanceof Error ? error.message : String(error);
1413
+ }
1399
1414
  }
1400
- const machineChecks = [
1415
+ const containerMachineChecks = [
1401
1416
  () => hostPortPlan
1402
1417
  ? {
1403
1418
  name: "Host Ports",
@@ -1434,7 +1449,7 @@ export const doctorCommand = new Command("doctor")
1434
1449
  requiresContainerRuntime("Docker Network Pool", () => checkDockerNetworkCapacity()),
1435
1450
  () => checkDuplicateFliskInstalls(),
1436
1451
  ];
1437
- const projectChecks = [
1452
+ const containerProjectChecks = [
1438
1453
  () => checkFliskVersion(),
1439
1454
  checkMasterConfigs,
1440
1455
  checkGeneratedProjectRuntimeAssets,
@@ -1495,8 +1510,31 @@ export const doctorCommand = new Command("doctor")
1495
1510
  // When Tilt is up, surface red resources (Traefik/apps never started).
1496
1511
  requiresContainerRuntime("Tilt Resources", checkTiltResourceHealth),
1497
1512
  ];
1513
+ const machineChecks = nativeResources
1514
+ ? [
1515
+ () => checkNativeRuntime(nativeResources),
1516
+ () => checkNativeCommands(nativeResources),
1517
+ () => checkWslProjectLocation(findProjectRoot() ?? process.cwd(), options.strict),
1518
+ () => checkDuplicateFliskInstalls(),
1519
+ ]
1520
+ : containerMachineChecks;
1521
+ const projectChecks = nativeResources
1522
+ ? [
1523
+ () => checkFliskVersion(),
1524
+ () => checkDuplicateResourceNames(),
1525
+ () => checkDependsOnShape(),
1526
+ () => checkNativeDependsOn(nativeResources),
1527
+ () => checkCircularDependencies(),
1528
+ () => checkServicePorts(),
1529
+ () => checkSchemaVersions(undefined, options.strict),
1530
+ () => checkDuplicateResourcePorts(),
1531
+ ]
1532
+ : containerProjectChecks;
1533
+ if (options.ping && nativeResources && projectRoot) {
1534
+ projectChecks.push(() => checkNativeServiceHealth(projectRoot, nativeResources, pingTimeout));
1535
+ }
1498
1536
  // Runs last: needs routable services (and working Traefik) to mean anything.
1499
- if (options.ping) {
1537
+ if (options.ping && !nativeResources) {
1500
1538
  // Probe the port a running stack is actually on, not the one the next `flisk up` would pick.
1501
1539
  const pingPort = projectRoot
1502
1540
  ? resolveRuntimeIngressPort(projectRoot, hostPortPlan?.ingressHttp)
@@ -8,8 +8,10 @@ import { pruneProjectNetworks, stopProjectTiltUp, waitForTiltUpExit, } from "../
8
8
  import { errorFactories, handleTiltFailure, withTiltCheck } from "../utils/errors.js";
9
9
  import { createJsonEmitter } from "../utils/json-output.js";
10
10
  import { writeMachineError } from "../utils/machine-output.js";
11
+ import { readNativeRunState, stopNativeRun } from "../utils/native-runtime.js";
12
+ import { isNativeProject } from "../utils/native-service.js";
11
13
  import { findProjectRoot } from "../utils/paths.js";
12
- import { discoverResources, discoverStacks } from "../utils/services.js";
14
+ import { discoverResources, discoverResourcesFromRoot, discoverStacks } from "../utils/services.js";
13
15
  import { buildTiltDownArgs, getTiltfilePath, runTilt } from "../utils/tilt.js";
14
16
  function allStackNames() {
15
17
  try {
@@ -20,6 +22,17 @@ function allStackNames() {
20
22
  return undefined;
21
23
  }
22
24
  }
25
+ /** True when `flisk down` stops local processes instead of Tilt: a native run is on record, or every service declares `command`. */
26
+ function isNativeRun(projectRoot) {
27
+ if (readNativeRunState(projectRoot))
28
+ return true;
29
+ try {
30
+ return isNativeProject(discoverResourcesFromRoot(projectRoot));
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
23
36
  function pruneNetworksOrWarn(json) {
24
37
  const root = findProjectRoot();
25
38
  if (!root)
@@ -64,6 +77,25 @@ export const downCommand = addYesOption(new Command("down"))
64
77
  }
65
78
  }
66
79
  const emit = options.json ? createJsonEmitter("DOWN_FAILED", "flisk down") : undefined;
80
+ // A project of `command` services (or one with a native run on record) stops without Docker or Tilt.
81
+ const projectRoot = findProjectRoot();
82
+ if (isNativeRun(projectRoot)) {
83
+ if (emit && options.dryRun) {
84
+ emit({ ok: true, dryRun: true, runtime: "native" });
85
+ return;
86
+ }
87
+ if (handleDryRun(options, "not stopping local processes", "stop the local processes of this project")) {
88
+ return;
89
+ }
90
+ const stopped = await stopNativeRun(projectRoot);
91
+ if (!options.json) {
92
+ console.log(stopped.wasRunning
93
+ ? chalk.blue(`Stopped local processes: ${stopped.services.join(", ")}`)
94
+ : chalk.gray("Nothing is running for this project."));
95
+ }
96
+ emit?.({ ok: true, runtime: "native", stopped: stopped.services });
97
+ return;
98
+ }
67
99
  await withTiltCheck(async () => {
68
100
  if (emit && options.dryRun) {
69
101
  emit({ ok: true, dryRun: true });
@@ -4,10 +4,13 @@ import { Command } from "commander";
4
4
  import { STANDARD_PORTS } from "../utils/constants.js";
5
5
  import { errorFactories, FliskError, runCommand, showErrorAndExit } from "../utils/errors.js";
6
6
  import { createMachineEnvelope } from "../utils/machine-output.js";
7
+ import { readNativeLogLines } from "../utils/native-runtime.js";
8
+ import { isNativeProject } from "../utils/native-service.js";
7
9
  import { findProjectRoot } from "../utils/paths.js";
8
10
  import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
11
+ import { discoverResourcesFromRoot } from "../utils/services.js";
9
12
  import { isTiltAvailable, runTilt } from "../utils/tilt.js";
10
- import { DEFAULT_LOG_TAIL, isTiltConnectionFailure, isValidPort, isValidSince, isValidTail, MAX_LOG_TAIL, parseTiltLogLines, } from "../utils/tilt-logs.js";
13
+ import { DEFAULT_LOG_TAIL, isTiltConnectionFailure, isValidPort, isValidSince, isValidTail, MAX_LOG_TAIL, parseTiltLogLines, sinceMilliseconds, } from "../utils/tilt-logs.js";
11
14
  import { tiltUnreachableMessage } from "../utils/tilt-unreachable-message.js";
12
15
  /** Resource names Tilt knows about, or null when they cannot be listed (the logs call then reports the real error). */
13
16
  async function tiltResourceNames(port) {
@@ -25,6 +28,17 @@ async function tiltResourceNames(port) {
25
28
  return null;
26
29
  }
27
30
  }
31
+ /** The project's services when every one declares `command` (they run as local processes), else null. */
32
+ function nativeResources(projectRoot) {
33
+ try {
34
+ const resources = discoverResourcesFromRoot(projectRoot);
35
+ return isNativeProject(resources) ? resources : null;
36
+ }
37
+ catch {
38
+ // Discovery problems are reported by the commands that start the stack; logs still asks Tilt.
39
+ return null;
40
+ }
41
+ }
28
42
  export const logsCommand = new Command("logs")
29
43
  .description("Print a bounded snapshot of recent logs from the running stack")
30
44
  .showHelpAfterError("\nService names are not positional. Use: flisk logs --service <name>")
@@ -81,6 +95,29 @@ export const logsCommand = new Command("logs")
81
95
  "Fix the read permission on .env, or move it out of the project",
82
96
  ]);
83
97
  }
98
+ // A project of `command` services keeps its output in .flisk/native/logs, so no Tilt is involved.
99
+ const resources = nativeResources(projectRoot);
100
+ if (resources) {
101
+ const known = resources.map((resource) => resource.name);
102
+ const unknown = services.filter((name) => !known.includes(name));
103
+ if (unknown.length > 0) {
104
+ const error = errorFactories.unknownServices(unknown, known);
105
+ fail("UNKNOWN_SERVICE", error.message, error.exitCode, error.suggestions);
106
+ }
107
+ const lines = readNativeLogLines(projectRoot, services.length > 0 ? services : known, {
108
+ tail,
109
+ sinceMs: options.since === undefined ? undefined : sinceMilliseconds(options.since),
110
+ });
111
+ if (options.json) {
112
+ console.log(JSON.stringify(redactValue(createMachineEnvelope({ services, tail, since: options.since ?? null, lines }), secrets)));
113
+ return;
114
+ }
115
+ const width = Math.max(0, ...lines.map((line) => line.resource.length));
116
+ for (const line of lines) {
117
+ console.log(redactSecrets(`${line.resource.padEnd(width)} | ${line.text}`, secrets));
118
+ }
119
+ return;
120
+ }
84
121
  if (!(await isTiltAvailable())) {
85
122
  fail("TILT_MISSING", "Tilt is not installed. Run: flisk doctor");
86
123
  }
@@ -213,6 +213,17 @@ const DEFAULT_PROJECT_JSON = {
213
213
  },
214
214
  overrides: {},
215
215
  };
216
+ /**
217
+ * Writes .flisk/project.json with the defaults `flisk project --yes` uses, and nothing else: no generated Tiltfile, Compose file,
218
+ * .env or engine copy. `flisk up` uses it to adopt a directory of `command` services as a project, so `flisk down` and
219
+ * `flisk doctor` find it afterwards.
220
+ */
221
+ export function writeDefaultProjectConfig(projectRoot) {
222
+ const projectConfig = JSON.parse(JSON.stringify(DEFAULT_PROJECT_JSON));
223
+ projectConfig.project.name = basename(projectRoot) || "my-project";
224
+ ensureDirectory(join(projectRoot, ".flisk"));
225
+ writeJsonFile(join(projectRoot, ".flisk", PROJECT_JSON), projectConfig);
226
+ }
216
227
  export const projectCommand = addYesOption(new Command("project"))
217
228
  .alias("init")
218
229
  .description("Initialize or validate project-level master configuration")
@@ -8,6 +8,8 @@ import { runCommand } from "../utils/errors.js";
8
8
  import { formatCount, showDetail, showStep } from "../utils/formatting.js";
9
9
  import { getHostPortPlan } from "../utils/host-port-config.js";
10
10
  import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
11
+ import { nativeServiceStates } from "../utils/native-runtime.js";
12
+ import { isNativeProject } from "../utils/native-service.js";
11
13
  import { findProjectRoot } from "../utils/paths.js";
12
14
  import { deriveServiceStates } from "../utils/service-runtime-state.js";
13
15
  import { groupSharedPostgres, readProjectName } from "../utils/shared-infra.js";
@@ -31,19 +33,30 @@ export const statusCommand = new Command("status")
31
33
  const sharedInfraGroup = projectName
32
34
  ? groupSharedPostgres(projectName, discovery.stacks)
33
35
  : null;
36
+ // A project of `command` services runs as local processes: its state comes from the run on record, not from Tilt.
37
+ const nativeStates = projectRoot && isNativeProject(discovery.resources)
38
+ ? nativeServiceStates(projectRoot, discovery.resources.map((resource) => resource.name))
39
+ : null;
34
40
  let tiltAvailable = false;
35
- try {
36
- tiltAvailable = await isTiltAvailable();
37
- }
38
- catch {
39
- // Tilt not available (spawn error)
40
- tiltAvailable = false;
41
+ if (!nativeStates) {
42
+ try {
43
+ tiltAvailable = await isTiltAvailable();
44
+ }
45
+ catch {
46
+ // Tilt not available (spawn error)
47
+ tiltAvailable = false;
48
+ }
41
49
  }
42
50
  if (!options.json) {
43
51
  showStep("Flisk Status\n");
44
- console.log(chalk.bold("Tilt:"), tiltAvailable ? chalk.green("available") : chalk.red("not found"));
45
- if (!tiltAvailable) {
46
- showDetail("Install Tilt: https://docs.tilt.dev/install.html");
52
+ if (nativeStates) {
53
+ console.log(chalk.bold("Runtime:"), "local processes (no Docker, no Tilt)");
54
+ }
55
+ else {
56
+ console.log(chalk.bold("Tilt:"), tiltAvailable ? chalk.green("available") : chalk.red("not found"));
57
+ if (!tiltAvailable) {
58
+ showDetail("Install Tilt: https://docs.tilt.dev/install.html");
59
+ }
47
60
  }
48
61
  console.log();
49
62
  }
@@ -53,7 +66,7 @@ export const statusCommand = new Command("status")
53
66
  // Tilt answers or its output cannot be read.
54
67
  let readiness = null;
55
68
  // The same look at Tilt feeds readiness and each service's state, so `flisk status` and `flisk ui` cannot disagree.
56
- let serviceStates = null;
69
+ let serviceStates = nativeStates;
57
70
  const tiltPort = getTiltPollingPort(process.env.TILT_PORT);
58
71
  // Port plan is best effort; service URLs are shown in both JSON and terminal output.
59
72
  let portPlan = null;
@@ -99,7 +112,23 @@ export const statusCommand = new Command("status")
99
112
  }
100
113
  if (options.json) {
101
114
  const errors = queryError ? [{ code: "TILT_STATUS_UNAVAILABLE", message: queryError }] : [];
115
+ // A native run answers the readiness question agents poll (data.tilt.readiness.ready) from its own run state.
116
+ if (nativeStates) {
117
+ const states = Object.entries(nativeStates);
118
+ const isRunning = states.some(([, state]) => state.status !== "unknown");
119
+ readiness = isRunning
120
+ ? {
121
+ ready: states.every(([, state]) => state.status === "ready"),
122
+ failures: [],
123
+ pending: states.filter(([, state]) => state.status !== "ready").length,
124
+ timedOut: false,
125
+ enabled: states.map(([name]) => name),
126
+ }
127
+ : null;
128
+ }
102
129
  const data = {
130
+ // Only a project of local processes says so; a container project's report keeps its shape.
131
+ ...(nativeStates ? { runtime: "native" } : {}),
103
132
  tilt: {
104
133
  available: tiltAvailable,
105
134
  resourcesQueried: options.tilt && tiltAvailable,
@@ -20,6 +20,9 @@ import { exportHostPortPlan, getHostPortPlan, writeSavedHostPortPlan, } from "..
20
20
  import { formatHostPortPlan } from "../utils/host-port-plan.js";
21
21
  import { createJsonEmitter } from "../utils/json-output.js";
22
22
  import { toMachineError } from "../utils/machine-output.js";
23
+ import { hasNearbyServiceJson } from "../utils/native-project.js";
24
+ import { formatMixedRuntimeRefusal, isNativeProject, isNativeService, resolveRuntimeMode, } from "../utils/native-service.js";
25
+ import { runNativeUp } from "../utils/native-up.js";
23
26
  import { nativeWindowsStackRefusal } from "../utils/native-windows.js";
24
27
  import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
25
28
  import { findAvailablePort } from "../utils/port-assignment.js";
@@ -38,7 +41,7 @@ import { stopTiltOnPort } from "../utils/tilt-process.js";
38
41
  import { parseTiltPort, resolveTiltPort, secondUpAction, stopTiltForUp, } from "../utils/tilt-startup.js";
39
42
  import { findUnknownServices, resolveOnlySelection } from "../utils/up-only.js";
40
43
  import { tiltGetUiResources, waitForTiltResourcesReady } from "../utils/up-readiness.js";
41
- import { enableDiscoveredStacks } from "./project.js";
44
+ import { enableDiscoveredStacks, writeDefaultProjectConfig } from "./project.js";
42
45
  export function formatUpSuccess(port, options = {}) {
43
46
  const { appUrls = [], serviceHealth = [], showLogsHint = serviceHealth.length > 0 } = options;
44
47
  const lines = [];
@@ -235,6 +238,67 @@ function envFileFailure(error) {
235
238
  exitCode: error instanceof FliskError ? error.exitCode : 2,
236
239
  };
237
240
  }
241
+ /**
242
+ * Decides whether `flisk up` runs the selection as local processes (#2229). Returns null for today's Docker + Tilt path, which
243
+ * a project without any `command` service always takes. Outside a project, a directory whose services all declare `command`
244
+ * becomes the project. Refuses (through `fail`) an unknown stack or service, an invalid service.json, and a run that would mix
245
+ * native and container services.
246
+ */
247
+ export function selectNativeRun(stackName, only, fail, cwd = process.cwd()) {
248
+ const foundRoot = findProjectRoot(cwd);
249
+ if (!foundRoot && !hasNearbyServiceJson(cwd))
250
+ return null;
251
+ const projectRoot = foundRoot ?? cwd;
252
+ let discovered;
253
+ try {
254
+ discovered = discoverResourcesWithProblems(projectRoot);
255
+ }
256
+ catch {
257
+ return null;
258
+ }
259
+ const all = discovered.resources;
260
+ if (!all.some(isNativeService))
261
+ return null;
262
+ if (!foundRoot && !isNativeProject(all))
263
+ return null;
264
+ if (discovered.problems.length > 0) {
265
+ fail("INVALID_SERVICE_CONFIG", discovered.problems
266
+ .map((problem) => `Invalid service.json at ${problem.path}: ${problem.reason}`)
267
+ .join("\n"));
268
+ }
269
+ let requested = all;
270
+ if (stackName) {
271
+ if (!stackExists(stackName, all)) {
272
+ const error = errorFactories.stackNotFound(stackName, discoverStacks(all).map((s) => s.name));
273
+ fail("STACK_NOT_FOUND", error.message, error.suggestions);
274
+ }
275
+ requested = getResourcesForStack(stackName, all);
276
+ }
277
+ if (only) {
278
+ const unknown = findUnknownServices(only, requested);
279
+ if (unknown.length > 0) {
280
+ const error = errorFactories.unknownServices(unknown, requested.map((service) => service.name));
281
+ fail("UNKNOWN_SERVICE", error.message, error.suggestions);
282
+ }
283
+ }
284
+ // A process cannot start before what it depends on, so a stack or --only selection always brings its dependsOn closure.
285
+ const names = only ?? requested.map((service) => service.name);
286
+ const selection = resolveOnlySelection(names, all);
287
+ const { mode, mixed } = resolveRuntimeMode(selection.selected, all);
288
+ if (mode === "container") {
289
+ const containerNames = (selection.selected.some(isNativeService) ? selection.selected : all)
290
+ .filter((service) => !isNativeService(service))
291
+ .map((service) => service.name);
292
+ fail("MIXED_RUNTIME", formatMixedRuntimeRefusal(mixed, containerNames));
293
+ }
294
+ return {
295
+ projectRoot,
296
+ isNewProject: !foundRoot,
297
+ all,
298
+ selected: selection.selected,
299
+ dependencies: only ? selection.dependencies : [],
300
+ };
301
+ }
238
302
  export const upCommand = new Command("up")
239
303
  .description("Start all services (optionally filtered by stack)")
240
304
  .alias("deploy")
@@ -266,6 +330,60 @@ export const upCommand = new Command("up")
266
330
  onFail: (message) => emit?.({ ok: false }, [{ code: "FLISK_VERSION_TOO_OLD", message }]),
267
331
  });
268
332
  }
333
+ // Every selected service declares `command`: run them as local processes, with no Docker, Compose, Tilt or generated
334
+ // files (#2229). Decided before any Docker or Tilt check; a project without `command` services never enters this branch.
335
+ const nativeRun = selectNativeRun(stackName, options.only, (code, message, suggestions) => {
336
+ emit?.({ ok: false }, [
337
+ { code, message, ...(suggestions && suggestions.length > 0 ? { suggestions } : {}) },
338
+ ]);
339
+ return showErrorAndExit(message, 1, suggestions);
340
+ });
341
+ if (nativeRun) {
342
+ const platformRefusal = nativeWindowsUpRefusal(process.platform, process.env.FLISK_ALLOW_NATIVE_WINDOWS);
343
+ if (platformRefusal)
344
+ showErrorAndExit(platformRefusal);
345
+ const { projectRoot } = nativeRun;
346
+ let envFilePath;
347
+ if (options.envFile !== undefined) {
348
+ try {
349
+ envFilePath = selectEnvFile(projectRoot, options.envFile).path;
350
+ }
351
+ catch (error) {
352
+ const { failure, exitCode } = envFileFailure(error);
353
+ emit?.({ ok: false }, [failure]);
354
+ showErrorAndExit(failure.message, exitCode, failure.suggestions);
355
+ }
356
+ }
357
+ if (!nativeRun.isNewProject) {
358
+ enforceSchemaVersionGate(projectRoot, {
359
+ resources: nativeRun.all,
360
+ onInvalid: (message) => emit?.({ ok: false }, [{ code: "INVALID_SCHEMA_VERSION", message }]),
361
+ });
362
+ }
363
+ enforceServiceConfigGate(projectRoot, {
364
+ resources: nativeRun.all,
365
+ onInvalid: (message) => emit?.({ ok: false }, [{ code: "INVALID_SERVICE_CONFIG", message }]),
366
+ });
367
+ if (nativeRun.isNewProject && !options.dryRun) {
368
+ writeDefaultProjectConfig(projectRoot);
369
+ if (!options.quiet) {
370
+ console.log(chalk.gray("Created .flisk/project.json (this directory is now a Flisk project)"));
371
+ }
372
+ }
373
+ await runNativeUp({
374
+ projectRoot,
375
+ selected: nativeRun.selected,
376
+ dependencies: nativeRun.dependencies,
377
+ stackName,
378
+ only: options.only,
379
+ envFilePath,
380
+ dryRun: options.dryRun,
381
+ quiet: options.quiet,
382
+ force: options.force,
383
+ emit,
384
+ });
385
+ return;
386
+ }
269
387
  // The env file of the run. `--env-file` replaces the project .env for this run, so the file is
270
388
  // resolved and read before any prerequisite check, any write and any service starts.
271
389
  const envFileRoot = versionRoot;
@@ -140,7 +140,7 @@ export function detectAllInstallations() {
140
140
  return [...unique.values()];
141
141
  }
142
142
  const BINARY_RELEASE_REPO = "flisk-sh/releases";
143
- const ENGINE_ASSET_NAME = "tdk-cli-engine.tar.gz";
143
+ const ENGINE_ASSET_NAME = "flisk-cli-engine.tar.gz";
144
144
  const CHECKSUMS_ASSET_NAME = "checksums.txt";
145
145
  /** Parses `shasum -a 256` / `sha256sum` output ("<hash> <name>" or "<hash> *<name>"). */
146
146
  /** @internal Exported for unit tests only; not part of the package API. */
@@ -656,7 +656,7 @@ export function createUpgradeCommand() {
656
656
  showErrorAndExit("Flisk Windows v1 supports AMD64 only. Use WSL2 Ubuntu or a 64-bit Intel/AMD PC.");
657
657
  }
658
658
  if (process.platform === "win32") {
659
- showErrorAndExit("No Windows AMD64 binary in this Flisk release. Need asset tdk-windows-amd64.exe.");
659
+ showErrorAndExit("No Windows AMD64 binary in this Flisk release. Need asset flisk-windows-amd64.exe.");
660
660
  }
661
661
  showErrorAndExit(`Unsupported OS: Flisk does not support this OS: ${process.platform}. Supported: linux, darwin, win32.`);
662
662
  }
@@ -1,10 +1,13 @@
1
+ import { isNativeService } from "./native-service.js";
1
2
  import { isApiServiceType } from "./resource-kind.js";
2
3
  /**
3
4
  * Bun runs the generated JS services. A project without any has nothing for Bun to run, so neither `flisk doctor` nor
4
- * `flisk up` needs it. Doctor and the start-up preflight share this rule so they cannot disagree.
5
+ * `flisk up` needs it. A `command` service runs whatever its command names, so it never needs Bun on Flisk's behalf.
6
+ * Doctor and the start-up preflight share this rule so they cannot disagree.
5
7
  */
6
8
  export function projectNeedsBun(resources) {
7
- return resources.some((resource) => isApiServiceType(resource.config?.appType) ||
8
- resource.config?.appType === "frontend" ||
9
- resource.config?.appType === "worker");
9
+ return resources.some((resource) => !isNativeService(resource) &&
10
+ (isApiServiceType(resource.config?.appType) ||
11
+ resource.config?.appType === "frontend" ||
12
+ resource.config?.appType === "worker"));
10
13
  }