@geekmidas/cli 0.54.0 → 1.0.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 +23 -0
- package/README.md +26 -5
- package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
- package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
- package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
- package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
- package/dist/Route53Provider-CpRIqu69.cjs +157 -0
- package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
- package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
- package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
- package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
- package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
- package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
- package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
- package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
- package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
- package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
- package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/credentials-C8DWtnMY.cjs +174 -0
- package/dist/credentials-C8DWtnMY.cjs.map +1 -0
- package/dist/credentials-DT1dSxIx.mjs +126 -0
- package/dist/credentials-DT1dSxIx.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
- package/dist/dokploy-api-CHa8G51l.mjs +3 -0
- package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
- package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
- package/dist/dokploy-api-CWc02yyg.cjs +3 -0
- package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
- package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
- package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/encryption-UUmaWAmz.mjs +3 -0
- package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
- package/dist/index-B5rGIc4g.d.cts.map +1 -0
- package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
- package/dist/index-KFEbMIRa.d.mts.map +1 -0
- package/dist/index.cjs +2265 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2242 -635
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
- package/dist/openapi-BMFmLnX6.mjs.map +1 -0
- package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
- package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
- package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
- package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +2 -2
- package/dist/openapi.mjs +3 -3
- package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
- package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
- package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
- package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
- package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
- package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
- package/dist/workspace-BFRUOOrh.cjs.map +1 -0
- package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
- package/dist/workspace-DAxG3_H2.mjs.map +1 -0
- package/package.json +14 -8
- package/scripts/sync-versions.ts +86 -0
- package/src/build/__tests__/handler-templates.spec.ts +115 -47
- package/src/deploy/CachedStateProvider.ts +86 -0
- package/src/deploy/LocalStateProvider.ts +57 -0
- package/src/deploy/SSMStateProvider.ts +93 -0
- package/src/deploy/StateProvider.ts +171 -0
- package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
- package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
- package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
- package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
- package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
- package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +28 -19
- package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
- package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
- package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
- package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
- package/src/deploy/__tests__/env-resolver.spec.ts +37 -15
- package/src/deploy/__tests__/sniffer.spec.ts +4 -20
- package/src/deploy/__tests__/state.spec.ts +13 -5
- package/src/deploy/dns/DnsProvider.ts +163 -0
- package/src/deploy/dns/HostingerProvider.ts +100 -0
- package/src/deploy/dns/Route53Provider.ts +256 -0
- package/src/deploy/dns/index.ts +257 -165
- package/src/deploy/env-resolver.ts +12 -5
- package/src/deploy/index.ts +16 -13
- package/src/deploy/sniffer-envkit-patch.ts +3 -1
- package/src/deploy/sniffer-routes-worker.ts +104 -0
- package/src/deploy/sniffer.ts +77 -55
- package/src/deploy/state-commands.ts +274 -0
- package/src/dev/__tests__/entry.spec.ts +8 -2
- package/src/dev/__tests__/index.spec.ts +1 -3
- package/src/dev/index.ts +9 -3
- package/src/docker/__tests__/templates.spec.ts +3 -1
- package/src/index.ts +88 -0
- package/src/init/__tests__/generators.spec.ts +273 -0
- package/src/init/__tests__/init.spec.ts +3 -3
- package/src/init/generators/auth.ts +1 -0
- package/src/init/generators/config.ts +2 -0
- package/src/init/generators/models.ts +6 -1
- package/src/init/generators/monorepo.ts +3 -0
- package/src/init/generators/ui.ts +1472 -0
- package/src/init/generators/web.ts +134 -87
- package/src/init/index.ts +22 -3
- package/src/init/templates/api.ts +109 -3
- package/src/init/versions.ts +25 -53
- package/src/openapi.ts +99 -13
- package/src/workspace/__tests__/schema.spec.ts +107 -0
- package/src/workspace/schema.ts +314 -4
- package/src/workspace/types.ts +22 -36
- package/dist/dokploy-api-CItuaWTq.mjs +0 -3
- package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
- package/dist/encryption-CQXBZGkt.mjs +0 -3
- package/dist/index-A70abJ1m.d.mts.map +0 -1
- package/dist/index-pOA56MWT.d.cts.map +0 -1
- package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
- package/dist/openapi-D7WwlpPF.cjs.map +0 -1
- package/dist/workspace-CaVW6j2q.cjs.map +0 -1
- package/dist/workspace-DLFRaDc-.mjs.map +0 -1
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS SSM Parameter Store State Provider
|
|
3
|
+
*
|
|
4
|
+
* Stores deployment state as SecureString parameters in AWS SSM.
|
|
5
|
+
* Uses AWS-managed KMS key for encryption (free tier).
|
|
6
|
+
*
|
|
7
|
+
* Parameter naming: /gkm/{workspaceName}/{stage}/state
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
GetParameterCommand,
|
|
12
|
+
ParameterNotFound,
|
|
13
|
+
PutParameterCommand,
|
|
14
|
+
SSMClient,
|
|
15
|
+
} from '@aws-sdk/client-ssm';
|
|
16
|
+
import type { AwsRegion, StateProvider } from './StateProvider';
|
|
17
|
+
import type { DokployStageState } from './state';
|
|
18
|
+
|
|
19
|
+
export interface SSMStateProviderOptions {
|
|
20
|
+
/** Workspace name (used in parameter path) */
|
|
21
|
+
workspaceName: string;
|
|
22
|
+
/** AWS region (required) */
|
|
23
|
+
region: AwsRegion;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AWS SSM Parameter Store state provider.
|
|
28
|
+
*
|
|
29
|
+
* Stores state as encrypted SecureString parameters.
|
|
30
|
+
* Parameter path: /gkm/{workspaceName}/{stage}/state
|
|
31
|
+
*/
|
|
32
|
+
export class SSMStateProvider implements StateProvider {
|
|
33
|
+
private readonly client: SSMClient;
|
|
34
|
+
private readonly workspaceName: string;
|
|
35
|
+
|
|
36
|
+
constructor(options: SSMStateProviderOptions) {
|
|
37
|
+
this.workspaceName = options.workspaceName;
|
|
38
|
+
this.client = new SSMClient({
|
|
39
|
+
region: options.region,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the SSM parameter name for a stage.
|
|
45
|
+
*/
|
|
46
|
+
private getParameterName(stage: string): string {
|
|
47
|
+
return `/gkm/${this.workspaceName}/${stage}/state`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async read(stage: string): Promise<DokployStageState | null> {
|
|
51
|
+
const parameterName = this.getParameterName(stage);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await this.client.send(
|
|
55
|
+
new GetParameterCommand({
|
|
56
|
+
Name: parameterName,
|
|
57
|
+
WithDecryption: true,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!response.Parameter?.Value) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return JSON.parse(response.Parameter.Value) as DokployStageState;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Parameter doesn't exist - return null (new deployment)
|
|
68
|
+
if (error instanceof ParameterNotFound) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Re-throw other errors (permission denied, network, etc.)
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async write(stage: string, state: DokployStageState): Promise<void> {
|
|
78
|
+
const parameterName = this.getParameterName(stage);
|
|
79
|
+
|
|
80
|
+
// Update last deployed timestamp
|
|
81
|
+
state.lastDeployedAt = new Date().toISOString();
|
|
82
|
+
|
|
83
|
+
await this.client.send(
|
|
84
|
+
new PutParameterCommand({
|
|
85
|
+
Name: parameterName,
|
|
86
|
+
Value: JSON.stringify(state),
|
|
87
|
+
Type: 'SecureString',
|
|
88
|
+
Overwrite: true,
|
|
89
|
+
Description: `GKM deployment state for ${this.workspaceName}/${stage}`,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstracts the storage backend for deployment state.
|
|
5
|
+
* Built-in providers: LocalStateProvider, SSMStateProvider
|
|
6
|
+
* Users can also supply custom implementations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { DokployStageState } from './state';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for deployment state storage providers.
|
|
13
|
+
*
|
|
14
|
+
* Implementations must handle:
|
|
15
|
+
* - Reading state for a stage (returns null if not found)
|
|
16
|
+
* - Writing state for a stage (creates or updates)
|
|
17
|
+
*/
|
|
18
|
+
export interface StateProvider {
|
|
19
|
+
/**
|
|
20
|
+
* Read deployment state for a stage.
|
|
21
|
+
*
|
|
22
|
+
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
23
|
+
* @returns The state object or null if not found
|
|
24
|
+
*/
|
|
25
|
+
read(stage: string): Promise<DokployStageState | null>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Write deployment state for a stage.
|
|
29
|
+
*
|
|
30
|
+
* @param stage - The deployment stage
|
|
31
|
+
* @param state - The state object to persist
|
|
32
|
+
*/
|
|
33
|
+
write(stage: string, state: DokployStageState): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Valid AWS regions.
|
|
38
|
+
*/
|
|
39
|
+
export type AwsRegion =
|
|
40
|
+
| 'us-east-1'
|
|
41
|
+
| 'us-east-2'
|
|
42
|
+
| 'us-west-1'
|
|
43
|
+
| 'us-west-2'
|
|
44
|
+
| 'af-south-1'
|
|
45
|
+
| 'ap-east-1'
|
|
46
|
+
| 'ap-south-1'
|
|
47
|
+
| 'ap-south-2'
|
|
48
|
+
| 'ap-southeast-1'
|
|
49
|
+
| 'ap-southeast-2'
|
|
50
|
+
| 'ap-southeast-3'
|
|
51
|
+
| 'ap-southeast-4'
|
|
52
|
+
| 'ap-northeast-1'
|
|
53
|
+
| 'ap-northeast-2'
|
|
54
|
+
| 'ap-northeast-3'
|
|
55
|
+
| 'ca-central-1'
|
|
56
|
+
| 'eu-central-1'
|
|
57
|
+
| 'eu-central-2'
|
|
58
|
+
| 'eu-west-1'
|
|
59
|
+
| 'eu-west-2'
|
|
60
|
+
| 'eu-west-3'
|
|
61
|
+
| 'eu-south-1'
|
|
62
|
+
| 'eu-south-2'
|
|
63
|
+
| 'eu-north-1'
|
|
64
|
+
| 'me-south-1'
|
|
65
|
+
| 'me-central-1'
|
|
66
|
+
| 'sa-east-1';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Local state provider config.
|
|
70
|
+
*/
|
|
71
|
+
export interface LocalStateConfig {
|
|
72
|
+
provider: 'local';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* SSM state provider config (requires region).
|
|
77
|
+
*/
|
|
78
|
+
export interface SSMStateConfig {
|
|
79
|
+
provider: 'ssm';
|
|
80
|
+
/** AWS region (required for SSM provider) */
|
|
81
|
+
region: AwsRegion;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Custom state provider config.
|
|
86
|
+
*/
|
|
87
|
+
export interface CustomStateConfig {
|
|
88
|
+
/** Custom StateProvider implementation */
|
|
89
|
+
provider: StateProvider;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* State configuration types.
|
|
94
|
+
*/
|
|
95
|
+
export type StateConfig = LocalStateConfig | SSMStateConfig | CustomStateConfig;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if value is a StateProvider implementation.
|
|
99
|
+
*/
|
|
100
|
+
export function isStateProvider(value: unknown): value is StateProvider {
|
|
101
|
+
return (
|
|
102
|
+
typeof value === 'object' &&
|
|
103
|
+
value !== null &&
|
|
104
|
+
typeof (value as StateProvider).read === 'function' &&
|
|
105
|
+
typeof (value as StateProvider).write === 'function'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CreateStateProviderOptions {
|
|
110
|
+
/** State config from workspace */
|
|
111
|
+
config?: StateConfig;
|
|
112
|
+
/** Workspace root directory (for local provider) */
|
|
113
|
+
workspaceRoot: string;
|
|
114
|
+
/** Workspace name (for SSM parameter path) */
|
|
115
|
+
workspaceName: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create a state provider based on configuration.
|
|
120
|
+
*
|
|
121
|
+
* - 'local': LocalStateProvider (default)
|
|
122
|
+
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
123
|
+
* - Custom: Use provided StateProvider implementation
|
|
124
|
+
*/
|
|
125
|
+
export async function createStateProvider(
|
|
126
|
+
options: CreateStateProviderOptions,
|
|
127
|
+
): Promise<StateProvider> {
|
|
128
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
129
|
+
|
|
130
|
+
// Default to local provider if no config
|
|
131
|
+
if (!config) {
|
|
132
|
+
const { LocalStateProvider } = await import('./LocalStateProvider');
|
|
133
|
+
return new LocalStateProvider(workspaceRoot);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Custom provider implementation
|
|
137
|
+
if (isStateProvider(config.provider)) {
|
|
138
|
+
return config.provider;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Built-in providers (discriminated by provider string)
|
|
142
|
+
const provider = config.provider;
|
|
143
|
+
|
|
144
|
+
if (provider === 'local') {
|
|
145
|
+
const { LocalStateProvider } = await import('./LocalStateProvider');
|
|
146
|
+
return new LocalStateProvider(workspaceRoot);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (provider === 'ssm') {
|
|
150
|
+
if (!workspaceName) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
'Workspace name is required for SSM state provider. Set "name" in gkm.config.ts.',
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { LocalStateProvider } = await import('./LocalStateProvider');
|
|
157
|
+
const { SSMStateProvider } = await import('./SSMStateProvider');
|
|
158
|
+
const { CachedStateProvider } = await import('./CachedStateProvider');
|
|
159
|
+
|
|
160
|
+
const local = new LocalStateProvider(workspaceRoot);
|
|
161
|
+
const ssm = new SSMStateProvider({
|
|
162
|
+
workspaceName,
|
|
163
|
+
region: (config as SSMStateConfig).region,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return new CachedStateProvider(ssm, local);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Should never reach here - custom providers handled above
|
|
170
|
+
throw new Error(`Unknown state provider: ${JSON.stringify(config)}`);
|
|
171
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { CachedStateProvider } from '../CachedStateProvider';
|
|
3
|
+
import type { StateProvider } from '../StateProvider';
|
|
4
|
+
import type { DokployStageState } from '../state';
|
|
5
|
+
|
|
6
|
+
const createMockProvider = (): StateProvider & {
|
|
7
|
+
readCalls: string[];
|
|
8
|
+
writeCalls: Array<{ stage: string; state: DokployStageState }>;
|
|
9
|
+
storage: Map<string, DokployStageState>;
|
|
10
|
+
} => {
|
|
11
|
+
const storage = new Map<string, DokployStageState>();
|
|
12
|
+
const readCalls: string[] = [];
|
|
13
|
+
const writeCalls: Array<{ stage: string; state: DokployStageState }> = [];
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
storage,
|
|
17
|
+
readCalls,
|
|
18
|
+
writeCalls,
|
|
19
|
+
async read(stage: string): Promise<DokployStageState | null> {
|
|
20
|
+
readCalls.push(stage);
|
|
21
|
+
return storage.get(stage) ?? null;
|
|
22
|
+
},
|
|
23
|
+
async write(stage: string, state: DokployStageState): Promise<void> {
|
|
24
|
+
writeCalls.push({ stage, state });
|
|
25
|
+
storage.set(stage, { ...state });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('CachedStateProvider', () => {
|
|
31
|
+
describe('read', () => {
|
|
32
|
+
it('should return local state if available', async () => {
|
|
33
|
+
const local = createMockProvider();
|
|
34
|
+
const remote = createMockProvider();
|
|
35
|
+
|
|
36
|
+
const state: DokployStageState = {
|
|
37
|
+
provider: 'dokploy',
|
|
38
|
+
stage: 'production',
|
|
39
|
+
environmentId: 'env_123',
|
|
40
|
+
applications: { api: 'local_app' },
|
|
41
|
+
services: {},
|
|
42
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
43
|
+
};
|
|
44
|
+
local.storage.set('production', state);
|
|
45
|
+
|
|
46
|
+
const cached = new CachedStateProvider(remote, local);
|
|
47
|
+
const result = await cached.read('production');
|
|
48
|
+
|
|
49
|
+
expect(result).toEqual(state);
|
|
50
|
+
expect(local.readCalls).toEqual(['production']);
|
|
51
|
+
expect(remote.readCalls).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should fetch from remote and cache locally if local missing', async () => {
|
|
55
|
+
const local = createMockProvider();
|
|
56
|
+
const remote = createMockProvider();
|
|
57
|
+
|
|
58
|
+
const state: DokployStageState = {
|
|
59
|
+
provider: 'dokploy',
|
|
60
|
+
stage: 'production',
|
|
61
|
+
environmentId: 'env_123',
|
|
62
|
+
applications: { api: 'remote_app' },
|
|
63
|
+
services: {},
|
|
64
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
65
|
+
};
|
|
66
|
+
remote.storage.set('production', state);
|
|
67
|
+
|
|
68
|
+
const cached = new CachedStateProvider(remote, local);
|
|
69
|
+
const result = await cached.read('production');
|
|
70
|
+
|
|
71
|
+
expect(result).toEqual(state);
|
|
72
|
+
expect(local.readCalls).toEqual(['production']);
|
|
73
|
+
expect(remote.readCalls).toEqual(['production']);
|
|
74
|
+
expect(local.writeCalls.length).toBe(1);
|
|
75
|
+
expect(local.writeCalls[0].stage).toBe('production');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return null if both local and remote are empty', async () => {
|
|
79
|
+
const local = createMockProvider();
|
|
80
|
+
const remote = createMockProvider();
|
|
81
|
+
|
|
82
|
+
const cached = new CachedStateProvider(remote, local);
|
|
83
|
+
const result = await cached.read('nonexistent');
|
|
84
|
+
|
|
85
|
+
expect(result).toBeNull();
|
|
86
|
+
expect(local.readCalls).toEqual(['nonexistent']);
|
|
87
|
+
expect(remote.readCalls).toEqual(['nonexistent']);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('write', () => {
|
|
92
|
+
it('should write to both remote and local', async () => {
|
|
93
|
+
const local = createMockProvider();
|
|
94
|
+
const remote = createMockProvider();
|
|
95
|
+
|
|
96
|
+
const state: DokployStageState = {
|
|
97
|
+
provider: 'dokploy',
|
|
98
|
+
stage: 'production',
|
|
99
|
+
environmentId: 'env_123',
|
|
100
|
+
applications: { api: 'app_123' },
|
|
101
|
+
services: {},
|
|
102
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const cached = new CachedStateProvider(remote, local);
|
|
106
|
+
await cached.write('production', state);
|
|
107
|
+
|
|
108
|
+
expect(remote.writeCalls.length).toBe(1);
|
|
109
|
+
expect(remote.writeCalls[0].stage).toBe('production');
|
|
110
|
+
expect(local.writeCalls.length).toBe(1);
|
|
111
|
+
expect(local.writeCalls[0].stage).toBe('production');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('pull', () => {
|
|
116
|
+
it('should fetch from remote and update local', async () => {
|
|
117
|
+
const local = createMockProvider();
|
|
118
|
+
const remote = createMockProvider();
|
|
119
|
+
|
|
120
|
+
const remoteState: DokployStageState = {
|
|
121
|
+
provider: 'dokploy',
|
|
122
|
+
stage: 'production',
|
|
123
|
+
environmentId: 'env_123',
|
|
124
|
+
applications: { api: 'remote_app' },
|
|
125
|
+
services: {},
|
|
126
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
127
|
+
};
|
|
128
|
+
remote.storage.set('production', remoteState);
|
|
129
|
+
|
|
130
|
+
const cached = new CachedStateProvider(remote, local);
|
|
131
|
+
const result = await cached.pull('production');
|
|
132
|
+
|
|
133
|
+
expect(result).toEqual(remoteState);
|
|
134
|
+
expect(remote.readCalls).toEqual(['production']);
|
|
135
|
+
expect(local.writeCalls.length).toBe(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return null if remote is empty', async () => {
|
|
139
|
+
const local = createMockProvider();
|
|
140
|
+
const remote = createMockProvider();
|
|
141
|
+
|
|
142
|
+
const cached = new CachedStateProvider(remote, local);
|
|
143
|
+
const result = await cached.pull('nonexistent');
|
|
144
|
+
|
|
145
|
+
expect(result).toBeNull();
|
|
146
|
+
expect(local.writeCalls.length).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('push', () => {
|
|
151
|
+
it('should push local state to remote', async () => {
|
|
152
|
+
const local = createMockProvider();
|
|
153
|
+
const remote = createMockProvider();
|
|
154
|
+
|
|
155
|
+
const localState: DokployStageState = {
|
|
156
|
+
provider: 'dokploy',
|
|
157
|
+
stage: 'production',
|
|
158
|
+
environmentId: 'env_123',
|
|
159
|
+
applications: { api: 'local_app' },
|
|
160
|
+
services: {},
|
|
161
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
162
|
+
};
|
|
163
|
+
local.storage.set('production', localState);
|
|
164
|
+
|
|
165
|
+
const cached = new CachedStateProvider(remote, local);
|
|
166
|
+
const result = await cached.push('production');
|
|
167
|
+
|
|
168
|
+
expect(result).toEqual(localState);
|
|
169
|
+
expect(local.readCalls).toEqual(['production']);
|
|
170
|
+
expect(remote.writeCalls.length).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return null if local is empty', async () => {
|
|
174
|
+
const local = createMockProvider();
|
|
175
|
+
const remote = createMockProvider();
|
|
176
|
+
|
|
177
|
+
const cached = new CachedStateProvider(remote, local);
|
|
178
|
+
const result = await cached.push('nonexistent');
|
|
179
|
+
|
|
180
|
+
expect(result).toBeNull();
|
|
181
|
+
expect(remote.writeCalls.length).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('diff', () => {
|
|
186
|
+
it('should return both local and remote state', async () => {
|
|
187
|
+
const local = createMockProvider();
|
|
188
|
+
const remote = createMockProvider();
|
|
189
|
+
|
|
190
|
+
const localState: DokployStageState = {
|
|
191
|
+
provider: 'dokploy',
|
|
192
|
+
stage: 'production',
|
|
193
|
+
environmentId: 'env_123',
|
|
194
|
+
applications: { api: 'local_app' },
|
|
195
|
+
services: {},
|
|
196
|
+
lastDeployedAt: '2024-01-01T00:00:00.000Z',
|
|
197
|
+
};
|
|
198
|
+
const remoteState: DokployStageState = {
|
|
199
|
+
provider: 'dokploy',
|
|
200
|
+
stage: 'production',
|
|
201
|
+
environmentId: 'env_123',
|
|
202
|
+
applications: { api: 'remote_app' },
|
|
203
|
+
services: {},
|
|
204
|
+
lastDeployedAt: '2024-01-02T00:00:00.000Z',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
local.storage.set('production', localState);
|
|
208
|
+
remote.storage.set('production', remoteState);
|
|
209
|
+
|
|
210
|
+
const cached = new CachedStateProvider(remote, local);
|
|
211
|
+
const result = await cached.diff('production');
|
|
212
|
+
|
|
213
|
+
expect(result.local).toEqual(localState);
|
|
214
|
+
expect(result.remote).toEqual(remoteState);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should handle missing states', async () => {
|
|
218
|
+
const local = createMockProvider();
|
|
219
|
+
const remote = createMockProvider();
|
|
220
|
+
|
|
221
|
+
const cached = new CachedStateProvider(remote, local);
|
|
222
|
+
const result = await cached.diff('nonexistent');
|
|
223
|
+
|
|
224
|
+
expect(result.local).toBeNull();
|
|
225
|
+
expect(result.remote).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|