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