@geekmidas/cli 1.9.0 → 1.9.1
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/{HostingerProvider-CEsQbmpY.cjs → HostingerProvider-5KYmwoK2.cjs} +1 -1
- package/dist/{HostingerProvider-CEsQbmpY.cjs.map → HostingerProvider-5KYmwoK2.cjs.map} +1 -1
- package/dist/{HostingerProvider-DkahM5AP.mjs → HostingerProvider-ANWchdiK.mjs} +1 -1
- package/dist/{HostingerProvider-DkahM5AP.mjs.map → HostingerProvider-ANWchdiK.mjs.map} +1 -1
- package/dist/{LocalStateProvider-Roi202l7.cjs → LocalStateProvider-CLifRC0Y.cjs} +1 -1
- package/dist/{LocalStateProvider-Roi202l7.cjs.map → LocalStateProvider-CLifRC0Y.cjs.map} +1 -1
- package/dist/{LocalStateProvider-DXIwWb7k.mjs → LocalStateProvider-Dp0KkRcw.mjs} +1 -1
- package/dist/{LocalStateProvider-DXIwWb7k.mjs.map → LocalStateProvider-Dp0KkRcw.mjs.map} +1 -1
- package/dist/{Route53Provider-Ckq_n5Be.mjs → Route53Provider-QoPgcXxn.mjs} +1 -1
- package/dist/{Route53Provider-Ckq_n5Be.mjs.map → Route53Provider-QoPgcXxn.mjs.map} +1 -1
- package/dist/{Route53Provider-BqXeHzuc.cjs → Route53Provider-owQQ4pn6.cjs} +1 -1
- package/dist/{Route53Provider-BqXeHzuc.cjs.map → Route53Provider-owQQ4pn6.cjs.map} +1 -1
- package/dist/{SSMStateProvider-BReQA5re.cjs → SSMStateProvider-CT8tjl9o.cjs} +1 -1
- package/dist/{SSMStateProvider-BReQA5re.cjs.map → SSMStateProvider-CT8tjl9o.cjs.map} +1 -1
- package/dist/{SSMStateProvider-wddd0_-d.mjs → SSMStateProvider-CksOTB8M.mjs} +1 -1
- package/dist/{SSMStateProvider-wddd0_-d.mjs.map → SSMStateProvider-CksOTB8M.mjs.map} +1 -1
- package/dist/{backup-provisioner-BAExdDtc.mjs → backup-provisioner-BEXoHTuC.mjs} +1 -1
- package/dist/{backup-provisioner-BAExdDtc.mjs.map → backup-provisioner-BEXoHTuC.mjs.map} +1 -1
- package/dist/{backup-provisioner-C8VK63I-.cjs → backup-provisioner-C4noe75O.cjs} +1 -1
- package/dist/{backup-provisioner-C8VK63I-.cjs.map → backup-provisioner-C4noe75O.cjs.map} +1 -1
- package/dist/{bundler-BxHyDhdt.mjs → bundler-DQYjKFPm.mjs} +1 -1
- package/dist/{bundler-BxHyDhdt.mjs.map → bundler-DQYjKFPm.mjs.map} +1 -1
- package/dist/{bundler-CuMIfXw5.cjs → bundler-NpfYPBUo.cjs} +1 -1
- package/dist/{bundler-CuMIfXw5.cjs.map → bundler-NpfYPBUo.cjs.map} +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/fullstack-secrets-COWz084x.cjs +238 -0
- package/dist/fullstack-secrets-COWz084x.cjs.map +1 -0
- package/dist/fullstack-secrets-UZAFWuH4.mjs +202 -0
- package/dist/fullstack-secrets-UZAFWuH4.mjs.map +1 -0
- package/dist/{index-BVNXOydm.d.mts → index-Bt2kX0-R.d.mts} +2 -2
- package/dist/{index-BVNXOydm.d.mts.map → index-Bt2kX0-R.d.mts.map} +1 -1
- package/dist/index.cjs +141 -276
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +128 -263
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-react-query-DaTMSPD5.mjs → openapi-react-query-C4UdILaI.mjs} +1 -1
- package/dist/{openapi-react-query-DaTMSPD5.mjs.map → openapi-react-query-C4UdILaI.mjs.map} +1 -1
- package/dist/{openapi-react-query-BeXvk-wa.cjs → openapi-react-query-DYbBq-WJ.cjs} +1 -1
- package/dist/{openapi-react-query-BeXvk-wa.cjs.map → openapi-react-query-DYbBq-WJ.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/reconcile-7yarEvmK.cjs +36 -0
- package/dist/reconcile-7yarEvmK.cjs.map +1 -0
- package/dist/reconcile-D2WCDQue.mjs +36 -0
- package/dist/reconcile-D2WCDQue.mjs.map +1 -0
- package/dist/{sync-BnqNNc6O.mjs → sync-6FoT41G3.mjs} +1 -1
- package/dist/{sync-CHfhmXF3.mjs → sync-CbeKrnQV.mjs} +1 -1
- package/dist/{sync-CHfhmXF3.mjs.map → sync-CbeKrnQV.mjs.map} +1 -1
- package/dist/{sync-BOS0jKLn.cjs → sync-DdkKaHqP.cjs} +1 -1
- package/dist/{sync-BOS0jKLn.cjs.map → sync-DdkKaHqP.cjs.map} +1 -1
- package/dist/sync-RsnjXYwG.cjs +4 -0
- package/dist/{types-eTlj5f2M.d.mts → types-wXMIMOyK.d.mts} +1 -1
- package/dist/{types-eTlj5f2M.d.mts.map → types-wXMIMOyK.d.mts.map} +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +3 -3
- package/src/dev/__tests__/index.spec.ts +49 -0
- package/src/dev/index.ts +84 -63
- package/src/index.ts +79 -1
- package/src/init/versions.ts +4 -4
- package/src/secrets/__tests__/reconcile.spec.ts +123 -0
- package/src/secrets/reconcile.ts +53 -0
- package/src/setup/fullstack-secrets.ts +2 -0
- package/dist/sync-BxFB34zW.cjs +0 -4
package/src/index.ts
CHANGED
|
@@ -518,8 +518,27 @@ program
|
|
|
518
518
|
|
|
519
519
|
const { loadWorkspaceConfig } = await import('./config');
|
|
520
520
|
const { pushSecrets } = await import('./secrets/sync');
|
|
521
|
+
const { reconcileMissingSecrets } = await import('./secrets/reconcile');
|
|
522
|
+
const { readStageSecrets, writeStageSecrets } = await import(
|
|
523
|
+
'./secrets/storage'
|
|
524
|
+
);
|
|
521
525
|
|
|
522
526
|
const { workspace } = await loadWorkspaceConfig();
|
|
527
|
+
|
|
528
|
+
const secrets = await readStageSecrets(options.stage, workspace.root);
|
|
529
|
+
if (secrets) {
|
|
530
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
531
|
+
if (result) {
|
|
532
|
+
await writeStageSecrets(result.secrets, workspace.root);
|
|
533
|
+
console.log(
|
|
534
|
+
` Reconciled ${result.addedKeys.length} missing secret(s):`,
|
|
535
|
+
);
|
|
536
|
+
for (const key of result.addedKeys) {
|
|
537
|
+
console.log(` + ${key}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
523
542
|
await pushSecrets(options.stage, workspace);
|
|
524
543
|
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
525
544
|
} catch (error) {
|
|
@@ -542,15 +561,27 @@ program
|
|
|
542
561
|
const { loadWorkspaceConfig } = await import('./config');
|
|
543
562
|
const { pullSecrets } = await import('./secrets/sync');
|
|
544
563
|
const { writeStageSecrets } = await import('./secrets/storage');
|
|
564
|
+
const { reconcileMissingSecrets } = await import('./secrets/reconcile');
|
|
545
565
|
|
|
546
566
|
const { workspace } = await loadWorkspaceConfig();
|
|
547
|
-
|
|
567
|
+
let secrets = await pullSecrets(options.stage, workspace);
|
|
548
568
|
|
|
549
569
|
if (!secrets) {
|
|
550
570
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
551
571
|
process.exit(1);
|
|
552
572
|
}
|
|
553
573
|
|
|
574
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
575
|
+
if (result) {
|
|
576
|
+
secrets = result.secrets;
|
|
577
|
+
console.log(
|
|
578
|
+
` Reconciled ${result.addedKeys.length} missing secret(s):`,
|
|
579
|
+
);
|
|
580
|
+
for (const key of result.addedKeys) {
|
|
581
|
+
console.log(` + ${key}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
554
585
|
await writeStageSecrets(secrets, workspace.root);
|
|
555
586
|
console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
|
|
556
587
|
} catch (error) {
|
|
@@ -559,6 +590,53 @@ program
|
|
|
559
590
|
}
|
|
560
591
|
});
|
|
561
592
|
|
|
593
|
+
program
|
|
594
|
+
.command('secrets:reconcile')
|
|
595
|
+
.description('Backfill missing custom secrets from workspace config')
|
|
596
|
+
.option('--stage <stage>', 'Stage name', 'development')
|
|
597
|
+
.action(async (options: { stage: string }) => {
|
|
598
|
+
try {
|
|
599
|
+
const globalOptions = program.opts();
|
|
600
|
+
if (globalOptions.cwd) {
|
|
601
|
+
process.chdir(globalOptions.cwd);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const { loadWorkspaceConfig } = await import('./config');
|
|
605
|
+
const { reconcileMissingSecrets } = await import('./secrets/reconcile');
|
|
606
|
+
const { readStageSecrets, writeStageSecrets } = await import(
|
|
607
|
+
'./secrets/storage'
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
611
|
+
const secrets = await readStageSecrets(options.stage, workspace.root);
|
|
612
|
+
|
|
613
|
+
if (!secrets) {
|
|
614
|
+
console.error(
|
|
615
|
+
`No secrets found for stage "${options.stage}". Run "gkm secrets:init --stage ${options.stage}" first.`,
|
|
616
|
+
);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
621
|
+
|
|
622
|
+
if (!result) {
|
|
623
|
+
console.log(`\n✓ Secrets for stage "${options.stage}" are up-to-date`);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
await writeStageSecrets(result.secrets, workspace.root);
|
|
628
|
+
console.log(
|
|
629
|
+
`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${options.stage}":`,
|
|
630
|
+
);
|
|
631
|
+
for (const key of result.addedKeys) {
|
|
632
|
+
console.log(` + ${key}`);
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
console.error(error instanceof Error ? error.message : 'Command failed');
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
562
640
|
// Deploy command
|
|
563
641
|
program
|
|
564
642
|
.command('deploy')
|
package/src/init/versions.ts
CHANGED
|
@@ -30,14 +30,14 @@ export const GEEKMIDAS_VERSIONS = {
|
|
|
30
30
|
'@geekmidas/audit': '~1.0.0',
|
|
31
31
|
'@geekmidas/auth': '~1.0.0',
|
|
32
32
|
'@geekmidas/cache': '~1.0.0',
|
|
33
|
-
'@geekmidas/client': '~
|
|
33
|
+
'@geekmidas/client': '~3.0.0',
|
|
34
34
|
'@geekmidas/cloud': '~1.0.0',
|
|
35
|
-
'@geekmidas/constructs': '~
|
|
35
|
+
'@geekmidas/constructs': '~2.0.0',
|
|
36
36
|
'@geekmidas/db': '~1.0.0',
|
|
37
37
|
'@geekmidas/emailkit': '~1.0.0',
|
|
38
38
|
'@geekmidas/envkit': '~1.0.3',
|
|
39
39
|
'@geekmidas/errors': '~1.0.0',
|
|
40
|
-
'@geekmidas/events': '~1.
|
|
40
|
+
'@geekmidas/events': '~1.1.0',
|
|
41
41
|
'@geekmidas/logger': '~1.0.0',
|
|
42
42
|
'@geekmidas/rate-limit': '~1.0.0',
|
|
43
43
|
'@geekmidas/schema': '~1.0.0',
|
|
@@ -45,7 +45,7 @@ export const GEEKMIDAS_VERSIONS = {
|
|
|
45
45
|
'@geekmidas/storage': '~1.0.0',
|
|
46
46
|
'@geekmidas/studio': '~1.0.0',
|
|
47
47
|
'@geekmidas/telescope': '~1.0.0',
|
|
48
|
-
'@geekmidas/testkit': '~1.0.
|
|
48
|
+
'@geekmidas/testkit': '~1.0.2',
|
|
49
49
|
'@geekmidas/cli': CLI_VERSION,
|
|
50
50
|
} as const;
|
|
51
51
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { generateFullstackCustomSecrets } from '../../setup/fullstack-secrets';
|
|
3
|
+
import type { NormalizedWorkspace } from '../../workspace/types';
|
|
4
|
+
import { reconcileMissingSecrets } from '../reconcile';
|
|
5
|
+
import type { StageSecrets } from '../types';
|
|
6
|
+
|
|
7
|
+
function createMultiAppWorkspace(
|
|
8
|
+
overrides?: Partial<NormalizedWorkspace>,
|
|
9
|
+
): NormalizedWorkspace {
|
|
10
|
+
return {
|
|
11
|
+
name: 'test-project',
|
|
12
|
+
root: '/tmp/test',
|
|
13
|
+
apps: {
|
|
14
|
+
api: {
|
|
15
|
+
type: 'backend',
|
|
16
|
+
path: 'apps/api',
|
|
17
|
+
port: 3000,
|
|
18
|
+
dependencies: [],
|
|
19
|
+
resolvedDeployTarget: 'dokploy',
|
|
20
|
+
},
|
|
21
|
+
web: {
|
|
22
|
+
type: 'frontend',
|
|
23
|
+
path: 'apps/web',
|
|
24
|
+
port: 3001,
|
|
25
|
+
dependencies: ['api'],
|
|
26
|
+
resolvedDeployTarget: 'dokploy',
|
|
27
|
+
framework: 'nextjs',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
services: { db: true },
|
|
31
|
+
deploy: {},
|
|
32
|
+
shared: {},
|
|
33
|
+
secrets: {},
|
|
34
|
+
...overrides,
|
|
35
|
+
} as NormalizedWorkspace;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createSecrets(custom: Record<string, string> = {}): StageSecrets {
|
|
39
|
+
return {
|
|
40
|
+
stage: 'development',
|
|
41
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
42
|
+
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
43
|
+
services: {},
|
|
44
|
+
urls: {},
|
|
45
|
+
custom,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('reconcileMissingSecrets', () => {
|
|
50
|
+
it('should return null for single-app workspace', () => {
|
|
51
|
+
const workspace = createMultiAppWorkspace({
|
|
52
|
+
apps: {
|
|
53
|
+
api: {
|
|
54
|
+
type: 'backend',
|
|
55
|
+
path: 'apps/api',
|
|
56
|
+
port: 3000,
|
|
57
|
+
dependencies: [],
|
|
58
|
+
resolvedDeployTarget: 'dokploy',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}) as NormalizedWorkspace;
|
|
62
|
+
|
|
63
|
+
const result = reconcileMissingSecrets(createSecrets(), workspace);
|
|
64
|
+
expect(result).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return null when no keys are missing', () => {
|
|
68
|
+
const workspace = createMultiAppWorkspace();
|
|
69
|
+
const expected = generateFullstackCustomSecrets(workspace);
|
|
70
|
+
|
|
71
|
+
const result = reconcileMissingSecrets(createSecrets(expected), workspace);
|
|
72
|
+
expect(result).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should backfill missing keys', () => {
|
|
76
|
+
const workspace = createMultiAppWorkspace();
|
|
77
|
+
const secrets = createSecrets();
|
|
78
|
+
|
|
79
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
80
|
+
|
|
81
|
+
expect(result).not.toBeNull();
|
|
82
|
+
expect(result!.addedKeys.length).toBeGreaterThan(0);
|
|
83
|
+
for (const key of result!.addedKeys) {
|
|
84
|
+
expect(result!.secrets.custom[key]).toBeDefined();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should never overwrite existing keys', () => {
|
|
89
|
+
const workspace = createMultiAppWorkspace();
|
|
90
|
+
const existingValue = 'my-custom-jwt-secret';
|
|
91
|
+
const secrets = createSecrets({
|
|
92
|
+
JWT_SECRET: existingValue,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
96
|
+
|
|
97
|
+
expect(result).not.toBeNull();
|
|
98
|
+
expect(result!.secrets.custom.JWT_SECRET).toBe(existingValue);
|
|
99
|
+
expect(result!.addedKeys).not.toContain('JWT_SECRET');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should update updatedAt timestamp', () => {
|
|
103
|
+
const workspace = createMultiAppWorkspace();
|
|
104
|
+
const secrets = createSecrets();
|
|
105
|
+
const originalUpdatedAt = secrets.updatedAt;
|
|
106
|
+
|
|
107
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
108
|
+
|
|
109
|
+
expect(result).not.toBeNull();
|
|
110
|
+
expect(result!.secrets.updatedAt).not.toBe(originalUpdatedAt);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should backfill frontend URL keys', () => {
|
|
114
|
+
const workspace = createMultiAppWorkspace();
|
|
115
|
+
const secrets = createSecrets();
|
|
116
|
+
|
|
117
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
118
|
+
|
|
119
|
+
expect(result).not.toBeNull();
|
|
120
|
+
expect(result!.secrets.custom.WEB_URL).toBe('http://localhost:3001');
|
|
121
|
+
expect(result!.addedKeys).toContain('WEB_URL');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';
|
|
2
|
+
import type { NormalizedWorkspace } from '../workspace/types.js';
|
|
3
|
+
import type { StageSecrets } from './types.js';
|
|
4
|
+
|
|
5
|
+
export interface ReconcileResult {
|
|
6
|
+
/** The updated secrets with missing keys backfilled */
|
|
7
|
+
secrets: StageSecrets;
|
|
8
|
+
/** Keys that were added */
|
|
9
|
+
addedKeys: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reconcile missing custom secrets for a workspace.
|
|
14
|
+
*
|
|
15
|
+
* Compares current secrets against what generateFullstackCustomSecrets()
|
|
16
|
+
* would produce and backfills any missing keys without overwriting
|
|
17
|
+
* existing values.
|
|
18
|
+
*
|
|
19
|
+
* @returns ReconcileResult if keys were added, null if secrets are up-to-date
|
|
20
|
+
*/
|
|
21
|
+
export function reconcileMissingSecrets(
|
|
22
|
+
secrets: StageSecrets,
|
|
23
|
+
workspace: NormalizedWorkspace,
|
|
24
|
+
): ReconcileResult | null {
|
|
25
|
+
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
26
|
+
if (!isMultiApp) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const expectedCustom = generateFullstackCustomSecrets(workspace);
|
|
31
|
+
const addedKeys: string[] = [];
|
|
32
|
+
const mergedCustom = { ...secrets.custom };
|
|
33
|
+
|
|
34
|
+
for (const [key, value] of Object.entries(expectedCustom)) {
|
|
35
|
+
if (!(key in mergedCustom)) {
|
|
36
|
+
mergedCustom[key] = value;
|
|
37
|
+
addedKeys.push(key);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (addedKeys.length === 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
secrets: {
|
|
47
|
+
...secrets,
|
|
48
|
+
updatedAt: new Date().toISOString(),
|
|
49
|
+
custom: mergedCustom,
|
|
50
|
+
},
|
|
51
|
+
addedKeys,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -57,6 +57,8 @@ export function generateFullstackCustomSecrets(
|
|
|
57
57
|
for (const [appName, appConfig] of Object.entries(workspace.apps)) {
|
|
58
58
|
if (appConfig.type === 'frontend') {
|
|
59
59
|
frontendPorts.push(appConfig.port);
|
|
60
|
+
const upperName = appName.toUpperCase();
|
|
61
|
+
customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
|
|
60
62
|
continue;
|
|
61
63
|
}
|
|
62
64
|
|
package/dist/sync-BxFB34zW.cjs
DELETED