@geekmidas/cli 1.8.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.
- package/CHANGELOG.md +15 -0
- package/dist/{HostingerProvider-BiXdHjiq.cjs → HostingerProvider-CEsQbmpY.cjs} +1 -1
- package/dist/{HostingerProvider-BiXdHjiq.cjs.map → HostingerProvider-CEsQbmpY.cjs.map} +1 -1
- package/dist/{HostingerProvider-402UdK89.mjs → HostingerProvider-DkahM5AP.mjs} +1 -1
- package/dist/{HostingerProvider-402UdK89.mjs.map → HostingerProvider-DkahM5AP.mjs.map} +1 -1
- package/dist/{LocalStateProvider-BDm7ZqJo.mjs → LocalStateProvider-DXIwWb7k.mjs} +1 -1
- package/dist/{LocalStateProvider-BDm7ZqJo.mjs.map → LocalStateProvider-DXIwWb7k.mjs.map} +1 -1
- package/dist/{LocalStateProvider-CdspeSVL.cjs → LocalStateProvider-Roi202l7.cjs} +1 -1
- package/dist/{LocalStateProvider-CdspeSVL.cjs.map → LocalStateProvider-Roi202l7.cjs.map} +1 -1
- package/dist/{Route53Provider-kfJ77LmL.cjs → Route53Provider-BqXeHzuc.cjs} +1 -1
- package/dist/{Route53Provider-kfJ77LmL.cjs.map → Route53Provider-BqXeHzuc.cjs.map} +1 -1
- package/dist/{Route53Provider-DbBo7Uz5.mjs → Route53Provider-Ckq_n5Be.mjs} +1 -1
- package/dist/{Route53Provider-DbBo7Uz5.mjs.map → Route53Provider-Ckq_n5Be.mjs.map} +1 -1
- package/dist/{SSMStateProvider-DGrqYll0.cjs → SSMStateProvider-BReQA5re.cjs} +1 -1
- package/dist/{SSMStateProvider-DGrqYll0.cjs.map → SSMStateProvider-BReQA5re.cjs.map} +1 -1
- package/dist/{SSMStateProvider-DT0WV-E_.mjs → SSMStateProvider-wddd0_-d.mjs} +1 -1
- package/dist/{SSMStateProvider-DT0WV-E_.mjs.map → SSMStateProvider-wddd0_-d.mjs.map} +1 -1
- package/dist/{backup-provisioner-BIArpmTr.mjs → backup-provisioner-BAExdDtc.mjs} +1 -1
- package/dist/{backup-provisioner-BIArpmTr.mjs.map → backup-provisioner-BAExdDtc.mjs.map} +1 -1
- package/dist/{backup-provisioner-B5e-F6zX.cjs → backup-provisioner-C8VK63I-.cjs} +1 -1
- package/dist/{backup-provisioner-B5e-F6zX.cjs.map → backup-provisioner-C8VK63I-.cjs.map} +1 -1
- package/dist/{bundler-DgXsOSxc.mjs → bundler-BxHyDhdt.mjs} +1 -1
- package/dist/{bundler-DgXsOSxc.mjs.map → bundler-BxHyDhdt.mjs.map} +1 -1
- package/dist/{bundler-tHLLwYuU.cjs → bundler-CuMIfXw5.cjs} +1 -1
- package/dist/{bundler-tHLLwYuU.cjs.map → bundler-CuMIfXw5.cjs.map} +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/{index-C-KxSGGK.d.mts → index-BVNXOydm.d.mts} +2 -2
- package/dist/{index-C-KxSGGK.d.mts.map → index-BVNXOydm.d.mts.map} +1 -1
- package/dist/index.cjs +1018 -550
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1016 -548
- package/dist/index.mjs.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/sync-BOS0jKLn.cjs +93 -0
- package/dist/sync-BOS0jKLn.cjs.map +1 -0
- package/dist/sync-BnqNNc6O.mjs +3 -0
- package/dist/sync-BxFB34zW.cjs +4 -0
- package/dist/sync-CHfhmXF3.mjs +76 -0
- package/dist/sync-CHfhmXF3.mjs.map +1 -0
- package/dist/{types-CZg5iUgD.d.mts → types-eTlj5f2M.d.mts} +1 -1
- package/dist/{types-CZg5iUgD.d.mts.map → types-eTlj5f2M.d.mts.map} +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +5 -5
- package/src/dev/index.ts +1 -1
- package/src/generators/SubscriberGenerator.ts +1 -0
- package/src/index.ts +93 -0
- package/src/init/index.ts +4 -23
- package/src/init/utils.ts +103 -2
- package/src/secrets/index.ts +20 -1
- package/src/secrets/sync.ts +136 -0
- package/src/setup/fullstack-secrets.ts +121 -0
- package/src/setup/index.ts +212 -0
- package/src/test/__tests__/web.spec.ts +1 -1
- package/src/upgrade/__tests__/index.spec.ts +354 -0
- package/src/upgrade/index.ts +253 -0
package/dist/openapi.d.mts
CHANGED
|
@@ -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,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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
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-
|
|
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-
|
|
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.
|
|
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,11 +56,11 @@
|
|
|
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.1",
|
|
61
61
|
"@geekmidas/errors": "~1.0.0",
|
|
62
|
-
"@geekmidas/
|
|
63
|
-
"@geekmidas/
|
|
62
|
+
"@geekmidas/logger": "~1.0.0",
|
|
63
|
+
"@geekmidas/schema": "~1.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -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.
|
|
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
|
|
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
|
+
}
|
package/src/secrets/index.ts
CHANGED
|
@@ -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 ' +
|