@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
|
@@ -6,6 +6,7 @@ import { getStackEmoji } from "../utils/constants.js";
|
|
|
6
6
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
7
7
|
import { logVerbose, requireProjectRoot } from "../utils/errors.js";
|
|
8
8
|
import { colorizeByStatus, DEFAULT_BOX_WIDTH, formatBoxLine, formatPadded, getStatusIcon, printBoxedHeader, } from "../utils/formatting.js";
|
|
9
|
+
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
9
10
|
import { findProjectRoot } from "../utils/paths.js";
|
|
10
11
|
import { checkPortStatus } from "../utils/port-assignment.js";
|
|
11
12
|
import { isValidPort, sanitizeForShell } from "../utils/validation.js";
|
|
@@ -156,118 +157,137 @@ export const networksCommand = new Command("networks")
|
|
|
156
157
|
.alias("urls")
|
|
157
158
|
.alias("traefik")
|
|
158
159
|
.option("-s, --stack <stack>", "Filter by stack name")
|
|
159
|
-
.option("--json", "Output
|
|
160
|
+
.option("--json", "Output a versioned JSON response")
|
|
161
|
+
.option("--json-legacy", "Output the legacy JSON array (deprecated)")
|
|
160
162
|
.option("--raw", "Output raw URLs only")
|
|
161
163
|
.action(async (options) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const appDomain = `app.${bareDomain}`;
|
|
167
|
-
const apiDomain = `api.${bareDomain}`;
|
|
168
|
-
const services = discovery.resources;
|
|
169
|
-
const servicesWithUrls = await Promise.all(services
|
|
170
|
-
.filter((s) => typeof s.config?.basePath === "string")
|
|
171
|
-
.map(async (s) => {
|
|
172
|
-
const basePath = s.config.basePath.replace(/^\//, "");
|
|
173
|
-
const isBackend = s.config?.appType === "backend";
|
|
174
|
-
const host = isBackend ? apiDomain : appDomain;
|
|
175
|
-
const url = `http://${host}/${basePath}`;
|
|
176
|
-
const port = s.config.port;
|
|
177
|
-
const status = await checkServiceStatus(s.name, port, url);
|
|
178
|
-
return {
|
|
179
|
-
name: s.name,
|
|
180
|
-
stack: s.stack,
|
|
181
|
-
basePath: s.config.basePath,
|
|
182
|
-
url,
|
|
183
|
-
...(process.platform === "win32"
|
|
184
|
-
? { loopbackUrl: `http://127.0.0.1:80/${basePath}`.replace(/\/$/, "") }
|
|
185
|
-
: {}),
|
|
186
|
-
port,
|
|
187
|
-
status,
|
|
188
|
-
};
|
|
189
|
-
}));
|
|
190
|
-
const filteredServices = options.stack
|
|
191
|
-
? servicesWithUrls.filter((s) => s.stack === options.stack)
|
|
192
|
-
: servicesWithUrls;
|
|
193
|
-
if (filteredServices.length === 0) {
|
|
194
|
-
if (options.stack) {
|
|
195
|
-
console.log(chalk.yellow(`⚠️ No services with basePath found in stack "${options.stack}"`));
|
|
164
|
+
const action = async () => {
|
|
165
|
+
const projectRoot = options.json || options.jsonLegacy ? findProjectRoot() : requireProjectRoot();
|
|
166
|
+
if (!projectRoot) {
|
|
167
|
+
throw new Error("Could not find project root (no .tdk/project.json found)");
|
|
196
168
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
169
|
+
const discovery = createDiscoveryContext();
|
|
170
|
+
const baseDomain = determineDefaultDomain();
|
|
171
|
+
const bareDomain = baseDomain.replace(/^(app|api)\./, "");
|
|
172
|
+
const appDomain = `app.${bareDomain}`;
|
|
173
|
+
const apiDomain = `api.${bareDomain}`;
|
|
174
|
+
const services = discovery.resources;
|
|
175
|
+
const servicesWithUrls = await Promise.all(services
|
|
176
|
+
.filter((s) => typeof s.config?.basePath === "string")
|
|
177
|
+
.map(async (s) => {
|
|
178
|
+
const basePath = s.config.basePath.replace(/^\//, "");
|
|
179
|
+
const isBackend = s.config?.appType === "backend";
|
|
180
|
+
const host = isBackend ? apiDomain : appDomain;
|
|
181
|
+
const url = `http://${host}/${basePath}`;
|
|
182
|
+
const port = s.config.port;
|
|
183
|
+
const status = await checkServiceStatus(s.name, port, url);
|
|
184
|
+
return {
|
|
185
|
+
name: s.name,
|
|
186
|
+
stack: s.stack,
|
|
187
|
+
basePath: s.config.basePath,
|
|
188
|
+
url,
|
|
189
|
+
...(process.platform === "win32"
|
|
190
|
+
? { loopbackUrl: `http://127.0.0.1:80/${basePath}`.replace(/\/$/, "") }
|
|
191
|
+
: {}),
|
|
192
|
+
port,
|
|
193
|
+
status,
|
|
194
|
+
};
|
|
195
|
+
}));
|
|
196
|
+
const filteredServices = options.stack
|
|
197
|
+
? servicesWithUrls.filter((s) => s.stack === options.stack)
|
|
198
|
+
: servicesWithUrls;
|
|
199
|
+
if (options.json || options.jsonLegacy) {
|
|
200
|
+
const output = options.jsonLegacy
|
|
201
|
+
? filteredServices
|
|
202
|
+
: createMachineEnvelope({ services: filteredServices });
|
|
203
|
+
console.log(JSON.stringify(output, null, 2));
|
|
204
|
+
return;
|
|
201
205
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
console.log(service.loopbackUrl);
|
|
206
|
+
if (filteredServices.length === 0) {
|
|
207
|
+
if (options.stack) {
|
|
208
|
+
console.log(chalk.yellow(`⚠️ No services with basePath found in stack "${options.stack}"`));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log(chalk.yellow("⚠️ No services with basePath found"));
|
|
212
|
+
console.log(chalk.gray("\nAdd basePath to your service.json:"));
|
|
213
|
+
console.log(chalk.gray(' "basePath": "/my-service"'));
|
|
214
|
+
}
|
|
215
|
+
process.exit(0);
|
|
213
216
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (stackServices.length > 0) {
|
|
222
|
-
stacks.set(stackName, stackServices);
|
|
217
|
+
if (options.raw) {
|
|
218
|
+
for (const service of filteredServices) {
|
|
219
|
+
console.log(service.url);
|
|
220
|
+
if (service.loopbackUrl)
|
|
221
|
+
console.log(service.loopbackUrl);
|
|
222
|
+
}
|
|
223
|
+
process.exit(0);
|
|
223
224
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
225
|
+
printBoxedHeader("🌐 TRAEFIK NETWORKS", `Domain: http://${baseDomain}`, DEFAULT_BOX_WIDTH);
|
|
226
|
+
// Group services by stack using discovery context's stack names for consistent ordering
|
|
227
|
+
const stacks = new Map();
|
|
228
|
+
for (const stackName of discovery.stackNames) {
|
|
229
|
+
const stackServices = filteredServices.filter((s) => s.stack === stackName);
|
|
230
|
+
if (stackServices.length > 0) {
|
|
231
|
+
stacks.set(stackName, stackServices);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Add unstacked services to 'default' group
|
|
235
|
+
const unstackedServices = filteredServices.filter((s) => !s.stack);
|
|
236
|
+
if (unstackedServices.length > 0) {
|
|
237
|
+
stacks.set("default", unstackedServices);
|
|
238
|
+
}
|
|
239
|
+
let isFirstStack = true;
|
|
240
|
+
for (const [stackName, stackServices] of stacks) {
|
|
241
|
+
if (!isFirstStack) {
|
|
242
|
+
console.log();
|
|
243
|
+
}
|
|
244
|
+
isFirstStack = false;
|
|
245
|
+
const emoji = getStackEmoji(stackName);
|
|
246
|
+
const stackTitle = `${emoji} ${stackName.toUpperCase()} STACK`;
|
|
233
247
|
console.log();
|
|
248
|
+
console.log(chalk.bold.white(stackTitle));
|
|
249
|
+
console.log(chalk.gray(formatBoxLine("━", DEFAULT_BOX_WIDTH - 4)));
|
|
250
|
+
for (const service of stackServices) {
|
|
251
|
+
const statusSymbol = getStatusIcon(service.status);
|
|
252
|
+
const statusEmoji = colorizeByStatus(statusSymbol, service.status);
|
|
253
|
+
const namePart = formatPadded(service.name, 22);
|
|
254
|
+
const urlPart = service.status === "running"
|
|
255
|
+
? chalk.cyan.underline(service.url)
|
|
256
|
+
: chalk.gray(service.url); // Gray out URL if stopped
|
|
257
|
+
const statusLabel = service.status !== "running" ? chalk.gray(` [${service.status}]`) : "";
|
|
258
|
+
console.log(` ${statusEmoji} ${chalk.white(namePart)} ${urlPart}${statusLabel}`);
|
|
259
|
+
if (service.loopbackUrl) {
|
|
260
|
+
console.log(` ${chalk.gray(`Loopback: ${service.loopbackUrl} (Host: ${new URL(service.url).hostname})`)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
234
263
|
}
|
|
235
|
-
isFirstStack = false;
|
|
236
|
-
const emoji = getStackEmoji(stackName);
|
|
237
|
-
const stackTitle = `${emoji} ${stackName.toUpperCase()} STACK`;
|
|
238
264
|
console.log();
|
|
239
|
-
console.log(chalk.
|
|
240
|
-
console.log(chalk.gray(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
265
|
+
console.log(chalk.gray(formatBoxLine("─", DEFAULT_BOX_WIDTH - 2)));
|
|
266
|
+
console.log(chalk.gray("🖱️ Click any URL above to open in browser"));
|
|
267
|
+
console.log(chalk.gray("📊 Status: ") +
|
|
268
|
+
chalk.green("✓ Running") +
|
|
269
|
+
" | " +
|
|
270
|
+
chalk.red("✗ Stopped") +
|
|
271
|
+
" | " +
|
|
272
|
+
chalk.gray("? Unknown"));
|
|
273
|
+
if (baseDomain === "localhost") {
|
|
274
|
+
console.log();
|
|
275
|
+
const projectConfig = readProjectConfig(projectRoot);
|
|
276
|
+
const projectName = projectConfig.project.name;
|
|
277
|
+
console.log(chalk.yellow("💡 Tip: Set custom domain with:"));
|
|
278
|
+
console.log(chalk.cyan(` export TDK_PUBLIC_HOST=${projectName}.localhost`));
|
|
253
279
|
}
|
|
254
|
-
}
|
|
255
|
-
console.log();
|
|
256
|
-
console.log(chalk.gray(formatBoxLine("─", DEFAULT_BOX_WIDTH - 2)));
|
|
257
|
-
console.log(chalk.gray("🖱️ Click any URL above to open in browser"));
|
|
258
|
-
console.log(chalk.gray("📊 Status: ") +
|
|
259
|
-
chalk.green("✓ Running") +
|
|
260
|
-
" | " +
|
|
261
|
-
chalk.red("✗ Stopped") +
|
|
262
|
-
" | " +
|
|
263
|
-
chalk.gray("? Unknown"));
|
|
264
|
-
if (baseDomain === "localhost") {
|
|
265
280
|
console.log();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
281
|
+
};
|
|
282
|
+
if (options.json || options.jsonLegacy) {
|
|
283
|
+
try {
|
|
284
|
+
await action();
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
writeMachineError(error);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
270
290
|
}
|
|
271
|
-
|
|
291
|
+
await action();
|
|
272
292
|
});
|
|
273
293
|
//# sourceMappingURL=networks.js.map
|
|
@@ -13,7 +13,7 @@ import { showCommandHeader } from "../utils/formatting.js";
|
|
|
13
13
|
import { assignPort } from "../utils/port-assignment.js";
|
|
14
14
|
import { promptSelect, promptText } from "../utils/prompt.js";
|
|
15
15
|
import { getDefaultFeaturesForResourceType } from "../utils/resource-features.js";
|
|
16
|
-
import { SERVICE_MANIFEST_SCHEMA_VERSION } from "../utils/service-manifest.js";
|
|
16
|
+
import { SERVICE_MANIFEST_SCHEMA_URL, SERVICE_MANIFEST_SCHEMA_VERSION, } from "../utils/service-manifest.js";
|
|
17
17
|
import { discoverResources } from "../utils/services.js";
|
|
18
18
|
import { createKebabCaseValidator, isPathSafe, validateResourceName } from "../utils/validation.js";
|
|
19
19
|
export const BASE_TEMPLATE = {
|
|
@@ -77,6 +77,7 @@ export function createServiceJson(name, type, stack, port, extraFeatures = [], f
|
|
|
77
77
|
}
|
|
78
78
|
return {
|
|
79
79
|
...base,
|
|
80
|
+
$schema: SERVICE_MANIFEST_SCHEMA_URL,
|
|
80
81
|
schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
81
82
|
appName: name,
|
|
82
83
|
appType: type,
|
|
@@ -494,6 +495,7 @@ export const resourceCommand = new Command("resource")
|
|
|
494
495
|
// Update with new values while preserving existing fields
|
|
495
496
|
const updatedServiceJson = {
|
|
496
497
|
...existingServiceJson,
|
|
498
|
+
$schema: existingServiceJson.$schema ?? SERVICE_MANIFEST_SCHEMA_URL,
|
|
497
499
|
schemaVersion: existingServiceJson.schemaVersion ?? SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
498
500
|
appName: resourceName,
|
|
499
501
|
appType: resourceType === "sdk" ? "sdk" : existingServiceJson.appType || resourceType,
|
|
@@ -525,6 +527,7 @@ export const resourceCommand = new Command("resource")
|
|
|
525
527
|
mkdirSync(fullPath, { recursive: true });
|
|
526
528
|
// Create service.json
|
|
527
529
|
const byoServiceJson = {
|
|
530
|
+
$schema: SERVICE_MANIFEST_SCHEMA_URL,
|
|
528
531
|
schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
529
532
|
appName: resourceName,
|
|
530
533
|
appType: "bring-your-own",
|
|
@@ -3,35 +3,56 @@ import { Command } from "commander";
|
|
|
3
3
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
4
4
|
import { requireProjectRoot, runCommand } from "../utils/errors.js";
|
|
5
5
|
import { formatCount, showAllSatisfyCondition, showDetail, showEmptyState, showStep, } from "../utils/formatting.js";
|
|
6
|
+
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
7
|
+
import { findProjectRoot } from "../utils/paths.js";
|
|
6
8
|
export const resourcesCommand = new Command("resources")
|
|
7
9
|
.description("List all resources (services) in the project")
|
|
8
10
|
.option("-v, --verbose", "Show detailed information about each resource", false)
|
|
9
11
|
.option("-s, --stack <stack>", "Filter resources by stack name")
|
|
10
12
|
.option("--no-stack", "Show only resources without a stack")
|
|
11
13
|
.option("--ports", "Show port assignments", false)
|
|
14
|
+
.option("--json", "Output a versioned JSON resource report", false)
|
|
12
15
|
.action(async (options) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return;
|
|
16
|
+
const action = async () => {
|
|
17
|
+
if (options.json) {
|
|
18
|
+
if (!findProjectRoot()) {
|
|
19
|
+
writeMachineError(new Error("Could not find project root (no .tdk/project.json found)"));
|
|
20
|
+
}
|
|
19
21
|
}
|
|
22
|
+
else {
|
|
23
|
+
requireProjectRoot();
|
|
24
|
+
}
|
|
25
|
+
const discovery = createDiscoveryContext();
|
|
20
26
|
let resources = discovery.resources;
|
|
21
27
|
if (options.stack) {
|
|
22
28
|
resources = discovery.resourcesByStack.get(options.stack) || [];
|
|
23
|
-
if (resources.length === 0) {
|
|
29
|
+
if (resources.length === 0 && !options.json) {
|
|
24
30
|
showEmptyState("stack-services", ` in stack "${options.stack}"`);
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
if (options.noStack) {
|
|
29
35
|
resources = discovery.unassignedResources;
|
|
30
|
-
if (resources.length === 0) {
|
|
36
|
+
if (resources.length === 0 && !options.json) {
|
|
31
37
|
showAllSatisfyCondition("resources", "assigned to a stack");
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
41
|
+
if (options.json) {
|
|
42
|
+
const data = resources.map((resource) => ({
|
|
43
|
+
name: resource.name,
|
|
44
|
+
stack: resource.stack ?? null,
|
|
45
|
+
type: resource.config?.appType ?? resource.type ?? "unknown",
|
|
46
|
+
port: resource.port ?? null,
|
|
47
|
+
path: resource.configPath,
|
|
48
|
+
}));
|
|
49
|
+
console.log(JSON.stringify(createMachineEnvelope({ resources: data })));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (resources.length === 0) {
|
|
53
|
+
showEmptyState(options.stack ? "stack-services" : "resources", options.stack ? ` in stack "${options.stack}"` : "");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
35
56
|
showStep(`Found ${formatCount(resources.length, "resource")}:\n`);
|
|
36
57
|
if (options.verbose || options.ports) {
|
|
37
58
|
for (const resource of resources) {
|
|
@@ -68,6 +89,16 @@ export const resourcesCommand = new Command("resources")
|
|
|
68
89
|
console.log(chalk.yellow(`\n${formatCount(withoutStackCount, "resource")} not assigned to any stack.`));
|
|
69
90
|
showDetail('Run "tdk resources --no-stack" to see them, or "tdk stack" to assign them.');
|
|
70
91
|
}
|
|
71
|
-
}
|
|
92
|
+
};
|
|
93
|
+
if (options.json) {
|
|
94
|
+
try {
|
|
95
|
+
await action();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
writeMachineError(error);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await runCommand(action);
|
|
72
103
|
});
|
|
73
104
|
//# sourceMappingURL=resources.js.map
|
|
@@ -3,15 +3,17 @@ import { Command } from "commander";
|
|
|
3
3
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
4
4
|
import { runCommand } from "../utils/errors.js";
|
|
5
5
|
import { formatCount, showDetail, showStep } from "../utils/formatting.js";
|
|
6
|
+
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
6
7
|
import { getTiltfilePath, isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
7
8
|
export const statusCommand = new Command("status")
|
|
8
9
|
.description("Show status of resources and stacks")
|
|
10
|
+
.option("--json", "Output a versioned JSON status report", false)
|
|
9
11
|
.option("-v, --verbose", "Show detailed information", false)
|
|
10
12
|
.option("--stacks", "Show stack information (default)", true)
|
|
11
13
|
.option("--resources", "Show all discovered resources", false)
|
|
12
14
|
.option("--tilt", "Show tilt resource status", false)
|
|
13
15
|
.action(async (options) => {
|
|
14
|
-
|
|
16
|
+
const action = async () => {
|
|
15
17
|
let tiltAvailable = false;
|
|
16
18
|
try {
|
|
17
19
|
tiltAvailable = await isTiltAvailable();
|
|
@@ -20,13 +22,59 @@ export const statusCommand = new Command("status")
|
|
|
20
22
|
// Tilt not available (spawn error)
|
|
21
23
|
tiltAvailable = false;
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (!options.json) {
|
|
26
|
+
showStep("TDK Status\n");
|
|
27
|
+
console.log(chalk.bold("Tilt:"), tiltAvailable ? chalk.green("available") : chalk.red("not found"));
|
|
28
|
+
if (!tiltAvailable) {
|
|
29
|
+
showDetail("Install Tilt: https://docs.tilt.dev/install.html");
|
|
30
|
+
}
|
|
31
|
+
console.log();
|
|
27
32
|
}
|
|
28
|
-
console.log();
|
|
29
33
|
const discovery = createDiscoveryContext();
|
|
34
|
+
let tiltResources = null;
|
|
35
|
+
let queryError = null;
|
|
36
|
+
if (options.json && options.tilt && tiltAvailable) {
|
|
37
|
+
const result = await runTilt("get", ["-f", getTiltfilePath(), "resources", "--output=json"], {
|
|
38
|
+
inheritStdio: false,
|
|
39
|
+
});
|
|
40
|
+
if (result.exitCode === 0) {
|
|
41
|
+
try {
|
|
42
|
+
tiltResources = JSON.parse(result.stdout);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
queryError = `Tilt returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
queryError = result.stderr || "Could not retrieve Tilt resource status";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options.json) {
|
|
53
|
+
const errors = queryError ? [{ code: "TILT_STATUS_UNAVAILABLE", message: queryError }] : [];
|
|
54
|
+
const data = {
|
|
55
|
+
tilt: {
|
|
56
|
+
available: tiltAvailable,
|
|
57
|
+
resourcesQueried: options.tilt && tiltAvailable,
|
|
58
|
+
resources: tiltResources,
|
|
59
|
+
},
|
|
60
|
+
resources: discovery.resources.map((resource) => ({
|
|
61
|
+
name: resource.name,
|
|
62
|
+
stack: resource.stack ?? null,
|
|
63
|
+
type: resource.type ?? "unknown",
|
|
64
|
+
port: resource.port ?? null,
|
|
65
|
+
})),
|
|
66
|
+
stacks: discovery.stacks.map((stack) => ({
|
|
67
|
+
name: stack.name,
|
|
68
|
+
resourceCount: stack.resourceCount,
|
|
69
|
+
})),
|
|
70
|
+
};
|
|
71
|
+
console.log(JSON.stringify(createMachineEnvelope(data, errors)));
|
|
72
|
+
if (errors.length > 0) {
|
|
73
|
+
console.error(queryError);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
30
78
|
console.log(chalk.bold("Resources:"), `${discovery.resources.length} discovered`);
|
|
31
79
|
console.log(chalk.bold("Stacks:"), `${discovery.stacks.length} defined`);
|
|
32
80
|
if (discovery.stacks.length > 0) {
|
|
@@ -66,6 +114,16 @@ export const statusCommand = new Command("status")
|
|
|
66
114
|
console.log();
|
|
67
115
|
showDetail('Run "tdk list-stacks" to see all stacks.');
|
|
68
116
|
showDetail('Run "tdk up <stack-name>" to start a stack.');
|
|
69
|
-
}
|
|
117
|
+
};
|
|
118
|
+
if (options.json) {
|
|
119
|
+
try {
|
|
120
|
+
await action();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
writeMachineError(error);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await runCommand(action);
|
|
70
128
|
});
|
|
71
129
|
//# sourceMappingURL=status.js.map
|
|
@@ -109,6 +109,7 @@ async function fetchPremium(key, resource, projectId) {
|
|
|
109
109
|
return fetch(`${url}&key=${encodeURIComponent(key)}`);
|
|
110
110
|
}
|
|
111
111
|
async function fetchPremiumBundle(key, projectId) {
|
|
112
|
+
const attempts = [];
|
|
112
113
|
for (const resource of KNOWN_RESOURCES) {
|
|
113
114
|
let res;
|
|
114
115
|
try {
|
|
@@ -119,18 +120,23 @@ async function fetchPremiumBundle(key, projectId) {
|
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
122
|
if (res.ok) {
|
|
122
|
-
|
|
123
|
+
const bundle = Buffer.from(await res.arrayBuffer());
|
|
124
|
+
console.info(`🔎 Premium entitlement probe: granted resource=${resource} (HTTP ${res.status}); prior denials=${attempts.join(", ") || "none"}`);
|
|
125
|
+
console.info(`🔎 Premium bundle download: ${bundle.byteLength} bytes`);
|
|
126
|
+
return bundle;
|
|
123
127
|
}
|
|
124
128
|
if (res.status === 401 || res.status === 403) {
|
|
125
129
|
// Not granted for this specific resource name - try the next one
|
|
126
130
|
// before concluding the key has nothing to offer.
|
|
131
|
+
attempts.push(`${resource}=${res.status}`);
|
|
127
132
|
continue;
|
|
128
133
|
}
|
|
129
134
|
// Anything else (404 missing bundle release, 502 upstream failure,
|
|
130
135
|
// etc.) won't be fixed by trying a different resource name.
|
|
131
|
-
console.warn(`⚠️ Premium fetch failed: HTTP ${res.status}`);
|
|
136
|
+
console.warn(`⚠️ Premium fetch failed: ${resource} returned HTTP ${res.status}; prior entitlement probes: ${attempts.join(", ") || "none"}`);
|
|
132
137
|
return null;
|
|
133
138
|
}
|
|
139
|
+
console.warn(`⚠️ Premium entitlement probes denied: ${attempts.join(", ")}`);
|
|
134
140
|
return null;
|
|
135
141
|
}
|
|
136
142
|
/**
|
|
@@ -146,6 +152,7 @@ export async function applyPremiumOverlay(projectRoot, destDir) {
|
|
|
146
152
|
return false;
|
|
147
153
|
const tarballPath = cacheTarballPath(key);
|
|
148
154
|
if (!isCacheFresh(tarballPath)) {
|
|
155
|
+
console.info("🔎 Premium bundle cache: miss; requesting bundle from distribution service");
|
|
149
156
|
const projectId = getOrCreateProjectId(projectRoot);
|
|
150
157
|
const bundle = await fetchPremiumBundle(key, projectId);
|
|
151
158
|
if (!bundle) {
|
|
@@ -155,20 +162,35 @@ export async function applyPremiumOverlay(projectRoot, destDir) {
|
|
|
155
162
|
mkdirSync(join(tarballPath, ".."), { recursive: true });
|
|
156
163
|
writeFileSync(tarballPath, bundle);
|
|
157
164
|
}
|
|
165
|
+
else {
|
|
166
|
+
console.info("🔎 Premium bundle cache: hit (cached bundle reused)");
|
|
167
|
+
}
|
|
158
168
|
try {
|
|
159
169
|
const extractDir = join(tarballPath, "..", "extracted");
|
|
160
170
|
mkdirSync(extractDir, { recursive: true });
|
|
161
171
|
extractTarball(readFileSync(tarballPath), extractDir);
|
|
162
172
|
let applied = 0;
|
|
173
|
+
const missing = [];
|
|
163
174
|
for (const [src, dest] of Object.entries(PREMIUM_PATH_MAP)) {
|
|
164
175
|
const from = join(extractDir, src);
|
|
165
|
-
if (!existsSync(from))
|
|
176
|
+
if (!existsSync(from)) {
|
|
177
|
+
missing.push(src);
|
|
166
178
|
continue;
|
|
179
|
+
}
|
|
167
180
|
const to = join(destDir, dest);
|
|
168
181
|
mkdirSync(join(to, ".."), { recursive: true });
|
|
169
182
|
writeFileSync(to, readFileSync(from));
|
|
170
183
|
applied++;
|
|
171
184
|
}
|
|
185
|
+
const sablierPath = join(extractDir, "networking/sablier_container_cycle.star");
|
|
186
|
+
console.info(`🔎 Premium bundle contents: mapped=${applied}/${Object.keys(PREMIUM_PATH_MAP).length}; sablier_overlay=${existsSync(sablierPath) ? "present" : "MISSING"}`);
|
|
187
|
+
if (missing.length > 0) {
|
|
188
|
+
console.info(`🔎 Premium bundle missing mapped files: ${missing.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
if (!existsSync(sablierPath) && process.env.TDK_PREMIUM_DIAGNOSTICS === "1") {
|
|
191
|
+
const sablierGranted = await hasSablierLicense(projectRoot);
|
|
192
|
+
console.info(`🔎 Sablier entitlement check: ${sablierGranted ? "granted" : "not confirmed"}; compare with sablier_overlay above to distinguish license access from bundle contents`);
|
|
193
|
+
}
|
|
172
194
|
if (applied > 0) {
|
|
173
195
|
console.log(`✓ Premium resources unlocked (${applied} file${applied === 1 ? "" : "s"})`);
|
|
174
196
|
return true;
|
|
@@ -211,6 +233,7 @@ async function hasLiveResourceLicense(projectRoot, resource) {
|
|
|
211
233
|
if (isCacheFresh(cachePath)) {
|
|
212
234
|
try {
|
|
213
235
|
const cached = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
236
|
+
console.info(`🔎 ${resource} entitlement probe: cache=${cached.granted === true ? "granted" : "denied"}`);
|
|
214
237
|
return cached.granted === true;
|
|
215
238
|
}
|
|
216
239
|
catch {
|
|
@@ -222,6 +245,7 @@ async function hasLiveResourceLicense(projectRoot, resource) {
|
|
|
222
245
|
try {
|
|
223
246
|
const res = await fetchPremium(key, resource, projectId);
|
|
224
247
|
granted = res.ok;
|
|
248
|
+
console.info(`🔎 ${resource} entitlement probe: HTTP ${res.status} (${granted ? "granted" : "denied"})`);
|
|
225
249
|
// Drain the body (a full premium.tar.gz on success) so we don't leave
|
|
226
250
|
// it dangling - we only need the status, not the content.
|
|
227
251
|
if (res.body) {
|
|
@@ -603,6 +603,16 @@ async function vendorTdkExtension(projectRoot) {
|
|
|
603
603
|
export function verifyMasterConfigs(projectRoot) {
|
|
604
604
|
const errors = [];
|
|
605
605
|
const warnings = [];
|
|
606
|
+
const diffs = [];
|
|
607
|
+
const addDrift = (displayPath, actual, expected) => {
|
|
608
|
+
if (actual === expected)
|
|
609
|
+
return;
|
|
610
|
+
errors.push(`${actual === undefined ? "Missing file" : "Out of sync"}: ${displayPath} (run 'tdk config regenerate')`);
|
|
611
|
+
diffs.push({
|
|
612
|
+
file: displayPath,
|
|
613
|
+
diff: createUnifiedDiff(displayPath, actual ?? "", expected),
|
|
614
|
+
});
|
|
615
|
+
};
|
|
606
616
|
const projectConfig = readProjectConfig(projectRoot);
|
|
607
617
|
const engine = new TemplateEngine();
|
|
608
618
|
const expectedFiles = engine.generateAll(projectConfig);
|
|
@@ -610,22 +620,10 @@ export function verifyMasterConfigs(projectRoot) {
|
|
|
610
620
|
for (const filename of ALL_GENERATED_FILES) {
|
|
611
621
|
const expectedContent = expectedFiles[filename];
|
|
612
622
|
const filePath = path.join(outputDir, filename);
|
|
613
|
-
|
|
614
|
-
errors.push(`Missing file: .tdk/.tdk-out/${filename}`);
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
const actualContent = fs.readFileSync(filePath, "utf-8");
|
|
618
|
-
if (actualContent !== expectedContent) {
|
|
619
|
-
errors.push(`Out of sync: .tdk/.tdk-out/${filename} (run 'tdk config regenerate')`);
|
|
620
|
-
}
|
|
623
|
+
addDrift(`.tdk/.tdk-out/${filename}`, fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : undefined, expectedContent);
|
|
621
624
|
}
|
|
622
625
|
const rootTiltIgnore = path.join(projectRoot, ".tiltignore");
|
|
623
|
-
|
|
624
|
-
errors.push("Missing file: .tiltignore");
|
|
625
|
-
}
|
|
626
|
-
else if (fs.readFileSync(rootTiltIgnore, "utf-8") !== expectedFiles[".tiltignore"]) {
|
|
627
|
-
errors.push("Out of sync: .tiltignore (run 'tdk config regenerate')");
|
|
628
|
-
}
|
|
626
|
+
addDrift(".tiltignore", fs.existsSync(rootTiltIgnore) ? fs.readFileSync(rootTiltIgnore, "utf-8") : undefined, expectedFiles[".tiltignore"]);
|
|
629
627
|
for (const manifestPath of discoverServiceManifestPaths(projectRoot)) {
|
|
630
628
|
const displayPath = path.relative(projectRoot, manifestPath).split(path.sep).join("/");
|
|
631
629
|
const validation = validateServiceManifestFile(manifestPath, displayPath);
|
|
@@ -633,7 +631,21 @@ export function verifyMasterConfigs(projectRoot) {
|
|
|
633
631
|
warnings.push(...validation.warnings);
|
|
634
632
|
errors.push(...verifyGeneratedResourceFiles(projectRoot, manifestPath));
|
|
635
633
|
}
|
|
636
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
634
|
+
return { valid: errors.length === 0, errors, warnings, diffs };
|
|
635
|
+
}
|
|
636
|
+
function createUnifiedDiff(file, actual, expected) {
|
|
637
|
+
const oldLines = actual.replace(/\n$/, "").split("\n");
|
|
638
|
+
const newLines = expected.replace(/\n$/, "").split("\n");
|
|
639
|
+
const lines = [
|
|
640
|
+
`--- a/${file}`,
|
|
641
|
+
`+++ b/${file}`,
|
|
642
|
+
`@@ -1,${oldLines.length} +1,${newLines.length} @@`,
|
|
643
|
+
];
|
|
644
|
+
for (const line of oldLines)
|
|
645
|
+
lines.push(`-${line}`);
|
|
646
|
+
for (const line of newLines)
|
|
647
|
+
lines.push(`+${line}`);
|
|
648
|
+
return lines.join("\n");
|
|
637
649
|
}
|
|
638
650
|
function verifyGeneratedResourceFiles(projectRoot, serviceJsonPath) {
|
|
639
651
|
const serviceDir = path.dirname(serviceJsonPath);
|