@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +59 -13
  3. package/cli/bin/tdk.js +22 -2
  4. package/cli/dist/cli.js +9 -2
  5. package/cli/dist/commands/completion.js +30 -3
  6. package/cli/dist/commands/config.js +59 -1
  7. package/cli/dist/commands/doctor.js +295 -98
  8. package/cli/dist/commands/eject.js +77 -0
  9. package/cli/dist/commands/networks.js +17 -4
  10. package/cli/dist/commands/project.js +28 -5
  11. package/cli/dist/commands/resource.js +118 -10
  12. package/cli/dist/commands/up.js +93 -21
  13. package/cli/dist/commands/upgrade.js +163 -68
  14. package/cli/dist/generator/template-engine.js +77 -13
  15. package/cli/dist/types/index.js +6 -1
  16. package/cli/dist/utils/cold-preflight.js +198 -0
  17. package/cli/dist/utils/constants.js +4 -0
  18. package/cli/dist/utils/docker.js +15 -1
  19. package/cli/dist/utils/doctor-runtime.js +48 -16
  20. package/cli/dist/utils/doctor-wiring.js +269 -0
  21. package/cli/dist/utils/errors.js +5 -5
  22. package/cli/dist/utils/exec-async.js +30 -0
  23. package/cli/dist/utils/generated-paths.js +49 -0
  24. package/cli/dist/utils/platform.js +53 -0
  25. package/cli/dist/utils/port-assignment.js +13 -20
  26. package/cli/dist/utils/project-templates.js +7 -2
  27. package/cli/dist/utils/resource-features.js +6 -2
  28. package/cli/dist/utils/service-manifest.js +73 -0
  29. package/cli/dist/utils/services.js +8 -1
  30. package/cli/dist/utils/tar.js +27 -5
  31. package/cli/dist/utils/tilt-process.js +53 -0
  32. package/cli/dist/utils/tilt.js +3 -1
  33. package/cli/dist/utils/which.js +26 -0
  34. package/cli/dist/utils/windows-doctor.js +175 -0
  35. package/cli/package.json +1 -1
  36. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +1 -2
  37. package/cli/templates/TILT_TECH_STACK.star.hbs +0 -1
  38. package/cli/templates/Tiltfile.hbs +24 -4
  39. package/cli/templates/spec.master.hbs +1 -2
  40. package/discovery/AGENTS.md +1 -1
  41. package/discovery/constants.star +2 -2
  42. package/discovery/discovery_orchestrator.star +2 -2
  43. package/discovery/manifest/constants.star +1 -1
  44. package/discovery/manifest/loading.star +7 -3
  45. package/discovery/registry.star +17 -3
  46. package/engine/README.md +2 -0
  47. package/engine/schemas/service-schema.json +47 -21
  48. package/engine/scripts/generate-manifest.sh +2 -1
  49. package/engine/topologies/platform/docker/compose/compose.star +37 -5
  50. package/engine/topologies/tilt/common/utils.star +54 -11
  51. package/engine/topologies/tilt/manifest/AGENTS.md +1 -1
  52. package/engine/topologies/tilt/manifest/constants.star +3 -0
  53. package/engine/topologies/tilt/manifest/schema.star +2 -4
  54. package/engine/topologies/tilt/manifest/validator.star +1 -1
  55. package/engine/topologies/tilt/resources/infra-loader.star +14 -3
  56. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +15 -11
  57. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +15 -6
  58. package/examples/tdk-example/LICENSE +21 -0
  59. package/examples/tdk-example/README.md +23 -0
  60. package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
  61. package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
  62. package/examples/tdk-example/Tiltfile +25 -0
  63. package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
  64. package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
  65. package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
  66. package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
  67. package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
  68. package/examples/tdk-example/services/shop/orders-app/package.json +20 -0
  69. package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
  70. package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
  71. package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
  72. package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
  73. package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
  74. package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
  75. package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
  76. package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
  77. 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
- execFileSync("git", ["clone", template.repo, destination], { stdio: "inherit" });
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("git clone failed. Check you have git installed and network access.");
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
- if (!validTypes.includes(options.type)) {
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 = options.type;
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 assignedPort = resourceType === "sdk"
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: ${assignedPort || "N/A (SDK)"}`));
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 = await confirmOrCancel(shouldRegisterExisting && hasServiceJson
444
- ? "\nRegister existing resource?"
445
- : "\nCreate resource?");
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 });
@@ -1,17 +1,56 @@
1
- import { execSync } from "node:child_process";
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
- await withTiltCheck(async () => {
25
- const projectRoot = findProjectRoot() ?? process.cwd();
26
- const copiedAssets = ensureProjectRuntimeAssets(projectRoot);
27
- if (copiedAssets.length > 0 && options.verbose && !options.quiet) {
28
- console.log(chalk.gray(`Refreshed runtime assets: ${copiedAssets.join(", ")}`));
29
- }
30
- const newlyEnabledStacks = enableDiscoveredStacks(projectRoot);
31
- if (newlyEnabledStacks.length > 0 && !options.quiet) {
32
- console.log(chalk.gray(`Added ${newlyEnabledStacks.join(", ")} to the pre_alpha phase in .tdk/project.json`));
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(`Starting ${formatCount(serviceNames.length, "service")} from ${stackDescription}...`));
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 result = await runTilt("up", tiltArgs, {
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