@stacksolo/cli 0.1.4 → 0.1.5

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/dist/index.js CHANGED
@@ -4172,132 +4172,132 @@ function validateProject(project, errors) {
4172
4172
  }
4173
4173
  checkDuplicateNames(project, errors);
4174
4174
  }
4175
- function validateBucket(bucket, path27, errors) {
4175
+ function validateBucket(bucket, path28, errors) {
4176
4176
  if (!bucket.name) {
4177
- errors.push({ path: `${path27}.name`, message: "name is required" });
4177
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4178
4178
  } else if (!isValidBucketName(bucket.name)) {
4179
4179
  errors.push({
4180
- path: `${path27}.name`,
4180
+ path: `${path28}.name`,
4181
4181
  message: "bucket name must be 3-63 chars, lowercase alphanumeric with hyphens/underscores",
4182
4182
  value: bucket.name
4183
4183
  });
4184
4184
  }
4185
4185
  if (bucket.storageClass && !["STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE"].includes(bucket.storageClass)) {
4186
4186
  errors.push({
4187
- path: `${path27}.storageClass`,
4187
+ path: `${path28}.storageClass`,
4188
4188
  message: "storageClass must be STANDARD, NEARLINE, COLDLINE, or ARCHIVE",
4189
4189
  value: bucket.storageClass
4190
4190
  });
4191
4191
  }
4192
4192
  }
4193
- function validateSecret(secret, path27, errors) {
4193
+ function validateSecret(secret, path28, errors) {
4194
4194
  if (!secret.name) {
4195
- errors.push({ path: `${path27}.name`, message: "name is required" });
4195
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4196
4196
  } else if (!isValidResourceName(secret.name)) {
4197
4197
  errors.push({
4198
- path: `${path27}.name`,
4198
+ path: `${path28}.name`,
4199
4199
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4200
4200
  value: secret.name
4201
4201
  });
4202
4202
  }
4203
4203
  }
4204
- function validateTopic(topic, path27, errors) {
4204
+ function validateTopic(topic, path28, errors) {
4205
4205
  if (!topic.name) {
4206
- errors.push({ path: `${path27}.name`, message: "name is required" });
4206
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4207
4207
  } else if (!isValidResourceName(topic.name)) {
4208
4208
  errors.push({
4209
- path: `${path27}.name`,
4209
+ path: `${path28}.name`,
4210
4210
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4211
4211
  value: topic.name
4212
4212
  });
4213
4213
  }
4214
4214
  }
4215
- function validateQueue(queue, path27, errors) {
4215
+ function validateQueue(queue, path28, errors) {
4216
4216
  if (!queue.name) {
4217
- errors.push({ path: `${path27}.name`, message: "name is required" });
4217
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4218
4218
  } else if (!isValidResourceName(queue.name)) {
4219
4219
  errors.push({
4220
- path: `${path27}.name`,
4220
+ path: `${path28}.name`,
4221
4221
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4222
4222
  value: queue.name
4223
4223
  });
4224
4224
  }
4225
4225
  }
4226
- function validateCron(cron, path27, errors) {
4226
+ function validateCron(cron, path28, errors) {
4227
4227
  if (!cron.name) {
4228
- errors.push({ path: `${path27}.name`, message: "name is required" });
4228
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4229
4229
  }
4230
4230
  if (!cron.schedule) {
4231
- errors.push({ path: `${path27}.schedule`, message: "schedule is required" });
4231
+ errors.push({ path: `${path28}.schedule`, message: "schedule is required" });
4232
4232
  } else if (!isValidCronExpression(cron.schedule)) {
4233
4233
  errors.push({
4234
- path: `${path27}.schedule`,
4234
+ path: `${path28}.schedule`,
4235
4235
  message: "schedule must be a valid cron expression",
4236
4236
  value: cron.schedule
4237
4237
  });
4238
4238
  }
4239
4239
  if (!cron.target) {
4240
- errors.push({ path: `${path27}.target`, message: "target is required" });
4240
+ errors.push({ path: `${path28}.target`, message: "target is required" });
4241
4241
  }
4242
4242
  }
4243
- function validateNetwork(network, path27, errors) {
4243
+ function validateNetwork(network, path28, errors) {
4244
4244
  if (!network.name) {
4245
- errors.push({ path: `${path27}.name`, message: "name is required" });
4245
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4246
4246
  } else if (!isValidResourceName(network.name)) {
4247
4247
  errors.push({
4248
- path: `${path27}.name`,
4248
+ path: `${path28}.name`,
4249
4249
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4250
4250
  value: network.name
4251
4251
  });
4252
4252
  }
4253
4253
  if (network.containers) {
4254
4254
  network.containers.forEach((container, i) => {
4255
- validateContainer(container, `${path27}.containers[${i}]`, errors);
4255
+ validateContainer(container, `${path28}.containers[${i}]`, errors);
4256
4256
  });
4257
4257
  }
4258
4258
  if (network.functions) {
4259
4259
  network.functions.forEach((fn, i) => {
4260
- validateFunction(fn, `${path27}.functions[${i}]`, errors);
4260
+ validateFunction(fn, `${path28}.functions[${i}]`, errors);
4261
4261
  });
4262
4262
  }
4263
4263
  if (network.databases) {
4264
4264
  network.databases.forEach((db, i) => {
4265
- validateDatabase(db, `${path27}.databases[${i}]`, errors);
4265
+ validateDatabase(db, `${path28}.databases[${i}]`, errors);
4266
4266
  });
4267
4267
  }
4268
4268
  if (network.caches) {
4269
4269
  network.caches.forEach((cache2, i) => {
4270
- validateCache(cache2, `${path27}.caches[${i}]`, errors);
4270
+ validateCache(cache2, `${path28}.caches[${i}]`, errors);
4271
4271
  });
4272
4272
  }
4273
4273
  }
4274
- function validateContainer(container, path27, errors) {
4274
+ function validateContainer(container, path28, errors) {
4275
4275
  if (!container.name) {
4276
- errors.push({ path: `${path27}.name`, message: "name is required" });
4276
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4277
4277
  } else if (!isValidResourceName(container.name)) {
4278
4278
  errors.push({
4279
- path: `${path27}.name`,
4279
+ path: `${path28}.name`,
4280
4280
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4281
4281
  value: container.name
4282
4282
  });
4283
4283
  }
4284
4284
  if (container.memory && !isValidMemoryFormat(container.memory)) {
4285
4285
  errors.push({
4286
- path: `${path27}.memory`,
4286
+ path: `${path28}.memory`,
4287
4287
  message: "memory must be in format like 256Mi, 1Gi, 2Gi",
4288
4288
  value: container.memory
4289
4289
  });
4290
4290
  }
4291
4291
  if (container.env) {
4292
- validateEnvReferences(container.env, `${path27}.env`, errors);
4292
+ validateEnvReferences(container.env, `${path28}.env`, errors);
4293
4293
  }
4294
4294
  }
4295
- function validateFunction(fn, path27, errors) {
4295
+ function validateFunction(fn, path28, errors) {
4296
4296
  if (!fn.name) {
4297
- errors.push({ path: `${path27}.name`, message: "name is required" });
4297
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4298
4298
  } else if (!isValidResourceName(fn.name)) {
4299
4299
  errors.push({
4300
- path: `${path27}.name`,
4300
+ path: `${path28}.name`,
4301
4301
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4302
4302
  value: fn.name
4303
4303
  });
@@ -4305,21 +4305,21 @@ function validateFunction(fn, path27, errors) {
4305
4305
  const validRuntimes = ["nodejs20", "nodejs18", "python311", "python310", "go121", "go120"];
4306
4306
  if (fn.runtime && !validRuntimes.includes(fn.runtime)) {
4307
4307
  errors.push({
4308
- path: `${path27}.runtime`,
4308
+ path: `${path28}.runtime`,
4309
4309
  message: `runtime must be one of: ${validRuntimes.join(", ")}`,
4310
4310
  value: fn.runtime
4311
4311
  });
4312
4312
  }
4313
4313
  if (fn.env) {
4314
- validateEnvReferences(fn.env, `${path27}.env`, errors);
4314
+ validateEnvReferences(fn.env, `${path28}.env`, errors);
4315
4315
  }
4316
4316
  }
4317
- function validateDatabase(db, path27, errors) {
4317
+ function validateDatabase(db, path28, errors) {
4318
4318
  if (!db.name) {
4319
- errors.push({ path: `${path27}.name`, message: "name is required" });
4319
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4320
4320
  } else if (!isValidResourceName(db.name)) {
4321
4321
  errors.push({
4322
- path: `${path27}.name`,
4322
+ path: `${path28}.name`,
4323
4323
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4324
4324
  value: db.name
4325
4325
  });
@@ -4327,36 +4327,36 @@ function validateDatabase(db, path27, errors) {
4327
4327
  const validVersions = ["POSTGRES_15", "POSTGRES_14", "MYSQL_8_0", "MYSQL_5_7"];
4328
4328
  if (db.databaseVersion && !validVersions.includes(db.databaseVersion)) {
4329
4329
  errors.push({
4330
- path: `${path27}.databaseVersion`,
4330
+ path: `${path28}.databaseVersion`,
4331
4331
  message: `databaseVersion must be one of: ${validVersions.join(", ")}`,
4332
4332
  value: db.databaseVersion
4333
4333
  });
4334
4334
  }
4335
4335
  }
4336
- function validateCache(cache2, path27, errors) {
4336
+ function validateCache(cache2, path28, errors) {
4337
4337
  if (!cache2.name) {
4338
- errors.push({ path: `${path27}.name`, message: "name is required" });
4338
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4339
4339
  } else if (!isValidResourceName(cache2.name)) {
4340
4340
  errors.push({
4341
- path: `${path27}.name`,
4341
+ path: `${path28}.name`,
4342
4342
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4343
4343
  value: cache2.name
4344
4344
  });
4345
4345
  }
4346
4346
  if (cache2.tier && !["BASIC", "STANDARD_HA"].includes(cache2.tier)) {
4347
4347
  errors.push({
4348
- path: `${path27}.tier`,
4348
+ path: `${path28}.tier`,
4349
4349
  message: "tier must be BASIC or STANDARD_HA",
4350
4350
  value: cache2.tier
4351
4351
  });
4352
4352
  }
4353
4353
  }
4354
- function validateEnvReferences(env, path27, errors) {
4354
+ function validateEnvReferences(env, path28, errors) {
4355
4355
  for (const [key, value] of Object.entries(env)) {
4356
4356
  if (value.startsWith("@")) {
4357
4357
  if (!isValidReference(value)) {
4358
4358
  errors.push({
4359
- path: `${path27}.${key}`,
4359
+ path: `${path28}.${key}`,
4360
4360
  message: "invalid reference format. Expected @type/name or @type/name.property",
4361
4361
  value
4362
4362
  });
@@ -4366,15 +4366,15 @@ function validateEnvReferences(env, path27, errors) {
4366
4366
  }
4367
4367
  function checkDuplicateNames(project, errors) {
4368
4368
  const seen = /* @__PURE__ */ new Map();
4369
- const check = (name, path27) => {
4369
+ const check = (name, path28) => {
4370
4370
  if (seen.has(name)) {
4371
4371
  errors.push({
4372
- path: path27,
4372
+ path: path28,
4373
4373
  message: `duplicate resource name "${name}" (also defined at ${seen.get(name)})`,
4374
4374
  value: name
4375
4375
  });
4376
4376
  } else {
4377
- seen.set(name, path27);
4377
+ seen.set(name, path28);
4378
4378
  }
4379
4379
  };
4380
4380
  project.buckets?.forEach((b, i) => check(b.name, `project.buckets[${i}]`));
@@ -4384,15 +4384,15 @@ function checkDuplicateNames(project, errors) {
4384
4384
  project.crons?.forEach((c, i) => check(c.name, `project.crons[${i}]`));
4385
4385
  project.networks?.forEach((network, ni) => {
4386
4386
  const networkSeen = /* @__PURE__ */ new Map();
4387
- const checkNetwork = (name, path27) => {
4387
+ const checkNetwork = (name, path28) => {
4388
4388
  if (networkSeen.has(name)) {
4389
4389
  errors.push({
4390
- path: path27,
4390
+ path: path28,
4391
4391
  message: `duplicate resource name "${name}" in network "${network.name}" (also at ${networkSeen.get(name)})`,
4392
4392
  value: name
4393
4393
  });
4394
4394
  } else {
4395
- networkSeen.set(name, path27);
4395
+ networkSeen.set(name, path28);
4396
4396
  }
4397
4397
  };
4398
4398
  network.containers?.forEach((c, i) => checkNetwork(c.name, `project.networks[${ni}].containers[${i}]`));
@@ -5569,11 +5569,11 @@ function buildDependencyGraph(resources) {
5569
5569
  function detectCycles(graph) {
5570
5570
  const visited = /* @__PURE__ */ new Set();
5571
5571
  const recursionStack = /* @__PURE__ */ new Set();
5572
- const path27 = [];
5572
+ const path28 = [];
5573
5573
  function dfs(nodeId) {
5574
5574
  visited.add(nodeId);
5575
5575
  recursionStack.add(nodeId);
5576
- path27.push(nodeId);
5576
+ path28.push(nodeId);
5577
5577
  const node = graph.get(nodeId);
5578
5578
  if (node) {
5579
5579
  for (const depId of node.dependencies) {
@@ -5581,20 +5581,20 @@ function detectCycles(graph) {
5581
5581
  if (dfs(depId))
5582
5582
  return true;
5583
5583
  } else if (recursionStack.has(depId)) {
5584
- const cycleStart = path27.indexOf(depId);
5585
- path27.push(depId);
5584
+ const cycleStart = path28.indexOf(depId);
5585
+ path28.push(depId);
5586
5586
  return true;
5587
5587
  }
5588
5588
  }
5589
5589
  }
5590
- path27.pop();
5590
+ path28.pop();
5591
5591
  recursionStack.delete(nodeId);
5592
5592
  return false;
5593
5593
  }
5594
5594
  for (const nodeId of graph.keys()) {
5595
5595
  if (!visited.has(nodeId)) {
5596
5596
  if (dfs(nodeId)) {
5597
- return path27;
5597
+ return path28;
5598
5598
  }
5599
5599
  }
5600
5600
  }
@@ -5649,18 +5649,18 @@ function prefixBucketName(projectName, bucketName) {
5649
5649
  const truncated = bucketName.slice(0, remaining);
5650
5650
  return `${truncated}-${hash}`;
5651
5651
  }
5652
- function prefixRoutePath(projectName, path27) {
5653
- if (path27 === "/*" || path27 === "/") {
5652
+ function prefixRoutePath(projectName, path28) {
5653
+ if (path28 === "/*" || path28 === "/") {
5654
5654
  return `/${projectName}/*`;
5655
5655
  }
5656
- const normalized = path27.startsWith("/") ? path27 : `/${path27}`;
5656
+ const normalized = path28.startsWith("/") ? path28 : `/${path28}`;
5657
5657
  return `/${projectName}${normalized}`;
5658
5658
  }
5659
5659
  function relativeSourceDir(sourceProjectPath, sourceDir, outputDir) {
5660
- const path27 = __require("path");
5661
- const relativeToSource = path27.relative(outputDir, sourceProjectPath);
5660
+ const path28 = __require("path");
5661
+ const relativeToSource = path28.relative(outputDir, sourceProjectPath);
5662
5662
  const normalized = sourceDir.startsWith("./") ? sourceDir.slice(2) : sourceDir;
5663
- return path27.join(relativeToSource, normalized);
5663
+ return path28.join(relativeToSource, normalized);
5664
5664
  }
5665
5665
  function simpleHash(str) {
5666
5666
  let hash = 0;
@@ -6142,9 +6142,9 @@ function validateMergedConfig(config) {
6142
6142
  const warnings = [];
6143
6143
  const baseResult = validateConfig(config);
6144
6144
  const project = config.project;
6145
- const checkNameLength = (name, path27) => {
6145
+ const checkNameLength = (name, path28) => {
6146
6146
  if (name.length > 50) {
6147
- warnings.push(`${path27}: name "${name}" is ${name.length} chars (max 63). Consider shorter names.`);
6147
+ warnings.push(`${path28}: name "${name}" is ${name.length} chars (max 63). Consider shorter names.`);
6148
6148
  }
6149
6149
  };
6150
6150
  for (const bucket of project.buckets || []) {
@@ -6182,9 +6182,9 @@ function validateMergedConfig(config) {
6182
6182
  }
6183
6183
  }
6184
6184
  }
6185
- for (const [path27, backends] of routePaths) {
6185
+ for (const [path28, backends] of routePaths) {
6186
6186
  if (backends.length > 1) {
6187
- warnings.push(`Route path "${path27}" is used by multiple backends: ${backends.join(", ")}`);
6187
+ warnings.push(`Route path "${path28}" is used by multiple backends: ${backends.join(", ")}`);
6188
6188
  }
6189
6189
  }
6190
6190
  return {
@@ -13355,9 +13355,9 @@ import * as path15 from "path";
13355
13355
 
13356
13356
  // src/api-client.ts
13357
13357
  var API_BASE = process.env.STACKSOLO_API_URL || "http://localhost:4000";
13358
- async function callApi(path27, method = "GET", body) {
13358
+ async function callApi(path28, method = "GET", body) {
13359
13359
  try {
13360
- const response = await fetch(`${API_BASE}${path27}`, {
13360
+ const response = await fetch(`${API_BASE}${path28}`, {
13361
13361
  method,
13362
13362
  headers: {
13363
13363
  "Content-Type": "application/json"
@@ -13391,7 +13391,7 @@ var api = {
13391
13391
  },
13392
13392
  patterns: {
13393
13393
  list: () => callApi("/trpc/patterns.list"),
13394
- detect: (path27) => callApi(`/trpc/patterns.detect?input=${encodeURIComponent(JSON.stringify({ path: path27 }))}`)
13394
+ detect: (path28) => callApi(`/trpc/patterns.detect?input=${encodeURIComponent(JSON.stringify({ path: path28 }))}`)
13395
13395
  },
13396
13396
  deployments: {
13397
13397
  deploy: (projectId) => callApi("/trpc/deployments.deploy", "POST", { projectId }),
@@ -14168,9 +14168,9 @@ var listCommand = new Command11("list").description("List all registered project
14168
14168
  // src/commands/infra/events.ts
14169
14169
  import { Command as Command12 } from "commander";
14170
14170
  import chalk13 from "chalk";
14171
- function drawTableLine(columns, char = "-", join26 = "+") {
14171
+ function drawTableLine(columns, char = "-", join27 = "+") {
14172
14172
  const segments = columns.map((col) => char.repeat(col.width + 2));
14173
- return join26 + segments.join(join26) + join26;
14173
+ return join27 + segments.join(join27) + join27;
14174
14174
  }
14175
14175
  function drawTableRow(columns, values) {
14176
14176
  const cells = columns.map((col, i) => {
@@ -14660,10 +14660,10 @@ async function getGcpProjectId(explicitProject) {
14660
14660
  return explicitProject;
14661
14661
  }
14662
14662
  try {
14663
- const fs22 = await import("fs/promises");
14664
- const path27 = await import("path");
14665
- const configPath = path27.join(process.cwd(), ".stacksolo", "stacksolo.config.json");
14666
- const configData = await fs22.readFile(configPath, "utf-8");
14663
+ const fs23 = await import("fs/promises");
14664
+ const path28 = await import("path");
14665
+ const configPath = path28.join(process.cwd(), ".stacksolo", "stacksolo.config.json");
14666
+ const configData = await fs23.readFile(configPath, "utf-8");
14667
14667
  const config = JSON.parse(configData);
14668
14668
  if (config.project?.gcpProjectId) {
14669
14669
  return config.project.gcpProjectId;
@@ -15531,11 +15531,11 @@ var buildCommand = new Command16("build").description("Build and push container
15531
15531
 
15532
15532
  // src/commands/dev/dev.ts
15533
15533
  import { Command as Command17 } from "commander";
15534
- import chalk18 from "chalk";
15535
- import ora8 from "ora";
15536
- import { spawn as spawn6, execSync as execSync2 } from "child_process";
15537
- import * as fs19 from "fs/promises";
15538
- import * as path22 from "path";
15534
+ import chalk19 from "chalk";
15535
+ import ora9 from "ora";
15536
+ import { spawn as spawn7, execSync as execSync2 } from "child_process";
15537
+ import * as fs20 from "fs/promises";
15538
+ import * as path23 from "path";
15539
15539
 
15540
15540
  // src/generators/k8s/namespace.ts
15541
15541
  function generateNamespace(projectName) {
@@ -16910,17 +16910,300 @@ function generateK8sManifests(options) {
16910
16910
  };
16911
16911
  }
16912
16912
  async function writeK8sManifests(manifests, outputDir) {
16913
- const fs22 = await import("fs/promises");
16914
- const path27 = await import("path");
16915
- await fs22.mkdir(outputDir, { recursive: true });
16913
+ const fs23 = await import("fs/promises");
16914
+ const path28 = await import("path");
16915
+ await fs23.mkdir(outputDir, { recursive: true });
16916
16916
  for (const manifest of manifests) {
16917
- const filePath = path27.join(outputDir, manifest.filename);
16918
- await fs22.writeFile(filePath, manifest.content, "utf-8");
16917
+ const filePath = path28.join(outputDir, manifest.filename);
16918
+ await fs23.writeFile(filePath, manifest.content, "utf-8");
16919
16919
  }
16920
16920
  }
16921
16921
 
16922
16922
  // src/commands/dev/dev.ts
16923
16923
  init_plugin_loader_service();
16924
+
16925
+ // src/services/local-dev.service.ts
16926
+ import { spawn as spawn6 } from "child_process";
16927
+ import chalk18 from "chalk";
16928
+ import ora8 from "ora";
16929
+ import * as path22 from "path";
16930
+ import * as fs19 from "fs/promises";
16931
+ var COLORS = [
16932
+ chalk18.cyan,
16933
+ chalk18.magenta,
16934
+ chalk18.yellow,
16935
+ chalk18.green,
16936
+ chalk18.blue,
16937
+ chalk18.red
16938
+ ];
16939
+ var LocalPortAllocator = class {
16940
+ functionPort = 8081;
16941
+ uiPort = 3e3;
16942
+ containerPort = 9e3;
16943
+ nextFunctionPort() {
16944
+ return this.functionPort++;
16945
+ }
16946
+ nextUiPort() {
16947
+ return this.uiPort++;
16948
+ }
16949
+ nextContainerPort() {
16950
+ return this.containerPort++;
16951
+ }
16952
+ };
16953
+ function collectServices(config, projectRoot) {
16954
+ const services = [];
16955
+ const portAllocator = new LocalPortAllocator();
16956
+ let colorIndex = 0;
16957
+ for (const network of config.project.networks || []) {
16958
+ for (const func of network.functions || []) {
16959
+ const sourceDir = func.sourceDir?.replace(/^\.\//, "") || `functions/${func.name}`;
16960
+ services.push({
16961
+ name: func.name,
16962
+ type: "function",
16963
+ sourceDir: path22.join(projectRoot, sourceDir),
16964
+ port: portAllocator.nextFunctionPort(),
16965
+ color: COLORS[colorIndex++ % COLORS.length]
16966
+ });
16967
+ }
16968
+ for (const ui of network.uis || []) {
16969
+ const sourceDir = ui.sourceDir?.replace(/^\.\//, "") || `apps/${ui.name}`;
16970
+ services.push({
16971
+ name: ui.name,
16972
+ type: "ui",
16973
+ sourceDir: path22.join(projectRoot, sourceDir),
16974
+ port: portAllocator.nextUiPort(),
16975
+ color: COLORS[colorIndex++ % COLORS.length]
16976
+ });
16977
+ }
16978
+ for (const container of network.containers || []) {
16979
+ const sourceDir = container.sourceDir?.replace(/^\.\//, "") || `containers/${container.name}`;
16980
+ services.push({
16981
+ name: container.name,
16982
+ type: "container",
16983
+ sourceDir: path22.join(projectRoot, sourceDir),
16984
+ port: container.port || portAllocator.nextContainerPort(),
16985
+ color: COLORS[colorIndex++ % COLORS.length]
16986
+ });
16987
+ }
16988
+ }
16989
+ return services;
16990
+ }
16991
+ async function hasPackageJson(sourceDir) {
16992
+ try {
16993
+ await fs19.access(path22.join(sourceDir, "package.json"));
16994
+ return true;
16995
+ } catch {
16996
+ return false;
16997
+ }
16998
+ }
16999
+ async function hasNodeModules(sourceDir) {
17000
+ try {
17001
+ await fs19.access(path22.join(sourceDir, "node_modules"));
17002
+ return true;
17003
+ } catch {
17004
+ return false;
17005
+ }
17006
+ }
17007
+ async function hasDevScript(sourceDir) {
17008
+ try {
17009
+ const pkgPath = path22.join(sourceDir, "package.json");
17010
+ const content = await fs19.readFile(pkgPath, "utf-8");
17011
+ const pkg2 = JSON.parse(content);
17012
+ return Boolean(pkg2.scripts?.dev);
17013
+ } catch {
17014
+ return false;
17015
+ }
17016
+ }
17017
+ function streamWithPrefix(stream, prefix, color) {
17018
+ stream.on("data", (data) => {
17019
+ const lines = data.toString().split("\n").filter(Boolean);
17020
+ for (const line of lines) {
17021
+ console.log(`${color(`[${prefix}]`)} ${line}`);
17022
+ }
17023
+ });
17024
+ }
17025
+ function spawnService(service, manager) {
17026
+ const env = {
17027
+ ...process.env,
17028
+ PORT: String(service.port),
17029
+ NODE_ENV: "development",
17030
+ // Firebase emulator connection vars
17031
+ FIRESTORE_EMULATOR_HOST: "localhost:8080",
17032
+ FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099",
17033
+ PUBSUB_EMULATOR_HOST: "localhost:8085"
17034
+ };
17035
+ const args = service.type === "ui" ? ["run", "dev", "--", "--port", String(service.port)] : ["run", "dev"];
17036
+ const proc = spawn6("npm", args, {
17037
+ cwd: service.sourceDir,
17038
+ env,
17039
+ stdio: ["ignore", "pipe", "pipe"],
17040
+ shell: true
17041
+ });
17042
+ if (proc.stdout) {
17043
+ streamWithPrefix(proc.stdout, service.name, service.color);
17044
+ }
17045
+ if (proc.stderr) {
17046
+ streamWithPrefix(proc.stderr, service.name, service.color);
17047
+ }
17048
+ proc.on("error", (err) => {
17049
+ console.log(service.color(`[${service.name}]`), chalk18.red(`Error: ${err.message}`));
17050
+ });
17051
+ proc.on("exit", (code) => {
17052
+ if (!manager.isShuttingDown) {
17053
+ console.log(
17054
+ service.color(`[${service.name}]`),
17055
+ code === 0 ? chalk18.gray("Exited") : chalk18.red(`Exited with code ${code}`)
17056
+ );
17057
+ }
17058
+ manager.processes.delete(service.name);
17059
+ });
17060
+ return proc;
17061
+ }
17062
+ async function startFirebaseEmulators(manager) {
17063
+ const spinner = ora8("Starting Firebase emulators...").start();
17064
+ try {
17065
+ const proc = spawn6(
17066
+ "firebase",
17067
+ ["emulators:start", "--only", "firestore,auth", "--project", "demo-local"],
17068
+ {
17069
+ stdio: ["ignore", "pipe", "pipe"],
17070
+ shell: true
17071
+ }
17072
+ );
17073
+ const color = chalk18.yellow;
17074
+ if (proc.stdout) {
17075
+ streamWithPrefix(proc.stdout, "firebase", color);
17076
+ }
17077
+ if (proc.stderr) {
17078
+ streamWithPrefix(proc.stderr, "firebase", color);
17079
+ }
17080
+ proc.on("error", () => {
17081
+ spinner.fail("Firebase CLI not found. Skipping emulators.");
17082
+ });
17083
+ await new Promise((resolve7) => setTimeout(resolve7, 3e3));
17084
+ spinner.succeed("Firebase emulators starting");
17085
+ return proc;
17086
+ } catch {
17087
+ spinner.warn("Firebase emulators not available");
17088
+ return null;
17089
+ }
17090
+ }
17091
+ function shutdown(manager) {
17092
+ manager.isShuttingDown = true;
17093
+ console.log(chalk18.gray("\n Shutting down services...\n"));
17094
+ for (const [name, proc] of manager.processes) {
17095
+ try {
17096
+ console.log(chalk18.gray(` Stopping ${name}...`));
17097
+ proc.kill("SIGTERM");
17098
+ } catch {
17099
+ }
17100
+ }
17101
+ setTimeout(() => {
17102
+ for (const [, proc] of manager.processes) {
17103
+ try {
17104
+ proc.kill("SIGKILL");
17105
+ } catch {
17106
+ }
17107
+ }
17108
+ process.exit(0);
17109
+ }, 5e3);
17110
+ }
17111
+ async function startLocalEnvironment(options) {
17112
+ console.log(chalk18.bold("\n StackSolo Local Development\n"));
17113
+ const projectRoot = process.cwd();
17114
+ const configPath = path22.join(projectRoot, ".stacksolo", "stacksolo.config.json");
17115
+ let config;
17116
+ try {
17117
+ const content = await fs19.readFile(configPath, "utf-8");
17118
+ config = JSON.parse(content);
17119
+ } catch {
17120
+ console.log(chalk18.red(` Config not found: .stacksolo/stacksolo.config.json`));
17121
+ console.log(chalk18.gray(` Run 'stacksolo init' first.
17122
+ `));
17123
+ process.exit(1);
17124
+ }
17125
+ const services = collectServices(config, projectRoot);
17126
+ if (services.length === 0) {
17127
+ console.log(chalk18.yellow(" No services found in config.\n"));
17128
+ return;
17129
+ }
17130
+ const validServices = [];
17131
+ const missingDeps = [];
17132
+ const missingDevScript = [];
17133
+ for (const service of services) {
17134
+ if (!await hasPackageJson(service.sourceDir)) {
17135
+ console.log(chalk18.yellow(` Warning: ${service.name} has no package.json at ${service.sourceDir}, skipping`));
17136
+ continue;
17137
+ }
17138
+ if (!await hasDevScript(service.sourceDir)) {
17139
+ missingDevScript.push(service);
17140
+ continue;
17141
+ }
17142
+ validServices.push(service);
17143
+ if (!await hasNodeModules(service.sourceDir)) {
17144
+ missingDeps.push(service);
17145
+ }
17146
+ }
17147
+ if (missingDevScript.length > 0) {
17148
+ console.log(chalk18.red('\n Error: Some services are missing "dev" script in package.json:\n'));
17149
+ for (const svc of missingDevScript) {
17150
+ console.log(chalk18.red(` \u2717 ${svc.name}`));
17151
+ console.log(chalk18.gray(` Add a "dev" script to ${path22.relative(projectRoot, svc.sourceDir)}/package.json`));
17152
+ }
17153
+ console.log(chalk18.gray("\n See: https://stacksolo.dev/reference/cli/#local-mode---local\n"));
17154
+ }
17155
+ if (validServices.length === 0) {
17156
+ console.log(chalk18.yellow(" No runnable services found.\n"));
17157
+ console.log(chalk18.gray(" Run `stacksolo scaffold` to generate service code.\n"));
17158
+ return;
17159
+ }
17160
+ if (missingDeps.length > 0) {
17161
+ console.log(chalk18.yellow("\n Warning: Some services are missing node_modules:"));
17162
+ for (const svc of missingDeps) {
17163
+ console.log(chalk18.yellow(` \u2022 ${svc.name}: Run \`cd ${path22.relative(projectRoot, svc.sourceDir)} && npm install\``));
17164
+ }
17165
+ console.log(chalk18.gray("\n Or run: stacksolo install\n"));
17166
+ }
17167
+ const manager = {
17168
+ processes: /* @__PURE__ */ new Map(),
17169
+ services: validServices,
17170
+ isShuttingDown: false
17171
+ };
17172
+ if (options.includeEmulators !== false) {
17173
+ const emulatorProc = await startFirebaseEmulators(manager);
17174
+ if (emulatorProc) {
17175
+ manager.processes.set("firebase-emulator", emulatorProc);
17176
+ }
17177
+ }
17178
+ const spinner = ora8("Starting services...").start();
17179
+ for (const service of validServices) {
17180
+ const proc = spawnService(service, manager);
17181
+ if (proc) {
17182
+ manager.processes.set(service.name, proc);
17183
+ }
17184
+ }
17185
+ spinner.succeed(`Started ${validServices.length} service(s)`);
17186
+ console.log(chalk18.bold("\n Services running:\n"));
17187
+ for (const service of validServices) {
17188
+ const url = `http://localhost:${service.port}`;
17189
+ console.log(` ${service.color("\u25CF")} ${service.name.padEnd(20)} ${chalk18.cyan(url)}`);
17190
+ }
17191
+ if (options.includeEmulators !== false) {
17192
+ console.log(chalk18.bold("\n Emulators:\n"));
17193
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase UI ${chalk18.cyan("http://localhost:4000")}`);
17194
+ console.log(` ${chalk18.yellow("\u25CF")} Firestore ${chalk18.gray("localhost:8080")}`);
17195
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase Auth ${chalk18.gray("localhost:9099")}`);
17196
+ }
17197
+ console.log(chalk18.bold("\n Commands:\n"));
17198
+ console.log(chalk18.gray(" Press Ctrl+C to stop all services"));
17199
+ console.log("");
17200
+ process.on("SIGINT", () => shutdown(manager));
17201
+ process.on("SIGTERM", () => shutdown(manager));
17202
+ await new Promise(() => {
17203
+ });
17204
+ }
17205
+
17206
+ // src/commands/dev/dev.ts
16924
17207
  function getKernelConfig(config) {
16925
17208
  if (config.project.kernel) {
16926
17209
  return {
@@ -16948,8 +17231,14 @@ var webAdminProcess = null;
16948
17231
  var isShuttingDown = false;
16949
17232
  var K8S_OUTPUT_DIR = ".stacksolo/k8s";
16950
17233
  var CONFIG_FILE = ".stacksolo/stacksolo.config.json";
16951
- var devCommand = new Command17("dev").description("Start local Kubernetes development environment").option("--stop", "Stop and tear down the environment").option("--status", "Show status of running pods with health").option("--health", "Check health of all services").option("--ports", "Show port-forward status").option("--restart [service]", "Restart port-forwards or specific service pod").option("--service-names", "List service names for use with other commands").option("--routes", "Show gateway routes and services").option("--describe [resource]", "Describe K8s resources (pods, services, all)").option("--logs [service]", "Tail logs (all pods or specific service)").option("--rebuild", "Force regenerate manifests before starting").option("--no-emulators", "Skip Firebase/Pub/Sub emulators").action(async (options) => {
17234
+ var devCommand = new Command17("dev").description("Start local development environment").option("--local", "Run services locally without Docker/K8s").option("--stop", "Stop and tear down the environment").option("--status", "Show status of running pods with health").option("--health", "Check health of all services").option("--ports", "Show port-forward status").option("--restart [service]", "Restart port-forwards or specific service pod").option("--service-names", "List service names for use with other commands").option("--routes", "Show gateway routes and services").option("--describe [resource]", "Describe K8s resources (pods, services, all)").option("--logs [service]", "Tail logs (all pods or specific service)").option("--rebuild", "Force regenerate manifests before starting").option("--no-emulators", "Skip Firebase/Pub/Sub emulators").action(async (options) => {
16952
17235
  try {
17236
+ if (options.local) {
17237
+ await startLocalEnvironment({
17238
+ includeEmulators: options.emulators !== false
17239
+ });
17240
+ return;
17241
+ }
16953
17242
  if (options.stop) {
16954
17243
  await stopEnvironment();
16955
17244
  return;
@@ -16994,38 +17283,38 @@ var devCommand = new Command17("dev").description("Start local Kubernetes develo
16994
17283
  includeEmulators: options.emulators !== false
16995
17284
  });
16996
17285
  } catch (error) {
16997
- console.error(chalk18.red(`
17286
+ console.error(chalk19.red(`
16998
17287
  Error: ${error instanceof Error ? error.message : error}
16999
17288
  `));
17000
17289
  process.exit(1);
17001
17290
  }
17002
17291
  });
17003
17292
  async function checkPrerequisites() {
17004
- const spinner = ora8("Checking prerequisites...").start();
17293
+ const spinner = ora9("Checking prerequisites...").start();
17005
17294
  try {
17006
17295
  execSync2("kubectl version --client --short 2>/dev/null || kubectl version --client", {
17007
17296
  stdio: "pipe"
17008
17297
  });
17009
17298
  } catch {
17010
17299
  spinner.fail("kubectl not found");
17011
- console.log(chalk18.gray("\n Install OrbStack: brew install orbstack"));
17012
- console.log(chalk18.gray(" Or install kubectl: brew install kubectl\n"));
17300
+ console.log(chalk19.gray("\n Install OrbStack: brew install orbstack"));
17301
+ console.log(chalk19.gray(" Or install kubectl: brew install kubectl\n"));
17013
17302
  throw new Error("kubectl is required but not found");
17014
17303
  }
17015
17304
  try {
17016
17305
  execSync2("kubectl cluster-info 2>/dev/null", { stdio: "pipe" });
17017
17306
  } catch {
17018
17307
  spinner.fail("Kubernetes cluster not available");
17019
- console.log(chalk18.gray("\n If using OrbStack, enable Kubernetes in preferences"));
17020
- console.log(chalk18.gray(" Settings \u2192 Kubernetes \u2192 Enable Kubernetes\n"));
17308
+ console.log(chalk19.gray("\n If using OrbStack, enable Kubernetes in preferences"));
17309
+ console.log(chalk19.gray(" Settings \u2192 Kubernetes \u2192 Enable Kubernetes\n"));
17021
17310
  throw new Error("Kubernetes cluster not available");
17022
17311
  }
17023
17312
  spinner.succeed("Prerequisites met");
17024
17313
  }
17025
17314
  async function loadConfig2() {
17026
- const configPath = path22.resolve(process.cwd(), CONFIG_FILE);
17315
+ const configPath = path23.resolve(process.cwd(), CONFIG_FILE);
17027
17316
  try {
17028
- const content = await fs19.readFile(configPath, "utf-8");
17317
+ const content = await fs20.readFile(configPath, "utf-8");
17029
17318
  return JSON.parse(content);
17030
17319
  } catch (error) {
17031
17320
  if (error.code === "ENOENT") {
@@ -17043,9 +17332,9 @@ async function validateSourceDirs(config) {
17043
17332
  const kernelService = getPluginService(validateKernelConfig.serviceName);
17044
17333
  const pluginSourcePath = kernelService ? getServiceSourcePath(kernelService) : null;
17045
17334
  if (!pluginSourcePath) {
17046
- const kernelDir = path22.join(projectRoot, "containers", validateKernelConfig.name);
17335
+ const kernelDir = path23.join(projectRoot, "containers", validateKernelConfig.name);
17047
17336
  try {
17048
- await fs19.access(kernelDir);
17337
+ await fs20.access(kernelDir);
17049
17338
  } catch {
17050
17339
  warnings.push(`Kernel directory not found: containers/${validateKernelConfig.name}/`);
17051
17340
  }
@@ -17053,17 +17342,17 @@ async function validateSourceDirs(config) {
17053
17342
  }
17054
17343
  for (const network of config.project.networks || []) {
17055
17344
  for (const func of network.functions || []) {
17056
- const funcDir = path22.join(projectRoot, "functions", func.name);
17345
+ const funcDir = path23.join(projectRoot, "functions", func.name);
17057
17346
  try {
17058
- await fs19.access(funcDir);
17347
+ await fs20.access(funcDir);
17059
17348
  } catch {
17060
17349
  warnings.push(`Function directory not found: functions/${func.name}/`);
17061
17350
  }
17062
17351
  }
17063
17352
  for (const ui of network.uis || []) {
17064
- const uiDir = path22.join(projectRoot, "ui", ui.name);
17353
+ const uiDir = path23.join(projectRoot, "ui", ui.name);
17065
17354
  try {
17066
- await fs19.access(uiDir);
17355
+ await fs20.access(uiDir);
17067
17356
  } catch {
17068
17357
  warnings.push(`UI directory not found: ui/${ui.name}/`);
17069
17358
  }
@@ -17077,7 +17366,7 @@ async function startWebAdmin(config) {
17077
17366
  return null;
17078
17367
  }
17079
17368
  const port = webAdmin.port || 3e3;
17080
- const spinner = ora8(`Starting web admin on port ${port}...`).start();
17369
+ const spinner = ora9(`Starting web admin on port ${port}...`).start();
17081
17370
  try {
17082
17371
  const webAdminService = getPluginService("web-admin");
17083
17372
  let appDir = null;
@@ -17088,9 +17377,9 @@ async function startWebAdmin(config) {
17088
17377
  }
17089
17378
  }
17090
17379
  if (!appDir) {
17091
- const nodeModulesPath = path22.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
17380
+ const nodeModulesPath = path23.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
17092
17381
  try {
17093
- await fs19.access(nodeModulesPath);
17382
+ await fs20.access(nodeModulesPath);
17094
17383
  appDir = nodeModulesPath;
17095
17384
  } catch {
17096
17385
  }
@@ -17099,16 +17388,16 @@ async function startWebAdmin(config) {
17099
17388
  spinner.warn("Web admin not found - install @stacksolo/plugin-web-admin or add to plugins");
17100
17389
  return null;
17101
17390
  }
17102
- const buildDir = path22.join(appDir, "build");
17391
+ const buildDir = path23.join(appDir, "build");
17103
17392
  let useDevMode = false;
17104
17393
  try {
17105
- await fs19.access(buildDir);
17394
+ await fs20.access(buildDir);
17106
17395
  } catch {
17107
17396
  useDevMode = true;
17108
17397
  }
17109
17398
  const projectPath = process.cwd();
17110
17399
  if (useDevMode) {
17111
- webAdminProcess = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
17400
+ webAdminProcess = spawn7("npm", ["run", "dev", "--", "--port", String(port)], {
17112
17401
  cwd: appDir,
17113
17402
  env: {
17114
17403
  ...process.env,
@@ -17119,7 +17408,7 @@ async function startWebAdmin(config) {
17119
17408
  detached: false
17120
17409
  });
17121
17410
  } else {
17122
- webAdminProcess = spawn6("node", ["build"], {
17411
+ webAdminProcess = spawn7("node", ["build"], {
17123
17412
  cwd: appDir,
17124
17413
  env: {
17125
17414
  ...process.env,
@@ -17154,20 +17443,20 @@ async function buildKernelImage(config) {
17154
17443
  const sourcePath = getServiceSourcePath(kernelService);
17155
17444
  if (sourcePath) {
17156
17445
  kernelDir = sourcePath;
17157
- console.log(chalk18.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17446
+ console.log(chalk19.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17158
17447
  } else {
17159
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17448
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17160
17449
  }
17161
17450
  } else {
17162
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17451
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17163
17452
  }
17164
17453
  try {
17165
- await fs19.access(kernelDir);
17454
+ await fs20.access(kernelDir);
17166
17455
  } catch {
17167
- console.log(chalk18.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17456
+ console.log(chalk19.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17168
17457
  return false;
17169
17458
  }
17170
- const spinner = ora8(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17459
+ const spinner = ora9(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17171
17460
  try {
17172
17461
  execSync2("npm install", { cwd: kernelDir, stdio: "pipe" });
17173
17462
  execSync2("npm run build", { cwd: kernelDir, stdio: "pipe" });
@@ -17176,32 +17465,32 @@ async function buildKernelImage(config) {
17176
17465
  return true;
17177
17466
  } catch (error) {
17178
17467
  spinner.fail(`Failed to build ${kernelType} kernel image`);
17179
- console.log(chalk18.gray(` Error: ${error instanceof Error ? error.message : error}`));
17468
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
17180
17469
  return false;
17181
17470
  }
17182
17471
  }
17183
17472
  async function startEnvironment(options) {
17184
- console.log(chalk18.bold("\n StackSolo Dev Environment\n"));
17473
+ console.log(chalk19.bold("\n StackSolo Dev Environment\n"));
17185
17474
  await checkPrerequisites();
17186
- const spinner = ora8("Loading configuration...").start();
17475
+ const spinner = ora9("Loading configuration...").start();
17187
17476
  const config = await loadConfig2();
17188
17477
  const projectName = config.project.name;
17189
17478
  const namespace = sanitizeNamespaceName(projectName);
17190
17479
  spinner.succeed(`Project: ${projectName}`);
17191
- const pluginSpinner = ora8("Loading plugins...").start();
17480
+ const pluginSpinner = ora9("Loading plugins...").start();
17192
17481
  await loadPlugins(config.project.plugins);
17193
17482
  pluginSpinner.succeed("Plugins loaded");
17194
17483
  const warnings = await validateSourceDirs(config);
17195
17484
  if (warnings.length > 0) {
17196
- console.log(chalk18.yellow("\n Warnings:"));
17485
+ console.log(chalk19.yellow("\n Warnings:"));
17197
17486
  for (const warning of warnings) {
17198
- console.log(chalk18.yellow(` \u2022 ${warning}`));
17487
+ console.log(chalk19.yellow(` \u2022 ${warning}`));
17199
17488
  }
17200
17489
  console.log("");
17201
17490
  }
17202
17491
  await buildKernelImage(config);
17203
- const genSpinner = ora8("Generating Kubernetes manifests...").start();
17204
- const outputDir = path22.resolve(process.cwd(), K8S_OUTPUT_DIR);
17492
+ const genSpinner = ora9("Generating Kubernetes manifests...").start();
17493
+ const outputDir = path23.resolve(process.cwd(), K8S_OUTPUT_DIR);
17205
17494
  const result = generateK8sManifests({
17206
17495
  config,
17207
17496
  projectRoot: process.cwd(),
@@ -17211,10 +17500,10 @@ async function startEnvironment(options) {
17211
17500
  genSpinner.succeed(`Generated ${result.manifests.length} manifests to ${K8S_OUTPUT_DIR}/`);
17212
17501
  if (result.warnings.length > 0) {
17213
17502
  for (const warning of result.warnings) {
17214
- console.log(chalk18.yellow(` \u26A0 ${warning}`));
17503
+ console.log(chalk19.yellow(` \u26A0 ${warning}`));
17215
17504
  }
17216
17505
  }
17217
- const applySpinner = ora8("Applying Kubernetes manifests...").start();
17506
+ const applySpinner = ora9("Applying Kubernetes manifests...").start();
17218
17507
  try {
17219
17508
  execSync2(`kubectl apply -f ${outputDir}/namespace.yaml`, { stdio: "pipe" });
17220
17509
  execSync2(`kubectl apply -f ${outputDir}`, { stdio: "pipe" });
@@ -17223,7 +17512,7 @@ async function startEnvironment(options) {
17223
17512
  applySpinner.fail("Failed to apply manifests");
17224
17513
  throw error;
17225
17514
  }
17226
- const readySpinner = ora8("Waiting for pods to be ready...").start();
17515
+ const readySpinner = ora9("Waiting for pods to be ready...").start();
17227
17516
  try {
17228
17517
  execSync2(
17229
17518
  `kubectl wait --for=condition=ready pod --all -n ${namespace} --timeout=120s`,
@@ -17233,7 +17522,7 @@ async function startEnvironment(options) {
17233
17522
  } catch {
17234
17523
  readySpinner.warn("Some pods may not be ready yet");
17235
17524
  }
17236
- const portForwardSpinner = ora8("Setting up port forwarding...").start();
17525
+ const portForwardSpinner = ora9("Setting up port forwarding...").start();
17237
17526
  const portMappings = await setupPortForwarding(namespace, config);
17238
17527
  portForwardSpinner.succeed("Port forwarding active");
17239
17528
  const webAdminPort = await startWebAdmin(config);
@@ -17246,7 +17535,7 @@ async function startEnvironment(options) {
17246
17535
  protocol: "http"
17247
17536
  });
17248
17537
  }
17249
- console.log(chalk18.bold("\n Services running:\n"));
17538
+ console.log(chalk19.bold("\n Services running:\n"));
17250
17539
  try {
17251
17540
  const podStatus = execSync2(
17252
17541
  `kubectl get pods -n ${namespace} -o wide --no-headers`,
@@ -17256,25 +17545,25 @@ async function startEnvironment(options) {
17256
17545
  const parts = line.split(/\s+/);
17257
17546
  const name = parts[0];
17258
17547
  const status = parts[2];
17259
- const statusColor = status === "Running" ? chalk18.green : chalk18.yellow;
17548
+ const statusColor = status === "Running" ? chalk19.green : chalk19.yellow;
17260
17549
  console.log(` ${statusColor("\u25CF")} ${name.padEnd(30)} ${statusColor(status)}`);
17261
17550
  }
17262
17551
  } catch {
17263
- console.log(chalk18.gray(" Unable to get pod status"));
17552
+ console.log(chalk19.gray(" Unable to get pod status"));
17264
17553
  }
17265
- console.log(chalk18.bold("\n Access:\n"));
17554
+ console.log(chalk19.bold("\n Access:\n"));
17266
17555
  for (const mapping of portMappings) {
17267
17556
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17268
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
17557
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17269
17558
  }
17270
- console.log(chalk18.bold("\n Commands:\n"));
17271
- console.log(chalk18.gray(" stacksolo dev --logs Tail all logs"));
17272
- console.log(chalk18.gray(" stacksolo dev --status Show pod status"));
17273
- console.log(chalk18.gray(" stacksolo dev --stop Stop environment"));
17559
+ console.log(chalk19.bold("\n Commands:\n"));
17560
+ console.log(chalk19.gray(" stacksolo dev --logs Tail all logs"));
17561
+ console.log(chalk19.gray(" stacksolo dev --status Show pod status"));
17562
+ console.log(chalk19.gray(" stacksolo dev --stop Stop environment"));
17274
17563
  console.log("");
17275
17564
  const cleanup = async () => {
17276
17565
  isShuttingDown = true;
17277
- console.log(chalk18.gray("\n Shutting down...\n"));
17566
+ console.log(chalk19.gray("\n Shutting down...\n"));
17278
17567
  if (webAdminProcess) {
17279
17568
  try {
17280
17569
  webAdminProcess.kill("SIGTERM");
@@ -17289,20 +17578,20 @@ async function startEnvironment(options) {
17289
17578
  }
17290
17579
  try {
17291
17580
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17292
- console.log(chalk18.green(" Environment stopped\n"));
17581
+ console.log(chalk19.green(" Environment stopped\n"));
17293
17582
  } catch {
17294
17583
  }
17295
17584
  process.exit(0);
17296
17585
  };
17297
17586
  process.on("SIGINT", cleanup);
17298
17587
  process.on("SIGTERM", cleanup);
17299
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
17588
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17300
17589
  await new Promise(() => {
17301
17590
  });
17302
17591
  }
17303
17592
  function startPortForwardWithRestart(namespace, service, localPort, targetPort, _name) {
17304
17593
  const startForward = () => {
17305
- const proc = spawn6(
17594
+ const proc = spawn7(
17306
17595
  "kubectl",
17307
17596
  ["port-forward", "-n", namespace, `svc/${service}`, `${localPort}:${targetPort}`],
17308
17597
  { stdio: "pipe", detached: false }
@@ -17420,18 +17709,18 @@ async function setupPortForwarding(namespace, config) {
17420
17709
  return portMappings;
17421
17710
  }
17422
17711
  async function stopEnvironment() {
17423
- console.log(chalk18.bold("\n Stopping StackSolo Dev Environment\n"));
17712
+ console.log(chalk19.bold("\n Stopping StackSolo Dev Environment\n"));
17424
17713
  const config = await loadConfig2();
17425
17714
  const namespace = sanitizeNamespaceName(config.project.name);
17426
17715
  const projectName = config.project.name;
17427
- const nsSpinner = ora8(`Deleting namespace ${namespace}...`).start();
17716
+ const nsSpinner = ora9(`Deleting namespace ${namespace}...`).start();
17428
17717
  try {
17429
17718
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17430
17719
  nsSpinner.succeed("Namespace deleted");
17431
17720
  } catch {
17432
17721
  nsSpinner.warn("Namespace may not exist or already deleted");
17433
17722
  }
17434
- const imgSpinner = ora8("Cleaning up Docker images...").start();
17723
+ const imgSpinner = ora9("Cleaning up Docker images...").start();
17435
17724
  try {
17436
17725
  const images = execSync2(
17437
17726
  `docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "^(${namespace}-|${projectName}-)" || true`,
@@ -17455,149 +17744,149 @@ async function stopEnvironment() {
17455
17744
  console.log("");
17456
17745
  }
17457
17746
  async function showStatus() {
17458
- console.log(chalk18.bold("\n StackSolo Dev Status\n"));
17747
+ console.log(chalk19.bold("\n StackSolo Dev Status\n"));
17459
17748
  const config = await loadConfig2();
17460
17749
  const namespace = sanitizeNamespaceName(config.project.name);
17461
17750
  try {
17462
- console.log(chalk18.bold(" Pods:\n"));
17751
+ console.log(chalk19.bold(" Pods:\n"));
17463
17752
  const pods = execSync2(`kubectl get pods -n ${namespace} -o wide`, { encoding: "utf-8" });
17464
17753
  console.log(pods.split("\n").map((l) => " " + l).join("\n"));
17465
- console.log(chalk18.bold("\n Services:\n"));
17754
+ console.log(chalk19.bold("\n Services:\n"));
17466
17755
  const services = execSync2(`kubectl get services -n ${namespace}`, { encoding: "utf-8" });
17467
17756
  console.log(services.split("\n").map((l) => " " + l).join("\n"));
17468
- console.log(chalk18.bold("\n Ingress:\n"));
17757
+ console.log(chalk19.bold("\n Ingress:\n"));
17469
17758
  const ingress = execSync2(`kubectl get ingress -n ${namespace}`, { encoding: "utf-8" });
17470
17759
  console.log(ingress.split("\n").map((l) => " " + l).join("\n"));
17471
17760
  } catch {
17472
- console.log(chalk18.yellow(` No resources found in namespace ${namespace}`));
17473
- console.log(chalk18.gray(' Run "stacksolo dev" to start the environment\n'));
17761
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17762
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17474
17763
  }
17475
17764
  console.log("");
17476
17765
  }
17477
17766
  async function showRoutes() {
17478
- console.log(chalk18.bold("\n StackSolo Gateway Routes\n"));
17767
+ console.log(chalk19.bold("\n StackSolo Gateway Routes\n"));
17479
17768
  const config = await loadConfig2();
17480
17769
  const kernelConfig = getKernelConfig(config);
17481
17770
  if (kernelConfig) {
17482
17771
  const label = kernelConfig.type === "nats" ? "Kernel (NATS)" : "Kernel (GCP)";
17483
17772
  const detail = kernelConfig.type === "nats" ? `Source: containers/${kernelConfig.name}/` : "Type: GCP-native (Cloud Run + Pub/Sub)";
17484
- console.log(chalk18.bold(` ${label}:
17773
+ console.log(chalk19.bold(` ${label}:
17485
17774
  `));
17486
- console.log(` ${chalk18.cyan("\u25CF")} ${kernelConfig.name}`);
17487
- console.log(chalk18.gray(` ${detail}`));
17775
+ console.log(` ${chalk19.cyan("\u25CF")} ${kernelConfig.name}`);
17776
+ console.log(chalk19.gray(` ${detail}`));
17488
17777
  console.log("");
17489
17778
  }
17490
17779
  for (const network of config.project.networks || []) {
17491
- console.log(chalk18.bold(` Network: ${network.name}
17780
+ console.log(chalk19.bold(` Network: ${network.name}
17492
17781
  `));
17493
17782
  if (network.functions && network.functions.length > 0) {
17494
- console.log(chalk18.bold(" Functions:"));
17783
+ console.log(chalk19.bold(" Functions:"));
17495
17784
  for (const func of network.functions) {
17496
- console.log(` ${chalk18.green("\u03BB")} ${func.name}`);
17497
- console.log(chalk18.gray(` Source: functions/${func.name}/`));
17785
+ console.log(` ${chalk19.green("\u03BB")} ${func.name}`);
17786
+ console.log(chalk19.gray(` Source: functions/${func.name}/`));
17498
17787
  }
17499
17788
  console.log("");
17500
17789
  }
17501
17790
  if (network.containers && network.containers.length > 0) {
17502
- console.log(chalk18.bold(" Containers:"));
17791
+ console.log(chalk19.bold(" Containers:"));
17503
17792
  for (const container of network.containers) {
17504
- console.log(` ${chalk18.blue("\u25FC")} ${container.name}`);
17505
- console.log(chalk18.gray(` Source: containers/${container.name}/`));
17793
+ console.log(` ${chalk19.blue("\u25FC")} ${container.name}`);
17794
+ console.log(chalk19.gray(` Source: containers/${container.name}/`));
17506
17795
  }
17507
17796
  console.log("");
17508
17797
  }
17509
17798
  if (network.uis && network.uis.length > 0) {
17510
- console.log(chalk18.bold(" UIs:"));
17799
+ console.log(chalk19.bold(" UIs:"));
17511
17800
  for (const ui of network.uis) {
17512
- console.log(` ${chalk18.magenta("\u25C6")} ${ui.name}`);
17513
- console.log(chalk18.gray(` Source: ui/${ui.name}/`));
17801
+ console.log(` ${chalk19.magenta("\u25C6")} ${ui.name}`);
17802
+ console.log(chalk19.gray(` Source: ui/${ui.name}/`));
17514
17803
  }
17515
17804
  console.log("");
17516
17805
  }
17517
17806
  if (network.loadBalancer?.routes && network.loadBalancer.routes.length > 0) {
17518
- console.log(chalk18.bold(" Gateway Routes:"));
17519
- console.log(chalk18.gray(" Path \u2192 Backend"));
17520
- console.log(chalk18.gray(" " + "\u2500".repeat(50)));
17807
+ console.log(chalk19.bold(" Gateway Routes:"));
17808
+ console.log(chalk19.gray(" Path \u2192 Backend"));
17809
+ console.log(chalk19.gray(" " + "\u2500".repeat(50)));
17521
17810
  for (const route of network.loadBalancer.routes) {
17522
17811
  const pathPadded = route.path.padEnd(24);
17523
- console.log(` ${chalk18.yellow(pathPadded)} \u2192 ${route.backend}`);
17812
+ console.log(` ${chalk19.yellow(pathPadded)} \u2192 ${route.backend}`);
17524
17813
  }
17525
17814
  console.log("");
17526
17815
  }
17527
17816
  }
17528
- console.log(chalk18.bold(" Emulators:\n"));
17529
- console.log(` ${chalk18.yellow("Firebase UI")} http://localhost:4000`);
17530
- console.log(` ${chalk18.yellow("Firestore")} localhost:8080`);
17531
- console.log(` ${chalk18.yellow("Firebase Auth")} localhost:9099`);
17532
- console.log(` ${chalk18.yellow("Pub/Sub")} localhost:8085`);
17817
+ console.log(chalk19.bold(" Emulators:\n"));
17818
+ console.log(` ${chalk19.yellow("Firebase UI")} http://localhost:4000`);
17819
+ console.log(` ${chalk19.yellow("Firestore")} localhost:8080`);
17820
+ console.log(` ${chalk19.yellow("Firebase Auth")} localhost:9099`);
17821
+ console.log(` ${chalk19.yellow("Pub/Sub")} localhost:8085`);
17533
17822
  console.log("");
17534
- console.log(chalk18.bold(" Local Access:\n"));
17535
- console.log(` ${chalk18.cyan("Gateway:")} http://localhost:8000`);
17823
+ console.log(chalk19.bold(" Local Access:\n"));
17824
+ console.log(` ${chalk19.cyan("Gateway:")} http://localhost:8000`);
17536
17825
  const routesKernelConfig = getKernelConfig(config);
17537
17826
  if (routesKernelConfig) {
17538
17827
  const label = routesKernelConfig.type === "nats" ? "Kernel HTTP" : "GCP Kernel";
17539
- console.log(` ${chalk18.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17828
+ console.log(` ${chalk19.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17540
17829
  if (routesKernelConfig.natsPort) {
17541
- console.log(` ${chalk18.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17830
+ console.log(` ${chalk19.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17542
17831
  }
17543
17832
  }
17544
17833
  console.log("");
17545
17834
  }
17546
17835
  async function describeResources(resource) {
17547
- console.log(chalk18.bold("\n StackSolo Dev - Resource Details\n"));
17836
+ console.log(chalk19.bold("\n StackSolo Dev - Resource Details\n"));
17548
17837
  const config = await loadConfig2();
17549
17838
  const namespace = sanitizeNamespaceName(config.project.name);
17550
17839
  try {
17551
17840
  const indent = (text) => text.split("\n").map((l) => " " + l).join("\n");
17552
17841
  if (resource === "all" || resource === "pods") {
17553
- console.log(chalk18.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17842
+ console.log(chalk19.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17554
17843
  const pods = execSync2(`kubectl describe pods -n ${namespace}`, { encoding: "utf-8" });
17555
17844
  console.log(indent(pods));
17556
17845
  }
17557
17846
  if (resource === "all" || resource === "services") {
17558
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17847
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17559
17848
  const services = execSync2(`kubectl describe services -n ${namespace}`, { encoding: "utf-8" });
17560
17849
  console.log(indent(services));
17561
17850
  }
17562
17851
  if (resource === "all" || resource === "deployments") {
17563
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17852
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17564
17853
  const deployments = execSync2(`kubectl describe deployments -n ${namespace}`, { encoding: "utf-8" });
17565
17854
  console.log(indent(deployments));
17566
17855
  }
17567
17856
  if (resource === "all" || resource === "ingress") {
17568
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17857
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17569
17858
  try {
17570
17859
  const ingress = execSync2(`kubectl describe ingress -n ${namespace}`, { encoding: "utf-8" });
17571
17860
  console.log(indent(ingress));
17572
17861
  } catch {
17573
- console.log(chalk18.gray(" No ingress resources found"));
17862
+ console.log(chalk19.gray(" No ingress resources found"));
17574
17863
  }
17575
17864
  }
17576
17865
  if (resource === "all" || resource === "configmaps") {
17577
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17866
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17578
17867
  const configmaps = execSync2(`kubectl describe configmaps -n ${namespace}`, { encoding: "utf-8" });
17579
17868
  console.log(indent(configmaps));
17580
17869
  }
17581
17870
  if (!["all", "pods", "services", "deployments", "ingress", "configmaps"].includes(resource)) {
17582
- console.log(chalk18.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17871
+ console.log(chalk19.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17583
17872
  `));
17584
17873
  try {
17585
17874
  const podDesc = execSync2(`kubectl describe pod/${resource} -n ${namespace} 2>/dev/null || kubectl describe deployment/${resource} -n ${namespace} 2>/dev/null || kubectl describe service/${resource} -n ${namespace}`, { encoding: "utf-8" });
17586
17875
  console.log(indent(podDesc));
17587
17876
  } catch {
17588
- console.log(chalk18.yellow(` Resource '${resource}' not found`));
17589
- console.log(chalk18.gray("\n Available options: all, pods, services, deployments, ingress, configmaps"));
17590
- console.log(chalk18.gray(" Or specify a resource name like: --describe api"));
17877
+ console.log(chalk19.yellow(` Resource '${resource}' not found`));
17878
+ console.log(chalk19.gray("\n Available options: all, pods, services, deployments, ingress, configmaps"));
17879
+ console.log(chalk19.gray(" Or specify a resource name like: --describe api"));
17591
17880
  }
17592
17881
  }
17593
17882
  } catch {
17594
- console.log(chalk18.yellow(` No resources found in namespace ${namespace}`));
17595
- console.log(chalk18.gray(' Run "stacksolo dev" to start the environment\n'));
17883
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17884
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17596
17885
  }
17597
17886
  console.log("");
17598
17887
  }
17599
17888
  async function checkHealth() {
17600
- console.log(chalk18.bold("\n StackSolo Dev - Health Check\n"));
17889
+ console.log(chalk19.bold("\n StackSolo Dev - Health Check\n"));
17601
17890
  const config = await loadConfig2();
17602
17891
  const namespace = sanitizeNamespaceName(config.project.name);
17603
17892
  const healthChecks = [];
@@ -17618,7 +17907,7 @@ async function checkHealth() {
17618
17907
  functionPort++;
17619
17908
  }
17620
17909
  }
17621
- console.log(chalk18.bold(" Pod Status:\n"));
17910
+ console.log(chalk19.bold(" Pod Status:\n"));
17622
17911
  try {
17623
17912
  const podOutput = execSync2(
17624
17913
  `kubectl get pods -n ${namespace} -o jsonpath='{range .items[*]}{.metadata.name}|{.status.phase}|{.status.conditions[?(@.type=="Ready")].status}{"\\n"}{end}'`,
@@ -17628,16 +17917,16 @@ async function checkHealth() {
17628
17917
  if (!line) continue;
17629
17918
  const [name, phase, ready] = line.split("|");
17630
17919
  const isHealthy = phase === "Running" && ready === "True";
17631
- const icon = isHealthy ? chalk18.green("\u2713") : chalk18.red("\u2717");
17632
- const status = isHealthy ? chalk18.green("Healthy") : chalk18.yellow(phase);
17920
+ const icon = isHealthy ? chalk19.green("\u2713") : chalk19.red("\u2717");
17921
+ const status = isHealthy ? chalk19.green("Healthy") : chalk19.yellow(phase);
17633
17922
  console.log(` ${icon} ${name.padEnd(40)} ${status}`);
17634
17923
  }
17635
17924
  } catch {
17636
- console.log(chalk18.yellow(" Unable to get pod status"));
17925
+ console.log(chalk19.yellow(" Unable to get pod status"));
17637
17926
  }
17638
- console.log(chalk18.bold("\n HTTP Endpoints:\n"));
17927
+ console.log(chalk19.bold("\n HTTP Endpoints:\n"));
17639
17928
  for (const check of healthChecks) {
17640
- const spinner = ora8({ text: `Checking ${check.name}...`, indent: 4 }).start();
17929
+ const spinner = ora9({ text: `Checking ${check.name}...`, indent: 4 }).start();
17641
17930
  try {
17642
17931
  const response = await Promise.race([
17643
17932
  fetch(`http://localhost:${check.port}${check.path}`),
@@ -17646,27 +17935,27 @@ async function checkHealth() {
17646
17935
  )
17647
17936
  ]);
17648
17937
  if (response.ok) {
17649
- spinner.succeed(`${check.name.padEnd(25)} ${chalk18.green("OK")} (port ${check.port})`);
17938
+ spinner.succeed(`${check.name.padEnd(25)} ${chalk19.green("OK")} (port ${check.port})`);
17650
17939
  } else {
17651
- spinner.warn(`${check.name.padEnd(25)} ${chalk18.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17940
+ spinner.warn(`${check.name.padEnd(25)} ${chalk19.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17652
17941
  }
17653
17942
  } catch (error) {
17654
17943
  const errMsg = error instanceof Error ? error.message : "Unknown error";
17655
17944
  if (errMsg.includes("ECONNREFUSED")) {
17656
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Connection refused")} (port ${check.port})`);
17945
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Connection refused")} (port ${check.port})`);
17657
17946
  } else if (errMsg.includes("Timeout")) {
17658
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Timeout")} (port ${check.port})`);
17947
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Timeout")} (port ${check.port})`);
17659
17948
  } else {
17660
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red(errMsg)} (port ${check.port})`);
17949
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red(errMsg)} (port ${check.port})`);
17661
17950
  }
17662
17951
  }
17663
17952
  }
17664
- console.log(chalk18.bold("\n Tip:\n"));
17665
- console.log(chalk18.gray(' If ports show "Connection refused", try: stacksolo dev --restart'));
17666
- console.log(chalk18.gray(" This will restart all port-forwards\n"));
17953
+ console.log(chalk19.bold("\n Tip:\n"));
17954
+ console.log(chalk19.gray(' If ports show "Connection refused", try: stacksolo dev --restart'));
17955
+ console.log(chalk19.gray(" This will restart all port-forwards\n"));
17667
17956
  }
17668
17957
  async function showPorts() {
17669
- console.log(chalk18.bold("\n StackSolo Dev - Port Forward Status\n"));
17958
+ console.log(chalk19.bold("\n StackSolo Dev - Port Forward Status\n"));
17670
17959
  const config = await loadConfig2();
17671
17960
  const namespace = sanitizeNamespaceName(config.project.name);
17672
17961
  const expectedPorts = [];
@@ -17702,9 +17991,9 @@ async function showPorts() {
17702
17991
  uiPort++;
17703
17992
  }
17704
17993
  }
17705
- console.log(chalk18.bold(" Expected Port Forwards:\n"));
17706
- console.log(chalk18.gray(" Name Port Service Status"));
17707
- console.log(chalk18.gray(" " + "\u2500".repeat(75)));
17994
+ console.log(chalk19.bold(" Expected Port Forwards:\n"));
17995
+ console.log(chalk19.gray(" Name Port Service Status"));
17996
+ console.log(chalk19.gray(" " + "\u2500".repeat(75)));
17708
17997
  for (const mapping of expectedPorts) {
17709
17998
  let status;
17710
17999
  try {
@@ -17714,22 +18003,22 @@ async function showPorts() {
17714
18003
  (_, reject) => setTimeout(() => reject(new Error("Timeout")), 500)
17715
18004
  )
17716
18005
  ]);
17717
- status = chalk18.green("\u25CF Active");
18006
+ status = chalk19.green("\u25CF Active");
17718
18007
  } catch (error) {
17719
18008
  const errMsg = error instanceof Error ? error.message : "";
17720
18009
  if (errMsg.includes("ECONNREFUSED")) {
17721
- status = chalk18.red("\u25CB Not listening");
18010
+ status = chalk19.red("\u25CB Not listening");
17722
18011
  } else if (errMsg.includes("Timeout")) {
17723
- status = chalk18.yellow("\u25CB No response");
18012
+ status = chalk19.yellow("\u25CB No response");
17724
18013
  } else {
17725
- status = chalk18.blue("\u25CF TCP only");
18014
+ status = chalk19.blue("\u25CF TCP only");
17726
18015
  }
17727
18016
  }
17728
18017
  console.log(
17729
18018
  ` ${mapping.name.padEnd(30)} ${String(mapping.port).padEnd(8)} ${mapping.service.padEnd(22)} ${status}`
17730
18019
  );
17731
18020
  }
17732
- console.log(chalk18.bold("\n Active Port-Forward Processes:\n"));
18021
+ console.log(chalk19.bold("\n Active Port-Forward Processes:\n"));
17733
18022
  try {
17734
18023
  const psOutput = execSync2(`ps aux | grep 'kubectl port-forward' | grep -v grep | grep ${namespace}`, {
17735
18024
  encoding: "utf-8"
@@ -17738,21 +18027,21 @@ async function showPorts() {
17738
18027
  for (const line of psOutput.trim().split("\n")) {
17739
18028
  const match = line.match(/port-forward.*svc\/([^\s]+)\s+(\d+:\d+)/);
17740
18029
  if (match) {
17741
- console.log(chalk18.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
18030
+ console.log(chalk19.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
17742
18031
  }
17743
18032
  }
17744
18033
  } else {
17745
- console.log(chalk18.yellow(" No active port-forward processes found"));
18034
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17746
18035
  }
17747
18036
  } catch {
17748
- console.log(chalk18.yellow(" No active port-forward processes found"));
18037
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17749
18038
  }
17750
- console.log(chalk18.bold("\n Commands:\n"));
17751
- console.log(chalk18.gray(" stacksolo dev --restart Restart all port-forwards"));
17752
- console.log(chalk18.gray(" stacksolo dev --health Check endpoint health\n"));
18039
+ console.log(chalk19.bold("\n Commands:\n"));
18040
+ console.log(chalk19.gray(" stacksolo dev --restart Restart all port-forwards"));
18041
+ console.log(chalk19.gray(" stacksolo dev --health Check endpoint health\n"));
17753
18042
  }
17754
18043
  async function showServiceNames() {
17755
- console.log(chalk18.bold("\n StackSolo Dev - Service Names\n"));
18044
+ console.log(chalk19.bold("\n StackSolo Dev - Service Names\n"));
17756
18045
  const config = await loadConfig2();
17757
18046
  const namespace = sanitizeNamespaceName(config.project.name);
17758
18047
  const services = [];
@@ -17795,15 +18084,15 @@ async function showServiceNames() {
17795
18084
  if (hasGateway) {
17796
18085
  services.push({ name: "gateway", type: "gateway", k8sName: "gateway" });
17797
18086
  }
17798
- console.log(chalk18.gray(" Name Type K8s Service Name"));
17799
- console.log(chalk18.gray(" " + "\u2500".repeat(60)));
18087
+ console.log(chalk19.gray(" Name Type K8s Service Name"));
18088
+ console.log(chalk19.gray(" " + "\u2500".repeat(60)));
17800
18089
  for (const svc of services) {
17801
- const typeColor = svc.type === "kernel" ? chalk18.magenta : svc.type === "gcp-kernel" ? chalk18.magenta : svc.type === "function" ? chalk18.green : svc.type === "container" ? chalk18.blue : svc.type === "ui" ? chalk18.cyan : svc.type === "gateway" ? chalk18.yellow : chalk18.gray;
18090
+ const typeColor = svc.type === "kernel" ? chalk19.magenta : svc.type === "gcp-kernel" ? chalk19.magenta : svc.type === "function" ? chalk19.green : svc.type === "container" ? chalk19.blue : svc.type === "ui" ? chalk19.cyan : svc.type === "gateway" ? chalk19.yellow : chalk19.gray;
17802
18091
  console.log(
17803
18092
  ` ${svc.name.padEnd(25)} ${typeColor(svc.type.padEnd(12))} ${svc.k8sName}`
17804
18093
  );
17805
18094
  }
17806
- console.log(chalk18.bold("\n Running Pods:\n"));
18095
+ console.log(chalk19.bold("\n Running Pods:\n"));
17807
18096
  try {
17808
18097
  const pods = execSync2(`kubectl get pods -n ${namespace} --no-headers -o custom-columns=NAME:.metadata.name`, {
17809
18098
  encoding: "utf-8"
@@ -17811,30 +18100,30 @@ async function showServiceNames() {
17811
18100
  for (const pod of pods.trim().split("\n")) {
17812
18101
  if (pod) {
17813
18102
  const serviceName = pod.replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17814
- console.log(` ${chalk18.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk18.gray(pod)}`);
18103
+ console.log(` ${chalk19.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk19.gray(pod)}`);
17815
18104
  }
17816
18105
  }
17817
18106
  } catch {
17818
- console.log(chalk18.yellow(" No pods found"));
18107
+ console.log(chalk19.yellow(" No pods found"));
17819
18108
  }
17820
- console.log(chalk18.bold("\n Usage:\n"));
17821
- console.log(chalk18.gray(" stacksolo dev --restart <name> Restart a specific service"));
17822
- console.log(chalk18.gray(" stacksolo dev --logs <name> Tail logs for a service"));
17823
- console.log(chalk18.gray(" stacksolo dev --describe <name> Describe a service\n"));
18109
+ console.log(chalk19.bold("\n Usage:\n"));
18110
+ console.log(chalk19.gray(" stacksolo dev --restart <name> Restart a specific service"));
18111
+ console.log(chalk19.gray(" stacksolo dev --logs <name> Tail logs for a service"));
18112
+ console.log(chalk19.gray(" stacksolo dev --describe <name> Describe a service\n"));
17824
18113
  }
17825
18114
  async function restartService(service) {
17826
- console.log(chalk18.bold("\n StackSolo Dev - Restart\n"));
18115
+ console.log(chalk19.bold("\n StackSolo Dev - Restart\n"));
17827
18116
  const config = await loadConfig2();
17828
18117
  const namespace = sanitizeNamespaceName(config.project.name);
17829
18118
  if (service) {
17830
- const spinner = ora8(`Restarting pod: ${service}...`).start();
18119
+ const spinner = ora9(`Restarting pod: ${service}...`).start();
17831
18120
  try {
17832
18121
  execSync2(
17833
18122
  `kubectl delete pod -n ${namespace} -l app.kubernetes.io/name=${service} --grace-period=5`,
17834
18123
  { stdio: "pipe" }
17835
18124
  );
17836
18125
  spinner.succeed(`Pod ${service} restarted`);
17837
- const waitSpinner = ora8(`Waiting for ${service} to be ready...`).start();
18126
+ const waitSpinner = ora9(`Waiting for ${service} to be ready...`).start();
17838
18127
  try {
17839
18128
  execSync2(
17840
18129
  `kubectl wait --for=condition=ready pod -n ${namespace} -l app.kubernetes.io/name=${service} --timeout=60s`,
@@ -17846,19 +18135,19 @@ async function restartService(service) {
17846
18135
  }
17847
18136
  } catch (error) {
17848
18137
  spinner.fail(`Failed to restart ${service}`);
17849
- console.log(chalk18.gray(` Error: ${error instanceof Error ? error.message : error}`));
17850
- console.log(chalk18.gray("\n Available services:"));
18138
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
18139
+ console.log(chalk19.gray("\n Available services:"));
17851
18140
  try {
17852
18141
  const pods = execSync2(`kubectl get pods -n ${namespace} -o name`, { encoding: "utf-8" });
17853
18142
  for (const pod of pods.trim().split("\n")) {
17854
18143
  const podName = pod.replace("pod/", "").replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17855
- console.log(chalk18.gray(` ${podName}`));
18144
+ console.log(chalk19.gray(` ${podName}`));
17856
18145
  }
17857
18146
  } catch {
17858
18147
  }
17859
18148
  }
17860
18149
  } else {
17861
- const killSpinner = ora8("Stopping existing port-forwards...").start();
18150
+ const killSpinner = ora9("Stopping existing port-forwards...").start();
17862
18151
  try {
17863
18152
  execSync2(`pkill -f "kubectl port-forward.*${namespace}"`, { stdio: "pipe" });
17864
18153
  killSpinner.succeed("Port-forwards stopped");
@@ -17867,21 +18156,21 @@ async function restartService(service) {
17867
18156
  }
17868
18157
  await new Promise((resolve7) => setTimeout(resolve7, 500));
17869
18158
  console.log("");
17870
- const spinner = ora8("Restarting port-forwards...").start();
18159
+ const spinner = ora9("Restarting port-forwards...").start();
17871
18160
  portForwardProcesses.length = 0;
17872
18161
  const portMappings = await setupPortForwarding(namespace, config);
17873
18162
  spinner.succeed("Port-forwards restarted");
17874
- console.log(chalk18.bold("\n Active Forwards:\n"));
18163
+ console.log(chalk19.bold("\n Active Forwards:\n"));
17875
18164
  for (const mapping of portMappings) {
17876
18165
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17877
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
18166
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17878
18167
  }
17879
- console.log(chalk18.bold("\n Tip:\n"));
17880
- console.log(chalk18.gray(" Run: stacksolo dev --health to verify endpoints\n"));
17881
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
18168
+ console.log(chalk19.bold("\n Tip:\n"));
18169
+ console.log(chalk19.gray(" Run: stacksolo dev --health to verify endpoints\n"));
18170
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17882
18171
  const cleanup = async () => {
17883
18172
  isShuttingDown = true;
17884
- console.log(chalk18.gray("\n Stopping port-forwards...\n"));
18173
+ console.log(chalk19.gray("\n Stopping port-forwards...\n"));
17885
18174
  for (const proc of portForwardProcesses) {
17886
18175
  try {
17887
18176
  proc.kill("SIGTERM");
@@ -17900,10 +18189,10 @@ async function restartService(service) {
17900
18189
  async function tailLogs(service) {
17901
18190
  const config = await loadConfig2();
17902
18191
  const namespace = sanitizeNamespaceName(config.project.name);
17903
- console.log(chalk18.bold("\n StackSolo Dev Logs\n"));
17904
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
18192
+ console.log(chalk19.bold("\n StackSolo Dev Logs\n"));
18193
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17905
18194
  const args = service ? ["logs", "-f", "-n", namespace, "-l", `app.kubernetes.io/name=${service}`] : ["logs", "-f", "-n", namespace, "--all-containers", "-l", "app.kubernetes.io/managed-by=stacksolo"];
17906
- const child = spawn6("kubectl", args, { stdio: "inherit" });
18195
+ const child = spawn7("kubectl", args, { stdio: "inherit" });
17907
18196
  process.on("SIGINT", () => {
17908
18197
  child.kill("SIGINT");
17909
18198
  process.exit(0);
@@ -17915,10 +18204,10 @@ async function tailLogs(service) {
17915
18204
 
17916
18205
  // src/commands/dev/install.ts
17917
18206
  import { Command as Command18 } from "commander";
17918
- import chalk19 from "chalk";
17919
- import ora9 from "ora";
17920
- import * as path23 from "path";
17921
- import * as fs20 from "fs/promises";
18207
+ import chalk20 from "chalk";
18208
+ import ora10 from "ora";
18209
+ import * as path24 from "path";
18210
+ import * as fs21 from "fs/promises";
17922
18211
  import { exec as exec14 } from "child_process";
17923
18212
  import { promisify as promisify14 } from "util";
17924
18213
  var execAsync14 = promisify14(exec14);
@@ -17926,13 +18215,13 @@ var STACKSOLO_DIR10 = ".stacksolo";
17926
18215
  var CONFIG_FILENAME8 = "stacksolo.config.json";
17927
18216
  var installCommand = new Command18("install").description("Install dependencies for all resources").option("-p, --parallel", "Install dependencies in parallel").action(async (options) => {
17928
18217
  const cwd = process.cwd();
17929
- console.log(chalk19.cyan("\n StackSolo Install\n"));
17930
- const configPath = path23.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
18218
+ console.log(chalk20.cyan("\n StackSolo Install\n"));
18219
+ const configPath = path24.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
17931
18220
  let config;
17932
18221
  try {
17933
18222
  config = parseConfig(configPath);
17934
18223
  } catch {
17935
- console.log(chalk19.red(" No config found. Run `stacksolo init` first.\n"));
18224
+ console.log(chalk20.red(" No config found. Run `stacksolo init` first.\n"));
17936
18225
  return;
17937
18226
  }
17938
18227
  const directories = [];
@@ -17941,7 +18230,7 @@ var installCommand = new Command18("install").description("Install dependencies
17941
18230
  const sourceDir = fn.sourceDir?.replace(/^\.\//, "") || `functions/${fn.name}`;
17942
18231
  directories.push({
17943
18232
  name: fn.name,
17944
- path: path23.join(cwd, sourceDir),
18233
+ path: path24.join(cwd, sourceDir),
17945
18234
  type: "function"
17946
18235
  });
17947
18236
  }
@@ -17949,7 +18238,7 @@ var installCommand = new Command18("install").description("Install dependencies
17949
18238
  const sourceDir = container.sourceDir?.replace(/^\.\//, "") || `containers/${container.name}`;
17950
18239
  directories.push({
17951
18240
  name: container.name,
17952
- path: path23.join(cwd, sourceDir),
18241
+ path: path24.join(cwd, sourceDir),
17953
18242
  type: "container"
17954
18243
  });
17955
18244
  }
@@ -17957,33 +18246,33 @@ var installCommand = new Command18("install").description("Install dependencies
17957
18246
  const sourceDir = ui.sourceDir?.replace(/^\.\//, "") || `apps/${ui.name}`;
17958
18247
  directories.push({
17959
18248
  name: ui.name,
17960
- path: path23.join(cwd, sourceDir),
18249
+ path: path24.join(cwd, sourceDir),
17961
18250
  type: "ui"
17962
18251
  });
17963
18252
  }
17964
18253
  }
17965
18254
  if (directories.length === 0) {
17966
- console.log(chalk19.yellow(" No resources found in config.\n"));
18255
+ console.log(chalk20.yellow(" No resources found in config.\n"));
17967
18256
  return;
17968
18257
  }
17969
- console.log(chalk19.gray(` Found ${directories.length} resource(s) to install:
18258
+ console.log(chalk20.gray(` Found ${directories.length} resource(s) to install:
17970
18259
  `));
17971
18260
  const validDirs = [];
17972
18261
  for (const dir of directories) {
17973
18262
  try {
17974
- await fs20.access(path23.join(dir.path, "package.json"));
18263
+ await fs21.access(path24.join(dir.path, "package.json"));
17975
18264
  validDirs.push(dir);
17976
- console.log(chalk19.gray(` - ${dir.name} (${dir.type})`));
18265
+ console.log(chalk20.gray(` - ${dir.name} (${dir.type})`));
17977
18266
  } catch {
17978
18267
  }
17979
18268
  }
17980
18269
  if (validDirs.length === 0) {
17981
- console.log(chalk19.yellow(" No resources with package.json found.\n"));
18270
+ console.log(chalk20.yellow(" No resources with package.json found.\n"));
17982
18271
  return;
17983
18272
  }
17984
18273
  console.log("");
17985
18274
  const installDir = async (dir) => {
17986
- const spinner = ora9(`Installing ${dir.name}...`).start();
18275
+ const spinner = ora10(`Installing ${dir.name}...`).start();
17987
18276
  try {
17988
18277
  await execAsync14("npm install", { cwd: dir.path, timeout: 12e4 });
17989
18278
  spinner.succeed(`Installed ${dir.name}`);
@@ -18006,34 +18295,34 @@ var installCommand = new Command18("install").description("Install dependencies
18006
18295
  const failed = results.filter((r) => !r.success).length;
18007
18296
  console.log("");
18008
18297
  if (failed === 0) {
18009
- console.log(chalk19.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
18298
+ console.log(chalk20.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
18010
18299
  `));
18011
18300
  } else {
18012
- console.log(chalk19.yellow(` Installed ${succeeded}, failed ${failed}
18301
+ console.log(chalk20.yellow(` Installed ${succeeded}, failed ${failed}
18013
18302
  `));
18014
18303
  }
18015
18304
  });
18016
18305
 
18017
18306
  // src/commands/dev/serve.ts
18018
18307
  import { Command as Command19 } from "commander";
18019
- import chalk20 from "chalk";
18308
+ import chalk21 from "chalk";
18020
18309
  var serveCommand = new Command19("serve").description("Start the StackSolo API server").option("-p, --port <port>", "Port to listen on", "3000").option("--host <host>", "Host to bind to", "localhost").action(async (options) => {
18021
- console.log(chalk20.bold("\n StackSolo Server\n"));
18310
+ console.log(chalk21.bold("\n StackSolo Server\n"));
18022
18311
  const port = parseInt(options.port, 10);
18023
18312
  const host = options.host;
18024
- console.log(chalk20.gray(` Starting API server on ${host}:${port}...`));
18313
+ console.log(chalk21.gray(` Starting API server on ${host}:${port}...`));
18025
18314
  try {
18026
18315
  const { startServer } = await import("@stacksolo/api");
18027
18316
  await startServer({ port, host });
18028
18317
  } catch (error) {
18029
- const { spawn: spawn7 } = await import("child_process");
18030
- const path27 = await import("path");
18318
+ const { spawn: spawn8 } = await import("child_process");
18319
+ const path28 = await import("path");
18031
18320
  const { fileURLToPath } = await import("url");
18032
- const __dirname = path27.dirname(fileURLToPath(import.meta.url));
18033
- const apiPath = path27.resolve(__dirname, "../../api/dist/index.js");
18034
- console.log(chalk20.gray(` Spawning API from ${apiPath}...
18321
+ const __dirname = path28.dirname(fileURLToPath(import.meta.url));
18322
+ const apiPath = path28.resolve(__dirname, "../../api/dist/index.js");
18323
+ console.log(chalk21.gray(` Spawning API from ${apiPath}...
18035
18324
  `));
18036
- const child = spawn7("node", [apiPath], {
18325
+ const child = spawn8("node", [apiPath], {
18037
18326
  env: {
18038
18327
  ...process.env,
18039
18328
  PORT: String(port),
@@ -18042,7 +18331,7 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
18042
18331
  stdio: "inherit"
18043
18332
  });
18044
18333
  child.on("error", (err) => {
18045
- console.log(chalk20.red(` Failed to start server: ${err.message}
18334
+ console.log(chalk21.red(` Failed to start server: ${err.message}
18046
18335
  `));
18047
18336
  process.exit(1);
18048
18337
  });
@@ -18060,21 +18349,21 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
18060
18349
 
18061
18350
  // src/commands/config/config.ts
18062
18351
  import { Command as Command20 } from "commander";
18063
- import chalk21 from "chalk";
18064
- import * as path24 from "path";
18352
+ import chalk22 from "chalk";
18353
+ import * as path25 from "path";
18065
18354
  var STACKSOLO_DIR11 = ".stacksolo";
18066
18355
  var CONFIG_FILENAME9 = "stacksolo.config.json";
18067
18356
  function getConfigPath6() {
18068
- return path24.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
18357
+ return path25.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
18069
18358
  }
18070
18359
  async function loadConfig3(configPath) {
18071
18360
  try {
18072
18361
  return parseConfig(configPath);
18073
18362
  } catch (error) {
18074
- console.log(chalk21.red(`
18363
+ console.log(chalk22.red(`
18075
18364
  Error: Could not read ${STACKSOLO_DIR11}/${CONFIG_FILENAME9}
18076
18365
  `));
18077
- console.log(chalk21.gray(` ${error}`));
18366
+ console.log(chalk22.gray(` ${error}`));
18078
18367
  return null;
18079
18368
  }
18080
18369
  }
@@ -18086,75 +18375,75 @@ var showCommand2 = new Command20("show").description("Display the current config
18086
18375
  console.log(JSON.stringify(config, null, 2));
18087
18376
  return;
18088
18377
  }
18089
- console.log(chalk21.bold("\n StackSolo Configuration\n"));
18090
- console.log(chalk21.cyan(" Project:"));
18091
- console.log(chalk21.white(` Name: ${config.project.name}`));
18092
- console.log(chalk21.white(` Region: ${config.project.region}`));
18093
- console.log(chalk21.white(` GCP Project: ${config.project.gcpProjectId}`));
18378
+ console.log(chalk22.bold("\n StackSolo Configuration\n"));
18379
+ console.log(chalk22.cyan(" Project:"));
18380
+ console.log(chalk22.white(` Name: ${config.project.name}`));
18381
+ console.log(chalk22.white(` Region: ${config.project.region}`));
18382
+ console.log(chalk22.white(` GCP Project: ${config.project.gcpProjectId}`));
18094
18383
  const { buckets, secrets, topics, queues, crons } = config.project;
18095
18384
  if (buckets?.length) {
18096
- console.log(chalk21.cyan("\n Buckets:"));
18385
+ console.log(chalk22.cyan("\n Buckets:"));
18097
18386
  buckets.forEach((b) => {
18098
- console.log(chalk21.white(` - ${b.name}`) + chalk21.gray(` (${b.storageClass || "STANDARD"})`));
18387
+ console.log(chalk22.white(` - ${b.name}`) + chalk22.gray(` (${b.storageClass || "STANDARD"})`));
18099
18388
  });
18100
18389
  }
18101
18390
  if (secrets?.length) {
18102
- console.log(chalk21.cyan("\n Secrets:"));
18391
+ console.log(chalk22.cyan("\n Secrets:"));
18103
18392
  secrets.forEach((s) => {
18104
- console.log(chalk21.white(` - ${s.name}`));
18393
+ console.log(chalk22.white(` - ${s.name}`));
18105
18394
  });
18106
18395
  }
18107
18396
  if (topics?.length) {
18108
- console.log(chalk21.cyan("\n Topics:"));
18397
+ console.log(chalk22.cyan("\n Topics:"));
18109
18398
  topics.forEach((t) => {
18110
- console.log(chalk21.white(` - ${t.name}`));
18399
+ console.log(chalk22.white(` - ${t.name}`));
18111
18400
  });
18112
18401
  }
18113
18402
  if (queues?.length) {
18114
- console.log(chalk21.cyan("\n Queues:"));
18403
+ console.log(chalk22.cyan("\n Queues:"));
18115
18404
  queues.forEach((q) => {
18116
- console.log(chalk21.white(` - ${q.name}`));
18405
+ console.log(chalk22.white(` - ${q.name}`));
18117
18406
  });
18118
18407
  }
18119
18408
  if (crons?.length) {
18120
- console.log(chalk21.cyan("\n Scheduled Jobs:"));
18409
+ console.log(chalk22.cyan("\n Scheduled Jobs:"));
18121
18410
  crons.forEach((c) => {
18122
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.schedule})`));
18411
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.schedule})`));
18123
18412
  });
18124
18413
  }
18125
18414
  const networks = config.project.networks || [];
18126
18415
  if (networks.length) {
18127
18416
  networks.forEach((network) => {
18128
- console.log(chalk21.cyan(`
18417
+ console.log(chalk22.cyan(`
18129
18418
  Network: ${network.name}`));
18130
18419
  if (network.subnets?.length) {
18131
- console.log(chalk21.gray(" Subnets:"));
18420
+ console.log(chalk22.gray(" Subnets:"));
18132
18421
  network.subnets.forEach((s) => {
18133
- console.log(chalk21.white(` - ${s.name}`) + chalk21.gray(` (${s.ipCidrRange})`));
18422
+ console.log(chalk22.white(` - ${s.name}`) + chalk22.gray(` (${s.ipCidrRange})`));
18134
18423
  });
18135
18424
  }
18136
18425
  if (network.containers?.length) {
18137
- console.log(chalk21.gray(" Containers:"));
18426
+ console.log(chalk22.gray(" Containers:"));
18138
18427
  network.containers.forEach((c) => {
18139
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memory || "256Mi"})`));
18428
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memory || "256Mi"})`));
18140
18429
  });
18141
18430
  }
18142
18431
  if (network.functions?.length) {
18143
- console.log(chalk21.gray(" Functions:"));
18432
+ console.log(chalk22.gray(" Functions:"));
18144
18433
  network.functions.forEach((f) => {
18145
- console.log(chalk21.white(` - ${f.name}`) + chalk21.gray(` (${f.runtime || "nodejs20"})`));
18434
+ console.log(chalk22.white(` - ${f.name}`) + chalk22.gray(` (${f.runtime || "nodejs20"})`));
18146
18435
  });
18147
18436
  }
18148
18437
  if (network.databases?.length) {
18149
- console.log(chalk21.gray(" Databases:"));
18438
+ console.log(chalk22.gray(" Databases:"));
18150
18439
  network.databases.forEach((d) => {
18151
- console.log(chalk21.white(` - ${d.name}`) + chalk21.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18440
+ console.log(chalk22.white(` - ${d.name}`) + chalk22.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18152
18441
  });
18153
18442
  }
18154
18443
  if (network.caches?.length) {
18155
- console.log(chalk21.gray(" Caches:"));
18444
+ console.log(chalk22.gray(" Caches:"));
18156
18445
  network.caches.forEach((c) => {
18157
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memorySizeGb || 1}GB)`));
18446
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memorySizeGb || 1}GB)`));
18158
18447
  });
18159
18448
  }
18160
18449
  });
@@ -18172,20 +18461,20 @@ var resourcesCommand = new Command20("resources").description("List all resource
18172
18461
  console.log(JSON.stringify(resolved.resources, null, 2));
18173
18462
  return;
18174
18463
  }
18175
- console.log(chalk21.bold("\n Resources to Create\n"));
18176
- console.log(chalk21.gray(` Order of creation (${order.length} resources):
18464
+ console.log(chalk22.bold("\n Resources to Create\n"));
18465
+ console.log(chalk22.gray(` Order of creation (${order.length} resources):
18177
18466
  `));
18178
18467
  order.forEach((id, index) => {
18179
18468
  const resource = resolved.resources.find((r) => r.id === id);
18180
18469
  if (!resource) return;
18181
- const deps = resource.dependsOn.length ? chalk21.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18470
+ const deps = resource.dependsOn.length ? chalk22.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18182
18471
  console.log(
18183
- chalk21.white(` ${String(index + 1).padStart(2)}. `) + chalk21.cyan(resource.type) + chalk21.white(` "${resource.name}"`) + deps
18472
+ chalk22.white(` ${String(index + 1).padStart(2)}. `) + chalk22.cyan(resource.type) + chalk22.white(` "${resource.name}"`) + deps
18184
18473
  );
18185
18474
  });
18186
18475
  console.log("");
18187
18476
  } catch (error) {
18188
- console.log(chalk21.red(`
18477
+ console.log(chalk22.red(`
18189
18478
  Error resolving config: ${error}
18190
18479
  `));
18191
18480
  }
@@ -18196,24 +18485,24 @@ var validateCommand = new Command20("validate").description("Validate the config
18196
18485
  if (!config) return;
18197
18486
  const result = validateConfig(config);
18198
18487
  if (result.valid) {
18199
- console.log(chalk21.green("\n \u2713 Configuration is valid\n"));
18488
+ console.log(chalk22.green("\n \u2713 Configuration is valid\n"));
18200
18489
  try {
18201
18490
  const resolved = resolveConfig(config);
18202
18491
  const order = topologicalSort(resolved.resources);
18203
- console.log(chalk21.gray(` ${resolved.resources.length} resources defined`));
18204
- console.log(chalk21.gray(` No circular dependencies detected
18492
+ console.log(chalk22.gray(` ${resolved.resources.length} resources defined`));
18493
+ console.log(chalk22.gray(` No circular dependencies detected
18205
18494
  `));
18206
18495
  } catch (error) {
18207
- console.log(chalk21.yellow(`
18496
+ console.log(chalk22.yellow(`
18208
18497
  \u26A0 Warning: ${error}
18209
18498
  `));
18210
18499
  }
18211
18500
  } else {
18212
- console.log(chalk21.red("\n \u2717 Configuration has errors:\n"));
18501
+ console.log(chalk22.red("\n \u2717 Configuration has errors:\n"));
18213
18502
  result.errors.forEach((err) => {
18214
- console.log(chalk21.red(` - ${err.path}: ${err.message}`));
18503
+ console.log(chalk22.red(` - ${err.path}: ${err.message}`));
18215
18504
  if (err.value !== void 0) {
18216
- console.log(chalk21.gray(` value: ${JSON.stringify(err.value)}`));
18505
+ console.log(chalk22.gray(` value: ${JSON.stringify(err.value)}`));
18217
18506
  }
18218
18507
  });
18219
18508
  console.log("");
@@ -18224,7 +18513,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18224
18513
  const configPath = getConfigPath6();
18225
18514
  const config = await loadConfig3(configPath);
18226
18515
  if (!config) return;
18227
- console.log(chalk21.bold("\n Resource References\n"));
18516
+ console.log(chalk22.bold("\n Resource References\n"));
18228
18517
  const allReferences = [];
18229
18518
  const networks = config.project.networks || [];
18230
18519
  networks.forEach((network) => {
@@ -18258,7 +18547,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18258
18547
  });
18259
18548
  });
18260
18549
  if (allReferences.length === 0) {
18261
- console.log(chalk21.gray(" No references found.\n"));
18550
+ console.log(chalk22.gray(" No references found.\n"));
18262
18551
  return;
18263
18552
  }
18264
18553
  const byType = /* @__PURE__ */ new Map();
@@ -18270,11 +18559,11 @@ var referencesCommand = new Command20("references").description("Show all resour
18270
18559
  byType.get(type).push(ref);
18271
18560
  });
18272
18561
  byType.forEach((refs, type) => {
18273
- console.log(chalk21.cyan(` @${type}:`));
18562
+ console.log(chalk22.cyan(` @${type}:`));
18274
18563
  refs.forEach((ref) => {
18275
18564
  const property = ref.parsed?.property ? `.${ref.parsed.property}` : "";
18276
18565
  console.log(
18277
- chalk21.white(` ${ref.reference}`) + chalk21.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18566
+ chalk22.white(` ${ref.reference}`) + chalk22.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18278
18567
  );
18279
18568
  });
18280
18569
  console.log("");
@@ -18287,47 +18576,47 @@ configCommand.action(() => {
18287
18576
 
18288
18577
  // src/commands/config/env.ts
18289
18578
  import { Command as Command21 } from "commander";
18290
- import chalk22 from "chalk";
18291
- import ora10 from "ora";
18292
- import * as fs21 from "fs/promises";
18293
- import * as path25 from "path";
18579
+ import chalk23 from "chalk";
18580
+ import ora11 from "ora";
18581
+ import * as fs22 from "fs/promises";
18582
+ import * as path26 from "path";
18294
18583
  var envCommand = new Command21("env").description("Generate environment configuration files").option("--stdout", "Print to stdout instead of writing files").option("--format <format>", "Output format: dotenv, json, typescript", "dotenv").action(async (options) => {
18295
18584
  const cwd = process.cwd();
18296
- console.log(chalk22.bold("\n StackSolo Env\n"));
18297
- const configPath = path25.join(cwd, ".stacksolo", "config.json");
18585
+ console.log(chalk23.bold("\n StackSolo Env\n"));
18586
+ const configPath = path26.join(cwd, ".stacksolo", "config.json");
18298
18587
  let config;
18299
18588
  try {
18300
- const configData = await fs21.readFile(configPath, "utf-8");
18589
+ const configData = await fs22.readFile(configPath, "utf-8");
18301
18590
  config = JSON.parse(configData);
18302
18591
  } catch {
18303
- console.log(chalk22.red(" Not initialized. Run `stacksolo init` first.\n"));
18592
+ console.log(chalk23.red(" Not initialized. Run `stacksolo init` first.\n"));
18304
18593
  return;
18305
18594
  }
18306
18595
  const apiConnected = await checkApiConnection();
18307
18596
  if (!apiConnected) {
18308
- console.log(chalk22.red(" StackSolo API not running."));
18309
- console.log(chalk22.gray(" Start with: stacksolo serve\n"));
18597
+ console.log(chalk23.red(" StackSolo API not running."));
18598
+ console.log(chalk23.gray(" Start with: stacksolo serve\n"));
18310
18599
  return;
18311
18600
  }
18312
- const spinner = ora10("Checking deployment...").start();
18601
+ const spinner = ora11("Checking deployment...").start();
18313
18602
  const statusResult = await api.deployments.status(config.projectId);
18314
18603
  if (!statusResult.success || !statusResult.data) {
18315
18604
  spinner.fail("Could not get deployment status");
18316
- console.log(chalk22.gray("\n Deploy first with: stacksolo deploy\n"));
18605
+ console.log(chalk23.gray("\n Deploy first with: stacksolo deploy\n"));
18317
18606
  return;
18318
18607
  }
18319
18608
  if (statusResult.data.status !== "succeeded") {
18320
18609
  spinner.fail("No successful deployment");
18321
- console.log(chalk22.gray(`
18610
+ console.log(chalk23.gray(`
18322
18611
  Current status: ${statusResult.data.status}`));
18323
- console.log(chalk22.gray(" Deploy first with: stacksolo deploy\n"));
18612
+ console.log(chalk23.gray(" Deploy first with: stacksolo deploy\n"));
18324
18613
  return;
18325
18614
  }
18326
18615
  spinner.text = "Generating configuration...";
18327
18616
  const configResult = await api.deployments.generateConfig(config.projectId);
18328
18617
  if (!configResult.success || !configResult.data) {
18329
18618
  spinner.fail("Failed to generate config");
18330
- console.log(chalk22.red(` ${configResult.error}
18619
+ console.log(chalk23.red(` ${configResult.error}
18331
18620
  `));
18332
18621
  return;
18333
18622
  }
@@ -18336,38 +18625,38 @@ var envCommand = new Command21("env").description("Generate environment configur
18336
18625
  const tsConfigPath = configResult.data.configPath;
18337
18626
  if (options.stdout) {
18338
18627
  try {
18339
- console.log(chalk22.gray("\n .env.local:"));
18340
- const envContent = await fs21.readFile(envPath, "utf-8");
18628
+ console.log(chalk23.gray("\n .env.local:"));
18629
+ const envContent = await fs22.readFile(envPath, "utf-8");
18341
18630
  console.log(envContent);
18342
- console.log(chalk22.gray("\n stacksolo.config.ts:"));
18343
- const tsContent = await fs21.readFile(tsConfigPath, "utf-8");
18631
+ console.log(chalk23.gray("\n stacksolo.config.ts:"));
18632
+ const tsContent = await fs22.readFile(tsConfigPath, "utf-8");
18344
18633
  console.log(tsContent);
18345
18634
  } catch (error) {
18346
- console.log(chalk22.red(` Error reading files: ${error}
18635
+ console.log(chalk23.red(` Error reading files: ${error}
18347
18636
  `));
18348
18637
  }
18349
18638
  } else {
18350
- console.log(chalk22.green("\n Files generated:"));
18351
- console.log(chalk22.gray(` ${envPath}`));
18352
- console.log(chalk22.gray(` ${tsConfigPath}`));
18639
+ console.log(chalk23.green("\n Files generated:"));
18640
+ console.log(chalk23.gray(` ${envPath}`));
18641
+ console.log(chalk23.gray(` ${tsConfigPath}`));
18353
18642
  console.log("");
18354
- console.log(chalk22.gray(" Add to .gitignore:"));
18355
- console.log(chalk22.gray(" .env.local"));
18643
+ console.log(chalk23.gray(" Add to .gitignore:"));
18644
+ console.log(chalk23.gray(" .env.local"));
18356
18645
  console.log("");
18357
- console.log(chalk22.gray(" Usage in your app:"));
18358
- console.log(chalk22.gray(" import { config } from './stacksolo.config';"));
18359
- console.log(chalk22.gray(" const dbUrl = config.database?.url;\n"));
18646
+ console.log(chalk23.gray(" Usage in your app:"));
18647
+ console.log(chalk23.gray(" import { config } from './stacksolo.config';"));
18648
+ console.log(chalk23.gray(" const dbUrl = config.database?.url;\n"));
18360
18649
  }
18361
18650
  });
18362
18651
 
18363
18652
  // src/commands/config/register.ts
18364
18653
  import { Command as Command22 } from "commander";
18365
- import chalk23 from "chalk";
18366
- import * as path26 from "path";
18654
+ import chalk24 from "chalk";
18655
+ import * as path27 from "path";
18367
18656
  var STACKSOLO_DIR12 = ".stacksolo";
18368
18657
  var CONFIG_FILENAME10 = "stacksolo.config.json";
18369
18658
  function getConfigPath7() {
18370
- return path26.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18659
+ return path27.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18371
18660
  }
18372
18661
  var registerCommand = new Command22("register").description("Register the current project in the global registry").option("-f, --force", "Overwrite existing registration").action(async (options) => {
18373
18662
  const configPath = getConfigPath7();
@@ -18375,11 +18664,11 @@ var registerCommand = new Command22("register").description("Register the curren
18375
18664
  try {
18376
18665
  config = parseConfig(configPath);
18377
18666
  } catch (error) {
18378
- console.log(chalk23.red(`
18667
+ console.log(chalk24.red(`
18379
18668
  Error: Could not read ${STACKSOLO_DIR12}/${CONFIG_FILENAME10}
18380
18669
  `));
18381
- console.log(chalk23.gray(` ${error}`));
18382
- console.log(chalk23.gray(`
18670
+ console.log(chalk24.gray(` ${error}`));
18671
+ console.log(chalk24.gray(`
18383
18672
  Run 'stacksolo init' to create a project first.
18384
18673
  `));
18385
18674
  process.exit(1);
@@ -18389,21 +18678,21 @@ var registerCommand = new Command22("register").description("Register the curren
18389
18678
  if (existingByPath) {
18390
18679
  if (options.force) {
18391
18680
  await registry3.unregisterProject(existingByPath.id);
18392
- console.log(chalk23.yellow(` Updating registration for "${existingByPath.name}"...`));
18681
+ console.log(chalk24.yellow(` Updating registration for "${existingByPath.name}"...`));
18393
18682
  } else {
18394
- console.log(chalk23.yellow(`
18683
+ console.log(chalk24.yellow(`
18395
18684
  Project already registered as "${existingByPath.name}"`));
18396
- console.log(chalk23.gray(` Use --force to update the registration.
18685
+ console.log(chalk24.gray(` Use --force to update the registration.
18397
18686
  `));
18398
18687
  return;
18399
18688
  }
18400
18689
  }
18401
18690
  const existingByName = await registry3.findProjectByName(config.project.name);
18402
18691
  if (existingByName && existingByName.configPath !== configPath) {
18403
- console.log(chalk23.red(`
18692
+ console.log(chalk24.red(`
18404
18693
  Error: Project name "${config.project.name}" is already registered`));
18405
- console.log(chalk23.gray(` Registered path: ${existingByName.configPath}`));
18406
- console.log(chalk23.gray(` Choose a different project name in stacksolo.config.json
18694
+ console.log(chalk24.gray(` Registered path: ${existingByName.configPath}`));
18695
+ console.log(chalk24.gray(` Choose a different project name in stacksolo.config.json
18407
18696
  `));
18408
18697
  process.exit(1);
18409
18698
  }
@@ -18413,42 +18702,42 @@ var registerCommand = new Command22("register").description("Register the curren
18413
18702
  region: config.project.region,
18414
18703
  configPath
18415
18704
  });
18416
- console.log(chalk23.green(`
18705
+ console.log(chalk24.green(`
18417
18706
  \u2713 Project registered: ${project.name}
18418
18707
  `));
18419
- console.log(chalk23.gray(` GCP Project: ${project.gcpProjectId}`));
18420
- console.log(chalk23.gray(` Region: ${project.region}`));
18421
- console.log(chalk23.gray(` Config: ${project.configPath}`));
18708
+ console.log(chalk24.gray(` GCP Project: ${project.gcpProjectId}`));
18709
+ console.log(chalk24.gray(` Region: ${project.region}`));
18710
+ console.log(chalk24.gray(` Config: ${project.configPath}`));
18422
18711
  console.log("");
18423
- console.log(chalk23.cyan(" Next steps:"));
18424
- console.log(chalk23.gray(" stacksolo list # View all registered projects"));
18425
- console.log(chalk23.gray(" stacksolo deploy # Deploy the project"));
18712
+ console.log(chalk24.cyan(" Next steps:"));
18713
+ console.log(chalk24.gray(" stacksolo list # View all registered projects"));
18714
+ console.log(chalk24.gray(" stacksolo deploy # Deploy the project"));
18426
18715
  console.log("");
18427
18716
  });
18428
18717
 
18429
18718
  // src/commands/config/unregister.ts
18430
18719
  import { Command as Command23 } from "commander";
18431
- import chalk24 from "chalk";
18720
+ import chalk25 from "chalk";
18432
18721
  import inquirer5 from "inquirer";
18433
18722
  var unregisterCommand = new Command23("unregister").description("Remove a project from the global registry").argument("<project>", "Project name to unregister").option("-y, --yes", "Skip confirmation prompt").action(async (projectName, options) => {
18434
18723
  const registry3 = getRegistry();
18435
18724
  const project = await registry3.findProjectByName(projectName);
18436
18725
  if (!project) {
18437
- console.log(chalk24.red(`
18726
+ console.log(chalk25.red(`
18438
18727
  Error: Project "${projectName}" not found.
18439
18728
  `));
18440
- console.log(chalk24.gray(" Run `stacksolo list` to see registered projects.\n"));
18729
+ console.log(chalk25.gray(" Run `stacksolo list` to see registered projects.\n"));
18441
18730
  process.exit(1);
18442
18731
  }
18443
18732
  const resources = await registry3.findResourcesByProject(project.id);
18444
18733
  if (!options.yes) {
18445
- console.log(chalk24.yellow(`
18734
+ console.log(chalk25.yellow(`
18446
18735
  Warning: This will remove "${projectName}" from the registry.`));
18447
18736
  if (resources.length > 0) {
18448
- console.log(chalk24.yellow(` This project has ${resources.length} registered resource(s).`));
18737
+ console.log(chalk25.yellow(` This project has ${resources.length} registered resource(s).`));
18449
18738
  }
18450
- console.log(chalk24.gray("\n Note: This does NOT destroy deployed cloud resources."));
18451
- console.log(chalk24.gray(" To destroy resources, run `stacksolo destroy` first.\n"));
18739
+ console.log(chalk25.gray("\n Note: This does NOT destroy deployed cloud resources."));
18740
+ console.log(chalk25.gray(" To destroy resources, run `stacksolo destroy` first.\n"));
18452
18741
  const { confirm } = await inquirer5.prompt([
18453
18742
  {
18454
18743
  type: "confirm",
@@ -18458,17 +18747,17 @@ var unregisterCommand = new Command23("unregister").description("Remove a projec
18458
18747
  }
18459
18748
  ]);
18460
18749
  if (!confirm) {
18461
- console.log(chalk24.gray("\n Cancelled.\n"));
18750
+ console.log(chalk25.gray("\n Cancelled.\n"));
18462
18751
  return;
18463
18752
  }
18464
18753
  }
18465
18754
  await registry3.unregisterProject(project.id);
18466
- console.log(chalk24.green(`
18755
+ console.log(chalk25.green(`
18467
18756
  \u2713 Project "${projectName}" removed from registry.
18468
18757
  `));
18469
18758
  if (project.configPath) {
18470
- console.log(chalk24.gray(` Config file still exists at: ${project.configPath}`));
18471
- console.log(chalk24.gray(" Run `stacksolo register` to re-register this project.\n"));
18759
+ console.log(chalk25.gray(` Config file still exists at: ${project.configPath}`));
18760
+ console.log(chalk25.gray(" Run `stacksolo register` to re-register this project.\n"));
18472
18761
  }
18473
18762
  });
18474
18763