@stacksolo/cli 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -877,25 +877,28 @@ async function scaffoldTemplates(cwd, projectType, uiFramework) {
877
877
  }
878
878
  async function scaffoldFunctionApi(cwd, name) {
879
879
  const dir = path.join(cwd, name);
880
- await fs.mkdir(dir, { recursive: true });
880
+ const srcDir = path.join(dir, "src");
881
+ await fs.mkdir(srcDir, { recursive: true });
881
882
  await fs.writeFile(
882
883
  path.join(dir, "package.json"),
883
884
  JSON.stringify(
884
885
  {
885
886
  name,
886
887
  version: "1.0.0",
887
- main: "index.js",
888
+ main: "dist/index.js",
888
889
  type: "module",
889
890
  scripts: {
890
- start: "npx functions-framework --target=handler",
891
- build: "tsc"
891
+ dev: "tsup src/index.ts --format esm --target node20 --watch --onSuccess 'functions-framework --source=dist --target=handler'",
892
+ build: "tsup src/index.ts --format esm --target node20",
893
+ start: "functions-framework --source=dist --target=handler"
892
894
  },
893
895
  dependencies: {
894
896
  "@google-cloud/functions-framework": "^3.3.0"
895
897
  },
896
898
  devDependencies: {
897
899
  typescript: "^5.0.0",
898
- "@types/node": "^20.0.0"
900
+ "@types/node": "^20.0.0",
901
+ tsup: "^8.0.0"
899
902
  }
900
903
  },
901
904
  null,
@@ -903,15 +906,21 @@ async function scaffoldFunctionApi(cwd, name) {
903
906
  ) + "\n"
904
907
  );
905
908
  await fs.writeFile(
906
- path.join(dir, "index.ts"),
907
- `import functions from '@google-cloud/functions-framework';
909
+ path.join(srcDir, "index.ts"),
910
+ `/**
911
+ * ${name} HTTP function
912
+ * Generated by: stacksolo init
913
+ */
914
+
915
+ import type { Request, Response } from '@google-cloud/functions-framework';
908
916
 
909
- functions.http('handler', (req, res) => {
917
+ export function handler(req: Request, res: Response): void {
910
918
  const { method, path } = req;
911
919
 
912
920
  // Health check
913
921
  if (path === '/health') {
914
- return res.json({ status: 'ok' });
922
+ res.json({ status: 'ok' });
923
+ return;
915
924
  }
916
925
 
917
926
  // Your API routes here
@@ -920,7 +929,7 @@ functions.http('handler', (req, res) => {
920
929
  method,
921
930
  path,
922
931
  });
923
- });
932
+ }
924
933
  `
925
934
  );
926
935
  await fs.writeFile(
@@ -930,19 +939,31 @@ functions.http('handler', (req, res) => {
930
939
  compilerOptions: {
931
940
  target: "ES2022",
932
941
  module: "ESNext",
933
- moduleResolution: "node",
942
+ moduleResolution: "bundler",
934
943
  esModuleInterop: true,
935
944
  strict: true,
945
+ skipLibCheck: true,
936
946
  outDir: "dist",
947
+ rootDir: "src",
937
948
  declaration: true
938
949
  },
939
- include: ["*.ts"]
950
+ include: ["src/**/*"],
951
+ exclude: ["node_modules", "dist"]
940
952
  },
941
953
  null,
942
954
  2
943
955
  ) + "\n"
944
956
  );
945
- return [`${name}/package.json`, `${name}/index.ts`, `${name}/tsconfig.json`];
957
+ await fs.writeFile(
958
+ path.join(dir, ".gitignore"),
959
+ `node_modules/
960
+ dist/
961
+ *.log
962
+ .env
963
+ .env.local
964
+ `
965
+ );
966
+ return [`${name}/package.json`, `${name}/src/index.ts`, `${name}/tsconfig.json`, `${name}/.gitignore`];
946
967
  }
947
968
  async function scaffoldContainerApi(cwd, name) {
948
969
  const dir = path.join(cwd, name);
@@ -1027,25 +1048,28 @@ CMD ["node", "index.js"]
1027
1048
  }
1028
1049
  async function scaffoldFunctionCron(cwd, name) {
1029
1050
  const dir = path.join(cwd, name);
1030
- await fs.mkdir(dir, { recursive: true });
1051
+ const srcDir = path.join(dir, "src");
1052
+ await fs.mkdir(srcDir, { recursive: true });
1031
1053
  await fs.writeFile(
1032
1054
  path.join(dir, "package.json"),
1033
1055
  JSON.stringify(
1034
1056
  {
1035
1057
  name,
1036
1058
  version: "1.0.0",
1037
- main: "index.js",
1059
+ main: "dist/index.js",
1038
1060
  type: "module",
1039
1061
  scripts: {
1040
- start: "npx functions-framework --target=handler",
1041
- build: "tsc"
1062
+ dev: "tsup src/index.ts --format esm --target node20 --watch --onSuccess 'functions-framework --source=dist --target=handler'",
1063
+ build: "tsup src/index.ts --format esm --target node20",
1064
+ start: "functions-framework --source=dist --target=handler"
1042
1065
  },
1043
1066
  dependencies: {
1044
1067
  "@google-cloud/functions-framework": "^3.3.0"
1045
1068
  },
1046
1069
  devDependencies: {
1047
1070
  typescript: "^5.0.0",
1048
- "@types/node": "^20.0.0"
1071
+ "@types/node": "^20.0.0",
1072
+ tsup: "^8.0.0"
1049
1073
  }
1050
1074
  },
1051
1075
  null,
@@ -1053,17 +1077,22 @@ async function scaffoldFunctionCron(cwd, name) {
1053
1077
  ) + "\n"
1054
1078
  );
1055
1079
  await fs.writeFile(
1056
- path.join(dir, "index.ts"),
1057
- `import functions from '@google-cloud/functions-framework';
1080
+ path.join(srcDir, "index.ts"),
1081
+ `/**
1082
+ * ${name} Cron function
1083
+ * Generated by: stacksolo init
1084
+ */
1085
+
1086
+ import type { Request, Response } from '@google-cloud/functions-framework';
1058
1087
 
1059
- functions.http('handler', (req, res) => {
1088
+ export function handler(req: Request, res: Response): void {
1060
1089
  console.log('Cron triggered:', new Date().toISOString());
1061
1090
 
1062
1091
  // Your scheduled job logic here
1063
1092
 
1064
1093
  console.log('Job complete');
1065
1094
  res.json({ status: 'ok' });
1066
- });
1095
+ }
1067
1096
  `
1068
1097
  );
1069
1098
  await fs.writeFile(
@@ -1073,19 +1102,31 @@ functions.http('handler', (req, res) => {
1073
1102
  compilerOptions: {
1074
1103
  target: "ES2022",
1075
1104
  module: "ESNext",
1076
- moduleResolution: "node",
1105
+ moduleResolution: "bundler",
1077
1106
  esModuleInterop: true,
1078
1107
  strict: true,
1108
+ skipLibCheck: true,
1079
1109
  outDir: "dist",
1110
+ rootDir: "src",
1080
1111
  declaration: true
1081
1112
  },
1082
- include: ["*.ts"]
1113
+ include: ["src/**/*"],
1114
+ exclude: ["node_modules", "dist"]
1083
1115
  },
1084
1116
  null,
1085
1117
  2
1086
1118
  ) + "\n"
1087
1119
  );
1088
- return [`${name}/package.json`, `${name}/index.ts`, `${name}/tsconfig.json`];
1120
+ await fs.writeFile(
1121
+ path.join(dir, ".gitignore"),
1122
+ `node_modules/
1123
+ dist/
1124
+ *.log
1125
+ .env
1126
+ .env.local
1127
+ `
1128
+ );
1129
+ return [`${name}/package.json`, `${name}/src/index.ts`, `${name}/tsconfig.json`, `${name}/.gitignore`];
1089
1130
  }
1090
1131
  async function scaffoldStaticWeb(cwd, name) {
1091
1132
  const dir = path.join(cwd, name);
@@ -4131,132 +4172,132 @@ function validateProject(project, errors) {
4131
4172
  }
4132
4173
  checkDuplicateNames(project, errors);
4133
4174
  }
4134
- function validateBucket(bucket, path27, errors) {
4175
+ function validateBucket(bucket, path28, errors) {
4135
4176
  if (!bucket.name) {
4136
- errors.push({ path: `${path27}.name`, message: "name is required" });
4177
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4137
4178
  } else if (!isValidBucketName(bucket.name)) {
4138
4179
  errors.push({
4139
- path: `${path27}.name`,
4180
+ path: `${path28}.name`,
4140
4181
  message: "bucket name must be 3-63 chars, lowercase alphanumeric with hyphens/underscores",
4141
4182
  value: bucket.name
4142
4183
  });
4143
4184
  }
4144
4185
  if (bucket.storageClass && !["STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE"].includes(bucket.storageClass)) {
4145
4186
  errors.push({
4146
- path: `${path27}.storageClass`,
4187
+ path: `${path28}.storageClass`,
4147
4188
  message: "storageClass must be STANDARD, NEARLINE, COLDLINE, or ARCHIVE",
4148
4189
  value: bucket.storageClass
4149
4190
  });
4150
4191
  }
4151
4192
  }
4152
- function validateSecret(secret, path27, errors) {
4193
+ function validateSecret(secret, path28, errors) {
4153
4194
  if (!secret.name) {
4154
- errors.push({ path: `${path27}.name`, message: "name is required" });
4195
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4155
4196
  } else if (!isValidResourceName(secret.name)) {
4156
4197
  errors.push({
4157
- path: `${path27}.name`,
4198
+ path: `${path28}.name`,
4158
4199
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4159
4200
  value: secret.name
4160
4201
  });
4161
4202
  }
4162
4203
  }
4163
- function validateTopic(topic, path27, errors) {
4204
+ function validateTopic(topic, path28, errors) {
4164
4205
  if (!topic.name) {
4165
- errors.push({ path: `${path27}.name`, message: "name is required" });
4206
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4166
4207
  } else if (!isValidResourceName(topic.name)) {
4167
4208
  errors.push({
4168
- path: `${path27}.name`,
4209
+ path: `${path28}.name`,
4169
4210
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4170
4211
  value: topic.name
4171
4212
  });
4172
4213
  }
4173
4214
  }
4174
- function validateQueue(queue, path27, errors) {
4215
+ function validateQueue(queue, path28, errors) {
4175
4216
  if (!queue.name) {
4176
- errors.push({ path: `${path27}.name`, message: "name is required" });
4217
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4177
4218
  } else if (!isValidResourceName(queue.name)) {
4178
4219
  errors.push({
4179
- path: `${path27}.name`,
4220
+ path: `${path28}.name`,
4180
4221
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4181
4222
  value: queue.name
4182
4223
  });
4183
4224
  }
4184
4225
  }
4185
- function validateCron(cron, path27, errors) {
4226
+ function validateCron(cron, path28, errors) {
4186
4227
  if (!cron.name) {
4187
- errors.push({ path: `${path27}.name`, message: "name is required" });
4228
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4188
4229
  }
4189
4230
  if (!cron.schedule) {
4190
- errors.push({ path: `${path27}.schedule`, message: "schedule is required" });
4231
+ errors.push({ path: `${path28}.schedule`, message: "schedule is required" });
4191
4232
  } else if (!isValidCronExpression(cron.schedule)) {
4192
4233
  errors.push({
4193
- path: `${path27}.schedule`,
4234
+ path: `${path28}.schedule`,
4194
4235
  message: "schedule must be a valid cron expression",
4195
4236
  value: cron.schedule
4196
4237
  });
4197
4238
  }
4198
4239
  if (!cron.target) {
4199
- errors.push({ path: `${path27}.target`, message: "target is required" });
4240
+ errors.push({ path: `${path28}.target`, message: "target is required" });
4200
4241
  }
4201
4242
  }
4202
- function validateNetwork(network, path27, errors) {
4243
+ function validateNetwork(network, path28, errors) {
4203
4244
  if (!network.name) {
4204
- errors.push({ path: `${path27}.name`, message: "name is required" });
4245
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4205
4246
  } else if (!isValidResourceName(network.name)) {
4206
4247
  errors.push({
4207
- path: `${path27}.name`,
4248
+ path: `${path28}.name`,
4208
4249
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4209
4250
  value: network.name
4210
4251
  });
4211
4252
  }
4212
4253
  if (network.containers) {
4213
4254
  network.containers.forEach((container, i) => {
4214
- validateContainer(container, `${path27}.containers[${i}]`, errors);
4255
+ validateContainer(container, `${path28}.containers[${i}]`, errors);
4215
4256
  });
4216
4257
  }
4217
4258
  if (network.functions) {
4218
4259
  network.functions.forEach((fn, i) => {
4219
- validateFunction(fn, `${path27}.functions[${i}]`, errors);
4260
+ validateFunction(fn, `${path28}.functions[${i}]`, errors);
4220
4261
  });
4221
4262
  }
4222
4263
  if (network.databases) {
4223
4264
  network.databases.forEach((db, i) => {
4224
- validateDatabase(db, `${path27}.databases[${i}]`, errors);
4265
+ validateDatabase(db, `${path28}.databases[${i}]`, errors);
4225
4266
  });
4226
4267
  }
4227
4268
  if (network.caches) {
4228
4269
  network.caches.forEach((cache2, i) => {
4229
- validateCache(cache2, `${path27}.caches[${i}]`, errors);
4270
+ validateCache(cache2, `${path28}.caches[${i}]`, errors);
4230
4271
  });
4231
4272
  }
4232
4273
  }
4233
- function validateContainer(container, path27, errors) {
4274
+ function validateContainer(container, path28, errors) {
4234
4275
  if (!container.name) {
4235
- errors.push({ path: `${path27}.name`, message: "name is required" });
4276
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4236
4277
  } else if (!isValidResourceName(container.name)) {
4237
4278
  errors.push({
4238
- path: `${path27}.name`,
4279
+ path: `${path28}.name`,
4239
4280
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4240
4281
  value: container.name
4241
4282
  });
4242
4283
  }
4243
4284
  if (container.memory && !isValidMemoryFormat(container.memory)) {
4244
4285
  errors.push({
4245
- path: `${path27}.memory`,
4286
+ path: `${path28}.memory`,
4246
4287
  message: "memory must be in format like 256Mi, 1Gi, 2Gi",
4247
4288
  value: container.memory
4248
4289
  });
4249
4290
  }
4250
4291
  if (container.env) {
4251
- validateEnvReferences(container.env, `${path27}.env`, errors);
4292
+ validateEnvReferences(container.env, `${path28}.env`, errors);
4252
4293
  }
4253
4294
  }
4254
- function validateFunction(fn, path27, errors) {
4295
+ function validateFunction(fn, path28, errors) {
4255
4296
  if (!fn.name) {
4256
- errors.push({ path: `${path27}.name`, message: "name is required" });
4297
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4257
4298
  } else if (!isValidResourceName(fn.name)) {
4258
4299
  errors.push({
4259
- path: `${path27}.name`,
4300
+ path: `${path28}.name`,
4260
4301
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4261
4302
  value: fn.name
4262
4303
  });
@@ -4264,21 +4305,21 @@ function validateFunction(fn, path27, errors) {
4264
4305
  const validRuntimes = ["nodejs20", "nodejs18", "python311", "python310", "go121", "go120"];
4265
4306
  if (fn.runtime && !validRuntimes.includes(fn.runtime)) {
4266
4307
  errors.push({
4267
- path: `${path27}.runtime`,
4308
+ path: `${path28}.runtime`,
4268
4309
  message: `runtime must be one of: ${validRuntimes.join(", ")}`,
4269
4310
  value: fn.runtime
4270
4311
  });
4271
4312
  }
4272
4313
  if (fn.env) {
4273
- validateEnvReferences(fn.env, `${path27}.env`, errors);
4314
+ validateEnvReferences(fn.env, `${path28}.env`, errors);
4274
4315
  }
4275
4316
  }
4276
- function validateDatabase(db, path27, errors) {
4317
+ function validateDatabase(db, path28, errors) {
4277
4318
  if (!db.name) {
4278
- errors.push({ path: `${path27}.name`, message: "name is required" });
4319
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4279
4320
  } else if (!isValidResourceName(db.name)) {
4280
4321
  errors.push({
4281
- path: `${path27}.name`,
4322
+ path: `${path28}.name`,
4282
4323
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4283
4324
  value: db.name
4284
4325
  });
@@ -4286,36 +4327,36 @@ function validateDatabase(db, path27, errors) {
4286
4327
  const validVersions = ["POSTGRES_15", "POSTGRES_14", "MYSQL_8_0", "MYSQL_5_7"];
4287
4328
  if (db.databaseVersion && !validVersions.includes(db.databaseVersion)) {
4288
4329
  errors.push({
4289
- path: `${path27}.databaseVersion`,
4330
+ path: `${path28}.databaseVersion`,
4290
4331
  message: `databaseVersion must be one of: ${validVersions.join(", ")}`,
4291
4332
  value: db.databaseVersion
4292
4333
  });
4293
4334
  }
4294
4335
  }
4295
- function validateCache(cache2, path27, errors) {
4336
+ function validateCache(cache2, path28, errors) {
4296
4337
  if (!cache2.name) {
4297
- errors.push({ path: `${path27}.name`, message: "name is required" });
4338
+ errors.push({ path: `${path28}.name`, message: "name is required" });
4298
4339
  } else if (!isValidResourceName(cache2.name)) {
4299
4340
  errors.push({
4300
- path: `${path27}.name`,
4341
+ path: `${path28}.name`,
4301
4342
  message: "name must be lowercase alphanumeric with hyphens, 1-63 chars",
4302
4343
  value: cache2.name
4303
4344
  });
4304
4345
  }
4305
4346
  if (cache2.tier && !["BASIC", "STANDARD_HA"].includes(cache2.tier)) {
4306
4347
  errors.push({
4307
- path: `${path27}.tier`,
4348
+ path: `${path28}.tier`,
4308
4349
  message: "tier must be BASIC or STANDARD_HA",
4309
4350
  value: cache2.tier
4310
4351
  });
4311
4352
  }
4312
4353
  }
4313
- function validateEnvReferences(env, path27, errors) {
4354
+ function validateEnvReferences(env, path28, errors) {
4314
4355
  for (const [key, value] of Object.entries(env)) {
4315
4356
  if (value.startsWith("@")) {
4316
4357
  if (!isValidReference(value)) {
4317
4358
  errors.push({
4318
- path: `${path27}.${key}`,
4359
+ path: `${path28}.${key}`,
4319
4360
  message: "invalid reference format. Expected @type/name or @type/name.property",
4320
4361
  value
4321
4362
  });
@@ -4325,15 +4366,15 @@ function validateEnvReferences(env, path27, errors) {
4325
4366
  }
4326
4367
  function checkDuplicateNames(project, errors) {
4327
4368
  const seen = /* @__PURE__ */ new Map();
4328
- const check = (name, path27) => {
4369
+ const check = (name, path28) => {
4329
4370
  if (seen.has(name)) {
4330
4371
  errors.push({
4331
- path: path27,
4372
+ path: path28,
4332
4373
  message: `duplicate resource name "${name}" (also defined at ${seen.get(name)})`,
4333
4374
  value: name
4334
4375
  });
4335
4376
  } else {
4336
- seen.set(name, path27);
4377
+ seen.set(name, path28);
4337
4378
  }
4338
4379
  };
4339
4380
  project.buckets?.forEach((b, i) => check(b.name, `project.buckets[${i}]`));
@@ -4343,15 +4384,15 @@ function checkDuplicateNames(project, errors) {
4343
4384
  project.crons?.forEach((c, i) => check(c.name, `project.crons[${i}]`));
4344
4385
  project.networks?.forEach((network, ni) => {
4345
4386
  const networkSeen = /* @__PURE__ */ new Map();
4346
- const checkNetwork = (name, path27) => {
4387
+ const checkNetwork = (name, path28) => {
4347
4388
  if (networkSeen.has(name)) {
4348
4389
  errors.push({
4349
- path: path27,
4390
+ path: path28,
4350
4391
  message: `duplicate resource name "${name}" in network "${network.name}" (also at ${networkSeen.get(name)})`,
4351
4392
  value: name
4352
4393
  });
4353
4394
  } else {
4354
- networkSeen.set(name, path27);
4395
+ networkSeen.set(name, path28);
4355
4396
  }
4356
4397
  };
4357
4398
  network.containers?.forEach((c, i) => checkNetwork(c.name, `project.networks[${ni}].containers[${i}]`));
@@ -5528,11 +5569,11 @@ function buildDependencyGraph(resources) {
5528
5569
  function detectCycles(graph) {
5529
5570
  const visited = /* @__PURE__ */ new Set();
5530
5571
  const recursionStack = /* @__PURE__ */ new Set();
5531
- const path27 = [];
5572
+ const path28 = [];
5532
5573
  function dfs(nodeId) {
5533
5574
  visited.add(nodeId);
5534
5575
  recursionStack.add(nodeId);
5535
- path27.push(nodeId);
5576
+ path28.push(nodeId);
5536
5577
  const node = graph.get(nodeId);
5537
5578
  if (node) {
5538
5579
  for (const depId of node.dependencies) {
@@ -5540,20 +5581,20 @@ function detectCycles(graph) {
5540
5581
  if (dfs(depId))
5541
5582
  return true;
5542
5583
  } else if (recursionStack.has(depId)) {
5543
- const cycleStart = path27.indexOf(depId);
5544
- path27.push(depId);
5584
+ const cycleStart = path28.indexOf(depId);
5585
+ path28.push(depId);
5545
5586
  return true;
5546
5587
  }
5547
5588
  }
5548
5589
  }
5549
- path27.pop();
5590
+ path28.pop();
5550
5591
  recursionStack.delete(nodeId);
5551
5592
  return false;
5552
5593
  }
5553
5594
  for (const nodeId of graph.keys()) {
5554
5595
  if (!visited.has(nodeId)) {
5555
5596
  if (dfs(nodeId)) {
5556
- return path27;
5597
+ return path28;
5557
5598
  }
5558
5599
  }
5559
5600
  }
@@ -5608,18 +5649,18 @@ function prefixBucketName(projectName, bucketName) {
5608
5649
  const truncated = bucketName.slice(0, remaining);
5609
5650
  return `${truncated}-${hash}`;
5610
5651
  }
5611
- function prefixRoutePath(projectName, path27) {
5612
- if (path27 === "/*" || path27 === "/") {
5652
+ function prefixRoutePath(projectName, path28) {
5653
+ if (path28 === "/*" || path28 === "/") {
5613
5654
  return `/${projectName}/*`;
5614
5655
  }
5615
- const normalized = path27.startsWith("/") ? path27 : `/${path27}`;
5656
+ const normalized = path28.startsWith("/") ? path28 : `/${path28}`;
5616
5657
  return `/${projectName}${normalized}`;
5617
5658
  }
5618
5659
  function relativeSourceDir(sourceProjectPath, sourceDir, outputDir) {
5619
- const path27 = __require("path");
5620
- const relativeToSource = path27.relative(outputDir, sourceProjectPath);
5660
+ const path28 = __require("path");
5661
+ const relativeToSource = path28.relative(outputDir, sourceProjectPath);
5621
5662
  const normalized = sourceDir.startsWith("./") ? sourceDir.slice(2) : sourceDir;
5622
- return path27.join(relativeToSource, normalized);
5663
+ return path28.join(relativeToSource, normalized);
5623
5664
  }
5624
5665
  function simpleHash(str) {
5625
5666
  let hash = 0;
@@ -6101,9 +6142,9 @@ function validateMergedConfig(config) {
6101
6142
  const warnings = [];
6102
6143
  const baseResult = validateConfig(config);
6103
6144
  const project = config.project;
6104
- const checkNameLength = (name, path27) => {
6145
+ const checkNameLength = (name, path28) => {
6105
6146
  if (name.length > 50) {
6106
- 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.`);
6107
6148
  }
6108
6149
  };
6109
6150
  for (const bucket of project.buckets || []) {
@@ -6141,9 +6182,9 @@ function validateMergedConfig(config) {
6141
6182
  }
6142
6183
  }
6143
6184
  }
6144
- for (const [path27, backends] of routePaths) {
6185
+ for (const [path28, backends] of routePaths) {
6145
6186
  if (backends.length > 1) {
6146
- 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(", ")}`);
6147
6188
  }
6148
6189
  }
6149
6190
  return {
@@ -13314,9 +13355,9 @@ import * as path15 from "path";
13314
13355
 
13315
13356
  // src/api-client.ts
13316
13357
  var API_BASE = process.env.STACKSOLO_API_URL || "http://localhost:4000";
13317
- async function callApi(path27, method = "GET", body) {
13358
+ async function callApi(path28, method = "GET", body) {
13318
13359
  try {
13319
- const response = await fetch(`${API_BASE}${path27}`, {
13360
+ const response = await fetch(`${API_BASE}${path28}`, {
13320
13361
  method,
13321
13362
  headers: {
13322
13363
  "Content-Type": "application/json"
@@ -13350,7 +13391,7 @@ var api = {
13350
13391
  },
13351
13392
  patterns: {
13352
13393
  list: () => callApi("/trpc/patterns.list"),
13353
- 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 }))}`)
13354
13395
  },
13355
13396
  deployments: {
13356
13397
  deploy: (projectId) => callApi("/trpc/deployments.deploy", "POST", { projectId }),
@@ -14127,9 +14168,9 @@ var listCommand = new Command11("list").description("List all registered project
14127
14168
  // src/commands/infra/events.ts
14128
14169
  import { Command as Command12 } from "commander";
14129
14170
  import chalk13 from "chalk";
14130
- function drawTableLine(columns, char = "-", join26 = "+") {
14171
+ function drawTableLine(columns, char = "-", join27 = "+") {
14131
14172
  const segments = columns.map((col) => char.repeat(col.width + 2));
14132
- return join26 + segments.join(join26) + join26;
14173
+ return join27 + segments.join(join27) + join27;
14133
14174
  }
14134
14175
  function drawTableRow(columns, values) {
14135
14176
  const cells = columns.map((col, i) => {
@@ -14619,10 +14660,10 @@ async function getGcpProjectId(explicitProject) {
14619
14660
  return explicitProject;
14620
14661
  }
14621
14662
  try {
14622
- const fs22 = await import("fs/promises");
14623
- const path27 = await import("path");
14624
- const configPath = path27.join(process.cwd(), ".stacksolo", "stacksolo.config.json");
14625
- 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");
14626
14667
  const config = JSON.parse(configData);
14627
14668
  if (config.project?.gcpProjectId) {
14628
14669
  return config.project.gcpProjectId;
@@ -15490,11 +15531,11 @@ var buildCommand = new Command16("build").description("Build and push container
15490
15531
 
15491
15532
  // src/commands/dev/dev.ts
15492
15533
  import { Command as Command17 } from "commander";
15493
- import chalk18 from "chalk";
15494
- import ora8 from "ora";
15495
- import { spawn as spawn6, execSync as execSync2 } from "child_process";
15496
- import * as fs19 from "fs/promises";
15497
- 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";
15498
15539
 
15499
15540
  // src/generators/k8s/namespace.ts
15500
15541
  function generateNamespace(projectName) {
@@ -15552,6 +15593,9 @@ function generateConfigMap(options) {
15552
15593
  } else {
15553
15594
  data.PUBSUB_EMULATOR_HOST = "pubsub-emulator:8085";
15554
15595
  }
15596
+ if (options.kernelUrl) {
15597
+ data.KERNEL_URL = options.kernelUrl;
15598
+ }
15555
15599
  if (options.additionalEnv) {
15556
15600
  Object.assign(data, options.additionalEnv);
15557
15601
  }
@@ -15660,9 +15704,31 @@ function generateFunctionManifests(options) {
15660
15704
  const functionName = sanitizeName(options.function.name);
15661
15705
  const runtimeConfig = getRuntimeConfig(options.function.runtime, options.function.entryPoint);
15662
15706
  const isPython = isPythonRuntime(options.function.runtime);
15663
- const installCmd = isPython ? "pip install -r requirements.txt 2>/dev/null || true && pip install functions-framework" : "npm install";
15664
15707
  const runCmd = isPython ? runtimeConfig.command.join(" ") : "npm run dev 2>/dev/null || " + runtimeConfig.command.join(" ");
15665
- const containerCommand = ["sh", "-c", `${installCmd} && ${runCmd}`];
15708
+ const containerCommand = isPython ? [
15709
+ "sh",
15710
+ "-c",
15711
+ [
15712
+ "cp -r /source/* /app/ 2>/dev/null || true",
15713
+ "cd /app",
15714
+ "pip install -r requirements.txt 2>/dev/null || true",
15715
+ "pip install functions-framework",
15716
+ runCmd
15717
+ ].join(" && ")
15718
+ ] : [
15719
+ "sh",
15720
+ "-c",
15721
+ [
15722
+ // Copy source files to /app, excluding node_modules (macOS binaries don't work in Linux)
15723
+ "cd /source",
15724
+ "find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\;",
15725
+ "cd /app",
15726
+ // Always install fresh for Linux platform
15727
+ "npm install",
15728
+ // Run the dev server
15729
+ runCmd
15730
+ ].join(" && ")
15731
+ ];
15666
15732
  const labels = {
15667
15733
  "app.kubernetes.io/name": functionName,
15668
15734
  "app.kubernetes.io/component": "function",
@@ -15711,16 +15777,21 @@ function generateFunctionManifests(options) {
15711
15777
  volumeMounts: [
15712
15778
  {
15713
15779
  name: "source",
15780
+ mountPath: "/source",
15781
+ readOnly: true
15782
+ },
15783
+ {
15784
+ name: "workdir",
15714
15785
  mountPath: "/app"
15715
15786
  }
15716
15787
  ],
15717
15788
  workingDir: "/app",
15718
15789
  resources: {
15719
15790
  limits: {
15720
- memory: options.function.memory || "256Mi"
15791
+ memory: options.function.memory || "512Mi"
15721
15792
  },
15722
15793
  requests: {
15723
- memory: "128Mi"
15794
+ memory: "256Mi"
15724
15795
  }
15725
15796
  }
15726
15797
  }
@@ -15732,6 +15803,10 @@ function generateFunctionManifests(options) {
15732
15803
  path: options.sourceDir,
15733
15804
  type: "DirectoryOrCreate"
15734
15805
  }
15806
+ },
15807
+ {
15808
+ name: "workdir",
15809
+ emptyDir: {}
15735
15810
  }
15736
15811
  ]
15737
15812
  }
@@ -15817,7 +15892,21 @@ function generateUIManifests(options) {
15817
15892
  {
15818
15893
  name: uiName,
15819
15894
  image: "node:20-slim",
15820
- command: ["sh", "-c", `npm install && ${frameworkConfig.command.join(" ")}`],
15895
+ // Copy source (excluding node_modules) to working directory, then install fresh Linux deps
15896
+ command: [
15897
+ "sh",
15898
+ "-c",
15899
+ [
15900
+ // Copy source files to /app, excluding node_modules (macOS binaries don't work in Linux)
15901
+ "cd /source",
15902
+ "find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\;",
15903
+ "cd /app",
15904
+ // Always install fresh for Linux platform
15905
+ "npm install",
15906
+ // Run the dev server
15907
+ frameworkConfig.command.join(" ")
15908
+ ].join(" && ")
15909
+ ],
15821
15910
  ports: [
15822
15911
  {
15823
15912
  containerPort: options.port,
@@ -15832,16 +15921,21 @@ function generateUIManifests(options) {
15832
15921
  volumeMounts: [
15833
15922
  {
15834
15923
  name: "source",
15924
+ mountPath: "/source",
15925
+ readOnly: true
15926
+ },
15927
+ {
15928
+ name: "workdir",
15835
15929
  mountPath: "/app"
15836
15930
  }
15837
15931
  ],
15838
15932
  workingDir: "/app",
15839
15933
  resources: {
15840
15934
  limits: {
15841
- memory: "512Mi"
15935
+ memory: "1Gi"
15842
15936
  },
15843
15937
  requests: {
15844
- memory: "256Mi"
15938
+ memory: "512Mi"
15845
15939
  }
15846
15940
  }
15847
15941
  }
@@ -15853,6 +15947,10 @@ function generateUIManifests(options) {
15853
15947
  path: options.sourceDir,
15854
15948
  type: "DirectoryOrCreate"
15855
15949
  }
15950
+ },
15951
+ {
15952
+ name: "workdir",
15953
+ emptyDir: {}
15856
15954
  }
15857
15955
  ]
15858
15956
  }
@@ -16569,7 +16667,7 @@ function generateNginxConfig(routes, servicePortMap) {
16569
16667
  const basePath = route.path.replace(/\/?\*$/, "");
16570
16668
  return `
16571
16669
  location ${basePath}/ {
16572
- proxy_pass ${upstream}/;
16670
+ proxy_pass ${upstream};
16573
16671
  proxy_http_version 1.1;
16574
16672
  proxy_set_header Upgrade $http_upgrade;
16575
16673
  proxy_set_header Connection 'upgrade';
@@ -16687,8 +16785,14 @@ function generateK8sManifests(options) {
16687
16785
  const projectName = config.project.name;
16688
16786
  const portAllocator = createPortAllocator();
16689
16787
  const servicePortMap = {};
16788
+ let kernelUrl;
16789
+ if (config.project.kernel) {
16790
+ kernelUrl = `http://${config.project.kernel.name}:8090`;
16791
+ } else if (config.project.gcpKernel) {
16792
+ kernelUrl = `http://${config.project.gcpKernel.name}:8080`;
16793
+ }
16690
16794
  manifests.push(generateNamespace(projectName));
16691
- manifests.push(generateConfigMap({ projectName }));
16795
+ manifests.push(generateConfigMap({ projectName, kernelUrl }));
16692
16796
  if (includeEmulators) {
16693
16797
  manifests.push(generateFirebaseEmulator({ projectName }));
16694
16798
  manifests.push(generatePubSubEmulator({ projectName }));
@@ -16806,17 +16910,300 @@ function generateK8sManifests(options) {
16806
16910
  };
16807
16911
  }
16808
16912
  async function writeK8sManifests(manifests, outputDir) {
16809
- const fs22 = await import("fs/promises");
16810
- const path27 = await import("path");
16811
- 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 });
16812
16916
  for (const manifest of manifests) {
16813
- const filePath = path27.join(outputDir, manifest.filename);
16814
- 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");
16815
16919
  }
16816
16920
  }
16817
16921
 
16818
16922
  // src/commands/dev/dev.ts
16819
16923
  init_plugin_loader_service();
16924
+
16925
+ // src/services/local-dev.service.ts
16926
+ import { spawn as spawn6 } from "child_process";
16927
+ import chalk18 from "chalk";
16928
+ import ora8 from "ora";
16929
+ import * as path22 from "path";
16930
+ import * as fs19 from "fs/promises";
16931
+ var COLORS = [
16932
+ chalk18.cyan,
16933
+ chalk18.magenta,
16934
+ chalk18.yellow,
16935
+ chalk18.green,
16936
+ chalk18.blue,
16937
+ chalk18.red
16938
+ ];
16939
+ var LocalPortAllocator = class {
16940
+ functionPort = 8081;
16941
+ uiPort = 3e3;
16942
+ containerPort = 9e3;
16943
+ nextFunctionPort() {
16944
+ return this.functionPort++;
16945
+ }
16946
+ nextUiPort() {
16947
+ return this.uiPort++;
16948
+ }
16949
+ nextContainerPort() {
16950
+ return this.containerPort++;
16951
+ }
16952
+ };
16953
+ function collectServices(config, projectRoot) {
16954
+ const services = [];
16955
+ const portAllocator = new LocalPortAllocator();
16956
+ let colorIndex = 0;
16957
+ for (const network of config.project.networks || []) {
16958
+ for (const func of network.functions || []) {
16959
+ const sourceDir = func.sourceDir?.replace(/^\.\//, "") || `functions/${func.name}`;
16960
+ services.push({
16961
+ name: func.name,
16962
+ type: "function",
16963
+ sourceDir: path22.join(projectRoot, sourceDir),
16964
+ port: portAllocator.nextFunctionPort(),
16965
+ color: COLORS[colorIndex++ % COLORS.length]
16966
+ });
16967
+ }
16968
+ for (const ui of network.uis || []) {
16969
+ const sourceDir = ui.sourceDir?.replace(/^\.\//, "") || `apps/${ui.name}`;
16970
+ services.push({
16971
+ name: ui.name,
16972
+ type: "ui",
16973
+ sourceDir: path22.join(projectRoot, sourceDir),
16974
+ port: portAllocator.nextUiPort(),
16975
+ color: COLORS[colorIndex++ % COLORS.length]
16976
+ });
16977
+ }
16978
+ for (const container of network.containers || []) {
16979
+ const sourceDir = container.sourceDir?.replace(/^\.\//, "") || `containers/${container.name}`;
16980
+ services.push({
16981
+ name: container.name,
16982
+ type: "container",
16983
+ sourceDir: path22.join(projectRoot, sourceDir),
16984
+ port: container.port || portAllocator.nextContainerPort(),
16985
+ color: COLORS[colorIndex++ % COLORS.length]
16986
+ });
16987
+ }
16988
+ }
16989
+ return services;
16990
+ }
16991
+ async function hasPackageJson(sourceDir) {
16992
+ try {
16993
+ await fs19.access(path22.join(sourceDir, "package.json"));
16994
+ return true;
16995
+ } catch {
16996
+ return false;
16997
+ }
16998
+ }
16999
+ async function hasNodeModules(sourceDir) {
17000
+ try {
17001
+ await fs19.access(path22.join(sourceDir, "node_modules"));
17002
+ return true;
17003
+ } catch {
17004
+ return false;
17005
+ }
17006
+ }
17007
+ async function hasDevScript(sourceDir) {
17008
+ try {
17009
+ const pkgPath = path22.join(sourceDir, "package.json");
17010
+ const content = await fs19.readFile(pkgPath, "utf-8");
17011
+ const pkg2 = JSON.parse(content);
17012
+ return Boolean(pkg2.scripts?.dev);
17013
+ } catch {
17014
+ return false;
17015
+ }
17016
+ }
17017
+ function streamWithPrefix(stream, prefix, color) {
17018
+ stream.on("data", (data) => {
17019
+ const lines = data.toString().split("\n").filter(Boolean);
17020
+ for (const line of lines) {
17021
+ console.log(`${color(`[${prefix}]`)} ${line}`);
17022
+ }
17023
+ });
17024
+ }
17025
+ function spawnService(service, manager) {
17026
+ const env = {
17027
+ ...process.env,
17028
+ PORT: String(service.port),
17029
+ NODE_ENV: "development",
17030
+ // Firebase emulator connection vars
17031
+ FIRESTORE_EMULATOR_HOST: "localhost:8080",
17032
+ FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099",
17033
+ PUBSUB_EMULATOR_HOST: "localhost:8085"
17034
+ };
17035
+ const args = service.type === "ui" ? ["run", "dev", "--", "--port", String(service.port)] : ["run", "dev"];
17036
+ const proc = spawn6("npm", args, {
17037
+ cwd: service.sourceDir,
17038
+ env,
17039
+ stdio: ["ignore", "pipe", "pipe"],
17040
+ shell: true
17041
+ });
17042
+ if (proc.stdout) {
17043
+ streamWithPrefix(proc.stdout, service.name, service.color);
17044
+ }
17045
+ if (proc.stderr) {
17046
+ streamWithPrefix(proc.stderr, service.name, service.color);
17047
+ }
17048
+ proc.on("error", (err) => {
17049
+ console.log(service.color(`[${service.name}]`), chalk18.red(`Error: ${err.message}`));
17050
+ });
17051
+ proc.on("exit", (code) => {
17052
+ if (!manager.isShuttingDown) {
17053
+ console.log(
17054
+ service.color(`[${service.name}]`),
17055
+ code === 0 ? chalk18.gray("Exited") : chalk18.red(`Exited with code ${code}`)
17056
+ );
17057
+ }
17058
+ manager.processes.delete(service.name);
17059
+ });
17060
+ return proc;
17061
+ }
17062
+ async function startFirebaseEmulators(manager) {
17063
+ const spinner = ora8("Starting Firebase emulators...").start();
17064
+ try {
17065
+ const proc = spawn6(
17066
+ "firebase",
17067
+ ["emulators:start", "--only", "firestore,auth", "--project", "demo-local"],
17068
+ {
17069
+ stdio: ["ignore", "pipe", "pipe"],
17070
+ shell: true
17071
+ }
17072
+ );
17073
+ const color = chalk18.yellow;
17074
+ if (proc.stdout) {
17075
+ streamWithPrefix(proc.stdout, "firebase", color);
17076
+ }
17077
+ if (proc.stderr) {
17078
+ streamWithPrefix(proc.stderr, "firebase", color);
17079
+ }
17080
+ proc.on("error", () => {
17081
+ spinner.fail("Firebase CLI not found. Skipping emulators.");
17082
+ });
17083
+ await new Promise((resolve7) => setTimeout(resolve7, 3e3));
17084
+ spinner.succeed("Firebase emulators starting");
17085
+ return proc;
17086
+ } catch {
17087
+ spinner.warn("Firebase emulators not available");
17088
+ return null;
17089
+ }
17090
+ }
17091
+ function shutdown(manager) {
17092
+ manager.isShuttingDown = true;
17093
+ console.log(chalk18.gray("\n Shutting down services...\n"));
17094
+ for (const [name, proc] of manager.processes) {
17095
+ try {
17096
+ console.log(chalk18.gray(` Stopping ${name}...`));
17097
+ proc.kill("SIGTERM");
17098
+ } catch {
17099
+ }
17100
+ }
17101
+ setTimeout(() => {
17102
+ for (const [, proc] of manager.processes) {
17103
+ try {
17104
+ proc.kill("SIGKILL");
17105
+ } catch {
17106
+ }
17107
+ }
17108
+ process.exit(0);
17109
+ }, 5e3);
17110
+ }
17111
+ async function startLocalEnvironment(options) {
17112
+ console.log(chalk18.bold("\n StackSolo Local Development\n"));
17113
+ const projectRoot = process.cwd();
17114
+ const configPath = path22.join(projectRoot, ".stacksolo", "stacksolo.config.json");
17115
+ let config;
17116
+ try {
17117
+ const content = await fs19.readFile(configPath, "utf-8");
17118
+ config = JSON.parse(content);
17119
+ } catch {
17120
+ console.log(chalk18.red(` Config not found: .stacksolo/stacksolo.config.json`));
17121
+ console.log(chalk18.gray(` Run 'stacksolo init' first.
17122
+ `));
17123
+ process.exit(1);
17124
+ }
17125
+ const services = collectServices(config, projectRoot);
17126
+ if (services.length === 0) {
17127
+ console.log(chalk18.yellow(" No services found in config.\n"));
17128
+ return;
17129
+ }
17130
+ const validServices = [];
17131
+ const missingDeps = [];
17132
+ const missingDevScript = [];
17133
+ for (const service of services) {
17134
+ if (!await hasPackageJson(service.sourceDir)) {
17135
+ console.log(chalk18.yellow(` Warning: ${service.name} has no package.json at ${service.sourceDir}, skipping`));
17136
+ continue;
17137
+ }
17138
+ if (!await hasDevScript(service.sourceDir)) {
17139
+ missingDevScript.push(service);
17140
+ continue;
17141
+ }
17142
+ validServices.push(service);
17143
+ if (!await hasNodeModules(service.sourceDir)) {
17144
+ missingDeps.push(service);
17145
+ }
17146
+ }
17147
+ if (missingDevScript.length > 0) {
17148
+ console.log(chalk18.red('\n Error: Some services are missing "dev" script in package.json:\n'));
17149
+ for (const svc of missingDevScript) {
17150
+ console.log(chalk18.red(` \u2717 ${svc.name}`));
17151
+ console.log(chalk18.gray(` Add a "dev" script to ${path22.relative(projectRoot, svc.sourceDir)}/package.json`));
17152
+ }
17153
+ console.log(chalk18.gray("\n See: https://stacksolo.dev/reference/cli/#local-mode---local\n"));
17154
+ }
17155
+ if (validServices.length === 0) {
17156
+ console.log(chalk18.yellow(" No runnable services found.\n"));
17157
+ console.log(chalk18.gray(" Run `stacksolo scaffold` to generate service code.\n"));
17158
+ return;
17159
+ }
17160
+ if (missingDeps.length > 0) {
17161
+ console.log(chalk18.yellow("\n Warning: Some services are missing node_modules:"));
17162
+ for (const svc of missingDeps) {
17163
+ console.log(chalk18.yellow(` \u2022 ${svc.name}: Run \`cd ${path22.relative(projectRoot, svc.sourceDir)} && npm install\``));
17164
+ }
17165
+ console.log(chalk18.gray("\n Or run: stacksolo install\n"));
17166
+ }
17167
+ const manager = {
17168
+ processes: /* @__PURE__ */ new Map(),
17169
+ services: validServices,
17170
+ isShuttingDown: false
17171
+ };
17172
+ if (options.includeEmulators !== false) {
17173
+ const emulatorProc = await startFirebaseEmulators(manager);
17174
+ if (emulatorProc) {
17175
+ manager.processes.set("firebase-emulator", emulatorProc);
17176
+ }
17177
+ }
17178
+ const spinner = ora8("Starting services...").start();
17179
+ for (const service of validServices) {
17180
+ const proc = spawnService(service, manager);
17181
+ if (proc) {
17182
+ manager.processes.set(service.name, proc);
17183
+ }
17184
+ }
17185
+ spinner.succeed(`Started ${validServices.length} service(s)`);
17186
+ console.log(chalk18.bold("\n Services running:\n"));
17187
+ for (const service of validServices) {
17188
+ const url = `http://localhost:${service.port}`;
17189
+ console.log(` ${service.color("\u25CF")} ${service.name.padEnd(20)} ${chalk18.cyan(url)}`);
17190
+ }
17191
+ if (options.includeEmulators !== false) {
17192
+ console.log(chalk18.bold("\n Emulators:\n"));
17193
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase UI ${chalk18.cyan("http://localhost:4000")}`);
17194
+ console.log(` ${chalk18.yellow("\u25CF")} Firestore ${chalk18.gray("localhost:8080")}`);
17195
+ console.log(` ${chalk18.yellow("\u25CF")} Firebase Auth ${chalk18.gray("localhost:9099")}`);
17196
+ }
17197
+ console.log(chalk18.bold("\n Commands:\n"));
17198
+ console.log(chalk18.gray(" Press Ctrl+C to stop all services"));
17199
+ console.log("");
17200
+ process.on("SIGINT", () => shutdown(manager));
17201
+ process.on("SIGTERM", () => shutdown(manager));
17202
+ await new Promise(() => {
17203
+ });
17204
+ }
17205
+
17206
+ // src/commands/dev/dev.ts
16820
17207
  function getKernelConfig(config) {
16821
17208
  if (config.project.kernel) {
16822
17209
  return {
@@ -16844,8 +17231,14 @@ var webAdminProcess = null;
16844
17231
  var isShuttingDown = false;
16845
17232
  var K8S_OUTPUT_DIR = ".stacksolo/k8s";
16846
17233
  var CONFIG_FILE = ".stacksolo/stacksolo.config.json";
16847
- var devCommand = new Command17("dev").description("Start local Kubernetes development environment").option("--stop", "Stop and tear down the environment").option("--status", "Show status of running pods with health").option("--health", "Check health of all services").option("--ports", "Show port-forward status").option("--restart [service]", "Restart port-forwards or specific service pod").option("--service-names", "List service names for use with other commands").option("--routes", "Show gateway routes and services").option("--describe [resource]", "Describe K8s resources (pods, services, all)").option("--logs [service]", "Tail logs (all pods or specific service)").option("--rebuild", "Force regenerate manifests before starting").option("--no-emulators", "Skip Firebase/Pub/Sub emulators").action(async (options) => {
17234
+ var devCommand = new Command17("dev").description("Start local development environment").option("--local", "Run services locally without Docker/K8s").option("--stop", "Stop and tear down the environment").option("--status", "Show status of running pods with health").option("--health", "Check health of all services").option("--ports", "Show port-forward status").option("--restart [service]", "Restart port-forwards or specific service pod").option("--service-names", "List service names for use with other commands").option("--routes", "Show gateway routes and services").option("--describe [resource]", "Describe K8s resources (pods, services, all)").option("--logs [service]", "Tail logs (all pods or specific service)").option("--rebuild", "Force regenerate manifests before starting").option("--no-emulators", "Skip Firebase/Pub/Sub emulators").action(async (options) => {
16848
17235
  try {
17236
+ if (options.local) {
17237
+ await startLocalEnvironment({
17238
+ includeEmulators: options.emulators !== false
17239
+ });
17240
+ return;
17241
+ }
16849
17242
  if (options.stop) {
16850
17243
  await stopEnvironment();
16851
17244
  return;
@@ -16890,38 +17283,38 @@ var devCommand = new Command17("dev").description("Start local Kubernetes develo
16890
17283
  includeEmulators: options.emulators !== false
16891
17284
  });
16892
17285
  } catch (error) {
16893
- console.error(chalk18.red(`
17286
+ console.error(chalk19.red(`
16894
17287
  Error: ${error instanceof Error ? error.message : error}
16895
17288
  `));
16896
17289
  process.exit(1);
16897
17290
  }
16898
17291
  });
16899
17292
  async function checkPrerequisites() {
16900
- const spinner = ora8("Checking prerequisites...").start();
17293
+ const spinner = ora9("Checking prerequisites...").start();
16901
17294
  try {
16902
17295
  execSync2("kubectl version --client --short 2>/dev/null || kubectl version --client", {
16903
17296
  stdio: "pipe"
16904
17297
  });
16905
17298
  } catch {
16906
17299
  spinner.fail("kubectl not found");
16907
- console.log(chalk18.gray("\n Install OrbStack: brew install orbstack"));
16908
- console.log(chalk18.gray(" Or install kubectl: brew install kubectl\n"));
17300
+ console.log(chalk19.gray("\n Install OrbStack: brew install orbstack"));
17301
+ console.log(chalk19.gray(" Or install kubectl: brew install kubectl\n"));
16909
17302
  throw new Error("kubectl is required but not found");
16910
17303
  }
16911
17304
  try {
16912
17305
  execSync2("kubectl cluster-info 2>/dev/null", { stdio: "pipe" });
16913
17306
  } catch {
16914
17307
  spinner.fail("Kubernetes cluster not available");
16915
- console.log(chalk18.gray("\n If using OrbStack, enable Kubernetes in preferences"));
16916
- console.log(chalk18.gray(" Settings \u2192 Kubernetes \u2192 Enable Kubernetes\n"));
17308
+ console.log(chalk19.gray("\n If using OrbStack, enable Kubernetes in preferences"));
17309
+ console.log(chalk19.gray(" Settings \u2192 Kubernetes \u2192 Enable Kubernetes\n"));
16917
17310
  throw new Error("Kubernetes cluster not available");
16918
17311
  }
16919
17312
  spinner.succeed("Prerequisites met");
16920
17313
  }
16921
17314
  async function loadConfig2() {
16922
- const configPath = path22.resolve(process.cwd(), CONFIG_FILE);
17315
+ const configPath = path23.resolve(process.cwd(), CONFIG_FILE);
16923
17316
  try {
16924
- const content = await fs19.readFile(configPath, "utf-8");
17317
+ const content = await fs20.readFile(configPath, "utf-8");
16925
17318
  return JSON.parse(content);
16926
17319
  } catch (error) {
16927
17320
  if (error.code === "ENOENT") {
@@ -16939,9 +17332,9 @@ async function validateSourceDirs(config) {
16939
17332
  const kernelService = getPluginService(validateKernelConfig.serviceName);
16940
17333
  const pluginSourcePath = kernelService ? getServiceSourcePath(kernelService) : null;
16941
17334
  if (!pluginSourcePath) {
16942
- const kernelDir = path22.join(projectRoot, "containers", validateKernelConfig.name);
17335
+ const kernelDir = path23.join(projectRoot, "containers", validateKernelConfig.name);
16943
17336
  try {
16944
- await fs19.access(kernelDir);
17337
+ await fs20.access(kernelDir);
16945
17338
  } catch {
16946
17339
  warnings.push(`Kernel directory not found: containers/${validateKernelConfig.name}/`);
16947
17340
  }
@@ -16949,17 +17342,17 @@ async function validateSourceDirs(config) {
16949
17342
  }
16950
17343
  for (const network of config.project.networks || []) {
16951
17344
  for (const func of network.functions || []) {
16952
- const funcDir = path22.join(projectRoot, "functions", func.name);
17345
+ const funcDir = path23.join(projectRoot, "functions", func.name);
16953
17346
  try {
16954
- await fs19.access(funcDir);
17347
+ await fs20.access(funcDir);
16955
17348
  } catch {
16956
17349
  warnings.push(`Function directory not found: functions/${func.name}/`);
16957
17350
  }
16958
17351
  }
16959
17352
  for (const ui of network.uis || []) {
16960
- const uiDir = path22.join(projectRoot, "ui", ui.name);
17353
+ const uiDir = path23.join(projectRoot, "ui", ui.name);
16961
17354
  try {
16962
- await fs19.access(uiDir);
17355
+ await fs20.access(uiDir);
16963
17356
  } catch {
16964
17357
  warnings.push(`UI directory not found: ui/${ui.name}/`);
16965
17358
  }
@@ -16973,7 +17366,7 @@ async function startWebAdmin(config) {
16973
17366
  return null;
16974
17367
  }
16975
17368
  const port = webAdmin.port || 3e3;
16976
- const spinner = ora8(`Starting web admin on port ${port}...`).start();
17369
+ const spinner = ora9(`Starting web admin on port ${port}...`).start();
16977
17370
  try {
16978
17371
  const webAdminService = getPluginService("web-admin");
16979
17372
  let appDir = null;
@@ -16984,9 +17377,9 @@ async function startWebAdmin(config) {
16984
17377
  }
16985
17378
  }
16986
17379
  if (!appDir) {
16987
- const nodeModulesPath = path22.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
17380
+ const nodeModulesPath = path23.join(process.cwd(), "node_modules", "@stacksolo", "plugin-web-admin", "app");
16988
17381
  try {
16989
- await fs19.access(nodeModulesPath);
17382
+ await fs20.access(nodeModulesPath);
16990
17383
  appDir = nodeModulesPath;
16991
17384
  } catch {
16992
17385
  }
@@ -16995,16 +17388,16 @@ async function startWebAdmin(config) {
16995
17388
  spinner.warn("Web admin not found - install @stacksolo/plugin-web-admin or add to plugins");
16996
17389
  return null;
16997
17390
  }
16998
- const buildDir = path22.join(appDir, "build");
17391
+ const buildDir = path23.join(appDir, "build");
16999
17392
  let useDevMode = false;
17000
17393
  try {
17001
- await fs19.access(buildDir);
17394
+ await fs20.access(buildDir);
17002
17395
  } catch {
17003
17396
  useDevMode = true;
17004
17397
  }
17005
17398
  const projectPath = process.cwd();
17006
17399
  if (useDevMode) {
17007
- webAdminProcess = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
17400
+ webAdminProcess = spawn7("npm", ["run", "dev", "--", "--port", String(port)], {
17008
17401
  cwd: appDir,
17009
17402
  env: {
17010
17403
  ...process.env,
@@ -17015,7 +17408,7 @@ async function startWebAdmin(config) {
17015
17408
  detached: false
17016
17409
  });
17017
17410
  } else {
17018
- webAdminProcess = spawn6("node", ["build"], {
17411
+ webAdminProcess = spawn7("node", ["build"], {
17019
17412
  cwd: appDir,
17020
17413
  env: {
17021
17414
  ...process.env,
@@ -17050,20 +17443,20 @@ async function buildKernelImage(config) {
17050
17443
  const sourcePath = getServiceSourcePath(kernelService);
17051
17444
  if (sourcePath) {
17052
17445
  kernelDir = sourcePath;
17053
- console.log(chalk18.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17446
+ console.log(chalk19.gray(` Using ${kernelType} kernel from plugin: ${kernelDir}`));
17054
17447
  } else {
17055
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17448
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17056
17449
  }
17057
17450
  } else {
17058
- kernelDir = path22.join(process.cwd(), "containers", kernelName);
17451
+ kernelDir = path23.join(process.cwd(), "containers", kernelName);
17059
17452
  }
17060
17453
  try {
17061
- await fs19.access(kernelDir);
17454
+ await fs20.access(kernelDir);
17062
17455
  } catch {
17063
- console.log(chalk18.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17456
+ console.log(chalk19.gray(` ${kernelType.toUpperCase()} kernel directory not found: ${kernelDir}`));
17064
17457
  return false;
17065
17458
  }
17066
- const spinner = ora8(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17459
+ const spinner = ora9(`Building ${kernelType} kernel image from ${kernelDir}...`).start();
17067
17460
  try {
17068
17461
  execSync2("npm install", { cwd: kernelDir, stdio: "pipe" });
17069
17462
  execSync2("npm run build", { cwd: kernelDir, stdio: "pipe" });
@@ -17072,32 +17465,32 @@ async function buildKernelImage(config) {
17072
17465
  return true;
17073
17466
  } catch (error) {
17074
17467
  spinner.fail(`Failed to build ${kernelType} kernel image`);
17075
- console.log(chalk18.gray(` Error: ${error instanceof Error ? error.message : error}`));
17468
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
17076
17469
  return false;
17077
17470
  }
17078
17471
  }
17079
17472
  async function startEnvironment(options) {
17080
- console.log(chalk18.bold("\n StackSolo Dev Environment\n"));
17473
+ console.log(chalk19.bold("\n StackSolo Dev Environment\n"));
17081
17474
  await checkPrerequisites();
17082
- const spinner = ora8("Loading configuration...").start();
17475
+ const spinner = ora9("Loading configuration...").start();
17083
17476
  const config = await loadConfig2();
17084
17477
  const projectName = config.project.name;
17085
17478
  const namespace = sanitizeNamespaceName(projectName);
17086
17479
  spinner.succeed(`Project: ${projectName}`);
17087
- const pluginSpinner = ora8("Loading plugins...").start();
17480
+ const pluginSpinner = ora9("Loading plugins...").start();
17088
17481
  await loadPlugins(config.project.plugins);
17089
17482
  pluginSpinner.succeed("Plugins loaded");
17090
17483
  const warnings = await validateSourceDirs(config);
17091
17484
  if (warnings.length > 0) {
17092
- console.log(chalk18.yellow("\n Warnings:"));
17485
+ console.log(chalk19.yellow("\n Warnings:"));
17093
17486
  for (const warning of warnings) {
17094
- console.log(chalk18.yellow(` \u2022 ${warning}`));
17487
+ console.log(chalk19.yellow(` \u2022 ${warning}`));
17095
17488
  }
17096
17489
  console.log("");
17097
17490
  }
17098
17491
  await buildKernelImage(config);
17099
- const genSpinner = ora8("Generating Kubernetes manifests...").start();
17100
- const outputDir = path22.resolve(process.cwd(), K8S_OUTPUT_DIR);
17492
+ const genSpinner = ora9("Generating Kubernetes manifests...").start();
17493
+ const outputDir = path23.resolve(process.cwd(), K8S_OUTPUT_DIR);
17101
17494
  const result = generateK8sManifests({
17102
17495
  config,
17103
17496
  projectRoot: process.cwd(),
@@ -17107,10 +17500,10 @@ async function startEnvironment(options) {
17107
17500
  genSpinner.succeed(`Generated ${result.manifests.length} manifests to ${K8S_OUTPUT_DIR}/`);
17108
17501
  if (result.warnings.length > 0) {
17109
17502
  for (const warning of result.warnings) {
17110
- console.log(chalk18.yellow(` \u26A0 ${warning}`));
17503
+ console.log(chalk19.yellow(` \u26A0 ${warning}`));
17111
17504
  }
17112
17505
  }
17113
- const applySpinner = ora8("Applying Kubernetes manifests...").start();
17506
+ const applySpinner = ora9("Applying Kubernetes manifests...").start();
17114
17507
  try {
17115
17508
  execSync2(`kubectl apply -f ${outputDir}/namespace.yaml`, { stdio: "pipe" });
17116
17509
  execSync2(`kubectl apply -f ${outputDir}`, { stdio: "pipe" });
@@ -17119,7 +17512,7 @@ async function startEnvironment(options) {
17119
17512
  applySpinner.fail("Failed to apply manifests");
17120
17513
  throw error;
17121
17514
  }
17122
- const readySpinner = ora8("Waiting for pods to be ready...").start();
17515
+ const readySpinner = ora9("Waiting for pods to be ready...").start();
17123
17516
  try {
17124
17517
  execSync2(
17125
17518
  `kubectl wait --for=condition=ready pod --all -n ${namespace} --timeout=120s`,
@@ -17129,7 +17522,7 @@ async function startEnvironment(options) {
17129
17522
  } catch {
17130
17523
  readySpinner.warn("Some pods may not be ready yet");
17131
17524
  }
17132
- const portForwardSpinner = ora8("Setting up port forwarding...").start();
17525
+ const portForwardSpinner = ora9("Setting up port forwarding...").start();
17133
17526
  const portMappings = await setupPortForwarding(namespace, config);
17134
17527
  portForwardSpinner.succeed("Port forwarding active");
17135
17528
  const webAdminPort = await startWebAdmin(config);
@@ -17142,7 +17535,7 @@ async function startEnvironment(options) {
17142
17535
  protocol: "http"
17143
17536
  });
17144
17537
  }
17145
- console.log(chalk18.bold("\n Services running:\n"));
17538
+ console.log(chalk19.bold("\n Services running:\n"));
17146
17539
  try {
17147
17540
  const podStatus = execSync2(
17148
17541
  `kubectl get pods -n ${namespace} -o wide --no-headers`,
@@ -17152,25 +17545,25 @@ async function startEnvironment(options) {
17152
17545
  const parts = line.split(/\s+/);
17153
17546
  const name = parts[0];
17154
17547
  const status = parts[2];
17155
- const statusColor = status === "Running" ? chalk18.green : chalk18.yellow;
17548
+ const statusColor = status === "Running" ? chalk19.green : chalk19.yellow;
17156
17549
  console.log(` ${statusColor("\u25CF")} ${name.padEnd(30)} ${statusColor(status)}`);
17157
17550
  }
17158
17551
  } catch {
17159
- console.log(chalk18.gray(" Unable to get pod status"));
17552
+ console.log(chalk19.gray(" Unable to get pod status"));
17160
17553
  }
17161
- console.log(chalk18.bold("\n Access:\n"));
17554
+ console.log(chalk19.bold("\n Access:\n"));
17162
17555
  for (const mapping of portMappings) {
17163
17556
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17164
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
17557
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17165
17558
  }
17166
- console.log(chalk18.bold("\n Commands:\n"));
17167
- console.log(chalk18.gray(" stacksolo dev --logs Tail all logs"));
17168
- console.log(chalk18.gray(" stacksolo dev --status Show pod status"));
17169
- console.log(chalk18.gray(" stacksolo dev --stop Stop environment"));
17559
+ console.log(chalk19.bold("\n Commands:\n"));
17560
+ console.log(chalk19.gray(" stacksolo dev --logs Tail all logs"));
17561
+ console.log(chalk19.gray(" stacksolo dev --status Show pod status"));
17562
+ console.log(chalk19.gray(" stacksolo dev --stop Stop environment"));
17170
17563
  console.log("");
17171
17564
  const cleanup = async () => {
17172
17565
  isShuttingDown = true;
17173
- console.log(chalk18.gray("\n Shutting down...\n"));
17566
+ console.log(chalk19.gray("\n Shutting down...\n"));
17174
17567
  if (webAdminProcess) {
17175
17568
  try {
17176
17569
  webAdminProcess.kill("SIGTERM");
@@ -17185,20 +17578,20 @@ async function startEnvironment(options) {
17185
17578
  }
17186
17579
  try {
17187
17580
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17188
- console.log(chalk18.green(" Environment stopped\n"));
17581
+ console.log(chalk19.green(" Environment stopped\n"));
17189
17582
  } catch {
17190
17583
  }
17191
17584
  process.exit(0);
17192
17585
  };
17193
17586
  process.on("SIGINT", cleanup);
17194
17587
  process.on("SIGTERM", cleanup);
17195
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
17588
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17196
17589
  await new Promise(() => {
17197
17590
  });
17198
17591
  }
17199
17592
  function startPortForwardWithRestart(namespace, service, localPort, targetPort, _name) {
17200
17593
  const startForward = () => {
17201
- const proc = spawn6(
17594
+ const proc = spawn7(
17202
17595
  "kubectl",
17203
17596
  ["port-forward", "-n", namespace, `svc/${service}`, `${localPort}:${targetPort}`],
17204
17597
  { stdio: "pipe", detached: false }
@@ -17316,18 +17709,18 @@ async function setupPortForwarding(namespace, config) {
17316
17709
  return portMappings;
17317
17710
  }
17318
17711
  async function stopEnvironment() {
17319
- console.log(chalk18.bold("\n Stopping StackSolo Dev Environment\n"));
17712
+ console.log(chalk19.bold("\n Stopping StackSolo Dev Environment\n"));
17320
17713
  const config = await loadConfig2();
17321
17714
  const namespace = sanitizeNamespaceName(config.project.name);
17322
17715
  const projectName = config.project.name;
17323
- const nsSpinner = ora8(`Deleting namespace ${namespace}...`).start();
17716
+ const nsSpinner = ora9(`Deleting namespace ${namespace}...`).start();
17324
17717
  try {
17325
17718
  execSync2(`kubectl delete namespace ${namespace}`, { stdio: "pipe" });
17326
17719
  nsSpinner.succeed("Namespace deleted");
17327
17720
  } catch {
17328
17721
  nsSpinner.warn("Namespace may not exist or already deleted");
17329
17722
  }
17330
- const imgSpinner = ora8("Cleaning up Docker images...").start();
17723
+ const imgSpinner = ora9("Cleaning up Docker images...").start();
17331
17724
  try {
17332
17725
  const images = execSync2(
17333
17726
  `docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "^(${namespace}-|${projectName}-)" || true`,
@@ -17351,149 +17744,149 @@ async function stopEnvironment() {
17351
17744
  console.log("");
17352
17745
  }
17353
17746
  async function showStatus() {
17354
- console.log(chalk18.bold("\n StackSolo Dev Status\n"));
17747
+ console.log(chalk19.bold("\n StackSolo Dev Status\n"));
17355
17748
  const config = await loadConfig2();
17356
17749
  const namespace = sanitizeNamespaceName(config.project.name);
17357
17750
  try {
17358
- console.log(chalk18.bold(" Pods:\n"));
17751
+ console.log(chalk19.bold(" Pods:\n"));
17359
17752
  const pods = execSync2(`kubectl get pods -n ${namespace} -o wide`, { encoding: "utf-8" });
17360
17753
  console.log(pods.split("\n").map((l) => " " + l).join("\n"));
17361
- console.log(chalk18.bold("\n Services:\n"));
17754
+ console.log(chalk19.bold("\n Services:\n"));
17362
17755
  const services = execSync2(`kubectl get services -n ${namespace}`, { encoding: "utf-8" });
17363
17756
  console.log(services.split("\n").map((l) => " " + l).join("\n"));
17364
- console.log(chalk18.bold("\n Ingress:\n"));
17757
+ console.log(chalk19.bold("\n Ingress:\n"));
17365
17758
  const ingress = execSync2(`kubectl get ingress -n ${namespace}`, { encoding: "utf-8" });
17366
17759
  console.log(ingress.split("\n").map((l) => " " + l).join("\n"));
17367
17760
  } catch {
17368
- console.log(chalk18.yellow(` No resources found in namespace ${namespace}`));
17369
- console.log(chalk18.gray(' Run "stacksolo dev" to start the environment\n'));
17761
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17762
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17370
17763
  }
17371
17764
  console.log("");
17372
17765
  }
17373
17766
  async function showRoutes() {
17374
- console.log(chalk18.bold("\n StackSolo Gateway Routes\n"));
17767
+ console.log(chalk19.bold("\n StackSolo Gateway Routes\n"));
17375
17768
  const config = await loadConfig2();
17376
17769
  const kernelConfig = getKernelConfig(config);
17377
17770
  if (kernelConfig) {
17378
17771
  const label = kernelConfig.type === "nats" ? "Kernel (NATS)" : "Kernel (GCP)";
17379
17772
  const detail = kernelConfig.type === "nats" ? `Source: containers/${kernelConfig.name}/` : "Type: GCP-native (Cloud Run + Pub/Sub)";
17380
- console.log(chalk18.bold(` ${label}:
17773
+ console.log(chalk19.bold(` ${label}:
17381
17774
  `));
17382
- console.log(` ${chalk18.cyan("\u25CF")} ${kernelConfig.name}`);
17383
- console.log(chalk18.gray(` ${detail}`));
17775
+ console.log(` ${chalk19.cyan("\u25CF")} ${kernelConfig.name}`);
17776
+ console.log(chalk19.gray(` ${detail}`));
17384
17777
  console.log("");
17385
17778
  }
17386
17779
  for (const network of config.project.networks || []) {
17387
- console.log(chalk18.bold(` Network: ${network.name}
17780
+ console.log(chalk19.bold(` Network: ${network.name}
17388
17781
  `));
17389
17782
  if (network.functions && network.functions.length > 0) {
17390
- console.log(chalk18.bold(" Functions:"));
17783
+ console.log(chalk19.bold(" Functions:"));
17391
17784
  for (const func of network.functions) {
17392
- console.log(` ${chalk18.green("\u03BB")} ${func.name}`);
17393
- console.log(chalk18.gray(` Source: functions/${func.name}/`));
17785
+ console.log(` ${chalk19.green("\u03BB")} ${func.name}`);
17786
+ console.log(chalk19.gray(` Source: functions/${func.name}/`));
17394
17787
  }
17395
17788
  console.log("");
17396
17789
  }
17397
17790
  if (network.containers && network.containers.length > 0) {
17398
- console.log(chalk18.bold(" Containers:"));
17791
+ console.log(chalk19.bold(" Containers:"));
17399
17792
  for (const container of network.containers) {
17400
- console.log(` ${chalk18.blue("\u25FC")} ${container.name}`);
17401
- console.log(chalk18.gray(` Source: containers/${container.name}/`));
17793
+ console.log(` ${chalk19.blue("\u25FC")} ${container.name}`);
17794
+ console.log(chalk19.gray(` Source: containers/${container.name}/`));
17402
17795
  }
17403
17796
  console.log("");
17404
17797
  }
17405
17798
  if (network.uis && network.uis.length > 0) {
17406
- console.log(chalk18.bold(" UIs:"));
17799
+ console.log(chalk19.bold(" UIs:"));
17407
17800
  for (const ui of network.uis) {
17408
- console.log(` ${chalk18.magenta("\u25C6")} ${ui.name}`);
17409
- console.log(chalk18.gray(` Source: ui/${ui.name}/`));
17801
+ console.log(` ${chalk19.magenta("\u25C6")} ${ui.name}`);
17802
+ console.log(chalk19.gray(` Source: ui/${ui.name}/`));
17410
17803
  }
17411
17804
  console.log("");
17412
17805
  }
17413
17806
  if (network.loadBalancer?.routes && network.loadBalancer.routes.length > 0) {
17414
- console.log(chalk18.bold(" Gateway Routes:"));
17415
- console.log(chalk18.gray(" Path \u2192 Backend"));
17416
- console.log(chalk18.gray(" " + "\u2500".repeat(50)));
17807
+ console.log(chalk19.bold(" Gateway Routes:"));
17808
+ console.log(chalk19.gray(" Path \u2192 Backend"));
17809
+ console.log(chalk19.gray(" " + "\u2500".repeat(50)));
17417
17810
  for (const route of network.loadBalancer.routes) {
17418
17811
  const pathPadded = route.path.padEnd(24);
17419
- console.log(` ${chalk18.yellow(pathPadded)} \u2192 ${route.backend}`);
17812
+ console.log(` ${chalk19.yellow(pathPadded)} \u2192 ${route.backend}`);
17420
17813
  }
17421
17814
  console.log("");
17422
17815
  }
17423
17816
  }
17424
- console.log(chalk18.bold(" Emulators:\n"));
17425
- console.log(` ${chalk18.yellow("Firebase UI")} http://localhost:4000`);
17426
- console.log(` ${chalk18.yellow("Firestore")} localhost:8080`);
17427
- console.log(` ${chalk18.yellow("Firebase Auth")} localhost:9099`);
17428
- console.log(` ${chalk18.yellow("Pub/Sub")} localhost:8085`);
17817
+ console.log(chalk19.bold(" Emulators:\n"));
17818
+ console.log(` ${chalk19.yellow("Firebase UI")} http://localhost:4000`);
17819
+ console.log(` ${chalk19.yellow("Firestore")} localhost:8080`);
17820
+ console.log(` ${chalk19.yellow("Firebase Auth")} localhost:9099`);
17821
+ console.log(` ${chalk19.yellow("Pub/Sub")} localhost:8085`);
17429
17822
  console.log("");
17430
- console.log(chalk18.bold(" Local Access:\n"));
17431
- console.log(` ${chalk18.cyan("Gateway:")} http://localhost:8000`);
17823
+ console.log(chalk19.bold(" Local Access:\n"));
17824
+ console.log(` ${chalk19.cyan("Gateway:")} http://localhost:8000`);
17432
17825
  const routesKernelConfig = getKernelConfig(config);
17433
17826
  if (routesKernelConfig) {
17434
17827
  const label = routesKernelConfig.type === "nats" ? "Kernel HTTP" : "GCP Kernel";
17435
- console.log(` ${chalk18.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17828
+ console.log(` ${chalk19.cyan(`${label}:`)}${" ".repeat(14 - label.length)}http://localhost:${routesKernelConfig.httpPort}`);
17436
17829
  if (routesKernelConfig.natsPort) {
17437
- console.log(` ${chalk18.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17830
+ console.log(` ${chalk19.cyan("Kernel NATS:")} localhost:${routesKernelConfig.natsPort}`);
17438
17831
  }
17439
17832
  }
17440
17833
  console.log("");
17441
17834
  }
17442
17835
  async function describeResources(resource) {
17443
- console.log(chalk18.bold("\n StackSolo Dev - Resource Details\n"));
17836
+ console.log(chalk19.bold("\n StackSolo Dev - Resource Details\n"));
17444
17837
  const config = await loadConfig2();
17445
17838
  const namespace = sanitizeNamespaceName(config.project.name);
17446
17839
  try {
17447
17840
  const indent = (text) => text.split("\n").map((l) => " " + l).join("\n");
17448
17841
  if (resource === "all" || resource === "pods") {
17449
- console.log(chalk18.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17842
+ console.log(chalk19.bold.cyan(" \u2550\u2550\u2550 Pods \u2550\u2550\u2550\n"));
17450
17843
  const pods = execSync2(`kubectl describe pods -n ${namespace}`, { encoding: "utf-8" });
17451
17844
  console.log(indent(pods));
17452
17845
  }
17453
17846
  if (resource === "all" || resource === "services") {
17454
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17847
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Services \u2550\u2550\u2550\n"));
17455
17848
  const services = execSync2(`kubectl describe services -n ${namespace}`, { encoding: "utf-8" });
17456
17849
  console.log(indent(services));
17457
17850
  }
17458
17851
  if (resource === "all" || resource === "deployments") {
17459
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17852
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Deployments \u2550\u2550\u2550\n"));
17460
17853
  const deployments = execSync2(`kubectl describe deployments -n ${namespace}`, { encoding: "utf-8" });
17461
17854
  console.log(indent(deployments));
17462
17855
  }
17463
17856
  if (resource === "all" || resource === "ingress") {
17464
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17857
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 Ingress \u2550\u2550\u2550\n"));
17465
17858
  try {
17466
17859
  const ingress = execSync2(`kubectl describe ingress -n ${namespace}`, { encoding: "utf-8" });
17467
17860
  console.log(indent(ingress));
17468
17861
  } catch {
17469
- console.log(chalk18.gray(" No ingress resources found"));
17862
+ console.log(chalk19.gray(" No ingress resources found"));
17470
17863
  }
17471
17864
  }
17472
17865
  if (resource === "all" || resource === "configmaps") {
17473
- console.log(chalk18.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17866
+ console.log(chalk19.bold.cyan("\n \u2550\u2550\u2550 ConfigMaps \u2550\u2550\u2550\n"));
17474
17867
  const configmaps = execSync2(`kubectl describe configmaps -n ${namespace}`, { encoding: "utf-8" });
17475
17868
  console.log(indent(configmaps));
17476
17869
  }
17477
17870
  if (!["all", "pods", "services", "deployments", "ingress", "configmaps"].includes(resource)) {
17478
- console.log(chalk18.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17871
+ console.log(chalk19.bold.cyan(` \u2550\u2550\u2550 ${resource} \u2550\u2550\u2550
17479
17872
  `));
17480
17873
  try {
17481
17874
  const podDesc = execSync2(`kubectl describe pod/${resource} -n ${namespace} 2>/dev/null || kubectl describe deployment/${resource} -n ${namespace} 2>/dev/null || kubectl describe service/${resource} -n ${namespace}`, { encoding: "utf-8" });
17482
17875
  console.log(indent(podDesc));
17483
17876
  } catch {
17484
- console.log(chalk18.yellow(` Resource '${resource}' not found`));
17485
- console.log(chalk18.gray("\n Available options: all, pods, services, deployments, ingress, configmaps"));
17486
- console.log(chalk18.gray(" Or specify a resource name like: --describe api"));
17877
+ console.log(chalk19.yellow(` Resource '${resource}' not found`));
17878
+ console.log(chalk19.gray("\n Available options: all, pods, services, deployments, ingress, configmaps"));
17879
+ console.log(chalk19.gray(" Or specify a resource name like: --describe api"));
17487
17880
  }
17488
17881
  }
17489
17882
  } catch {
17490
- console.log(chalk18.yellow(` No resources found in namespace ${namespace}`));
17491
- console.log(chalk18.gray(' Run "stacksolo dev" to start the environment\n'));
17883
+ console.log(chalk19.yellow(` No resources found in namespace ${namespace}`));
17884
+ console.log(chalk19.gray(' Run "stacksolo dev" to start the environment\n'));
17492
17885
  }
17493
17886
  console.log("");
17494
17887
  }
17495
17888
  async function checkHealth() {
17496
- console.log(chalk18.bold("\n StackSolo Dev - Health Check\n"));
17889
+ console.log(chalk19.bold("\n StackSolo Dev - Health Check\n"));
17497
17890
  const config = await loadConfig2();
17498
17891
  const namespace = sanitizeNamespaceName(config.project.name);
17499
17892
  const healthChecks = [];
@@ -17514,7 +17907,7 @@ async function checkHealth() {
17514
17907
  functionPort++;
17515
17908
  }
17516
17909
  }
17517
- console.log(chalk18.bold(" Pod Status:\n"));
17910
+ console.log(chalk19.bold(" Pod Status:\n"));
17518
17911
  try {
17519
17912
  const podOutput = execSync2(
17520
17913
  `kubectl get pods -n ${namespace} -o jsonpath='{range .items[*]}{.metadata.name}|{.status.phase}|{.status.conditions[?(@.type=="Ready")].status}{"\\n"}{end}'`,
@@ -17524,16 +17917,16 @@ async function checkHealth() {
17524
17917
  if (!line) continue;
17525
17918
  const [name, phase, ready] = line.split("|");
17526
17919
  const isHealthy = phase === "Running" && ready === "True";
17527
- const icon = isHealthy ? chalk18.green("\u2713") : chalk18.red("\u2717");
17528
- const status = isHealthy ? chalk18.green("Healthy") : chalk18.yellow(phase);
17920
+ const icon = isHealthy ? chalk19.green("\u2713") : chalk19.red("\u2717");
17921
+ const status = isHealthy ? chalk19.green("Healthy") : chalk19.yellow(phase);
17529
17922
  console.log(` ${icon} ${name.padEnd(40)} ${status}`);
17530
17923
  }
17531
17924
  } catch {
17532
- console.log(chalk18.yellow(" Unable to get pod status"));
17925
+ console.log(chalk19.yellow(" Unable to get pod status"));
17533
17926
  }
17534
- console.log(chalk18.bold("\n HTTP Endpoints:\n"));
17927
+ console.log(chalk19.bold("\n HTTP Endpoints:\n"));
17535
17928
  for (const check of healthChecks) {
17536
- const spinner = ora8({ text: `Checking ${check.name}...`, indent: 4 }).start();
17929
+ const spinner = ora9({ text: `Checking ${check.name}...`, indent: 4 }).start();
17537
17930
  try {
17538
17931
  const response = await Promise.race([
17539
17932
  fetch(`http://localhost:${check.port}${check.path}`),
@@ -17542,27 +17935,27 @@ async function checkHealth() {
17542
17935
  )
17543
17936
  ]);
17544
17937
  if (response.ok) {
17545
- spinner.succeed(`${check.name.padEnd(25)} ${chalk18.green("OK")} (port ${check.port})`);
17938
+ spinner.succeed(`${check.name.padEnd(25)} ${chalk19.green("OK")} (port ${check.port})`);
17546
17939
  } else {
17547
- spinner.warn(`${check.name.padEnd(25)} ${chalk18.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17940
+ spinner.warn(`${check.name.padEnd(25)} ${chalk19.yellow(`HTTP ${response.status}`)} (port ${check.port})`);
17548
17941
  }
17549
17942
  } catch (error) {
17550
17943
  const errMsg = error instanceof Error ? error.message : "Unknown error";
17551
17944
  if (errMsg.includes("ECONNREFUSED")) {
17552
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Connection refused")} (port ${check.port})`);
17945
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Connection refused")} (port ${check.port})`);
17553
17946
  } else if (errMsg.includes("Timeout")) {
17554
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red("Timeout")} (port ${check.port})`);
17947
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red("Timeout")} (port ${check.port})`);
17555
17948
  } else {
17556
- spinner.fail(`${check.name.padEnd(25)} ${chalk18.red(errMsg)} (port ${check.port})`);
17949
+ spinner.fail(`${check.name.padEnd(25)} ${chalk19.red(errMsg)} (port ${check.port})`);
17557
17950
  }
17558
17951
  }
17559
17952
  }
17560
- console.log(chalk18.bold("\n Tip:\n"));
17561
- console.log(chalk18.gray(' If ports show "Connection refused", try: stacksolo dev --restart'));
17562
- console.log(chalk18.gray(" This will restart all port-forwards\n"));
17953
+ console.log(chalk19.bold("\n Tip:\n"));
17954
+ console.log(chalk19.gray(' If ports show "Connection refused", try: stacksolo dev --restart'));
17955
+ console.log(chalk19.gray(" This will restart all port-forwards\n"));
17563
17956
  }
17564
17957
  async function showPorts() {
17565
- console.log(chalk18.bold("\n StackSolo Dev - Port Forward Status\n"));
17958
+ console.log(chalk19.bold("\n StackSolo Dev - Port Forward Status\n"));
17566
17959
  const config = await loadConfig2();
17567
17960
  const namespace = sanitizeNamespaceName(config.project.name);
17568
17961
  const expectedPorts = [];
@@ -17598,9 +17991,9 @@ async function showPorts() {
17598
17991
  uiPort++;
17599
17992
  }
17600
17993
  }
17601
- console.log(chalk18.bold(" Expected Port Forwards:\n"));
17602
- console.log(chalk18.gray(" Name Port Service Status"));
17603
- console.log(chalk18.gray(" " + "\u2500".repeat(75)));
17994
+ console.log(chalk19.bold(" Expected Port Forwards:\n"));
17995
+ console.log(chalk19.gray(" Name Port Service Status"));
17996
+ console.log(chalk19.gray(" " + "\u2500".repeat(75)));
17604
17997
  for (const mapping of expectedPorts) {
17605
17998
  let status;
17606
17999
  try {
@@ -17610,22 +18003,22 @@ async function showPorts() {
17610
18003
  (_, reject) => setTimeout(() => reject(new Error("Timeout")), 500)
17611
18004
  )
17612
18005
  ]);
17613
- status = chalk18.green("\u25CF Active");
18006
+ status = chalk19.green("\u25CF Active");
17614
18007
  } catch (error) {
17615
18008
  const errMsg = error instanceof Error ? error.message : "";
17616
18009
  if (errMsg.includes("ECONNREFUSED")) {
17617
- status = chalk18.red("\u25CB Not listening");
18010
+ status = chalk19.red("\u25CB Not listening");
17618
18011
  } else if (errMsg.includes("Timeout")) {
17619
- status = chalk18.yellow("\u25CB No response");
18012
+ status = chalk19.yellow("\u25CB No response");
17620
18013
  } else {
17621
- status = chalk18.blue("\u25CF TCP only");
18014
+ status = chalk19.blue("\u25CF TCP only");
17622
18015
  }
17623
18016
  }
17624
18017
  console.log(
17625
18018
  ` ${mapping.name.padEnd(30)} ${String(mapping.port).padEnd(8)} ${mapping.service.padEnd(22)} ${status}`
17626
18019
  );
17627
18020
  }
17628
- console.log(chalk18.bold("\n Active Port-Forward Processes:\n"));
18021
+ console.log(chalk19.bold("\n Active Port-Forward Processes:\n"));
17629
18022
  try {
17630
18023
  const psOutput = execSync2(`ps aux | grep 'kubectl port-forward' | grep -v grep | grep ${namespace}`, {
17631
18024
  encoding: "utf-8"
@@ -17634,21 +18027,21 @@ async function showPorts() {
17634
18027
  for (const line of psOutput.trim().split("\n")) {
17635
18028
  const match = line.match(/port-forward.*svc\/([^\s]+)\s+(\d+:\d+)/);
17636
18029
  if (match) {
17637
- console.log(chalk18.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
18030
+ console.log(chalk19.gray(` kubectl port-forward svc/${match[1]} ${match[2]}`));
17638
18031
  }
17639
18032
  }
17640
18033
  } else {
17641
- console.log(chalk18.yellow(" No active port-forward processes found"));
18034
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17642
18035
  }
17643
18036
  } catch {
17644
- console.log(chalk18.yellow(" No active port-forward processes found"));
18037
+ console.log(chalk19.yellow(" No active port-forward processes found"));
17645
18038
  }
17646
- console.log(chalk18.bold("\n Commands:\n"));
17647
- console.log(chalk18.gray(" stacksolo dev --restart Restart all port-forwards"));
17648
- console.log(chalk18.gray(" stacksolo dev --health Check endpoint health\n"));
18039
+ console.log(chalk19.bold("\n Commands:\n"));
18040
+ console.log(chalk19.gray(" stacksolo dev --restart Restart all port-forwards"));
18041
+ console.log(chalk19.gray(" stacksolo dev --health Check endpoint health\n"));
17649
18042
  }
17650
18043
  async function showServiceNames() {
17651
- console.log(chalk18.bold("\n StackSolo Dev - Service Names\n"));
18044
+ console.log(chalk19.bold("\n StackSolo Dev - Service Names\n"));
17652
18045
  const config = await loadConfig2();
17653
18046
  const namespace = sanitizeNamespaceName(config.project.name);
17654
18047
  const services = [];
@@ -17691,15 +18084,15 @@ async function showServiceNames() {
17691
18084
  if (hasGateway) {
17692
18085
  services.push({ name: "gateway", type: "gateway", k8sName: "gateway" });
17693
18086
  }
17694
- console.log(chalk18.gray(" Name Type K8s Service Name"));
17695
- console.log(chalk18.gray(" " + "\u2500".repeat(60)));
18087
+ console.log(chalk19.gray(" Name Type K8s Service Name"));
18088
+ console.log(chalk19.gray(" " + "\u2500".repeat(60)));
17696
18089
  for (const svc of services) {
17697
- const typeColor = svc.type === "kernel" ? chalk18.magenta : svc.type === "gcp-kernel" ? chalk18.magenta : svc.type === "function" ? chalk18.green : svc.type === "container" ? chalk18.blue : svc.type === "ui" ? chalk18.cyan : svc.type === "gateway" ? chalk18.yellow : chalk18.gray;
18090
+ const typeColor = svc.type === "kernel" ? chalk19.magenta : svc.type === "gcp-kernel" ? chalk19.magenta : svc.type === "function" ? chalk19.green : svc.type === "container" ? chalk19.blue : svc.type === "ui" ? chalk19.cyan : svc.type === "gateway" ? chalk19.yellow : chalk19.gray;
17698
18091
  console.log(
17699
18092
  ` ${svc.name.padEnd(25)} ${typeColor(svc.type.padEnd(12))} ${svc.k8sName}`
17700
18093
  );
17701
18094
  }
17702
- console.log(chalk18.bold("\n Running Pods:\n"));
18095
+ console.log(chalk19.bold("\n Running Pods:\n"));
17703
18096
  try {
17704
18097
  const pods = execSync2(`kubectl get pods -n ${namespace} --no-headers -o custom-columns=NAME:.metadata.name`, {
17705
18098
  encoding: "utf-8"
@@ -17707,30 +18100,30 @@ async function showServiceNames() {
17707
18100
  for (const pod of pods.trim().split("\n")) {
17708
18101
  if (pod) {
17709
18102
  const serviceName = pod.replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17710
- console.log(` ${chalk18.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk18.gray(pod)}`);
18103
+ console.log(` ${chalk19.gray("\u25CF")} ${serviceName.padEnd(25)} ${chalk19.gray(pod)}`);
17711
18104
  }
17712
18105
  }
17713
18106
  } catch {
17714
- console.log(chalk18.yellow(" No pods found"));
18107
+ console.log(chalk19.yellow(" No pods found"));
17715
18108
  }
17716
- console.log(chalk18.bold("\n Usage:\n"));
17717
- console.log(chalk18.gray(" stacksolo dev --restart <name> Restart a specific service"));
17718
- console.log(chalk18.gray(" stacksolo dev --logs <name> Tail logs for a service"));
17719
- console.log(chalk18.gray(" stacksolo dev --describe <name> Describe a service\n"));
18109
+ console.log(chalk19.bold("\n Usage:\n"));
18110
+ console.log(chalk19.gray(" stacksolo dev --restart <name> Restart a specific service"));
18111
+ console.log(chalk19.gray(" stacksolo dev --logs <name> Tail logs for a service"));
18112
+ console.log(chalk19.gray(" stacksolo dev --describe <name> Describe a service\n"));
17720
18113
  }
17721
18114
  async function restartService(service) {
17722
- console.log(chalk18.bold("\n StackSolo Dev - Restart\n"));
18115
+ console.log(chalk19.bold("\n StackSolo Dev - Restart\n"));
17723
18116
  const config = await loadConfig2();
17724
18117
  const namespace = sanitizeNamespaceName(config.project.name);
17725
18118
  if (service) {
17726
- const spinner = ora8(`Restarting pod: ${service}...`).start();
18119
+ const spinner = ora9(`Restarting pod: ${service}...`).start();
17727
18120
  try {
17728
18121
  execSync2(
17729
18122
  `kubectl delete pod -n ${namespace} -l app.kubernetes.io/name=${service} --grace-period=5`,
17730
18123
  { stdio: "pipe" }
17731
18124
  );
17732
18125
  spinner.succeed(`Pod ${service} restarted`);
17733
- const waitSpinner = ora8(`Waiting for ${service} to be ready...`).start();
18126
+ const waitSpinner = ora9(`Waiting for ${service} to be ready...`).start();
17734
18127
  try {
17735
18128
  execSync2(
17736
18129
  `kubectl wait --for=condition=ready pod -n ${namespace} -l app.kubernetes.io/name=${service} --timeout=60s`,
@@ -17742,19 +18135,19 @@ async function restartService(service) {
17742
18135
  }
17743
18136
  } catch (error) {
17744
18137
  spinner.fail(`Failed to restart ${service}`);
17745
- console.log(chalk18.gray(` Error: ${error instanceof Error ? error.message : error}`));
17746
- console.log(chalk18.gray("\n Available services:"));
18138
+ console.log(chalk19.gray(` Error: ${error instanceof Error ? error.message : error}`));
18139
+ console.log(chalk19.gray("\n Available services:"));
17747
18140
  try {
17748
18141
  const pods = execSync2(`kubectl get pods -n ${namespace} -o name`, { encoding: "utf-8" });
17749
18142
  for (const pod of pods.trim().split("\n")) {
17750
18143
  const podName = pod.replace("pod/", "").replace(/-[a-z0-9]+-[a-z0-9]+$/, "");
17751
- console.log(chalk18.gray(` ${podName}`));
18144
+ console.log(chalk19.gray(` ${podName}`));
17752
18145
  }
17753
18146
  } catch {
17754
18147
  }
17755
18148
  }
17756
18149
  } else {
17757
- const killSpinner = ora8("Stopping existing port-forwards...").start();
18150
+ const killSpinner = ora9("Stopping existing port-forwards...").start();
17758
18151
  try {
17759
18152
  execSync2(`pkill -f "kubectl port-forward.*${namespace}"`, { stdio: "pipe" });
17760
18153
  killSpinner.succeed("Port-forwards stopped");
@@ -17763,21 +18156,21 @@ async function restartService(service) {
17763
18156
  }
17764
18157
  await new Promise((resolve7) => setTimeout(resolve7, 500));
17765
18158
  console.log("");
17766
- const spinner = ora8("Restarting port-forwards...").start();
18159
+ const spinner = ora9("Restarting port-forwards...").start();
17767
18160
  portForwardProcesses.length = 0;
17768
18161
  const portMappings = await setupPortForwarding(namespace, config);
17769
18162
  spinner.succeed("Port-forwards restarted");
17770
- console.log(chalk18.bold("\n Active Forwards:\n"));
18163
+ console.log(chalk19.bold("\n Active Forwards:\n"));
17771
18164
  for (const mapping of portMappings) {
17772
18165
  const url = mapping.protocol === "http" ? `http://localhost:${mapping.localPort}` : `localhost:${mapping.localPort}`;
17773
- console.log(` ${chalk18.cyan(mapping.name.padEnd(20))} ${url}`);
18166
+ console.log(` ${chalk19.cyan(mapping.name.padEnd(20))} ${url}`);
17774
18167
  }
17775
- console.log(chalk18.bold("\n Tip:\n"));
17776
- console.log(chalk18.gray(" Run: stacksolo dev --health to verify endpoints\n"));
17777
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
18168
+ console.log(chalk19.bold("\n Tip:\n"));
18169
+ console.log(chalk19.gray(" Run: stacksolo dev --health to verify endpoints\n"));
18170
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17778
18171
  const cleanup = async () => {
17779
18172
  isShuttingDown = true;
17780
- console.log(chalk18.gray("\n Stopping port-forwards...\n"));
18173
+ console.log(chalk19.gray("\n Stopping port-forwards...\n"));
17781
18174
  for (const proc of portForwardProcesses) {
17782
18175
  try {
17783
18176
  proc.kill("SIGTERM");
@@ -17796,10 +18189,10 @@ async function restartService(service) {
17796
18189
  async function tailLogs(service) {
17797
18190
  const config = await loadConfig2();
17798
18191
  const namespace = sanitizeNamespaceName(config.project.name);
17799
- console.log(chalk18.bold("\n StackSolo Dev Logs\n"));
17800
- console.log(chalk18.gray(" Press Ctrl+C to stop\n"));
18192
+ console.log(chalk19.bold("\n StackSolo Dev Logs\n"));
18193
+ console.log(chalk19.gray(" Press Ctrl+C to stop\n"));
17801
18194
  const args = service ? ["logs", "-f", "-n", namespace, "-l", `app.kubernetes.io/name=${service}`] : ["logs", "-f", "-n", namespace, "--all-containers", "-l", "app.kubernetes.io/managed-by=stacksolo"];
17802
- const child = spawn6("kubectl", args, { stdio: "inherit" });
18195
+ const child = spawn7("kubectl", args, { stdio: "inherit" });
17803
18196
  process.on("SIGINT", () => {
17804
18197
  child.kill("SIGINT");
17805
18198
  process.exit(0);
@@ -17811,10 +18204,10 @@ async function tailLogs(service) {
17811
18204
 
17812
18205
  // src/commands/dev/install.ts
17813
18206
  import { Command as Command18 } from "commander";
17814
- import chalk19 from "chalk";
17815
- import ora9 from "ora";
17816
- import * as path23 from "path";
17817
- import * as fs20 from "fs/promises";
18207
+ import chalk20 from "chalk";
18208
+ import ora10 from "ora";
18209
+ import * as path24 from "path";
18210
+ import * as fs21 from "fs/promises";
17818
18211
  import { exec as exec14 } from "child_process";
17819
18212
  import { promisify as promisify14 } from "util";
17820
18213
  var execAsync14 = promisify14(exec14);
@@ -17822,13 +18215,13 @@ var STACKSOLO_DIR10 = ".stacksolo";
17822
18215
  var CONFIG_FILENAME8 = "stacksolo.config.json";
17823
18216
  var installCommand = new Command18("install").description("Install dependencies for all resources").option("-p, --parallel", "Install dependencies in parallel").action(async (options) => {
17824
18217
  const cwd = process.cwd();
17825
- console.log(chalk19.cyan("\n StackSolo Install\n"));
17826
- const configPath = path23.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
18218
+ console.log(chalk20.cyan("\n StackSolo Install\n"));
18219
+ const configPath = path24.join(cwd, STACKSOLO_DIR10, CONFIG_FILENAME8);
17827
18220
  let config;
17828
18221
  try {
17829
18222
  config = parseConfig(configPath);
17830
18223
  } catch {
17831
- console.log(chalk19.red(" No config found. Run `stacksolo init` first.\n"));
18224
+ console.log(chalk20.red(" No config found. Run `stacksolo init` first.\n"));
17832
18225
  return;
17833
18226
  }
17834
18227
  const directories = [];
@@ -17837,7 +18230,7 @@ var installCommand = new Command18("install").description("Install dependencies
17837
18230
  const sourceDir = fn.sourceDir?.replace(/^\.\//, "") || `functions/${fn.name}`;
17838
18231
  directories.push({
17839
18232
  name: fn.name,
17840
- path: path23.join(cwd, sourceDir),
18233
+ path: path24.join(cwd, sourceDir),
17841
18234
  type: "function"
17842
18235
  });
17843
18236
  }
@@ -17845,7 +18238,7 @@ var installCommand = new Command18("install").description("Install dependencies
17845
18238
  const sourceDir = container.sourceDir?.replace(/^\.\//, "") || `containers/${container.name}`;
17846
18239
  directories.push({
17847
18240
  name: container.name,
17848
- path: path23.join(cwd, sourceDir),
18241
+ path: path24.join(cwd, sourceDir),
17849
18242
  type: "container"
17850
18243
  });
17851
18244
  }
@@ -17853,33 +18246,33 @@ var installCommand = new Command18("install").description("Install dependencies
17853
18246
  const sourceDir = ui.sourceDir?.replace(/^\.\//, "") || `apps/${ui.name}`;
17854
18247
  directories.push({
17855
18248
  name: ui.name,
17856
- path: path23.join(cwd, sourceDir),
18249
+ path: path24.join(cwd, sourceDir),
17857
18250
  type: "ui"
17858
18251
  });
17859
18252
  }
17860
18253
  }
17861
18254
  if (directories.length === 0) {
17862
- console.log(chalk19.yellow(" No resources found in config.\n"));
18255
+ console.log(chalk20.yellow(" No resources found in config.\n"));
17863
18256
  return;
17864
18257
  }
17865
- console.log(chalk19.gray(` Found ${directories.length} resource(s) to install:
18258
+ console.log(chalk20.gray(` Found ${directories.length} resource(s) to install:
17866
18259
  `));
17867
18260
  const validDirs = [];
17868
18261
  for (const dir of directories) {
17869
18262
  try {
17870
- await fs20.access(path23.join(dir.path, "package.json"));
18263
+ await fs21.access(path24.join(dir.path, "package.json"));
17871
18264
  validDirs.push(dir);
17872
- console.log(chalk19.gray(` - ${dir.name} (${dir.type})`));
18265
+ console.log(chalk20.gray(` - ${dir.name} (${dir.type})`));
17873
18266
  } catch {
17874
18267
  }
17875
18268
  }
17876
18269
  if (validDirs.length === 0) {
17877
- console.log(chalk19.yellow(" No resources with package.json found.\n"));
18270
+ console.log(chalk20.yellow(" No resources with package.json found.\n"));
17878
18271
  return;
17879
18272
  }
17880
18273
  console.log("");
17881
18274
  const installDir = async (dir) => {
17882
- const spinner = ora9(`Installing ${dir.name}...`).start();
18275
+ const spinner = ora10(`Installing ${dir.name}...`).start();
17883
18276
  try {
17884
18277
  await execAsync14("npm install", { cwd: dir.path, timeout: 12e4 });
17885
18278
  spinner.succeed(`Installed ${dir.name}`);
@@ -17902,34 +18295,34 @@ var installCommand = new Command18("install").description("Install dependencies
17902
18295
  const failed = results.filter((r) => !r.success).length;
17903
18296
  console.log("");
17904
18297
  if (failed === 0) {
17905
- console.log(chalk19.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
18298
+ console.log(chalk20.green(` \u2713 Installed dependencies for ${succeeded} resource(s)
17906
18299
  `));
17907
18300
  } else {
17908
- console.log(chalk19.yellow(` Installed ${succeeded}, failed ${failed}
18301
+ console.log(chalk20.yellow(` Installed ${succeeded}, failed ${failed}
17909
18302
  `));
17910
18303
  }
17911
18304
  });
17912
18305
 
17913
18306
  // src/commands/dev/serve.ts
17914
18307
  import { Command as Command19 } from "commander";
17915
- import chalk20 from "chalk";
18308
+ import chalk21 from "chalk";
17916
18309
  var serveCommand = new Command19("serve").description("Start the StackSolo API server").option("-p, --port <port>", "Port to listen on", "3000").option("--host <host>", "Host to bind to", "localhost").action(async (options) => {
17917
- console.log(chalk20.bold("\n StackSolo Server\n"));
18310
+ console.log(chalk21.bold("\n StackSolo Server\n"));
17918
18311
  const port = parseInt(options.port, 10);
17919
18312
  const host = options.host;
17920
- console.log(chalk20.gray(` Starting API server on ${host}:${port}...`));
18313
+ console.log(chalk21.gray(` Starting API server on ${host}:${port}...`));
17921
18314
  try {
17922
18315
  const { startServer } = await import("@stacksolo/api");
17923
18316
  await startServer({ port, host });
17924
18317
  } catch (error) {
17925
- const { spawn: spawn7 } = await import("child_process");
17926
- const path27 = await import("path");
18318
+ const { spawn: spawn8 } = await import("child_process");
18319
+ const path28 = await import("path");
17927
18320
  const { fileURLToPath } = await import("url");
17928
- const __dirname = path27.dirname(fileURLToPath(import.meta.url));
17929
- const apiPath = path27.resolve(__dirname, "../../api/dist/index.js");
17930
- console.log(chalk20.gray(` Spawning API from ${apiPath}...
18321
+ const __dirname = path28.dirname(fileURLToPath(import.meta.url));
18322
+ const apiPath = path28.resolve(__dirname, "../../api/dist/index.js");
18323
+ console.log(chalk21.gray(` Spawning API from ${apiPath}...
17931
18324
  `));
17932
- const child = spawn7("node", [apiPath], {
18325
+ const child = spawn8("node", [apiPath], {
17933
18326
  env: {
17934
18327
  ...process.env,
17935
18328
  PORT: String(port),
@@ -17938,7 +18331,7 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
17938
18331
  stdio: "inherit"
17939
18332
  });
17940
18333
  child.on("error", (err) => {
17941
- console.log(chalk20.red(` Failed to start server: ${err.message}
18334
+ console.log(chalk21.red(` Failed to start server: ${err.message}
17942
18335
  `));
17943
18336
  process.exit(1);
17944
18337
  });
@@ -17956,21 +18349,21 @@ var serveCommand = new Command19("serve").description("Start the StackSolo API s
17956
18349
 
17957
18350
  // src/commands/config/config.ts
17958
18351
  import { Command as Command20 } from "commander";
17959
- import chalk21 from "chalk";
17960
- import * as path24 from "path";
18352
+ import chalk22 from "chalk";
18353
+ import * as path25 from "path";
17961
18354
  var STACKSOLO_DIR11 = ".stacksolo";
17962
18355
  var CONFIG_FILENAME9 = "stacksolo.config.json";
17963
18356
  function getConfigPath6() {
17964
- return path24.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
18357
+ return path25.join(process.cwd(), STACKSOLO_DIR11, CONFIG_FILENAME9);
17965
18358
  }
17966
18359
  async function loadConfig3(configPath) {
17967
18360
  try {
17968
18361
  return parseConfig(configPath);
17969
18362
  } catch (error) {
17970
- console.log(chalk21.red(`
18363
+ console.log(chalk22.red(`
17971
18364
  Error: Could not read ${STACKSOLO_DIR11}/${CONFIG_FILENAME9}
17972
18365
  `));
17973
- console.log(chalk21.gray(` ${error}`));
18366
+ console.log(chalk22.gray(` ${error}`));
17974
18367
  return null;
17975
18368
  }
17976
18369
  }
@@ -17982,75 +18375,75 @@ var showCommand2 = new Command20("show").description("Display the current config
17982
18375
  console.log(JSON.stringify(config, null, 2));
17983
18376
  return;
17984
18377
  }
17985
- console.log(chalk21.bold("\n StackSolo Configuration\n"));
17986
- console.log(chalk21.cyan(" Project:"));
17987
- console.log(chalk21.white(` Name: ${config.project.name}`));
17988
- console.log(chalk21.white(` Region: ${config.project.region}`));
17989
- console.log(chalk21.white(` GCP Project: ${config.project.gcpProjectId}`));
18378
+ console.log(chalk22.bold("\n StackSolo Configuration\n"));
18379
+ console.log(chalk22.cyan(" Project:"));
18380
+ console.log(chalk22.white(` Name: ${config.project.name}`));
18381
+ console.log(chalk22.white(` Region: ${config.project.region}`));
18382
+ console.log(chalk22.white(` GCP Project: ${config.project.gcpProjectId}`));
17990
18383
  const { buckets, secrets, topics, queues, crons } = config.project;
17991
18384
  if (buckets?.length) {
17992
- console.log(chalk21.cyan("\n Buckets:"));
18385
+ console.log(chalk22.cyan("\n Buckets:"));
17993
18386
  buckets.forEach((b) => {
17994
- console.log(chalk21.white(` - ${b.name}`) + chalk21.gray(` (${b.storageClass || "STANDARD"})`));
18387
+ console.log(chalk22.white(` - ${b.name}`) + chalk22.gray(` (${b.storageClass || "STANDARD"})`));
17995
18388
  });
17996
18389
  }
17997
18390
  if (secrets?.length) {
17998
- console.log(chalk21.cyan("\n Secrets:"));
18391
+ console.log(chalk22.cyan("\n Secrets:"));
17999
18392
  secrets.forEach((s) => {
18000
- console.log(chalk21.white(` - ${s.name}`));
18393
+ console.log(chalk22.white(` - ${s.name}`));
18001
18394
  });
18002
18395
  }
18003
18396
  if (topics?.length) {
18004
- console.log(chalk21.cyan("\n Topics:"));
18397
+ console.log(chalk22.cyan("\n Topics:"));
18005
18398
  topics.forEach((t) => {
18006
- console.log(chalk21.white(` - ${t.name}`));
18399
+ console.log(chalk22.white(` - ${t.name}`));
18007
18400
  });
18008
18401
  }
18009
18402
  if (queues?.length) {
18010
- console.log(chalk21.cyan("\n Queues:"));
18403
+ console.log(chalk22.cyan("\n Queues:"));
18011
18404
  queues.forEach((q) => {
18012
- console.log(chalk21.white(` - ${q.name}`));
18405
+ console.log(chalk22.white(` - ${q.name}`));
18013
18406
  });
18014
18407
  }
18015
18408
  if (crons?.length) {
18016
- console.log(chalk21.cyan("\n Scheduled Jobs:"));
18409
+ console.log(chalk22.cyan("\n Scheduled Jobs:"));
18017
18410
  crons.forEach((c) => {
18018
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.schedule})`));
18411
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.schedule})`));
18019
18412
  });
18020
18413
  }
18021
18414
  const networks = config.project.networks || [];
18022
18415
  if (networks.length) {
18023
18416
  networks.forEach((network) => {
18024
- console.log(chalk21.cyan(`
18417
+ console.log(chalk22.cyan(`
18025
18418
  Network: ${network.name}`));
18026
18419
  if (network.subnets?.length) {
18027
- console.log(chalk21.gray(" Subnets:"));
18420
+ console.log(chalk22.gray(" Subnets:"));
18028
18421
  network.subnets.forEach((s) => {
18029
- console.log(chalk21.white(` - ${s.name}`) + chalk21.gray(` (${s.ipCidrRange})`));
18422
+ console.log(chalk22.white(` - ${s.name}`) + chalk22.gray(` (${s.ipCidrRange})`));
18030
18423
  });
18031
18424
  }
18032
18425
  if (network.containers?.length) {
18033
- console.log(chalk21.gray(" Containers:"));
18426
+ console.log(chalk22.gray(" Containers:"));
18034
18427
  network.containers.forEach((c) => {
18035
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memory || "256Mi"})`));
18428
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memory || "256Mi"})`));
18036
18429
  });
18037
18430
  }
18038
18431
  if (network.functions?.length) {
18039
- console.log(chalk21.gray(" Functions:"));
18432
+ console.log(chalk22.gray(" Functions:"));
18040
18433
  network.functions.forEach((f) => {
18041
- console.log(chalk21.white(` - ${f.name}`) + chalk21.gray(` (${f.runtime || "nodejs20"})`));
18434
+ console.log(chalk22.white(` - ${f.name}`) + chalk22.gray(` (${f.runtime || "nodejs20"})`));
18042
18435
  });
18043
18436
  }
18044
18437
  if (network.databases?.length) {
18045
- console.log(chalk21.gray(" Databases:"));
18438
+ console.log(chalk22.gray(" Databases:"));
18046
18439
  network.databases.forEach((d) => {
18047
- console.log(chalk21.white(` - ${d.name}`) + chalk21.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18440
+ console.log(chalk22.white(` - ${d.name}`) + chalk22.gray(` (${d.databaseVersion || "POSTGRES_15"})`));
18048
18441
  });
18049
18442
  }
18050
18443
  if (network.caches?.length) {
18051
- console.log(chalk21.gray(" Caches:"));
18444
+ console.log(chalk22.gray(" Caches:"));
18052
18445
  network.caches.forEach((c) => {
18053
- console.log(chalk21.white(` - ${c.name}`) + chalk21.gray(` (${c.memorySizeGb || 1}GB)`));
18446
+ console.log(chalk22.white(` - ${c.name}`) + chalk22.gray(` (${c.memorySizeGb || 1}GB)`));
18054
18447
  });
18055
18448
  }
18056
18449
  });
@@ -18068,20 +18461,20 @@ var resourcesCommand = new Command20("resources").description("List all resource
18068
18461
  console.log(JSON.stringify(resolved.resources, null, 2));
18069
18462
  return;
18070
18463
  }
18071
- console.log(chalk21.bold("\n Resources to Create\n"));
18072
- console.log(chalk21.gray(` Order of creation (${order.length} resources):
18464
+ console.log(chalk22.bold("\n Resources to Create\n"));
18465
+ console.log(chalk22.gray(` Order of creation (${order.length} resources):
18073
18466
  `));
18074
18467
  order.forEach((id, index) => {
18075
18468
  const resource = resolved.resources.find((r) => r.id === id);
18076
18469
  if (!resource) return;
18077
- const deps = resource.dependsOn.length ? chalk21.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18470
+ const deps = resource.dependsOn.length ? chalk22.gray(` \u2192 depends on: ${resource.dependsOn.join(", ")}`) : "";
18078
18471
  console.log(
18079
- chalk21.white(` ${String(index + 1).padStart(2)}. `) + chalk21.cyan(resource.type) + chalk21.white(` "${resource.name}"`) + deps
18472
+ chalk22.white(` ${String(index + 1).padStart(2)}. `) + chalk22.cyan(resource.type) + chalk22.white(` "${resource.name}"`) + deps
18080
18473
  );
18081
18474
  });
18082
18475
  console.log("");
18083
18476
  } catch (error) {
18084
- console.log(chalk21.red(`
18477
+ console.log(chalk22.red(`
18085
18478
  Error resolving config: ${error}
18086
18479
  `));
18087
18480
  }
@@ -18092,24 +18485,24 @@ var validateCommand = new Command20("validate").description("Validate the config
18092
18485
  if (!config) return;
18093
18486
  const result = validateConfig(config);
18094
18487
  if (result.valid) {
18095
- console.log(chalk21.green("\n \u2713 Configuration is valid\n"));
18488
+ console.log(chalk22.green("\n \u2713 Configuration is valid\n"));
18096
18489
  try {
18097
18490
  const resolved = resolveConfig(config);
18098
18491
  const order = topologicalSort(resolved.resources);
18099
- console.log(chalk21.gray(` ${resolved.resources.length} resources defined`));
18100
- console.log(chalk21.gray(` No circular dependencies detected
18492
+ console.log(chalk22.gray(` ${resolved.resources.length} resources defined`));
18493
+ console.log(chalk22.gray(` No circular dependencies detected
18101
18494
  `));
18102
18495
  } catch (error) {
18103
- console.log(chalk21.yellow(`
18496
+ console.log(chalk22.yellow(`
18104
18497
  \u26A0 Warning: ${error}
18105
18498
  `));
18106
18499
  }
18107
18500
  } else {
18108
- console.log(chalk21.red("\n \u2717 Configuration has errors:\n"));
18501
+ console.log(chalk22.red("\n \u2717 Configuration has errors:\n"));
18109
18502
  result.errors.forEach((err) => {
18110
- console.log(chalk21.red(` - ${err.path}: ${err.message}`));
18503
+ console.log(chalk22.red(` - ${err.path}: ${err.message}`));
18111
18504
  if (err.value !== void 0) {
18112
- console.log(chalk21.gray(` value: ${JSON.stringify(err.value)}`));
18505
+ console.log(chalk22.gray(` value: ${JSON.stringify(err.value)}`));
18113
18506
  }
18114
18507
  });
18115
18508
  console.log("");
@@ -18120,7 +18513,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18120
18513
  const configPath = getConfigPath6();
18121
18514
  const config = await loadConfig3(configPath);
18122
18515
  if (!config) return;
18123
- console.log(chalk21.bold("\n Resource References\n"));
18516
+ console.log(chalk22.bold("\n Resource References\n"));
18124
18517
  const allReferences = [];
18125
18518
  const networks = config.project.networks || [];
18126
18519
  networks.forEach((network) => {
@@ -18154,7 +18547,7 @@ var referencesCommand = new Command20("references").description("Show all resour
18154
18547
  });
18155
18548
  });
18156
18549
  if (allReferences.length === 0) {
18157
- console.log(chalk21.gray(" No references found.\n"));
18550
+ console.log(chalk22.gray(" No references found.\n"));
18158
18551
  return;
18159
18552
  }
18160
18553
  const byType = /* @__PURE__ */ new Map();
@@ -18166,11 +18559,11 @@ var referencesCommand = new Command20("references").description("Show all resour
18166
18559
  byType.get(type).push(ref);
18167
18560
  });
18168
18561
  byType.forEach((refs, type) => {
18169
- console.log(chalk21.cyan(` @${type}:`));
18562
+ console.log(chalk22.cyan(` @${type}:`));
18170
18563
  refs.forEach((ref) => {
18171
18564
  const property = ref.parsed?.property ? `.${ref.parsed.property}` : "";
18172
18565
  console.log(
18173
- chalk21.white(` ${ref.reference}`) + chalk21.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18566
+ chalk22.white(` ${ref.reference}`) + chalk22.gray(` \u2192 ${ref.location}.env.${ref.envVar}`)
18174
18567
  );
18175
18568
  });
18176
18569
  console.log("");
@@ -18183,47 +18576,47 @@ configCommand.action(() => {
18183
18576
 
18184
18577
  // src/commands/config/env.ts
18185
18578
  import { Command as Command21 } from "commander";
18186
- import chalk22 from "chalk";
18187
- import ora10 from "ora";
18188
- import * as fs21 from "fs/promises";
18189
- import * as path25 from "path";
18579
+ import chalk23 from "chalk";
18580
+ import ora11 from "ora";
18581
+ import * as fs22 from "fs/promises";
18582
+ import * as path26 from "path";
18190
18583
  var envCommand = new Command21("env").description("Generate environment configuration files").option("--stdout", "Print to stdout instead of writing files").option("--format <format>", "Output format: dotenv, json, typescript", "dotenv").action(async (options) => {
18191
18584
  const cwd = process.cwd();
18192
- console.log(chalk22.bold("\n StackSolo Env\n"));
18193
- const configPath = path25.join(cwd, ".stacksolo", "config.json");
18585
+ console.log(chalk23.bold("\n StackSolo Env\n"));
18586
+ const configPath = path26.join(cwd, ".stacksolo", "config.json");
18194
18587
  let config;
18195
18588
  try {
18196
- const configData = await fs21.readFile(configPath, "utf-8");
18589
+ const configData = await fs22.readFile(configPath, "utf-8");
18197
18590
  config = JSON.parse(configData);
18198
18591
  } catch {
18199
- console.log(chalk22.red(" Not initialized. Run `stacksolo init` first.\n"));
18592
+ console.log(chalk23.red(" Not initialized. Run `stacksolo init` first.\n"));
18200
18593
  return;
18201
18594
  }
18202
18595
  const apiConnected = await checkApiConnection();
18203
18596
  if (!apiConnected) {
18204
- console.log(chalk22.red(" StackSolo API not running."));
18205
- console.log(chalk22.gray(" Start with: stacksolo serve\n"));
18597
+ console.log(chalk23.red(" StackSolo API not running."));
18598
+ console.log(chalk23.gray(" Start with: stacksolo serve\n"));
18206
18599
  return;
18207
18600
  }
18208
- const spinner = ora10("Checking deployment...").start();
18601
+ const spinner = ora11("Checking deployment...").start();
18209
18602
  const statusResult = await api.deployments.status(config.projectId);
18210
18603
  if (!statusResult.success || !statusResult.data) {
18211
18604
  spinner.fail("Could not get deployment status");
18212
- console.log(chalk22.gray("\n Deploy first with: stacksolo deploy\n"));
18605
+ console.log(chalk23.gray("\n Deploy first with: stacksolo deploy\n"));
18213
18606
  return;
18214
18607
  }
18215
18608
  if (statusResult.data.status !== "succeeded") {
18216
18609
  spinner.fail("No successful deployment");
18217
- console.log(chalk22.gray(`
18610
+ console.log(chalk23.gray(`
18218
18611
  Current status: ${statusResult.data.status}`));
18219
- console.log(chalk22.gray(" Deploy first with: stacksolo deploy\n"));
18612
+ console.log(chalk23.gray(" Deploy first with: stacksolo deploy\n"));
18220
18613
  return;
18221
18614
  }
18222
18615
  spinner.text = "Generating configuration...";
18223
18616
  const configResult = await api.deployments.generateConfig(config.projectId);
18224
18617
  if (!configResult.success || !configResult.data) {
18225
18618
  spinner.fail("Failed to generate config");
18226
- console.log(chalk22.red(` ${configResult.error}
18619
+ console.log(chalk23.red(` ${configResult.error}
18227
18620
  `));
18228
18621
  return;
18229
18622
  }
@@ -18232,38 +18625,38 @@ var envCommand = new Command21("env").description("Generate environment configur
18232
18625
  const tsConfigPath = configResult.data.configPath;
18233
18626
  if (options.stdout) {
18234
18627
  try {
18235
- console.log(chalk22.gray("\n .env.local:"));
18236
- const envContent = await fs21.readFile(envPath, "utf-8");
18628
+ console.log(chalk23.gray("\n .env.local:"));
18629
+ const envContent = await fs22.readFile(envPath, "utf-8");
18237
18630
  console.log(envContent);
18238
- console.log(chalk22.gray("\n stacksolo.config.ts:"));
18239
- const tsContent = await fs21.readFile(tsConfigPath, "utf-8");
18631
+ console.log(chalk23.gray("\n stacksolo.config.ts:"));
18632
+ const tsContent = await fs22.readFile(tsConfigPath, "utf-8");
18240
18633
  console.log(tsContent);
18241
18634
  } catch (error) {
18242
- console.log(chalk22.red(` Error reading files: ${error}
18635
+ console.log(chalk23.red(` Error reading files: ${error}
18243
18636
  `));
18244
18637
  }
18245
18638
  } else {
18246
- console.log(chalk22.green("\n Files generated:"));
18247
- console.log(chalk22.gray(` ${envPath}`));
18248
- console.log(chalk22.gray(` ${tsConfigPath}`));
18639
+ console.log(chalk23.green("\n Files generated:"));
18640
+ console.log(chalk23.gray(` ${envPath}`));
18641
+ console.log(chalk23.gray(` ${tsConfigPath}`));
18249
18642
  console.log("");
18250
- console.log(chalk22.gray(" Add to .gitignore:"));
18251
- console.log(chalk22.gray(" .env.local"));
18643
+ console.log(chalk23.gray(" Add to .gitignore:"));
18644
+ console.log(chalk23.gray(" .env.local"));
18252
18645
  console.log("");
18253
- console.log(chalk22.gray(" Usage in your app:"));
18254
- console.log(chalk22.gray(" import { config } from './stacksolo.config';"));
18255
- console.log(chalk22.gray(" const dbUrl = config.database?.url;\n"));
18646
+ console.log(chalk23.gray(" Usage in your app:"));
18647
+ console.log(chalk23.gray(" import { config } from './stacksolo.config';"));
18648
+ console.log(chalk23.gray(" const dbUrl = config.database?.url;\n"));
18256
18649
  }
18257
18650
  });
18258
18651
 
18259
18652
  // src/commands/config/register.ts
18260
18653
  import { Command as Command22 } from "commander";
18261
- import chalk23 from "chalk";
18262
- import * as path26 from "path";
18654
+ import chalk24 from "chalk";
18655
+ import * as path27 from "path";
18263
18656
  var STACKSOLO_DIR12 = ".stacksolo";
18264
18657
  var CONFIG_FILENAME10 = "stacksolo.config.json";
18265
18658
  function getConfigPath7() {
18266
- return path26.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18659
+ return path27.join(process.cwd(), STACKSOLO_DIR12, CONFIG_FILENAME10);
18267
18660
  }
18268
18661
  var registerCommand = new Command22("register").description("Register the current project in the global registry").option("-f, --force", "Overwrite existing registration").action(async (options) => {
18269
18662
  const configPath = getConfigPath7();
@@ -18271,11 +18664,11 @@ var registerCommand = new Command22("register").description("Register the curren
18271
18664
  try {
18272
18665
  config = parseConfig(configPath);
18273
18666
  } catch (error) {
18274
- console.log(chalk23.red(`
18667
+ console.log(chalk24.red(`
18275
18668
  Error: Could not read ${STACKSOLO_DIR12}/${CONFIG_FILENAME10}
18276
18669
  `));
18277
- console.log(chalk23.gray(` ${error}`));
18278
- console.log(chalk23.gray(`
18670
+ console.log(chalk24.gray(` ${error}`));
18671
+ console.log(chalk24.gray(`
18279
18672
  Run 'stacksolo init' to create a project first.
18280
18673
  `));
18281
18674
  process.exit(1);
@@ -18285,21 +18678,21 @@ var registerCommand = new Command22("register").description("Register the curren
18285
18678
  if (existingByPath) {
18286
18679
  if (options.force) {
18287
18680
  await registry3.unregisterProject(existingByPath.id);
18288
- console.log(chalk23.yellow(` Updating registration for "${existingByPath.name}"...`));
18681
+ console.log(chalk24.yellow(` Updating registration for "${existingByPath.name}"...`));
18289
18682
  } else {
18290
- console.log(chalk23.yellow(`
18683
+ console.log(chalk24.yellow(`
18291
18684
  Project already registered as "${existingByPath.name}"`));
18292
- console.log(chalk23.gray(` Use --force to update the registration.
18685
+ console.log(chalk24.gray(` Use --force to update the registration.
18293
18686
  `));
18294
18687
  return;
18295
18688
  }
18296
18689
  }
18297
18690
  const existingByName = await registry3.findProjectByName(config.project.name);
18298
18691
  if (existingByName && existingByName.configPath !== configPath) {
18299
- console.log(chalk23.red(`
18692
+ console.log(chalk24.red(`
18300
18693
  Error: Project name "${config.project.name}" is already registered`));
18301
- console.log(chalk23.gray(` Registered path: ${existingByName.configPath}`));
18302
- console.log(chalk23.gray(` Choose a different project name in stacksolo.config.json
18694
+ console.log(chalk24.gray(` Registered path: ${existingByName.configPath}`));
18695
+ console.log(chalk24.gray(` Choose a different project name in stacksolo.config.json
18303
18696
  `));
18304
18697
  process.exit(1);
18305
18698
  }
@@ -18309,42 +18702,42 @@ var registerCommand = new Command22("register").description("Register the curren
18309
18702
  region: config.project.region,
18310
18703
  configPath
18311
18704
  });
18312
- console.log(chalk23.green(`
18705
+ console.log(chalk24.green(`
18313
18706
  \u2713 Project registered: ${project.name}
18314
18707
  `));
18315
- console.log(chalk23.gray(` GCP Project: ${project.gcpProjectId}`));
18316
- console.log(chalk23.gray(` Region: ${project.region}`));
18317
- console.log(chalk23.gray(` Config: ${project.configPath}`));
18708
+ console.log(chalk24.gray(` GCP Project: ${project.gcpProjectId}`));
18709
+ console.log(chalk24.gray(` Region: ${project.region}`));
18710
+ console.log(chalk24.gray(` Config: ${project.configPath}`));
18318
18711
  console.log("");
18319
- console.log(chalk23.cyan(" Next steps:"));
18320
- console.log(chalk23.gray(" stacksolo list # View all registered projects"));
18321
- console.log(chalk23.gray(" stacksolo deploy # Deploy the project"));
18712
+ console.log(chalk24.cyan(" Next steps:"));
18713
+ console.log(chalk24.gray(" stacksolo list # View all registered projects"));
18714
+ console.log(chalk24.gray(" stacksolo deploy # Deploy the project"));
18322
18715
  console.log("");
18323
18716
  });
18324
18717
 
18325
18718
  // src/commands/config/unregister.ts
18326
18719
  import { Command as Command23 } from "commander";
18327
- import chalk24 from "chalk";
18720
+ import chalk25 from "chalk";
18328
18721
  import inquirer5 from "inquirer";
18329
18722
  var unregisterCommand = new Command23("unregister").description("Remove a project from the global registry").argument("<project>", "Project name to unregister").option("-y, --yes", "Skip confirmation prompt").action(async (projectName, options) => {
18330
18723
  const registry3 = getRegistry();
18331
18724
  const project = await registry3.findProjectByName(projectName);
18332
18725
  if (!project) {
18333
- console.log(chalk24.red(`
18726
+ console.log(chalk25.red(`
18334
18727
  Error: Project "${projectName}" not found.
18335
18728
  `));
18336
- console.log(chalk24.gray(" Run `stacksolo list` to see registered projects.\n"));
18729
+ console.log(chalk25.gray(" Run `stacksolo list` to see registered projects.\n"));
18337
18730
  process.exit(1);
18338
18731
  }
18339
18732
  const resources = await registry3.findResourcesByProject(project.id);
18340
18733
  if (!options.yes) {
18341
- console.log(chalk24.yellow(`
18734
+ console.log(chalk25.yellow(`
18342
18735
  Warning: This will remove "${projectName}" from the registry.`));
18343
18736
  if (resources.length > 0) {
18344
- console.log(chalk24.yellow(` This project has ${resources.length} registered resource(s).`));
18737
+ console.log(chalk25.yellow(` This project has ${resources.length} registered resource(s).`));
18345
18738
  }
18346
- console.log(chalk24.gray("\n Note: This does NOT destroy deployed cloud resources."));
18347
- console.log(chalk24.gray(" To destroy resources, run `stacksolo destroy` first.\n"));
18739
+ console.log(chalk25.gray("\n Note: This does NOT destroy deployed cloud resources."));
18740
+ console.log(chalk25.gray(" To destroy resources, run `stacksolo destroy` first.\n"));
18348
18741
  const { confirm } = await inquirer5.prompt([
18349
18742
  {
18350
18743
  type: "confirm",
@@ -18354,17 +18747,17 @@ var unregisterCommand = new Command23("unregister").description("Remove a projec
18354
18747
  }
18355
18748
  ]);
18356
18749
  if (!confirm) {
18357
- console.log(chalk24.gray("\n Cancelled.\n"));
18750
+ console.log(chalk25.gray("\n Cancelled.\n"));
18358
18751
  return;
18359
18752
  }
18360
18753
  }
18361
18754
  await registry3.unregisterProject(project.id);
18362
- console.log(chalk24.green(`
18755
+ console.log(chalk25.green(`
18363
18756
  \u2713 Project "${projectName}" removed from registry.
18364
18757
  `));
18365
18758
  if (project.configPath) {
18366
- console.log(chalk24.gray(` Config file still exists at: ${project.configPath}`));
18367
- console.log(chalk24.gray(" Run `stacksolo register` to re-register this project.\n"));
18759
+ console.log(chalk25.gray(` Config file still exists at: ${project.configPath}`));
18760
+ console.log(chalk25.gray(" Run `stacksolo register` to re-register this project.\n"));
18368
18761
  }
18369
18762
  });
18370
18763