@stacksolo/cli 0.1.4 → 0.1.6

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
@@ -1249,7 +1249,7 @@ export default defineConfig({
1249
1249
  plugins: [react()],
1250
1250
  server: {
1251
1251
  proxy: {
1252
- '/api': 'http://localhost:8080',
1252
+ '/api': 'http://localhost:8081',
1253
1253
  },
1254
1254
  },
1255
1255
  });
@@ -1408,7 +1408,7 @@ export default defineConfig({
1408
1408
  plugins: [vue()],
1409
1409
  server: {
1410
1410
  proxy: {
1411
- '/api': 'http://localhost:8080',
1411
+ '/api': 'http://localhost:8081',
1412
1412
  },
1413
1413
  },
1414
1414
  });
@@ -1588,7 +1588,7 @@ export default defineConfig({
1588
1588
  plugins: [sveltekit()],
1589
1589
  server: {
1590
1590
  proxy: {
1591
- '/api': 'http://localhost:8080',
1591
+ '/api': 'http://localhost:8081',
1592
1592
  },
1593
1593
  },
1594
1594
  });
@@ -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,304 @@ 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, firebaseProjectId) {
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
+ if (firebaseProjectId) {
17036
+ env.FIREBASE_PROJECT_ID = firebaseProjectId;
17037
+ }
17038
+ const args = service.type === "ui" ? ["run", "dev", "--", "--port", String(service.port)] : ["run", "dev"];
17039
+ const proc = spawn6("npm", args, {
17040
+ cwd: service.sourceDir,
17041
+ env,
17042
+ stdio: ["ignore", "pipe", "pipe"],
17043
+ shell: true
17044
+ });
17045
+ if (proc.stdout) {
17046
+ streamWithPrefix(proc.stdout, service.name, service.color);
17047
+ }
17048
+ if (proc.stderr) {
17049
+ streamWithPrefix(proc.stderr, service.name, service.color);
17050
+ }
17051
+ proc.on("error", (err) => {
17052
+ console.log(service.color(`[${service.name}]`), chalk18.red(`Error: ${err.message}`));
17053
+ });
17054
+ proc.on("exit", (code) => {
17055
+ if (!manager.isShuttingDown) {
17056
+ console.log(
17057
+ service.color(`[${service.name}]`),
17058
+ code === 0 ? chalk18.gray("Exited") : chalk18.red(`Exited with code ${code}`)
17059
+ );
17060
+ }
17061
+ manager.processes.delete(service.name);
17062
+ });
17063
+ return proc;
17064
+ }
17065
+ async function startFirebaseEmulators(manager) {
17066
+ const spinner = ora8("Starting Firebase emulators...").start();
17067
+ try {
17068
+ const proc = spawn6(
17069
+ "firebase",
17070
+ ["emulators:start", "--only", "firestore,auth", "--project", "demo-local"],
17071
+ {
17072
+ stdio: ["ignore", "pipe", "pipe"],
17073
+ shell: true
17074
+ }
17075
+ );
17076
+ const color = chalk18.yellow;
17077
+ if (proc.stdout) {
17078
+ streamWithPrefix(proc.stdout, "firebase", color);
17079
+ }
17080
+ if (proc.stderr) {
17081
+ streamWithPrefix(proc.stderr, "firebase", color);
17082
+ }
17083
+ proc.on("error", () => {
17084
+ spinner.fail("Firebase CLI not found. Skipping emulators.");
17085
+ });
17086
+ await new Promise((resolve7) => setTimeout(resolve7, 3e3));
17087
+ spinner.succeed("Firebase emulators starting");
17088
+ return proc;
17089
+ } catch {
17090
+ spinner.warn("Firebase emulators not available");
17091
+ return null;
17092
+ }
17093
+ }
17094
+ function shutdown(manager) {
17095
+ manager.isShuttingDown = true;
17096
+ console.log(chalk18.gray("\n Shutting down services...\n"));
17097
+ for (const [name, proc] of manager.processes) {
17098
+ try {
17099
+ console.log(chalk18.gray(` Stopping ${name}...`));
17100
+ proc.kill("SIGTERM");
17101
+ } catch {
17102
+ }
17103
+ }
17104
+ setTimeout(() => {
17105
+ for (const [, proc] of manager.processes) {
17106
+ try {
17107
+ proc.kill("SIGKILL");
17108
+ } catch {
17109
+ }
17110
+ }
17111
+ process.exit(0);
17112
+ }, 5e3);
17113
+ }
17114
+ async function startLocalEnvironment(options) {
17115
+ console.log(chalk18.bold("\n StackSolo Local Development\n"));
17116
+ const projectRoot = process.cwd();
17117
+ const configPath = path22.join(projectRoot, ".stacksolo", "stacksolo.config.json");
17118
+ let config;
17119
+ try {
17120
+ const content = await fs19.readFile(configPath, "utf-8");
17121
+ config = JSON.parse(content);
17122
+ } catch {
17123
+ console.log(chalk18.red(` Config not found: .stacksolo/stacksolo.config.json`));
17124
+ console.log(chalk18.gray(` Run 'stacksolo init' first.
17125
+ `));
17126
+ process.exit(1);
17127
+ }
17128
+ const services = collectServices(config, projectRoot);
17129
+ if (services.length === 0) {
17130
+ console.log(chalk18.yellow(" No services found in config.\n"));
17131
+ return;
17132
+ }
17133
+ const validServices = [];
17134
+ const missingDeps = [];
17135
+ const missingDevScript = [];
17136
+ for (const service of services) {
17137
+ if (!await hasPackageJson(service.sourceDir)) {
17138
+ console.log(chalk18.yellow(` Warning: ${service.name} has no package.json at ${service.sourceDir}, skipping`));
17139
+ continue;
17140
+ }
17141
+ if (!await hasDevScript(service.sourceDir)) {
17142
+ missingDevScript.push(service);
17143
+ continue;
17144
+ }
17145
+ validServices.push(service);
17146
+ if (!await hasNodeModules(service.sourceDir)) {
17147
+ missingDeps.push(service);
17148
+ }
17149
+ }
17150
+ if (missingDevScript.length > 0) {
17151
+ console.log(chalk18.red('\n Error: Some services are missing "dev" script in package.json:\n'));
17152
+ for (const svc of missingDevScript) {
17153
+ console.log(chalk18.red(` \u2717 ${svc.name}`));
17154
+ console.log(chalk18.gray(` Add a "dev" script to ${path22.relative(projectRoot, svc.sourceDir)}/package.json`));
17155
+ }
17156
+ console.log(chalk18.gray("\n See: https://stacksolo.dev/reference/cli/#local-mode---local\n"));
17157
+ }
17158
+ if (validServices.length === 0) {
17159
+ console.log(chalk18.yellow(" No runnable services found.\n"));
17160
+ console.log(chalk18.gray(" Run `stacksolo scaffold` to generate service code.\n"));
17161
+ return;
17162
+ }
17163
+ if (missingDeps.length > 0) {
17164
+ console.log(chalk18.yellow("\n Warning: Some services are missing node_modules:"));
17165
+ for (const svc of missingDeps) {
17166
+ console.log(chalk18.yellow(` \u2022 ${svc.name}: Run \`cd ${path22.relative(projectRoot, svc.sourceDir)} && npm install\``));
17167
+ }
17168
+ console.log(chalk18.gray("\n Or run: stacksolo install\n"));
17169
+ }
17170
+ const manager = {
17171
+ processes: /* @__PURE__ */ new Map(),
17172
+ services: validServices,
17173
+ isShuttingDown: false
17174
+ };
17175
+ if (options.includeEmulators !== false) {
17176
+ const emulatorProc = await startFirebaseEmulators(manager);
17177
+ if (emulatorProc) {
17178
+ manager.processes.set("firebase-emulator", emulatorProc);
17179
+ }
17180
+ }
17181
+ const firebaseProjectId = config.project.gcpKernel?.firebaseProjectId || config.project.gcpProjectId;
17182
+ const spinner = ora8("Starting services...").start();
17183
+ for (const service of validServices) {
17184
+ const proc = spawnService(service, manager, firebaseProjectId);
17185
+ if (proc) {
17186
+ manager.processes.set(service.name, proc);
17187
+ }
17188
+ }
17189
+ spinner.succeed(`Started ${validServices.length} service(s)`);
17190
+ console.log(chalk18.bold("\n Services running:\n"));
17191
+ for (const service of validServices) {
17192
+ const url = `http://localhost:${service.port}`;
17193
+ console.log(` ${service.color("\u25CF")} ${service.name.padEnd(20)} ${chalk18.cyan(url)}`);
17194
+ }
17195
+ if (options.includeEmulators !== false) {
17196
+ console.log(chalk18.bold("\n Emulators:\n"));
17197
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase UI ${chalk18.cyan("http://localhost:4000")}`);
17198
+ console.log(` ${chalk18.yellow("\u25CF")} Firestore ${chalk18.gray("localhost:8080")}`);
17199
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase Auth ${chalk18.gray("localhost:9099")}`);
17200
+ }
17201
+ console.log(chalk18.bold("\n Commands:\n"));
17202
+ console.log(chalk18.gray(" Press Ctrl+C to stop all services"));
17203
+ console.log("");
17204
+ process.on("SIGINT", () => shutdown(manager));
17205
+ process.on("SIGTERM", () => shutdown(manager));
17206
+ await new Promise(() => {
17207
+ });
17208
+ }
17209
+
17210
+ // src/commands/dev/dev.ts
16924
17211
  function getKernelConfig(config) {
16925
17212
  if (config.project.kernel) {
16926
17213
  return {
@@ -16948,8 +17235,14 @@ var webAdminProcess = null;
16948
17235
  var isShuttingDown = false;
16949
17236
  var K8S_OUTPUT_DIR = ".stacksolo/k8s";
16950
17237
  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) => {
17238
+ 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
17239
  try {
17240
+ if (options.local) {
17241
+ await startLocalEnvironment({
17242
+ includeEmulators: options.emulators !== false
17243
+ });
17244
+ return;
17245
+ }
16953
17246
  if (options.stop) {
16954
17247
  await stopEnvironment();
16955
17248
  return;
@@ -16994,38 +17287,38 @@ var devCommand = new Command17("dev").description("Start local Kubernetes develo
16994
17287
  includeEmulators: options.emulators !== false
16995
17288
  });
16996
17289
  } catch (error) {
16997
- console.error(chalk18.red(`
17290
+ console.error(chalk19.red(`
16998
17291
  Error: ${error instanceof Error ? error.message : error}
16999
17292
  `));
17000
17293
  process.exit(1);
17001
17294
  }
17002
17295
  });
17003
17296
  async function checkPrerequisites() {
17004
- const spinner = ora8("Checking prerequisites...").start();
17297
+ const spinner = ora9("Checking prerequisites...").start();
17005
17298
  try {
17006
17299
  execSync2("kubectl version --client --short 2>/dev/null || kubectl version --client", {
17007
17300
  stdio: "pipe"
17008
17301
  });
17009
17302
  } catch {
17010
17303
  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"));
17304
+ console.log(chalk19.gray("\n Install OrbStack: brew install orbstack"));
17305
+ console.log(chalk19.gray(" Or install kubectl: brew install kubectl\n"));
17013
17306
  throw new Error("kubectl is required but not found");
17014
17307
  }
17015
17308
  try {
17016
17309
  execSync2("kubectl cluster-info 2>/dev/null", { stdio: "pipe" });
17017
17310
  } catch {
17018
17311
  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"));
17312
+ console.log(chalk19.gray("\n If using OrbStack, enable Kubernetes in preferences"));
17313
+ console.log(chalk19.gray(" Settings \u2192 Kubernetes \u2192 Enable Kubernetes\n"));
17021
17314
  throw new Error("Kubernetes cluster not available");
17022
17315
  }
17023
17316
  spinner.succeed("Prerequisites met");
17024
17317
  }
17025
17318
  async function loadConfig2() {
17026
- const configPath = path22.resolve(process.cwd(), CONFIG_FILE);
17319
+ const configPath = path23.resolve(process.cwd(), CONFIG_FILE);
17027
17320
  try {
17028
- const content = await fs19.readFile(configPath, "utf-8");
17321
+ const content = await fs20.readFile(configPath, "utf-8");
17029
17322
  return JSON.parse(content);
17030
17323
  } catch (error) {
17031
17324
  if (error.code === "ENOENT") {
@@ -17043,9 +17336,9 @@ async function validateSourceDirs(config) {
17043
17336
  const kernelService = getPluginService(validateKernelConfig.serviceName);
17044
17337
  const pluginSourcePath = kernelService ? getServiceSourcePath(kernelService) : null;
17045
17338
  if (!pluginSourcePath) {
17046
- const kernelDir = path22.join(projectRoot, "containers", validateKernelConfig.name);
17339
+ const kernelDir = path23.join(projectRoot, "containers", validateKernelConfig.name);
17047
17340
  try {
17048
- await fs19.access(kernelDir);
17341
+ await fs20.access(kernelDir);
17049
17342
  } catch {
17050
17343
  warnings.push(`Kernel directory not found: containers/${validateKernelConfig.name}/`);
17051
17344
  }
@@ -17053,17 +17346,17 @@ async function validateSourceDirs(config) {
17053
17346
  }
17054
17347
  for (const network of config.project.networks || []) {
17055
17348
  for (const func of network.functions || []) {
17056
- const funcDir = path22.join(projectRoot, "functions", func.name);
17349
+ const funcDir = path23.join(projectRoot, "functions", func.name);
17057
17350
  try {
17058
- await fs19.access(funcDir);
17351
+ await fs20.access(funcDir);
17059
17352
  } catch {
17060
17353
  warnings.push(`Function directory not found: functions/${func.name}/`);
17061
17354
  }
17062
17355
  }
17063
17356
  for (const ui of network.uis || []) {
17064
- const uiDir = path22.join(projectRoot, "ui", ui.name);
17357
+ const uiDir = path23.join(projectRoot, "ui", ui.name);
17065
17358
  try {
17066
- await fs19.access(uiDir);
17359
+ await fs20.access(uiDir);
17067
17360
  } catch {
17068
17361
  warnings.push(`UI directory not found: ui/${ui.name}/`);
17069
17362
  }
@@ -17077,7 +17370,7 @@ async function startWebAdmin(config) {
17077
17370
  return null;
17078
17371
  }
17079
17372
  const port = webAdmin.port || 3e3;
17080
- const spinner = ora8(`Starting web admin on port ${port}...`).start();
17373
+ const spinner = ora9(`Starting web admin on port ${port}...`).start();
17081
17374
  try {
17082
17375
  const webAdminService = getPluginService("web-admin");
17083
17376
  let appDir = null;
@@ -17088,9 +17381,9 @@ async function startWebAdmin(config) {
17088
17381
  }
17089
17382
  }
17090
17383
  if (!appDir) {
17091
- const nodeModulesPath = path22.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
17384
+ const nodeModulesPath = path23.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
17092
17385
  try {
17093
- await fs19.access(nodeModulesPath);
17386
+ await fs20.access(nodeModulesPath);
17094
17387
  appDir = nodeModulesPath;
17095
17388
  } catch {
17096
17389
  }
@@ -17099,16 +17392,16 @@ async function startWebAdmin(config) {
17099
17392
  spinner.warn("Web admin not found - install @stacksolo/plugin-web-admin or add to plugins");
17100
17393
  return null;
17101
17394
  }
17102
- const buildDir = path22.join(appDir, "build");
17395
+ const buildDir = path23.join(appDir, "build");
17103
17396
  let useDevMode = false;
17104
17397
  try {
17105
- await fs19.access(buildDir);
17398
+ await fs20.access(buildDir);
17106
17399
  } catch {
17107
17400
  useDevMode = true;
17108
17401
  }
17109
17402
  const projectPath = process.cwd();
17110
17403
  if (useDevMode) {
17111
- webAdminProcess = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
17404
+ webAdminProcess = spawn7("npm", ["run", "dev", "--", "--port", String(port)], {
17112
17405
  cwd: appDir,
17113
17406
  env: {
17114
17407
  ...process.env,
@@ -17119,7 +17412,7 @@ async function startWebAdmin(config) {
17119
17412
  detached: false
17120
17413
  });
17121
17414
  } else {
17122
- webAdminProcess = spawn6("node", ["build"], {
17415
+ webAdminProcess = spawn7("node", ["build"], {
17123
17416
  cwd: appDir,
17124
17417
  env: {
17125
17418
  ...process.env,
@@ -17154,20 +17447,20 @@ async function buildKernelImage(config) {
17154
17447
  const sourcePath = getServiceSourcePath(kernelService);
17155
17448
  if (sourcePath) {
17156
17449
  kernelDir = sourcePath;
17157
- console.log(chalk18.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17450
+ console.log(chalk19.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17158
17451
  } else {
17159
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17452
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17160
17453
  }
17161
17454
  } else {
17162
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17455
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17163
17456
  }
17164
17457
  try {
17165
- await fs19.access(kernelDir);
17458
+ await fs20.access(kernelDir);
17166
17459
  } catch {
17167
- console.log(chalk18.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17460
+ console.log(chalk19.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17168
17461
  return false;
17169
17462
  }
17170
- const spinner = ora8(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17463
+ const spinner = ora9(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17171
17464
  try {
17172
17465
  execSync2("npm install", { cwd: kernelDir, stdio: "pipe" });
17173
17466
  execSync2("npm run build", { cwd: kernelDir, stdio: "pipe" });
@@ -17176,32 +17469,32 @@ async function buildKernelImage(config) {
17176
17469
  return true;
17177
17470
  } catch (error) {
17178
17471
  spinner.fail(`Failed to build ${kernelType} kernel image`);
17179
- console.log(chalk18.gray(` Error: ${error instanceof Error ? error.message : error}`));
17472
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
17180
17473
  return false;
17181
17474
  }
17182
17475
  }
17183
17476
  async function startEnvironment(options) {
17184
- console.log(chalk18.bold("\n StackSolo Dev Environment\n"));
17477
+ console.log(chalk19.bold("\n StackSolo Dev Environment\n"));
17185
17478
  await checkPrerequisites();
17186
- const spinner = ora8("Loading configuration...").start();
17479
+ const spinner = ora9("Loading configuration...").start();
17187
17480
  const config = await loadConfig2();
17188
17481
  const projectName = config.project.name;
17189
17482
  const namespace = sanitizeNamespaceName(projectName);
17190
17483
  spinner.succeed(`Project: ${projectName}`);
17191
- const pluginSpinner = ora8("Loading plugins...").start();
17484
+ const pluginSpinner = ora9("Loading plugins...").start();
17192
17485
  await loadPlugins(config.project.plugins);
17193
17486
  pluginSpinner.succeed("Plugins loaded");
17194
17487
  const warnings = await validateSourceDirs(config);
17195
17488
  if (warnings.length > 0) {
17196
- console.log(chalk18.yellow("\n Warnings:"));
17489
+ console.log(chalk19.yellow("\n Warnings:"));
17197
17490
  for (const warning of warnings) {
17198
- console.log(chalk18.yellow(` \u2022 ${warning}`));
17491
+ console.log(chalk19.yellow(` \u2022 ${warning}`));
17199
17492
  }
17200
17493
  console.log("");
17201
17494
  }
17202
17495
  await buildKernelImage(config);
17203
- const genSpinner = ora8("Generating Kubernetes manifests...").start();
17204
- const outputDir = path22.resolve(process.cwd(), K8S_OUTPUT_DIR);
17496
+ const genSpinner = ora9("Generating Kubernetes manifests...").start();
17497
+ const outputDir = path23.resolve(process.cwd(), K8S_OUTPUT_DIR);
17205
17498
  const result = generateK8sManifests({
17206
17499
  config,
17207
17500
  projectRoot: process.cwd(),
@@ -17211,10 +17504,10 @@ async function startEnvironment(options) {
17211
17504
  genSpinner.succeed(`Generated ${result.manifests.length} manifests to ${K8S_OUTPUT_DIR}/`);
17212
17505
  if (result.warnings.length > 0) {
17213
17506
  for (const warning of result.warnings) {
17214
- console.log(chalk18.yellow(` \u26A0 ${warning}`));
17507
+ console.log(chalk19.yellow(` \u26A0 ${warning}`));
17215
17508
  }
17216
17509
  }
17217
- const applySpinner = ora8("Applying Kubernetes manifests...").start();
17510
+ const applySpinner = ora9("Applying Kubernetes manifests...").start();
17218
17511
  try {
17219
17512
  execSync2(`kubectl apply -f ${outputDir}/namespace.yaml`, { stdio: "pipe" });
17220
17513
  execSync2(`kubectl apply -f ${outputDir}`, { stdio: "pipe" });
@@ -17223,7 +17516,7 @@ async function startEnvironment(options) {
17223
17516
  applySpinner.fail("Failed to apply manifests");
17224
17517
  throw error;
17225
17518
  }
17226
- const readySpinner = ora8("Waiting for pods to be ready...").start();
17519
+ const readySpinner = ora9("Waiting for pods to be ready...").start();
17227
17520
  try {
17228
17521
  execSync2(
17229
17522
  `kubectl wait --for=condition=ready pod --all -n ${namespace} --timeout=120s`,
@@ -17233,7 +17526,7 @@ async function startEnvironment(options) {
17233
17526
  } catch {
17234
17527
  readySpinner.warn("Some pods may not be ready yet");
17235
17528
  }
17236
- const portForwardSpinner = ora8("Setting up port forwarding...").start();
17529
+ const portForwardSpinner = ora9("Setting up port forwarding...").start();
17237
17530
  const portMappings = await setupPortForwarding(namespace, config);
17238
17531
  portForwardSpinner.succeed("Port forwarding active");
17239
17532
  const webAdminPort = await startWebAdmin(config);
@@ -17246,7 +17539,7 @@ async function startEnvironment(options) {
17246
17539
  protocol: "http"
17247
17540
  });
17248
17541
  }
17249
- console.log(chalk18.bold("\n Services running:\n"));
17542
+ console.log(chalk19.bold("\n Services running:\n"));
17250
17543
  try {
17251
17544
  const podStatus = execSync2(
17252
17545
  `kubectl get pods -n ${namespace} -o wide --no-headers`,
@@ -17256,25 +17549,25 @@ async function startEnvironment(options) {
17256
17549
  const parts = line.split(/\s+/);
17257
17550
  const name = parts[0];
17258
17551
  const status = parts[2];
17259
- const statusColor = status === "Running" ? chalk18.green : chalk18.yellow;
17552
+ const statusColor = status === "Running" ? chalk19.green : chalk19.yellow;
17260
17553
  console.log(` ${statusColor("\u25CF")} ${name.padEnd(30)} ${statusColor(status)}`);
17261
17554
  }
17262
17555
  } catch {
17263
- console.log(chalk18.gray(" Unable to get pod status"));
17556
+ console.log(chalk19.gray(" Unable to get pod status"));
17264
17557
  }
17265
- console.log(chalk18.bold("\n Access:\n"));
17558
+ console.log(chalk19.bold("\n Access:\n"));
17266
17559
  for (const mapping of portMappings) {
17267
17560
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17268
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
17561
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17269
17562
  }
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"));
17563
+ console.log(chalk19.bold("\n Commands:\n"));
17564
+ console.log(chalk19.gray(" stacksolo dev --logs Tail all logs"));
17565
+ console.log(chalk19.gray(" stacksolo dev --status Show pod status"));
17566
+ console.log(chalk19.gray(" stacksolo dev --stop Stop environment"));
17274
17567
  console.log("");
17275
17568
  const cleanup = async () => {
17276
17569
  isShuttingDown = true;
17277
- console.log(chalk18.gray("\n Shutting down...\n"));
17570
+ console.log(chalk19.gray("\n Shutting down...\n"));
17278
17571
  if (webAdminProcess) {
17279
17572
  try {
17280
17573
  webAdminProcess.kill("SIGTERM");
@@ -17289,20 +17582,20 @@ async function startEnvironment(options) {
17289
17582
  }
17290
17583
  try {
17291
17584
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17292
- console.log(chalk18.green(" Environment stopped\n"));
17585
+ console.log(chalk19.green(" Environment stopped\n"));
17293
17586
  } catch {
17294
17587
  }
17295
17588
  process.exit(0);
17296
17589
  };
17297
17590
  process.on("SIGINT", cleanup);
17298
17591
  process.on("SIGTERM", cleanup);
17299
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
17592
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17300
17593
  await new Promise(() => {
17301
17594
  });
17302
17595
  }
17303
17596
  function startPortForwardWithRestart(namespace, service, localPort, targetPort, _name) {
17304
17597
  const startForward = () => {
17305
- const proc = spawn6(
17598
+ const proc = spawn7(
17306
17599
  "kubectl",
17307
17600
  ["port-forward", "-n", namespace, `svc/${service}`, `${localPort}:${targetPort}`],
17308
17601
  { stdio: "pipe", detached: false }
@@ -17420,18 +17713,18 @@ async function setupPortForwarding(namespace, config) {
17420
17713
  return portMappings;
17421
17714
  }
17422
17715
  async function stopEnvironment() {
17423
- console.log(chalk18.bold("\n Stopping StackSolo Dev Environment\n"));
17716
+ console.log(chalk19.bold("\n Stopping StackSolo Dev Environment\n"));
17424
17717
  const config = await loadConfig2();
17425
17718
  const namespace = sanitizeNamespaceName(config.project.name);
17426
17719
  const projectName = config.project.name;
17427
- const nsSpinner = ora8(`Deleting namespace ${namespace}...`).start();
17720
+ const nsSpinner = ora9(`Deleting namespace ${namespace}...`).start();
17428
17721
  try {
17429
17722
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17430
17723
  nsSpinner.succeed("Namespace deleted");
17431
17724
  } catch {
17432
17725
  nsSpinner.warn("Namespace may not exist or already deleted");
17433
17726
  }
17434
- const imgSpinner = ora8("Cleaning up Docker images...").start();
17727
+ const imgSpinner = ora9("Cleaning up Docker images...").start();
17435
17728
  try {
17436
17729
  const images = execSync2(
17437
17730
  `docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "^(${namespace}-|${projectName}-)" || true`,
@@ -17455,149 +17748,149 @@ async function stopEnvironment() {
17455
17748
  console.log("");
17456
17749
  }
17457
17750
  async function showStatus() {
17458
- console.log(chalk18.bold("\n StackSolo Dev Status\n"));
17751
+ console.log(chalk19.bold("\n StackSolo Dev Status\n"));
17459
17752
  const config = await loadConfig2();
17460
17753
  const namespace = sanitizeNamespaceName(config.project.name);
17461
17754
  try {
17462
- console.log(chalk18.bold(" Pods:\n"));
17755
+ console.log(chalk19.bold(" Pods:\n"));
17463
17756
  const pods = execSync2(`kubectl get pods -n ${namespace} -o wide`, { encoding: "utf-8" });
17464
17757
  console.log(pods.split("\n").map((l) => " " + l).join("\n"));
17465
- console.log(chalk18.bold("\n Services:\n"));
17758
+ console.log(chalk19.bold("\n Services:\n"));
17466
17759
  const services = execSync2(`kubectl get services -n ${namespace}`, { encoding: "utf-8" });
17467
17760
  console.log(services.split("\n").map((l) => " " + l).join("\n"));
17468
- console.log(chalk18.bold("\n Ingress:\n"));
17761
+ console.log(chalk19.bold("\n Ingress:\n"));
17469
17762
  const ingress = execSync2(`kubectl get ingress -n ${namespace}`, { encoding: "utf-8" });
17470
17763
  console.log(ingress.split("\n").map((l) => " " + l).join("\n"));
17471
17764
  } 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'));
17765
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17766
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17474
17767
  }
17475
17768
  console.log("");
17476
17769
  }
17477
17770
  async function showRoutes() {
17478
- console.log(chalk18.bold("\n StackSolo Gateway Routes\n"));
17771
+ console.log(chalk19.bold("\n StackSolo Gateway Routes\n"));
17479
17772
  const config = await loadConfig2();
17480
17773
  const kernelConfig = getKernelConfig(config);
17481
17774
  if (kernelConfig) {
17482
17775
  const label = kernelConfig.type === "nats" ? "Kernel (NATS)" : "Kernel (GCP)";
17483
17776
  const detail = kernelConfig.type === "nats" ? `Source: containers/${kernelConfig.name}/` : "Type: GCP-native (Cloud Run + Pub/Sub)";
17484
- console.log(chalk18.bold(` ${label}:
17777
+ console.log(chalk19.bold(` ${label}:
17485
17778
  `));
17486
- console.log(` ${chalk18.cyan("\u25CF")} ${kernelConfig.name}`);
17487
- console.log(chalk18.gray(` ${detail}`));
17779
+ console.log(` ${chalk19.cyan("\u25CF")} ${kernelConfig.name}`);
17780
+ console.log(chalk19.gray(` ${detail}`));
17488
17781
  console.log("");
17489
17782
  }
17490
17783
  for (const network of config.project.networks || []) {
17491
- console.log(chalk18.bold(` Network: ${network.name}
17784
+ console.log(chalk19.bold(` Network: ${network.name}
17492
17785
  `));
17493
17786
  if (network.functions && network.functions.length > 0) {
17494
- console.log(chalk18.bold(" Functions:"));
17787
+ console.log(chalk19.bold(" Functions:"));
17495
17788
  for (const func of network.functions) {
17496
- console.log(` ${chalk18.green("\u03BB")} ${func.name}`);
17497
- console.log(chalk18.gray(` Source: functions/${func.name}/`));
17789
+ console.log(` ${chalk19.green("\u03BB")} ${func.name}`);
17790
+ console.log(chalk19.gray(` Source: functions/${func.name}/`));
17498
17791
  }
17499
17792
  console.log("");
17500
17793
  }
17501
17794
  if (network.containers && network.containers.length > 0) {
17502
- console.log(chalk18.bold(" Containers:"));
17795
+ console.log(chalk19.bold(" Containers:"));
17503
17796
  for (const container of network.containers) {
17504
- console.log(` ${chalk18.blue("\u25FC")} ${container.name}`);
17505
- console.log(chalk18.gray(` Source: containers/${container.name}/`));
17797
+ console.log(` ${chalk19.blue("\u25FC")} ${container.name}`);
17798
+ console.log(chalk19.gray(` Source: containers/${container.name}/`));
17506
17799
  }
17507
17800
  console.log("");
17508
17801
  }
17509
17802
  if (network.uis && network.uis.length > 0) {
17510
- console.log(chalk18.bold(" UIs:"));
17803
+ console.log(chalk19.bold(" UIs:"));
17511
17804
  for (const ui of network.uis) {
17512
- console.log(` ${chalk18.magenta("\u25C6")} ${ui.name}`);
17513
- console.log(chalk18.gray(` Source: ui/${ui.name}/`));
17805
+ console.log(` ${chalk19.magenta("\u25C6")} ${ui.name}`);
17806
+ console.log(chalk19.gray(` Source: ui/${ui.name}/`));
17514
17807
  }
17515
17808
  console.log("");
17516
17809
  }
17517
17810
  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)));
17811
+ console.log(chalk19.bold(" Gateway Routes:"));
17812
+ console.log(chalk19.gray(" Path \u2192 Backend"));
17813
+ console.log(chalk19.gray(" " + "\u2500".repeat(50)));
17521
17814
  for (const route of network.loadBalancer.routes) {
17522
17815
  const pathPadded = route.path.padEnd(24);
17523
- console.log(` ${chalk18.yellow(pathPadded)} \u2192 ${route.backend}`);
17816
+ console.log(` ${chalk19.yellow(pathPadded)} \u2192 ${route.backend}`);
17524
17817
  }
17525
17818
  console.log("");
17526
17819
  }
17527
17820
  }
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`);
17821
+ console.log(chalk19.bold(" Emulators:\n"));
17822
+ console.log(` ${chalk19.yellow("Firebase UI")} http://localhost:4000`);
17823
+ console.log(` ${chalk19.yellow("Firestore")} localhost:8080`);
17824
+ console.log(` ${chalk19.yellow("Firebase Auth")} localhost:9099`);
17825
+ console.log(` ${chalk19.yellow("Pub/Sub")} localhost:8085`);
17533
17826
  console.log("");
17534
- console.log(chalk18.bold(" Local Access:\n"));
17535
- console.log(` ${chalk18.cyan("Gateway:")} http://localhost:8000`);
17827
+ console.log(chalk19.bold(" Local Access:\n"));
17828
+ console.log(` ${chalk19.cyan("Gateway:")} http://localhost:8000`);
17536
17829
  const routesKernelConfig = getKernelConfig(config);
17537
17830
  if (routesKernelConfig) {
17538
17831
  const label = routesKernelConfig.type === "nats" ? "Kernel HTTP" : "GCP Kernel";
17539
- console.log(` ${chalk18.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17832
+ console.log(` ${chalk19.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17540
17833
  if (routesKernelConfig.natsPort) {
17541
- console.log(` ${chalk18.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17834
+ console.log(` ${chalk19.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17542
17835
  }
17543
17836
  }
17544
17837
  console.log("");
17545
17838
  }
17546
17839
  async function describeResources(resource) {
17547
- console.log(chalk18.bold("\n StackSolo Dev - Resource Details\n"));
17840
+ console.log(chalk19.bold("\n StackSolo Dev - Resource Details\n"));
17548
17841
  const config = await loadConfig2();
17549
17842
  const namespace = sanitizeNamespaceName(config.project.name);
17550
17843
  try {
17551
17844
  const indent = (text) => text.split("\n").map((l) => " " + l).join("\n");
17552
17845
  if (resource === "all" || resource === "pods") {
17553
- console.log(chalk18.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17846
+ console.log(chalk19.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17554
17847
  const pods = execSync2(`kubectl describe pods -n ${namespace}`, { encoding: "utf-8" });
17555
17848
  console.log(indent(pods));
17556
17849
  }
17557
17850
  if (resource === "all" || resource === "services") {
17558
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17851
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17559
17852
  const services = execSync2(`kubectl describe services -n ${namespace}`, { encoding: "utf-8" });
17560
17853
  console.log(indent(services));
17561
17854
  }
17562
17855
  if (resource === "all" || resource === "deployments") {
17563
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17856
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17564
17857
  const deployments = execSync2(`kubectl describe deployments -n ${namespace}`, { encoding: "utf-8" });
17565
17858
  console.log(indent(deployments));
17566
17859
  }
17567
17860
  if (resource === "all" || resource === "ingress") {
17568
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17861
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17569
17862
  try {
17570
17863
  const ingress = execSync2(`kubectl describe ingress -n ${namespace}`, { encoding: "utf-8" });
17571
17864
  console.log(indent(ingress));
17572
17865
  } catch {
17573
- console.log(chalk18.gray(" No ingress resources found"));
17866
+ console.log(chalk19.gray(" No ingress resources found"));
17574
17867
  }
17575
17868
  }
17576
17869
  if (resource === "all" || resource === "configmaps") {
17577
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17870
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17578
17871
  const configmaps = execSync2(`kubectl describe configmaps -n ${namespace}`, { encoding: "utf-8" });
17579
17872
  console.log(indent(configmaps));
17580
17873
  }
17581
17874
  if (!["all", "pods", "services", "deployments", "ingress", "configmaps"].includes(resource)) {
17582
- console.log(chalk18.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17875
+ console.log(chalk19.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17583
17876
  `));
17584
17877
  try {
17585
17878
  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
17879
  console.log(indent(podDesc));
17587
17880
  } 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"));
17881
+ console.log(chalk19.yellow(` Resource '${resource}' not found`));
17882
+ console.log(chalk19.gray("\n Available options: all, pods, services, deployments, ingress, configmaps"));
17883
+ console.log(chalk19.gray(" Or specify a resource name like: --describe api"));
17591
17884
  }
17592
17885
  }
17593
17886
  } 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'));
17887
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17888
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17596
17889
  }
17597
17890
  console.log("");
17598
17891
  }
17599
17892
  async function checkHealth() {
17600
- console.log(chalk18.bold("\n StackSolo Dev - Health Check\n"));
17893
+ console.log(chalk19.bold("\n StackSolo Dev - Health Check\n"));
17601
17894
  const config = await loadConfig2();
17602
17895
  const namespace = sanitizeNamespaceName(config.project.name);
17603
17896
  const healthChecks = [];
@@ -17618,7 +17911,7 @@ async function checkHealth() {
17618
17911
  functionPort++;
17619
17912
  }
17620
17913
  }
17621
- console.log(chalk18.bold(" Pod Status:\n"));
17914
+ console.log(chalk19.bold(" Pod Status:\n"));
17622
17915
  try {
17623
17916
  const podOutput = execSync2(
17624
17917
  `kubectl get pods -n ${namespace} -o jsonpath='{range .items[*]}{.metadata.name}|{.status.phase}|{.status.conditions[?(@.type=="Ready")].status}{"\\n"}{end}'`,
@@ -17628,16 +17921,16 @@ async function checkHealth() {
17628
17921
  if (!line) continue;
17629
17922
  const [name, phase, ready] = line.split("|");
17630
17923
  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);
17924
+ const icon = isHealthy ? chalk19.green("\u2713") : chalk19.red("\u2717");
17925
+ const status = isHealthy ? chalk19.green("Healthy") : chalk19.yellow(phase);
17633
17926
  console.log(` ${icon} ${name.padEnd(40)} ${status}`);
17634
17927
  }
17635
17928
  } catch {
17636
- console.log(chalk18.yellow(" Unable to get pod status"));
17929
+ console.log(chalk19.yellow(" Unable to get pod status"));
17637
17930
  }
17638
- console.log(chalk18.bold("\n HTTP Endpoints:\n"));
17931
+ console.log(chalk19.bold("\n HTTP Endpoints:\n"));
17639
17932
  for (const check of healthChecks) {
17640
- const spinner = ora8({ text: `Checking ${check.name}...`, indent: 4 }).start();
17933
+ const spinner = ora9({ text: `Checking ${check.name}...`, indent: 4 }).start();
17641
17934
  try {
17642
17935
  const response = await Promise.race([
17643
17936
  fetch(`http://localhost:${check.port}${check.path}`),
@@ -17646,27 +17939,27 @@ async function checkHealth() {
17646
17939
  )
17647
17940
  ]);
17648
17941
  if (response.ok) {
17649
- spinner.succeed(`${check.name.padEnd(25)} ${chalk18.green("OK")} (port ${check.port})`);
17942
+ spinner.succeed(`${check.name.padEnd(25)} ${chalk19.green("OK")} (port ${check.port})`);
17650
17943
  } else {
17651
- spinner.warn(`${check.name.padEnd(25)} ${chalk18.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17944
+ spinner.warn(`${check.name.padEnd(25)} ${chalk19.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17652
17945
  }
17653
17946
  } catch (error) {
17654
17947
  const errMsg = error instanceof Error ? error.message : "Unknown error";
17655
17948
  if (errMsg.includes("ECONNREFUSED")) {
17656
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Connection refused")} (port ${check.port})`);
17949
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Connection refused")} (port ${check.port})`);
17657
17950
  } else if (errMsg.includes("Timeout")) {
17658
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Timeout")} (port ${check.port})`);
17951
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Timeout")} (port ${check.port})`);
17659
17952
  } else {
17660
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red(errMsg)} (port ${check.port})`);
17953
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red(errMsg)} (port ${check.port})`);
17661
17954
  }
17662
17955
  }
17663
17956
  }
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"));
17957
+ console.log(chalk19.bold("\n Tip:\n"));
17958
+ console.log(chalk19.gray(' If ports show "Connection refused", try: stacksolo dev --restart'));
17959
+ console.log(chalk19.gray(" This will restart all port-forwards\n"));
17667
17960
  }
17668
17961
  async function showPorts() {
17669
- console.log(chalk18.bold("\n StackSolo Dev - Port Forward Status\n"));
17962
+ console.log(chalk19.bold("\n StackSolo Dev - Port Forward Status\n"));
17670
17963
  const config = await loadConfig2();
17671
17964
  const namespace = sanitizeNamespaceName(config.project.name);
17672
17965
  const expectedPorts = [];
@@ -17702,9 +17995,9 @@ async function showPorts() {
17702
17995
  uiPort++;
17703
17996
  }
17704
17997
  }
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)));
17998
+ console.log(chalk19.bold(" Expected Port Forwards:\n"));
17999
+ console.log(chalk19.gray(" Name Port Service Status"));
18000
+ console.log(chalk19.gray(" " + "\u2500".repeat(75)));
17708
18001
  for (const mapping of expectedPorts) {
17709
18002
  let status;
17710
18003
  try {
@@ -17714,22 +18007,22 @@ async function showPorts() {
17714
18007
  (_, reject) => setTimeout(() => reject(new Error("Timeout")), 500)
17715
18008
  )
17716
18009
  ]);
17717
- status = chalk18.green("\u25CF Active");
18010
+ status = chalk19.green("\u25CF Active");
17718
18011
  } catch (error) {
17719
18012
  const errMsg = error instanceof Error ? error.message : "";
17720
18013
  if (errMsg.includes("ECONNREFUSED")) {
17721
- status = chalk18.red("\u25CB Not listening");
18014
+ status = chalk19.red("\u25CB Not listening");
17722
18015
  } else if (errMsg.includes("Timeout")) {
17723
- status = chalk18.yellow("\u25CB No response");
18016
+ status = chalk19.yellow("\u25CB No response");
17724
18017
  } else {
17725
- status = chalk18.blue("\u25CF TCP only");
18018
+ status = chalk19.blue("\u25CF TCP only");
17726
18019
  }
17727
18020
  }
17728
18021
  console.log(
17729
18022
  ` ${mapping.name.padEnd(30)} ${String(mapping.port).padEnd(8)} ${mapping.service.padEnd(22)} ${status}`
17730
18023
  );
17731
18024
  }
17732
- console.log(chalk18.bold("\n Active Port-Forward Processes:\n"));
18025
+ console.log(chalk19.bold("\n Active Port-Forward Processes:\n"));
17733
18026
  try {
17734
18027
  const psOutput = execSync2(`ps aux | grep 'kubectl port-forward' | grep -v grep | grep ${namespace}`, {
17735
18028
  encoding: "utf-8"
@@ -17738,21 +18031,21 @@ async function showPorts() {
17738
18031
  for (const line of psOutput.trim().split("\n")) {
17739
18032
  const match = line.match(/port-forward.*svc\/([^\s]+)\s+(\d+:\d+)/);
17740
18033
  if (match) {
17741
- console.log(chalk18.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
18034
+ console.log(chalk19.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
17742
18035
  }
17743
18036
  }
17744
18037
  } else {
17745
- console.log(chalk18.yellow(" No active port-forward processes found"));
18038
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17746
18039
  }
17747
18040
  } catch {
17748
- console.log(chalk18.yellow(" No active port-forward processes found"));
18041
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17749
18042
  }
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"));
18043
+ console.log(chalk19.bold("\n Commands:\n"));
18044
+ console.log(chalk19.gray(" stacksolo dev --restart Restart all port-forwards"));
18045
+ console.log(chalk19.gray(" stacksolo dev --health Check endpoint health\n"));
17753
18046
  }
17754
18047
  async function showServiceNames() {
17755
- console.log(chalk18.bold("\n StackSolo Dev - Service Names\n"));
18048
+ console.log(chalk19.bold("\n StackSolo Dev - Service Names\n"));
17756
18049
  const config = await loadConfig2();
17757
18050
  const namespace = sanitizeNamespaceName(config.project.name);
17758
18051
  const services = [];
@@ -17795,15 +18088,15 @@ async function showServiceNames() {
17795
18088
  if (hasGateway) {
17796
18089
  services.push({ name: "gateway", type: "gateway", k8sName: "gateway" });
17797
18090
  }
17798
- console.log(chalk18.gray(" Name Type K8s Service Name"));
17799
- console.log(chalk18.gray(" " + "\u2500".repeat(60)));
18091
+ console.log(chalk19.gray(" Name Type K8s Service Name"));
18092
+ console.log(chalk19.gray(" " + "\u2500".repeat(60)));
17800
18093
  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;
18094
+ 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
18095
  console.log(
17803
18096
  ` ${svc.name.padEnd(25)} ${typeColor(svc.type.padEnd(12))} ${svc.k8sName}`
17804
18097
  );
17805
18098
  }
17806
- console.log(chalk18.bold("\n Running Pods:\n"));
18099
+ console.log(chalk19.bold("\n Running Pods:\n"));
17807
18100
  try {
17808
18101
  const pods = execSync2(`kubectl get pods -n ${namespace} --no-headers -o custom-columns=NAME:.metadata.name`, {
17809
18102
  encoding: "utf-8"
@@ -17811,30 +18104,30 @@ async function showServiceNames() {
17811
18104
  for (const pod of pods.trim().split("\n")) {
17812
18105
  if (pod) {
17813
18106
  const serviceName = pod.replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17814
- console.log(` ${chalk18.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk18.gray(pod)}`);
18107
+ console.log(` ${chalk19.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk19.gray(pod)}`);
17815
18108
  }
17816
18109
  }
17817
18110
  } catch {
17818
- console.log(chalk18.yellow(" No pods found"));
18111
+ console.log(chalk19.yellow(" No pods found"));
17819
18112
  }
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"));
18113
+ console.log(chalk19.bold("\n Usage:\n"));
18114
+ console.log(chalk19.gray(" stacksolo dev --restart <name> Restart a specific service"));
18115
+ console.log(chalk19.gray(" stacksolo dev --logs <name> Tail logs for a service"));
18116
+ console.log(chalk19.gray(" stacksolo dev --describe <name> Describe a service\n"));
17824
18117
  }
17825
18118
  async function restartService(service) {
17826
- console.log(chalk18.bold("\n StackSolo Dev - Restart\n"));
18119
+ console.log(chalk19.bold("\n StackSolo Dev - Restart\n"));
17827
18120
  const config = await loadConfig2();
17828
18121
  const namespace = sanitizeNamespaceName(config.project.name);
17829
18122
  if (service) {
17830
- const spinner = ora8(`Restarting pod: ${service}...`).start();
18123
+ const spinner = ora9(`Restarting pod: ${service}...`).start();
17831
18124
  try {
17832
18125
  execSync2(
17833
18126
  `kubectl delete pod -n ${namespace} -l app.kubernetes.io/name=${service} --grace-period=5`,
17834
18127
  { stdio: "pipe" }
17835
18128
  );
17836
18129
  spinner.succeed(`Pod ${service} restarted`);
17837
- const waitSpinner = ora8(`Waiting for ${service} to be ready...`).start();
18130
+ const waitSpinner = ora9(`Waiting for ${service} to be ready...`).start();
17838
18131
  try {
17839
18132
  execSync2(
17840
18133
  `kubectl wait --for=condition=ready pod -n ${namespace} -l app.kubernetes.io/name=${service} --timeout=60s`,
@@ -17846,19 +18139,19 @@ async function restartService(service) {
17846
18139
  }
17847
18140
  } catch (error) {
17848
18141
  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:"));
18142
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
18143
+ console.log(chalk19.gray("\n Available services:"));
17851
18144
  try {
17852
18145
  const pods = execSync2(`kubectl get pods -n ${namespace} -o name`, { encoding: "utf-8" });
17853
18146
  for (const pod of pods.trim().split("\n")) {
17854
18147
  const podName = pod.replace("pod/", "").replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17855
- console.log(chalk18.gray(` ${podName}`));
18148
+ console.log(chalk19.gray(` ${podName}`));
17856
18149
  }
17857
18150
  } catch {
17858
18151
  }
17859
18152
  }
17860
18153
  } else {
17861
- const killSpinner = ora8("Stopping existing port-forwards...").start();
18154
+ const killSpinner = ora9("Stopping existing port-forwards...").start();
17862
18155
  try {
17863
18156
  execSync2(`pkill -f "kubectl port-forward.*${namespace}"`, { stdio: "pipe" });
17864
18157
  killSpinner.succeed("Port-forwards stopped");
@@ -17867,21 +18160,21 @@ async function restartService(service) {
17867
18160
  }
17868
18161
  await new Promise((resolve7) => setTimeout(resolve7, 500));
17869
18162
  console.log("");
17870
- const spinner = ora8("Restarting port-forwards...").start();
18163
+ const spinner = ora9("Restarting port-forwards...").start();
17871
18164
  portForwardProcesses.length = 0;
17872
18165
  const portMappings = await setupPortForwarding(namespace, config);
17873
18166
  spinner.succeed("Port-forwards restarted");
17874
- console.log(chalk18.bold("\n Active Forwards:\n"));
18167
+ console.log(chalk19.bold("\n Active Forwards:\n"));
17875
18168
  for (const mapping of portMappings) {
17876
18169
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17877
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
18170
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17878
18171
  }
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"));
18172
+ console.log(chalk19.bold("\n Tip:\n"));
18173
+ console.log(chalk19.gray(" Run: stacksolo dev --health to verify endpoints\n"));
18174
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17882
18175
  const cleanup = async () => {
17883
18176
  isShuttingDown = true;
17884
- console.log(chalk18.gray("\n Stopping port-forwards...\n"));
18177
+ console.log(chalk19.gray("\n Stopping port-forwards...\n"));
17885
18178
  for (const proc of portForwardProcesses) {
17886
18179
  try {
17887
18180
  proc.kill("SIGTERM");
@@ -17900,10 +18193,10 @@ async function restartService(service) {
17900
18193
  async function tailLogs(service) {
17901
18194
  const config = await loadConfig2();
17902
18195
  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"));
18196
+ console.log(chalk19.bold("\n StackSolo Dev Logs\n"));
18197
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17905
18198
  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" });
18199
+ const child = spawn7("kubectl", args, { stdio: "inherit" });
17907
18200
  process.on("SIGINT", () => {
17908
18201
  child.kill("SIGINT");
17909
18202
  process.exit(0);
@@ -17915,10 +18208,10 @@ async function tailLogs(service) {
17915
18208
 
17916
18209
  // src/commands/dev/install.ts
17917
18210
  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";
18211
+ import chalk20 from "chalk";
18212
+ import ora10 from "ora";
18213
+ import * as path24 from "path";
18214
+ import * as fs21 from "fs/promises";
17922
18215
  import { exec as exec14 } from "child_process";
17923
18216
  import { promisify as promisify14 } from "util";
17924
18217
  var execAsync14 = promisify14(exec14);
@@ -17926,13 +18219,13 @@ var STACKSOLO_DIR10 = ".stacksolo";
17926
18219
  var CONFIG_FILENAME8 = "stacksolo.config.json";
17927
18220
  var installCommand = new Command18("install").description("Install dependencies for all resources").option("-p, --parallel", "Install dependencies in parallel").action(async (options) => {
17928
18221
  const cwd = process.cwd();
17929
- console.log(chalk19.cyan("\n StackSolo Install\n"));
17930
- const configPath = path23.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
18222
+ console.log(chalk20.cyan("\n StackSolo Install\n"));
18223
+ const configPath = path24.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
17931
18224
  let config;
17932
18225
  try {
17933
18226
  config = parseConfig(configPath);
17934
18227
  } catch {
17935
- console.log(chalk19.red(" No config found. Run `stacksolo init` first.\n"));
18228
+ console.log(chalk20.red(" No config found. Run `stacksolo init` first.\n"));
17936
18229
  return;
17937
18230
  }
17938
18231
  const directories = [];
@@ -17941,7 +18234,7 @@ var installCommand = new Command18("install").description("Install dependencies
17941
18234
  const sourceDir = fn.sourceDir?.replace(/^\.\//, "") || `functions/${fn.name}`;
17942
18235
  directories.push({
17943
18236
  name: fn.name,
17944
- path: path23.join(cwd, sourceDir),
18237
+ path: path24.join(cwd, sourceDir),
17945
18238
  type: "function"
17946
18239
  });
17947
18240
  }
@@ -17949,7 +18242,7 @@ var installCommand = new Command18("install").description("Install dependencies
17949
18242
  const sourceDir = container.sourceDir?.replace(/^\.\//, "") || `containers/${container.name}`;
17950
18243
  directories.push({
17951
18244
  name: container.name,
17952
- path: path23.join(cwd, sourceDir),
18245
+ path: path24.join(cwd, sourceDir),
17953
18246
  type: "container"
17954
18247
  });
17955
18248
  }
@@ -17957,33 +18250,33 @@ var installCommand = new Command18("install").description("Install dependencies
17957
18250
  const sourceDir = ui.sourceDir?.replace(/^\.\//, "") || `apps/${ui.name}`;
17958
18251
  directories.push({
17959
18252
  name: ui.name,
17960
- path: path23.join(cwd, sourceDir),
18253
+ path: path24.join(cwd, sourceDir),
17961
18254
  type: "ui"
17962
18255
  });
17963
18256
  }
17964
18257
  }
17965
18258
  if (directories.length === 0) {
17966
- console.log(chalk19.yellow(" No resources found in config.\n"));
18259
+ console.log(chalk20.yellow(" No resources found in config.\n"));
17967
18260
  return;
17968
18261
  }
17969
- console.log(chalk19.gray(` Found ${directories.length} resource(s) to install:
18262
+ console.log(chalk20.gray(` Found ${directories.length} resource(s) to install:
17970
18263
  `));
17971
18264
  const validDirs = [];
17972
18265
  for (const dir of directories) {
17973
18266
  try {
17974
- await fs20.access(path23.join(dir.path, "package.json"));
18267
+ await fs21.access(path24.join(dir.path, "package.json"));
17975
18268
  validDirs.push(dir);
17976
- console.log(chalk19.gray(` - ${dir.name} (${dir.type})`));
18269
+ console.log(chalk20.gray(` - ${dir.name} (${dir.type})`));
17977
18270
  } catch {
17978
18271
  }
17979
18272
  }
17980
18273
  if (validDirs.length === 0) {
17981
- console.log(chalk19.yellow(" No resources with package.json found.\n"));
18274
+ console.log(chalk20.yellow(" No resources with package.json found.\n"));
17982
18275
  return;
17983
18276
  }
17984
18277
  console.log("");
17985
18278
  const installDir = async (dir) => {
17986
- const spinner = ora9(`Installing ${dir.name}...`).start();
18279
+ const spinner = ora10(`Installing ${dir.name}...`).start();
17987
18280
  try {
17988
18281
  await execAsync14("npm install", { cwd: dir.path, timeout: 12e4 });
17989
18282
  spinner.succeed(`Installed ${dir.name}`);
@@ -18006,34 +18299,34 @@ var installCommand = new Command18("install").description("Install dependencies
18006
18299
  const failed = results.filter((r) => !r.success).length;
18007
18300
  console.log("");
18008
18301
  if (failed === 0) {
18009
- console.log(chalk19.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
18302
+ console.log(chalk20.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
18010
18303
  `));
18011
18304
  } else {
18012
- console.log(chalk19.yellow(` Installed ${succeeded}, failed ${failed}
18305
+ console.log(chalk20.yellow(` Installed ${succeeded}, failed ${failed}
18013
18306
  `));
18014
18307
  }
18015
18308
  });
18016
18309
 
18017
18310
  // src/commands/dev/serve.ts
18018
18311
  import { Command as Command19 } from "commander";
18019
- import chalk20 from "chalk";
18312
+ import chalk21 from "chalk";
18020
18313
  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"));
18314
+ console.log(chalk21.bold("\n StackSolo Server\n"));
18022
18315
  const port = parseInt(options.port, 10);
18023
18316
  const host = options.host;
18024
- console.log(chalk20.gray(` Starting API server on ${host}:${port}...`));
18317
+ console.log(chalk21.gray(` Starting API server on ${host}:${port}...`));
18025
18318
  try {
18026
18319
  const { startServer } = await import("@stacksolo/api");
18027
18320
  await startServer({ port, host });
18028
18321
  } catch (error) {
18029
- const { spawn: spawn7 } = await import("child_process");
18030
- const path27 = await import("path");
18322
+ const { spawn: spawn8 } = await import("child_process");
18323
+ const path28 = await import("path");
18031
18324
  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}...
18325
+ const __dirname = path28.dirname(fileURLToPath(import.meta.url));
18326
+ const apiPath = path28.resolve(__dirname, "../../api/dist/index.js");
18327
+ console.log(chalk21.gray(` Spawning API from ${apiPath}...
18035
18328
  `));
18036
- const child = spawn7("node", [apiPath], {
18329
+ const child = spawn8("node", [apiPath], {
18037
18330
  env: {
18038
18331
  ...process.env,
18039
18332
  PORT: String(port),
@@ -18042,7 +18335,7 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
18042
18335
  stdio: "inherit"
18043
18336
  });
18044
18337
  child.on("error", (err) => {
18045
- console.log(chalk20.red(` Failed to start server: ${err.message}
18338
+ console.log(chalk21.red(` Failed to start server: ${err.message}
18046
18339
  `));
18047
18340
  process.exit(1);
18048
18341
  });
@@ -18060,21 +18353,21 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
18060
18353
 
18061
18354
  // src/commands/config/config.ts
18062
18355
  import { Command as Command20 } from "commander";
18063
- import chalk21 from "chalk";
18064
- import * as path24 from "path";
18356
+ import chalk22 from "chalk";
18357
+ import * as path25 from "path";
18065
18358
  var STACKSOLO_DIR11 = ".stacksolo";
18066
18359
  var CONFIG_FILENAME9 = "stacksolo.config.json";
18067
18360
  function getConfigPath6() {
18068
- return path24.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
18361
+ return path25.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
18069
18362
  }
18070
18363
  async function loadConfig3(configPath) {
18071
18364
  try {
18072
18365
  return parseConfig(configPath);
18073
18366
  } catch (error) {
18074
- console.log(chalk21.red(`
18367
+ console.log(chalk22.red(`
18075
18368
  Error: Could not read ${STACKSOLO_DIR11}/${CONFIG_FILENAME9}
18076
18369
  `));
18077
- console.log(chalk21.gray(` ${error}`));
18370
+ console.log(chalk22.gray(` ${error}`));
18078
18371
  return null;
18079
18372
  }
18080
18373
  }
@@ -18086,75 +18379,75 @@ var showCommand2 = new Command20("show").description("Display the current config
18086
18379
  console.log(JSON.stringify(config, null, 2));
18087
18380
  return;
18088
18381
  }
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}`));
18382
+ console.log(chalk22.bold("\n StackSolo Configuration\n"));
18383
+ console.log(chalk22.cyan(" Project:"));
18384
+ console.log(chalk22.white(` Name: ${config.project.name}`));
18385
+ console.log(chalk22.white(` Region: ${config.project.region}`));
18386
+ console.log(chalk22.white(` GCP Project: ${config.project.gcpProjectId}`));
18094
18387
  const { buckets, secrets, topics, queues, crons } = config.project;
18095
18388
  if (buckets?.length) {
18096
- console.log(chalk21.cyan("\n Buckets:"));
18389
+ console.log(chalk22.cyan("\n Buckets:"));
18097
18390
  buckets.forEach((b) => {
18098
- console.log(chalk21.white(` - ${b.name}`) + chalk21.gray(` (${b.storageClass || "STANDARD"})`));
18391
+ console.log(chalk22.white(` - ${b.name}`) + chalk22.gray(` (${b.storageClass || "STANDARD"})`));
18099
18392
  });
18100
18393
  }
18101
18394
  if (secrets?.length) {
18102
- console.log(chalk21.cyan("\n Secrets:"));
18395
+ console.log(chalk22.cyan("\n Secrets:"));
18103
18396
  secrets.forEach((s) => {
18104
- console.log(chalk21.white(` - ${s.name}`));
18397
+ console.log(chalk22.white(` - ${s.name}`));
18105
18398
  });
18106
18399
  }
18107
18400
  if (topics?.length) {
18108
- console.log(chalk21.cyan("\n Topics:"));
18401
+ console.log(chalk22.cyan("\n Topics:"));
18109
18402
  topics.forEach((t) => {
18110
- console.log(chalk21.white(` - ${t.name}`));
18403
+ console.log(chalk22.white(` - ${t.name}`));
18111
18404
  });
18112
18405
  }
18113
18406
  if (queues?.length) {
18114
- console.log(chalk21.cyan("\n Queues:"));
18407
+ console.log(chalk22.cyan("\n Queues:"));
18115
18408
  queues.forEach((q) => {
18116
- console.log(chalk21.white(` - ${q.name}`));
18409
+ console.log(chalk22.white(` - ${q.name}`));
18117
18410
  });
18118
18411
  }
18119
18412
  if (crons?.length) {
18120
- console.log(chalk21.cyan("\n Scheduled Jobs:"));
18413
+ console.log(chalk22.cyan("\n Scheduled Jobs:"));
18121
18414
  crons.forEach((c) => {
18122
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.schedule})`));
18415
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.schedule})`));
18123
18416
  });
18124
18417
  }
18125
18418
  const networks = config.project.networks || [];
18126
18419
  if (networks.length) {
18127
18420
  networks.forEach((network) => {
18128
- console.log(chalk21.cyan(`
18421
+ console.log(chalk22.cyan(`
18129
18422
  Network: ${network.name}`));
18130
18423
  if (network.subnets?.length) {
18131
- console.log(chalk21.gray(" Subnets:"));
18424
+ console.log(chalk22.gray(" Subnets:"));
18132
18425
  network.subnets.forEach((s) => {
18133
- console.log(chalk21.white(` - ${s.name}`) + chalk21.gray(` (${s.ipCidrRange})`));
18426
+ console.log(chalk22.white(` - ${s.name}`) + chalk22.gray(` (${s.ipCidrRange})`));
18134
18427
  });
18135
18428
  }
18136
18429
  if (network.containers?.length) {
18137
- console.log(chalk21.gray(" Containers:"));
18430
+ console.log(chalk22.gray(" Containers:"));
18138
18431
  network.containers.forEach((c) => {
18139
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memory || "256Mi"})`));
18432
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memory || "256Mi"})`));
18140
18433
  });
18141
18434
  }
18142
18435
  if (network.functions?.length) {
18143
- console.log(chalk21.gray(" Functions:"));
18436
+ console.log(chalk22.gray(" Functions:"));
18144
18437
  network.functions.forEach((f) => {
18145
- console.log(chalk21.white(` - ${f.name}`) + chalk21.gray(` (${f.runtime || "nodejs20"})`));
18438
+ console.log(chalk22.white(` - ${f.name}`) + chalk22.gray(` (${f.runtime || "nodejs20"})`));
18146
18439
  });
18147
18440
  }
18148
18441
  if (network.databases?.length) {
18149
- console.log(chalk21.gray(" Databases:"));
18442
+ console.log(chalk22.gray(" Databases:"));
18150
18443
  network.databases.forEach((d) => {
18151
- console.log(chalk21.white(` - ${d.name}`) + chalk21.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18444
+ console.log(chalk22.white(` - ${d.name}`) + chalk22.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18152
18445
  });
18153
18446
  }
18154
18447
  if (network.caches?.length) {
18155
- console.log(chalk21.gray(" Caches:"));
18448
+ console.log(chalk22.gray(" Caches:"));
18156
18449
  network.caches.forEach((c) => {
18157
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memorySizeGb || 1}GB)`));
18450
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memorySizeGb || 1}GB)`));
18158
18451
  });
18159
18452
  }
18160
18453
  });
@@ -18172,20 +18465,20 @@ var resourcesCommand = new Command20("resources").description("List all resource
18172
18465
  console.log(JSON.stringify(resolved.resources, null, 2));
18173
18466
  return;
18174
18467
  }
18175
- console.log(chalk21.bold("\n Resources to Create\n"));
18176
- console.log(chalk21.gray(` Order of creation (${order.length} resources):
18468
+ console.log(chalk22.bold("\n Resources to Create\n"));
18469
+ console.log(chalk22.gray(` Order of creation (${order.length} resources):
18177
18470
  `));
18178
18471
  order.forEach((id, index) => {
18179
18472
  const resource = resolved.resources.find((r) => r.id === id);
18180
18473
  if (!resource) return;
18181
- const deps = resource.dependsOn.length ? chalk21.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18474
+ const deps = resource.dependsOn.length ? chalk22.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18182
18475
  console.log(
18183
- chalk21.white(` ${String(index + 1).padStart(2)}. `) + chalk21.cyan(resource.type) + chalk21.white(` "${resource.name}"`) + deps
18476
+ chalk22.white(` ${String(index + 1).padStart(2)}. `) + chalk22.cyan(resource.type) + chalk22.white(` "${resource.name}"`) + deps
18184
18477
  );
18185
18478
  });
18186
18479
  console.log("");
18187
18480
  } catch (error) {
18188
- console.log(chalk21.red(`
18481
+ console.log(chalk22.red(`
18189
18482
  Error resolving config: ${error}
18190
18483
  `));
18191
18484
  }
@@ -18196,24 +18489,24 @@ var validateCommand = new Command20("validate").description("Validate the config
18196
18489
  if (!config) return;
18197
18490
  const result = validateConfig(config);
18198
18491
  if (result.valid) {
18199
- console.log(chalk21.green("\n \u2713 Configuration is valid\n"));
18492
+ console.log(chalk22.green("\n \u2713 Configuration is valid\n"));
18200
18493
  try {
18201
18494
  const resolved = resolveConfig(config);
18202
18495
  const order = topologicalSort(resolved.resources);
18203
- console.log(chalk21.gray(` ${resolved.resources.length} resources defined`));
18204
- console.log(chalk21.gray(` No circular dependencies detected
18496
+ console.log(chalk22.gray(` ${resolved.resources.length} resources defined`));
18497
+ console.log(chalk22.gray(` No circular dependencies detected
18205
18498
  `));
18206
18499
  } catch (error) {
18207
- console.log(chalk21.yellow(`
18500
+ console.log(chalk22.yellow(`
18208
18501
  \u26A0 Warning: ${error}
18209
18502
  `));
18210
18503
  }
18211
18504
  } else {
18212
- console.log(chalk21.red("\n \u2717 Configuration has errors:\n"));
18505
+ console.log(chalk22.red("\n \u2717 Configuration has errors:\n"));
18213
18506
  result.errors.forEach((err) => {
18214
- console.log(chalk21.red(` - ${err.path}: ${err.message}`));
18507
+ console.log(chalk22.red(` - ${err.path}: ${err.message}`));
18215
18508
  if (err.value !== void 0) {
18216
- console.log(chalk21.gray(` value: ${JSON.stringify(err.value)}`));
18509
+ console.log(chalk22.gray(` value: ${JSON.stringify(err.value)}`));
18217
18510
  }
18218
18511
  });
18219
18512
  console.log("");
@@ -18224,7 +18517,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18224
18517
  const configPath = getConfigPath6();
18225
18518
  const config = await loadConfig3(configPath);
18226
18519
  if (!config) return;
18227
- console.log(chalk21.bold("\n Resource References\n"));
18520
+ console.log(chalk22.bold("\n Resource References\n"));
18228
18521
  const allReferences = [];
18229
18522
  const networks = config.project.networks || [];
18230
18523
  networks.forEach((network) => {
@@ -18258,7 +18551,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18258
18551
  });
18259
18552
  });
18260
18553
  if (allReferences.length === 0) {
18261
- console.log(chalk21.gray(" No references found.\n"));
18554
+ console.log(chalk22.gray(" No references found.\n"));
18262
18555
  return;
18263
18556
  }
18264
18557
  const byType = /* @__PURE__ */ new Map();
@@ -18270,11 +18563,11 @@ var referencesCommand = new Command20("references").description("Show all resour
18270
18563
  byType.get(type).push(ref);
18271
18564
  });
18272
18565
  byType.forEach((refs, type) => {
18273
- console.log(chalk21.cyan(` @${type}:`));
18566
+ console.log(chalk22.cyan(` @${type}:`));
18274
18567
  refs.forEach((ref) => {
18275
18568
  const property = ref.parsed?.property ? `.${ref.parsed.property}` : "";
18276
18569
  console.log(
18277
- chalk21.white(` ${ref.reference}`) + chalk21.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18570
+ chalk22.white(` ${ref.reference}`) + chalk22.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18278
18571
  );
18279
18572
  });
18280
18573
  console.log("");
@@ -18287,47 +18580,47 @@ configCommand.action(() => {
18287
18580
 
18288
18581
  // src/commands/config/env.ts
18289
18582
  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";
18583
+ import chalk23 from "chalk";
18584
+ import ora11 from "ora";
18585
+ import * as fs22 from "fs/promises";
18586
+ import * as path26 from "path";
18294
18587
  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
18588
  const cwd = process.cwd();
18296
- console.log(chalk22.bold("\n StackSolo Env\n"));
18297
- const configPath = path25.join(cwd, ".stacksolo", "config.json");
18589
+ console.log(chalk23.bold("\n StackSolo Env\n"));
18590
+ const configPath = path26.join(cwd, ".stacksolo", "config.json");
18298
18591
  let config;
18299
18592
  try {
18300
- const configData = await fs21.readFile(configPath, "utf-8");
18593
+ const configData = await fs22.readFile(configPath, "utf-8");
18301
18594
  config = JSON.parse(configData);
18302
18595
  } catch {
18303
- console.log(chalk22.red(" Not initialized. Run `stacksolo init` first.\n"));
18596
+ console.log(chalk23.red(" Not initialized. Run `stacksolo init` first.\n"));
18304
18597
  return;
18305
18598
  }
18306
18599
  const apiConnected = await checkApiConnection();
18307
18600
  if (!apiConnected) {
18308
- console.log(chalk22.red(" StackSolo API not running."));
18309
- console.log(chalk22.gray(" Start with: stacksolo serve\n"));
18601
+ console.log(chalk23.red(" StackSolo API not running."));
18602
+ console.log(chalk23.gray(" Start with: stacksolo serve\n"));
18310
18603
  return;
18311
18604
  }
18312
- const spinner = ora10("Checking deployment...").start();
18605
+ const spinner = ora11("Checking deployment...").start();
18313
18606
  const statusResult = await api.deployments.status(config.projectId);
18314
18607
  if (!statusResult.success || !statusResult.data) {
18315
18608
  spinner.fail("Could not get deployment status");
18316
- console.log(chalk22.gray("\n Deploy first with: stacksolo deploy\n"));
18609
+ console.log(chalk23.gray("\n Deploy first with: stacksolo deploy\n"));
18317
18610
  return;
18318
18611
  }
18319
18612
  if (statusResult.data.status !== "succeeded") {
18320
18613
  spinner.fail("No successful deployment");
18321
- console.log(chalk22.gray(`
18614
+ console.log(chalk23.gray(`
18322
18615
  Current status: ${statusResult.data.status}`));
18323
- console.log(chalk22.gray(" Deploy first with: stacksolo deploy\n"));
18616
+ console.log(chalk23.gray(" Deploy first with: stacksolo deploy\n"));
18324
18617
  return;
18325
18618
  }
18326
18619
  spinner.text = "Generating configuration...";
18327
18620
  const configResult = await api.deployments.generateConfig(config.projectId);
18328
18621
  if (!configResult.success || !configResult.data) {
18329
18622
  spinner.fail("Failed to generate config");
18330
- console.log(chalk22.red(` ${configResult.error}
18623
+ console.log(chalk23.red(` ${configResult.error}
18331
18624
  `));
18332
18625
  return;
18333
18626
  }
@@ -18336,38 +18629,38 @@ var envCommand = new Command21("env").description("Generate environment configur
18336
18629
  const tsConfigPath = configResult.data.configPath;
18337
18630
  if (options.stdout) {
18338
18631
  try {
18339
- console.log(chalk22.gray("\n .env.local:"));
18340
- const envContent = await fs21.readFile(envPath, "utf-8");
18632
+ console.log(chalk23.gray("\n .env.local:"));
18633
+ const envContent = await fs22.readFile(envPath, "utf-8");
18341
18634
  console.log(envContent);
18342
- console.log(chalk22.gray("\n stacksolo.config.ts:"));
18343
- const tsContent = await fs21.readFile(tsConfigPath, "utf-8");
18635
+ console.log(chalk23.gray("\n stacksolo.config.ts:"));
18636
+ const tsContent = await fs22.readFile(tsConfigPath, "utf-8");
18344
18637
  console.log(tsContent);
18345
18638
  } catch (error) {
18346
- console.log(chalk22.red(` Error reading files: ${error}
18639
+ console.log(chalk23.red(` Error reading files: ${error}
18347
18640
  `));
18348
18641
  }
18349
18642
  } else {
18350
- console.log(chalk22.green("\n Files generated:"));
18351
- console.log(chalk22.gray(` ${envPath}`));
18352
- console.log(chalk22.gray(` ${tsConfigPath}`));
18643
+ console.log(chalk23.green("\n Files generated:"));
18644
+ console.log(chalk23.gray(` ${envPath}`));
18645
+ console.log(chalk23.gray(` ${tsConfigPath}`));
18353
18646
  console.log("");
18354
- console.log(chalk22.gray(" Add to .gitignore:"));
18355
- console.log(chalk22.gray(" .env.local"));
18647
+ console.log(chalk23.gray(" Add to .gitignore:"));
18648
+ console.log(chalk23.gray(" .env.local"));
18356
18649
  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"));
18650
+ console.log(chalk23.gray(" Usage in your app:"));
18651
+ console.log(chalk23.gray(" import { config } from './stacksolo.config';"));
18652
+ console.log(chalk23.gray(" const dbUrl = config.database?.url;\n"));
18360
18653
  }
18361
18654
  });
18362
18655
 
18363
18656
  // src/commands/config/register.ts
18364
18657
  import { Command as Command22 } from "commander";
18365
- import chalk23 from "chalk";
18366
- import * as path26 from "path";
18658
+ import chalk24 from "chalk";
18659
+ import * as path27 from "path";
18367
18660
  var STACKSOLO_DIR12 = ".stacksolo";
18368
18661
  var CONFIG_FILENAME10 = "stacksolo.config.json";
18369
18662
  function getConfigPath7() {
18370
- return path26.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18663
+ return path27.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18371
18664
  }
18372
18665
  var registerCommand = new Command22("register").description("Register the current project in the global registry").option("-f, --force", "Overwrite existing registration").action(async (options) => {
18373
18666
  const configPath = getConfigPath7();
@@ -18375,11 +18668,11 @@ var registerCommand = new Command22("register").description("Register the curren
18375
18668
  try {
18376
18669
  config = parseConfig(configPath);
18377
18670
  } catch (error) {
18378
- console.log(chalk23.red(`
18671
+ console.log(chalk24.red(`
18379
18672
  Error: Could not read ${STACKSOLO_DIR12}/${CONFIG_FILENAME10}
18380
18673
  `));
18381
- console.log(chalk23.gray(` ${error}`));
18382
- console.log(chalk23.gray(`
18674
+ console.log(chalk24.gray(` ${error}`));
18675
+ console.log(chalk24.gray(`
18383
18676
  Run 'stacksolo init' to create a project first.
18384
18677
  `));
18385
18678
  process.exit(1);
@@ -18389,21 +18682,21 @@ var registerCommand = new Command22("register").description("Register the curren
18389
18682
  if (existingByPath) {
18390
18683
  if (options.force) {
18391
18684
  await registry3.unregisterProject(existingByPath.id);
18392
- console.log(chalk23.yellow(` Updating registration for "${existingByPath.name}"...`));
18685
+ console.log(chalk24.yellow(` Updating registration for "${existingByPath.name}"...`));
18393
18686
  } else {
18394
- console.log(chalk23.yellow(`
18687
+ console.log(chalk24.yellow(`
18395
18688
  Project already registered as "${existingByPath.name}"`));
18396
- console.log(chalk23.gray(` Use --force to update the registration.
18689
+ console.log(chalk24.gray(` Use --force to update the registration.
18397
18690
  `));
18398
18691
  return;
18399
18692
  }
18400
18693
  }
18401
18694
  const existingByName = await registry3.findProjectByName(config.project.name);
18402
18695
  if (existingByName && existingByName.configPath !== configPath) {
18403
- console.log(chalk23.red(`
18696
+ console.log(chalk24.red(`
18404
18697
  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
18698
+ console.log(chalk24.gray(` Registered path: ${existingByName.configPath}`));
18699
+ console.log(chalk24.gray(` Choose a different project name in stacksolo.config.json
18407
18700
  `));
18408
18701
  process.exit(1);
18409
18702
  }
@@ -18413,42 +18706,42 @@ var registerCommand = new Command22("register").description("Register the curren
18413
18706
  region: config.project.region,
18414
18707
  configPath
18415
18708
  });
18416
- console.log(chalk23.green(`
18709
+ console.log(chalk24.green(`
18417
18710
  \u2713 Project registered: ${project.name}
18418
18711
  `));
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}`));
18712
+ console.log(chalk24.gray(` GCP Project: ${project.gcpProjectId}`));
18713
+ console.log(chalk24.gray(` Region: ${project.region}`));
18714
+ console.log(chalk24.gray(` Config: ${project.configPath}`));
18422
18715
  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"));
18716
+ console.log(chalk24.cyan(" Next steps:"));
18717
+ console.log(chalk24.gray(" stacksolo list # View all registered projects"));
18718
+ console.log(chalk24.gray(" stacksolo deploy # Deploy the project"));
18426
18719
  console.log("");
18427
18720
  });
18428
18721
 
18429
18722
  // src/commands/config/unregister.ts
18430
18723
  import { Command as Command23 } from "commander";
18431
- import chalk24 from "chalk";
18724
+ import chalk25 from "chalk";
18432
18725
  import inquirer5 from "inquirer";
18433
18726
  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
18727
  const registry3 = getRegistry();
18435
18728
  const project = await registry3.findProjectByName(projectName);
18436
18729
  if (!project) {
18437
- console.log(chalk24.red(`
18730
+ console.log(chalk25.red(`
18438
18731
  Error: Project "${projectName}" not found.
18439
18732
  `));
18440
- console.log(chalk24.gray(" Run `stacksolo list` to see registered projects.\n"));
18733
+ console.log(chalk25.gray(" Run `stacksolo list` to see registered projects.\n"));
18441
18734
  process.exit(1);
18442
18735
  }
18443
18736
  const resources = await registry3.findResourcesByProject(project.id);
18444
18737
  if (!options.yes) {
18445
- console.log(chalk24.yellow(`
18738
+ console.log(chalk25.yellow(`
18446
18739
  Warning: This will remove "${projectName}" from the registry.`));
18447
18740
  if (resources.length > 0) {
18448
- console.log(chalk24.yellow(` This project has ${resources.length} registered resource(s).`));
18741
+ console.log(chalk25.yellow(` This project has ${resources.length} registered resource(s).`));
18449
18742
  }
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"));
18743
+ console.log(chalk25.gray("\n Note: This does NOT destroy deployed cloud resources."));
18744
+ console.log(chalk25.gray(" To destroy resources, run `stacksolo destroy` first.\n"));
18452
18745
  const { confirm } = await inquirer5.prompt([
18453
18746
  {
18454
18747
  type: "confirm",
@@ -18458,17 +18751,17 @@ var unregisterCommand = new Command23("unregister").description("Remove a projec
18458
18751
  }
18459
18752
  ]);
18460
18753
  if (!confirm) {
18461
- console.log(chalk24.gray("\n Cancelled.\n"));
18754
+ console.log(chalk25.gray("\n Cancelled.\n"));
18462
18755
  return;
18463
18756
  }
18464
18757
  }
18465
18758
  await registry3.unregisterProject(project.id);
18466
- console.log(chalk24.green(`
18759
+ console.log(chalk25.green(`
18467
18760
  \u2713 Project "${projectName}" removed from registry.
18468
18761
  `));
18469
18762
  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"));
18763
+ console.log(chalk25.gray(` Config file still exists at: ${project.configPath}`));
18764
+ console.log(chalk25.gray(" Run `stacksolo register` to re-register this project.\n"));
18472
18765
  }
18473
18766
  });
18474
18767