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