@tdk-landscape/tdk-cli-core 1.3.78 → 1.3.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +27 -213
- package/cli/dist/commands/config.js +34 -6
- package/cli/dist/commands/doctor.js +57 -23
- package/cli/dist/commands/help.js +3 -2
- package/cli/dist/commands/networks.js +121 -101
- package/cli/dist/commands/resource.js +4 -1
- package/cli/dist/commands/resources.js +40 -9
- package/cli/dist/commands/status.js +65 -7
- package/cli/dist/generator/extension-fetch.js +27 -3
- package/cli/dist/generator/template-engine.js +27 -15
- package/cli/dist/utils/doctor-report.js +40 -0
- package/cli/dist/utils/doctor-runtime.js +16 -4
- package/cli/dist/utils/host-port-plan.js +62 -0
- package/cli/dist/utils/machine-output.js +24 -0
- package/cli/dist/utils/service-manifest.js +1 -0
- package/cli/package.json +1 -1
- package/engine/schemas/project-schema.json +142 -0
- package/package.json +2 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Wait for concurrent probes to finish even if one throws, so they can release resources. */
|
|
2
|
+
export async function collectDoctorChecks(machineChecks, projectChecks) {
|
|
3
|
+
const checks = [];
|
|
4
|
+
const errors = [];
|
|
5
|
+
const append = (result) => {
|
|
6
|
+
if (result.status === "fulfilled")
|
|
7
|
+
checks.push(result.value);
|
|
8
|
+
else
|
|
9
|
+
errors.push({
|
|
10
|
+
code: "INTERNAL",
|
|
11
|
+
message: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
for (const result of await Promise.allSettled(machineChecks.map((check) => Promise.resolve().then(check)))) {
|
|
15
|
+
append(result);
|
|
16
|
+
}
|
|
17
|
+
for (const check of projectChecks) {
|
|
18
|
+
for (const result of await Promise.allSettled([Promise.resolve().then(check)]))
|
|
19
|
+
append(result);
|
|
20
|
+
}
|
|
21
|
+
return { checks, errors };
|
|
22
|
+
}
|
|
23
|
+
/** Produce the same readiness decision for human and machine consumers. */
|
|
24
|
+
export function createDoctorReport(checks, inProject, errors = []) {
|
|
25
|
+
return {
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
data: {
|
|
28
|
+
ready: errors.length === 0 &&
|
|
29
|
+
checks.every((check) => check.didPass || check.isSkipped || check.isWarning),
|
|
30
|
+
inProject,
|
|
31
|
+
checks,
|
|
32
|
+
},
|
|
33
|
+
errors,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** 0: ready (warnings permitted), 1: blocking findings, 2: usage/internal failure. */
|
|
37
|
+
export function getDoctorExitCode(report) {
|
|
38
|
+
return report.errors.length > 0 ? 2 : report.data.ready ? 0 : 1;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=doctor-report.js.map
|
|
@@ -376,10 +376,22 @@ function probeAddress(port, host) {
|
|
|
376
376
|
* it before trying 127.0.0.1 avoids that self-collision on every platform.
|
|
377
377
|
*/
|
|
378
378
|
export async function probeHostPort(port) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
// Several `tdk project` E2E tests can run at once. Their preflight probes
|
|
380
|
+
// briefly bind the same port, so an EADDRINUSE can be another probe rather
|
|
381
|
+
// than a service. Retry once after those short-lived binds have closed.
|
|
382
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
383
|
+
const wildcard = await probeAddress(port, "0.0.0.0");
|
|
384
|
+
if (wildcard === "unknown")
|
|
385
|
+
return wildcard;
|
|
386
|
+
if (wildcard === "free") {
|
|
387
|
+
const loopback = await probeAddress(port, "127.0.0.1");
|
|
388
|
+
if (loopback === "free" || loopback === "unknown")
|
|
389
|
+
return loopback;
|
|
390
|
+
}
|
|
391
|
+
if (attempt === 0)
|
|
392
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
393
|
+
}
|
|
394
|
+
return "in-use";
|
|
383
395
|
}
|
|
384
396
|
/** What an HTTP status from a service's health URL means, in the words a user needs. */
|
|
385
397
|
function describeProbeFailure(probe) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
2
|
+
const DEFAULT_RANGES = {
|
|
3
|
+
ingressHttp: { start: 8080, end: 8180 },
|
|
4
|
+
ingressHttps: { start: 8443, end: 8543 },
|
|
5
|
+
postgres: { start: 15432, end: 15532 },
|
|
6
|
+
};
|
|
7
|
+
const ENV_KEYS = {
|
|
8
|
+
ingressHttp: "TDK_HTTP_PORT",
|
|
9
|
+
ingressHttps: "TDK_HTTPS_PORT",
|
|
10
|
+
postgres: "TDK_POSTGRES_PORT",
|
|
11
|
+
};
|
|
12
|
+
async function isHostPortAvailable(port) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const server = createServer();
|
|
15
|
+
server.once("error", () => resolve(false));
|
|
16
|
+
// Docker Desktop and Windows publish these host ports through IPv4 loopback.
|
|
17
|
+
// Match that target instead of probing wildcard binds that may differ by OS.
|
|
18
|
+
server.listen(port, "127.0.0.1", () => server.close(() => resolve(true)));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function parsePort(name, value) {
|
|
22
|
+
const port = Number(value);
|
|
23
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
24
|
+
throw new Error(`${name} must be an integer between 1 and 65535 (received ${JSON.stringify(value)})`);
|
|
25
|
+
}
|
|
26
|
+
return port;
|
|
27
|
+
}
|
|
28
|
+
export async function createHostPortPlan(options = {}) {
|
|
29
|
+
const env = options.env ?? process.env;
|
|
30
|
+
const isAvailable = options.isAvailable ?? isHostPortAvailable;
|
|
31
|
+
const explicitFlags = { ingressHttp: false, ingressHttps: false, postgres: false };
|
|
32
|
+
const selected = { explicit: explicitFlags };
|
|
33
|
+
const reserved = new Set();
|
|
34
|
+
for (const key of ["ingressHttp", "ingressHttps", "postgres"]) {
|
|
35
|
+
const envValue = env[ENV_KEYS[key]];
|
|
36
|
+
const explicit = envValue !== undefined && envValue.trim() !== "";
|
|
37
|
+
const range = options.ranges?.[key] ?? DEFAULT_RANGES[key];
|
|
38
|
+
const candidates = explicit
|
|
39
|
+
? [parsePort(ENV_KEYS[key], envValue)]
|
|
40
|
+
: Array.from({ length: range.end - range.start + 1 }, (_, index) => range.start + index);
|
|
41
|
+
let chosen;
|
|
42
|
+
for (const port of candidates) {
|
|
43
|
+
if (reserved.has(port))
|
|
44
|
+
continue;
|
|
45
|
+
if (await isAvailable(port)) {
|
|
46
|
+
chosen = port;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (explicit) {
|
|
50
|
+
throw new Error(`${ENV_KEYS[key]}=${port} is already in use. Choose a free host port and retry.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (chosen === undefined) {
|
|
54
|
+
throw new Error(`No available host port in ${range.start}-${range.end} for ${key}. Set ${ENV_KEYS[key]} to a free port and retry.`);
|
|
55
|
+
}
|
|
56
|
+
reserved.add(chosen);
|
|
57
|
+
selected[key] = chosen;
|
|
58
|
+
explicitFlags[key] = explicit;
|
|
59
|
+
}
|
|
60
|
+
return selected;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=host-port-plan.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TdkError } from "./errors.js";
|
|
2
|
+
export function createMachineEnvelope(data, errors = []) {
|
|
3
|
+
return { schemaVersion: 1, data, errors };
|
|
4
|
+
}
|
|
5
|
+
export function toMachineError(error) {
|
|
6
|
+
if (error instanceof TdkError) {
|
|
7
|
+
return { error: { code: "COMMAND_FAILED", message: error.message }, exitCode: 1 };
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
error: {
|
|
11
|
+
code: "INTERNAL",
|
|
12
|
+
message: error instanceof Error ? error.message : String(error),
|
|
13
|
+
},
|
|
14
|
+
exitCode: 2,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Keep machine stdout parseable when a command fails before it has data to return. */
|
|
18
|
+
export function writeMachineError(error) {
|
|
19
|
+
const result = toMachineError(error);
|
|
20
|
+
console.log(JSON.stringify(createMachineEnvelope(null, [result.error])));
|
|
21
|
+
console.error(result.error.message);
|
|
22
|
+
process.exit(result.exitCode);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=machine-output.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
export const SERVICE_MANIFEST_SCHEMA_VERSION = 1;
|
|
3
|
+
export const SERVICE_MANIFEST_SCHEMA_URL = "https://tdk-landscape.github.io/schema.service.json";
|
|
3
4
|
const KNOWN_SERVICE_FIELDS = new Set([
|
|
4
5
|
"$schema",
|
|
5
6
|
"apiBasePath",
|
package/cli/package.json
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/tdk-landscape/tdk-cli-core/main/engine/schemas/project-schema.json",
|
|
4
|
+
"title": "TDK Project Configuration",
|
|
5
|
+
"description": "Editor assistance for .tdk/project.json. TDK's CLI remains the runtime source of truth.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"required": [
|
|
9
|
+
"version",
|
|
10
|
+
"project",
|
|
11
|
+
"phases",
|
|
12
|
+
"optional_infra",
|
|
13
|
+
"discovery"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"version": {
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"project": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": true,
|
|
22
|
+
"required": [
|
|
23
|
+
"name",
|
|
24
|
+
"version"
|
|
25
|
+
],
|
|
26
|
+
"properties": {
|
|
27
|
+
"name": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"version": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"phases": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": true,
|
|
38
|
+
"required": [
|
|
39
|
+
"pre_alpha",
|
|
40
|
+
"alpha",
|
|
41
|
+
"beta",
|
|
42
|
+
"out_of_scope"
|
|
43
|
+
],
|
|
44
|
+
"properties": {
|
|
45
|
+
"pre_alpha": {
|
|
46
|
+
"$ref": "#/definitions/phase"
|
|
47
|
+
},
|
|
48
|
+
"alpha": {
|
|
49
|
+
"$ref": "#/definitions/phase"
|
|
50
|
+
},
|
|
51
|
+
"beta": {
|
|
52
|
+
"$ref": "#/definitions/phase"
|
|
53
|
+
},
|
|
54
|
+
"out_of_scope": {
|
|
55
|
+
"$ref": "#/definitions/phase"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"stacks": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": true
|
|
62
|
+
},
|
|
63
|
+
"always_enabled_infra": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"optional_infra": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": true,
|
|
72
|
+
"required": [
|
|
73
|
+
"monitoring",
|
|
74
|
+
"elk",
|
|
75
|
+
"debezium",
|
|
76
|
+
"golden_image",
|
|
77
|
+
"verdaccio"
|
|
78
|
+
],
|
|
79
|
+
"properties": {
|
|
80
|
+
"monitoring": {
|
|
81
|
+
"type": "boolean"
|
|
82
|
+
},
|
|
83
|
+
"elk": {
|
|
84
|
+
"type": "boolean"
|
|
85
|
+
},
|
|
86
|
+
"debezium": {
|
|
87
|
+
"type": "boolean"
|
|
88
|
+
},
|
|
89
|
+
"golden_image": {
|
|
90
|
+
"type": "boolean"
|
|
91
|
+
},
|
|
92
|
+
"verdaccio": {
|
|
93
|
+
"type": "boolean"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"discovery": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"additionalProperties": true,
|
|
100
|
+
"required": [
|
|
101
|
+
"paths"
|
|
102
|
+
],
|
|
103
|
+
"properties": {
|
|
104
|
+
"paths": {
|
|
105
|
+
"type": "array",
|
|
106
|
+
"items": {
|
|
107
|
+
"type": "string"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"overrides": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"definitions": {
|
|
118
|
+
"phase": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"additionalProperties": true,
|
|
121
|
+
"required": [
|
|
122
|
+
"name",
|
|
123
|
+
"description",
|
|
124
|
+
"enabledStacks"
|
|
125
|
+
],
|
|
126
|
+
"properties": {
|
|
127
|
+
"name": {
|
|
128
|
+
"type": "string"
|
|
129
|
+
},
|
|
130
|
+
"description": {
|
|
131
|
+
"type": "string"
|
|
132
|
+
},
|
|
133
|
+
"enabledStacks": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"items": {
|
|
136
|
+
"type": "string"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tdk-landscape/tdk-cli-core",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.80",
|
|
4
|
+
"description": "TDK runs many services on your laptop with Docker + Tilt. Helm still deploys the cluster.",
|
|
5
5
|
"homepage": "https://tdk-landscape.github.io/tdk-website",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|