@geekmidas/cli 1.10.9 → 1.10.10
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 +6 -0
- package/dist/{fullstack-secrets-CtWIYuI0.cjs → fullstack-secrets-BC9t9wWB.cjs} +13 -1
- package/dist/{fullstack-secrets-CtWIYuI0.cjs.map → fullstack-secrets-BC9t9wWB.cjs.map} +1 -1
- package/dist/{fullstack-secrets-C2lbdbLZ.mjs → fullstack-secrets-DmUOfLeX.mjs} +2 -2
- package/dist/{fullstack-secrets-C2lbdbLZ.mjs.map → fullstack-secrets-DmUOfLeX.mjs.map} +1 -1
- package/dist/index.cjs +53 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +53 -17
- package/dist/index.mjs.map +1 -1
- package/dist/{reconcile-D6u4HSg8.cjs → reconcile-BZ8j_-0z.cjs} +2 -2
- package/dist/{reconcile-D6u4HSg8.cjs.map → reconcile-BZ8j_-0z.cjs.map} +1 -1
- package/dist/{reconcile-BnM6FA6g.mjs → reconcile-C0dsg-Gq.mjs} +2 -2
- package/dist/{reconcile-BnM6FA6g.mjs.map → reconcile-C0dsg-Gq.mjs.map} +1 -1
- package/package.json +5 -5
- package/src/setup/__tests__/reconcile-secrets.spec.ts +59 -0
- package/src/setup/index.ts +51 -16
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_fullstack_secrets = require('./fullstack-secrets-
|
|
1
|
+
const require_fullstack_secrets = require('./fullstack-secrets-BC9t9wWB.cjs');
|
|
2
2
|
|
|
3
3
|
//#region src/secrets/reconcile.ts
|
|
4
4
|
/**
|
|
@@ -33,4 +33,4 @@ function reconcileMissingSecrets(secrets, workspace) {
|
|
|
33
33
|
|
|
34
34
|
//#endregion
|
|
35
35
|
exports.reconcileMissingSecrets = reconcileMissingSecrets;
|
|
36
|
-
//# sourceMappingURL=reconcile-
|
|
36
|
+
//# sourceMappingURL=reconcile-BZ8j_-0z.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-
|
|
1
|
+
{"version":3,"file":"reconcile-BZ8j_-0z.cjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,yDAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateFullstackCustomSecrets } from "./fullstack-secrets-
|
|
1
|
+
import { generateFullstackCustomSecrets } from "./fullstack-secrets-DmUOfLeX.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/secrets/reconcile.ts
|
|
4
4
|
/**
|
|
@@ -33,4 +33,4 @@ function reconcileMissingSecrets(secrets, workspace) {
|
|
|
33
33
|
|
|
34
34
|
//#endregion
|
|
35
35
|
export { reconcileMissingSecrets };
|
|
36
|
-
//# sourceMappingURL=reconcile-
|
|
36
|
+
//# sourceMappingURL=reconcile-C0dsg-Gq.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-
|
|
1
|
+
{"version":3,"file":"reconcile-C0dsg-Gq.mjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,+BAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.10",
|
|
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/
|
|
60
|
-
"@geekmidas/envkit": "~1.0.3",
|
|
59
|
+
"@geekmidas/errors": "~1.0.0",
|
|
61
60
|
"@geekmidas/constructs": "~3.0.2",
|
|
62
|
-
"@geekmidas/
|
|
63
|
-
"@geekmidas/
|
|
61
|
+
"@geekmidas/envkit": "~1.0.3",
|
|
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",
|
|
@@ -124,6 +124,65 @@ describe('reconcileSecrets', () => {
|
|
|
124
124
|
expect(result).toBeNull();
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it('should add missing service credentials when workspace config adds a new service', () => {
|
|
128
|
+
const workspace = createWorkspace({
|
|
129
|
+
services: { db: true, storage: true },
|
|
130
|
+
});
|
|
131
|
+
// Secrets only have postgres, not minio
|
|
132
|
+
const secrets = createSecrets({
|
|
133
|
+
NODE_ENV: 'development',
|
|
134
|
+
PORT: '3000',
|
|
135
|
+
API_DATABASE_URL: 'postgresql://api:pass@localhost:5432/test_dev',
|
|
136
|
+
API_DB_PASSWORD: 'pass',
|
|
137
|
+
AUTH_DATABASE_URL: 'postgresql://auth:pass@localhost:5432/test_dev',
|
|
138
|
+
AUTH_DB_PASSWORD: 'pass',
|
|
139
|
+
WEB_URL: 'http://localhost:3002',
|
|
140
|
+
BETTER_AUTH_SECRET: 'existing',
|
|
141
|
+
BETTER_AUTH_URL: 'http://localhost:3001',
|
|
142
|
+
BETTER_AUTH_TRUSTED_ORIGINS:
|
|
143
|
+
'http://localhost:3000,http://localhost:3001,http://localhost:3002',
|
|
144
|
+
AUTH_PORT: '3001',
|
|
145
|
+
AUTH_URL: 'http://localhost:3001',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = reconcileSecrets(secrets, workspace);
|
|
149
|
+
|
|
150
|
+
expect(result).not.toBeNull();
|
|
151
|
+
expect(result!.services.minio).toBeDefined();
|
|
152
|
+
expect(result!.services.minio!.host).toBe('localhost');
|
|
153
|
+
expect(result!.services.minio!.port).toBe(9000);
|
|
154
|
+
expect(result!.services.minio!.bucket).toBe('app');
|
|
155
|
+
expect(result!.services.minio!.password).toHaveLength(32);
|
|
156
|
+
expect(result!.urls.S3_ENDPOINT).toBe('http://localhost:9000');
|
|
157
|
+
// Existing postgres should be preserved
|
|
158
|
+
expect(result!.services.postgres).toEqual(secrets.services.postgres);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should not regenerate credentials for existing services', () => {
|
|
162
|
+
const workspace = createWorkspace({
|
|
163
|
+
services: { db: true, storage: true },
|
|
164
|
+
});
|
|
165
|
+
// Include ALL expected custom keys so reconcile has nothing to add
|
|
166
|
+
const expected = generateFullstackCustomSecrets(
|
|
167
|
+
createWorkspace({ services: { db: true, storage: true } }),
|
|
168
|
+
);
|
|
169
|
+
const secrets = createSecrets(expected);
|
|
170
|
+
// Add existing minio creds
|
|
171
|
+
secrets.services.minio = {
|
|
172
|
+
host: 'localhost',
|
|
173
|
+
port: 9000,
|
|
174
|
+
username: 'mykey',
|
|
175
|
+
password: 'mysecret',
|
|
176
|
+
bucket: 'my-bucket',
|
|
177
|
+
};
|
|
178
|
+
secrets.urls.S3_ENDPOINT = 'http://localhost:9000';
|
|
179
|
+
|
|
180
|
+
const result = reconcileSecrets(secrets, workspace);
|
|
181
|
+
|
|
182
|
+
// No changes needed — all services and custom keys present
|
|
183
|
+
expect(result).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
127
186
|
it('should return null for single-app workspaces', () => {
|
|
128
187
|
const workspace = createWorkspace({
|
|
129
188
|
apps: {
|
package/src/setup/index.ts
CHANGED
|
@@ -3,7 +3,11 @@ import { join } from 'node:path';
|
|
|
3
3
|
import prompts from 'prompts';
|
|
4
4
|
import { loadWorkspaceConfig } from '../config.js';
|
|
5
5
|
import { resolveServicePorts, startWorkspaceServices } from '../dev/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createStageSecrets,
|
|
8
|
+
generateConnectionUrls,
|
|
9
|
+
generateServiceCredentials,
|
|
10
|
+
} from '../secrets/generator.js';
|
|
7
11
|
import {
|
|
8
12
|
readStageSecrets,
|
|
9
13
|
secretsExist,
|
|
@@ -153,32 +157,63 @@ export function reconcileSecrets(
|
|
|
153
157
|
secrets: StageSecrets,
|
|
154
158
|
workspace: NormalizedWorkspace,
|
|
155
159
|
): StageSecrets | null {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
let changed = false;
|
|
161
|
+
let result = { ...secrets };
|
|
162
|
+
|
|
163
|
+
// Reconcile service credentials: add missing services
|
|
164
|
+
const serviceMap: {
|
|
165
|
+
key: keyof typeof workspace.services;
|
|
166
|
+
name: ComposeServiceName;
|
|
167
|
+
}[] = [
|
|
168
|
+
{ key: 'db', name: 'postgres' },
|
|
169
|
+
{ key: 'cache', name: 'redis' },
|
|
170
|
+
{ key: 'storage', name: 'minio' },
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
for (const { key, name } of serviceMap) {
|
|
174
|
+
if (workspace.services[key] && !result.services[name]) {
|
|
175
|
+
const creds = generateServiceCredentials(name);
|
|
176
|
+
result = {
|
|
177
|
+
...result,
|
|
178
|
+
services: { ...result.services, [name]: creds },
|
|
179
|
+
};
|
|
180
|
+
result.urls = generateConnectionUrls(result.services);
|
|
181
|
+
logger.log(` 🔄 Adding missing service credentials: ${name}`);
|
|
182
|
+
changed = true;
|
|
183
|
+
}
|
|
159
184
|
}
|
|
160
185
|
|
|
161
|
-
|
|
162
|
-
const
|
|
186
|
+
// Reconcile custom secrets for multi-app workspaces
|
|
187
|
+
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
188
|
+
if (isMultiApp) {
|
|
189
|
+
const expected = generateFullstackCustomSecrets(workspace);
|
|
190
|
+
const missing: Record<string, string> = {};
|
|
191
|
+
|
|
192
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
193
|
+
if (!(key in result.custom)) {
|
|
194
|
+
missing[key] = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
163
197
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
if (Object.keys(missing).length > 0) {
|
|
199
|
+
logger.log(
|
|
200
|
+
` 🔄 Adding missing secrets: ${Object.keys(missing).join(', ')}`,
|
|
201
|
+
);
|
|
202
|
+
result = {
|
|
203
|
+
...result,
|
|
204
|
+
custom: { ...result.custom, ...missing },
|
|
205
|
+
};
|
|
206
|
+
changed = true;
|
|
167
207
|
}
|
|
168
208
|
}
|
|
169
209
|
|
|
170
|
-
if (
|
|
210
|
+
if (!changed) {
|
|
171
211
|
return null;
|
|
172
212
|
}
|
|
173
213
|
|
|
174
|
-
logger.log(
|
|
175
|
-
` 🔄 Adding missing secrets: ${Object.keys(missing).join(', ')}`,
|
|
176
|
-
);
|
|
177
|
-
|
|
178
214
|
return {
|
|
179
|
-
...
|
|
215
|
+
...result,
|
|
180
216
|
updatedAt: new Date().toISOString(),
|
|
181
|
-
custom: { ...secrets.custom, ...missing },
|
|
182
217
|
};
|
|
183
218
|
}
|
|
184
219
|
|