@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{HostingerProvider-CEsQbmpY.cjs → HostingerProvider-5KYmwoK2.cjs} +1 -1
  3. package/dist/{HostingerProvider-CEsQbmpY.cjs.map → HostingerProvider-5KYmwoK2.cjs.map} +1 -1
  4. package/dist/{HostingerProvider-DkahM5AP.mjs → HostingerProvider-ANWchdiK.mjs} +1 -1
  5. package/dist/{HostingerProvider-DkahM5AP.mjs.map → HostingerProvider-ANWchdiK.mjs.map} +1 -1
  6. package/dist/{LocalStateProvider-Roi202l7.cjs → LocalStateProvider-CLifRC0Y.cjs} +1 -1
  7. package/dist/{LocalStateProvider-Roi202l7.cjs.map → LocalStateProvider-CLifRC0Y.cjs.map} +1 -1
  8. package/dist/{LocalStateProvider-DXIwWb7k.mjs → LocalStateProvider-Dp0KkRcw.mjs} +1 -1
  9. package/dist/{LocalStateProvider-DXIwWb7k.mjs.map → LocalStateProvider-Dp0KkRcw.mjs.map} +1 -1
  10. package/dist/{Route53Provider-Ckq_n5Be.mjs → Route53Provider-QoPgcXxn.mjs} +1 -1
  11. package/dist/{Route53Provider-Ckq_n5Be.mjs.map → Route53Provider-QoPgcXxn.mjs.map} +1 -1
  12. package/dist/{Route53Provider-BqXeHzuc.cjs → Route53Provider-owQQ4pn6.cjs} +1 -1
  13. package/dist/{Route53Provider-BqXeHzuc.cjs.map → Route53Provider-owQQ4pn6.cjs.map} +1 -1
  14. package/dist/{SSMStateProvider-BReQA5re.cjs → SSMStateProvider-CT8tjl9o.cjs} +1 -1
  15. package/dist/{SSMStateProvider-BReQA5re.cjs.map → SSMStateProvider-CT8tjl9o.cjs.map} +1 -1
  16. package/dist/{SSMStateProvider-wddd0_-d.mjs → SSMStateProvider-CksOTB8M.mjs} +1 -1
  17. package/dist/{SSMStateProvider-wddd0_-d.mjs.map → SSMStateProvider-CksOTB8M.mjs.map} +1 -1
  18. package/dist/{backup-provisioner-BAExdDtc.mjs → backup-provisioner-BEXoHTuC.mjs} +1 -1
  19. package/dist/{backup-provisioner-BAExdDtc.mjs.map → backup-provisioner-BEXoHTuC.mjs.map} +1 -1
  20. package/dist/{backup-provisioner-C8VK63I-.cjs → backup-provisioner-C4noe75O.cjs} +1 -1
  21. package/dist/{backup-provisioner-C8VK63I-.cjs.map → backup-provisioner-C4noe75O.cjs.map} +1 -1
  22. package/dist/{bundler-BxHyDhdt.mjs → bundler-DQYjKFPm.mjs} +1 -1
  23. package/dist/{bundler-BxHyDhdt.mjs.map → bundler-DQYjKFPm.mjs.map} +1 -1
  24. package/dist/{bundler-CuMIfXw5.cjs → bundler-NpfYPBUo.cjs} +1 -1
  25. package/dist/{bundler-CuMIfXw5.cjs.map → bundler-NpfYPBUo.cjs.map} +1 -1
  26. package/dist/config.d.mts +2 -2
  27. package/dist/fullstack-secrets-COWz084x.cjs +238 -0
  28. package/dist/fullstack-secrets-COWz084x.cjs.map +1 -0
  29. package/dist/fullstack-secrets-UZAFWuH4.mjs +202 -0
  30. package/dist/fullstack-secrets-UZAFWuH4.mjs.map +1 -0
  31. package/dist/{index-BVNXOydm.d.mts → index-Bt2kX0-R.d.mts} +2 -2
  32. package/dist/{index-BVNXOydm.d.mts.map → index-Bt2kX0-R.d.mts.map} +1 -1
  33. package/dist/index.cjs +141 -276
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.mjs +128 -263
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{openapi-react-query-DaTMSPD5.mjs → openapi-react-query-C4UdILaI.mjs} +1 -1
  38. package/dist/{openapi-react-query-DaTMSPD5.mjs.map → openapi-react-query-C4UdILaI.mjs.map} +1 -1
  39. package/dist/{openapi-react-query-BeXvk-wa.cjs → openapi-react-query-DYbBq-WJ.cjs} +1 -1
  40. package/dist/{openapi-react-query-BeXvk-wa.cjs.map → openapi-react-query-DYbBq-WJ.cjs.map} +1 -1
  41. package/dist/openapi-react-query.cjs +1 -1
  42. package/dist/openapi-react-query.mjs +1 -1
  43. package/dist/openapi.d.mts +1 -1
  44. package/dist/reconcile-7yarEvmK.cjs +36 -0
  45. package/dist/reconcile-7yarEvmK.cjs.map +1 -0
  46. package/dist/reconcile-D2WCDQue.mjs +36 -0
  47. package/dist/reconcile-D2WCDQue.mjs.map +1 -0
  48. package/dist/{sync-BnqNNc6O.mjs → sync-6FoT41G3.mjs} +1 -1
  49. package/dist/{sync-CHfhmXF3.mjs → sync-CbeKrnQV.mjs} +1 -1
  50. package/dist/{sync-CHfhmXF3.mjs.map → sync-CbeKrnQV.mjs.map} +1 -1
  51. package/dist/{sync-BOS0jKLn.cjs → sync-DdkKaHqP.cjs} +1 -1
  52. package/dist/{sync-BOS0jKLn.cjs.map → sync-DdkKaHqP.cjs.map} +1 -1
  53. package/dist/sync-RsnjXYwG.cjs +4 -0
  54. package/dist/{types-eTlj5f2M.d.mts → types-wXMIMOyK.d.mts} +1 -1
  55. package/dist/{types-eTlj5f2M.d.mts.map → types-wXMIMOyK.d.mts.map} +1 -1
  56. package/dist/workspace/index.d.mts +2 -2
  57. package/package.json +3 -3
  58. package/src/dev/__tests__/index.spec.ts +49 -0
  59. package/src/dev/index.ts +84 -63
  60. package/src/index.ts +79 -1
  61. package/src/init/versions.ts +4 -4
  62. package/src/secrets/__tests__/reconcile.spec.ts +123 -0
  63. package/src/secrets/reconcile.ts +53 -0
  64. package/src/setup/fullstack-secrets.ts +2 -0
  65. 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
- const secrets = await pullSecrets(options.stage, workspace);
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')
@@ -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': '~2.0.0',
33
+ '@geekmidas/client': '~3.0.0',
34
34
  '@geekmidas/cloud': '~1.0.0',
35
- '@geekmidas/constructs': '~1.1.1',
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.0.0',
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.1',
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
 
@@ -1,4 +0,0 @@
1
- const require_sync = require('./sync-BOS0jKLn.cjs');
2
-
3
- exports.pullSecrets = require_sync.pullSecrets;
4
- exports.pushSecrets = require_sync.pushSecrets;