@geekmidas/cli 0.46.0 → 0.48.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/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/{dokploy-api-D8a0eQQB.cjs → dokploy-api-BDLu0qWi.cjs} +12 -1
- package/dist/dokploy-api-BDLu0qWi.cjs.map +1 -0
- package/dist/dokploy-api-BN3V57z1.mjs +3 -0
- package/dist/dokploy-api-BdCKjFDA.cjs +3 -0
- package/dist/{dokploy-api-b6usLLKk.mjs → dokploy-api-DvzIDxTj.mjs} +12 -1
- package/dist/dokploy-api-DvzIDxTj.mjs.map +1 -0
- package/dist/{index-BtnjoghR.d.mts → index-A70abJ1m.d.mts} +60 -2
- package/dist/index-A70abJ1m.d.mts.map +1 -0
- package/dist/{index-c89X2mi2.d.cts → index-pOA56MWT.d.cts} +60 -2
- package/dist/index-pOA56MWT.d.cts.map +1 -0
- package/dist/index.cjs +685 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +685 -249
- package/dist/index.mjs.map +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace-CaVW6j2q.cjs.map +1 -1
- package/dist/workspace-DLFRaDc-.mjs.map +1 -1
- package/package.json +3 -3
- package/src/auth/credentials.ts +66 -0
- package/src/deploy/dns/hostinger-api.ts +258 -0
- package/src/deploy/dns/index.ts +398 -0
- package/src/deploy/dokploy-api.ts +12 -0
- package/src/deploy/index.ts +108 -35
- package/src/docker/templates.ts +10 -14
- package/src/workspace/types.ts +64 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/dokploy-api-C1JgU9Vr.mjs +0 -3
- package/dist/dokploy-api-Cpq_tLSz.cjs +0 -3
- package/dist/dokploy-api-D8a0eQQB.cjs.map +0 -1
- package/dist/dokploy-api-b6usLLKk.mjs.map +0 -1
- package/dist/index-BtnjoghR.d.mts.map +0 -1
- package/dist/index-c89X2mi2.d.cts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"prompts": "~2.4.2",
|
|
51
51
|
"@geekmidas/constructs": "~0.7.0",
|
|
52
52
|
"@geekmidas/envkit": "~0.6.0",
|
|
53
|
-
"@geekmidas/schema": "~0.1.0",
|
|
54
53
|
"@geekmidas/errors": "~0.1.0",
|
|
55
|
-
"@geekmidas/logger": "~0.4.0"
|
|
54
|
+
"@geekmidas/logger": "~0.4.0",
|
|
55
|
+
"@geekmidas/schema": "~0.1.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/lodash.kebabcase": "^4.1.9",
|
package/src/auth/credentials.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface StoredCredentials {
|
|
|
17
17
|
/** When the credentials were stored */
|
|
18
18
|
storedAt: string;
|
|
19
19
|
};
|
|
20
|
+
hostinger?: {
|
|
21
|
+
/** API token from hpanel.hostinger.com/profile/api */
|
|
22
|
+
token: string;
|
|
23
|
+
/** When the credentials were stored */
|
|
24
|
+
storedAt: string;
|
|
25
|
+
};
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
/**
|
|
@@ -218,3 +224,63 @@ export async function getDokployRegistryId(
|
|
|
218
224
|
const stored = await getDokployCredentials(options);
|
|
219
225
|
return stored?.registryId ?? undefined;
|
|
220
226
|
}
|
|
227
|
+
|
|
228
|
+
// ============================================
|
|
229
|
+
// Hostinger credentials
|
|
230
|
+
// ============================================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Store Hostinger API token
|
|
234
|
+
*
|
|
235
|
+
* @param token - API token from hpanel.hostinger.com/profile/api
|
|
236
|
+
*/
|
|
237
|
+
export async function storeHostingerToken(
|
|
238
|
+
token: string,
|
|
239
|
+
options?: CredentialOptions,
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
const credentials = await readCredentials(options);
|
|
242
|
+
|
|
243
|
+
credentials.hostinger = {
|
|
244
|
+
token,
|
|
245
|
+
storedAt: new Date().toISOString(),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
await writeCredentials(credentials, options);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get stored Hostinger API token
|
|
253
|
+
*
|
|
254
|
+
* Checks environment variable first (HOSTINGER_API_TOKEN),
|
|
255
|
+
* then falls back to stored credentials.
|
|
256
|
+
*/
|
|
257
|
+
export async function getHostingerToken(
|
|
258
|
+
options?: CredentialOptions,
|
|
259
|
+
): Promise<string | null> {
|
|
260
|
+
// First check environment variable (takes precedence)
|
|
261
|
+
const envToken = process.env.HOSTINGER_API_TOKEN;
|
|
262
|
+
if (envToken) {
|
|
263
|
+
return envToken;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Then check stored credentials
|
|
267
|
+
const credentials = await readCredentials(options);
|
|
268
|
+
return credentials.hostinger?.token ?? null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Remove Hostinger credentials
|
|
273
|
+
*/
|
|
274
|
+
export async function removeHostingerCredentials(
|
|
275
|
+
options?: CredentialOptions,
|
|
276
|
+
): Promise<boolean> {
|
|
277
|
+
const credentials = await readCredentials(options);
|
|
278
|
+
|
|
279
|
+
if (!credentials.hostinger) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
delete credentials.hostinger;
|
|
284
|
+
await writeCredentials(credentials, options);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostinger DNS API client
|
|
3
|
+
*
|
|
4
|
+
* API Documentation: https://developers.hostinger.com/
|
|
5
|
+
* Authentication: Bearer token from hpanel.hostinger.com/profile/api
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const HOSTINGER_API_BASE = 'https://developers.hostinger.com';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DNS record types supported by Hostinger
|
|
12
|
+
*/
|
|
13
|
+
export type DnsRecordType =
|
|
14
|
+
| 'A'
|
|
15
|
+
| 'AAAA'
|
|
16
|
+
| 'CNAME'
|
|
17
|
+
| 'MX'
|
|
18
|
+
| 'TXT'
|
|
19
|
+
| 'NS'
|
|
20
|
+
| 'SRV'
|
|
21
|
+
| 'CAA';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A single DNS record
|
|
25
|
+
*/
|
|
26
|
+
export interface DnsRecord {
|
|
27
|
+
/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Record type */
|
|
30
|
+
type: DnsRecordType;
|
|
31
|
+
/** TTL in seconds */
|
|
32
|
+
ttl: number;
|
|
33
|
+
/** Record values */
|
|
34
|
+
records: Array<{ content: string }>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Filter for deleting specific records
|
|
39
|
+
*/
|
|
40
|
+
export interface DnsRecordFilter {
|
|
41
|
+
name: string;
|
|
42
|
+
type: DnsRecordType;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* API error response
|
|
47
|
+
*/
|
|
48
|
+
export interface HostingerErrorResponse {
|
|
49
|
+
message?: string;
|
|
50
|
+
errors?: Record<string, string[]>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hostinger API error
|
|
55
|
+
*/
|
|
56
|
+
export class HostingerApiError extends Error {
|
|
57
|
+
constructor(
|
|
58
|
+
message: string,
|
|
59
|
+
public status: number,
|
|
60
|
+
public statusText: string,
|
|
61
|
+
public errors?: Record<string, string[]>,
|
|
62
|
+
) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = 'HostingerApiError';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Hostinger DNS API client
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const api = new HostingerApi(token);
|
|
74
|
+
*
|
|
75
|
+
* // Get all records for a domain
|
|
76
|
+
* const records = await api.getRecords('traflabs.io');
|
|
77
|
+
*
|
|
78
|
+
* // Create/update records
|
|
79
|
+
* await api.upsertRecords('traflabs.io', [
|
|
80
|
+
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
81
|
+
* ]);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export class HostingerApi {
|
|
85
|
+
private token: string;
|
|
86
|
+
|
|
87
|
+
constructor(token: string) {
|
|
88
|
+
this.token = token;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Make a request to the Hostinger API
|
|
93
|
+
*/
|
|
94
|
+
private async request<T>(
|
|
95
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
96
|
+
endpoint: string,
|
|
97
|
+
body?: unknown,
|
|
98
|
+
): Promise<T> {
|
|
99
|
+
const url = `${HOSTINGER_API_BASE}${endpoint}`;
|
|
100
|
+
|
|
101
|
+
const response = await fetch(url, {
|
|
102
|
+
method,
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
Authorization: `Bearer ${this.token}`,
|
|
106
|
+
},
|
|
107
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
112
|
+
let errors: Record<string, string[]> | undefined;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const errorBody = (await response.json()) as HostingerErrorResponse;
|
|
116
|
+
if (errorBody.message) {
|
|
117
|
+
errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
118
|
+
}
|
|
119
|
+
errors = errorBody.errors;
|
|
120
|
+
} catch {
|
|
121
|
+
// Ignore JSON parse errors
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new HostingerApiError(
|
|
125
|
+
errorMessage,
|
|
126
|
+
response.status,
|
|
127
|
+
response.statusText,
|
|
128
|
+
errors,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle empty responses
|
|
133
|
+
const text = await response.text();
|
|
134
|
+
if (!text || text.trim() === '') {
|
|
135
|
+
return undefined as T;
|
|
136
|
+
}
|
|
137
|
+
return JSON.parse(text) as T;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all DNS records for a domain
|
|
142
|
+
*
|
|
143
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
144
|
+
*/
|
|
145
|
+
async getRecords(domain: string): Promise<DnsRecord[]> {
|
|
146
|
+
interface RecordResponse {
|
|
147
|
+
data: Array<{
|
|
148
|
+
name: string;
|
|
149
|
+
type: DnsRecordType;
|
|
150
|
+
ttl: number;
|
|
151
|
+
records: Array<{ content: string }>;
|
|
152
|
+
}>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const response = await this.request<RecordResponse>(
|
|
156
|
+
'GET',
|
|
157
|
+
`/api/dns/v1/zones/${domain}`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return response.data || [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create or update DNS records
|
|
165
|
+
*
|
|
166
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
167
|
+
* @param records - Records to create/update
|
|
168
|
+
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
169
|
+
*/
|
|
170
|
+
async upsertRecords(
|
|
171
|
+
domain: string,
|
|
172
|
+
records: DnsRecord[],
|
|
173
|
+
overwrite = false,
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
await this.request('PUT', `/api/dns/v1/zones/${domain}`, {
|
|
176
|
+
overwrite,
|
|
177
|
+
zone: records,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validate DNS records before applying
|
|
183
|
+
*
|
|
184
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
185
|
+
* @param records - Records to validate
|
|
186
|
+
* @returns true if valid, throws if invalid
|
|
187
|
+
*/
|
|
188
|
+
async validateRecords(domain: string, records: DnsRecord[]): Promise<boolean> {
|
|
189
|
+
await this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {
|
|
190
|
+
overwrite: false,
|
|
191
|
+
zone: records,
|
|
192
|
+
});
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete specific DNS records
|
|
198
|
+
*
|
|
199
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
200
|
+
* @param filters - Filters to match records for deletion
|
|
201
|
+
*/
|
|
202
|
+
async deleteRecords(
|
|
203
|
+
domain: string,
|
|
204
|
+
filters: DnsRecordFilter[],
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
await this.request('DELETE', `/api/dns/v1/zones/${domain}`, {
|
|
207
|
+
filters,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Check if a specific record exists
|
|
213
|
+
*
|
|
214
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
215
|
+
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
216
|
+
* @param type - Record type (e.g., 'A')
|
|
217
|
+
*/
|
|
218
|
+
async recordExists(
|
|
219
|
+
domain: string,
|
|
220
|
+
name: string,
|
|
221
|
+
type: DnsRecordType = 'A',
|
|
222
|
+
): Promise<boolean> {
|
|
223
|
+
const records = await this.getRecords(domain);
|
|
224
|
+
return records.some((r) => r.name === name && r.type === type);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create a single A record if it doesn't exist
|
|
229
|
+
*
|
|
230
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
231
|
+
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
232
|
+
* @param ip - IP address to point to
|
|
233
|
+
* @param ttl - TTL in seconds (default: 300)
|
|
234
|
+
* @returns true if created, false if already exists
|
|
235
|
+
*/
|
|
236
|
+
async createARecordIfNotExists(
|
|
237
|
+
domain: string,
|
|
238
|
+
subdomain: string,
|
|
239
|
+
ip: string,
|
|
240
|
+
ttl = 300,
|
|
241
|
+
): Promise<boolean> {
|
|
242
|
+
const exists = await this.recordExists(domain, subdomain, 'A');
|
|
243
|
+
if (exists) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await this.upsertRecords(domain, [
|
|
248
|
+
{
|
|
249
|
+
name: subdomain,
|
|
250
|
+
type: 'A',
|
|
251
|
+
ttl,
|
|
252
|
+
records: [{ content: ip }],
|
|
253
|
+
},
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|