@tdk-landscape/tdk-cli-core 1.3.123 → 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 +1 -1
- package/cli/dist/commands/doctor.js +2 -6
- package/cli/dist/commands/up.js +8 -0
- package/cli/dist/utils/bun-requirement.js +11 -0
- package/cli/dist/utils/cold-preflight.js +14 -9
- package/cli/dist/utils/env-validator.js +36 -10
- package/cli/dist/utils/schema-version-gate.js +39 -0
- package/cli/dist/utils/services.js +2 -0
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://socket.dev/npm/package/@tdk-landscape/tdk-cli-core/overview)
|
|
12
12
|
[](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
|
-
|
|
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,
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
63
|
-
function unquote(raw) {
|
|
62
|
+
function parseEnvValue(raw) {
|
|
64
63
|
const value = raw.trim();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
95
|
+
const commentIndex = raw.search(/\s#/);
|
|
96
|
+
return (commentIndex === -1 ? raw : raw.slice(0, commentIndex)).trim();
|
|
72
97
|
}
|
|
73
|
-
/** Parses the
|
|
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
|
-
|
|
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,
|
|
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
|
package/cli/package.json
CHANGED