@tdk-landscape/tdk-cli-core 1.3.122 → 1.3.124

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/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Socket Badge](https://badge.socket.dev/npm/package/@tdk-landscape/tdk-cli-core/latest)](https://socket.dev/npm/package/@tdk-landscape/tdk-cli-core/overview)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
13
 
14
- English | [简体中文](README-zh_cn.md) | [繁體中文](README-zh_tw.md) | [日本語](README-ja.md) | [한국어](README-ko.md)
14
+ English | [简体中文](docs/README-zh_cn.md) | [繁體中文](docs/README-zh_tw.md) | [日本語](docs/README-ja.md) | [한국어](docs/README-ko.md)
15
15
 
16
16
  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.
17
17
 
@@ -5,6 +5,7 @@ import chalk from "chalk";
5
5
  import { Command } from "commander";
6
6
  import { hasVerdaccioLicense } from "../generator/extension-fetch.js";
7
7
  import { DEVCONTAINER_DOCKER_FIX, detectHost, isContainerHost, WEBCONTAINER_DOCS, WEBCONTAINER_UP_MESSAGE, } from "../utils/agent-host.js";
8
+ import { projectNeedsBun } from "../utils/bun-requirement.js";
8
9
  import { MASTER_CONFIG_FILES, REQUIRED_PACKAGE_SCRIPTS } from "../utils/constants.js";
9
10
  import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
10
11
  import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
@@ -16,7 +17,6 @@ import { formatCount } from "../utils/formatting.js";
16
17
  import { getHostPortPlan } from "../utils/host-port-config.js";
17
18
  import { createHostPortPlan } from "../utils/host-port-plan.js";
18
19
  import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
19
- import { isApiServiceType } from "../utils/resource-kind.js";
20
20
  import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
21
21
  import { checkCircularDependencies, checkDependsOnShape, checkSchemaVersions, checkServicePorts, } from "../utils/service-config-checks.js";
22
22
  import { buildHealthTargets, pingHealthTargets } from "../utils/service-urls.js";
@@ -363,11 +363,7 @@ export async function checkBun(exec = execAsync) {
363
363
  message: "Not in a project - skipping Bun check",
364
364
  };
365
365
  }
366
- const resources = discoverResourcesFromRoot(projectRoot);
367
- const hasGeneratedJsServices = resources.some((resource) => isApiServiceType(resource.config?.appType) ||
368
- resource.config?.appType === "frontend" ||
369
- resource.config?.appType === "worker");
370
- if (!hasGeneratedJsServices) {
366
+ if (!projectNeedsBun(discoverResourcesFromRoot(projectRoot))) {
371
367
  return {
372
368
  name: "Bun",
373
369
  didPass: true,
@@ -18,6 +18,7 @@ import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
18
18
  import { findAvailablePort } from "../utils/port-assignment.js";
19
19
  import { formatPortFallbackNotice } from "../utils/port-fallback-notice.js";
20
20
  import { isApiServiceType } from "../utils/resource-kind.js";
21
+ import { enforceSchemaVersionGate } from "../utils/schema-version-gate.js";
21
22
  import { enforceServiceConfigGate, warnSchemaVersions } from "../utils/service-config-checks.js";
22
23
  import { appendHealthPath, resolveSubdomainBases } from "../utils/service-urls.js";
23
24
  import { discoverResources, discoverResourcesStrict, discoverStacks, stackExists, } from "../utils/services.js";
@@ -180,6 +181,13 @@ export const upCommand = new Command("up")
180
181
  const platformRefusal = nativeWindowsUpRefusal(process.platform, process.env.TDK_ALLOW_NATIVE_WINDOWS);
181
182
  if (platformRefusal)
182
183
  showErrorAndExit(platformRefusal);
184
+ // An unknown schemaVersion is refused before the machine check, so --json can name the file even when Bun is missing.
185
+ const schemaRoot = findProjectRoot();
186
+ if (schemaRoot) {
187
+ enforceSchemaVersionGate(schemaRoot, {
188
+ onInvalid: (message) => emit?.({ ok: false }, [{ code: "INVALID_SCHEMA_VERSION", message }]),
189
+ });
190
+ }
183
191
  if (!options.dryRun) {
184
192
  const { assertMachineReadyOrExit } = await import("../utils/cold-preflight.js");
185
193
  await assertMachineReadyOrExit();
@@ -0,0 +1,11 @@
1
+ import { isApiServiceType } from "./resource-kind.js";
2
+ /**
3
+ * Bun runs the generated JS services. A project without any has nothing for Bun to run, so neither `tdk doctor` nor
4
+ * `tdk up` needs it. Doctor and the start-up preflight share this rule so they cannot disagree.
5
+ */
6
+ export function projectNeedsBun(resources) {
7
+ return resources.some((resource) => isApiServiceType(resource.config?.appType) ||
8
+ resource.config?.appType === "frontend" ||
9
+ resource.config?.appType === "worker");
10
+ }
11
+ //# sourceMappingURL=bun-requirement.js.map
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { checkDockerCompose, checkDockerRuntime, checkDockerVersions, checkTilt, } from "../commands/doctor.js";
5
5
  import { BUN_FLOOR_LABEL, bunMeetsFloor } from "./bun-floor.js";
6
+ import { projectNeedsBun } from "./bun-requirement.js";
6
7
  import { checkNatsBroker } from "./doctor-wiring.js";
7
8
  import { getHostPortPlan } from "./host-port-config.js";
8
9
  import { findProjectRoot } from "./paths.js";
@@ -79,8 +80,10 @@ export async function runColdPreflight(opts = {}) {
79
80
  return requireVerifiedDockerVersions(versions);
80
81
  }, "Docker Engine or Compose version check failed"));
81
82
  items.push(await safeCheck("tilt", checkTilt, "Tilt CLI not found"));
83
+ // Outside a project Bun stays required: `tdk project` is about to create services that need it.
84
+ const bunNeeded = projectRoot ? projectNeedsBun(discoverResourcesFromRoot(projectRoot)) : true;
82
85
  let bun = false;
83
- const bunPath = findOnPath("bun");
86
+ const bunPath = bunNeeded ? findOnPath("bun") : undefined;
84
87
  if (bunPath) {
85
88
  try {
86
89
  const version = execFileSync(bunPath, ["--version"], {
@@ -93,14 +96,16 @@ export async function runColdPreflight(opts = {}) {
93
96
  /* reported as a failed runtime check */
94
97
  }
95
98
  }
96
- items.push({
97
- id: "bun",
98
- ok: bun,
99
- message: bun
100
- ? `Bun ${BUN_FLOOR_LABEL} is available on PATH`
101
- : `Bun ${BUN_FLOOR_LABEL} not found on PATH`,
102
- fix: bun ? undefined : "curl -fsSL https://bun.sh/install | bash",
103
- });
99
+ items.push(bunNeeded
100
+ ? {
101
+ id: "bun",
102
+ ok: bun,
103
+ message: bun
104
+ ? `Bun ${BUN_FLOOR_LABEL} is available on PATH`
105
+ : `Bun ${BUN_FLOOR_LABEL} not found on PATH`,
106
+ fix: bun ? undefined : "curl -fsSL https://bun.sh/install | bash",
107
+ }
108
+ : { id: "bun", ok: true, message: "Bun not needed: no generated JS services" });
104
109
  try {
105
110
  const plan = await getHostPortPlan(projectRoot ?? cwd);
106
111
  items.push({
@@ -59,21 +59,47 @@ function generatedValue(name, password) {
59
59
  return `postgresql://postgres:${password}@postgres:5432/app_dev`;
60
60
  return REQUIRED_ENV_VARS.find((v) => v.name === name)?.default ?? "";
61
61
  }
62
- /** Strips one layer of matching quotes, like a dotenv parser would. */
63
- function unquote(raw) {
62
+ function parseEnvValue(raw) {
64
63
  const value = raw.trim();
65
- if (value.length >= 2) {
66
- const first = value[0];
67
- if ((first === '"' || first === "'") && value[value.length - 1] === first) {
68
- return value.slice(1, -1);
64
+ const quote = value[0];
65
+ if (quote === '"' || quote === "'") {
66
+ let escaped = false;
67
+ for (let index = 1; index < value.length; index += 1) {
68
+ const char = value[index];
69
+ if (escaped) {
70
+ escaped = false;
71
+ continue;
72
+ }
73
+ if (char === "\\") {
74
+ escaped = true;
75
+ continue;
76
+ }
77
+ if (char === quote) {
78
+ const quoted = value.slice(1, index);
79
+ if (quote === '"') {
80
+ const escapes = {
81
+ n: "\n",
82
+ r: "\r",
83
+ t: "\t",
84
+ '"': '"',
85
+ "\\": "\\",
86
+ $: "$",
87
+ };
88
+ return quoted.replace(/\\([nrt"\\$])/g, (_match, escapedChar) => escapes[escapedChar] ?? escapedChar);
89
+ }
90
+ return quoted.replace(/\\'/g, "'");
91
+ }
69
92
  }
93
+ return value;
70
94
  }
71
- return value;
95
+ const commentIndex = raw.search(/\s#/);
96
+ return (commentIndex === -1 ? raw : raw.slice(0, commentIndex)).trim();
72
97
  }
73
- /** Parses the assignments in a dotenv file: `NAME=value` and `export NAME=value`, ignoring comments and blank lines. */
98
+ /** Parses the Compose-compatible subset used by TDK for project environment files. */
74
99
  export function parseEnv(content) {
75
100
  const result = new Map();
76
- for (const rawLine of content.split(/\r?\n/)) {
101
+ const normalized = content.startsWith("\uFEFF") ? content.slice(1) : content;
102
+ for (const rawLine of normalized.split(/\r?\n/)) {
77
103
  const line = rawLine.trim();
78
104
  if (!line || line.startsWith("#"))
79
105
  continue;
@@ -84,7 +110,7 @@ export function parseEnv(content) {
84
110
  const name = assignment.slice(0, equals).trim();
85
111
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
86
112
  continue;
87
- result.set(name, unquote(assignment.slice(equals + 1)));
113
+ result.set(name, parseEnvValue(assignment.slice(equals + 1)));
88
114
  }
89
115
  return result;
90
116
  }
@@ -0,0 +1,39 @@
1
+ import { relative } from "node:path";
2
+ import chalk from "chalk";
3
+ import { formatCount } from "./formatting.js";
4
+ import { SERVICE_MANIFEST_SCHEMA_VERSION } from "./service-manifest.js";
5
+ import { discoverResourcesFromRoot } from "./services.js";
6
+ /**
7
+ * A service.json that declares a schemaVersion this tdk does not know is refused before anything starts: a newer format
8
+ * read as if it were the old one could start the wrong thing. A missing schemaVersion is not refused (see
9
+ * warnSchemaVersions), because every project began without one.
10
+ */
11
+ export function enforceSchemaVersionGate(projectRoot, options = {}, exit = process.exit) {
12
+ // A root that cannot be read is reported by the checks that run next; this gate only refuses what it can read.
13
+ let resources;
14
+ try {
15
+ resources = discoverResourcesFromRoot(projectRoot);
16
+ }
17
+ catch {
18
+ return;
19
+ }
20
+ const unsupported = resources.flatMap((resource) => {
21
+ const version = resource.config?.schemaVersion;
22
+ if (version === undefined || version === SERVICE_MANIFEST_SCHEMA_VERSION)
23
+ return [];
24
+ const file = relative(projectRoot, resource.configPath) || resource.configPath;
25
+ return [
26
+ `${file} schemaVersion ${JSON.stringify(version)}: this tdk supports ${SERVICE_MANIFEST_SCHEMA_VERSION}`,
27
+ ];
28
+ });
29
+ if (unsupported.length === 0)
30
+ return;
31
+ const verb = unsupported.length === 1 ? "declares" : "declare";
32
+ const message = `${formatCount(unsupported.length, "service.json file")} ${verb} a schemaVersion this tdk does not support:\n ${unsupported.join("\n ")}`;
33
+ const fix = "Upgrade tdk to a version that supports these files. Changing schemaVersion by hand is not a fix";
34
+ options.onInvalid?.(`${message}\nFix: ${fix}`);
35
+ console.error(chalk.red(`✗ ${message}`));
36
+ console.error(chalk.gray(` Fix: ${fix}`));
37
+ exit(1);
38
+ }
39
+ //# sourceMappingURL=schema-version-gate.js.map
@@ -99,6 +99,8 @@ function parseResource(serviceJsonPath) {
99
99
  configPath: serviceJsonPath,
100
100
  config,
101
101
  stack: config.stack,
102
+ port: config.port,
103
+ type: config.appType,
102
104
  };
103
105
  }
104
106
  export function discoverResourcesWithProblems(projectRoot) {
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.122",
4
+ "version": "1.3.124",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.122",
3
+ "version": "1.3.124",
4
4
  "description": "TDK CLI — start services on your laptop.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {