@geekmidas/cli 1.10.14 → 1.10.16

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{bundler-Di5Gz9Ou.mjs → bundler-B4AackW5.mjs} +2 -2
  3. package/dist/{bundler-Di5Gz9Ou.mjs.map → bundler-B4AackW5.mjs.map} +1 -1
  4. package/dist/{bundler-DVJkwNMQ.cjs → bundler-BhhfkI9T.cjs} +2 -2
  5. package/dist/{bundler-DVJkwNMQ.cjs.map → bundler-BhhfkI9T.cjs.map} +1 -1
  6. package/dist/config.d.cts +2 -2
  7. package/dist/config.d.mts +2 -2
  8. package/dist/{fullstack-secrets-D9rjTNyx.cjs → fullstack-secrets-DOHBU4Rp.cjs} +110 -4
  9. package/dist/fullstack-secrets-DOHBU4Rp.cjs.map +1 -0
  10. package/dist/{fullstack-secrets-BIFFv4UZ.mjs → fullstack-secrets-x2Kffx7-.mjs} +99 -5
  11. package/dist/fullstack-secrets-x2Kffx7-.mjs.map +1 -0
  12. package/dist/{index-UCsZ_Vkw.d.cts → index-BkibYzso.d.cts} +15 -4
  13. package/dist/index-BkibYzso.d.cts.map +1 -0
  14. package/dist/{index-gXAGDSGu.d.mts → index-CY-ieuRp.d.mts} +15 -4
  15. package/dist/index-CY-ieuRp.d.mts.map +1 -0
  16. package/dist/index.cjs +326 -46
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.mjs +326 -46
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/openapi-BYxAWwok.cjs.map +1 -1
  21. package/dist/openapi-DenF-okj.mjs.map +1 -1
  22. package/dist/openapi.d.cts +1 -1
  23. package/dist/openapi.d.mts +1 -1
  24. package/dist/{reconcile-DxTEausy.mjs → reconcile-BLh6rswz.mjs} +2 -2
  25. package/dist/{reconcile-DxTEausy.mjs.map → reconcile-BLh6rswz.mjs.map} +1 -1
  26. package/dist/{reconcile-LaaJkFlO.cjs → reconcile-Ch7sIcf8.cjs} +2 -2
  27. package/dist/{reconcile-LaaJkFlO.cjs.map → reconcile-Ch7sIcf8.cjs.map} +1 -1
  28. package/dist/{storage-6GBoLCYF.cjs → storage-B1wvztiJ.cjs} +14 -2
  29. package/dist/{storage-DMf420PP.mjs.map → storage-B1wvztiJ.cjs.map} +1 -1
  30. package/dist/{storage-BFqrVsip.mjs → storage-Cs4WBsc4.mjs} +1 -1
  31. package/dist/{storage-DCqjCiDn.cjs → storage-DOEtT2Hr.cjs} +1 -1
  32. package/dist/{storage-DMf420PP.mjs → storage-dbb9RyBl.mjs} +14 -2
  33. package/dist/{storage-6GBoLCYF.cjs.map → storage-dbb9RyBl.mjs.map} +1 -1
  34. package/dist/{sync-DjD_TeNX.mjs → sync-COnAugP-.mjs} +1 -1
  35. package/dist/sync-D1Pa30oV.cjs +4 -0
  36. package/dist/{sync-DIGGOxCw.cjs → sync-DGXXSk2v.cjs} +2 -2
  37. package/dist/{sync-DIGGOxCw.cjs.map → sync-DGXXSk2v.cjs.map} +1 -1
  38. package/dist/{sync-Do9O7QZ8.mjs → sync-D_NowTkZ.mjs} +2 -2
  39. package/dist/{sync-Do9O7QZ8.mjs.map → sync-D_NowTkZ.mjs.map} +1 -1
  40. package/dist/{types-JvWj5Ckc.d.cts → types-DdHfUbxk.d.cts} +13 -3
  41. package/dist/types-DdHfUbxk.d.cts.map +1 -0
  42. package/dist/{types-DiV9Mbvc.d.mts → types-OszPdw9m.d.mts} +13 -3
  43. package/dist/types-OszPdw9m.d.mts.map +1 -0
  44. package/dist/workspace/index.d.cts +2 -2
  45. package/dist/workspace/index.d.mts +2 -2
  46. package/dist/workspace-4SP3Gx4Y.cjs.map +1 -1
  47. package/dist/workspace-D4z4A4cq.mjs.map +1 -1
  48. package/package.json +4 -4
  49. package/src/dev/__tests__/entry.spec.ts +3 -5
  50. package/src/dev/__tests__/index.spec.ts +5 -5
  51. package/src/dev/index.ts +10 -10
  52. package/src/docker/compose.ts +134 -2
  53. package/src/init/__tests__/generators.spec.ts +84 -0
  54. package/src/init/generators/docker.ts +128 -16
  55. package/src/init/index.ts +26 -1
  56. package/src/init/templates/index.ts +28 -0
  57. package/src/secrets/__tests__/generator.spec.ts +183 -0
  58. package/src/secrets/generator.ts +116 -4
  59. package/src/secrets/storage.ts +14 -0
  60. package/src/secrets/types.ts +11 -1
  61. package/src/setup/__tests__/reconcile-secrets.spec.ts +86 -0
  62. package/src/setup/index.ts +64 -1
  63. package/src/test/__tests__/index.spec.ts +1 -4
  64. package/src/types.ts +13 -1
  65. package/src/workspace/types.ts +13 -2
  66. package/dist/fullstack-secrets-BIFFv4UZ.mjs.map +0 -1
  67. package/dist/fullstack-secrets-D9rjTNyx.cjs.map +0 -1
  68. package/dist/index-UCsZ_Vkw.d.cts.map +0 -1
  69. package/dist/index-gXAGDSGu.d.mts.map +0 -1
  70. package/dist/sync-BVNso6AA.cjs +0 -4
  71. package/dist/types-DiV9Mbvc.d.mts.map +0 -1
  72. package/dist/types-JvWj5Ckc.d.cts.map +0 -1
package/dist/index.cjs CHANGED
@@ -4,13 +4,13 @@ const require_workspace = require('./workspace-4SP3Gx4Y.cjs');
4
4
  const require_config = require('./config-D3ORuiUs.cjs');
5
5
  const require_credentials = require('./credentials-C8DWtnMY.cjs');
6
6
  const require_openapi = require('./openapi-BYxAWwok.cjs');
7
- const require_storage = require('./storage-6GBoLCYF.cjs');
7
+ const require_storage = require('./storage-B1wvztiJ.cjs');
8
8
  const require_dokploy_api = require('./dokploy-api-DLgvEQlr.cjs');
9
9
  const require_encryption = require('./encryption-BE0UOb8j.cjs');
10
10
  const require_CachedStateProvider = require('./CachedStateProvider-D73dCqfH.cjs');
11
- const require_fullstack_secrets = require('./fullstack-secrets-D9rjTNyx.cjs');
11
+ const require_fullstack_secrets = require('./fullstack-secrets-DOHBU4Rp.cjs');
12
12
  const require_openapi_react_query = require('./openapi-react-query-DYbBq-WJ.cjs');
13
- const require_sync = require('./sync-DIGGOxCw.cjs');
13
+ const require_sync = require('./sync-DGXXSk2v.cjs');
14
14
  const node_fs = require_chunk.__toESM(require("node:fs"));
15
15
  const node_path = require_chunk.__toESM(require("node:path"));
16
16
  const commander = require_chunk.__toESM(require("commander"));
@@ -35,7 +35,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
35
35
 
36
36
  //#region package.json
37
37
  var name = "@geekmidas/cli";
38
- var version = "1.10.13";
38
+ var version = "1.10.15";
39
39
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
40
40
  var private$1 = false;
41
41
  var type = "module";
@@ -1521,17 +1521,16 @@ function findSecretsRoot(startDir) {
1521
1521
  * @internal
1522
1522
  */
1523
1523
  function generateCredentialsInjection(secretsJsonPath) {
1524
- return `import { Credentials } from '@geekmidas/envkit/credentials';
1525
- import { existsSync, readFileSync } from 'node:fs';
1524
+ return `import { existsSync, readFileSync } from 'node:fs';
1526
1525
 
1527
- // Inject dev secrets into Credentials and process.env
1526
+ // Inject dev secrets via globalThis and process.env
1527
+ // Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where
1528
+ // Object.assign on the Credentials export only mutates one module copy.
1528
1529
  const secretsPath = '${secretsJsonPath}';
1529
1530
  if (existsSync(secretsPath)) {
1530
1531
  const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1531
- Object.assign(Credentials, secrets);
1532
+ globalThis.__gkm_credentials__ = secrets;
1532
1533
  Object.assign(process.env, secrets);
1533
- // Debug: uncomment to verify preload is running
1534
- // console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
1535
1534
  }
1536
1535
  `;
1537
1536
  }
@@ -1720,13 +1719,14 @@ var EntryRunner = class {
1720
1719
  */
1721
1720
  function generateServerEntryContent(options) {
1722
1721
  const { secretsJsonPath, runtime = "node", enableOpenApi = false, appImportPath = "./app.js" } = options;
1723
- const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1724
- import { existsSync, readFileSync } from 'node:fs';
1722
+ const credentialsInjection = secretsJsonPath ? `import { existsSync, readFileSync } from 'node:fs';
1725
1723
 
1726
- // Inject dev secrets into Credentials (must happen before app import)
1724
+ // Inject dev secrets via globalThis (must happen before app import)
1727
1725
  const secretsPath = '${secretsJsonPath}';
1728
1726
  if (existsSync(secretsPath)) {
1729
- Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1727
+ const __secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1728
+ globalThis.__gkm_credentials__ = __secrets;
1729
+ Object.assign(process.env, __secrets);
1730
1730
  }
1731
1731
 
1732
1732
  ` : "";
@@ -2220,7 +2220,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
2220
2220
  let masterKey;
2221
2221
  if (context.production?.bundle && !skipBundle) {
2222
2222
  logger$9.log(`\n📦 Bundling production server...`);
2223
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DVJkwNMQ.cjs"));
2223
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-BhhfkI9T.cjs"));
2224
2224
  const allConstructs = [
2225
2225
  ...endpoints.map((e) => e.construct),
2226
2226
  ...functions.map((f) => f.construct),
@@ -2884,7 +2884,8 @@ const DEFAULT_SERVICE_IMAGES = {
2884
2884
  redis: "redis",
2885
2885
  rabbitmq: "rabbitmq",
2886
2886
  minio: "minio/minio",
2887
- mailpit: "axllent/mailpit"
2887
+ mailpit: "axllent/mailpit",
2888
+ localstack: "localstack/localstack"
2888
2889
  };
2889
2890
  /** Default Docker image versions for services */
2890
2891
  const DEFAULT_SERVICE_VERSIONS = {
@@ -2892,7 +2893,8 @@ const DEFAULT_SERVICE_VERSIONS = {
2892
2893
  redis: "7-alpine",
2893
2894
  rabbitmq: "3-management-alpine",
2894
2895
  minio: "latest",
2895
- mailpit: "latest"
2896
+ mailpit: "latest",
2897
+ localstack: "latest"
2896
2898
  };
2897
2899
  /** Get the default full image reference for a service */
2898
2900
  function getDefaultImage(serviceName) {
@@ -2957,6 +2959,13 @@ services:
2957
2959
  - SMTP_PORT=\${SMTP_PORT:-1025}
2958
2960
  - SMTP_USER=\${SMTP_USER:-${imageName}}
2959
2961
  - SMTP_PASS=\${SMTP_PASS:-${imageName}}
2962
+ - SMTP_SECURE=\${SMTP_SECURE:-false}
2963
+ - MAIL_FROM=\${MAIL_FROM:-noreply@localhost}
2964
+ `;
2965
+ if (serviceMap.has("localstack")) yaml$1 += ` - AWS_ACCESS_KEY_ID=\${AWS_ACCESS_KEY_ID:-localstack}
2966
+ - AWS_SECRET_ACCESS_KEY=\${AWS_SECRET_ACCESS_KEY:-localstack}
2967
+ - AWS_REGION=\${AWS_REGION:-us-east-1}
2968
+ - AWS_ENDPOINT_URL=\${AWS_ENDPOINT_URL:-http://localstack:4566}
2960
2969
  `;
2961
2970
  yaml$1 += ` healthcheck:
2962
2971
  test: ["CMD", "wget", "-q", "--spider", "http://localhost:${port}${healthCheckPath}"]
@@ -3073,6 +3082,29 @@ services:
3073
3082
  retries: 5
3074
3083
  networks:
3075
3084
  - app-network
3085
+ `;
3086
+ const localstackImage = serviceMap.get("localstack");
3087
+ if (localstackImage) yaml$1 += `
3088
+ localstack:
3089
+ image: ${localstackImage}
3090
+ container_name: localstack
3091
+ restart: unless-stopped
3092
+ environment:
3093
+ SERVICES: sns,sqs
3094
+ AWS_DEFAULT_REGION: \${AWS_REGION:-us-east-1}
3095
+ AWS_ACCESS_KEY_ID: \${AWS_ACCESS_KEY_ID:-localstack}
3096
+ AWS_SECRET_ACCESS_KEY: \${AWS_SECRET_ACCESS_KEY:-localstack}
3097
+ ports:
3098
+ - "\${LOCALSTACK_PORT:-4566}:4566"
3099
+ volumes:
3100
+ - localstack_data:/var/lib/localstack
3101
+ healthcheck:
3102
+ test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
3103
+ interval: 10s
3104
+ timeout: 5s
3105
+ retries: 5
3106
+ networks:
3107
+ - app-network
3076
3108
  `;
3077
3109
  yaml$1 += `
3078
3110
  volumes:
@@ -3084,6 +3116,8 @@ volumes:
3084
3116
  if (serviceMap.has("rabbitmq")) yaml$1 += ` rabbitmq_data:
3085
3117
  `;
3086
3118
  if (serviceMap.has("minio")) yaml$1 += ` minio_data:
3119
+ `;
3120
+ if (serviceMap.has("localstack")) yaml$1 += ` localstack_data:
3087
3121
  `;
3088
3122
  yaml$1 += `
3089
3123
  networks:
@@ -3140,6 +3174,9 @@ function generateWorkspaceCompose(workspace, options = {}) {
3140
3174
  const hasRedis = services.cache !== void 0 && services.cache !== false;
3141
3175
  const hasMail = services.mail !== void 0 && services.mail !== false;
3142
3176
  const hasMinio = services.storage !== void 0 && services.storage !== false;
3177
+ const eventsBackend = services.events;
3178
+ const hasLocalStack = eventsBackend === "sns";
3179
+ const hasRabbitMQ = eventsBackend === "rabbitmq" || services.rabbitmq !== void 0;
3143
3180
  const postgresImage = getInfraServiceImage("postgres", services.db);
3144
3181
  const redisImage = getInfraServiceImage("redis", services.cache);
3145
3182
  const minioImage = getInfraServiceImage("minio", services.storage);
@@ -3155,7 +3192,8 @@ services:
3155
3192
  hasPostgres,
3156
3193
  hasRedis,
3157
3194
  hasMinio,
3158
- hasMail
3195
+ hasMail,
3196
+ eventsBackend
3159
3197
  });
3160
3198
  if (hasPostgres) yaml$1 += `
3161
3199
  postgres:
@@ -3231,6 +3269,49 @@ services:
3231
3269
  retries: 5
3232
3270
  networks:
3233
3271
  - workspace-network
3272
+ `;
3273
+ if (hasLocalStack) yaml$1 += `
3274
+ localstack:
3275
+ image: localstack/localstack:latest
3276
+ container_name: ${workspace.name}-localstack
3277
+ restart: unless-stopped
3278
+ environment:
3279
+ SERVICES: sns,sqs
3280
+ AWS_DEFAULT_REGION: \${AWS_REGION:-us-east-1}
3281
+ AWS_ACCESS_KEY_ID: \${AWS_ACCESS_KEY_ID:-localstack}
3282
+ AWS_SECRET_ACCESS_KEY: \${AWS_SECRET_ACCESS_KEY:-localstack}
3283
+ ports:
3284
+ - "\${LOCALSTACK_PORT:-4566}:4566"
3285
+ volumes:
3286
+ - localstack_data:/var/lib/localstack
3287
+ healthcheck:
3288
+ test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
3289
+ interval: 10s
3290
+ timeout: 5s
3291
+ retries: 5
3292
+ networks:
3293
+ - workspace-network
3294
+ `;
3295
+ if (hasRabbitMQ) yaml$1 += `
3296
+ rabbitmq:
3297
+ image: rabbitmq:3-management-alpine
3298
+ container_name: ${workspace.name}-rabbitmq
3299
+ restart: unless-stopped
3300
+ environment:
3301
+ RABBITMQ_DEFAULT_USER: \${RABBITMQ_USER:-guest}
3302
+ RABBITMQ_DEFAULT_PASS: \${RABBITMQ_PASSWORD:-guest}
3303
+ ports:
3304
+ - "\${RABBITMQ_HOST_PORT:-5672}:5672"
3305
+ - "\${RABBITMQ_MGMT_HOST_PORT:-15672}:15672"
3306
+ volumes:
3307
+ - rabbitmq_data:/var/lib/rabbitmq
3308
+ healthcheck:
3309
+ test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
3310
+ interval: 10s
3311
+ timeout: 5s
3312
+ retries: 5
3313
+ networks:
3314
+ - workspace-network
3234
3315
  `;
3235
3316
  yaml$1 += `
3236
3317
  volumes:
@@ -3240,6 +3321,10 @@ volumes:
3240
3321
  if (hasRedis) yaml$1 += ` redis_data:
3241
3322
  `;
3242
3323
  if (hasMinio) yaml$1 += ` minio_data:
3324
+ `;
3325
+ if (hasLocalStack) yaml$1 += ` localstack_data:
3326
+ `;
3327
+ if (hasRabbitMQ) yaml$1 += ` rabbitmq_data:
3243
3328
  `;
3244
3329
  yaml$1 += `
3245
3330
  networks:
@@ -3275,7 +3360,7 @@ function getInfraServiceImage(serviceName, config) {
3275
3360
  * Generate a service definition for an app.
3276
3361
  */
3277
3362
  function generateAppService(appName, app, allApps, options) {
3278
- const { registry, projectName, hasPostgres, hasRedis, hasMinio, hasMail } = options;
3363
+ const { registry, projectName, hasPostgres, hasRedis, hasMinio, hasMail, eventsBackend } = options;
3279
3364
  const imageRef = registry ? `\${REGISTRY:-${registry}}/` : "";
3280
3365
  const healthCheckPath = app.type === "frontend" ? "/" : "/health";
3281
3366
  const healthCheckCmd = app.type === "frontend" ? `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}/"]` : `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}${healthCheckPath}"]`;
@@ -3314,7 +3399,19 @@ function generateAppService(appName, app, allApps, options) {
3314
3399
  - SMTP_PORT=\${SMTP_PORT:-1025}
3315
3400
  - SMTP_USER=\${SMTP_USER:-${projectName}}
3316
3401
  - SMTP_PASS=\${SMTP_PASS:-${projectName}}
3402
+ - SMTP_SECURE=\${SMTP_SECURE:-false}
3403
+ - MAIL_FROM=\${MAIL_FROM:-noreply@localhost}
3404
+ `;
3405
+ if (eventsBackend) {
3406
+ yaml$1 += ` - EVENT_PUBLISHER_CONNECTION_STRING=\${EVENT_PUBLISHER_CONNECTION_STRING}
3407
+ - EVENT_SUBSCRIBER_CONNECTION_STRING=\${EVENT_SUBSCRIBER_CONNECTION_STRING}
3408
+ `;
3409
+ if (eventsBackend === "sns") yaml$1 += ` - AWS_ACCESS_KEY_ID=\${AWS_ACCESS_KEY_ID:-localstack}
3410
+ - AWS_SECRET_ACCESS_KEY=\${AWS_SECRET_ACCESS_KEY:-localstack}
3411
+ - AWS_REGION=\${AWS_REGION:-us-east-1}
3412
+ - AWS_ENDPOINT_URL=\${AWS_ENDPOINT_URL:-http://localstack:4566}
3317
3413
  `;
3414
+ }
3318
3415
  }
3319
3416
  yaml$1 += ` healthcheck:
3320
3417
  test: ${healthCheckCmd}
@@ -3328,6 +3425,8 @@ function generateAppService(appName, app, allApps, options) {
3328
3425
  if (hasRedis) dependencies$1.push("redis");
3329
3426
  if (hasMinio) dependencies$1.push("minio");
3330
3427
  if (hasMail) dependencies$1.push("mailpit");
3428
+ if (eventsBackend === "sns") dependencies$1.push("localstack");
3429
+ if (eventsBackend === "rabbitmq") dependencies$1.push("rabbitmq");
3331
3430
  }
3332
3431
  if (dependencies$1.length > 0) {
3333
3432
  yaml$1 += ` depends_on:
@@ -6372,7 +6471,7 @@ async function deployCommand(options) {
6372
6471
  dokployConfig = setupResult.config;
6373
6472
  finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
6374
6473
  if (setupResult.serviceUrls) {
6375
- const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-DCqjCiDn.cjs"));
6474
+ const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
6376
6475
  let secrets = await readStageSecrets$1(stage);
6377
6476
  if (!secrets) {
6378
6477
  logger$3.log(` Creating secrets file for stage "${stage}"...`);
@@ -7141,11 +7240,11 @@ function generateDockerFiles(options, template, dbApps) {
7141
7240
  if (isFullstack && dbApps?.length) {
7142
7241
  files.push({
7143
7242
  path: "docker/postgres/init.sh",
7144
- content: generatePostgresInitScript(dbApps)
7243
+ content: generatePostgresInitScript(dbApps, options.services?.events)
7145
7244
  });
7146
7245
  files.push({
7147
7246
  path: "docker/.env",
7148
- content: generateDockerEnv(dbApps)
7247
+ content: generateDockerEnv(dbApps, options.services?.events)
7149
7248
  });
7150
7249
  }
7151
7250
  }
@@ -7245,6 +7344,47 @@ function generateDockerFiles(options, template, dbApps) {
7245
7344
  retries: 5`);
7246
7345
  volumes.push(" minio_data:");
7247
7346
  }
7347
+ if (options.services?.events === "sns") {
7348
+ services.push(` localstack:
7349
+ image: localstack/localstack:latest
7350
+ container_name: ${options.name}-localstack
7351
+ restart: unless-stopped
7352
+ environment:
7353
+ SERVICES: sns,sqs
7354
+ AWS_DEFAULT_REGION: \${AWS_REGION:-us-east-1}
7355
+ AWS_ACCESS_KEY_ID: \${AWS_ACCESS_KEY_ID:-localstack}
7356
+ AWS_SECRET_ACCESS_KEY: \${AWS_SECRET_ACCESS_KEY:-localstack}
7357
+ ports:
7358
+ - '\${LOCALSTACK_PORT:-4566}:4566'
7359
+ volumes:
7360
+ - localstack_data:/var/lib/localstack
7361
+ healthcheck:
7362
+ test: ['CMD', 'curl', '-f', 'http://localhost:4566/_localstack/health']
7363
+ interval: 10s
7364
+ timeout: 5s
7365
+ retries: 5`);
7366
+ volumes.push(" localstack_data:");
7367
+ }
7368
+ if (options.services?.events === "rabbitmq" && !hasWorker) {
7369
+ services.push(` rabbitmq:
7370
+ image: rabbitmq:3-management-alpine
7371
+ container_name: ${options.name}-rabbitmq
7372
+ restart: unless-stopped
7373
+ ports:
7374
+ - '\${RABBITMQ_HOST_PORT:-5672}:5672'
7375
+ - '\${RABBITMQ_MGMT_HOST_PORT:-15672}:15672'
7376
+ environment:
7377
+ RABBITMQ_DEFAULT_USER: guest
7378
+ RABBITMQ_DEFAULT_PASS: guest
7379
+ volumes:
7380
+ - rabbitmq_data:/var/lib/rabbitmq
7381
+ healthcheck:
7382
+ test: ['CMD', 'rabbitmq-diagnostics', 'check_running']
7383
+ interval: 10s
7384
+ timeout: 5s
7385
+ retries: 5`);
7386
+ if (!volumes.includes(" rabbitmq_data:")) volumes.push(" rabbitmq_data:");
7387
+ }
7248
7388
  let dockerCompose = `# Use "gkm dev" or "gkm test" to start services.
7249
7389
  # Running "docker compose up" directly will not inject secrets or resolve ports.
7250
7390
  services:
@@ -7263,11 +7403,12 @@ ${volumes.join("\n")}
7263
7403
  /**
7264
7404
  * Generate .env file for docker-compose with database passwords
7265
7405
  */
7266
- function generateDockerEnv(apps) {
7406
+ function generateDockerEnv(apps, eventsBackend) {
7267
7407
  const envVars = apps.map((app) => {
7268
7408
  const envVar = `${app.name.toUpperCase()}_DB_PASSWORD`;
7269
7409
  return `${envVar}=${app.password}`;
7270
7410
  });
7411
+ if (eventsBackend === "pgboss") envVars.push(`PGBOSS_DB_PASSWORD=pgboss-dev-password`);
7271
7412
  return `# Auto-generated docker environment file
7272
7413
  # Contains database passwords for docker-compose postgres init
7273
7414
  # This file is gitignored - do not commit to version control
@@ -7275,33 +7416,50 @@ ${envVars.join("\n")}
7275
7416
  `;
7276
7417
  }
7277
7418
  /**
7278
- * Generate PostgreSQL init shell script that creates per-app users with separate schemas
7279
- * Uses environment variables for passwords (more secure than hardcoded values)
7419
+ * Generate PostgreSQL init shell script that creates per-app users with separate schemas.
7420
+ * Uses idempotent DO blocks so the script can be re-run safely.
7280
7421
  * - api user: uses public schema
7281
7422
  * - auth user: uses auth schema with search_path=auth
7423
+ * - pgboss user: uses pgboss schema (when events === 'pgboss')
7282
7424
  */
7283
- function generatePostgresInitScript(apps) {
7425
+ function generatePostgresInitScript(apps, eventsBackend) {
7284
7426
  const userCreations = apps.map((app) => {
7285
7427
  const userName = app.name.replace(/-/g, "_");
7286
7428
  const envVar = `${app.name.toUpperCase()}_DB_PASSWORD`;
7287
7429
  const isApi = app.name === "api";
7288
7430
  const schemaName = isApi ? "public" : userName;
7289
7431
  if (isApi) return `
7290
- # Create ${app.name} user (uses public schema)
7432
+ # Create ${app.name} user (uses public schema) - idempotent
7291
7433
  echo "Creating user ${userName}..."
7292
7434
  psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
7293
- CREATE USER ${userName} WITH PASSWORD '$${envVar}';
7435
+ DO \\$\\$
7436
+ BEGIN
7437
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${userName}') THEN
7438
+ CREATE USER ${userName} WITH PASSWORD '$${envVar}';
7439
+ ELSE
7440
+ ALTER USER ${userName} WITH PASSWORD '$${envVar}';
7441
+ END IF;
7442
+ END
7443
+ \\$\\$;
7294
7444
  GRANT ALL ON SCHEMA public TO ${userName};
7295
7445
  ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${userName};
7296
7446
  ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${userName};
7297
7447
  EOSQL
7298
7448
  `;
7299
7449
  return `
7300
- # Create ${app.name} user with dedicated schema
7450
+ # Create ${app.name} user with dedicated schema - idempotent
7301
7451
  echo "Creating user ${userName} with schema ${schemaName}..."
7302
7452
  psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
7303
- CREATE USER ${userName} WITH PASSWORD '$${envVar}';
7304
- CREATE SCHEMA ${schemaName} AUTHORIZATION ${userName};
7453
+ DO \\$\\$
7454
+ BEGIN
7455
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${userName}') THEN
7456
+ CREATE USER ${userName} WITH PASSWORD '$${envVar}';
7457
+ ELSE
7458
+ ALTER USER ${userName} WITH PASSWORD '$${envVar}';
7459
+ END IF;
7460
+ END
7461
+ \\$\\$;
7462
+ CREATE SCHEMA IF NOT EXISTS ${schemaName} AUTHORIZATION ${userName};
7305
7463
  ALTER USER ${userName} SET search_path TO ${schemaName};
7306
7464
  GRANT USAGE ON SCHEMA ${schemaName} TO ${userName};
7307
7465
  GRANT ALL ON ALL TABLES IN SCHEMA ${schemaName} TO ${userName};
@@ -7311,14 +7469,46 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
7311
7469
  EOSQL
7312
7470
  `;
7313
7471
  });
7472
+ let pgbossBlock = "";
7473
+ if (eventsBackend === "pgboss") pgbossBlock = `
7474
+ # Create pgboss user with dedicated schema - idempotent
7475
+ echo "Creating pgboss user and schema..."
7476
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
7477
+ DO \\$\\$
7478
+ BEGIN
7479
+ IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'pgboss') THEN
7480
+ CREATE USER pgboss WITH PASSWORD '$PGBOSS_DB_PASSWORD';
7481
+ ELSE
7482
+ ALTER USER pgboss WITH PASSWORD '$PGBOSS_DB_PASSWORD';
7483
+ END IF;
7484
+ END
7485
+ \\$\\$;
7486
+ CREATE SCHEMA IF NOT EXISTS pgboss AUTHORIZATION pgboss;
7487
+ ALTER USER pgboss SET search_path TO pgboss;
7488
+ GRANT USAGE ON SCHEMA pgboss TO pgboss;
7489
+ GRANT ALL ON ALL TABLES IN SCHEMA pgboss TO pgboss;
7490
+ GRANT ALL ON ALL SEQUENCES IN SCHEMA pgboss TO pgboss;
7491
+ ALTER DEFAULT PRIVILEGES IN SCHEMA pgboss GRANT ALL ON TABLES TO pgboss;
7492
+ ALTER DEFAULT PRIVILEGES IN SCHEMA pgboss GRANT ALL ON SEQUENCES TO pgboss;
7493
+ EOSQL
7494
+ `;
7495
+ const extensions = `
7496
+ # Create extensions
7497
+ echo "Creating extensions..."
7498
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
7499
+ CREATE EXTENSION IF NOT EXISTS pg_trgm;
7500
+ CREATE EXTENSION IF NOT EXISTS citext;
7501
+ EOSQL
7502
+ `;
7314
7503
  return `#!/bin/bash
7315
7504
  set -e
7316
7505
 
7317
- # Auto-generated PostgreSQL init script
7506
+ # Auto-generated PostgreSQL init script (idempotent - safe to re-run)
7318
7507
  # Creates per-app users with separate schemas in a single database
7319
7508
  # - api: uses public schema
7320
- # - auth: uses auth schema (search_path=auth)
7321
- ${userCreations.join("\n")}
7509
+ # - auth: uses auth schema (search_path=auth)${eventsBackend === "pgboss" ? "\n# - pgboss: uses pgboss schema for event processing" : ""}
7510
+ ${extensions}
7511
+ ${userCreations.join("\n")}${pgbossBlock}
7322
7512
  echo "Database initialization complete!"
7323
7513
  `;
7324
7514
  }
@@ -8810,6 +9000,31 @@ const servicesChoices = [
8810
9000
  }
8811
9001
  ];
8812
9002
  /**
9003
+ * Event backend choices for prompts
9004
+ */
9005
+ const eventsBackendChoices = [
9006
+ {
9007
+ title: "pg-boss",
9008
+ value: "pgboss",
9009
+ description: "PostgreSQL-based job queue (reuses postgres, no extra container)"
9010
+ },
9011
+ {
9012
+ title: "SNS/SQS",
9013
+ value: "sns",
9014
+ description: "AWS SNS+SQS via LocalStack for local dev"
9015
+ },
9016
+ {
9017
+ title: "RabbitMQ",
9018
+ value: "rabbitmq",
9019
+ description: "AMQP message broker"
9020
+ },
9021
+ {
9022
+ title: "None",
9023
+ value: void 0,
9024
+ description: "Skip event backend"
9025
+ }
9026
+ ];
9027
+ /**
8813
9028
  * Get a template by name
8814
9029
  */
8815
9030
  function getTemplate(name$1) {
@@ -10917,6 +11132,13 @@ async function initCommand(projectName, options = {}) {
10917
11132
  })),
10918
11133
  hint: "- Space to select. Return to submit"
10919
11134
  },
11135
+ {
11136
+ type: options.yes ? null : "select",
11137
+ name: "eventsBackend",
11138
+ message: "Event backend:",
11139
+ choices: eventsBackendChoices,
11140
+ initial: 0
11141
+ },
10920
11142
  {
10921
11143
  type: options.yes ? null : "select",
10922
11144
  name: "packageManager",
@@ -10979,12 +11201,15 @@ async function initCommand(projectName, options = {}) {
10979
11201
  "mail",
10980
11202
  "storage"
10981
11203
  ] : answers.services || [];
11204
+ const eventsBackend = options.yes ? isFullstack ? "pgboss" : void 0 : answers.eventsBackend;
10982
11205
  const services = {
10983
11206
  db: servicesArray.includes("db"),
10984
11207
  cache: servicesArray.includes("cache"),
10985
11208
  mail: servicesArray.includes("mail"),
10986
- storage: servicesArray.includes("storage")
11209
+ storage: servicesArray.includes("storage"),
11210
+ events: eventsBackend
10987
11211
  };
11212
+ if (services.events === "pgboss") services.db = true;
10988
11213
  const pkgManager = options.pm ? options.pm : options.yes ? "pnpm" : answers.packageManager ?? detectedPkgManager;
10989
11214
  const deployTarget = options.yes ? "dokploy" : answers.deployTarget ?? "dokploy";
10990
11215
  const database = services.db;
@@ -11067,7 +11292,12 @@ async function initCommand(projectName, options = {}) {
11067
11292
  if (services.cache) secretServices.push("redis");
11068
11293
  if (services.storage) secretServices.push("minio");
11069
11294
  if (services.mail) secretServices.push("mailpit");
11070
- const devSecrets = require_fullstack_secrets.createStageSecrets("development", secretServices, { projectName: name$1 });
11295
+ if (services.events === "sns") secretServices.push("localstack");
11296
+ if (services.events === "rabbitmq") secretServices.push("rabbitmq");
11297
+ const devSecrets = require_fullstack_secrets.createStageSecrets("development", secretServices, {
11298
+ projectName: name$1,
11299
+ eventsBackend: services.events
11300
+ });
11071
11301
  const customSecrets = {
11072
11302
  NODE_ENV: "development",
11073
11303
  PORT: "3000",
@@ -11507,10 +11737,55 @@ function reconcileSecrets(secrets, workspace) {
11507
11737
  [name$1]: creds
11508
11738
  }
11509
11739
  };
11510
- result.urls = require_fullstack_secrets.generateConnectionUrls(result.services);
11740
+ result.urls = require_fullstack_secrets.generateConnectionUrls(result.services, result.eventsBackend);
11511
11741
  logger$1.log(` 🔄 Adding missing service credentials: ${name$1}`);
11512
11742
  changed = true;
11513
11743
  }
11744
+ const eventsBackend = workspace.services.events;
11745
+ if (eventsBackend && result.eventsBackend !== eventsBackend) {
11746
+ result.eventsBackend = eventsBackend;
11747
+ if (eventsBackend === "pgboss" && !result.services.pgboss) {
11748
+ result = {
11749
+ ...result,
11750
+ services: {
11751
+ ...result.services,
11752
+ pgboss: {
11753
+ host: result.services.postgres?.host ?? "localhost",
11754
+ port: result.services.postgres?.port ?? 5432,
11755
+ username: "pgboss",
11756
+ password: require_fullstack_secrets.generateSecurePassword(),
11757
+ database: result.services.postgres?.database ?? "app"
11758
+ }
11759
+ }
11760
+ };
11761
+ logger$1.log(" 🔄 Adding missing service credentials: pgboss");
11762
+ changed = true;
11763
+ }
11764
+ if (eventsBackend === "sns" && !result.services.localstack) {
11765
+ result = {
11766
+ ...result,
11767
+ services: {
11768
+ ...result.services,
11769
+ localstack: require_fullstack_secrets.generateLocalStackCredentials()
11770
+ }
11771
+ };
11772
+ logger$1.log(" 🔄 Adding missing service credentials: localstack");
11773
+ changed = true;
11774
+ }
11775
+ if (eventsBackend === "rabbitmq" && !result.services.rabbitmq) {
11776
+ result = {
11777
+ ...result,
11778
+ services: {
11779
+ ...result.services,
11780
+ rabbitmq: require_fullstack_secrets.generateServiceCredentials("rabbitmq")
11781
+ }
11782
+ };
11783
+ logger$1.log(" 🔄 Adding missing service credentials: rabbitmq");
11784
+ changed = true;
11785
+ }
11786
+ result.urls = require_fullstack_secrets.generateConnectionUrls(result.services, eventsBackend);
11787
+ changed = true;
11788
+ }
11514
11789
  const isMultiApp = Object.keys(workspace.apps).length > 1;
11515
11790
  if (isMultiApp) {
11516
11791
  const expected = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
@@ -11543,7 +11818,12 @@ async function generateFreshSecrets(stage, workspace, options) {
11543
11818
  if (workspace.services.cache) serviceNames.push("redis");
11544
11819
  if (workspace.services.storage) serviceNames.push("minio");
11545
11820
  if (workspace.services.mail) serviceNames.push("mailpit");
11546
- const secrets = require_fullstack_secrets.createStageSecrets(stage, serviceNames, { projectName: workspace.name });
11821
+ if (workspace.services.events === "sns") serviceNames.push("localstack");
11822
+ if (workspace.services.events === "rabbitmq") serviceNames.push("rabbitmq");
11823
+ const secrets = require_fullstack_secrets.createStageSecrets(stage, serviceNames, {
11824
+ projectName: workspace.name,
11825
+ eventsBackend: workspace.services.events
11826
+ });
11547
11827
  const isMultiApp = Object.keys(workspace.apps).length > 1;
11548
11828
  if (isMultiApp) {
11549
11829
  const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
@@ -12093,9 +12373,9 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
12093
12373
  const globalOptions = program.opts();
12094
12374
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12095
12375
  const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
12096
- const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-BVNso6AA.cjs"));
12097
- const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-LaaJkFlO.cjs"));
12098
- const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DCqjCiDn.cjs"));
12376
+ const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-D1Pa30oV.cjs"));
12377
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-Ch7sIcf8.cjs"));
12378
+ const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
12099
12379
  const { workspace } = await loadWorkspaceConfig$1();
12100
12380
  const secrets = await readStageSecrets$1(options.stage, workspace.root);
12101
12381
  if (secrets) {
@@ -12118,9 +12398,9 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
12118
12398
  const globalOptions = program.opts();
12119
12399
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12120
12400
  const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
12121
- const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-BVNso6AA.cjs"));
12122
- const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DCqjCiDn.cjs"));
12123
- const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-LaaJkFlO.cjs"));
12401
+ const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-D1Pa30oV.cjs"));
12402
+ const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
12403
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-Ch7sIcf8.cjs"));
12124
12404
  const { workspace } = await loadWorkspaceConfig$1();
12125
12405
  let secrets = await pullSecrets$1(options.stage, workspace);
12126
12406
  if (!secrets) {
@@ -12145,8 +12425,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
12145
12425
  const globalOptions = program.opts();
12146
12426
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12147
12427
  const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
12148
- const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-LaaJkFlO.cjs"));
12149
- const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DCqjCiDn.cjs"));
12428
+ const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-Ch7sIcf8.cjs"));
12429
+ const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
12150
12430
  const { workspace } = await loadWorkspaceConfig$1();
12151
12431
  const secrets = await readStageSecrets$1(options.stage, workspace.root);
12152
12432
  if (!secrets) {