@tdk-landscape/tdk-cli-core 1.3.75 → 1.3.77
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 +59 -13
- package/cli/bin/tdk.js +22 -2
- package/cli/dist/cli.js +9 -2
- package/cli/dist/commands/completion.js +30 -3
- package/cli/dist/commands/config.js +59 -1
- package/cli/dist/commands/doctor.js +295 -98
- package/cli/dist/commands/eject.js +77 -0
- package/cli/dist/commands/networks.js +17 -4
- package/cli/dist/commands/project.js +28 -5
- package/cli/dist/commands/resource.js +118 -10
- package/cli/dist/commands/up.js +93 -21
- package/cli/dist/commands/upgrade.js +163 -68
- package/cli/dist/generator/template-engine.js +77 -13
- package/cli/dist/types/index.js +6 -1
- package/cli/dist/utils/cold-preflight.js +198 -0
- package/cli/dist/utils/constants.js +4 -0
- package/cli/dist/utils/docker.js +15 -1
- package/cli/dist/utils/doctor-runtime.js +48 -16
- package/cli/dist/utils/doctor-wiring.js +269 -0
- package/cli/dist/utils/errors.js +5 -5
- package/cli/dist/utils/exec-async.js +30 -0
- package/cli/dist/utils/generated-paths.js +49 -0
- package/cli/dist/utils/platform.js +53 -0
- package/cli/dist/utils/port-assignment.js +13 -20
- package/cli/dist/utils/project-templates.js +7 -2
- package/cli/dist/utils/resource-features.js +6 -2
- package/cli/dist/utils/service-manifest.js +73 -0
- package/cli/dist/utils/services.js +8 -1
- package/cli/dist/utils/tar.js +27 -5
- package/cli/dist/utils/tilt-process.js +53 -0
- package/cli/dist/utils/tilt.js +3 -1
- package/cli/dist/utils/which.js +26 -0
- package/cli/dist/utils/windows-doctor.js +175 -0
- package/cli/package.json +1 -1
- package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +1 -2
- package/cli/templates/TILT_TECH_STACK.star.hbs +0 -1
- package/cli/templates/Tiltfile.hbs +24 -4
- package/cli/templates/spec.master.hbs +1 -2
- package/discovery/AGENTS.md +1 -1
- package/discovery/constants.star +2 -2
- package/discovery/discovery_orchestrator.star +2 -2
- package/discovery/manifest/constants.star +1 -1
- package/discovery/manifest/loading.star +7 -3
- package/discovery/registry.star +17 -3
- package/engine/README.md +2 -0
- package/engine/schemas/service-schema.json +47 -21
- package/engine/scripts/generate-manifest.sh +2 -1
- package/engine/topologies/platform/docker/compose/compose.star +37 -5
- package/engine/topologies/tilt/common/utils.star +54 -11
- package/engine/topologies/tilt/manifest/AGENTS.md +1 -1
- package/engine/topologies/tilt/manifest/constants.star +3 -0
- package/engine/topologies/tilt/manifest/schema.star +2 -4
- package/engine/topologies/tilt/manifest/validator.star +1 -1
- package/engine/topologies/tilt/resources/infra-loader.star +14 -3
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +15 -11
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +15 -6
- package/examples/tdk-example/LICENSE +21 -0
- package/examples/tdk-example/README.md +23 -0
- package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
- package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
- package/examples/tdk-example/Tiltfile +25 -0
- package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
- package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
- package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
- package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
- package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
- package/examples/tdk-example/services/shop/orders-app/package.json +20 -0
- package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
- package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
- package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
- package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
- package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
- package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
- package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
- package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
- package/package.json +2 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { cpSync, existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { cwd } from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
import { Command } from "commander";
|
|
7
8
|
import { generateMasterConfigs, readProjectConfig } from "../generator/template-engine.js";
|
|
@@ -94,13 +95,33 @@ async function cloneProjectTemplate(templateName, targetDir) {
|
|
|
94
95
|
}
|
|
95
96
|
showCommandHeader(`Cloning template: ${templateName}`);
|
|
96
97
|
showDetail(`${template.description}\n`, 0);
|
|
97
|
-
showDetail(`Source: ${template.repo}`, 0);
|
|
98
|
+
showDetail(`Source: ${template.bundledPath ? "bundled TDK example" : template.repo}`, 0);
|
|
98
99
|
showDetail(`Destination: ${destination}\n`, 0);
|
|
99
100
|
try {
|
|
100
|
-
|
|
101
|
+
if (template.bundledPath) {
|
|
102
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../");
|
|
103
|
+
cpSync(resolve(packageRoot, template.bundledPath), destination, { recursive: true });
|
|
104
|
+
const projectSlug = basename(destination).replace(/-/g, "_");
|
|
105
|
+
for (const file of [
|
|
106
|
+
"services/platform/messaging/docker-compose.yml",
|
|
107
|
+
"services/shop/orders-api/service.json",
|
|
108
|
+
"services/shop/orders-api/src/index.ts",
|
|
109
|
+
"services/shop/orders-worker/service.json",
|
|
110
|
+
"services/shop/orders-worker/src/index.ts",
|
|
111
|
+
]) {
|
|
112
|
+
const copiedPath = join(destination, file);
|
|
113
|
+
const contents = readFileSync(copiedPath, "utf-8");
|
|
114
|
+
writeFileSync(copiedPath, contents.replaceAll("tdk_example", projectSlug));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
execFileSync("git", ["clone", template.repo, destination], { stdio: "inherit" });
|
|
119
|
+
}
|
|
101
120
|
}
|
|
102
121
|
catch {
|
|
103
|
-
showErrorAndExit(
|
|
122
|
+
showErrorAndExit(template.bundledPath
|
|
123
|
+
? "Bundled example copy failed. Reinstall the TDK CLI package and try again."
|
|
124
|
+
: "git clone failed. Check you have git installed and network access.");
|
|
104
125
|
}
|
|
105
126
|
console.log(chalk.green(`\n✅ Cloned "${templateName}" into ${dirName}/`));
|
|
106
127
|
showDetail("\nNext steps:", 0);
|
|
@@ -160,6 +181,8 @@ export const projectCommand = new Command("project")
|
|
|
160
181
|
.option("--yes", "Non-interactive mode (use defaults)")
|
|
161
182
|
.option("--config-file <path>", "Load project config from existing JSON file")
|
|
162
183
|
.action(async (template, options) => {
|
|
184
|
+
const { assertMachineReadyOrExit } = await import("../utils/cold-preflight.js");
|
|
185
|
+
await assertMachineReadyOrExit();
|
|
163
186
|
if (template) {
|
|
164
187
|
await runCommand(async () => cloneProjectTemplate(template, options.path));
|
|
165
188
|
return;
|
|
@@ -7,12 +7,13 @@ import { hasDddLicense } from "../generator/extension-fetch.js";
|
|
|
7
7
|
import { CREATABLE_RESOURCE_TYPES } from "../types/index.js";
|
|
8
8
|
import { assertValid, confirmOrCancel } from "../utils/command-helpers.js";
|
|
9
9
|
import { chooseResourcePath, isPathDiscovered, readDiscoveryPaths, } from "../utils/discovery-paths.js";
|
|
10
|
-
import { errorFactories, requireProjectRoot, runCommand } from "../utils/errors.js";
|
|
10
|
+
import { errorFactories, requireProjectRoot, runCommand, TdkError } from "../utils/errors.js";
|
|
11
11
|
import { writeFilesWithProgress } from "../utils/file-helpers.js";
|
|
12
12
|
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
17
|
import { discoverResources } from "../utils/services.js";
|
|
17
18
|
import { createKebabCaseValidator, isPathSafe, validateResourceName } from "../utils/validation.js";
|
|
18
19
|
export const BASE_TEMPLATE = {
|
|
@@ -43,7 +44,25 @@ export const TYPE_SPECIFIC = {
|
|
|
43
44
|
watch: ["src/**/*"],
|
|
44
45
|
},
|
|
45
46
|
},
|
|
47
|
+
"bring-your-own": {
|
|
48
|
+
healthCheck: "/health",
|
|
49
|
+
},
|
|
46
50
|
};
|
|
51
|
+
export function resolveByoPort(value, assignedPort, resources) {
|
|
52
|
+
if (!value)
|
|
53
|
+
return assignedPort;
|
|
54
|
+
if (!/^\d+$/.test(value)) {
|
|
55
|
+
throw new TdkError("BYO --port must be an integer from 4000 through 5999.");
|
|
56
|
+
}
|
|
57
|
+
const port = Number(value);
|
|
58
|
+
if (!Number.isInteger(port) || port < 4000 || port > 5999) {
|
|
59
|
+
throw new TdkError("BYO --port must be an integer from 4000 through 5999.");
|
|
60
|
+
}
|
|
61
|
+
if (resources.some((resource) => resource.config?.port === port)) {
|
|
62
|
+
throw new TdkError(`BYO port ${port} is already assigned to another resource.`);
|
|
63
|
+
}
|
|
64
|
+
return port;
|
|
65
|
+
}
|
|
47
66
|
export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId) {
|
|
48
67
|
const framework = resolveFrontendFramework(type, frameworkId);
|
|
49
68
|
const typeSpecific = TYPE_SPECIFIC[type];
|
|
@@ -58,6 +77,7 @@ export function createServiceJson(name, type, stack, port, extraFeatures = [], f
|
|
|
58
77
|
}
|
|
59
78
|
return {
|
|
60
79
|
...base,
|
|
80
|
+
schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
61
81
|
appName: name,
|
|
62
82
|
appType: type,
|
|
63
83
|
featuresEnabled: [...getDefaultFeaturesForResourceType(type), ...extraFeatures],
|
|
@@ -289,13 +309,19 @@ describe('${name}', () => {
|
|
|
289
309
|
export const resourceCommand = new Command("resource")
|
|
290
310
|
.description("Create a new resource (service) from scratch, or register an existing one")
|
|
291
311
|
.argument("[name]", "Resource name (kebab-case)")
|
|
292
|
-
.option("-t, --type <type>", "Resource type: backend, frontend, worker, sdk", "backend")
|
|
312
|
+
.option("-t, --type <type>", "Resource type: backend, frontend, worker, bring-your-own, sdk", "backend")
|
|
293
313
|
.option("--framework <id>", "Frontend framework: react (default), vue")
|
|
294
314
|
.option("-s, --stack <stack>", "Stack to assign resource to", "default")
|
|
295
315
|
.option("-p, --path <path>", "Custom path for resource directory")
|
|
296
316
|
.option("--resource-path <path>", "Alias for --path (for backward compatibility)")
|
|
297
317
|
.option("--register-existing", "Register an existing resource without creating templates")
|
|
318
|
+
.option("-y, --yes", "Skip confirmation prompt", false)
|
|
298
319
|
.option("--ddd", "Scaffold DDD (domain-driven design) folders + path aliases (Premium - requires TDK_LICENSE_KEY)")
|
|
320
|
+
.option("--dockerfile <path>", "Path to Dockerfile relative to resource dir (for bring-your-own)")
|
|
321
|
+
.option("--health-path <path>", "HTTP health check path (for bring-your-own)", "/health")
|
|
322
|
+
.option("--no-proxy", "Disable the Traefik route (bring-your-own only)")
|
|
323
|
+
.option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
|
|
324
|
+
.option("--port <port>", "Port number (default: next free in 4000-5999)")
|
|
299
325
|
.action(async (name, options) => {
|
|
300
326
|
await runCommand(async () => {
|
|
301
327
|
const projectRoot = requireProjectRoot();
|
|
@@ -320,20 +346,28 @@ export const resourceCommand = new Command("resource")
|
|
|
320
346
|
// Support sdk type for registering existing SDKs
|
|
321
347
|
let resourceType;
|
|
322
348
|
const validTypes = [...CREATABLE_RESOURCE_TYPES, "sdk"];
|
|
323
|
-
|
|
349
|
+
// Normalize bring-your-own aliases
|
|
350
|
+
const normalizedType = options.type === "byo" || options.type === "bring-your-own"
|
|
351
|
+
? "bring-your-own"
|
|
352
|
+
: options.type;
|
|
353
|
+
if (!validTypes.includes(normalizedType)) {
|
|
324
354
|
const selectedType = await promptSelect({
|
|
325
355
|
message: "Resource type:",
|
|
326
356
|
choices: [
|
|
327
357
|
{ title: "backend - API service with HTTP endpoints", value: "backend" },
|
|
328
358
|
{ title: "frontend - Web application/UI", value: "frontend" },
|
|
329
359
|
{ title: "worker - Background job processor", value: "worker" },
|
|
360
|
+
{
|
|
361
|
+
title: "bring-your-own - Wrap an existing service. No app scaffold. Needs Dockerfile or --image.",
|
|
362
|
+
value: "bring-your-own",
|
|
363
|
+
},
|
|
330
364
|
{ title: "sdk - Library/SDK (register existing)", value: "sdk" },
|
|
331
365
|
],
|
|
332
366
|
});
|
|
333
367
|
resourceType = selectedType;
|
|
334
368
|
}
|
|
335
369
|
else {
|
|
336
|
-
resourceType =
|
|
370
|
+
resourceType = normalizedType;
|
|
337
371
|
}
|
|
338
372
|
const frontendFramework = resolveFrontendFramework(resourceType, options.framework);
|
|
339
373
|
let dddEnabled = false;
|
|
@@ -400,6 +434,7 @@ export const resourceCommand = new Command("resource")
|
|
|
400
434
|
backend: `services/${stackName}/${resourceName}`,
|
|
401
435
|
frontend: `apps/${resourceName}`,
|
|
402
436
|
worker: `workers/${resourceName}`,
|
|
437
|
+
"bring-your-own": `services/${stackName}/${resourceName}`,
|
|
403
438
|
sdk: `packages/${resourceName}`,
|
|
404
439
|
};
|
|
405
440
|
finalResourcePath = defaultPaths[resourceType];
|
|
@@ -425,24 +460,28 @@ export const resourceCommand = new Command("resource")
|
|
|
425
460
|
const shouldRegisterExisting = options.registerExisting ||
|
|
426
461
|
resourceType === "sdk" ||
|
|
427
462
|
(isExistingResource && hasServiceJson);
|
|
428
|
-
if (isExistingResource && !shouldRegisterExisting) {
|
|
463
|
+
if (isExistingResource && !shouldRegisterExisting && resourceType !== "bring-your-own") {
|
|
429
464
|
errorFactories.directoryExists(fullPath).exit();
|
|
430
465
|
}
|
|
431
|
-
const
|
|
466
|
+
const nextPort = resourceType === "sdk"
|
|
432
467
|
? 0
|
|
433
468
|
: assignPort(resourceType, allResources);
|
|
469
|
+
const assignedPort = resourceType === "bring-your-own"
|
|
470
|
+
? resolveByoPort(options.port, nextPort, allResources)
|
|
471
|
+
: nextPort;
|
|
434
472
|
console.log(chalk.gray("\nResource details:"));
|
|
435
473
|
console.log(chalk.gray(` Name: ${resourceName}`));
|
|
436
474
|
console.log(chalk.gray(` Type: ${resourceType}`));
|
|
437
475
|
console.log(chalk.gray(` Stack: ${stackName}`));
|
|
438
|
-
console.log(chalk.gray(` Port:
|
|
476
|
+
console.log(chalk.gray(` Port: ${assignedPort || "N/A (SDK)"}`));
|
|
439
477
|
console.log(chalk.gray(` Path: ${finalResourcePath}`));
|
|
440
478
|
if (shouldRegisterExisting && hasServiceJson) {
|
|
441
479
|
console.log(chalk.yellow("\n⚠️ Existing resource detected - will update service.json only"));
|
|
442
480
|
}
|
|
443
|
-
const confirmed =
|
|
444
|
-
|
|
445
|
-
|
|
481
|
+
const confirmed = options.yes ||
|
|
482
|
+
(await confirmOrCancel(shouldRegisterExisting && hasServiceJson
|
|
483
|
+
? "\nRegister existing resource?"
|
|
484
|
+
: "\nCreate resource?"));
|
|
446
485
|
if (!confirmed)
|
|
447
486
|
return;
|
|
448
487
|
// Handle existing resource registration
|
|
@@ -455,6 +494,7 @@ export const resourceCommand = new Command("resource")
|
|
|
455
494
|
// Update with new values while preserving existing fields
|
|
456
495
|
const updatedServiceJson = {
|
|
457
496
|
...existingServiceJson,
|
|
497
|
+
schemaVersion: existingServiceJson.schemaVersion ?? SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
458
498
|
appName: resourceName,
|
|
459
499
|
appType: resourceType === "sdk" ? "sdk" : existingServiceJson.appType || resourceType,
|
|
460
500
|
stack: stackName,
|
|
@@ -469,6 +509,74 @@ export const resourceCommand = new Command("resource")
|
|
|
469
509
|
console.log(chalk.gray(` tdk up ${stackName}`));
|
|
470
510
|
return;
|
|
471
511
|
}
|
|
512
|
+
// Handle bring-your-own type
|
|
513
|
+
if (resourceType === "bring-your-own") {
|
|
514
|
+
console.log(chalk.blue("\n📁 Creating bring-your-own resource..."));
|
|
515
|
+
// Parse port option
|
|
516
|
+
const port = assignedPort;
|
|
517
|
+
const dockerfile = options.dockerfile || "./Dockerfile";
|
|
518
|
+
const dockerfilePath = resolve(fullPath, dockerfile);
|
|
519
|
+
const dockerfileRelativePath = relative(fullPath, dockerfilePath);
|
|
520
|
+
if (isAbsolute(dockerfileRelativePath) ||
|
|
521
|
+
dockerfileRelativePath === ".." ||
|
|
522
|
+
dockerfileRelativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
523
|
+
errorFactories.invalidPath(dockerfile).exit();
|
|
524
|
+
}
|
|
525
|
+
mkdirSync(fullPath, { recursive: true });
|
|
526
|
+
// Create service.json
|
|
527
|
+
const byoServiceJson = {
|
|
528
|
+
schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
|
|
529
|
+
appName: resourceName,
|
|
530
|
+
appType: "bring-your-own",
|
|
531
|
+
stack: stackName,
|
|
532
|
+
port,
|
|
533
|
+
healthCheckPath: options.healthPath || "/health",
|
|
534
|
+
...(options.image ? { image: options.image } : { dockerfile }),
|
|
535
|
+
...(options.proxy === false ? { exposeViaProxy: false } : {}),
|
|
536
|
+
};
|
|
537
|
+
const { writeFileSync } = await import("node:fs");
|
|
538
|
+
writeFileSync(resolve(fullPath, "service.json"), JSON.stringify(byoServiceJson, null, 2));
|
|
539
|
+
// Create Dockerfile stub if no image provided and dockerfile doesn't exist
|
|
540
|
+
if (!options.image && !existsSync(dockerfilePath)) {
|
|
541
|
+
mkdirSync(resolve(dockerfilePath, ".."), { recursive: true });
|
|
542
|
+
const healthConfigPath = resolve(dockerfilePath, "..", "health.conf");
|
|
543
|
+
const healthConfigRelativePath = relative(fullPath, healthConfigPath).replace(/\\/g, "/");
|
|
544
|
+
const dockerfileContent = `FROM nginx:1.27-alpine
|
|
545
|
+
RUN apk add --no-cache curl
|
|
546
|
+
COPY ${healthConfigRelativePath} /etc/nginx/conf.d/default.conf
|
|
547
|
+
EXPOSE ${port}
|
|
548
|
+
`;
|
|
549
|
+
writeFileSync(dockerfilePath, dockerfileContent);
|
|
550
|
+
const healthConfContent = `server {
|
|
551
|
+
listen ${port};
|
|
552
|
+
location /health {
|
|
553
|
+
default_type text/plain;
|
|
554
|
+
return 200 'ok';
|
|
555
|
+
}
|
|
556
|
+
location / {
|
|
557
|
+
default_type text/plain;
|
|
558
|
+
return 200 'byo';
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
`;
|
|
562
|
+
writeFileSync(healthConfigPath, healthConfContent);
|
|
563
|
+
}
|
|
564
|
+
// Create AGENTS.md
|
|
565
|
+
const agentsMdContent = `# AGENTS.md
|
|
566
|
+
|
|
567
|
+
DO NOT DELETE service.json
|
|
568
|
+
|
|
569
|
+
This file contains the resource configuration for TDK.
|
|
570
|
+
`;
|
|
571
|
+
const agentsPath = resolve(fullPath, "AGENTS.md");
|
|
572
|
+
if (!existsSync(agentsPath))
|
|
573
|
+
writeFileSync(agentsPath, agentsMdContent);
|
|
574
|
+
console.log(chalk.green("\n✅ Bring-your-own resource created successfully!"));
|
|
575
|
+
console.log(chalk.gray(`\nLocation: ${fullPath}`));
|
|
576
|
+
console.log(chalk.gray(`\nNext steps:`));
|
|
577
|
+
console.log(chalk.gray(` tdk up ${stackName}`));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
472
580
|
// Create directory structure for new resources
|
|
473
581
|
console.log(chalk.blue("\n📁 Creating directory structure..."));
|
|
474
582
|
mkdirSync(fullPath, { recursive: true });
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -1,17 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { connect } from "node:net";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { ensureProjectRuntimeAssets } from "../generator/template-engine.js";
|
|
5
5
|
import { handleDryRun } from "../utils/command-helpers.js";
|
|
6
6
|
import { checkHostPorts } from "../utils/doctor-runtime.js";
|
|
7
|
-
import { errorFactories, handleTiltFailure, withTiltCheck } from "../utils/errors.js";
|
|
7
|
+
import { errorFactories, handleTiltFailure, requireProjectRoot, runCommand, showErrorAndExit, withTiltCheck, } from "../utils/errors.js";
|
|
8
8
|
import { formatCount } from "../utils/formatting.js";
|
|
9
9
|
import { findProjectRoot } from "../utils/paths.js";
|
|
10
10
|
import { findAvailablePort } from "../utils/port-assignment.js";
|
|
11
11
|
import { appendHealthPath, resolveSubdomainBases } from "../utils/service-urls.js";
|
|
12
12
|
import { discoverResources, discoverStacks, getResourcesForStack, stackExists, } from "../utils/services.js";
|
|
13
13
|
import { buildTiltUpArgs, runTilt } from "../utils/tilt.js";
|
|
14
|
+
import { stopTiltOnPort } from "../utils/tilt-process.js";
|
|
14
15
|
import { enableDiscoveredStacks } from "./project.js";
|
|
16
|
+
export function formatUpSuccess(port, appUrls = []) {
|
|
17
|
+
return [
|
|
18
|
+
"TDK is up.",
|
|
19
|
+
`Tilt UI: http://localhost:${port}`,
|
|
20
|
+
"App URLs:",
|
|
21
|
+
...(appUrls.length > 0
|
|
22
|
+
? appUrls.slice(0, 5).map((url) => ` ${url}`)
|
|
23
|
+
: [" run: tdk networks"]),
|
|
24
|
+
"Stop: tdk down",
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
export function nativeWindowsUpRefusal(platform, allowNativeWindows) {
|
|
28
|
+
if (platform !== "win32" || allowNativeWindows === "1")
|
|
29
|
+
return null;
|
|
30
|
+
return "Native Windows is not supported for `tdk up`. Use WSL2 with Docker Desktop integration, then run `tdk doctor` inside your WSL project.";
|
|
31
|
+
}
|
|
32
|
+
function waitForTiltUi(port, timeoutMs = 30_000) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
|
+
const attempt = () => {
|
|
36
|
+
const socket = connect({ host: "127.0.0.1", port });
|
|
37
|
+
let settled = false;
|
|
38
|
+
const finish = (ready) => {
|
|
39
|
+
if (settled)
|
|
40
|
+
return;
|
|
41
|
+
settled = true;
|
|
42
|
+
socket.destroy();
|
|
43
|
+
if (ready || Date.now() >= deadline)
|
|
44
|
+
resolve(ready);
|
|
45
|
+
else
|
|
46
|
+
setTimeout(attempt, 250);
|
|
47
|
+
};
|
|
48
|
+
socket.once("connect", () => finish(true));
|
|
49
|
+
socket.once("error", () => finish(false));
|
|
50
|
+
};
|
|
51
|
+
attempt();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
15
54
|
export const upCommand = new Command("up")
|
|
16
55
|
.description("Start all services (optionally filtered by stack)")
|
|
17
56
|
.alias("deploy")
|
|
@@ -21,15 +60,26 @@ export const upCommand = new Command("up")
|
|
|
21
60
|
.option("--dry-run", "Show what would be started without starting", false)
|
|
22
61
|
.option("-f, --force", "Kill existing Tilt process before starting", false)
|
|
23
62
|
.action(async (stackName, options) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
const platformRefusal = nativeWindowsUpRefusal(process.platform, process.env.TDK_ALLOW_NATIVE_WINDOWS);
|
|
64
|
+
if (platformRefusal)
|
|
65
|
+
showErrorAndExit(platformRefusal);
|
|
66
|
+
if (!options.dryRun) {
|
|
67
|
+
const { assertMachineReadyOrExit } = await import("../utils/cold-preflight.js");
|
|
68
|
+
await assertMachineReadyOrExit();
|
|
69
|
+
}
|
|
70
|
+
const action = async () => {
|
|
71
|
+
const projectRoot = options.dryRun
|
|
72
|
+
? requireProjectRoot()
|
|
73
|
+
: (findProjectRoot() ?? process.cwd());
|
|
74
|
+
if (!options.dryRun) {
|
|
75
|
+
const copiedAssets = ensureProjectRuntimeAssets(projectRoot);
|
|
76
|
+
if (copiedAssets.length > 0 && options.verbose && !options.quiet) {
|
|
77
|
+
console.log(chalk.gray(`Refreshed runtime assets: ${copiedAssets.join(", ")}`));
|
|
78
|
+
}
|
|
79
|
+
const newlyEnabledStacks = enableDiscoveredStacks(projectRoot);
|
|
80
|
+
if (newlyEnabledStacks.length > 0 && !options.quiet) {
|
|
81
|
+
console.log(chalk.gray(`Added ${newlyEnabledStacks.join(", ")} to the pre_alpha phase in .tdk/project.json`));
|
|
82
|
+
}
|
|
33
83
|
}
|
|
34
84
|
let servicesToStart;
|
|
35
85
|
let stackDescription;
|
|
@@ -52,7 +102,7 @@ export const upCommand = new Command("up")
|
|
|
52
102
|
}
|
|
53
103
|
const serviceNames = servicesToStart.map((s) => s.name);
|
|
54
104
|
if (!options.quiet) {
|
|
55
|
-
console.log(chalk.blue(
|
|
105
|
+
console.log(chalk.blue(`${options.dryRun ? "Would start" : "Starting"} ${formatCount(serviceNames.length, "service")} from ${stackDescription}...`));
|
|
56
106
|
serviceNames.forEach((name) => {
|
|
57
107
|
console.log(chalk.gray(` - ${name}`));
|
|
58
108
|
});
|
|
@@ -93,17 +143,12 @@ export const upCommand = new Command("up")
|
|
|
93
143
|
if (hostPorts.fix)
|
|
94
144
|
console.log(chalk.gray(` ${hostPorts.fix}\n`));
|
|
95
145
|
}
|
|
96
|
-
if (options.force && !options.quiet) {
|
|
97
|
-
console.log(chalk.yellow("Force flag set - killing any existing Tilt processes..."));
|
|
98
|
-
execSync("killall tilt 2>/dev/null || true", { shell: "/bin/sh", stdio: "pipe" });
|
|
99
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
100
|
-
}
|
|
101
146
|
const basePort = 10350;
|
|
102
147
|
let port = basePort;
|
|
103
148
|
if (process.env.TILT_PORT) {
|
|
104
149
|
port = parseInt(process.env.TILT_PORT, 10);
|
|
105
150
|
}
|
|
106
|
-
else {
|
|
151
|
+
else if (!options.force) {
|
|
107
152
|
const availablePort = await findAvailablePort(basePort, 10);
|
|
108
153
|
if (availablePort && availablePort !== basePort) {
|
|
109
154
|
port = availablePort;
|
|
@@ -114,6 +159,13 @@ export const upCommand = new Command("up")
|
|
|
114
159
|
}
|
|
115
160
|
}
|
|
116
161
|
process.env.TILT_PORT = port.toString();
|
|
162
|
+
if (options.force) {
|
|
163
|
+
if (!options.quiet) {
|
|
164
|
+
console.log(chalk.yellow(`Force flag set - stopping Tilt on port ${port} if running...`));
|
|
165
|
+
}
|
|
166
|
+
stopTiltOnPort(port);
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
168
|
+
}
|
|
117
169
|
const tiltArgs = buildTiltUpArgs(focusServiceNames, {
|
|
118
170
|
verbose: options.verbose,
|
|
119
171
|
quiet: options.quiet,
|
|
@@ -123,16 +175,36 @@ export const upCommand = new Command("up")
|
|
|
123
175
|
if (!options.quiet) {
|
|
124
176
|
console.log(chalk.gray("\nRunning tilt up..."));
|
|
125
177
|
console.log(chalk.gray(`Using Tiltfile: .tdk/.tdk-out/Tiltfile`));
|
|
126
|
-
console.log(chalk.blue(`📊 Tilt UI: http://localhost:${port}/\n`));
|
|
127
178
|
}
|
|
128
|
-
const
|
|
179
|
+
const tiltRun = runTilt("up", tiltArgs, {
|
|
129
180
|
verbose: options.verbose,
|
|
130
181
|
quiet: options.quiet,
|
|
131
182
|
inheritStdio: !options.quiet, // Suppress tilt output in quiet mode
|
|
132
183
|
});
|
|
184
|
+
let printedSuccess = false;
|
|
185
|
+
const successOutput = (async () => {
|
|
186
|
+
const uiReady = await waitForTiltUi(port);
|
|
187
|
+
if (uiReady && !options.quiet) {
|
|
188
|
+
for (const line of formatUpSuccess(port))
|
|
189
|
+
console.log(chalk.blue(line));
|
|
190
|
+
printedSuccess = true;
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
const result = await tiltRun;
|
|
194
|
+
await successOutput;
|
|
133
195
|
if (result.exitCode !== 0) {
|
|
134
196
|
handleTiltFailure("up", result.exitCode);
|
|
135
197
|
}
|
|
136
|
-
|
|
198
|
+
if (!options.quiet && result.exitCode === 0 && !printedSuccess) {
|
|
199
|
+
for (const line of formatUpSuccess(port))
|
|
200
|
+
console.log(chalk.blue(line));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
if (options.dryRun) {
|
|
204
|
+
await runCommand(action);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
await withTiltCheck(action);
|
|
208
|
+
}
|
|
137
209
|
});
|
|
138
210
|
//# sourceMappingURL=up.js.map
|