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