@flisk-sh/flisk 1.3.190 → 1.3.192

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 (153) hide show
  1. package/CHANGELOG.md +9 -9
  2. package/LICENSE +1 -1
  3. package/README.md +41 -41
  4. package/Tiltfile +2 -2
  5. package/cli/bin/{flisk.js → tdk.js} +1 -1
  6. package/cli/dist/cli.js +59 -0
  7. package/cli/dist/commands/__tests__/project-e2e-helpers.js +2 -2
  8. package/cli/dist/commands/completion.js +26 -26
  9. package/cli/dist/commands/config.js +1 -1
  10. package/cli/dist/commands/import.js +4 -4
  11. package/cli/dist/commands/maintainers.js +1 -1
  12. package/cli/dist/commands/mcp.js +1 -4
  13. package/cli/dist/commands/project.js +10 -2
  14. package/cli/dist/commands/resource.js +1 -1
  15. package/cli/dist/commands/ui.js +1 -1
  16. package/cli/dist/commands/up.js +77 -3
  17. package/cli/dist/commands/upgrade.js +28 -14
  18. package/cli/dist/generator/extension-fetch.js +3 -3
  19. package/cli/dist/generator/template-engine.js +8 -8
  20. package/cli/dist/mcp/tools.js +2 -5
  21. package/cli/dist/program.js +2 -0
  22. package/cli/dist/utils/cli-command.js +107 -0
  23. package/cli/dist/utils/cold-preflight.js +2 -1
  24. package/cli/dist/utils/constants.js +1 -1
  25. package/cli/dist/utils/doctor-fix.js +4 -5
  26. package/cli/dist/utils/doctor-runtime.js +1 -1
  27. package/cli/dist/utils/doctor-starlark.js +1 -1
  28. package/cli/dist/utils/doctor-wiring.js +2 -2
  29. package/cli/dist/utils/env-selection.js +201 -0
  30. package/cli/dist/utils/env-validator.js +6 -0
  31. package/cli/dist/utils/one-command.js +50 -0
  32. package/cli/dist/utils/platform.js +2 -6
  33. package/cli/dist/utils/project-templates.js +11 -7
  34. package/cli/dist/utils/secret-redaction.js +15 -6
  35. package/cli/dist/utils/service-manifest.js +1 -1
  36. package/cli/dist/utils/smoke.js +2 -2
  37. package/cli/dist/utils/tar.js +1 -1
  38. package/cli/dist-bundle/{chunk-BZ4H7MM4.js → chunk-42IVF3YT.js} +2 -2
  39. package/cli/dist-bundle/chunk-GOEBYLFN.js +2 -0
  40. package/cli/dist-bundle/chunk-KGKKDD4K.js +2 -0
  41. package/cli/dist-bundle/chunk-QR6O2A7L.js +34 -0
  42. package/cli/dist-bundle/chunk-RHSMS45X.js +438 -0
  43. package/cli/dist-bundle/chunk-XYFVXSEO.js +75 -0
  44. package/cli/dist-bundle/cli.js +342 -370
  45. package/cli/dist-bundle/cold-preflight-ZJAU3E7K.js +2 -0
  46. package/cli/dist-bundle/{extension-fetch-J4FFH7TJ.js → extension-fetch-6XNTDF44.js} +1 -1
  47. package/cli/dist-bundle/one-command-A4R272U3.js +4 -0
  48. package/cli/dist-bundle/project-FOAR7E55.js +2 -0
  49. package/cli/dist-bundle/project-templates-BHURA3LT.js +2 -0
  50. package/cli/dist-bundle/url-actions-D6ROTHZG.js +2 -0
  51. package/cli/package.json +2 -4
  52. package/cli/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +1 -1
  53. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +1 -1
  54. package/cli/shared-platform-engineering/docker-templates/install-deps.sh +1 -1
  55. package/cli/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +1 -1
  56. package/cli/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +1 -1
  57. package/cli/templates/Tiltfile.hbs +1 -1
  58. package/discovery/AGENTS.md +1 -1
  59. package/discovery/TILT_DISCOVERY.star +3 -3
  60. package/discovery/Tiltfile +3 -3
  61. package/discovery/config.star +1 -1
  62. package/discovery/constants.star +1 -1
  63. package/discovery/discovery_orchestrator.star +1 -1
  64. package/discovery/identity-management-frontend/service.yaml +1 -1
  65. package/discovery/json_manifest_scanner.star +1 -1
  66. package/discovery/manifest/constants.star +1 -1
  67. package/discovery/manifest/loading.star +2 -2
  68. package/discovery/manifest/normalize.star +1 -1
  69. package/discovery/registry.star +1 -1
  70. package/discovery/resource_snapshot.py +1 -1
  71. package/discovery/resource_snapshot.star +1 -1
  72. package/discovery/test_resource_snapshot.py +2 -2
  73. package/engine/AGENTS.md +7 -7
  74. package/engine/README.md +1 -1
  75. package/engine/Tiltfile +3 -3
  76. package/engine/assets/docker/wake-gateway/decide.ts +1 -1
  77. package/engine/assets/docker/wake-gateway/server.ts +1 -1
  78. package/engine/assets/scripts/mac-m1-resource-monitor.sh +8 -8
  79. package/engine/lifecycle/orchestrator.star +6 -6
  80. package/engine/paths.star +1 -1
  81. package/engine/schemas/project-schema.json +2 -2
  82. package/engine/schemas/service-schema.json +3 -3
  83. package/engine/scripts/debug-container.sh +2 -2
  84. package/engine/spec.master +1 -1
  85. package/engine/topologies/platform/docker/compose/compose.star +1 -1
  86. package/engine/topologies/platform/docker/compose/traefik_standalone.star +1 -1
  87. package/engine/topologies/platform/docker/config/healthcheck.star +1 -1
  88. package/engine/topologies/platform/docker/constants.star +2 -2
  89. package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +3 -3
  90. package/engine/topologies/platform/docker/layers/l1_base_layers.star +1 -1
  91. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +1 -1
  92. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +1 -1
  93. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +1 -1
  94. package/engine/topologies/platform/docker/layers/prisma/constants.star +1 -1
  95. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +2 -2
  96. package/engine/topologies/platform/docker/networking/api_path_constants.star +1 -1
  97. package/engine/topologies/platform/docker/networking/traefik_static_routes.star +1 -1
  98. package/engine/topologies/platform/docker/secrets/env_secrets.star +1 -1
  99. package/engine/topologies/platform/docker/secrets/infisical.star +1 -1
  100. package/engine/topologies/platform/security/secrets.star +1 -1
  101. package/engine/topologies/tilt/common/utils.star +5 -3
  102. package/engine/topologies/tilt/common/utils_debug.star +1 -1
  103. package/engine/topologies/tilt/common/utils_deterministic.star +1 -1
  104. package/engine/topologies/tilt/common/utils_env.star +23 -3
  105. package/engine/topologies/tilt/config/profiles.star +1 -1
  106. package/engine/topologies/tilt/generators/infisical/organization.star +1 -1
  107. package/engine/topologies/tilt/generators/npmrc.star +4 -4
  108. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +1 -1
  109. package/engine/topologies/tilt/generators/vite/helpers.star +1 -1
  110. package/engine/topologies/tilt/generators/vite/templates.star +1 -1
  111. package/engine/topologies/tilt/manifest/json_to_yaml.py +2 -2
  112. package/engine/topologies/tilt/manifest/loader.star +1 -1
  113. package/engine/topologies/tilt/manifest/schema.star +2 -2
  114. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +3 -3
  115. package/engine/topologies/tilt/manifest/tests/test_validator.star +2 -2
  116. package/engine/topologies/tilt/manifest/yaml_to_json.py +1 -1
  117. package/engine/topologies/tilt/resources/databases.star +1 -1
  118. package/engine/topologies/tilt/resources/infra-loader.star +8 -6
  119. package/engine/topologies/tilt/resources/libs_paths.star +1 -1
  120. package/engine/topologies/tilt/resources/libs_resource_definition.star +1 -1
  121. package/engine/topologies/tilt/resources/libs_setup.star +1 -1
  122. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +13 -8
  123. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +1 -1
  124. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +1 -1
  125. package/engine/topologies/tilt/resources/watchers.star +7 -7
  126. package/engine/where-things-live.star +1 -1
  127. package/examples/tdk-example/LICENSE +1 -1
  128. package/examples/tdk-example/Tiltfile +7 -7
  129. package/examples/tdk-example/services/shop/orders-api/service.json +1 -1
  130. package/examples/tdk-example/services/shop/orders-app/index.html +1 -1
  131. package/examples/tdk-example/services/shop/orders-app/service.json +1 -1
  132. package/examples/tdk-example/services/shop/orders-worker/service.json +1 -1
  133. package/ext/Tiltfile +3 -3
  134. package/ext/ide-components/code_viewer/server.py +1 -1
  135. package/ext/ide-components/config_inspector/server.py +1 -1
  136. package/ext/ide-components/file_browser/server.py +1 -1
  137. package/ext/ide-components/shared/config_service.py +2 -2
  138. package/ext/ide-components/shared/file_utils.py +2 -2
  139. package/ext/ide-components/shared/http_utils.py +1 -1
  140. package/ext/ide-components/shared/syntax_highlighter.py +1 -1
  141. package/ext/ide-components/shared/template_engine.py +1 -1
  142. package/ext/ide-components/shared/templates/base.html +1 -1
  143. package/ext/ide-components/shared/test_config_service.py +1 -1
  144. package/ext/ide-components/shared/tilt_integration.py +1 -1
  145. package/ext/ui-enhancements/tiltfile.py +1 -1
  146. package/package.json +6 -10
  147. package/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +1 -1
  148. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +1 -1
  149. package/shared-platform-engineering/docker-templates/install-deps.sh +1 -1
  150. package/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +1 -1
  151. package/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +1 -1
  152. package/cli/dist-bundle/chunk-CLCUIDBH.js +0 -510
  153. package/cli/dist-bundle/cold-preflight-SY26O2IG.js +0 -2
@@ -0,0 +1,107 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { execFileSync } from "node:child_process";
4
+ import { accessSync, constants, readdirSync } from "node:fs";
5
+ import { delimiter, join } from "node:path";
6
+ /** The command users install and type. */
7
+ export const CLI_COMMAND = "flisk";
8
+ /** The npm package that installs it. */
9
+ export const CLI_PACKAGE = "@flisk-sh/flisk";
10
+ /** The package the CLI was published under before the rename. It owns the old `flisk` command. */
11
+ export const LEGACY_PACKAGE = "@tdk-landscape/tdk-cli-core";
12
+ /** True when the command can be typed as-is, so `flisk ...` resolves. */
13
+ export function isCommandOnPath(command = CLI_COMMAND, env = process.env) {
14
+ const path = env.PATH ?? env.Path ?? "";
15
+ for (const dir of path.split(delimiter)) {
16
+ if (!dir)
17
+ continue;
18
+ const candidates = [
19
+ join(dir, command),
20
+ join(dir, `${command}.cmd`),
21
+ join(dir, `${command}.exe`),
22
+ ];
23
+ for (const candidate of candidates) {
24
+ try {
25
+ accessSync(candidate, constants.X_OK);
26
+ return true;
27
+ }
28
+ catch {
29
+ // Not on this PATH entry; keep looking.
30
+ }
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+ /**
36
+ * A folder to start a project in. It follows the platform convention: Documents when the
37
+ * machine has one, a plain folder in the home directory otherwise.
38
+ */
39
+ export function suggestedWorkspace(env = process.env) {
40
+ const home = env.HOME ?? env.USERPROFILE ?? "";
41
+ if (!home)
42
+ return "flisk-workspace";
43
+ const documents = join(home, "Documents");
44
+ try {
45
+ accessSync(documents, constants.F_OK);
46
+ return join(documents, "flisk");
47
+ }
48
+ catch {
49
+ return join(home, "flisk");
50
+ }
51
+ }
52
+ /** True when the directory holds no visible entries, so a project can start there as-is. */
53
+ export function isEmptyWorkspace(dir) {
54
+ try {
55
+ return readdirSync(dir).every((entry) => entry.startsWith("."));
56
+ }
57
+ catch {
58
+ return true;
59
+ }
60
+ }
61
+ /**
62
+ * The next step to print once the machine is ready.
63
+ *
64
+ * An ephemeral run (`npx @flisk-sh/flisk`) never installs the command, so pointing at
65
+ * `flisk ...` there would fail with "command not found"; that case says how to install it
66
+ * instead. A project also should not start in whatever folder the shell happens to be in,
67
+ * so a directory that already holds files gets a workspace to move into first.
68
+ */
69
+ export function readyNextStep(env = process.env, cwd = process.cwd()) {
70
+ if (!isCommandOnPath(CLI_COMMAND, env)) {
71
+ return (`The \`${CLI_COMMAND}\` command is not on PATH (this run is not a global install). ` +
72
+ `Install it with \`npm install -g ${CLI_PACKAGE}\`, then run \`${CLI_COMMAND} project --yes\`. ` +
73
+ `If npm reports EEXIST for \`flisk\`, the old package still owns that command: ` +
74
+ `run \`npm uninstall -g ${LEGACY_PACKAGE}\` first.`);
75
+ }
76
+ if (isEmptyWorkspace(cwd)) {
77
+ return `Next: ${CLI_COMMAND} project --yes`;
78
+ }
79
+ const workspace = suggestedWorkspace(env);
80
+ return (`Next: mkdir -p ${workspace} && cd ${workspace} && ${CLI_COMMAND} project --yes\n` +
81
+ `(${CLI_COMMAND} project writes .tdk/ and the master configs into the current folder, ` +
82
+ `so it starts clean in a folder of its own. Or clone a starter: ${CLI_COMMAND} project example)`);
83
+ }
84
+ /**
85
+ * Installs the CLI globally when it is only running ephemerally (`npx @flisk-sh/flisk`),
86
+ * so the `flisk` command keeps working after the run. The old package is removed first:
87
+ * it owns the `flisk` command and would make the install fail with EEXIST.
88
+ */
89
+ export function ensureGlobalInstall(env = process.env) {
90
+ if (isCommandOnPath(CLI_COMMAND, env))
91
+ return "present";
92
+ const manager = env.npm_execpath?.includes("bun") ? "bun" : "npm";
93
+ const remove = manager === "bun" ? "bun remove -g" : "npm uninstall -g";
94
+ try {
95
+ execFileSync(remove, [LEGACY_PACKAGE], { stdio: "ignore", timeout: 120000 });
96
+ }
97
+ catch {
98
+ // Not installed under the old name, or the manager is missing.
99
+ }
100
+ try {
101
+ execFileSync(manager, ["install", "-g", CLI_PACKAGE], { stdio: "inherit", timeout: 300000 });
102
+ return "installed";
103
+ }
104
+ catch {
105
+ return "failed";
106
+ }
107
+ }
@@ -6,6 +6,7 @@ import { join } from "node:path";
6
6
  import { checkDockerCompose, checkDockerRuntime, checkDockerVersions, checkTilt, DOCTOR_FIXES, } from "../commands/doctor.js";
7
7
  import { BUN_FLOOR_LABEL, bunMeetsFloor } from "./bun-floor.js";
8
8
  import { projectNeedsBun } from "./bun-requirement.js";
9
+ import { readyNextStep } from "./cli-command.js";
9
10
  import { PROJECT_JSON } from "./constants.js";
10
11
  import { checkNatsBroker } from "./doctor-wiring.js";
11
12
  import { getHostPortPlan } from "./host-port-config.js";
@@ -200,7 +201,7 @@ export async function runColdPreflight(opts = {}) {
200
201
  ? ""
201
202
  : inProject
202
203
  ? "Environment ready for Flisk"
203
- : "This machine is ready for Flisk.\nNext: flisk project --yes";
204
+ : `This machine is ready for Flisk.\n${readyNextStep()}`;
204
205
  return { ok: failures.length === 0, inProject, items: failures, header, footer };
205
206
  }
206
207
  /** Formats only failed preflight checks and the applicable readiness message. */
@@ -89,7 +89,7 @@ export const STANDARD_PORTS = {
89
89
  tiltUi: 10350,
90
90
  traefik: 8080,
91
91
  };
92
- export const QUICKSTART_DOCS_URL = "https://tdk-landscape.github.io/tdk-website/docs/quickstart/";
92
+ export const QUICKSTART_DOCS_URL = "https://flisk.sh/docs/quickstart/";
93
93
  export const BASE_DOMAIN_DEFAULT = "tdk-project.localhost";
94
94
  export const APP_SUBDOMAIN_PREFIX = "app";
95
95
  export const API_SUBDOMAIN_PREFIX = "api";
@@ -7,13 +7,12 @@ import { join } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import { DISK_WARN_GIB } from "./doctor-host.js";
9
9
  import { findProjectTiltUpPids, projectNetworkNames, tdkNetworkOwner, tdkNetworkPrefixes, } from "./down-cleanup.js";
10
- import { executableName, legacyExecutableName } from "./platform.js";
11
10
  import { tiltGetUiResources } from "./up-readiness.js";
12
11
  import { findAllOnPath } from "./which.js";
13
12
  const execFilePromise = promisify(execFile);
14
13
  const DOCKER_TIMEOUT_MS = 15_000;
15
14
  /** The networks `flisk doctor` creates to test capacity; an interrupted run leaves one behind. */
16
- const PROBE_NETWORK = /^flisk_doctor_probe_(\d+)$/;
15
+ const PROBE_NETWORK = /^tdk_doctor_probe_(\d+)$/;
17
16
  const runDocker = async (args) => {
18
17
  const { stdout } = await execFilePromise("docker", args, {
19
18
  encoding: "utf-8",
@@ -335,8 +334,8 @@ async function readTdkVersion(path) {
335
334
  return null;
336
335
  }
337
336
  }
338
- /** Every distinct Flisk install on PATH (symlinks to one binary count once), in PATH order. */
339
- export async function findTdkInstalls(paths = [...findAllOnPath(executableName()), ...findAllOnPath(legacyExecutableName())], readVersion = readTdkVersion) {
337
+ /** Every distinct `flisk` on PATH (symlinks to one binary count once), in PATH order. */
338
+ export async function findTdkInstalls(paths = findAllOnPath("flisk"), readVersion = readTdkVersion) {
340
339
  const seen = new Set();
341
340
  const installs = [];
342
341
  for (const path of paths) {
@@ -367,7 +366,7 @@ export async function checkDuplicateTdkInstalls(installs) {
367
366
  didPass: false,
368
367
  isWarning: true,
369
368
  message: `${found.length} flisk installs on PATH${versions.size > 1 ? " with different versions" : ""}: ${list}`,
370
- fix: "Keep one: remove the others, or run flisk upgrade. The first on PATH is the one your shell runs; tools that launch /usr/local/bin/flisk directly may use another",
369
+ fix: "Keep one: remove the others, or run flisk upgrade. The first on PATH is the one your shell runs; tools that launch /usr/local/bin/tdk directly may use another",
371
370
  };
372
371
  }
373
372
  /**
@@ -311,7 +311,7 @@ export function checkPrivateNpmRegistry(exec = execSync, projectRoot = findProje
311
311
  return {
312
312
  name: "Private npm registry",
313
313
  didPass: false,
314
- message: `Private npm registry (Verdaccio) is not reachable at ${registryUrl}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @tdk-landscape/* and project-scoped packages.`,
314
+ message: `Private npm registry (Verdaccio) is not reachable at ${registryUrl}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @flisk-sh/* and project-scoped packages.`,
315
315
  fix: "Start the registry with `bun run verdaccio:start` (or `docker compose -f docker-compose.verdaccio.yml up -d`), publish packages with `bun run publish:all` / `scripts/build/publish-to-verdaccio.sh`, then `tilt trigger` failed resources or re-run `flisk up`.",
316
316
  };
317
317
  }
@@ -22,7 +22,7 @@ export function findPrivateStarlarkLoadExports(content) {
22
22
  return Array.from(privateExports).sort();
23
23
  }
24
24
  export function resolveStarlarkLoadTarget(file, modulePath, projectRoot) {
25
- if (modulePath === "ext://flisk" || modulePath === "ext://tdk-cli") {
25
+ if (modulePath === "ext://tdk-cli") {
26
26
  return join(projectRoot, ".tdk", ".tdk-out", "tdk-cli-ext", "Tiltfile");
27
27
  }
28
28
  if (modulePath.startsWith(".") || (!modulePath.includes("://") && !modulePath.startsWith("@"))) {
@@ -667,7 +667,7 @@ export function checkTiltInstances(projectRoot = findProjectRoot() ?? process.cw
667
667
  return { name: "Tilt processes", didPass: true, message: "No stray Tilt processes" };
668
668
  }
669
669
  export async function checkDockerNetworkCapacity(exec = execAsync) {
670
- const probe = `flisk_doctor_probe_${process.pid}`;
670
+ const probe = `tdk_doctor_probe_${process.pid}`;
671
671
  try {
672
672
  await exec(`docker network create ${probe}`, EXEC_TIMEOUT_MS);
673
673
  }
@@ -692,7 +692,7 @@ export async function checkDockerNetworkCapacity(exec = execAsync) {
692
692
  await exec(`docker network rm ${probe}`, EXEC_TIMEOUT_MS);
693
693
  }
694
694
  catch {
695
- // A leftover probe network is harmless and flisk_doctor_probe_* is easy to prune.
695
+ // A leftover probe network is harmless and tdk_doctor_probe_* is easy to prune.
696
696
  }
697
697
  return { name: "Docker networks", didPass: true, message: "Docker can create networks" };
698
698
  }
@@ -0,0 +1,201 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
4
+ import { isAbsolute, relative, resolve, sep } from "node:path";
5
+ import { COMPLETED_ENV_VARS, parseEnv } from "./env-validator.js";
6
+ import { TdkError } from "./errors.js";
7
+ /**
8
+ * The environment variable the engine reads the env file of the run from. The CLI resolves the
9
+ * requested path and exports it, so the engine reads one file for the whole run: the Tilt
10
+ * environment (engine/topologies/tilt/common/utils_env.star) and the Compose interpolation alike.
11
+ */
12
+ export const PROJECT_ENV_FILE_VAR = "TDK_ENV_FILE";
13
+ /**
14
+ * The keys the platform needs in the env file of a run. They are the keys `completeEnvFile` keeps
15
+ * complete in the project `.env`: the Tilt environment, the Postgres password and the JWT secret.
16
+ * A selected file replaces the project `.env` for the run, so it has to carry them too.
17
+ */
18
+ export const REQUIRED_ENV_FILE_KEYS = COMPLETED_ENV_VARS;
19
+ /** Exit code for a rejected `--env-file`: invalid arguments (see docs/reference/machine-readable-cli.md). */
20
+ export const ENV_FILE_ERROR_EXIT_CODE = 2;
21
+ /** Exit code when the project's vendored engine is too old for the option: a blocking finding. */
22
+ export const ENV_FILE_ENGINE_EXIT_CODE = 1;
23
+ /**
24
+ * A rejected `--env-file`. It carries a code so a JSON consumer can tell an unusable file from
25
+ * another failure, and it is a TdkError so the CLI shows it in the house style.
26
+ */
27
+ export class EnvFileError extends TdkError {
28
+ code;
29
+ constructor(code, message, suggestions = [], exitCode = ENV_FILE_ERROR_EXIT_CODE) {
30
+ super(message, suggestions, exitCode);
31
+ this.code = code;
32
+ this.name = "EnvFileError";
33
+ }
34
+ }
35
+ /** True when the path is inside the project directory. */
36
+ export function isInsideProject(projectRoot, path) {
37
+ const relativePath = relative(resolve(projectRoot), resolve(path));
38
+ if (relativePath === "")
39
+ return true;
40
+ if (isAbsolute(relativePath))
41
+ return false;
42
+ return relativePath !== ".." && !relativePath.startsWith(`..${sep}`);
43
+ }
44
+ /**
45
+ * Resolves the requested env file to an absolute path inside the project.
46
+ *
47
+ * A relative path is resolved against the current directory, the way `docker compose --env-file`
48
+ * and Node.js' own `--env-file` resolve it. Resolving it the same way keeps the check Node.js does
49
+ * before the CLI starts in step with this one.
50
+ *
51
+ * The file has to live in the project: it is the project env file of the run, and the wake gateway
52
+ * (which mounts the project read-only and runs Compose itself) can only read a file inside it. A
53
+ * path outside the project would also turn the flag into a way to read any file on the machine.
54
+ *
55
+ * @throws TdkError when the path is empty, leaves the project, or does not exist
56
+ */
57
+ export function resolveEnvFilePath(projectRoot, requested, options = {}) {
58
+ const trimmed = requested.trim();
59
+ if (!trimmed) {
60
+ throw new EnvFileError("ENV_FILE_INVALID", "No env file was given", [
61
+ "Pass the path of a project env file, for example: flisk up --env-file .env.integration",
62
+ ]);
63
+ }
64
+ const baseDir = options.baseDir ?? process.cwd();
65
+ const absolute = isAbsolute(trimmed) ? resolve(trimmed) : resolve(baseDir, trimmed);
66
+ if (!existsSync(absolute)) {
67
+ throw new EnvFileError("ENV_FILE_INVALID", `Env file not found: ${trimmed}`, [
68
+ "Create it first, for example: cp .env .env.integration",
69
+ "A relative path is resolved against the current directory",
70
+ ]);
71
+ }
72
+ if (!statSync(absolute).isFile()) {
73
+ throw new EnvFileError("ENV_FILE_INVALID", `Env file is not a file: ${trimmed}`, [
74
+ "Point --env-file at an env file, not at a directory",
75
+ ]);
76
+ }
77
+ // The real paths decide whether the file belongs to the project: a project directory reached
78
+ // through a symbolic link (macOS /var -> /private/var) is still the project, and a symbolic link
79
+ // inside the project can point at a file outside it. The engine reads the file through the
80
+ // project mount, so a file outside the project would break the run in the wake gateway.
81
+ if (!isInsideProject(realPathOrSelf(projectRoot), realPathOrSelf(absolute))) {
82
+ throw new EnvFileError("ENV_FILE_INVALID", `Env file is outside the project: ${trimmed}`, [
83
+ "The env file of a run belongs to the project, next to its .env",
84
+ `The project directory is ${projectRoot}`,
85
+ ]);
86
+ }
87
+ return absolute;
88
+ }
89
+ /** The real path of a file, or the path itself when it cannot be resolved. */
90
+ function realPathOrSelf(path) {
91
+ try {
92
+ return realpathSync(path);
93
+ }
94
+ catch {
95
+ return path;
96
+ }
97
+ }
98
+ /**
99
+ * Reads the env file. The values are secrets, so they are only used to configure the run and to
100
+ * mask output; they are never printed.
101
+ *
102
+ * @throws TdkError when the file cannot be read
103
+ */
104
+ export function readEnvFileValues(path) {
105
+ let content;
106
+ try {
107
+ content = readFileSync(path, "utf-8");
108
+ }
109
+ catch (error) {
110
+ throw new EnvFileError("ENV_FILE_INVALID", `Could not read env file ${path}: ${error instanceof Error ? error.message : String(error)}`, ["Check the permissions of the env file"]);
111
+ }
112
+ return parseEnv(content);
113
+ }
114
+ /** The platform keys the file is missing, so the run would start without them. */
115
+ export function missingRequiredEnvKeys(values) {
116
+ return REQUIRED_ENV_FILE_KEYS.filter((key) => !(values.get(key) ?? "").trim());
117
+ }
118
+ /**
119
+ * Selects the env file of the run and checks that it can carry the run.
120
+ *
121
+ * The selected file replaces the project `.env` for the run, so it has to carry the same keys. Flisk
122
+ * does not complete it: a file the user named may well be committed, and writing a generated
123
+ * credential into it would leak one.
124
+ *
125
+ * @throws TdkError when the file is unusable or misses a key the platform needs
126
+ */
127
+ export function selectEnvFile(projectRoot, requested, options = {}) {
128
+ const path = resolveEnvFilePath(projectRoot, requested, options);
129
+ const values = readEnvFileValues(path);
130
+ const missing = missingRequiredEnvKeys(values);
131
+ if (missing.length > 0) {
132
+ throw new EnvFileError("ENV_FILE_INVALID", `Env file ${requested} is missing ${missing.join(", ")}`, [
133
+ "The selected env file replaces the project .env for this run, so it needs the same keys",
134
+ "Copy them from the project .env (they are per-project credentials)",
135
+ "Run without --env-file to use the project .env",
136
+ ]);
137
+ }
138
+ return {
139
+ path,
140
+ relativePath: relative(projectRoot, path).split(sep).join("/"),
141
+ values,
142
+ };
143
+ }
144
+ /**
145
+ * Points the engine at the selected env file for this run. The engine reads the same variable for
146
+ * the Tilt environment and for the Compose interpolation, so one file drives both.
147
+ */
148
+ export function applyEnvFileSelection(selection) {
149
+ process.env[PROJECT_ENV_FILE_VAR] = selection.path;
150
+ }
151
+ /**
152
+ * The vendored engine copy that resolves the env file of the run. A project vendors it at generation
153
+ * time, so a project created before this option existed runs an engine that ignores the selection.
154
+ */
155
+ const VENDORED_ENGINE_ENV_FILE = ".tdk/.tdk-out/tdk-cli-ext/engine/topologies/tilt/common/utils_env.star";
156
+ /**
157
+ * Refuses the run when the project's vendored engine cannot honour the selection. Without this check
158
+ * the flag would be ignored silently and the run would use the project .env after all.
159
+ *
160
+ * @throws TdkError when the vendored engine is older than this option
161
+ */
162
+ export function assertEngineSupportsEnvFile(projectRoot) {
163
+ const vendored = resolve(projectRoot, VENDORED_ENGINE_ENV_FILE);
164
+ if (!existsSync(vendored))
165
+ return;
166
+ let content;
167
+ try {
168
+ content = readFileSync(vendored, "utf-8");
169
+ }
170
+ catch {
171
+ // An unreadable engine copy fails on its own, with a clearer message than this one.
172
+ return;
173
+ }
174
+ if (content.includes(PROJECT_ENV_FILE_VAR))
175
+ return;
176
+ throw new EnvFileError("ENV_FILE_ENGINE_TOO_OLD", "The Flisk engine in this project does not support --env-file yet", [
177
+ "Run `flisk config regenerate` to refresh the vendored engine, then run `flisk up --env-file ...` again",
178
+ `The engine copy is at ${VENDORED_ENGINE_ENV_FILE}`,
179
+ ], ENV_FILE_ENGINE_EXIT_CODE);
180
+ }
181
+ /**
182
+ * A warning when the selected env file would break the local database. The Postgres volume keeps the
183
+ * password it was created with, so a different DB_PASSWORD does not change it.
184
+ */
185
+ export function envFileWarning(projectRoot, selection) {
186
+ const projectEnvPath = resolve(projectRoot, ".env");
187
+ if (!existsSync(projectEnvPath))
188
+ return null;
189
+ let projectValues;
190
+ try {
191
+ projectValues = parseEnv(readFileSync(projectEnvPath, "utf-8"));
192
+ }
193
+ catch {
194
+ return null;
195
+ }
196
+ const projectPassword = (projectValues.get("DB_PASSWORD") ?? "").trim();
197
+ const selectedPassword = (selection.values.get("DB_PASSWORD") ?? "").trim();
198
+ if (!projectPassword || !selectedPassword || projectPassword === selectedPassword)
199
+ return null;
200
+ return "The selected env file sets a different DB_PASSWORD than the project .env. The local Postgres volume keeps the password it was created with, so the database may refuse the new one.";
201
+ }
@@ -53,6 +53,12 @@ const REQUIRED_ENV_VARS = [
53
53
  ];
54
54
  const generateDbPassword = () => randomBytes(16).toString("hex");
55
55
  const generateJwtSecret = () => randomBytes(32).toString("hex");
56
+ /**
57
+ * The keys `completeEnvFile` keeps complete in the project `.env`. An env file that replaces the
58
+ * project `.env` for a run (`flisk up --env-file`) carries the same keys, so it is read from here
59
+ * instead of from a second list.
60
+ */
61
+ export const COMPLETED_ENV_VARS = REQUIRED_ENV_VARS.filter((v) => v.complete).map((v) => v.name);
56
62
  function generatedValue(name, password) {
57
63
  if (name === "DB_PASSWORD")
58
64
  return password;
@@ -0,0 +1,50 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { spawn } from "node:child_process";
4
+ /** The marker the stack prints before the frontend URLs. */
5
+ const FRONTEND_MARKER = "Frontend URLs";
6
+ /** Picks the first frontend URL out of the `flisk up` output, if it printed one. */
7
+ export function findFrontendUrl(output) {
8
+ const lines = output.split("\n");
9
+ const markerIndex = lines.findIndex((line) => line.includes(FRONTEND_MARKER));
10
+ if (markerIndex === -1)
11
+ return undefined;
12
+ for (const line of lines.slice(markerIndex + 1)) {
13
+ const match = line.match(/^\s*-\s+\S+:\s+(https?:\/\/\S+)/);
14
+ if (match)
15
+ return match[1];
16
+ if (line.trim() === "" || line.includes("URLs:"))
17
+ break;
18
+ }
19
+ return undefined;
20
+ }
21
+ /**
22
+ * Runs `flisk up` in the project directory and streams its output to the terminal. The stack
23
+ * keeps running in the foreground, so the caller is told about the frontend URL as soon as it
24
+ * shows up instead of waiting for the process to exit.
25
+ */
26
+ export function runStack(binPath, cwd, env = process.env, spawnFn = spawn, onFrontendUrl) {
27
+ return new Promise((resolve) => {
28
+ const child = spawnFn(process.execPath, [binPath, "up"], {
29
+ cwd,
30
+ env,
31
+ stdio: ["inherit", "pipe", "inherit"],
32
+ });
33
+ let output = "";
34
+ let announced = false;
35
+ child.stdout?.on("data", (chunk) => {
36
+ const text = String(chunk);
37
+ output += text;
38
+ process.stdout.write(text);
39
+ if (!announced) {
40
+ const url = findFrontendUrl(output);
41
+ if (url) {
42
+ announced = true;
43
+ onFrontendUrl?.(url);
44
+ }
45
+ }
46
+ });
47
+ child.on("error", (error) => resolve({ code: 1, output: `${output}\n${error.message}` }));
48
+ child.on("close", (code) => resolve({ code, output }));
49
+ });
50
+ }
@@ -33,18 +33,14 @@ export function binaryAssetNameFor(platformName, architecture) {
33
33
  return null;
34
34
  if (osName === "windows" && archName !== "amd64")
35
35
  return null;
36
- const base = `flisk-${osName}-${archName}`;
36
+ const base = `tdk-${osName}-${archName}`;
37
37
  return osName === "windows" ? `${base}.exe` : base;
38
38
  }
39
39
  export function binaryAssetName() {
40
40
  return binaryAssetNameFor(process.platform, process.arch);
41
41
  }
42
42
  export function executableName() {
43
- return isWindows() ? "flisk.exe" : "flisk";
44
- }
45
- /** Installs published before the Flisk rename shipped the binary as `tdk`. */
46
- export function legacyExecutableName() {
47
- return isWindows() ? "tdk.exe" : "tdk";
43
+ return isWindows() ? "tdk.exe" : "flisk";
48
44
  }
49
45
  export function pathLookups(binName, windows = isWindows()) {
50
46
  if (!windows)
@@ -1,32 +1,36 @@
1
1
  /** Public starter repositories plus the bundled default product example. */
2
2
  export const PROJECT_TEMPLATES = {
3
+ demo: {
4
+ repo: "https://github.com/flisk-sh/tdk-restaurant-example.git",
5
+ description: "The demo stack: restaurant ops with reservations, kitchen and floor control",
6
+ },
3
7
  restaurant: {
4
- repo: "https://github.com/tdk-landscape/tdk-restaurant-example.git",
8
+ repo: "https://github.com/flisk-sh/tdk-restaurant-example.git",
5
9
  description: "Restaurant ops: reservations, kitchen, floor control (Hono + Vite + Bun)",
6
10
  },
7
11
  saas: {
8
- repo: "https://github.com/tdk-landscape/tdk-saas-starter.git",
12
+ repo: "https://github.com/flisk-sh/tdk-saas-starter.git",
9
13
  description: "SaaS starter: account dashboard and a working checkout button (Hono + Vite + Bun)",
10
14
  },
11
15
  erp: {
12
- repo: "https://github.com/tdk-landscape/tdk-erp-system.git",
16
+ repo: "https://github.com/flisk-sh/tdk-erp-system.git",
13
17
  description: "Enterprise ERP: 100 microservices across 7 business domains",
14
18
  },
15
19
  "user-management": {
16
- repo: "https://github.com/tdk-landscape/tdk-user-management.git",
20
+ repo: "https://github.com/flisk-sh/tdk-user-management.git",
17
21
  description: "User/auth management services",
18
22
  },
19
23
  ecommerce: {
20
- repo: "https://github.com/tdk-landscape/tdk-ecommerce-example.git",
24
+ repo: "https://github.com/flisk-sh/tdk-ecommerce-example.git",
21
25
  description: "Vue 3 storefront and Hono catalog API (needs Flisk 1.3.75+ for --framework vue)",
22
26
  },
23
27
  example: {
24
- repo: "https://github.com/tdk-landscape/tdk-example.git",
28
+ repo: "https://github.com/flisk-sh/tdk-example.git",
25
29
  bundledPath: "examples/tdk-example",
26
30
  description: "Hono + Postgres + NATS worker + Vite example with a routed write path",
27
31
  },
28
32
  "agent-stack": {
29
- repo: "https://github.com/tdk-landscape/tdk-agent-stack.git",
33
+ repo: "https://github.com/flisk-sh/tdk-agent-stack.git",
30
34
  description: "AI agent stack: Bun agent + MCP tool server + Postgres + NATS",
31
35
  },
32
36
  };
@@ -21,8 +21,15 @@ export const MIN_REDACTED_VALUE_LENGTH = 8;
21
21
  * Values of the project's own .env file, longest first so a value that contains another is masked whole. Throws when the
22
22
  * file exists but cannot be read: continuing without the values would print secrets unmasked.
23
23
  */
24
- export function envSecretValues(projectRoot) {
25
- const envPath = join(projectRoot, ".env");
24
+ export function envSecretValues(projectRoot, extraEnvPaths = []) {
25
+ const values = [
26
+ ...secretValuesOf(join(projectRoot, ".env")),
27
+ ...extraEnvPaths.flatMap((path) => secretValuesOf(path)),
28
+ ];
29
+ return [...new Set(values)].sort((a, b) => b.length - a.length);
30
+ }
31
+ /** The values of one env file, long enough to be worth masking. Throws when the file cannot be read. */
32
+ function secretValuesOf(envPath) {
26
33
  if (!existsSync(envPath))
27
34
  return [];
28
35
  let content;
@@ -32,8 +39,7 @@ export function envSecretValues(projectRoot) {
32
39
  catch (error) {
33
40
  throw new EnvUnreadableError(envPath, error instanceof Error ? error.message : String(error));
34
41
  }
35
- const values = [...parseEnv(content).values()].filter((value) => value.length >= MIN_REDACTED_VALUE_LENGTH);
36
- return [...new Set(values)].sort((a, b) => b.length - a.length);
42
+ return [...parseEnv(content).values()].filter((value) => value.length >= MIN_REDACTED_VALUE_LENGTH);
37
43
  }
38
44
  export function redactSecrets(text, secrets) {
39
45
  let result = text;
@@ -58,11 +64,14 @@ export function redactValue(value, secrets) {
58
64
  * A redactor for text that may hold the project's own secrets. When the project's .env exists but cannot be read, the values
59
65
  * are unknown, so the redactor throws instead of guessing: the caller then shows and stores nothing rather than raw text. The
60
66
  * original error is kept, so a caller can still tell an unreadable .env from another failure.
67
+ *
68
+ * `extraEnvPaths` are the other env files of the run (the file `flisk up --env-file` selected): their values are secrets as
69
+ * much as the ones in the project .env.
61
70
  */
62
- export function createEnvRedactor(projectRoot) {
71
+ export function createEnvRedactor(projectRoot, extraEnvPaths = []) {
63
72
  let secrets;
64
73
  try {
65
- secrets = envSecretValues(projectRoot);
74
+ secrets = envSecretValues(projectRoot, extraEnvPaths);
66
75
  }
67
76
  catch (error) {
68
77
  return () => {
@@ -3,7 +3,7 @@
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { validateSmoke } from "./smoke.js";
5
5
  export const SERVICE_MANIFEST_SCHEMA_VERSION = 1;
6
- export const SERVICE_MANIFEST_SCHEMA_URL = "https://tdk-landscape.github.io/schema.service.json";
6
+ export const SERVICE_MANIFEST_SCHEMA_URL = "https://flisk.sh/schema.service.json";
7
7
  /**
8
8
  * Fields that are still read, or still tolerated, but should not be used. The warning says what replaces each one. `jwtSecret` is
9
9
  * ignored outright: a secret must not be committed (see docs/environment.md).
@@ -389,9 +389,9 @@ export function formatSmokeFailure(result) {
389
389
  * The dependencies that keep smoke text safe: the project's own secret values are masked before anything is shown or stored.
390
390
  * Both `flisk up` and `flisk doctor` use them, so neither prints a body that holds a secret.
391
391
  */
392
- export function safeSmokeDeps(projectRoot, warn) {
392
+ export function safeSmokeDeps(projectRoot, warn, options = {}) {
393
393
  return {
394
- redact: createEnvRedactor(projectRoot),
394
+ redact: createEnvRedactor(projectRoot, options.envFiles ?? []),
395
395
  ...(warn ? { warn } : {}),
396
396
  };
397
397
  }
@@ -10,7 +10,7 @@ import { join } from "node:path";
10
10
  * implementation - available by default on macOS and Linux.
11
11
  */
12
12
  export function extractTarball(data, destDir) {
13
- const tempDir = mkdtempSync(join(tmpdir(), "flisk-ext-"));
13
+ const tempDir = mkdtempSync(join(tmpdir(), "tdk-ext-"));
14
14
  const tmpFile = join(tempDir, "bundle.tar.gz");
15
15
  writeFileSync(tmpFile, data);
16
16
  mkdirSync(destDir, { recursive: true });