@geekmidas/cli 1.10.17 → 1.10.19
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 +12 -0
- package/dist/{bundler-B4AackW5.mjs → bundler-C5xkxnyr.mjs} +2 -2
- package/dist/{bundler-B4AackW5.mjs.map → bundler-C5xkxnyr.mjs.map} +1 -1
- package/dist/{bundler-BhhfkI9T.cjs → bundler-i-az1DZ2.cjs} +2 -2
- package/dist/{bundler-BhhfkI9T.cjs.map → bundler-i-az1DZ2.cjs.map} +1 -1
- package/dist/index.cjs +701 -707
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +689 -695
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-BYxAWwok.cjs → openapi-CsCNpSf8.cjs} +1 -1
- package/dist/{openapi-BYxAWwok.cjs.map → openapi-CsCNpSf8.cjs.map} +1 -1
- package/dist/{openapi-DenF-okj.mjs → openapi-kvwpKbNe.mjs} +1 -1
- package/dist/{openapi-DenF-okj.mjs.map → openapi-kvwpKbNe.mjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{storage-DOEtT2Hr.cjs → storage-ChVQI_G7.cjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs → storage-CpMNB77O.mjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs.map → storage-CpMNB77O.mjs.map} +1 -1
- package/dist/{storage-B1wvztiJ.cjs → storage-DLEb8Dkd.cjs} +1 -1
- package/dist/{storage-B1wvztiJ.cjs.map → storage-DLEb8Dkd.cjs.map} +1 -1
- package/dist/{storage-Cs4WBsc4.mjs → storage-mwbL7PhP.mjs} +1 -1
- package/dist/{sync-DGXXSk2v.cjs → sync-BWD_I5Ai.cjs} +2 -2
- package/dist/{sync-DGXXSk2v.cjs.map → sync-BWD_I5Ai.cjs.map} +1 -1
- package/dist/sync-ByaRPBxh.cjs +4 -0
- package/dist/{sync-COnAugP-.mjs → sync-CYBVB64f.mjs} +1 -1
- package/dist/{sync-D_NowTkZ.mjs → sync-lExOTa9t.mjs} +2 -2
- package/dist/{sync-D_NowTkZ.mjs.map → sync-lExOTa9t.mjs.map} +1 -1
- package/package.json +2 -2
- package/src/credentials/__tests__/fullDockerPorts.spec.ts +144 -0
- package/src/credentials/__tests__/helpers.ts +112 -0
- package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +125 -0
- package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +190 -0
- package/src/credentials/__tests__/workspaceCredentials.spec.ts +209 -0
- package/src/credentials/index.ts +826 -0
- package/src/dev/index.ts +48 -830
- package/src/exec/index.ts +120 -0
- package/src/setup/index.ts +4 -1
- package/src/test/index.ts +32 -109
- package/dist/sync-D1Pa30oV.cjs +0 -4
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,14 @@ import { __require } from "./chunk-Duj1WY3L.mjs";
|
|
|
3
3
|
import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-D4z4A4cq.mjs";
|
|
4
4
|
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-jsRYHOHU.mjs";
|
|
5
5
|
import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-s1kLcIzK.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-CpMNB77O.mjs";
|
|
7
|
+
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, copyAllClients, copyClientToFrontends, generateOpenApi, getBackendOpenApiPath, isPartitionedRoutes, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-kvwpKbNe.mjs";
|
|
8
8
|
import { DokployApi } from "./dokploy-api-2ldYoN3i.mjs";
|
|
9
9
|
import { encryptSecrets } from "./encryption-BOH5M-f-.mjs";
|
|
10
10
|
import { CachedStateProvider } from "./CachedStateProvider-BDq5WqSy.mjs";
|
|
11
11
|
import { createStageSecrets, generateConnectionUrls, generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, generateLocalStackCredentials, generateSecurePassword, generateServiceCredentials, rotateServicePassword, writeDockerEnvFromSecrets } from "./fullstack-secrets-x2Kffx7-.mjs";
|
|
12
12
|
import { generateReactQueryCommand } from "./openapi-react-query-C4UdILaI.mjs";
|
|
13
|
-
import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-
|
|
13
|
+
import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-lExOTa9t.mjs";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
16
16
|
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
@@ -19,15 +19,15 @@ import { stdin, stdout } from "node:process";
|
|
|
19
19
|
import * as readline from "node:readline/promises";
|
|
20
20
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
21
21
|
import { execSync, spawn } from "node:child_process";
|
|
22
|
-
import { createServer } from "node:net";
|
|
23
22
|
import chokidar from "chokidar";
|
|
24
|
-
import { config } from "dotenv";
|
|
25
23
|
import fg from "fast-glob";
|
|
24
|
+
import { createServer } from "node:net";
|
|
25
|
+
import { config } from "dotenv";
|
|
26
26
|
import { parse as parse$1 } from "yaml";
|
|
27
|
+
import { randomBytes } from "node:crypto";
|
|
27
28
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
28
29
|
import { Function } from "@geekmidas/constructs/functions";
|
|
29
30
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
30
|
-
import { randomBytes } from "node:crypto";
|
|
31
31
|
import { Client } from "pg";
|
|
32
32
|
import { lookup } from "node:dns/promises";
|
|
33
33
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -35,7 +35,7 @@ import prompts from "prompts";
|
|
|
35
35
|
|
|
36
36
|
//#region package.json
|
|
37
37
|
var name = "@geekmidas/cli";
|
|
38
|
-
var version = "1.10.
|
|
38
|
+
var version = "1.10.18";
|
|
39
39
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
40
40
|
var private$1 = false;
|
|
41
41
|
var type = "module";
|
|
@@ -133,7 +133,7 @@ var package_default = {
|
|
|
133
133
|
|
|
134
134
|
//#endregion
|
|
135
135
|
//#region src/auth/index.ts
|
|
136
|
-
const logger$
|
|
136
|
+
const logger$14 = console;
|
|
137
137
|
/**
|
|
138
138
|
* Validate Dokploy token by making a test API call
|
|
139
139
|
*/
|
|
@@ -201,36 +201,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
201
201
|
async function loginCommand(options) {
|
|
202
202
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
203
203
|
if (service === "dokploy") {
|
|
204
|
-
logger$
|
|
204
|
+
logger$14.log("\n🔐 Logging in to Dokploy...\n");
|
|
205
205
|
let endpoint = providedEndpoint;
|
|
206
206
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
207
207
|
endpoint = endpoint.replace(/\/$/, "");
|
|
208
208
|
try {
|
|
209
209
|
new URL(endpoint);
|
|
210
210
|
} catch {
|
|
211
|
-
logger$
|
|
211
|
+
logger$14.error("Invalid URL format");
|
|
212
212
|
process.exit(1);
|
|
213
213
|
}
|
|
214
214
|
let token = providedToken;
|
|
215
215
|
if (!token) {
|
|
216
|
-
logger$
|
|
216
|
+
logger$14.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
217
217
|
token = await prompt$1("API Token: ", true);
|
|
218
218
|
}
|
|
219
219
|
if (!token) {
|
|
220
|
-
logger$
|
|
220
|
+
logger$14.error("Token is required");
|
|
221
221
|
process.exit(1);
|
|
222
222
|
}
|
|
223
|
-
logger$
|
|
223
|
+
logger$14.log("\nValidating credentials...");
|
|
224
224
|
const isValid = await validateDokployToken(endpoint, token);
|
|
225
225
|
if (!isValid) {
|
|
226
|
-
logger$
|
|
226
|
+
logger$14.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
227
227
|
process.exit(1);
|
|
228
228
|
}
|
|
229
229
|
await storeDokployCredentials(token, endpoint);
|
|
230
|
-
logger$
|
|
231
|
-
logger$
|
|
232
|
-
logger$
|
|
233
|
-
logger$
|
|
230
|
+
logger$14.log("\n✓ Successfully logged in to Dokploy!");
|
|
231
|
+
logger$14.log(` Endpoint: ${endpoint}`);
|
|
232
|
+
logger$14.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
233
|
+
logger$14.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
@@ -240,28 +240,28 @@ async function logoutCommand(options) {
|
|
|
240
240
|
const { service = "dokploy" } = options;
|
|
241
241
|
if (service === "all") {
|
|
242
242
|
const dokployRemoved = await removeDokployCredentials();
|
|
243
|
-
if (dokployRemoved) logger$
|
|
244
|
-
else logger$
|
|
243
|
+
if (dokployRemoved) logger$14.log("\n✓ Logged out from all services");
|
|
244
|
+
else logger$14.log("\nNo stored credentials found");
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
247
|
if (service === "dokploy") {
|
|
248
248
|
const removed = await removeDokployCredentials();
|
|
249
|
-
if (removed) logger$
|
|
250
|
-
else logger$
|
|
249
|
+
if (removed) logger$14.log("\n✓ Logged out from Dokploy");
|
|
250
|
+
else logger$14.log("\nNo Dokploy credentials found");
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
254
|
* Show current login status
|
|
255
255
|
*/
|
|
256
256
|
async function whoamiCommand() {
|
|
257
|
-
logger$
|
|
257
|
+
logger$14.log("\n📋 Current credentials:\n");
|
|
258
258
|
const dokploy = await getDokployCredentials();
|
|
259
259
|
if (dokploy) {
|
|
260
|
-
logger$
|
|
261
|
-
logger$
|
|
262
|
-
logger$
|
|
263
|
-
} else logger$
|
|
264
|
-
logger$
|
|
260
|
+
logger$14.log(" Dokploy:");
|
|
261
|
+
logger$14.log(` Endpoint: ${dokploy.endpoint}`);
|
|
262
|
+
logger$14.log(` Token: ${maskToken(dokploy.token)}`);
|
|
263
|
+
} else logger$14.log(" Dokploy: Not logged in");
|
|
264
|
+
logger$14.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
265
265
|
}
|
|
266
266
|
/**
|
|
267
267
|
* Mask a token for display
|
|
@@ -343,135 +343,590 @@ function isEnabled(config$1) {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
//#endregion
|
|
346
|
-
//#region src/
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
environment: await construct.getEnvironment()
|
|
346
|
+
//#region src/credentials/index.ts
|
|
347
|
+
const logger$13 = console;
|
|
348
|
+
/**
|
|
349
|
+
* Load environment files
|
|
350
|
+
* @internal Exported for testing
|
|
351
|
+
*/
|
|
352
|
+
function loadEnvFiles(envConfig, cwd = process.cwd()) {
|
|
353
|
+
const loaded = [];
|
|
354
|
+
const missing = [];
|
|
355
|
+
const envFiles = envConfig ? Array.isArray(envConfig) ? envConfig : [envConfig] : [".env"];
|
|
356
|
+
for (const envFile of envFiles) {
|
|
357
|
+
const envPath = resolve(cwd, envFile);
|
|
358
|
+
if (existsSync(envPath)) {
|
|
359
|
+
config({
|
|
360
|
+
path: envPath,
|
|
361
|
+
override: true,
|
|
362
|
+
quiet: true
|
|
364
363
|
});
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
return cronInfos;
|
|
368
|
-
}
|
|
369
|
-
isConstruct(value) {
|
|
370
|
-
return Cron.isCron(value);
|
|
371
|
-
}
|
|
372
|
-
async generateCronHandler(outputDir, sourceFile, exportName, context) {
|
|
373
|
-
const handlerFileName = `${exportName}.ts`;
|
|
374
|
-
const handlerPath = join(outputDir, handlerFileName);
|
|
375
|
-
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
376
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
377
|
-
const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
|
|
378
|
-
const relativeLoggerPath = relative(dirname(handlerPath), context.loggerPath);
|
|
379
|
-
const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/crons';
|
|
380
|
-
import { ${exportName} } from '${importPath}';
|
|
381
|
-
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
382
|
-
import ${context.loggerImportPattern} from '${relativeLoggerPath}';
|
|
383
|
-
|
|
384
|
-
const adapter = new AWSScheduledFunction(envParser, ${exportName});
|
|
385
|
-
|
|
386
|
-
export const handler = adapter.handler;
|
|
387
|
-
`;
|
|
388
|
-
await writeFile(handlerPath, content);
|
|
389
|
-
return handlerPath;
|
|
364
|
+
loaded.push(envFile);
|
|
365
|
+
} else if (envConfig) missing.push(envFile);
|
|
390
366
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
367
|
+
return {
|
|
368
|
+
loaded,
|
|
369
|
+
missing
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Check if a port is available
|
|
374
|
+
* @internal Exported for testing
|
|
375
|
+
*/
|
|
376
|
+
async function isPortAvailable(port) {
|
|
377
|
+
return new Promise((resolve$1) => {
|
|
378
|
+
const server = createServer();
|
|
379
|
+
server.once("error", (err) => {
|
|
380
|
+
if (err.code === "EADDRINUSE") resolve$1(false);
|
|
381
|
+
else resolve$1(false);
|
|
382
|
+
});
|
|
383
|
+
server.once("listening", () => {
|
|
384
|
+
server.close();
|
|
385
|
+
resolve$1(true);
|
|
386
|
+
});
|
|
387
|
+
server.listen(port);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Find an available port starting from the preferred port
|
|
392
|
+
* @internal Exported for testing
|
|
393
|
+
*/
|
|
394
|
+
async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
395
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
396
|
+
const port = preferredPort + i;
|
|
397
|
+
if (await isPortAvailable(port)) return port;
|
|
398
|
+
logger$13.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
398
399
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
400
|
+
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
401
|
+
}
|
|
402
|
+
const PORT_STATE_PATH = ".gkm/ports.json";
|
|
403
|
+
/**
|
|
404
|
+
* Parse docker-compose.yml and extract all port mappings that use env var interpolation.
|
|
405
|
+
* Entries like `'${POSTGRES_HOST_PORT:-5432}:5432'` are captured.
|
|
406
|
+
* Fixed port mappings like `'5050:80'` are skipped.
|
|
407
|
+
* @internal Exported for testing
|
|
408
|
+
*/
|
|
409
|
+
function parseComposePortMappings(composePath) {
|
|
410
|
+
if (!existsSync(composePath)) return [];
|
|
411
|
+
const content = readFileSync(composePath, "utf-8");
|
|
412
|
+
const compose = parse$1(content);
|
|
413
|
+
if (!compose?.services) return [];
|
|
414
|
+
const results = [];
|
|
415
|
+
for (const [serviceName, serviceConfig] of Object.entries(compose.services)) for (const portMapping of serviceConfig?.ports ?? []) {
|
|
416
|
+
const match = String(portMapping).match(/\$\{(\w+):-(\d+)\}:(\d+)/);
|
|
417
|
+
if (match?.[1] && match[2] && match[3]) results.push({
|
|
418
|
+
service: serviceName,
|
|
419
|
+
envVar: match[1],
|
|
420
|
+
defaultPort: Number(match[2]),
|
|
421
|
+
containerPort: Number(match[3])
|
|
422
|
+
});
|
|
418
423
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const adapter = new AWSLambdaFunction(envParser, ${exportName});
|
|
432
|
-
|
|
433
|
-
export const handler = adapter.handler;
|
|
434
|
-
`;
|
|
435
|
-
await writeFile(handlerPath, content);
|
|
436
|
-
return handlerPath;
|
|
424
|
+
return results;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Load saved port state from .gkm/ports.json.
|
|
428
|
+
* @internal Exported for testing
|
|
429
|
+
*/
|
|
430
|
+
async function loadPortState(workspaceRoot) {
|
|
431
|
+
try {
|
|
432
|
+
const raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), "utf-8");
|
|
433
|
+
return JSON.parse(raw);
|
|
434
|
+
} catch {
|
|
435
|
+
return {};
|
|
437
436
|
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Save port state to .gkm/ports.json.
|
|
440
|
+
* @internal Exported for testing
|
|
441
|
+
*/
|
|
442
|
+
async function savePortState(workspaceRoot, ports) {
|
|
443
|
+
const dir = join(workspaceRoot, ".gkm");
|
|
444
|
+
await mkdir(dir, { recursive: true });
|
|
445
|
+
await writeFile(join(workspaceRoot, PORT_STATE_PATH), `${JSON.stringify(ports, null, 2)}\n`);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Check if a project's own Docker container is running and return its host port.
|
|
449
|
+
* Uses `docker compose port` scoped to the project's compose file.
|
|
450
|
+
* @internal Exported for testing
|
|
451
|
+
*/
|
|
452
|
+
function getContainerHostPort(workspaceRoot, service, containerPort) {
|
|
453
|
+
try {
|
|
454
|
+
const result = execSync(`docker compose port ${service} ${containerPort}`, {
|
|
455
|
+
cwd: workspaceRoot,
|
|
456
|
+
stdio: "pipe"
|
|
457
|
+
}).toString().trim();
|
|
458
|
+
const match = result.match(/:(\d+)$/);
|
|
459
|
+
return match ? Number(match[1]) : null;
|
|
460
|
+
} catch {
|
|
461
|
+
return null;
|
|
445
462
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Resolve host ports for Docker services by parsing docker-compose.yml.
|
|
466
|
+
* Priority: running container → saved state → find available port.
|
|
467
|
+
* Persists resolved ports to .gkm/ports.json.
|
|
468
|
+
* @internal Exported for testing
|
|
469
|
+
*/
|
|
470
|
+
async function resolveServicePorts(workspaceRoot) {
|
|
471
|
+
const composePath = join(workspaceRoot, "docker-compose.yml");
|
|
472
|
+
const mappings = parseComposePortMappings(composePath);
|
|
473
|
+
if (mappings.length === 0) return {
|
|
474
|
+
dockerEnv: {},
|
|
475
|
+
ports: {},
|
|
476
|
+
mappings: []
|
|
477
|
+
};
|
|
478
|
+
const savedState = await loadPortState(workspaceRoot);
|
|
479
|
+
const dockerEnv = {};
|
|
480
|
+
const ports = {};
|
|
481
|
+
const assignedPorts = /* @__PURE__ */ new Set();
|
|
482
|
+
logger$13.log("\n🔌 Resolving service ports...");
|
|
483
|
+
for (const mapping of mappings) {
|
|
484
|
+
const containerPort = getContainerHostPort(workspaceRoot, mapping.service, mapping.containerPort);
|
|
485
|
+
if (containerPort !== null) {
|
|
486
|
+
ports[mapping.envVar] = containerPort;
|
|
487
|
+
dockerEnv[mapping.envVar] = String(containerPort);
|
|
488
|
+
assignedPorts.add(containerPort);
|
|
489
|
+
logger$13.log(` 🔄 ${mapping.service}:${mapping.containerPort}: reusing existing container on port ${containerPort}`);
|
|
490
|
+
continue;
|
|
470
491
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
492
|
+
const savedPort = savedState[mapping.envVar];
|
|
493
|
+
if (savedPort && !assignedPorts.has(savedPort) && await isPortAvailable(savedPort)) {
|
|
494
|
+
ports[mapping.envVar] = savedPort;
|
|
495
|
+
dockerEnv[mapping.envVar] = String(savedPort);
|
|
496
|
+
assignedPorts.add(savedPort);
|
|
497
|
+
logger$13.log(` 💾 ${mapping.service}:${mapping.containerPort}: using saved port ${savedPort}`);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
let resolvedPort = await findAvailablePort(mapping.defaultPort);
|
|
501
|
+
while (assignedPorts.has(resolvedPort)) resolvedPort = await findAvailablePort(resolvedPort + 1);
|
|
502
|
+
ports[mapping.envVar] = resolvedPort;
|
|
503
|
+
dockerEnv[mapping.envVar] = String(resolvedPort);
|
|
504
|
+
assignedPorts.add(resolvedPort);
|
|
505
|
+
if (resolvedPort !== mapping.defaultPort) logger$13.log(` ⚡ ${mapping.service}:${mapping.containerPort}: port ${mapping.defaultPort} occupied, using port ${resolvedPort}`);
|
|
506
|
+
else logger$13.log(` ✅ ${mapping.service}:${mapping.containerPort}: using default port ${resolvedPort}`);
|
|
507
|
+
}
|
|
508
|
+
await savePortState(workspaceRoot, ports);
|
|
509
|
+
return {
|
|
510
|
+
dockerEnv,
|
|
511
|
+
ports,
|
|
512
|
+
mappings
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Replace a port in a URL string.
|
|
517
|
+
* Handles both `hostname:port` and `localhost:port` patterns.
|
|
518
|
+
* @internal Exported for testing
|
|
519
|
+
*/
|
|
520
|
+
function replacePortInUrl(url, oldPort, newPort) {
|
|
521
|
+
if (oldPort === newPort) return url;
|
|
522
|
+
let result = url.replace(new RegExp(`:${oldPort}(?=[/?#]|$)`, "g"), `:${newPort}`);
|
|
523
|
+
result = result.replace(new RegExp(`%3A${oldPort}(?=[%/?#&]|$)`, "gi"), `%3A${newPort}`);
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Rewrite connection URLs and port vars in secrets with resolved ports.
|
|
528
|
+
* Uses the parsed compose mappings to determine which default ports to replace.
|
|
529
|
+
* Pure transform — does not modify secrets on disk.
|
|
530
|
+
* @internal Exported for testing
|
|
531
|
+
*/
|
|
532
|
+
function rewriteUrlsWithPorts(secrets, resolvedPorts) {
|
|
533
|
+
const { ports, mappings } = resolvedPorts;
|
|
534
|
+
const result = { ...secrets };
|
|
535
|
+
const portReplacements = [];
|
|
536
|
+
const serviceNames = /* @__PURE__ */ new Set();
|
|
537
|
+
for (const mapping of mappings) {
|
|
538
|
+
serviceNames.add(mapping.service);
|
|
539
|
+
const resolved = ports[mapping.envVar];
|
|
540
|
+
if (resolved !== void 0) portReplacements.push({
|
|
541
|
+
defaultPort: mapping.defaultPort,
|
|
542
|
+
resolvedPort: resolved
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
for (const [key, value] of Object.entries(result)) {
|
|
546
|
+
if (!key.endsWith("_HOST")) continue;
|
|
547
|
+
if (serviceNames.has(value)) result[key] = "localhost";
|
|
548
|
+
}
|
|
549
|
+
for (const [key, value] of Object.entries(result)) {
|
|
550
|
+
if (!key.endsWith("_PORT")) continue;
|
|
551
|
+
for (const { defaultPort, resolvedPort } of portReplacements) if (value === String(defaultPort)) result[key] = String(resolvedPort);
|
|
552
|
+
}
|
|
553
|
+
for (const [key, value] of Object.entries(result)) {
|
|
554
|
+
if (!key.endsWith("_URL") && !key.endsWith("_ENDPOINT") && !key.endsWith("_CONNECTION_STRING") && key !== "DATABASE_URL") continue;
|
|
555
|
+
let rewritten = value;
|
|
556
|
+
for (const name$1 of serviceNames) rewritten = rewritten.replace(new RegExp(`@${name$1}:`, "g"), "@localhost:");
|
|
557
|
+
for (const { defaultPort, resolvedPort } of portReplacements) rewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);
|
|
558
|
+
result[key] = rewritten;
|
|
559
|
+
}
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Build the environment variables to pass to `docker compose up`.
|
|
564
|
+
* Merges process.env, secrets, and port mappings so that Docker Compose
|
|
565
|
+
* can interpolate variables like ${POSTGRES_USER} correctly.
|
|
566
|
+
* @internal Exported for testing
|
|
567
|
+
*/
|
|
568
|
+
function buildDockerComposeEnv(secretsEnv, portEnv) {
|
|
569
|
+
return {
|
|
570
|
+
...process.env,
|
|
571
|
+
...secretsEnv,
|
|
572
|
+
...portEnv
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Parse all service names from a docker-compose.yml file.
|
|
577
|
+
* @internal Exported for testing
|
|
578
|
+
*/
|
|
579
|
+
function parseComposeServiceNames(composePath) {
|
|
580
|
+
if (!existsSync(composePath)) return [];
|
|
581
|
+
const content = readFileSync(composePath, "utf-8");
|
|
582
|
+
const compose = parse$1(content);
|
|
583
|
+
return Object.keys(compose?.services ?? {});
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Start docker-compose services for a single-app project (no workspace config).
|
|
587
|
+
* Starts all services defined in docker-compose.yml.
|
|
588
|
+
*/
|
|
589
|
+
async function startComposeServices(cwd, portEnv, secretsEnv) {
|
|
590
|
+
const composeFile = join(cwd, "docker-compose.yml");
|
|
591
|
+
if (!existsSync(composeFile)) return;
|
|
592
|
+
const servicesToStart = parseComposeServiceNames(composeFile);
|
|
593
|
+
if (servicesToStart.length === 0) return;
|
|
594
|
+
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
595
|
+
try {
|
|
596
|
+
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
597
|
+
cwd,
|
|
598
|
+
stdio: "inherit",
|
|
599
|
+
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
600
|
+
});
|
|
601
|
+
logger$13.log("✅ Services started");
|
|
602
|
+
} catch (error) {
|
|
603
|
+
logger$13.error("❌ Failed to start services:", error.message);
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Start docker-compose services for a workspace.
|
|
609
|
+
* Discovers all services from docker-compose.yml and starts everything
|
|
610
|
+
* except app services (which are managed by turbo).
|
|
611
|
+
* @internal Exported for testing
|
|
612
|
+
*/
|
|
613
|
+
async function startWorkspaceServices(workspace, portEnv, secretsEnv) {
|
|
614
|
+
const composeFile = join(workspace.root, "docker-compose.yml");
|
|
615
|
+
if (!existsSync(composeFile)) return;
|
|
616
|
+
const allServices = parseComposeServiceNames(composeFile);
|
|
617
|
+
const appNames = new Set(Object.keys(workspace.apps));
|
|
618
|
+
const servicesToStart = allServices.filter((name$1) => !appNames.has(name$1));
|
|
619
|
+
if (servicesToStart.length === 0) return;
|
|
620
|
+
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
621
|
+
try {
|
|
622
|
+
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
623
|
+
cwd: workspace.root,
|
|
624
|
+
stdio: "inherit",
|
|
625
|
+
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
626
|
+
});
|
|
627
|
+
logger$13.log("✅ Services started");
|
|
628
|
+
} catch (error) {
|
|
629
|
+
logger$13.error("❌ Failed to start services:", error.message);
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Load and flatten secrets for an app from encrypted storage.
|
|
635
|
+
* For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
|
|
636
|
+
* @internal Exported for testing
|
|
637
|
+
*/
|
|
638
|
+
async function loadSecretsForApp(secretsRoot, appName, stages = ["dev", "development"]) {
|
|
639
|
+
let secrets = {};
|
|
640
|
+
for (const stage of stages) if (secretsExist(stage, secretsRoot)) {
|
|
641
|
+
const stageSecrets = await readStageSecrets(stage, secretsRoot);
|
|
642
|
+
if (stageSecrets) {
|
|
643
|
+
logger$13.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
644
|
+
secrets = toEmbeddableSecrets(stageSecrets);
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (Object.keys(secrets).length === 0) return {};
|
|
649
|
+
if (!appName) return secrets;
|
|
650
|
+
const prefix = appName.toUpperCase();
|
|
651
|
+
const mapped = { ...secrets };
|
|
652
|
+
const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
|
|
653
|
+
if (appDbUrl) mapped.DATABASE_URL = appDbUrl;
|
|
654
|
+
return mapped;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Walk up the directory tree to find the root containing .gkm/secrets/.
|
|
658
|
+
* @internal Exported for testing
|
|
659
|
+
*/
|
|
660
|
+
function findSecretsRoot(startDir) {
|
|
661
|
+
let dir = startDir;
|
|
662
|
+
while (dir !== "/") {
|
|
663
|
+
if (existsSync(join(dir, ".gkm", "secrets"))) return dir;
|
|
664
|
+
const parent = dirname(dir);
|
|
665
|
+
if (parent === dir) break;
|
|
666
|
+
dir = parent;
|
|
667
|
+
}
|
|
668
|
+
return startDir;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Generate the credentials injection code snippet.
|
|
672
|
+
* This is the common logic used by both entry wrapper and exec preload.
|
|
673
|
+
* @internal
|
|
674
|
+
*/
|
|
675
|
+
function generateCredentialsInjection(secretsJsonPath) {
|
|
676
|
+
return `import { existsSync, readFileSync } from 'node:fs';
|
|
677
|
+
|
|
678
|
+
// Inject dev secrets via globalThis and process.env
|
|
679
|
+
// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where
|
|
680
|
+
// Object.assign on the Credentials export only mutates one module copy.
|
|
681
|
+
const secretsPath = '${secretsJsonPath}';
|
|
682
|
+
if (existsSync(secretsPath)) {
|
|
683
|
+
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
684
|
+
globalThis.__gkm_credentials__ = secrets;
|
|
685
|
+
Object.assign(process.env, secrets);
|
|
686
|
+
}
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Create a preload script that injects secrets into Credentials.
|
|
691
|
+
* Used by `gkm exec` to inject secrets before running any command.
|
|
692
|
+
* @internal Exported for testing
|
|
693
|
+
*/
|
|
694
|
+
async function createCredentialsPreload(preloadPath, secretsJsonPath) {
|
|
695
|
+
const content = `/**
|
|
696
|
+
* Credentials preload generated by 'gkm exec'
|
|
697
|
+
* This file is loaded via NODE_OPTIONS="--import <path>"
|
|
698
|
+
*/
|
|
699
|
+
${generateCredentialsInjection(secretsJsonPath)}`;
|
|
700
|
+
await writeFile(preloadPath, content);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Create a wrapper script that injects secrets before importing the entry file.
|
|
704
|
+
* @internal Exported for testing
|
|
705
|
+
*/
|
|
706
|
+
async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
|
|
707
|
+
const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
|
|
708
|
+
` : "";
|
|
709
|
+
const content = `#!/usr/bin/env node
|
|
710
|
+
/**
|
|
711
|
+
* Entry wrapper generated by 'gkm dev --entry'
|
|
712
|
+
*/
|
|
713
|
+
${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)
|
|
714
|
+
await import('${entryPath}');
|
|
715
|
+
`;
|
|
716
|
+
await writeFile(wrapperPath, content);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Prepare credentials for dev/exec/test modes.
|
|
720
|
+
* Loads workspace config, secrets, resolves Docker ports, rewrites URLs,
|
|
721
|
+
* injects PORT, dependency URLs, and writes credentials JSON.
|
|
722
|
+
*
|
|
723
|
+
* @param options.resolveDockerPorts - How to resolve Docker ports:
|
|
724
|
+
* - `'full'` (default): probe running containers, saved state, then find available ports. Used by dev/test.
|
|
725
|
+
* - `'readonly'`: check running containers and saved state only, never probe for new ports. Used by exec.
|
|
726
|
+
* @param options.stages - Secret stages to try, in order. Default: ['dev', 'development'].
|
|
727
|
+
* @param options.startDocker - Start Docker Compose services after port resolution. Default: false.
|
|
728
|
+
* @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.
|
|
729
|
+
* @internal Exported for testing
|
|
730
|
+
*/
|
|
731
|
+
async function prepareEntryCredentials(options) {
|
|
732
|
+
const cwd = options.cwd ?? process.cwd();
|
|
733
|
+
const portMode = options.resolveDockerPorts ?? "full";
|
|
734
|
+
let workspaceAppPort;
|
|
735
|
+
let secretsRoot = cwd;
|
|
736
|
+
let appName;
|
|
737
|
+
let appInfo;
|
|
738
|
+
try {
|
|
739
|
+
appInfo = await loadWorkspaceAppInfo(cwd);
|
|
740
|
+
workspaceAppPort = appInfo.app.port;
|
|
741
|
+
secretsRoot = appInfo.workspaceRoot;
|
|
742
|
+
appName = appInfo.appName;
|
|
743
|
+
} catch (error) {
|
|
744
|
+
logger$13.log(`⚠️ Could not load workspace config: ${error.message}`);
|
|
745
|
+
secretsRoot = findSecretsRoot(cwd);
|
|
746
|
+
appName = getAppNameFromCwd(cwd) ?? void 0;
|
|
747
|
+
}
|
|
748
|
+
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3e3;
|
|
749
|
+
const credentials = await loadSecretsForApp(secretsRoot, appName, options.stages);
|
|
750
|
+
credentials.PORT = String(resolvedPort);
|
|
751
|
+
const composePath = join(secretsRoot, "docker-compose.yml");
|
|
752
|
+
const mappings = parseComposePortMappings(composePath);
|
|
753
|
+
if (mappings.length > 0) {
|
|
754
|
+
let resolvedPorts;
|
|
755
|
+
if (portMode === "full") resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
756
|
+
else {
|
|
757
|
+
const savedPorts = await loadPortState(secretsRoot);
|
|
758
|
+
const ports = {};
|
|
759
|
+
for (const mapping of mappings) {
|
|
760
|
+
const containerPort = getContainerHostPort(secretsRoot, mapping.service, mapping.containerPort);
|
|
761
|
+
if (containerPort !== null) ports[mapping.envVar] = containerPort;
|
|
762
|
+
else {
|
|
763
|
+
const saved = savedPorts[mapping.envVar];
|
|
764
|
+
if (saved !== void 0) ports[mapping.envVar] = saved;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
resolvedPorts = {
|
|
768
|
+
dockerEnv: {},
|
|
769
|
+
ports,
|
|
770
|
+
mappings
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (options.startDocker) if (appInfo) await startWorkspaceServices(appInfo.workspace, resolvedPorts.dockerEnv, credentials);
|
|
774
|
+
else await startComposeServices(secretsRoot, resolvedPorts.dockerEnv, credentials);
|
|
775
|
+
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
776
|
+
const rewritten = rewriteUrlsWithPorts(credentials, resolvedPorts);
|
|
777
|
+
Object.assign(credentials, rewritten);
|
|
778
|
+
logger$13.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (appInfo?.appName) {
|
|
782
|
+
const depEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);
|
|
783
|
+
Object.assign(credentials, depEnv);
|
|
784
|
+
}
|
|
785
|
+
const secretsDir = join(secretsRoot, ".gkm");
|
|
786
|
+
await mkdir(secretsDir, { recursive: true });
|
|
787
|
+
const secretsFileName = options.secretsFileName ?? (appName ? `dev-secrets-${appName}.json` : "dev-secrets.json");
|
|
788
|
+
const secretsJsonPath = join(secretsDir, secretsFileName);
|
|
789
|
+
await writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
790
|
+
return {
|
|
791
|
+
credentials,
|
|
792
|
+
resolvedPort,
|
|
793
|
+
secretsJsonPath,
|
|
794
|
+
appName,
|
|
795
|
+
secretsRoot,
|
|
796
|
+
appInfo
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/generators/CronGenerator.ts
|
|
802
|
+
var CronGenerator = class extends ConstructGenerator {
|
|
803
|
+
async build(context, constructs, outputDir, options) {
|
|
804
|
+
const provider = options?.provider || "aws-lambda";
|
|
805
|
+
const logger$15 = console;
|
|
806
|
+
const cronInfos = [];
|
|
807
|
+
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
808
|
+
const cronsDir = join(outputDir, "crons");
|
|
809
|
+
await mkdir(cronsDir, { recursive: true });
|
|
810
|
+
for (const { key, construct, path } of constructs) {
|
|
811
|
+
const handlerFile = await this.generateCronHandler(cronsDir, path.relative, key, context);
|
|
812
|
+
cronInfos.push({
|
|
813
|
+
name: key,
|
|
814
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
815
|
+
schedule: construct.schedule || "rate(1 hour)",
|
|
816
|
+
timeout: construct.timeout,
|
|
817
|
+
memorySize: construct.memorySize,
|
|
818
|
+
environment: await construct.getEnvironment()
|
|
819
|
+
});
|
|
820
|
+
logger$15.log(`Generated cron handler: ${key}`);
|
|
821
|
+
}
|
|
822
|
+
return cronInfos;
|
|
823
|
+
}
|
|
824
|
+
isConstruct(value) {
|
|
825
|
+
return Cron.isCron(value);
|
|
826
|
+
}
|
|
827
|
+
async generateCronHandler(outputDir, sourceFile, exportName, context) {
|
|
828
|
+
const handlerFileName = `${exportName}.ts`;
|
|
829
|
+
const handlerPath = join(outputDir, handlerFileName);
|
|
830
|
+
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
831
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
832
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
|
|
833
|
+
const relativeLoggerPath = relative(dirname(handlerPath), context.loggerPath);
|
|
834
|
+
const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/crons';
|
|
835
|
+
import { ${exportName} } from '${importPath}';
|
|
836
|
+
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
837
|
+
import ${context.loggerImportPattern} from '${relativeLoggerPath}';
|
|
838
|
+
|
|
839
|
+
const adapter = new AWSScheduledFunction(envParser, ${exportName});
|
|
840
|
+
|
|
841
|
+
export const handler = adapter.handler;
|
|
842
|
+
`;
|
|
843
|
+
await writeFile(handlerPath, content);
|
|
844
|
+
return handlerPath;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/generators/FunctionGenerator.ts
|
|
850
|
+
var FunctionGenerator = class extends ConstructGenerator {
|
|
851
|
+
isConstruct(value) {
|
|
852
|
+
return Function.isFunction(value);
|
|
853
|
+
}
|
|
854
|
+
async build(context, constructs, outputDir, options) {
|
|
855
|
+
const provider = options?.provider || "aws-lambda";
|
|
856
|
+
const logger$15 = console;
|
|
857
|
+
const functionInfos = [];
|
|
858
|
+
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
859
|
+
const functionsDir = join(outputDir, "functions");
|
|
860
|
+
await mkdir(functionsDir, { recursive: true });
|
|
861
|
+
for (const { key, construct, path } of constructs) {
|
|
862
|
+
const handlerFile = await this.generateFunctionHandler(functionsDir, path.relative, key, context);
|
|
863
|
+
functionInfos.push({
|
|
864
|
+
name: key,
|
|
865
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
866
|
+
timeout: construct.timeout,
|
|
867
|
+
memorySize: construct.memorySize,
|
|
868
|
+
environment: await construct.getEnvironment()
|
|
869
|
+
});
|
|
870
|
+
logger$15.log(`Generated function handler: ${key}`);
|
|
871
|
+
}
|
|
872
|
+
return functionInfos;
|
|
873
|
+
}
|
|
874
|
+
async generateFunctionHandler(outputDir, sourceFile, exportName, context) {
|
|
875
|
+
const handlerFileName = `${exportName}.ts`;
|
|
876
|
+
const handlerPath = join(outputDir, handlerFileName);
|
|
877
|
+
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
878
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
879
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
|
|
880
|
+
const relativeLoggerPath = relative(dirname(handlerPath), context.loggerPath);
|
|
881
|
+
const content = `import { AWSLambdaFunction } from '@geekmidas/constructs/aws';
|
|
882
|
+
import { ${exportName} } from '${importPath}';
|
|
883
|
+
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
884
|
+
import ${context.loggerImportPattern} from '${relativeLoggerPath}';
|
|
885
|
+
|
|
886
|
+
const adapter = new AWSLambdaFunction(envParser, ${exportName});
|
|
887
|
+
|
|
888
|
+
export const handler = adapter.handler;
|
|
889
|
+
`;
|
|
890
|
+
await writeFile(handlerPath, content);
|
|
891
|
+
return handlerPath;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
//#endregion
|
|
896
|
+
//#region src/generators/SubscriberGenerator.ts
|
|
897
|
+
var SubscriberGenerator = class extends ConstructGenerator {
|
|
898
|
+
isConstruct(value) {
|
|
899
|
+
return Subscriber.isSubscriber(value);
|
|
900
|
+
}
|
|
901
|
+
async build(context, constructs, outputDir, options) {
|
|
902
|
+
const provider = options?.provider || "aws-lambda";
|
|
903
|
+
const logger$15 = console;
|
|
904
|
+
const subscriberInfos = [];
|
|
905
|
+
if (provider === "server") {
|
|
906
|
+
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
907
|
+
logger$15.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
908
|
+
return subscriberInfos;
|
|
909
|
+
}
|
|
910
|
+
if (constructs.length === 0) return subscriberInfos;
|
|
911
|
+
if (provider !== "aws-lambda") return subscriberInfos;
|
|
912
|
+
const subscribersDir = join(outputDir, "subscribers");
|
|
913
|
+
await mkdir(subscribersDir, { recursive: true });
|
|
914
|
+
for (const { key, construct, path } of constructs) {
|
|
915
|
+
const handlerFile = await this.generateSubscriberHandler(subscribersDir, path.relative, key, construct, context);
|
|
916
|
+
subscriberInfos.push({
|
|
917
|
+
name: key,
|
|
918
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
919
|
+
subscribedEvents: construct.subscribedEvents || [],
|
|
920
|
+
timeout: construct.timeout,
|
|
921
|
+
memorySize: construct.memorySize,
|
|
922
|
+
environment: await construct.getEnvironment()
|
|
923
|
+
});
|
|
924
|
+
logger$15.log(`Generated subscriber handler: ${key}`);
|
|
925
|
+
}
|
|
926
|
+
return subscriberInfos;
|
|
927
|
+
}
|
|
928
|
+
async generateSubscriberHandler(outputDir, sourceFile, exportName, _subscriber, context) {
|
|
929
|
+
const handlerFileName = `${exportName}.ts`;
|
|
475
930
|
const handlerPath = join(outputDir, handlerFileName);
|
|
476
931
|
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
477
932
|
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
@@ -632,222 +1087,69 @@ export async function setupSubscribers(
|
|
|
632
1087
|
};
|
|
633
1088
|
|
|
634
1089
|
//#endregion
|
|
635
|
-
//#region src/
|
|
636
|
-
const logger$
|
|
637
|
-
/**
|
|
638
|
-
* Load environment files
|
|
639
|
-
* @internal Exported for testing
|
|
640
|
-
*/
|
|
641
|
-
function loadEnvFiles(envConfig, cwd = process.cwd()) {
|
|
642
|
-
const loaded = [];
|
|
643
|
-
const missing = [];
|
|
644
|
-
const envFiles = envConfig ? Array.isArray(envConfig) ? envConfig : [envConfig] : [".env"];
|
|
645
|
-
for (const envFile of envFiles) {
|
|
646
|
-
const envPath = resolve(cwd, envFile);
|
|
647
|
-
if (existsSync(envPath)) {
|
|
648
|
-
config({
|
|
649
|
-
path: envPath,
|
|
650
|
-
override: true,
|
|
651
|
-
quiet: true
|
|
652
|
-
});
|
|
653
|
-
loaded.push(envFile);
|
|
654
|
-
} else if (envConfig) missing.push(envFile);
|
|
655
|
-
}
|
|
656
|
-
return {
|
|
657
|
-
loaded,
|
|
658
|
-
missing
|
|
659
|
-
};
|
|
660
|
-
}
|
|
1090
|
+
//#region src/exec/index.ts
|
|
1091
|
+
const logger$12 = console;
|
|
661
1092
|
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
1093
|
+
* Run a command with secrets injected into Credentials.
|
|
1094
|
+
* Uses Node's --import flag to preload a script that populates Credentials
|
|
1095
|
+
* before the command loads any modules that depend on them.
|
|
1096
|
+
*
|
|
1097
|
+
* @example
|
|
1098
|
+
* ```bash
|
|
1099
|
+
* gkm exec -- npx @better-auth/cli migrate
|
|
1100
|
+
* gkm exec -- npx prisma migrate dev
|
|
1101
|
+
* ```
|
|
664
1102
|
*/
|
|
665
|
-
async function
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
server.close();
|
|
674
|
-
resolve$1(true);
|
|
675
|
-
});
|
|
676
|
-
server.listen(port);
|
|
1103
|
+
async function execCommand(commandArgs, options = {}) {
|
|
1104
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1105
|
+
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1106
|
+
const defaultEnv = loadEnvFiles(".env");
|
|
1107
|
+
if (defaultEnv.loaded.length > 0) logger$12.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1108
|
+
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
|
|
1109
|
+
cwd,
|
|
1110
|
+
resolveDockerPorts: "readonly"
|
|
677
1111
|
});
|
|
1112
|
+
if (appName) logger$12.log(`📦 App: ${appName}`);
|
|
1113
|
+
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1114
|
+
if (secretCount > 0) logger$12.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1115
|
+
const preloadDir = join(cwd, ".gkm");
|
|
1116
|
+
await mkdir(preloadDir, { recursive: true });
|
|
1117
|
+
const preloadPath = join(preloadDir, "credentials-preload.ts");
|
|
1118
|
+
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1119
|
+
const [cmd, ...rawArgs] = commandArgs;
|
|
1120
|
+
if (!cmd) throw new Error("No command specified");
|
|
1121
|
+
const args = rawArgs.map((arg) => arg.replace(/\$PORT\b/g, credentials.PORT ?? "3000"));
|
|
1122
|
+
logger$12.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1123
|
+
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
1124
|
+
const tsxImport = "--import=tsx";
|
|
1125
|
+
const preloadImport = `--import=${preloadPath}`;
|
|
1126
|
+
const nodeOptions = [
|
|
1127
|
+
existingNodeOptions,
|
|
1128
|
+
tsxImport,
|
|
1129
|
+
preloadImport
|
|
1130
|
+
].filter(Boolean).join(" ");
|
|
1131
|
+
const child = spawn(cmd, args, {
|
|
1132
|
+
cwd,
|
|
1133
|
+
stdio: "inherit",
|
|
1134
|
+
env: {
|
|
1135
|
+
...process.env,
|
|
1136
|
+
...credentials,
|
|
1137
|
+
NODE_OPTIONS: nodeOptions
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
const exitCode = await new Promise((resolve$1) => {
|
|
1141
|
+
child.on("close", (code) => resolve$1(code ?? 0));
|
|
1142
|
+
child.on("error", (error) => {
|
|
1143
|
+
logger$12.error(`Failed to run command: ${error.message}`);
|
|
1144
|
+
resolve$1(1);
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
678
1148
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
684
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
685
|
-
const port = preferredPort + i;
|
|
686
|
-
if (await isPortAvailable(port)) return port;
|
|
687
|
-
logger$11.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
688
|
-
}
|
|
689
|
-
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
690
|
-
}
|
|
691
|
-
const PORT_STATE_PATH = ".gkm/ports.json";
|
|
692
|
-
/**
|
|
693
|
-
* Parse docker-compose.yml and extract all port mappings that use env var interpolation.
|
|
694
|
-
* Entries like `'${POSTGRES_HOST_PORT:-5432}:5432'` are captured.
|
|
695
|
-
* Fixed port mappings like `'5050:80'` are skipped.
|
|
696
|
-
* @internal Exported for testing
|
|
697
|
-
*/
|
|
698
|
-
function parseComposePortMappings(composePath) {
|
|
699
|
-
if (!existsSync(composePath)) return [];
|
|
700
|
-
const content = readFileSync(composePath, "utf-8");
|
|
701
|
-
const compose = parse$1(content);
|
|
702
|
-
if (!compose?.services) return [];
|
|
703
|
-
const results = [];
|
|
704
|
-
for (const [serviceName, serviceConfig] of Object.entries(compose.services)) for (const portMapping of serviceConfig?.ports ?? []) {
|
|
705
|
-
const match = String(portMapping).match(/\$\{(\w+):-(\d+)\}:(\d+)/);
|
|
706
|
-
if (match?.[1] && match[2] && match[3]) results.push({
|
|
707
|
-
service: serviceName,
|
|
708
|
-
envVar: match[1],
|
|
709
|
-
defaultPort: Number(match[2]),
|
|
710
|
-
containerPort: Number(match[3])
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
return results;
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Load saved port state from .gkm/ports.json.
|
|
717
|
-
* @internal Exported for testing
|
|
718
|
-
*/
|
|
719
|
-
async function loadPortState(workspaceRoot) {
|
|
720
|
-
try {
|
|
721
|
-
const raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), "utf-8");
|
|
722
|
-
return JSON.parse(raw);
|
|
723
|
-
} catch {
|
|
724
|
-
return {};
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Save port state to .gkm/ports.json.
|
|
729
|
-
* @internal Exported for testing
|
|
730
|
-
*/
|
|
731
|
-
async function savePortState(workspaceRoot, ports) {
|
|
732
|
-
const dir = join(workspaceRoot, ".gkm");
|
|
733
|
-
await mkdir(dir, { recursive: true });
|
|
734
|
-
await writeFile(join(workspaceRoot, PORT_STATE_PATH), `${JSON.stringify(ports, null, 2)}\n`);
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* Check if a project's own Docker container is running and return its host port.
|
|
738
|
-
* Uses `docker compose port` scoped to the project's compose file.
|
|
739
|
-
* @internal Exported for testing
|
|
740
|
-
*/
|
|
741
|
-
function getContainerHostPort(workspaceRoot, service, containerPort) {
|
|
742
|
-
try {
|
|
743
|
-
const result = execSync(`docker compose port ${service} ${containerPort}`, {
|
|
744
|
-
cwd: workspaceRoot,
|
|
745
|
-
stdio: "pipe"
|
|
746
|
-
}).toString().trim();
|
|
747
|
-
const match = result.match(/:(\d+)$/);
|
|
748
|
-
return match ? Number(match[1]) : null;
|
|
749
|
-
} catch {
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Resolve host ports for Docker services by parsing docker-compose.yml.
|
|
755
|
-
* Priority: running container → saved state → find available port.
|
|
756
|
-
* Persists resolved ports to .gkm/ports.json.
|
|
757
|
-
* @internal Exported for testing
|
|
758
|
-
*/
|
|
759
|
-
async function resolveServicePorts(workspaceRoot) {
|
|
760
|
-
const composePath = join(workspaceRoot, "docker-compose.yml");
|
|
761
|
-
const mappings = parseComposePortMappings(composePath);
|
|
762
|
-
if (mappings.length === 0) return {
|
|
763
|
-
dockerEnv: {},
|
|
764
|
-
ports: {},
|
|
765
|
-
mappings: []
|
|
766
|
-
};
|
|
767
|
-
const savedState = await loadPortState(workspaceRoot);
|
|
768
|
-
const dockerEnv = {};
|
|
769
|
-
const ports = {};
|
|
770
|
-
const assignedPorts = /* @__PURE__ */ new Set();
|
|
771
|
-
logger$11.log("\n🔌 Resolving service ports...");
|
|
772
|
-
for (const mapping of mappings) {
|
|
773
|
-
const containerPort = getContainerHostPort(workspaceRoot, mapping.service, mapping.containerPort);
|
|
774
|
-
if (containerPort !== null) {
|
|
775
|
-
ports[mapping.envVar] = containerPort;
|
|
776
|
-
dockerEnv[mapping.envVar] = String(containerPort);
|
|
777
|
-
assignedPorts.add(containerPort);
|
|
778
|
-
logger$11.log(` 🔄 ${mapping.service}:${mapping.containerPort}: reusing existing container on port ${containerPort}`);
|
|
779
|
-
continue;
|
|
780
|
-
}
|
|
781
|
-
const savedPort = savedState[mapping.envVar];
|
|
782
|
-
if (savedPort && !assignedPorts.has(savedPort) && await isPortAvailable(savedPort)) {
|
|
783
|
-
ports[mapping.envVar] = savedPort;
|
|
784
|
-
dockerEnv[mapping.envVar] = String(savedPort);
|
|
785
|
-
assignedPorts.add(savedPort);
|
|
786
|
-
logger$11.log(` 💾 ${mapping.service}:${mapping.containerPort}: using saved port ${savedPort}`);
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
let resolvedPort = await findAvailablePort(mapping.defaultPort);
|
|
790
|
-
while (assignedPorts.has(resolvedPort)) resolvedPort = await findAvailablePort(resolvedPort + 1);
|
|
791
|
-
ports[mapping.envVar] = resolvedPort;
|
|
792
|
-
dockerEnv[mapping.envVar] = String(resolvedPort);
|
|
793
|
-
assignedPorts.add(resolvedPort);
|
|
794
|
-
if (resolvedPort !== mapping.defaultPort) logger$11.log(` ⚡ ${mapping.service}:${mapping.containerPort}: port ${mapping.defaultPort} occupied, using port ${resolvedPort}`);
|
|
795
|
-
else logger$11.log(` ✅ ${mapping.service}:${mapping.containerPort}: using default port ${resolvedPort}`);
|
|
796
|
-
}
|
|
797
|
-
await savePortState(workspaceRoot, ports);
|
|
798
|
-
return {
|
|
799
|
-
dockerEnv,
|
|
800
|
-
ports,
|
|
801
|
-
mappings
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Replace a port in a URL string.
|
|
806
|
-
* Handles both `hostname:port` and `localhost:port` patterns.
|
|
807
|
-
* @internal Exported for testing
|
|
808
|
-
*/
|
|
809
|
-
function replacePortInUrl(url, oldPort, newPort) {
|
|
810
|
-
if (oldPort === newPort) return url;
|
|
811
|
-
let result = url.replace(new RegExp(`:${oldPort}(?=[/?#]|$)`, "g"), `:${newPort}`);
|
|
812
|
-
result = result.replace(new RegExp(`%3A${oldPort}(?=[%/?#&]|$)`, "gi"), `%3A${newPort}`);
|
|
813
|
-
return result;
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Rewrite connection URLs and port vars in secrets with resolved ports.
|
|
817
|
-
* Uses the parsed compose mappings to determine which default ports to replace.
|
|
818
|
-
* Pure transform — does not modify secrets on disk.
|
|
819
|
-
* @internal Exported for testing
|
|
820
|
-
*/
|
|
821
|
-
function rewriteUrlsWithPorts(secrets, resolvedPorts) {
|
|
822
|
-
const { ports, mappings } = resolvedPorts;
|
|
823
|
-
const result = { ...secrets };
|
|
824
|
-
const portReplacements = [];
|
|
825
|
-
const serviceNames = /* @__PURE__ */ new Set();
|
|
826
|
-
for (const mapping of mappings) {
|
|
827
|
-
serviceNames.add(mapping.service);
|
|
828
|
-
const resolved = ports[mapping.envVar];
|
|
829
|
-
if (resolved !== void 0) portReplacements.push({
|
|
830
|
-
defaultPort: mapping.defaultPort,
|
|
831
|
-
resolvedPort: resolved
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
for (const [key, value] of Object.entries(result)) {
|
|
835
|
-
if (!key.endsWith("_HOST")) continue;
|
|
836
|
-
if (serviceNames.has(value)) result[key] = "localhost";
|
|
837
|
-
}
|
|
838
|
-
for (const [key, value] of Object.entries(result)) {
|
|
839
|
-
if (!key.endsWith("_PORT")) continue;
|
|
840
|
-
for (const { defaultPort, resolvedPort } of portReplacements) if (value === String(defaultPort)) result[key] = String(resolvedPort);
|
|
841
|
-
}
|
|
842
|
-
for (const [key, value] of Object.entries(result)) {
|
|
843
|
-
if (!key.endsWith("_URL") && !key.endsWith("_ENDPOINT") && !key.endsWith("_CONNECTION_STRING") && key !== "DATABASE_URL") continue;
|
|
844
|
-
let rewritten = value;
|
|
845
|
-
for (const name$1 of serviceNames) rewritten = rewritten.replace(new RegExp(`@${name$1}:`, "g"), "@localhost:");
|
|
846
|
-
for (const { defaultPort, resolvedPort } of portReplacements) rewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);
|
|
847
|
-
result[key] = rewritten;
|
|
848
|
-
}
|
|
849
|
-
return result;
|
|
850
|
-
}
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/dev/index.ts
|
|
1152
|
+
const logger$11 = console;
|
|
851
1153
|
/**
|
|
852
1154
|
* Normalize telescope configuration
|
|
853
1155
|
* @internal Exported for testing
|
|
@@ -1025,7 +1327,8 @@ async function devCommand(options) {
|
|
|
1025
1327
|
if (Object.keys(appSecrets).length > 0) {
|
|
1026
1328
|
const secretsDir = join(secretsRoot, ".gkm");
|
|
1027
1329
|
await mkdir(secretsDir, { recursive: true });
|
|
1028
|
-
|
|
1330
|
+
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
1331
|
+
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
1029
1332
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1030
1333
|
logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1031
1334
|
}
|
|
@@ -1204,103 +1507,6 @@ async function loadDevSecrets(workspace) {
|
|
|
1204
1507
|
return {};
|
|
1205
1508
|
}
|
|
1206
1509
|
/**
|
|
1207
|
-
* Load secrets from a path for dev mode.
|
|
1208
|
-
* For single app: returns secrets as-is.
|
|
1209
|
-
* For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
|
|
1210
|
-
* @internal Exported for testing
|
|
1211
|
-
*/
|
|
1212
|
-
async function loadSecretsForApp(secretsRoot, appName) {
|
|
1213
|
-
const stages = ["dev", "development"];
|
|
1214
|
-
let secrets = {};
|
|
1215
|
-
for (const stage of stages) if (secretsExist(stage, secretsRoot)) {
|
|
1216
|
-
const stageSecrets = await readStageSecrets(stage, secretsRoot);
|
|
1217
|
-
if (stageSecrets) {
|
|
1218
|
-
logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1219
|
-
secrets = toEmbeddableSecrets(stageSecrets);
|
|
1220
|
-
break;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (Object.keys(secrets).length === 0) return {};
|
|
1224
|
-
if (!appName) return secrets;
|
|
1225
|
-
const prefix = appName.toUpperCase();
|
|
1226
|
-
const mapped = { ...secrets };
|
|
1227
|
-
const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
|
|
1228
|
-
if (appDbUrl) mapped.DATABASE_URL = appDbUrl;
|
|
1229
|
-
return mapped;
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Build the environment variables to pass to `docker compose up`.
|
|
1233
|
-
* Merges process.env, secrets, and port mappings so that Docker Compose
|
|
1234
|
-
* can interpolate variables like ${POSTGRES_USER} correctly.
|
|
1235
|
-
* @internal Exported for testing
|
|
1236
|
-
*/
|
|
1237
|
-
function buildDockerComposeEnv(secretsEnv, portEnv) {
|
|
1238
|
-
return {
|
|
1239
|
-
...process.env,
|
|
1240
|
-
...secretsEnv,
|
|
1241
|
-
...portEnv
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Parse all service names from a docker-compose.yml file.
|
|
1246
|
-
* @internal Exported for testing
|
|
1247
|
-
*/
|
|
1248
|
-
function parseComposeServiceNames(composePath) {
|
|
1249
|
-
if (!existsSync(composePath)) return [];
|
|
1250
|
-
const content = readFileSync(composePath, "utf-8");
|
|
1251
|
-
const compose = parse$1(content);
|
|
1252
|
-
return Object.keys(compose?.services ?? {});
|
|
1253
|
-
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Start docker-compose services for the workspace.
|
|
1256
|
-
* Parses the docker-compose.yml to discover all services and starts
|
|
1257
|
-
* everything except app services (which are managed by turbo).
|
|
1258
|
-
* This ensures manually added services are always started.
|
|
1259
|
-
* @internal Exported for testing
|
|
1260
|
-
*/
|
|
1261
|
-
/**
|
|
1262
|
-
* Start docker-compose services for a single-app project (no workspace config).
|
|
1263
|
-
* Starts all services defined in docker-compose.yml.
|
|
1264
|
-
*/
|
|
1265
|
-
async function startComposeServices(cwd, portEnv, secretsEnv) {
|
|
1266
|
-
const composeFile = join(cwd, "docker-compose.yml");
|
|
1267
|
-
if (!existsSync(composeFile)) return;
|
|
1268
|
-
const servicesToStart = parseComposeServiceNames(composeFile);
|
|
1269
|
-
if (servicesToStart.length === 0) return;
|
|
1270
|
-
logger$11.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
1271
|
-
try {
|
|
1272
|
-
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
1273
|
-
cwd,
|
|
1274
|
-
stdio: "inherit",
|
|
1275
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
1276
|
-
});
|
|
1277
|
-
logger$11.log("✅ Services started");
|
|
1278
|
-
} catch (error) {
|
|
1279
|
-
logger$11.error("❌ Failed to start services:", error.message);
|
|
1280
|
-
throw error;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
async function startWorkspaceServices(workspace, portEnv, secretsEnv) {
|
|
1284
|
-
const composeFile = join(workspace.root, "docker-compose.yml");
|
|
1285
|
-
if (!existsSync(composeFile)) return;
|
|
1286
|
-
const allServices = parseComposeServiceNames(composeFile);
|
|
1287
|
-
const appNames = new Set(Object.keys(workspace.apps));
|
|
1288
|
-
const servicesToStart = allServices.filter((name$1) => !appNames.has(name$1));
|
|
1289
|
-
if (servicesToStart.length === 0) return;
|
|
1290
|
-
logger$11.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
1291
|
-
try {
|
|
1292
|
-
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
1293
|
-
cwd: workspace.root,
|
|
1294
|
-
stdio: "inherit",
|
|
1295
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
1296
|
-
});
|
|
1297
|
-
logger$11.log("✅ Services started");
|
|
1298
|
-
} catch (error) {
|
|
1299
|
-
logger$11.error("❌ Failed to start services:", error.message);
|
|
1300
|
-
throw error;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
/**
|
|
1304
1510
|
* Workspace dev command - orchestrates multi-app development using Turbo.
|
|
1305
1511
|
*
|
|
1306
1512
|
* Flow:
|
|
@@ -1503,105 +1709,6 @@ async function buildServer(config$1, context, provider, enableOpenApi, appRoot =
|
|
|
1503
1709
|
]);
|
|
1504
1710
|
}
|
|
1505
1711
|
/**
|
|
1506
|
-
* Find the directory containing .gkm/secrets/.
|
|
1507
|
-
* Walks up from cwd until it finds one, or returns cwd.
|
|
1508
|
-
* @internal Exported for testing
|
|
1509
|
-
*/
|
|
1510
|
-
function findSecretsRoot(startDir) {
|
|
1511
|
-
let dir = startDir;
|
|
1512
|
-
while (dir !== "/") {
|
|
1513
|
-
if (existsSync(join(dir, ".gkm", "secrets"))) return dir;
|
|
1514
|
-
const parent = dirname(dir);
|
|
1515
|
-
if (parent === dir) break;
|
|
1516
|
-
dir = parent;
|
|
1517
|
-
}
|
|
1518
|
-
return startDir;
|
|
1519
|
-
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Generate the credentials injection code snippet.
|
|
1522
|
-
* This is the common logic used by both entry wrapper and exec preload.
|
|
1523
|
-
* @internal
|
|
1524
|
-
*/
|
|
1525
|
-
function generateCredentialsInjection(secretsJsonPath) {
|
|
1526
|
-
return `import { existsSync, readFileSync } from 'node:fs';
|
|
1527
|
-
|
|
1528
|
-
// Inject dev secrets via globalThis and process.env
|
|
1529
|
-
// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where
|
|
1530
|
-
// Object.assign on the Credentials export only mutates one module copy.
|
|
1531
|
-
const secretsPath = '${secretsJsonPath}';
|
|
1532
|
-
if (existsSync(secretsPath)) {
|
|
1533
|
-
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
1534
|
-
globalThis.__gkm_credentials__ = secrets;
|
|
1535
|
-
Object.assign(process.env, secrets);
|
|
1536
|
-
}
|
|
1537
|
-
`;
|
|
1538
|
-
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Create a preload script that injects secrets into Credentials.
|
|
1541
|
-
* Used by `gkm exec` to inject secrets before running any command.
|
|
1542
|
-
* @internal Exported for testing
|
|
1543
|
-
*/
|
|
1544
|
-
async function createCredentialsPreload(preloadPath, secretsJsonPath) {
|
|
1545
|
-
const content = `/**
|
|
1546
|
-
* Credentials preload generated by 'gkm exec'
|
|
1547
|
-
* This file is loaded via NODE_OPTIONS="--import <path>"
|
|
1548
|
-
*/
|
|
1549
|
-
${generateCredentialsInjection(secretsJsonPath)}`;
|
|
1550
|
-
await writeFile(preloadPath, content);
|
|
1551
|
-
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Create a wrapper script that injects secrets before importing the entry file.
|
|
1554
|
-
* @internal Exported for testing
|
|
1555
|
-
*/
|
|
1556
|
-
async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
|
|
1557
|
-
const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
|
|
1558
|
-
` : "";
|
|
1559
|
-
const content = `#!/usr/bin/env node
|
|
1560
|
-
/**
|
|
1561
|
-
* Entry wrapper generated by 'gkm dev --entry'
|
|
1562
|
-
*/
|
|
1563
|
-
${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)
|
|
1564
|
-
await import('${entryPath}');
|
|
1565
|
-
`;
|
|
1566
|
-
await writeFile(wrapperPath, content);
|
|
1567
|
-
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Prepare credentials for entry dev mode.
|
|
1570
|
-
* Loads workspace config, secrets, and injects PORT.
|
|
1571
|
-
* @internal Exported for testing
|
|
1572
|
-
*/
|
|
1573
|
-
async function prepareEntryCredentials(options) {
|
|
1574
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1575
|
-
let workspaceAppPort;
|
|
1576
|
-
let secretsRoot = cwd;
|
|
1577
|
-
let appName;
|
|
1578
|
-
try {
|
|
1579
|
-
const appInfo = await loadWorkspaceAppInfo(cwd);
|
|
1580
|
-
workspaceAppPort = appInfo.app.port;
|
|
1581
|
-
secretsRoot = appInfo.workspaceRoot;
|
|
1582
|
-
appName = appInfo.appName;
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
logger$11.log(`⚠️ Could not load workspace config: ${error.message}`);
|
|
1585
|
-
secretsRoot = findSecretsRoot(cwd);
|
|
1586
|
-
appName = getAppNameFromCwd(cwd) ?? void 0;
|
|
1587
|
-
}
|
|
1588
|
-
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3e3;
|
|
1589
|
-
const credentials = await loadSecretsForApp(secretsRoot, appName);
|
|
1590
|
-
credentials.PORT = String(resolvedPort);
|
|
1591
|
-
const secretsDir = join(secretsRoot, ".gkm");
|
|
1592
|
-
await mkdir(secretsDir, { recursive: true });
|
|
1593
|
-
const secretsFileName = appName ? `dev-secrets-${appName}.json` : "dev-secrets.json";
|
|
1594
|
-
const secretsJsonPath = join(secretsDir, secretsFileName);
|
|
1595
|
-
await writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
1596
|
-
return {
|
|
1597
|
-
credentials,
|
|
1598
|
-
resolvedPort,
|
|
1599
|
-
secretsJsonPath,
|
|
1600
|
-
appName,
|
|
1601
|
-
secretsRoot
|
|
1602
|
-
};
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
1712
|
* Run any TypeScript file with secret injection.
|
|
1606
1713
|
* Does not require gkm.config.ts.
|
|
1607
1714
|
*/
|
|
@@ -1876,73 +1983,6 @@ var DevServer = class {
|
|
|
1876
1983
|
await fsWriteFile(serverPath, content);
|
|
1877
1984
|
}
|
|
1878
1985
|
};
|
|
1879
|
-
/**
|
|
1880
|
-
* Run a command with secrets injected into Credentials.
|
|
1881
|
-
* Uses Node's --import flag to preload a script that populates Credentials
|
|
1882
|
-
* before the command loads any modules that depend on them.
|
|
1883
|
-
*
|
|
1884
|
-
* @example
|
|
1885
|
-
* ```bash
|
|
1886
|
-
* gkm exec -- npx @better-auth/cli migrate
|
|
1887
|
-
* gkm exec -- npx prisma migrate dev
|
|
1888
|
-
* ```
|
|
1889
|
-
*/
|
|
1890
|
-
async function execCommand(commandArgs, options = {}) {
|
|
1891
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1892
|
-
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1893
|
-
const defaultEnv = loadEnvFiles(".env");
|
|
1894
|
-
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1895
|
-
const { credentials, secretsJsonPath, appName, secretsRoot } = await prepareEntryCredentials({ cwd });
|
|
1896
|
-
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
1897
|
-
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1898
|
-
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1899
|
-
const resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
1900
|
-
if (resolvedPorts.mappings.length > 0 && Object.keys(resolvedPorts.ports).length > 0) {
|
|
1901
|
-
const rewritten = rewriteUrlsWithPorts(credentials, resolvedPorts);
|
|
1902
|
-
Object.assign(credentials, rewritten);
|
|
1903
|
-
logger$11.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
1904
|
-
}
|
|
1905
|
-
try {
|
|
1906
|
-
const appInfo = await loadWorkspaceAppInfo(cwd);
|
|
1907
|
-
if (appInfo.appName) {
|
|
1908
|
-
const depEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);
|
|
1909
|
-
Object.assign(credentials, depEnv);
|
|
1910
|
-
}
|
|
1911
|
-
} catch {}
|
|
1912
|
-
const preloadDir = join(cwd, ".gkm");
|
|
1913
|
-
await mkdir(preloadDir, { recursive: true });
|
|
1914
|
-
const preloadPath = join(preloadDir, "credentials-preload.ts");
|
|
1915
|
-
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1916
|
-
const [cmd, ...rawArgs] = commandArgs;
|
|
1917
|
-
if (!cmd) throw new Error("No command specified");
|
|
1918
|
-
const args = rawArgs.map((arg) => arg.replace(/\$PORT\b/g, credentials.PORT ?? "3000"));
|
|
1919
|
-
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1920
|
-
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
1921
|
-
const tsxImport = "--import=tsx";
|
|
1922
|
-
const preloadImport = `--import=${preloadPath}`;
|
|
1923
|
-
const nodeOptions = [
|
|
1924
|
-
existingNodeOptions,
|
|
1925
|
-
tsxImport,
|
|
1926
|
-
preloadImport
|
|
1927
|
-
].filter(Boolean).join(" ");
|
|
1928
|
-
const child = spawn(cmd, args, {
|
|
1929
|
-
cwd,
|
|
1930
|
-
stdio: "inherit",
|
|
1931
|
-
env: {
|
|
1932
|
-
...process.env,
|
|
1933
|
-
...credentials,
|
|
1934
|
-
NODE_OPTIONS: nodeOptions
|
|
1935
|
-
}
|
|
1936
|
-
});
|
|
1937
|
-
const exitCode = await new Promise((resolve$1) => {
|
|
1938
|
-
child.on("close", (code) => resolve$1(code ?? 0));
|
|
1939
|
-
child.on("error", (error) => {
|
|
1940
|
-
logger$11.error(`Failed to run command: ${error.message}`);
|
|
1941
|
-
resolve$1(1);
|
|
1942
|
-
});
|
|
1943
|
-
});
|
|
1944
|
-
if (exitCode !== 0) process.exit(exitCode);
|
|
1945
|
-
}
|
|
1946
1986
|
|
|
1947
1987
|
//#endregion
|
|
1948
1988
|
//#region src/build/manifests.ts
|
|
@@ -2214,7 +2254,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2214
2254
|
let masterKey;
|
|
2215
2255
|
if (context.production?.bundle && !skipBundle) {
|
|
2216
2256
|
logger$9.log(`\n📦 Bundling production server...`);
|
|
2217
|
-
const { bundleServer } = await import("./bundler-
|
|
2257
|
+
const { bundleServer } = await import("./bundler-C5xkxnyr.mjs");
|
|
2218
2258
|
const allConstructs = [
|
|
2219
2259
|
...endpoints.map((e) => e.construct),
|
|
2220
2260
|
...functions.map((f) => f.construct),
|
|
@@ -6465,7 +6505,7 @@ async function deployCommand(options) {
|
|
|
6465
6505
|
dokployConfig = setupResult.config;
|
|
6466
6506
|
finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
|
|
6467
6507
|
if (setupResult.serviceUrls) {
|
|
6468
|
-
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await import("./storage-
|
|
6508
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await import("./storage-mwbL7PhP.mjs");
|
|
6469
6509
|
let secrets = await readStageSecrets$1(stage);
|
|
6470
6510
|
if (!secrets) {
|
|
6471
6511
|
logger$3.log(` Creating secrets file for stage "${stage}"...`);
|
|
@@ -11875,75 +11915,29 @@ async function testCommand(options = {}) {
|
|
|
11875
11915
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
11876
11916
|
const defaultEnv = loadEnvFiles(".env");
|
|
11877
11917
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
else throw error;
|
|
11888
|
-
}
|
|
11889
|
-
let dependencyEnv = {};
|
|
11890
|
-
try {
|
|
11891
|
-
const appInfo = await loadWorkspaceAppInfo(cwd);
|
|
11892
|
-
const resolvedPorts = await resolveServicePorts(appInfo.workspaceRoot);
|
|
11893
|
-
await startWorkspaceServices(appInfo.workspace, resolvedPorts.dockerEnv, secretsEnv);
|
|
11894
|
-
if (resolvedPorts.mappings.length > 0) {
|
|
11895
|
-
secretsEnv = rewriteUrlsWithPorts(secretsEnv, resolvedPorts);
|
|
11896
|
-
console.log(` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
11897
|
-
}
|
|
11898
|
-
dependencyEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);
|
|
11899
|
-
if (Object.keys(dependencyEnv).length > 0) console.log(` 🔗 Loaded ${Object.keys(dependencyEnv).length} dependency URL(s)`);
|
|
11900
|
-
const sniffed = await sniffAppEnvironment(appInfo.app, appInfo.appName, appInfo.workspaceRoot, { logWarnings: false });
|
|
11918
|
+
const result = await prepareEntryCredentials({
|
|
11919
|
+
stages: [stage],
|
|
11920
|
+
startDocker: true,
|
|
11921
|
+
secretsFileName: "test-secrets.json",
|
|
11922
|
+
resolveDockerPorts: "full"
|
|
11923
|
+
});
|
|
11924
|
+
let finalCredentials = { ...result.credentials };
|
|
11925
|
+
if (result.appInfo) {
|
|
11926
|
+
const sniffed = await sniffAppEnvironment(result.appInfo.app, result.appInfo.appName, result.appInfo.workspaceRoot, { logWarnings: false });
|
|
11901
11927
|
if (sniffed.requiredEnvVars.length > 0) {
|
|
11902
11928
|
const needed = new Set(sniffed.requiredEnvVars);
|
|
11903
|
-
const
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
};
|
|
11907
|
-
const filteredEnv = {};
|
|
11908
|
-
for (const [key, value] of Object.entries(allEnv)) if (needed.has(key)) filteredEnv[key] = value;
|
|
11909
|
-
secretsEnv = {};
|
|
11910
|
-
dependencyEnv = filteredEnv;
|
|
11929
|
+
const filtered = {};
|
|
11930
|
+
for (const [key, value] of Object.entries(finalCredentials)) if (needed.has(key)) filtered[key] = value;
|
|
11931
|
+
finalCredentials = filtered;
|
|
11911
11932
|
console.log(` 🔍 Sniffed ${sniffed.requiredEnvVars.length} required env var(s)`);
|
|
11912
11933
|
}
|
|
11913
|
-
} catch {
|
|
11914
|
-
const composePath = join(cwd, "docker-compose.yml");
|
|
11915
|
-
const mappings = parseComposePortMappings(composePath);
|
|
11916
|
-
if (mappings.length > 0) {
|
|
11917
|
-
const resolvedPorts = await resolveServicePorts(cwd);
|
|
11918
|
-
await startComposeServices(cwd, resolvedPorts.dockerEnv, secretsEnv);
|
|
11919
|
-
if (resolvedPorts.mappings.length > 0) {
|
|
11920
|
-
secretsEnv = rewriteUrlsWithPorts(secretsEnv, resolvedPorts);
|
|
11921
|
-
console.log(` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
11922
|
-
} else {
|
|
11923
|
-
const ports = await loadPortState(cwd);
|
|
11924
|
-
if (Object.keys(ports).length > 0) {
|
|
11925
|
-
secretsEnv = rewriteUrlsWithPorts(secretsEnv, {
|
|
11926
|
-
dockerEnv: {},
|
|
11927
|
-
ports,
|
|
11928
|
-
mappings
|
|
11929
|
-
});
|
|
11930
|
-
console.log(` 🔌 Applied ${Object.keys(ports).length} port mapping(s)`);
|
|
11931
|
-
}
|
|
11932
|
-
}
|
|
11933
|
-
}
|
|
11934
11934
|
}
|
|
11935
|
-
|
|
11935
|
+
finalCredentials = rewriteDatabaseUrlForTests(finalCredentials);
|
|
11936
11936
|
console.log("");
|
|
11937
|
-
|
|
11938
|
-
...secretsEnv,
|
|
11939
|
-
...dependencyEnv
|
|
11940
|
-
};
|
|
11937
|
+
await writeFile(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
11941
11938
|
const gkmDir = join(cwd, ".gkm");
|
|
11942
|
-
await mkdir(gkmDir, { recursive: true });
|
|
11943
|
-
const secretsJsonPath = join(gkmDir, "test-secrets.json");
|
|
11944
|
-
await writeFile(secretsJsonPath, JSON.stringify(allSecrets, null, 2));
|
|
11945
11939
|
const preloadPath = join(gkmDir, "test-credentials-preload.ts");
|
|
11946
|
-
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
11940
|
+
await createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
11947
11941
|
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
11948
11942
|
const tsxImport = "--import=tsx";
|
|
11949
11943
|
const preloadImport = `--import=${preloadPath}`;
|
|
@@ -11963,7 +11957,7 @@ async function testCommand(options = {}) {
|
|
|
11963
11957
|
stdio: "inherit",
|
|
11964
11958
|
env: {
|
|
11965
11959
|
...process.env,
|
|
11966
|
-
...
|
|
11960
|
+
...finalCredentials,
|
|
11967
11961
|
NODE_ENV: "test",
|
|
11968
11962
|
NODE_OPTIONS: nodeOptions
|
|
11969
11963
|
}
|
|
@@ -12367,9 +12361,9 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
12367
12361
|
const globalOptions = program.opts();
|
|
12368
12362
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12369
12363
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
12370
|
-
const { pushSecrets: pushSecrets$1 } = await import("./sync-
|
|
12364
|
+
const { pushSecrets: pushSecrets$1 } = await import("./sync-CYBVB64f.mjs");
|
|
12371
12365
|
const { reconcileMissingSecrets } = await import("./reconcile-BLh6rswz.mjs");
|
|
12372
|
-
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-
|
|
12366
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-mwbL7PhP.mjs");
|
|
12373
12367
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
12374
12368
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
12375
12369
|
if (secrets) {
|
|
@@ -12392,8 +12386,8 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
12392
12386
|
const globalOptions = program.opts();
|
|
12393
12387
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12394
12388
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
12395
|
-
const { pullSecrets: pullSecrets$1 } = await import("./sync-
|
|
12396
|
-
const { writeStageSecrets: writeStageSecrets$1 } = await import("./storage-
|
|
12389
|
+
const { pullSecrets: pullSecrets$1 } = await import("./sync-CYBVB64f.mjs");
|
|
12390
|
+
const { writeStageSecrets: writeStageSecrets$1 } = await import("./storage-mwbL7PhP.mjs");
|
|
12397
12391
|
const { reconcileMissingSecrets } = await import("./reconcile-BLh6rswz.mjs");
|
|
12398
12392
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
12399
12393
|
let secrets = await pullSecrets$1(options.stage, workspace);
|
|
@@ -12420,7 +12414,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
12420
12414
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12421
12415
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
12422
12416
|
const { reconcileMissingSecrets } = await import("./reconcile-BLh6rswz.mjs");
|
|
12423
|
-
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-
|
|
12417
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-mwbL7PhP.mjs");
|
|
12424
12418
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
12425
12419
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
12426
12420
|
if (!secrets) {
|