@geekmidas/cli 1.7.0 → 1.9.0

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 (56) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{HostingerProvider-BiXdHjiq.cjs → HostingerProvider-CEsQbmpY.cjs} +1 -1
  3. package/dist/{HostingerProvider-BiXdHjiq.cjs.map → HostingerProvider-CEsQbmpY.cjs.map} +1 -1
  4. package/dist/{HostingerProvider-402UdK89.mjs → HostingerProvider-DkahM5AP.mjs} +1 -1
  5. package/dist/{HostingerProvider-402UdK89.mjs.map → HostingerProvider-DkahM5AP.mjs.map} +1 -1
  6. package/dist/{LocalStateProvider-BDm7ZqJo.mjs → LocalStateProvider-DXIwWb7k.mjs} +1 -1
  7. package/dist/{LocalStateProvider-BDm7ZqJo.mjs.map → LocalStateProvider-DXIwWb7k.mjs.map} +1 -1
  8. package/dist/{LocalStateProvider-CdspeSVL.cjs → LocalStateProvider-Roi202l7.cjs} +1 -1
  9. package/dist/{LocalStateProvider-CdspeSVL.cjs.map → LocalStateProvider-Roi202l7.cjs.map} +1 -1
  10. package/dist/{Route53Provider-kfJ77LmL.cjs → Route53Provider-BqXeHzuc.cjs} +1 -1
  11. package/dist/{Route53Provider-kfJ77LmL.cjs.map → Route53Provider-BqXeHzuc.cjs.map} +1 -1
  12. package/dist/{Route53Provider-DbBo7Uz5.mjs → Route53Provider-Ckq_n5Be.mjs} +1 -1
  13. package/dist/{Route53Provider-DbBo7Uz5.mjs.map → Route53Provider-Ckq_n5Be.mjs.map} +1 -1
  14. package/dist/{SSMStateProvider-DGrqYll0.cjs → SSMStateProvider-BReQA5re.cjs} +1 -1
  15. package/dist/{SSMStateProvider-DGrqYll0.cjs.map → SSMStateProvider-BReQA5re.cjs.map} +1 -1
  16. package/dist/{SSMStateProvider-DT0WV-E_.mjs → SSMStateProvider-wddd0_-d.mjs} +1 -1
  17. package/dist/{SSMStateProvider-DT0WV-E_.mjs.map → SSMStateProvider-wddd0_-d.mjs.map} +1 -1
  18. package/dist/{backup-provisioner-BIArpmTr.mjs → backup-provisioner-BAExdDtc.mjs} +1 -1
  19. package/dist/{backup-provisioner-BIArpmTr.mjs.map → backup-provisioner-BAExdDtc.mjs.map} +1 -1
  20. package/dist/{backup-provisioner-B5e-F6zX.cjs → backup-provisioner-C8VK63I-.cjs} +1 -1
  21. package/dist/{backup-provisioner-B5e-F6zX.cjs.map → backup-provisioner-C8VK63I-.cjs.map} +1 -1
  22. package/dist/{bundler-DgXsOSxc.mjs → bundler-BxHyDhdt.mjs} +1 -1
  23. package/dist/{bundler-DgXsOSxc.mjs.map → bundler-BxHyDhdt.mjs.map} +1 -1
  24. package/dist/{bundler-tHLLwYuU.cjs → bundler-CuMIfXw5.cjs} +1 -1
  25. package/dist/{bundler-tHLLwYuU.cjs.map → bundler-CuMIfXw5.cjs.map} +1 -1
  26. package/dist/config.d.mts +2 -2
  27. package/dist/{index-C-KxSGGK.d.mts → index-BVNXOydm.d.mts} +2 -2
  28. package/dist/{index-C-KxSGGK.d.mts.map → index-BVNXOydm.d.mts.map} +1 -1
  29. package/dist/index.cjs +1019 -551
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.mjs +1017 -549
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/openapi.d.mts +1 -1
  34. package/dist/sync-BOS0jKLn.cjs +93 -0
  35. package/dist/sync-BOS0jKLn.cjs.map +1 -0
  36. package/dist/sync-BnqNNc6O.mjs +3 -0
  37. package/dist/sync-BxFB34zW.cjs +4 -0
  38. package/dist/sync-CHfhmXF3.mjs +76 -0
  39. package/dist/sync-CHfhmXF3.mjs.map +1 -0
  40. package/dist/{types-CZg5iUgD.d.mts → types-eTlj5f2M.d.mts} +1 -1
  41. package/dist/{types-CZg5iUgD.d.mts.map → types-eTlj5f2M.d.mts.map} +1 -1
  42. package/dist/workspace/index.d.mts +2 -2
  43. package/package.json +4 -4
  44. package/src/dev/index.ts +1 -1
  45. package/src/generators/SubscriberGenerator.ts +1 -0
  46. package/src/index.ts +93 -0
  47. package/src/init/index.ts +4 -23
  48. package/src/init/utils.ts +103 -2
  49. package/src/init/versions.ts +1 -1
  50. package/src/secrets/index.ts +20 -1
  51. package/src/secrets/sync.ts +136 -0
  52. package/src/setup/fullstack-secrets.ts +121 -0
  53. package/src/setup/index.ts +212 -0
  54. package/src/test/__tests__/web.spec.ts +1 -1
  55. package/src/upgrade/__tests__/index.spec.ts +354 -0
  56. package/src/upgrade/index.ts +253 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env -S npx tsx
2
- import { GkmConfig, OpenApiConfig } from "./types-CZg5iUgD.mjs";
2
+ import { GkmConfig, OpenApiConfig } from "./types-eTlj5f2M.mjs";
3
3
 
4
4
  //#region src/openapi.d.ts
5
5
  interface OpenAPIOptions {
@@ -0,0 +1,93 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const __aws_sdk_client_ssm = require_chunk.__toESM(require("@aws-sdk/client-ssm"));
3
+
4
+ //#region src/secrets/sync.ts
5
+ /**
6
+ * Get the SSM parameter name for secrets.
7
+ */
8
+ function getSecretsParameterName(workspaceName, stage) {
9
+ return `/gkm/${workspaceName}/${stage}/secrets`;
10
+ }
11
+ /**
12
+ * Create an SSM client from workspace state config.
13
+ */
14
+ function createSSMClient(config) {
15
+ const clientConfig = { region: config.region };
16
+ if (config.profile) {
17
+ const { fromIni } = require("@aws-sdk/credential-providers");
18
+ clientConfig.credentials = fromIni({ profile: config.profile });
19
+ }
20
+ return new __aws_sdk_client_ssm.SSMClient(clientConfig);
21
+ }
22
+ /**
23
+ * Push secrets to SSM Parameter Store.
24
+ *
25
+ * Stores the full StageSecrets object as a SecureString parameter.
26
+ */
27
+ async function pushSecrets(stage, workspace) {
28
+ const config = workspace.state;
29
+ if (!config || config.provider !== "ssm") throw new Error("SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.");
30
+ if (!workspace.name) throw new Error("Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.");
31
+ const client = createSSMClient(config);
32
+ const parameterName = getSecretsParameterName(workspace.name, stage);
33
+ const { readStageSecrets } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
34
+ const secrets = await readStageSecrets(stage, workspace.root);
35
+ if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
36
+ await client.send(new __aws_sdk_client_ssm.PutParameterCommand({
37
+ Name: parameterName,
38
+ Value: JSON.stringify(secrets),
39
+ Type: "SecureString",
40
+ Overwrite: true,
41
+ Description: `GKM secrets for ${workspace.name}/${stage}`
42
+ }));
43
+ }
44
+ /**
45
+ * Pull secrets from SSM Parameter Store.
46
+ *
47
+ * @returns StageSecrets or null if no secrets are stored remotely
48
+ */
49
+ async function pullSecrets(stage, workspace) {
50
+ const config = workspace.state;
51
+ if (!config || config.provider !== "ssm") return null;
52
+ if (!workspace.name) return null;
53
+ const client = createSSMClient(config);
54
+ const parameterName = getSecretsParameterName(workspace.name, stage);
55
+ try {
56
+ const response = await client.send(new __aws_sdk_client_ssm.GetParameterCommand({
57
+ Name: parameterName,
58
+ WithDecryption: true
59
+ }));
60
+ if (!response.Parameter?.Value) return null;
61
+ return JSON.parse(response.Parameter.Value);
62
+ } catch (error) {
63
+ if (error instanceof __aws_sdk_client_ssm.ParameterNotFound) return null;
64
+ throw error;
65
+ }
66
+ }
67
+ /**
68
+ * Check if SSM is configured for the workspace.
69
+ */
70
+ function isSSMConfigured(workspace) {
71
+ return !!workspace.state && workspace.state.provider === "ssm";
72
+ }
73
+
74
+ //#endregion
75
+ Object.defineProperty(exports, 'isSSMConfigured', {
76
+ enumerable: true,
77
+ get: function () {
78
+ return isSSMConfigured;
79
+ }
80
+ });
81
+ Object.defineProperty(exports, 'pullSecrets', {
82
+ enumerable: true,
83
+ get: function () {
84
+ return pullSecrets;
85
+ }
86
+ });
87
+ Object.defineProperty(exports, 'pushSecrets', {
88
+ enumerable: true,
89
+ get: function () {
90
+ return pushSecrets;
91
+ }
92
+ });
93
+ //# sourceMappingURL=sync-BOS0jKLn.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-BOS0jKLn.cjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","SSMClient","workspace: NormalizedWorkspace","PutParameterCommand","GetParameterCommand","ParameterNotFound"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,QAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAIC,+BAAU;AACrB;;;;;;AAOD,eAAsB,YACrBH,OACAI,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,2CAAM;CACnC,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAIC,yCAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBL,OACAI,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAIE,yCAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiBC,uCACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBH,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
@@ -0,0 +1,3 @@
1
+ import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-CHfhmXF3.mjs";
2
+
3
+ export { pullSecrets, pushSecrets };
@@ -0,0 +1,4 @@
1
+ const require_sync = require('./sync-BOS0jKLn.cjs');
2
+
3
+ exports.pullSecrets = require_sync.pullSecrets;
4
+ exports.pushSecrets = require_sync.pushSecrets;
@@ -0,0 +1,76 @@
1
+ import { __require } from "./chunk-Duj1WY3L.mjs";
2
+ import { GetParameterCommand, ParameterNotFound, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
3
+
4
+ //#region src/secrets/sync.ts
5
+ /**
6
+ * Get the SSM parameter name for secrets.
7
+ */
8
+ function getSecretsParameterName(workspaceName, stage) {
9
+ return `/gkm/${workspaceName}/${stage}/secrets`;
10
+ }
11
+ /**
12
+ * Create an SSM client from workspace state config.
13
+ */
14
+ function createSSMClient(config) {
15
+ const clientConfig = { region: config.region };
16
+ if (config.profile) {
17
+ const { fromIni } = __require("@aws-sdk/credential-providers");
18
+ clientConfig.credentials = fromIni({ profile: config.profile });
19
+ }
20
+ return new SSMClient(clientConfig);
21
+ }
22
+ /**
23
+ * Push secrets to SSM Parameter Store.
24
+ *
25
+ * Stores the full StageSecrets object as a SecureString parameter.
26
+ */
27
+ async function pushSecrets(stage, workspace) {
28
+ const config = workspace.state;
29
+ if (!config || config.provider !== "ssm") throw new Error("SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.");
30
+ if (!workspace.name) throw new Error("Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.");
31
+ const client = createSSMClient(config);
32
+ const parameterName = getSecretsParameterName(workspace.name, stage);
33
+ const { readStageSecrets } = await import("./storage-Dx_jZbq6.mjs");
34
+ const secrets = await readStageSecrets(stage, workspace.root);
35
+ if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
36
+ await client.send(new PutParameterCommand({
37
+ Name: parameterName,
38
+ Value: JSON.stringify(secrets),
39
+ Type: "SecureString",
40
+ Overwrite: true,
41
+ Description: `GKM secrets for ${workspace.name}/${stage}`
42
+ }));
43
+ }
44
+ /**
45
+ * Pull secrets from SSM Parameter Store.
46
+ *
47
+ * @returns StageSecrets or null if no secrets are stored remotely
48
+ */
49
+ async function pullSecrets(stage, workspace) {
50
+ const config = workspace.state;
51
+ if (!config || config.provider !== "ssm") return null;
52
+ if (!workspace.name) return null;
53
+ const client = createSSMClient(config);
54
+ const parameterName = getSecretsParameterName(workspace.name, stage);
55
+ try {
56
+ const response = await client.send(new GetParameterCommand({
57
+ Name: parameterName,
58
+ WithDecryption: true
59
+ }));
60
+ if (!response.Parameter?.Value) return null;
61
+ return JSON.parse(response.Parameter.Value);
62
+ } catch (error) {
63
+ if (error instanceof ParameterNotFound) return null;
64
+ throw error;
65
+ }
66
+ }
67
+ /**
68
+ * Check if SSM is configured for the workspace.
69
+ */
70
+ function isSSMConfigured(workspace) {
71
+ return !!workspace.state && workspace.state.provider === "ssm";
72
+ }
73
+
74
+ //#endregion
75
+ export { isSSMConfigured, pullSecrets, pushSecrets };
76
+ //# sourceMappingURL=sync-CHfhmXF3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-CHfhmXF3.mjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","workspace: NormalizedWorkspace"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,UAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAI,UAAU;AACrB;;;;;;AAOD,eAAsB,YACrBF,OACAG,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,MAAM,OAAO;CAC1C,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAI,oBAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBH,OACAG,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAI,oBAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBA,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
@@ -261,4 +261,4 @@ interface GkmConfig {
261
261
  }
262
262
  //#endregion
263
263
  export { GkmConfig, HooksConfig, OpenApiConfig, ProvidersConfig, Routes, Runtime, StudioConfig, TelescopeConfig };
264
- //# sourceMappingURL=types-CZg5iUgD.d.mts.map
264
+ //# sourceMappingURL=types-eTlj5f2M.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-CZg5iUgD.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;AASiB,KAFL,MAAA,GAEmB,MAAA,GAAA,MAAA,EAAA;AAKd,UALA,cAAA,CAKoB;EAIpB,OAAA,CAAA,EAAA,OAAA;EAIA,SAAA,CAAA,EAAA,MAAA;AA4BjB;AAcY,UAlDK,mBAAA,SAA4B,cAkDf,CAAA,CAG9B;AAAiC,UAjDhB,eAAA,SAAwB,cAiDR,CAAA;AACM,UA9CtB,gBAAA,CA8CsB;EAAa;EAGnC,OAAA,CAAA,EAAA,OAAY;EAAA;EAAA,MAwBhB,CAAA,EAAA,OAAA;EAAqB;EAAqB,MAAA,CAAA,EAAA,OAAA;EAItC;EAAa,WAAA,CAAA,EAAA,MAAA;EAAA;EAIA,gBAJQ,CAAA,EAAA,OAAA;EAAc;EAOxC,QAAA,CAAA,EAAO,MAAA,EAAA;EAEF;EAiBA,WAAA,CAAA,EAAA,SAAY,GAAA,SAAA;EASZ;EAWA,OAAA,CAAA,EAAA,OAAW;EA6BX;AAajB;;;;;;EAQoC,iBAGhB,CAAA,EAAA,OAAA;;AAEsB;AAGzB,UAzJA,aAAA,CAyJS;EAAA;;;;EAGX,KACA,CAAA,EAAA,MAAA;EAAM;;;;EA+BoB,OAmBpB,CAAA,EAAA,MAAA;;;AA8BC,KA/NV,kBAAA,GA+NU,UAAA,GAAA,OAAA,GAAA,UAAA;;KA5NV,qBAAA,WACL,gCAAgC;UAGtB,YAAA;;;;;;;;;;;;;;;;;;;;;;;;eAwBJ,wBAAwB;;;UAIpB,YAAA,SAAqB;;;;eAIxB;;KAGF,OAAA;UAEK,eAAA;;;;;;;;;;;;;;;;UAiBA,YAAA;;;;;;;;UASA,aAAA;;;;;;;;;;UAWA,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6BA,qBAAA;;;;;;;;;;;;UAaA,eAAA;;;qBAGC;qBACA;;;4BAGO;wBACJ;;;qBAGD;;sBAEC;;UAGJ,SAAA;UACR;cACI;UACJ;gBACM;;;cAGF;;;;;;;;;;UAUJ;;;;;;;;iCAQuB;;;;;;;;;;8BAUH;;;;;;;;;;;;;;;;;;;sBAmBR;;YAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BD"}
1
+ {"version":3,"file":"types-eTlj5f2M.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;AASiB,KAFL,MAAA,GAEmB,MAAA,GAAA,MAAA,EAAA;AAKd,UALA,cAAA,CAKoB;EAIpB,OAAA,CAAA,EAAA,OAAA;EAIA,SAAA,CAAA,EAAA,MAAA;AA4BjB;AAcY,UAlDK,mBAAA,SAA4B,cAkDf,CAAA,CAG9B;AAAiC,UAjDhB,eAAA,SAAwB,cAiDR,CAAA;AACM,UA9CtB,gBAAA,CA8CsB;EAAa;EAGnC,OAAA,CAAA,EAAA,OAAY;EAAA;EAAA,MAwBhB,CAAA,EAAA,OAAA;EAAqB;EAAqB,MAAA,CAAA,EAAA,OAAA;EAItC;EAAa,WAAA,CAAA,EAAA,MAAA;EAAA;EAIA,gBAJQ,CAAA,EAAA,OAAA;EAAc;EAOxC,QAAA,CAAA,EAAO,MAAA,EAAA;EAEF;EAiBA,WAAA,CAAA,EAAA,SAAY,GAAA,SAAA;EASZ;EAWA,OAAA,CAAA,EAAA,OAAW;EA6BX;AAajB;;;;;;EAQoC,iBAGhB,CAAA,EAAA,OAAA;;AAEsB;AAGzB,UAzJA,aAAA,CAyJS;EAAA;;;;EAGX,KACA,CAAA,EAAA,MAAA;EAAM;;;;EA+BoB,OAmBpB,CAAA,EAAA,MAAA;;;AA8BC,KA/NV,kBAAA,GA+NU,UAAA,GAAA,OAAA,GAAA,UAAA;;KA5NV,qBAAA,WACL,gCAAgC;UAGtB,YAAA;;;;;;;;;;;;;;;;;;;;;;;;eAwBJ,wBAAwB;;;UAIpB,YAAA,SAAqB;;;;eAIxB;;KAGF,OAAA;UAEK,eAAA;;;;;;;;;;;;;;;;UAiBA,YAAA;;;;;;;;UASA,aAAA;;;;;;;;;;UAWA,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6BA,qBAAA;;;;;;;;;;;;UAaA,eAAA;;;qBAGC;qBACA;;;4BAGO;wBACJ;;;qBAGD;;sBAEC;;UAGJ,SAAA;UACR;cACI;UACJ;gBACM;;;cAGF;;;;;;;;;;UAUJ;;;;;;;;iCAQuB;;;;;;;;;;8BAUH;;;;;;;;;;;;;;;;;;;sBAmBR;;YAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BD"}
@@ -1,3 +1,3 @@
1
- import "../types-CZg5iUgD.mjs";
2
- import { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ClientConfig, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, validateWorkspaceConfig, wrapSingleAppAsWorkspace } from "../index-C-KxSGGK.mjs";
1
+ import "../types-eTlj5f2M.mjs";
2
+ import { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ClientConfig, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, validateWorkspaceConfig, wrapSingleAppAsWorkspace } from "../index-BVNXOydm.mjs";
3
3
  export { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ClientConfig, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, validateWorkspaceConfig, wrapSingleAppAsWorkspace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -56,10 +56,10 @@
56
56
  "prompts": "~2.4.2",
57
57
  "tsx": "~4.20.3",
58
58
  "yaml": "~2.8.2",
59
+ "@geekmidas/constructs": "~2.0.0",
59
60
  "@geekmidas/envkit": "~1.0.3",
60
- "@geekmidas/constructs": "~1.1.0",
61
- "@geekmidas/logger": "~1.0.0",
62
61
  "@geekmidas/errors": "~1.0.0",
62
+ "@geekmidas/logger": "~1.0.0",
63
63
  "@geekmidas/schema": "~1.0.0"
64
64
  },
65
65
  "devDependencies": {
@@ -70,7 +70,7 @@
70
70
  "typescript": "^5.8.2",
71
71
  "vitest": "^3.2.4",
72
72
  "zod": "~4.1.13",
73
- "@geekmidas/testkit": "1.0.1"
73
+ "@geekmidas/testkit": "1.0.2"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@geekmidas/telescope": "~1.0.0"
package/src/dev/index.ts CHANGED
@@ -1041,7 +1041,7 @@ export async function loadDevSecrets(
1041
1041
  }
1042
1042
 
1043
1043
  logger.warn(
1044
- '⚠️ Secrets enabled but no dev/development secrets found. Run "gkm secrets:init --stage dev"',
1044
+ '⚠️ Secrets enabled but no dev/development secrets found. Run "gkm setup" to initialize your development environment',
1045
1045
  );
1046
1046
  return {};
1047
1047
  }
@@ -164,6 +164,7 @@ export const handler = adapter.handler;
164
164
  * - sqs://region/account-id/queue-name (SQS queue)
165
165
  * - sns://region/account-id/topic-name (SNS topic)
166
166
  * - rabbitmq://host:port/queue-name (RabbitMQ)
167
+ * - pgboss://user:pass@host:port/database (pg-boss / PostgreSQL)
167
168
  * - basic://in-memory (In-memory for testing)
168
169
  */
169
170
  import type { EnvironmentParser } from '@geekmidas/envkit';
package/src/index.ts CHANGED
@@ -24,8 +24,10 @@ import {
24
24
  secretsSetCommand,
25
25
  secretsShowCommand,
26
26
  } from './secrets';
27
+ import { type SetupOptions, setupCommand } from './setup/index';
27
28
  import { type TestOptions, testCommand } from './test/index';
28
29
  import type { ComposeServiceName, LegacyProvider, MainProvider } from './types';
30
+ import { type UpgradeOptions, upgradeCommand } from './upgrade/index';
29
31
 
30
32
  const program = new Command();
31
33
 
@@ -61,6 +63,26 @@ program
61
63
  }
62
64
  });
63
65
 
66
+ program
67
+ .command('setup')
68
+ .description('Setup development environment (secrets, Docker, database)')
69
+ .option('--stage <stage>', 'Stage name', 'development')
70
+ .option('--force', 'Regenerate secrets even if they exist')
71
+ .option('--skip-docker', 'Skip starting Docker services')
72
+ .option('-y, --yes', 'Skip prompts')
73
+ .action(async (options: SetupOptions) => {
74
+ try {
75
+ const globalOptions = program.opts();
76
+ if (globalOptions.cwd) {
77
+ process.chdir(globalOptions.cwd);
78
+ }
79
+ await setupCommand(options);
80
+ } catch (error) {
81
+ console.error(error instanceof Error ? error.message : 'Command failed');
82
+ process.exit(1);
83
+ }
84
+ });
85
+
64
86
  program
65
87
  .command('build')
66
88
  .description('Build handlers from endpoints, functions, and crons')
@@ -483,6 +505,60 @@ program
483
505
  }
484
506
  });
485
507
 
508
+ program
509
+ .command('secrets:push')
510
+ .description('Push secrets to remote provider (SSM)')
511
+ .requiredOption('--stage <stage>', 'Stage name')
512
+ .action(async (options: { stage: string }) => {
513
+ try {
514
+ const globalOptions = program.opts();
515
+ if (globalOptions.cwd) {
516
+ process.chdir(globalOptions.cwd);
517
+ }
518
+
519
+ const { loadWorkspaceConfig } = await import('./config');
520
+ const { pushSecrets } = await import('./secrets/sync');
521
+
522
+ const { workspace } = await loadWorkspaceConfig();
523
+ await pushSecrets(options.stage, workspace);
524
+ console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
525
+ } catch (error) {
526
+ console.error(error instanceof Error ? error.message : 'Command failed');
527
+ process.exit(1);
528
+ }
529
+ });
530
+
531
+ program
532
+ .command('secrets:pull')
533
+ .description('Pull secrets from remote provider (SSM)')
534
+ .requiredOption('--stage <stage>', 'Stage name')
535
+ .action(async (options: { stage: string }) => {
536
+ try {
537
+ const globalOptions = program.opts();
538
+ if (globalOptions.cwd) {
539
+ process.chdir(globalOptions.cwd);
540
+ }
541
+
542
+ const { loadWorkspaceConfig } = await import('./config');
543
+ const { pullSecrets } = await import('./secrets/sync');
544
+ const { writeStageSecrets } = await import('./secrets/storage');
545
+
546
+ const { workspace } = await loadWorkspaceConfig();
547
+ const secrets = await pullSecrets(options.stage, workspace);
548
+
549
+ if (!secrets) {
550
+ console.error(`No remote secrets found for stage "${options.stage}".`);
551
+ process.exit(1);
552
+ }
553
+
554
+ await writeStageSecrets(secrets, workspace.root);
555
+ console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
556
+ } catch (error) {
557
+ console.error(error instanceof Error ? error.message : 'Command failed');
558
+ process.exit(1);
559
+ }
560
+ });
561
+
486
562
  // Deploy command
487
563
  program
488
564
  .command('deploy')
@@ -798,4 +874,21 @@ program
798
874
  }
799
875
  });
800
876
 
877
+ program
878
+ .command('upgrade')
879
+ .description('Upgrade all @geekmidas packages to their latest versions')
880
+ .option('--dry-run', 'Show what would be upgraded without making changes')
881
+ .action(async (options: UpgradeOptions) => {
882
+ try {
883
+ const globalOptions = program.opts();
884
+ if (globalOptions.cwd) {
885
+ process.chdir(globalOptions.cwd);
886
+ }
887
+ await upgradeCommand(options);
888
+ } catch (error) {
889
+ console.error(error instanceof Error ? error.message : 'Command failed');
890
+ process.exit(1);
891
+ }
892
+ });
893
+
801
894
  program.parse();
package/src/init/index.ts CHANGED
@@ -5,6 +5,10 @@ import prompts from 'prompts';
5
5
  import { createStageSecrets } from '../secrets/generator.js';
6
6
  import { getKeyPath } from '../secrets/keystore.js';
7
7
  import { writeStageSecrets } from '../secrets/storage.js';
8
+ import {
9
+ generateDbPassword,
10
+ generateDbUrl,
11
+ } from '../setup/fullstack-secrets.js';
8
12
  import type { ComposeServiceName } from '../types.js';
9
13
  import { generateAuthAppFiles } from './generators/auth.js';
10
14
  import { generateConfigFiles } from './generators/config.js';
@@ -60,29 +64,6 @@ export interface InitOptions {
60
64
  pm?: PackageManager;
61
65
  }
62
66
 
63
- /**
64
- * Generate a secure random password for database users
65
- */
66
- function generateDbPassword(): string {
67
- return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
68
- }
69
-
70
- /**
71
- * Generate database URL for an app
72
- * All apps connect to the same database, but use different users/schemas
73
- */
74
- function generateDbUrl(
75
- appName: string,
76
- password: string,
77
- projectName: string,
78
- host = 'localhost',
79
- port = 5432,
80
- ): string {
81
- const userName = appName.replace(/-/g, '_');
82
- const dbName = `${projectName.replace(/-/g, '_')}_dev`;
83
- return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
84
- }
85
-
86
67
  /**
87
68
  * Main init command - scaffolds a new project
88
69
  */
package/src/init/utils.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { existsSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join, parse } from 'node:path';
3
+ import fg from 'fast-glob';
4
+ import { parse as parseYaml } from 'yaml';
3
5
 
4
6
  export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
5
7
 
@@ -94,3 +96,102 @@ export function getRunCommand(
94
96
  return `npm run ${script}`;
95
97
  }
96
98
  }
99
+
100
+ const lockfileByPm: Record<PackageManager, string> = {
101
+ pnpm: 'pnpm-lock.yaml',
102
+ yarn: 'yarn.lock',
103
+ npm: 'package-lock.json',
104
+ bun: 'bun.lockb',
105
+ };
106
+
107
+ /**
108
+ * Find the workspace/project root by walking up from cwd.
109
+ * Checks for PM-specific workspace config, package.json#workspaces,
110
+ * and lockfiles.
111
+ */
112
+ export function findWorkspaceRoot(cwd: string, pm: PackageManager): string {
113
+ let dir = cwd;
114
+ const root = parse(dir).root;
115
+ const lockfile = lockfileByPm[pm];
116
+
117
+ while (dir !== root) {
118
+ if (pm === 'pnpm' && existsSync(join(dir, 'pnpm-workspace.yaml'))) {
119
+ return dir;
120
+ }
121
+
122
+ const pkgJsonPath = join(dir, 'package.json');
123
+ if (existsSync(pkgJsonPath)) {
124
+ try {
125
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
126
+ if (pkg.workspaces) return dir;
127
+ } catch {
128
+ // ignore malformed package.json
129
+ }
130
+ }
131
+
132
+ if (existsSync(join(dir, lockfile))) {
133
+ return dir;
134
+ }
135
+
136
+ dir = dirname(dir);
137
+ }
138
+
139
+ return cwd;
140
+ }
141
+
142
+ /**
143
+ * Get workspace package glob patterns from pnpm-workspace.yaml
144
+ * or package.json#workspaces.
145
+ */
146
+ export function getWorkspaceGlobs(root: string): string[] {
147
+ const pnpmWorkspacePath = join(root, 'pnpm-workspace.yaml');
148
+ if (existsSync(pnpmWorkspacePath)) {
149
+ const content = readFileSync(pnpmWorkspacePath, 'utf-8');
150
+ const parsed = parseYaml(content);
151
+ return parsed?.packages ?? [];
152
+ }
153
+
154
+ const rootPkgJsonPath = join(root, 'package.json');
155
+ if (existsSync(rootPkgJsonPath)) {
156
+ const pkg = JSON.parse(readFileSync(rootPkgJsonPath, 'utf-8'));
157
+ if (Array.isArray(pkg.workspaces)) {
158
+ return pkg.workspaces;
159
+ }
160
+ if (pkg.workspaces?.packages) {
161
+ return pkg.workspaces.packages;
162
+ }
163
+ }
164
+
165
+ return [];
166
+ }
167
+
168
+ /**
169
+ * Find all package.json files across a workspace.
170
+ * Returns the root package.json plus all workspace member package.json paths.
171
+ */
172
+ export function findWorkspacePackages(
173
+ cwd: string,
174
+ pm: PackageManager,
175
+ ): string[] {
176
+ const workspaceRoot = findWorkspaceRoot(cwd, pm);
177
+ const results: string[] = [];
178
+
179
+ const rootPkgJson = join(workspaceRoot, 'package.json');
180
+ if (existsSync(rootPkgJson)) {
181
+ results.push(rootPkgJson);
182
+ }
183
+
184
+ const globs = getWorkspaceGlobs(workspaceRoot);
185
+
186
+ for (const glob of globs) {
187
+ const pattern = `${glob}/package.json`;
188
+ const matches = fg.sync(pattern, {
189
+ cwd: workspaceRoot,
190
+ absolute: true,
191
+ ignore: ['**/node_modules/**'],
192
+ });
193
+ results.push(...matches);
194
+ }
195
+
196
+ return [...new Set(results)];
197
+ }
@@ -32,7 +32,7 @@ export const GEEKMIDAS_VERSIONS = {
32
32
  '@geekmidas/cache': '~1.0.0',
33
33
  '@geekmidas/client': '~2.0.0',
34
34
  '@geekmidas/cloud': '~1.0.0',
35
- '@geekmidas/constructs': '~1.1.0',
35
+ '@geekmidas/constructs': '~1.1.1',
36
36
  '@geekmidas/db': '~1.0.0',
37
37
  '@geekmidas/emailkit': '~1.0.0',
38
38
  '@geekmidas/envkit': '~1.0.3',
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
- import { loadConfig } from '../config';
3
+ import { loadConfig, loadWorkspaceConfig } from '../config';
4
+ import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets';
4
5
  import type { ComposeServiceName, ComposeServicesConfig } from '../types';
5
6
  import { createStageSecrets, rotateServicePassword } from './generator';
6
7
  import {
@@ -88,6 +89,20 @@ export async function secretsInitCommand(
88
89
  // Generate secrets
89
90
  const secrets = createStageSecrets(stage, services);
90
91
 
92
+ // Detect workspace mode and generate fullstack-aware custom secrets
93
+ try {
94
+ const loaded = await loadWorkspaceConfig();
95
+ const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
96
+
97
+ if (isMultiApp) {
98
+ const customSecrets = generateFullstackCustomSecrets(loaded.workspace);
99
+ secrets.custom = customSecrets;
100
+ logger.log(' Detected workspace mode — generating per-app secrets');
101
+ }
102
+ } catch {
103
+ // Not a workspace — single-app mode, skip custom secrets
104
+ }
105
+
91
106
  // Write to file
92
107
  await writeStageSecrets(secrets);
93
108
 
@@ -109,6 +124,10 @@ export async function secretsInitCommand(
109
124
  logger.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
110
125
  }
111
126
 
127
+ if (Object.keys(secrets.custom).length > 0) {
128
+ logger.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
129
+ }
130
+
112
131
  logger.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
113
132
  logger.log(
114
133
  ' Use "gkm secrets:set <KEY> <VALUE> --stage ' +