@geekmidas/cli 10.0.0-alpha.26 → 10.0.0-alpha.28
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/{HostingerProvider-BY26bWwB.mjs → HostingerProvider-Btxpto71.mjs} +2 -2
- package/dist/{HostingerProvider-BY26bWwB.mjs.map → HostingerProvider-Btxpto71.mjs.map} +1 -1
- package/dist/{bundler-mwH7WOb1.mjs → bundler-D5RcGmVM.mjs} +2 -2
- package/dist/{bundler-mwH7WOb1.mjs.map → bundler-D5RcGmVM.mjs.map} +1 -1
- package/dist/{config-Dn9DW2Fz.cjs → config-BTFLHfRb.cjs} +25 -4
- package/dist/{config-Dn9DW2Fz.cjs.map → config-BTFLHfRb.cjs.map} +1 -1
- package/dist/{config-DBGpRTUy.mjs → config-Dqxa-_Ge.mjs} +20 -5
- package/dist/{config-DBGpRTUy.mjs.map → config-Dqxa-_Ge.mjs.map} +1 -1
- package/dist/config.cjs +3 -2
- package/dist/config.d.cts +12 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +12 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{discover-B_HtYJCr.cjs → discover-BTh87DOO.cjs} +24 -2
- package/dist/{discover-B_HtYJCr.cjs.map → discover-BTh87DOO.cjs.map} +1 -1
- package/dist/{discover-0fhCbnk0.mjs → discover-CXGlQHm1.mjs} +24 -2
- package/dist/{discover-0fhCbnk0.mjs.map → discover-CXGlQHm1.mjs.map} +1 -1
- package/dist/{github-D58lt7RR.cjs → github-BUCXEl2v.cjs} +6 -6
- package/dist/{github-D58lt7RR.cjs.map → github-BUCXEl2v.cjs.map} +1 -1
- package/dist/{github-D5SBlOx5.mjs → github-zkzu-ka2.mjs} +4 -4
- package/dist/{github-D5SBlOx5.mjs.map → github-zkzu-ka2.mjs.map} +1 -1
- package/dist/{index-RCUizGxL.d.cts → index-BPN7kghQ.d.cts} +2 -12
- package/dist/{index-B4H4XF7A.d.mts.map → index-BPN7kghQ.d.cts.map} +1 -1
- package/dist/{index-B4H4XF7A.d.mts → index-BqWKWRm0.d.mts} +2 -12
- package/dist/{index-RCUizGxL.d.cts.map → index-BqWKWRm0.d.mts.map} +1 -1
- package/dist/index.cjs +805 -790
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +754 -741
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DHQlstv6.mjs → openapi-D-Lk5Hr5.mjs} +3 -3
- package/dist/{openapi-DHQlstv6.mjs.map → openapi-D-Lk5Hr5.mjs.map} +1 -1
- package/dist/{openapi-BAKQLho-.cjs → openapi-K4H5tHzA.cjs} +3 -3
- package/dist/{openapi-BAKQLho-.cjs.map → openapi-K4H5tHzA.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +12 -0
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +12 -0
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/{generator-LfZU7NlU.mjs → reconcile-DhbCvh4K.mjs} +111 -2
- package/dist/reconcile-DhbCvh4K.mjs.map +1 -0
- package/dist/{generator-Wdfp0jCS.cjs → reconcile-szmJw_Sb.cjs} +130 -3
- package/dist/reconcile-szmJw_Sb.cjs.map +1 -0
- package/dist/workspace/index.cjs +1 -2
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-CRD9YinV.cjs → workspace-Bav3TVrP.cjs} +47 -78
- package/dist/workspace-Bav3TVrP.cjs.map +1 -0
- package/dist/{credentials-fQSAe4Nb.cjs → workspace-Cv48QwPK.cjs} +347 -516
- package/dist/workspace-Cv48QwPK.cjs.map +1 -0
- package/dist/{workspace-Bu3xNCej.mjs → workspace-T4IVGSoB.mjs} +48 -73
- package/dist/workspace-T4IVGSoB.mjs.map +1 -0
- package/dist/{credentials-DI9kJcaP.mjs → workspace-cBobQVoA.mjs} +349 -500
- package/dist/workspace-cBobQVoA.mjs.map +1 -0
- package/package.json +14 -14
- package/dist/credentials-DI9kJcaP.mjs.map +0 -1
- package/dist/credentials-fQSAe4Nb.cjs.map +0 -1
- package/dist/generator-LfZU7NlU.mjs.map +0 -1
- package/dist/generator-Wdfp0jCS.cjs.map +0 -1
- package/dist/reconcile-gb1lz1Dk.mjs +0 -113
- package/dist/reconcile-gb1lz1Dk.mjs.map +0 -1
- package/dist/reconcile-hx_vBvux.cjs +0 -136
- package/dist/reconcile-hx_vBvux.cjs.map +0 -1
- package/dist/transfer-CAJs_vhu.mjs +0 -122
- package/dist/transfer-CAJs_vhu.mjs.map +0 -1
- package/dist/transfer-D2qLFXsw.cjs +0 -151
- package/dist/transfer-D2qLFXsw.cjs.map +0 -1
- package/dist/workspace-Bu3xNCej.mjs.map +0 -1
- package/dist/workspace-CRD9YinV.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
|
-
import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { C as providerOf, S as eventsBackendFor, _ as COMPOSE_PATH, a as loadEnvFiles, b as resourceName, c as resolveServicePorts, d as backendsOf, f as constructGlobs, h as usesConstructs, i as isPortAvailable, l as rewriteUrlsWithPorts, m as reconcileWorkspace, n as createEntryWrapper, o as loadSecretsForApp, p as derivedContainers, r as findAvailablePort, s as prepareEntryCredentials, t as createCredentialsPreload, u as startWorkspaceServices, v as LOCAL_CA_PATH, w as storageBackendFor, x as cacheBackendFor, y as applyPostgres } from "./credentials-DI9kJcaP.mjs";
|
|
7
|
-
import { a as generateServiceCredentials, i as generateSecurePassword, n as generateConnectionUrls, o as rotateServicePassword, t as createStageSecrets } from "./generator-LfZU7NlU.mjs";
|
|
3
|
+
import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-CXGlQHm1.mjs";
|
|
4
|
+
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dqxa-_Ge.mjs";
|
|
5
|
+
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-T4IVGSoB.mjs";
|
|
8
6
|
import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-TP0x118M.mjs";
|
|
9
|
-
import {
|
|
7
|
+
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-cBobQVoA.mjs";
|
|
8
|
+
import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator } from "./openapi-D-Lk5Hr5.mjs";
|
|
10
9
|
import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
|
|
11
|
-
import { i as writeDockerEnvFromSecrets, r as generateFullstackCustomSecrets } from "./reconcile-
|
|
12
|
-
import { a as isRemoteStore, n as pullStageSecrets, r as pushStageSecrets, t as NoStoredSecrets } from "./transfer-CAJs_vhu.mjs";
|
|
10
|
+
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-DhbCvh4K.mjs";
|
|
13
11
|
import { createRequire } from "node:module";
|
|
14
12
|
import { Command } from "commander";
|
|
15
13
|
import { stdin, stdout } from "node:process";
|
|
@@ -22,24 +20,24 @@ import { execFile, execSync, spawn } from "node:child_process";
|
|
|
22
20
|
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
23
21
|
import fg from "fast-glob";
|
|
24
22
|
import chokidar from "chokidar";
|
|
25
|
-
import { parse as parse$1, parseDocument } from "yaml";
|
|
26
23
|
import { createCipheriv, createHash, randomBytes } from "node:crypto";
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27
25
|
import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
|
|
28
26
|
import { Client } from "pg";
|
|
27
|
+
import { parse as parse$1, parseDocument } from "yaml";
|
|
29
28
|
import { promisify } from "node:util";
|
|
30
29
|
import { cacheTableStatements } from "@geekmidas/cache/postgres";
|
|
31
|
-
import
|
|
30
|
+
import prompts from "prompts";
|
|
32
31
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
33
32
|
import { Function } from "@geekmidas/constructs/functions";
|
|
34
33
|
import { Queue } from "@geekmidas/constructs/queue";
|
|
35
34
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
36
35
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
37
36
|
import { lookup } from "node:dns/promises";
|
|
38
|
-
import prompts from "prompts";
|
|
39
37
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
40
38
|
import { serviceContext } from "@geekmidas/services";
|
|
41
39
|
//#region package.json
|
|
42
|
-
var version = "10.0.0-alpha.
|
|
40
|
+
var version = "10.0.0-alpha.28";
|
|
43
41
|
//#endregion
|
|
44
42
|
//#region src/auth/credentials.ts
|
|
45
43
|
/**
|
|
@@ -406,8 +404,182 @@ function normalizeRoutes(routes) {
|
|
|
406
404
|
return Array.isArray(routes) ? routes : [routes];
|
|
407
405
|
}
|
|
408
406
|
//#endregion
|
|
409
|
-
//#region src/
|
|
407
|
+
//#region src/trust/index.ts
|
|
408
|
+
/**
|
|
409
|
+
* Installing the local edge's certificate authority into the OS trust store.
|
|
410
|
+
*
|
|
411
|
+
* `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
|
|
412
|
+
* suite, anything under `gkm exec` — because reconcile injects it. A browser
|
|
413
|
+
* reads none of that: it asks the operating system, so until the root is in the
|
|
414
|
+
* system store a local `https://` address is "not secure", which is true and
|
|
415
|
+
* unhelpful.
|
|
416
|
+
*
|
|
417
|
+
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
418
|
+
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
419
|
+
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
420
|
+
* the line in the same place.
|
|
421
|
+
*/
|
|
410
422
|
const logger$12 = console;
|
|
423
|
+
const run = promisify(execFile);
|
|
424
|
+
/**
|
|
425
|
+
* How each platform is told about a new authority.
|
|
426
|
+
*
|
|
427
|
+
* Separate rather than shelled through one script because the failure modes
|
|
428
|
+
* differ and the message should name the real one: macOS refuses without an
|
|
429
|
+
* admin password, Debian needs the file in a specific directory before its
|
|
430
|
+
* updater will look at it.
|
|
431
|
+
*/
|
|
432
|
+
function installation(platform, certificate) {
|
|
433
|
+
if (platform === "darwin") return {
|
|
434
|
+
describe: "the System keychain",
|
|
435
|
+
steps: [["sudo", [
|
|
436
|
+
"security",
|
|
437
|
+
"add-trusted-cert",
|
|
438
|
+
"-d",
|
|
439
|
+
"-r",
|
|
440
|
+
"trustRoot",
|
|
441
|
+
"-k",
|
|
442
|
+
"/Library/Keychains/System.keychain",
|
|
443
|
+
certificate
|
|
444
|
+
]]]
|
|
445
|
+
};
|
|
446
|
+
if (platform === "linux") {
|
|
447
|
+
const debian = existsSync("/usr/local/share/ca-certificates");
|
|
448
|
+
return {
|
|
449
|
+
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
450
|
+
steps: debian ? [["sudo", [
|
|
451
|
+
"cp",
|
|
452
|
+
certificate,
|
|
453
|
+
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
454
|
+
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
455
|
+
"cp",
|
|
456
|
+
certificate,
|
|
457
|
+
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
458
|
+
]], ["sudo", ["update-ca-trust"]]]
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
throw new UnsupportedPlatform(platform, certificate);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Trust the certificate authority this project's edge issues from.
|
|
465
|
+
*
|
|
466
|
+
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
467
|
+
* same entry, so running it twice is not an error.
|
|
468
|
+
*/
|
|
469
|
+
async function trustCommand(options = {}) {
|
|
470
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
471
|
+
const certificate = join(workspace.root, LOCAL_CA_PATH);
|
|
472
|
+
if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
|
|
473
|
+
const { describe, steps } = installation(process.platform, certificate);
|
|
474
|
+
logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
475
|
+
logger$12.log(` ${certificate}\n`);
|
|
476
|
+
if (options.dryRun) {
|
|
477
|
+
for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
logger$12.log(" This needs an admin password.\n");
|
|
481
|
+
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
482
|
+
logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Trust the authority if this machine lacks it and its owner agrees.
|
|
486
|
+
*
|
|
487
|
+
* A machine's answer, not the project's: whether a laptop installs a root
|
|
488
|
+
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
489
|
+
* machine previously answered, then a prompt. Never a silent `sudo` — that is
|
|
490
|
+
* the one thing a setup command should not do on somebody's behalf.
|
|
491
|
+
*
|
|
492
|
+
* Silent and free when the machine already trusts it, which is what makes it
|
|
493
|
+
* acceptable to call on every `gkm setup`.
|
|
494
|
+
*/
|
|
495
|
+
async function ensureTrusted(root, url, options = {}) {
|
|
496
|
+
if (await isTrusted(url)) return;
|
|
497
|
+
if (!options.assumeYes) {
|
|
498
|
+
if (!process.stdin.isTTY) {
|
|
499
|
+
logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (await declined(root)) return;
|
|
503
|
+
const { install } = await prompts({
|
|
504
|
+
type: "confirm",
|
|
505
|
+
name: "install",
|
|
506
|
+
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
507
|
+
initial: false
|
|
508
|
+
});
|
|
509
|
+
if (!install) {
|
|
510
|
+
await recordDeclined(root);
|
|
511
|
+
logger$12.log(" Skipped. Run \"gkm trust\" later.");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
await trustCommand();
|
|
516
|
+
}
|
|
517
|
+
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
518
|
+
const TRUST_STATE = ".gkm/trust.json";
|
|
519
|
+
async function declined(root) {
|
|
520
|
+
try {
|
|
521
|
+
const raw = await readFile(join(root, TRUST_STATE), "utf-8");
|
|
522
|
+
return JSON.parse(raw).declined === true;
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function recordDeclined(root) {
|
|
528
|
+
const path = join(root, TRUST_STATE);
|
|
529
|
+
await mkdir(dirname(path), { recursive: true });
|
|
530
|
+
await writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Whether this machine already trusts the local authority.
|
|
534
|
+
*
|
|
535
|
+
* Asked by connecting rather than by reading a keychain: what matters is
|
|
536
|
+
* whether a client with no special configuration verifies the edge, and that is
|
|
537
|
+
* one platform-neutral question instead of three platform-specific ones. It is
|
|
538
|
+
* also the exact thing a browser is about to do.
|
|
539
|
+
*
|
|
540
|
+
* A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
|
|
541
|
+
* this process almost certainly has it set — asking in-process would answer
|
|
542
|
+
* "yes" for everyone.
|
|
543
|
+
*/
|
|
544
|
+
async function isTrusted(url) {
|
|
545
|
+
const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
|
|
546
|
+
try {
|
|
547
|
+
await run(process.execPath, [
|
|
548
|
+
"--use-system-ca",
|
|
549
|
+
"-e",
|
|
550
|
+
`fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
|
|
551
|
+
], {
|
|
552
|
+
env,
|
|
553
|
+
timeout: 15e3
|
|
554
|
+
});
|
|
555
|
+
return true;
|
|
556
|
+
} catch {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/** The edge has not run, so there is no authority to trust yet. */
|
|
561
|
+
var NoLocalAuthority = class extends Error {
|
|
562
|
+
path;
|
|
563
|
+
constructor(path) {
|
|
564
|
+
super(`No local certificate authority at ${path}. It is generated the first time the edge starts, so run "gkm setup" (or "gkm dev") once — and if this project declares no file server, nothing needs one.`);
|
|
565
|
+
this.path = path;
|
|
566
|
+
this.name = "NoLocalAuthority";
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
/** A platform with no trust store this knows how to write to. */
|
|
570
|
+
var UnsupportedPlatform = class extends Error {
|
|
571
|
+
platform;
|
|
572
|
+
certificate;
|
|
573
|
+
constructor(platform, certificate) {
|
|
574
|
+
super(`gkm does not know how to install a certificate authority on ${platform}. The root is at ${certificate} — add it to the trust store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`);
|
|
575
|
+
this.platform = platform;
|
|
576
|
+
this.certificate = certificate;
|
|
577
|
+
this.name = "UnsupportedPlatform";
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/exec/index.ts
|
|
582
|
+
const logger$11 = console;
|
|
411
583
|
/** `gkm exec` was given nothing to run. */
|
|
412
584
|
var NoCommandSpecified = class extends Error {
|
|
413
585
|
constructor() {
|
|
@@ -430,20 +602,17 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
430
602
|
const cwd = options.cwd ?? process.cwd();
|
|
431
603
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
432
604
|
const defaultEnv = loadEnvFiles(".env");
|
|
433
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
434
|
-
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
|
|
435
|
-
|
|
436
|
-
resolveDockerPorts: "readonly"
|
|
437
|
-
});
|
|
438
|
-
if (appName) logger$12.log(`📦 App: ${appName}`);
|
|
605
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
606
|
+
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
|
|
607
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
439
608
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
440
|
-
if (secretCount > 0) logger$
|
|
609
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
441
610
|
const preloadDir = join(cwd, ".gkm");
|
|
442
611
|
await mkdir(preloadDir, { recursive: true });
|
|
443
612
|
const preloadPath = join(preloadDir, "credentials-preload.mjs");
|
|
444
613
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
445
614
|
const [cmd, ...args] = commandArgs;
|
|
446
|
-
logger$
|
|
615
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
447
616
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
448
617
|
const child = spawn(cmd, args, {
|
|
449
618
|
cwd,
|
|
@@ -457,7 +626,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
457
626
|
const exitCode = await new Promise((resolve) => {
|
|
458
627
|
child.on("close", (code) => resolve(code ?? 0));
|
|
459
628
|
child.on("error", (error) => {
|
|
460
|
-
logger$
|
|
629
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
461
630
|
resolve(1);
|
|
462
631
|
});
|
|
463
632
|
});
|
|
@@ -465,7 +634,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
465
634
|
}
|
|
466
635
|
//#endregion
|
|
467
636
|
//#region src/dev/index.ts
|
|
468
|
-
const logger$
|
|
637
|
+
const logger$10 = console;
|
|
469
638
|
/**
|
|
470
639
|
* Normalize telescope configuration
|
|
471
640
|
* @internal Exported for testing
|
|
@@ -559,7 +728,7 @@ function getProductionConfigFromGkm(config) {
|
|
|
559
728
|
async function devCommand(options) {
|
|
560
729
|
if (options.entry) return entryDevCommand(options);
|
|
561
730
|
const defaultEnv = loadEnvFiles(".env");
|
|
562
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
731
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
563
732
|
const appName = getAppNameFromCwd();
|
|
564
733
|
let config;
|
|
565
734
|
let appRoot = process.cwd();
|
|
@@ -575,9 +744,8 @@ async function devCommand(options) {
|
|
|
575
744
|
workspaceAppName = appConfig.appName;
|
|
576
745
|
workspaceAppPort = appConfig.app.port;
|
|
577
746
|
workspace = appConfig.workspace;
|
|
578
|
-
logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
579
747
|
if (appConfig.app.entry) {
|
|
580
|
-
logger$
|
|
748
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
581
749
|
return entryDevCommand({
|
|
582
750
|
...options,
|
|
583
751
|
entry: appConfig.app.entry,
|
|
@@ -587,38 +755,27 @@ async function devCommand(options) {
|
|
|
587
755
|
}
|
|
588
756
|
} catch {
|
|
589
757
|
const loadedConfig = await loadWorkspaceConfig();
|
|
590
|
-
if (loadedConfig.type === "workspace")
|
|
591
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
592
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
593
|
-
}
|
|
758
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
594
759
|
config = loadedConfig.raw;
|
|
760
|
+
workspace = loadedConfig.workspace;
|
|
595
761
|
}
|
|
596
762
|
else {
|
|
597
763
|
const loadedConfig = await loadWorkspaceConfig();
|
|
598
|
-
if (loadedConfig.type === "workspace")
|
|
599
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
600
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
601
|
-
}
|
|
764
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
602
765
|
config = loadedConfig.raw;
|
|
603
766
|
workspace = loadedConfig.workspace;
|
|
604
767
|
}
|
|
605
768
|
if (config.env) {
|
|
606
769
|
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
607
|
-
if (loaded.length > 0) logger$
|
|
608
|
-
if (missing.length > 0) logger$
|
|
770
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
771
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
609
772
|
}
|
|
610
773
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
611
|
-
logger$11.log("🚀 Starting development server...");
|
|
612
|
-
logger$11.log(`Loading constructs from: ${config.constructs}`);
|
|
613
774
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
614
|
-
if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
615
775
|
const studio = normalizeStudioConfig(config.studio);
|
|
616
|
-
if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
617
776
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
618
|
-
if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
619
777
|
const enableOpenApi = resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
|
|
620
|
-
|
|
621
|
-
const build = (bustCache = false) => buildApp({
|
|
778
|
+
const build = (bustCache = false) => quietly(() => buildApp({
|
|
622
779
|
config,
|
|
623
780
|
workspaceRoot: workspace?.root ?? secretsRoot,
|
|
624
781
|
appRoot,
|
|
@@ -631,29 +788,21 @@ async function devCommand(options) {
|
|
|
631
788
|
skipBundle: true,
|
|
632
789
|
bustCache,
|
|
633
790
|
serveEmpty: true
|
|
634
|
-
});
|
|
791
|
+
}));
|
|
635
792
|
const initial = await build();
|
|
636
793
|
const runtime = config.runtime ?? "node";
|
|
637
794
|
let secretsJsonPath;
|
|
795
|
+
let publicUrl;
|
|
638
796
|
const appSecrets = await loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
639
|
-
if (workspace
|
|
797
|
+
if (workspace) {
|
|
640
798
|
const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
641
799
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
642
|
-
logger$
|
|
643
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$
|
|
800
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
801
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
644
802
|
}
|
|
645
803
|
Object.assign(appSecrets, reconciled.env);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
649
|
-
const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
650
|
-
Object.assign(appSecrets, rewritten);
|
|
651
|
-
logger$11.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (workspace && workspaceAppName) {
|
|
655
|
-
const depEnv = getDependencyEnvVars(workspace, workspaceAppName);
|
|
656
|
-
for (const [key, value] of Object.entries(depEnv)) appSecrets[key] ??= value;
|
|
804
|
+
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
|
|
805
|
+
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
657
806
|
}
|
|
658
807
|
if (Object.keys(appSecrets).length > 0) {
|
|
659
808
|
const secretsDir = join(secretsRoot, ".gkm");
|
|
@@ -661,13 +810,13 @@ async function devCommand(options) {
|
|
|
661
810
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
662
811
|
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
663
812
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
664
|
-
logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
665
813
|
}
|
|
666
814
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
|
|
815
|
+
devServer.label = workspaceAppName ?? "server";
|
|
816
|
+
devServer.publicUrl = publicUrl;
|
|
667
817
|
await devServer.start();
|
|
668
818
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
669
819
|
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
670
|
-
logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
671
820
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
672
821
|
cwd: appRoot,
|
|
673
822
|
absolute: false,
|
|
@@ -676,34 +825,28 @@ async function devCommand(options) {
|
|
|
676
825
|
const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
|
|
677
826
|
return f.split("/").slice(0, -1).join("/");
|
|
678
827
|
}))];
|
|
679
|
-
logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
680
828
|
const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
|
|
681
829
|
ignored: /(^|[/\\])\../,
|
|
682
830
|
persistent: true,
|
|
683
831
|
ignoreInitial: true,
|
|
684
832
|
cwd: appRoot
|
|
685
833
|
});
|
|
686
|
-
watcher.on("ready", () => {
|
|
687
|
-
logger$11.log("🔍 File watcher ready");
|
|
688
|
-
});
|
|
689
834
|
watcher.on("error", (error) => {
|
|
690
|
-
logger$
|
|
835
|
+
logger$10.error("❌ Watcher error:", error);
|
|
691
836
|
});
|
|
692
837
|
let rebuildTimeout = null;
|
|
693
838
|
watcher.on("change", async (path) => {
|
|
694
|
-
logger$
|
|
839
|
+
logger$10.log(`🔄 ${path} changed — rebuilding`);
|
|
695
840
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
696
841
|
rebuildTimeout = setTimeout(async () => {
|
|
697
842
|
try {
|
|
698
|
-
logger$11.log("🔄 Rebuilding...");
|
|
699
843
|
const rebuilt = await build(true);
|
|
700
|
-
logger$11.log("✅ Rebuild complete, restarting server...");
|
|
701
844
|
devServer.selfServing = rebuilt.selfServing !== void 0;
|
|
702
845
|
await devServer.restart();
|
|
703
846
|
} catch (error) {
|
|
704
847
|
const err = error;
|
|
705
|
-
logger$
|
|
706
|
-
if (err.stack) logger$
|
|
848
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
849
|
+
if (err.stack) logger$10.error(err.stack);
|
|
707
850
|
}
|
|
708
851
|
}, 300);
|
|
709
852
|
});
|
|
@@ -711,9 +854,9 @@ async function devCommand(options) {
|
|
|
711
854
|
const shutdown = () => {
|
|
712
855
|
if (isShuttingDown) return;
|
|
713
856
|
isShuttingDown = true;
|
|
714
|
-
logger$
|
|
857
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
715
858
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
716
|
-
logger$
|
|
859
|
+
logger$10.error("Error during shutdown:", err);
|
|
717
860
|
}).finally(() => {
|
|
718
861
|
process.exit(0);
|
|
719
862
|
});
|
|
@@ -722,19 +865,6 @@ async function devCommand(options) {
|
|
|
722
865
|
process.on("SIGTERM", shutdown);
|
|
723
866
|
}
|
|
724
867
|
/**
|
|
725
|
-
* Generate all dependency environment variables for all apps.
|
|
726
|
-
* Returns a flat object with all {APP_NAME}_URL variables.
|
|
727
|
-
* @internal Exported for testing
|
|
728
|
-
*/
|
|
729
|
-
function generateAllDependencyEnvVars(workspace, urlPrefix = "http://localhost") {
|
|
730
|
-
const env = {};
|
|
731
|
-
for (const appName of Object.keys(workspace.apps)) {
|
|
732
|
-
const appEnv = getDependencyEnvVars(workspace, appName, urlPrefix);
|
|
733
|
-
Object.assign(env, appEnv);
|
|
734
|
-
}
|
|
735
|
-
return env;
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
868
|
* Check for port conflicts across all apps.
|
|
739
869
|
* Returns list of conflicts if any ports are duplicated.
|
|
740
870
|
* @internal Exported for testing
|
|
@@ -912,12 +1042,8 @@ async function loadDevSecrets(workspace) {
|
|
|
912
1042
|
const stage = workspace.stages.local;
|
|
913
1043
|
if (secretsExist(stage, workspace.root)) {
|
|
914
1044
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
915
|
-
if (secrets)
|
|
916
|
-
logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
917
|
-
return toEmbeddableSecrets(secrets);
|
|
918
|
-
}
|
|
1045
|
+
if (secrets) return toEmbeddableSecrets(secrets);
|
|
919
1046
|
}
|
|
920
|
-
logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
|
|
921
1047
|
return {};
|
|
922
1048
|
}
|
|
923
1049
|
/**
|
|
@@ -931,66 +1057,43 @@ async function loadDevSecrets(workspace) {
|
|
|
931
1057
|
*/
|
|
932
1058
|
async function workspaceDevCommand(workspace, options) {
|
|
933
1059
|
const appCount = Object.keys(workspace.apps).length;
|
|
934
|
-
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
935
1060
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
936
|
-
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
937
|
-
logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
938
|
-
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
939
|
-
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
940
|
-
logger$11.log(` ${counts.join(", ")} app(s)`);
|
|
941
1061
|
const conflicts = checkPortConflicts(workspace);
|
|
942
1062
|
if (conflicts.length > 0) {
|
|
943
|
-
for (const conflict of conflicts) logger$
|
|
1063
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
944
1064
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
945
1065
|
}
|
|
946
1066
|
if (frontendApps.length > 0) {
|
|
947
|
-
logger$11.log("\n🔍 Validating frontend apps...");
|
|
948
1067
|
const validationResults = await validateFrontendApps(workspace);
|
|
949
1068
|
let hasErrors = false;
|
|
950
1069
|
for (const result of validationResults) {
|
|
951
1070
|
if (!result.valid) {
|
|
952
1071
|
hasErrors = true;
|
|
953
|
-
logger$
|
|
954
|
-
for (const error of result.errors) logger$
|
|
1072
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1073
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
955
1074
|
}
|
|
956
|
-
for (const warning of result.warnings) logger$
|
|
1075
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
957
1076
|
}
|
|
958
1077
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
959
|
-
logger$11.log("✅ Frontend apps validated");
|
|
960
1078
|
}
|
|
961
1079
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
962
|
-
|
|
1080
|
+
const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
1081
|
+
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1082
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1083
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
1084
|
+
}
|
|
1085
|
+
const secretsEnv = {
|
|
1086
|
+
...rawSecrets,
|
|
1087
|
+
...reconciled.env
|
|
1088
|
+
};
|
|
963
1089
|
const appUrls = {};
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
|
|
969
|
-
}
|
|
970
|
-
secretsEnv = {
|
|
971
|
-
...rawSecrets,
|
|
972
|
-
...reconciled.env
|
|
973
|
-
};
|
|
974
|
-
for (const resource of reconciled.plan.resources) {
|
|
975
|
-
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
976
|
-
const address = reconciled.env[resource.envKey];
|
|
977
|
-
if (address) appUrls[resource.id] = address;
|
|
978
|
-
}
|
|
979
|
-
} else {
|
|
980
|
-
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
981
|
-
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
982
|
-
secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
983
|
-
}
|
|
984
|
-
if (Object.keys(secretsEnv).length > 0) logger$11.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
985
|
-
if (Object.keys(appUrls).length > 0) {
|
|
986
|
-
logger$11.log("🔒 App URLs:");
|
|
987
|
-
for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
|
|
988
|
-
}
|
|
989
|
-
const dependencyEnv = Object.fromEntries(Object.entries(generateAllDependencyEnvVars(workspace)).filter(([key]) => secretsEnv[key] === void 0));
|
|
990
|
-
if (Object.keys(dependencyEnv).length > 0) {
|
|
991
|
-
logger$11.log("📡 Dependency URLs:");
|
|
992
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$11.log(` ${key}=${value}`);
|
|
1090
|
+
for (const resource of reconciled.plan.resources) {
|
|
1091
|
+
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
1092
|
+
const address = reconciled.env[resource.envKey];
|
|
1093
|
+
if (address) appUrls[resource.id] = address;
|
|
993
1094
|
}
|
|
1095
|
+
const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
|
|
1096
|
+
if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
|
|
994
1097
|
let turboFilter = [];
|
|
995
1098
|
if (options.app) {
|
|
996
1099
|
if (!workspace.apps[options.app]) {
|
|
@@ -998,25 +1101,23 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
998
1101
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
999
1102
|
}
|
|
1000
1103
|
turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
turboFilter = ["--filter", options.filter];
|
|
1004
|
-
logger$11.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1005
|
-
} else {
|
|
1104
|
+
} else if (options.filter) turboFilter = ["--filter", options.filter];
|
|
1105
|
+
else {
|
|
1006
1106
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
1007
1107
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
1008
|
-
if (unpackaged.length > 0) logger$
|
|
1009
|
-
logger$11.log(`\n🎯 Running all ${appCount} apps`);
|
|
1108
|
+
if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
|
|
1010
1109
|
}
|
|
1011
1110
|
const buildOrder = getAppBuildOrder(workspace);
|
|
1012
|
-
|
|
1111
|
+
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1112
|
+
logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
1013
1113
|
for (const appName of buildOrder) {
|
|
1014
1114
|
const app = workspace.apps[appName];
|
|
1015
1115
|
if (!app) continue;
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
logger$
|
|
1116
|
+
const local = `http://localhost:${app.port}`;
|
|
1117
|
+
const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
|
|
1118
|
+
logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
1019
1119
|
}
|
|
1120
|
+
logger$10.log("");
|
|
1020
1121
|
const configFiles = [
|
|
1021
1122
|
"gkm.config.ts",
|
|
1022
1123
|
"gkm.config.js",
|
|
@@ -1033,7 +1134,6 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1033
1134
|
const turboEnv = {
|
|
1034
1135
|
...process.env,
|
|
1035
1136
|
...secretsEnv,
|
|
1036
|
-
...dependencyEnv,
|
|
1037
1137
|
NODE_ENV: "development",
|
|
1038
1138
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1039
1139
|
};
|
|
@@ -1048,7 +1148,6 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1048
1148
|
});
|
|
1049
1149
|
}
|
|
1050
1150
|
if (held.length > 0) throw new WorkspacePortsInUse(held);
|
|
1051
|
-
logger$11.log("\n🏃 Starting turbo run dev...\n");
|
|
1052
1151
|
const turboProcess = spawn("pnpm", [
|
|
1053
1152
|
"turbo",
|
|
1054
1153
|
"run",
|
|
@@ -1064,7 +1163,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1064
1163
|
const shutdown = () => {
|
|
1065
1164
|
if (isShuttingDown) return;
|
|
1066
1165
|
isShuttingDown = true;
|
|
1067
|
-
logger$
|
|
1166
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1068
1167
|
const pid = turboProcess.pid;
|
|
1069
1168
|
if (pid) try {
|
|
1070
1169
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1088,7 +1187,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1088
1187
|
process.on("SIGTERM", shutdown);
|
|
1089
1188
|
return new Promise((resolve, reject) => {
|
|
1090
1189
|
turboProcess.on("error", (error) => {
|
|
1091
|
-
logger$
|
|
1190
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1092
1191
|
reject(error);
|
|
1093
1192
|
});
|
|
1094
1193
|
turboProcess.on("exit", (code) => {
|
|
@@ -1107,11 +1206,11 @@ async function entryDevCommand(options) {
|
|
|
1107
1206
|
const entryPath = resolve(process.cwd(), entry);
|
|
1108
1207
|
if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1109
1208
|
const defaultEnv = loadEnvFiles(".env");
|
|
1110
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1209
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1111
1210
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1112
|
-
if (appName) logger$
|
|
1113
|
-
logger$
|
|
1114
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1211
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1212
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1213
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1115
1214
|
const wrapperDir = join(process.cwd(), ".gkm");
|
|
1116
1215
|
await mkdir(wrapperDir, { recursive: true });
|
|
1117
1216
|
const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1122,7 +1221,7 @@ async function entryDevCommand(options) {
|
|
|
1122
1221
|
const shutdown = () => {
|
|
1123
1222
|
if (isShuttingDown) return;
|
|
1124
1223
|
isShuttingDown = true;
|
|
1125
|
-
logger$
|
|
1224
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1126
1225
|
runner.stop();
|
|
1127
1226
|
process.exit(0);
|
|
1128
1227
|
};
|
|
@@ -1158,14 +1257,14 @@ var EntryRunner = class {
|
|
|
1158
1257
|
});
|
|
1159
1258
|
let restartTimeout = null;
|
|
1160
1259
|
this.watcher.on("change", (path) => {
|
|
1161
|
-
logger$
|
|
1260
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1162
1261
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1163
1262
|
restartTimeout = setTimeout(async () => {
|
|
1164
|
-
logger$
|
|
1263
|
+
logger$10.log("🔄 Restarting...");
|
|
1165
1264
|
await this.restart();
|
|
1166
1265
|
}, 300);
|
|
1167
1266
|
});
|
|
1168
|
-
logger$
|
|
1267
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1169
1268
|
}
|
|
1170
1269
|
}
|
|
1171
1270
|
async runProcess() {
|
|
@@ -1173,24 +1272,24 @@ var EntryRunner = class {
|
|
|
1173
1272
|
...process.env,
|
|
1174
1273
|
PORT: String(this.port)
|
|
1175
1274
|
};
|
|
1176
|
-
this.childProcess = spawn(
|
|
1275
|
+
this.childProcess = spawn(...tsxCommand(dirname(this.wrapperPath), [this.wrapperPath]), {
|
|
1177
1276
|
stdio: "inherit",
|
|
1178
1277
|
env,
|
|
1179
1278
|
detached: true
|
|
1180
1279
|
});
|
|
1181
1280
|
this.isRunning = true;
|
|
1182
1281
|
this.childProcess.on("error", (error) => {
|
|
1183
|
-
logger$
|
|
1282
|
+
logger$10.error("❌ Process error:", error);
|
|
1184
1283
|
});
|
|
1185
1284
|
this.childProcess.on("exit", (code) => {
|
|
1186
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1285
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1187
1286
|
this.isRunning = false;
|
|
1188
1287
|
});
|
|
1189
1288
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1190
1289
|
if (this.isRunning) {
|
|
1191
|
-
logger$
|
|
1192
|
-
logger$
|
|
1193
|
-
logger$
|
|
1290
|
+
logger$10.log("");
|
|
1291
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1292
|
+
logger$10.log("");
|
|
1194
1293
|
}
|
|
1195
1294
|
}
|
|
1196
1295
|
async restart() {
|
|
@@ -1276,7 +1375,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
|
|
|
1276
1375
|
const { app } = await import('${appImportPath}');
|
|
1277
1376
|
|
|
1278
1377
|
${serveCode.replace(/^ {4}/gm, "")}
|
|
1279
|
-
console.log(\`Server started on port \${port}\`);
|
|
1280
1378
|
`;
|
|
1281
1379
|
return `#!/usr/bin/env node
|
|
1282
1380
|
/**
|
|
@@ -1305,6 +1403,31 @@ start({
|
|
|
1305
1403
|
});
|
|
1306
1404
|
`;
|
|
1307
1405
|
}
|
|
1406
|
+
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
1407
|
+
async function quietly(fn) {
|
|
1408
|
+
const log = console.log;
|
|
1409
|
+
console.log = () => {};
|
|
1410
|
+
try {
|
|
1411
|
+
return await fn();
|
|
1412
|
+
} finally {
|
|
1413
|
+
console.log = log;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* How to run a TypeScript entry with the app's own tsx.
|
|
1418
|
+
*
|
|
1419
|
+
* Straight through node rather than `npx tsx`: npx reads the developer's npm
|
|
1420
|
+
* config on every start and warned about each key it did not recognise — nine
|
|
1421
|
+
* lines per app, per restart, about nothing gkm does.
|
|
1422
|
+
*/
|
|
1423
|
+
function tsxCommand(appRoot, args) {
|
|
1424
|
+
try {
|
|
1425
|
+
const cli = createRequire(join(appRoot, "package.json")).resolve("tsx/cli");
|
|
1426
|
+
return [process.execPath, [cli, ...args]];
|
|
1427
|
+
} catch {
|
|
1428
|
+
return ["npx", ["tsx", ...args]];
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1308
1431
|
/** A port this app must serve on is held by something else. */
|
|
1309
1432
|
var DevPortInUse = class extends Error {
|
|
1310
1433
|
port;
|
|
@@ -1371,6 +1494,13 @@ var DevServer = class {
|
|
|
1371
1494
|
this.selfServing = selfServing;
|
|
1372
1495
|
this.actualPort = requestedPort;
|
|
1373
1496
|
}
|
|
1497
|
+
/** What the ready line calls this app. */
|
|
1498
|
+
label = "server";
|
|
1499
|
+
/**
|
|
1500
|
+
* Where the app is reached — its HTTPS address behind the edge, when it has
|
|
1501
|
+
* one. The local port is only what the edge forwards to.
|
|
1502
|
+
*/
|
|
1503
|
+
publicUrl;
|
|
1374
1504
|
async start() {
|
|
1375
1505
|
this.startTime = Date.now();
|
|
1376
1506
|
if (this.isRunning) await this.stop();
|
|
@@ -1379,17 +1509,15 @@ var DevServer = class {
|
|
|
1379
1509
|
this.actualPort = this.requestedPort;
|
|
1380
1510
|
} else {
|
|
1381
1511
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
1382
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1512
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1383
1513
|
}
|
|
1384
1514
|
const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1385
1515
|
await this.createServerEntry();
|
|
1386
|
-
|
|
1387
|
-
this.serverProcess = spawn("npx", [
|
|
1388
|
-
"tsx",
|
|
1516
|
+
this.serverProcess = spawn(...tsxCommand(this.appRoot, [
|
|
1389
1517
|
serverEntryPath,
|
|
1390
1518
|
"--port",
|
|
1391
1519
|
this.actualPort.toString()
|
|
1392
|
-
], {
|
|
1520
|
+
]), {
|
|
1393
1521
|
stdio: "inherit",
|
|
1394
1522
|
env: {
|
|
1395
1523
|
...process.env,
|
|
@@ -1400,29 +1528,24 @@ var DevServer = class {
|
|
|
1400
1528
|
});
|
|
1401
1529
|
this.isRunning = true;
|
|
1402
1530
|
this.serverProcess.on("error", (error) => {
|
|
1403
|
-
logger$
|
|
1531
|
+
logger$10.error("❌ Server error:", error);
|
|
1404
1532
|
});
|
|
1405
1533
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1406
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1534
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
1407
1535
|
this.isRunning = false;
|
|
1408
1536
|
});
|
|
1409
1537
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1410
1538
|
if (this.isRunning) {
|
|
1411
|
-
const
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
logger$
|
|
1421
|
-
logger$11.log("");
|
|
1422
|
-
logger$11.log(` ┌${border}┐`);
|
|
1423
|
-
for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
|
|
1424
|
-
logger$11.log(` └${border}┘`);
|
|
1425
|
-
logger$11.log("");
|
|
1539
|
+
const local = `http://localhost:${this.actualPort}`;
|
|
1540
|
+
const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
|
|
1541
|
+
const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
1542
|
+
logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
|
|
1543
|
+
const tools = this.selfServing ? [] : [
|
|
1544
|
+
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
1545
|
+
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
1546
|
+
...this.studio ? [`studio ${this.studio.path}`] : []
|
|
1547
|
+
];
|
|
1548
|
+
if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
|
|
1426
1549
|
}
|
|
1427
1550
|
}
|
|
1428
1551
|
async stop() {
|
|
@@ -2237,7 +2360,7 @@ var TopicGenerator = class extends ConstructGenerator {
|
|
|
2237
2360
|
};
|
|
2238
2361
|
//#endregion
|
|
2239
2362
|
//#region src/build/manifests.ts
|
|
2240
|
-
const logger$
|
|
2363
|
+
const logger$9 = console;
|
|
2241
2364
|
function isPartitioned(field) {
|
|
2242
2365
|
return !Array.isArray(field);
|
|
2243
2366
|
}
|
|
@@ -2334,8 +2457,8 @@ export type RoutePath = Route['path'];
|
|
|
2334
2457
|
`;
|
|
2335
2458
|
const manifestPath = join(manifestDir, "aws.ts");
|
|
2336
2459
|
await writeFile(manifestPath, content);
|
|
2337
|
-
logger$
|
|
2338
|
-
logger$
|
|
2460
|
+
logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
|
|
2461
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2339
2462
|
}
|
|
2340
2463
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
|
|
2341
2464
|
const manifestDir = join(outputDir, "manifest");
|
|
@@ -2390,8 +2513,8 @@ export type RoutePath = Route['path'];
|
|
|
2390
2513
|
`;
|
|
2391
2514
|
const manifestPath = join(manifestDir, "server.ts");
|
|
2392
2515
|
await writeFile(manifestPath, content);
|
|
2393
|
-
logger$
|
|
2394
|
-
logger$
|
|
2516
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
|
|
2517
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2395
2518
|
}
|
|
2396
2519
|
/**
|
|
2397
2520
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2596,7 +2719,7 @@ export default app;
|
|
|
2596
2719
|
}
|
|
2597
2720
|
//#endregion
|
|
2598
2721
|
//#region src/build/index.ts
|
|
2599
|
-
const logger$
|
|
2722
|
+
const logger$8 = console;
|
|
2600
2723
|
/**
|
|
2601
2724
|
* What a build run at the workspace root reads.
|
|
2602
2725
|
*
|
|
@@ -2621,7 +2744,7 @@ async function buildCommand(options) {
|
|
|
2621
2744
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2622
2745
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => resolve(workspaceRoot, app.path) !== workspaceRoot);
|
|
2623
2746
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
2624
|
-
logger$
|
|
2747
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2625
2748
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2626
2749
|
}
|
|
2627
2750
|
}
|
|
@@ -2630,15 +2753,15 @@ async function buildCommand(options) {
|
|
|
2630
2753
|
const cacheBackend = cacheBackendFor(providerOf(loadedConfig.workspace));
|
|
2631
2754
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2632
2755
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2633
|
-
if (production) logger$
|
|
2634
|
-
logger$
|
|
2635
|
-
logger$
|
|
2756
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
2757
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2758
|
+
logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
2636
2759
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2637
|
-
if (telescope) logger$
|
|
2760
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2638
2761
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2639
|
-
if (studio) logger$
|
|
2762
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2640
2763
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2641
|
-
if (hooks) logger$
|
|
2764
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2642
2765
|
return buildApp({
|
|
2643
2766
|
config,
|
|
2644
2767
|
workspaceRoot: loadedConfig.workspace.root,
|
|
@@ -2681,7 +2804,7 @@ async function buildApp(input) {
|
|
|
2681
2804
|
});
|
|
2682
2805
|
if (selfServing) {
|
|
2683
2806
|
await writeSurfaceEntry(join(appRoot, ".gkm", "server"), selfServing.source);
|
|
2684
|
-
logger$
|
|
2807
|
+
logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
2685
2808
|
return { selfServing: selfServing.id };
|
|
2686
2809
|
}
|
|
2687
2810
|
const derived = ownersContext({
|
|
@@ -2721,14 +2844,14 @@ async function buildApp(input) {
|
|
|
2721
2844
|
topicGenerator.load(code, appRoot, bustCache)
|
|
2722
2845
|
]);
|
|
2723
2846
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
2724
|
-
logger$
|
|
2725
|
-
logger$
|
|
2726
|
-
logger$
|
|
2727
|
-
logger$
|
|
2728
|
-
logger$
|
|
2729
|
-
logger$
|
|
2847
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
2848
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
2849
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
2850
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2851
|
+
logger$8.log(`Found ${allQueues.length} queues`);
|
|
2852
|
+
logger$8.log(`Found ${allTopics.length} topics`);
|
|
2730
2853
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2731
|
-
logger$
|
|
2854
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2732
2855
|
return {};
|
|
2733
2856
|
}
|
|
2734
2857
|
const rootOutputDir = join(appRoot, ".gkm");
|
|
@@ -2745,7 +2868,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2745
2868
|
const rootOutputDir = join(appRoot, ".gkm");
|
|
2746
2869
|
const outputDir = join(rootOutputDir, provider);
|
|
2747
2870
|
await mkdir(outputDir, { recursive: true });
|
|
2748
|
-
logger$
|
|
2871
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2749
2872
|
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2750
2873
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2751
2874
|
provider,
|
|
@@ -2757,7 +2880,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2757
2880
|
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2758
2881
|
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2759
2882
|
]);
|
|
2760
|
-
logger$
|
|
2883
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
|
|
2761
2884
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2762
2885
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2763
2886
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
@@ -2777,8 +2900,8 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2777
2900
|
}, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2778
2901
|
let masterKey;
|
|
2779
2902
|
if (context.production?.bundle && !skipBundle) {
|
|
2780
|
-
logger$
|
|
2781
|
-
const { bundleServer } = await import("./bundler-
|
|
2903
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2904
|
+
const { bundleServer } = await import("./bundler-D5RcGmVM.mjs");
|
|
2782
2905
|
const allConstructs = [
|
|
2783
2906
|
...endpoints.map((e) => e.construct),
|
|
2784
2907
|
...functions.map((f) => f.construct),
|
|
@@ -2795,10 +2918,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2795
2918
|
stage,
|
|
2796
2919
|
constructs: allConstructs
|
|
2797
2920
|
})).masterKey;
|
|
2798
|
-
logger$
|
|
2921
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2799
2922
|
if (masterKey) {
|
|
2800
|
-
logger$
|
|
2801
|
-
logger$
|
|
2923
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2924
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2802
2925
|
}
|
|
2803
2926
|
}
|
|
2804
2927
|
return { masterKey };
|
|
@@ -2885,19 +3008,19 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2885
3008
|
const apps = Object.entries(workspace.apps);
|
|
2886
3009
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2887
3010
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2888
|
-
logger$
|
|
2889
|
-
logger$
|
|
2890
|
-
logger$
|
|
2891
|
-
if (options.production) logger$
|
|
3011
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
3012
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3013
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3014
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2892
3015
|
const buildOrder = getAppBuildOrder(workspace);
|
|
2893
|
-
logger$
|
|
3016
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2894
3017
|
const pm = detectPackageManager$2();
|
|
2895
|
-
logger$
|
|
3018
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2896
3019
|
try {
|
|
2897
3020
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
2898
3021
|
if (unpackaged.length > 0) throw new Error(`No package.json for workspace app(s): ${unpackaged.join(", ")}. Each app needs one — turbo builds packages, and gkm reads the name back to know which app it is building.`);
|
|
2899
3022
|
const turboCommand = getTurboCommand(pm, filters);
|
|
2900
|
-
logger$
|
|
3023
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2901
3024
|
await new Promise((resolve, reject) => {
|
|
2902
3025
|
const child = spawn(turboCommand, {
|
|
2903
3026
|
shell: true,
|
|
@@ -2925,15 +3048,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2925
3048
|
outputPath
|
|
2926
3049
|
});
|
|
2927
3050
|
}
|
|
2928
|
-
logger$
|
|
2929
|
-
logger$
|
|
3051
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
3052
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2930
3053
|
for (const result of results) {
|
|
2931
3054
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2932
|
-
logger$
|
|
3055
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2933
3056
|
}
|
|
2934
3057
|
} catch (error) {
|
|
2935
3058
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2936
|
-
logger$
|
|
3059
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2937
3060
|
for (const [appName, app] of apps) results.push({
|
|
2938
3061
|
appName,
|
|
2939
3062
|
type: app.type,
|
|
@@ -3567,10 +3690,6 @@ var UnprovisionableBucket = class extends Error {
|
|
|
3567
3690
|
*/
|
|
3568
3691
|
/**
|
|
3569
3692
|
* Provision everything the workspace declares, and return what it resolved.
|
|
3570
|
-
*
|
|
3571
|
-
* Nothing happens for a project that has not adopted the model: `usesConstructs`
|
|
3572
|
-
* is the same hard switch reconcile reads, so an existing deploy is untouched
|
|
3573
|
-
* until it declares something.
|
|
3574
3693
|
*/
|
|
3575
3694
|
async function provisionDeclared(options) {
|
|
3576
3695
|
const { workspace } = options;
|
|
@@ -3580,7 +3699,6 @@ async function provisionDeclared(options) {
|
|
|
3580
3699
|
provisioned: {},
|
|
3581
3700
|
clusters: {}
|
|
3582
3701
|
};
|
|
3583
|
-
if (!usesConstructs(workspace)) return empty;
|
|
3584
3702
|
const manifest = options.manifest ?? await discover({
|
|
3585
3703
|
patterns: constructGlobs(workspace),
|
|
3586
3704
|
cwd: workspace.root
|
|
@@ -3794,7 +3912,7 @@ async function createDnsProvider(options) {
|
|
|
3794
3912
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
3795
3913
|
const provider = config.provider;
|
|
3796
3914
|
if (provider === "hostinger") {
|
|
3797
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
3915
|
+
const { HostingerProvider } = await import("./HostingerProvider-Btxpto71.mjs");
|
|
3798
3916
|
return new HostingerProvider();
|
|
3799
3917
|
}
|
|
3800
3918
|
if (provider === "route53") {
|
|
@@ -3816,7 +3934,7 @@ async function createDnsProvider(options) {
|
|
|
3816
3934
|
*
|
|
3817
3935
|
* Handles automatic DNS record creation for deployed applications.
|
|
3818
3936
|
*/
|
|
3819
|
-
const logger$
|
|
3937
|
+
const logger$7 = console;
|
|
3820
3938
|
/**
|
|
3821
3939
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
3822
3940
|
*/
|
|
@@ -3853,7 +3971,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
3853
3971
|
for (const [appName, hostname] of appHostnames) {
|
|
3854
3972
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
3855
3973
|
if (!rootDomain) {
|
|
3856
|
-
logger$
|
|
3974
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
3857
3975
|
continue;
|
|
3858
3976
|
}
|
|
3859
3977
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -3903,10 +4021,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
3903
4021
|
* Print DNS records table
|
|
3904
4022
|
*/
|
|
3905
4023
|
function printDnsRecordsTable(records, rootDomain) {
|
|
3906
|
-
logger$
|
|
3907
|
-
logger$
|
|
3908
|
-
logger$
|
|
3909
|
-
logger$
|
|
4024
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
4025
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4026
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4027
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
3910
4028
|
for (const record of records) {
|
|
3911
4029
|
const subdomain = record.subdomain.padEnd(35);
|
|
3912
4030
|
const type = record.type.padEnd(4);
|
|
@@ -3916,18 +4034,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
3916
4034
|
else if (record.created) status = "✓ new";
|
|
3917
4035
|
else if (record.existed) status = "✓";
|
|
3918
4036
|
else status = "?";
|
|
3919
|
-
logger$
|
|
4037
|
+
logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
|
|
3920
4038
|
}
|
|
3921
|
-
logger$
|
|
4039
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
3922
4040
|
}
|
|
3923
4041
|
/**
|
|
3924
4042
|
* Print DNS records in a simple format for manual setup
|
|
3925
4043
|
*/
|
|
3926
4044
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
3927
|
-
logger$
|
|
3928
|
-
logger$
|
|
3929
|
-
for (const record of records) logger$
|
|
3930
|
-
logger$
|
|
4045
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
4046
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4047
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4048
|
+
logger$7.log("");
|
|
3931
4049
|
}
|
|
3932
4050
|
/**
|
|
3933
4051
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -3939,7 +4057,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3939
4057
|
provider = await createDnsProvider({ config: providerConfig });
|
|
3940
4058
|
} catch (error) {
|
|
3941
4059
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3942
|
-
logger$
|
|
4060
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
3943
4061
|
return records.map((r) => ({
|
|
3944
4062
|
...r,
|
|
3945
4063
|
error: message
|
|
@@ -3993,7 +4111,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3993
4111
|
}
|
|
3994
4112
|
} catch (error) {
|
|
3995
4113
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3996
|
-
logger$
|
|
4114
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
3997
4115
|
return records.map((r) => ({
|
|
3998
4116
|
hostname: r.hostname,
|
|
3999
4117
|
subdomain: r.subdomain,
|
|
@@ -4020,20 +4138,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
4020
4138
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
4021
4139
|
if (!dnsConfig) return null;
|
|
4022
4140
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
4023
|
-
logger$
|
|
4141
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
4024
4142
|
let serverIp;
|
|
4025
4143
|
try {
|
|
4026
4144
|
const endpointUrl = new URL(dokployEndpoint);
|
|
4027
4145
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
4028
|
-
logger$
|
|
4146
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
4029
4147
|
} catch (error) {
|
|
4030
4148
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4031
|
-
logger$
|
|
4149
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
4032
4150
|
return null;
|
|
4033
4151
|
}
|
|
4034
4152
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
4035
4153
|
if (groupedHostnames.size === 0) {
|
|
4036
|
-
logger$
|
|
4154
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
4037
4155
|
return {
|
|
4038
4156
|
records: [],
|
|
4039
4157
|
success: true,
|
|
@@ -4045,22 +4163,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4045
4163
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
4046
4164
|
const providerConfig = normalizedConfig[rootDomain];
|
|
4047
4165
|
if (!providerConfig) {
|
|
4048
|
-
logger$
|
|
4166
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
4049
4167
|
continue;
|
|
4050
4168
|
}
|
|
4051
4169
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
4052
4170
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
4053
4171
|
if (requiredRecords.length === 0) continue;
|
|
4054
|
-
logger$
|
|
4172
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
4055
4173
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
4056
4174
|
allRecords.push(...domainRecords);
|
|
4057
4175
|
const created = domainRecords.filter((r) => r.created).length;
|
|
4058
4176
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
4059
4177
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
4060
|
-
if (created > 0) logger$
|
|
4061
|
-
if (existed > 0) logger$
|
|
4178
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
4179
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
4062
4180
|
if (failed > 0) {
|
|
4063
|
-
logger$
|
|
4181
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
4064
4182
|
hasFailures = true;
|
|
4065
4183
|
}
|
|
4066
4184
|
if (state) {
|
|
@@ -4097,10 +4215,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4097
4215
|
*/
|
|
4098
4216
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
4099
4217
|
const results = [];
|
|
4100
|
-
logger$
|
|
4218
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
4101
4219
|
for (const [appName, hostname] of appHostnames) {
|
|
4102
4220
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
4103
|
-
logger$
|
|
4221
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
4104
4222
|
results.push({
|
|
4105
4223
|
hostname,
|
|
4106
4224
|
appName,
|
|
@@ -4114,7 +4232,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4114
4232
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
4115
4233
|
if (resolvedIp === serverIp) {
|
|
4116
4234
|
setDnsVerification(state, hostname, serverIp);
|
|
4117
|
-
logger$
|
|
4235
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
4118
4236
|
results.push({
|
|
4119
4237
|
hostname,
|
|
4120
4238
|
appName,
|
|
@@ -4123,7 +4241,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4123
4241
|
expectedIp: serverIp
|
|
4124
4242
|
});
|
|
4125
4243
|
} else {
|
|
4126
|
-
logger$
|
|
4244
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
4127
4245
|
results.push({
|
|
4128
4246
|
hostname,
|
|
4129
4247
|
appName,
|
|
@@ -4134,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4134
4252
|
}
|
|
4135
4253
|
} catch (error) {
|
|
4136
4254
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4137
|
-
logger$
|
|
4255
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
4138
4256
|
results.push({
|
|
4139
4257
|
hostname,
|
|
4140
4258
|
appName,
|
|
@@ -4148,9 +4266,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4148
4266
|
const skipped = results.filter((r) => r.skipped).length;
|
|
4149
4267
|
const pending = results.filter((r) => !r.verified).length;
|
|
4150
4268
|
if (pending > 0) {
|
|
4151
|
-
logger$
|
|
4152
|
-
logger$
|
|
4153
|
-
} else if (skipped > 0) logger$
|
|
4269
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
4270
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
4271
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
4154
4272
|
return results;
|
|
4155
4273
|
}
|
|
4156
4274
|
//#endregion
|
|
@@ -4183,43 +4301,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
|
4183
4301
|
return `${appName}.${baseDomain}`;
|
|
4184
4302
|
}
|
|
4185
4303
|
/**
|
|
4186
|
-
*
|
|
4187
|
-
*
|
|
4188
|
-
* Three rules, in order, and the last one is the point:
|
|
4189
|
-
*
|
|
4190
|
-
* 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
|
|
4191
|
-
* 2. **Named `web`** — the convention wins, because people already rely on it.
|
|
4192
|
-
* 3. **Declared `root: true`** — the site says so itself.
|
|
4193
|
-
*
|
|
4194
|
-
* Anything else throws. It used to take the *first* `type: 'web'` it iterated,
|
|
4195
|
-
* and that object is built from the manifest, which is built in glob traversal
|
|
4196
|
-
* order — so two sites with neither named `web` meant renaming a file could
|
|
4197
|
-
* move the production root domain, silently. The same shape as
|
|
4198
|
-
* `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
|
|
4199
|
-
* error rather than a coin toss.
|
|
4304
|
+
* Whether this app is the site the base domain points at — see `rootSite`,
|
|
4305
|
+
* which the local edge asks too.
|
|
4200
4306
|
*
|
|
4201
4307
|
* @throws {AmbiguousRootSite} when several sites could be the root and none says it is
|
|
4202
4308
|
*/
|
|
4203
4309
|
function isMainFrontendApp(appName, app, allApps) {
|
|
4204
4310
|
if (app.type !== "web") return false;
|
|
4205
|
-
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web")
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
}
|
|
4212
|
-
/** Several sites could hold the base domain, and none of them says it does. */
|
|
4213
|
-
var AmbiguousRootSite = class extends Error {
|
|
4214
|
-
sites;
|
|
4215
|
-
tooMany;
|
|
4216
|
-
constructor(sites, tooMany = false) {
|
|
4217
|
-
super(tooMany ? `More than one site declares \`root: true\` — ${sites.join(", ")}. The base domain points at one of them.` : `${sites.length} sites and nothing says which holds the base domain: ${sites.join(", ")}. Name one of them \`web\`, or declare \`root: true\` on the one the base domain points at. It used to be whichever the glob reached first, which is not a thing to decide a production hostname.`);
|
|
4218
|
-
this.sites = sites;
|
|
4219
|
-
this.tooMany = tooMany;
|
|
4220
|
-
this.name = "AmbiguousRootSite";
|
|
4221
|
-
}
|
|
4222
|
-
};
|
|
4311
|
+
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
|
|
4312
|
+
name,
|
|
4313
|
+
root: a.root
|
|
4314
|
+
}));
|
|
4315
|
+
return rootSite(sites) === appName;
|
|
4316
|
+
}
|
|
4223
4317
|
/**
|
|
4224
4318
|
* Get public URL arg names from app dependencies.
|
|
4225
4319
|
*
|
|
@@ -5082,7 +5176,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5082
5176
|
}
|
|
5083
5177
|
//#endregion
|
|
5084
5178
|
//#region src/docker/index.ts
|
|
5085
|
-
const logger$
|
|
5179
|
+
const logger$6 = console;
|
|
5086
5180
|
/**
|
|
5087
5181
|
* Docker command implementation
|
|
5088
5182
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5093,7 +5187,7 @@ const logger$7 = console;
|
|
|
5093
5187
|
async function dockerCommand(options) {
|
|
5094
5188
|
const loadedConfig = await loadWorkspaceConfig();
|
|
5095
5189
|
if (loadedConfig.type === "workspace") {
|
|
5096
|
-
logger$
|
|
5190
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
5097
5191
|
return workspaceDockerCommand(loadedConfig.workspace);
|
|
5098
5192
|
}
|
|
5099
5193
|
const config = await loadConfig();
|
|
@@ -5113,7 +5207,7 @@ async function dockerCommand(options) {
|
|
|
5113
5207
|
if (inMonorepo && !useSlim) {
|
|
5114
5208
|
if (hasTurbo) {
|
|
5115
5209
|
useTurbo = true;
|
|
5116
|
-
logger$
|
|
5210
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
5117
5211
|
} else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
|
|
5118
5212
|
}
|
|
5119
5213
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
@@ -5121,7 +5215,7 @@ async function dockerCommand(options) {
|
|
|
5121
5215
|
const pkg = __require(`${process.cwd()}/package.json`);
|
|
5122
5216
|
if (pkg.name) {
|
|
5123
5217
|
turboPackage = pkg.name;
|
|
5124
|
-
logger$
|
|
5218
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
5125
5219
|
}
|
|
5126
5220
|
} catch {}
|
|
5127
5221
|
const templateOptions = {
|
|
@@ -5138,16 +5232,16 @@ async function dockerCommand(options) {
|
|
|
5138
5232
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
5139
5233
|
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
5140
5234
|
await writeFile(dockerfilePath, dockerfile);
|
|
5141
|
-
logger$
|
|
5235
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
5142
5236
|
const composePath = await writeConstructsCompose(loadedConfig.workspace);
|
|
5143
5237
|
const dockerignore = generateDockerignore();
|
|
5144
5238
|
const dockerignorePath = join(process.cwd(), ".dockerignore");
|
|
5145
5239
|
await writeFile(dockerignorePath, dockerignore);
|
|
5146
|
-
logger$
|
|
5240
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
5147
5241
|
const entrypoint = generateDockerEntrypoint();
|
|
5148
5242
|
const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
|
|
5149
5243
|
await writeFile(entrypointPath, entrypoint);
|
|
5150
|
-
logger$
|
|
5244
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
5151
5245
|
const result = {
|
|
5152
5246
|
dockerfile: dockerfilePath,
|
|
5153
5247
|
dockerCompose: composePath,
|
|
@@ -5166,13 +5260,13 @@ async function dockerCommand(options) {
|
|
|
5166
5260
|
function ensureLockfile(cwd) {
|
|
5167
5261
|
const lockfilePath = findLockfilePath(cwd);
|
|
5168
5262
|
if (!lockfilePath) {
|
|
5169
|
-
logger$
|
|
5263
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
5170
5264
|
return null;
|
|
5171
5265
|
}
|
|
5172
5266
|
const lockfileName = basename(lockfilePath);
|
|
5173
5267
|
const localLockfile = join(cwd, lockfileName);
|
|
5174
5268
|
if (lockfilePath === localLockfile) return null;
|
|
5175
|
-
logger$
|
|
5269
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
5176
5270
|
copyFileSync(lockfilePath, localLockfile);
|
|
5177
5271
|
return () => {
|
|
5178
5272
|
try {
|
|
@@ -5188,7 +5282,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5188
5282
|
const tag = options.tag ?? "latest";
|
|
5189
5283
|
const registry = options.registry;
|
|
5190
5284
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
5191
|
-
logger$
|
|
5285
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5192
5286
|
const cwd = process.cwd();
|
|
5193
5287
|
const cleanup = ensureLockfile(cwd);
|
|
5194
5288
|
try {
|
|
@@ -5200,7 +5294,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5200
5294
|
DOCKER_BUILDKIT: "1"
|
|
5201
5295
|
}
|
|
5202
5296
|
});
|
|
5203
|
-
logger$
|
|
5297
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5204
5298
|
} catch (error) {
|
|
5205
5299
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5206
5300
|
} finally {
|
|
@@ -5215,13 +5309,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
5215
5309
|
const registry = options.registry;
|
|
5216
5310
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
5217
5311
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
5218
|
-
logger$
|
|
5312
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5219
5313
|
try {
|
|
5220
5314
|
execSync(`docker push ${fullImageName}`, {
|
|
5221
5315
|
cwd: process.cwd(),
|
|
5222
5316
|
stdio: "inherit"
|
|
5223
5317
|
});
|
|
5224
|
-
logger$
|
|
5318
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5225
5319
|
} catch (error) {
|
|
5226
5320
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5227
5321
|
}
|
|
@@ -5246,21 +5340,21 @@ function getAppPackageName(appPath) {
|
|
|
5246
5340
|
async function workspaceDockerCommand(workspace) {
|
|
5247
5341
|
const results = [];
|
|
5248
5342
|
const apps = Object.entries(workspace.apps);
|
|
5249
|
-
logger$
|
|
5343
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
5250
5344
|
const dockerDir = join(workspace.root, ".gkm", "docker");
|
|
5251
5345
|
await mkdir(dockerDir, { recursive: true });
|
|
5252
5346
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
5253
|
-
logger$
|
|
5347
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
5254
5348
|
for (const [appName, app] of apps) {
|
|
5255
5349
|
if (app.type === "mobile") {
|
|
5256
|
-
logger$
|
|
5350
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
5257
5351
|
continue;
|
|
5258
5352
|
}
|
|
5259
5353
|
const appPath = app.path;
|
|
5260
5354
|
const turboPackage = getAppPackageName(join(workspace.root, appPath)) ?? appName;
|
|
5261
5355
|
const imageName = appName;
|
|
5262
5356
|
const buildType = !!app.entry ? "entry" : app.type;
|
|
5263
|
-
logger$
|
|
5357
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
5264
5358
|
let dockerfile;
|
|
5265
5359
|
if (app.type === "web") {
|
|
5266
5360
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -5304,7 +5398,7 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5304
5398
|
});
|
|
5305
5399
|
const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
|
|
5306
5400
|
await writeFile(dockerfilePath, dockerfile);
|
|
5307
|
-
logger$
|
|
5401
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
5308
5402
|
results.push({
|
|
5309
5403
|
appName,
|
|
5310
5404
|
type: app.type,
|
|
@@ -5315,13 +5409,13 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5315
5409
|
const dockerignore = generateDockerignore();
|
|
5316
5410
|
const dockerignorePath = join(workspace.root, ".dockerignore");
|
|
5317
5411
|
await writeFile(dockerignorePath, dockerignore);
|
|
5318
|
-
logger$
|
|
5412
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
5319
5413
|
const composePath = await writeConstructsCompose(workspace);
|
|
5320
|
-
logger$
|
|
5321
|
-
logger$
|
|
5414
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`);
|
|
5415
|
+
logger$6.log("\n📋 Build commands:");
|
|
5322
5416
|
for (const result of results) {
|
|
5323
5417
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
5324
|
-
logger$
|
|
5418
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
5325
5419
|
}
|
|
5326
5420
|
printRunInstructions(workspace);
|
|
5327
5421
|
return {
|
|
@@ -5342,18 +5436,18 @@ async function writeConstructsCompose(workspace) {
|
|
|
5342
5436
|
stage: workspace.stages.local,
|
|
5343
5437
|
start: false
|
|
5344
5438
|
});
|
|
5345
|
-
logger$
|
|
5439
|
+
logger$6.log(` Generated: ${COMPOSE_PATH} (project root)`);
|
|
5346
5440
|
return join(workspace.root, COMPOSE_PATH);
|
|
5347
5441
|
}
|
|
5348
5442
|
function printRunInstructions(workspace) {
|
|
5349
5443
|
const project = existsSync(join(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
|
|
5350
|
-
logger$
|
|
5351
|
-
logger$
|
|
5352
|
-
logger$
|
|
5444
|
+
logger$6.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
|
|
5445
|
+
logger$6.log(" gkm setup # starts the containers and creates what they hold");
|
|
5446
|
+
logger$6.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
|
|
5353
5447
|
}
|
|
5354
5448
|
//#endregion
|
|
5355
5449
|
//#region src/deploy/docker.ts
|
|
5356
|
-
const logger$
|
|
5450
|
+
const logger$5 = console;
|
|
5357
5451
|
/**
|
|
5358
5452
|
* Get the full image reference
|
|
5359
5453
|
*/
|
|
@@ -5368,9 +5462,9 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5368
5462
|
* @param buildArgs - Build arguments to pass to docker build
|
|
5369
5463
|
*/
|
|
5370
5464
|
async function buildImage(imageRef, _appName, buildArgs) {
|
|
5371
|
-
logger$
|
|
5465
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5372
5466
|
const cwd = process.cwd();
|
|
5373
|
-
logger$
|
|
5467
|
+
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5374
5468
|
await dockerCommand({ slim: true });
|
|
5375
5469
|
const dockerfilePath = join(cwd, ".gkm", "docker", "Dockerfile");
|
|
5376
5470
|
const buildCwd = cwd;
|
|
@@ -5392,7 +5486,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5392
5486
|
DOCKER_BUILDKIT: "1"
|
|
5393
5487
|
}
|
|
5394
5488
|
});
|
|
5395
|
-
logger$
|
|
5489
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5396
5490
|
} catch (error) {
|
|
5397
5491
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5398
5492
|
}
|
|
@@ -5401,13 +5495,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5401
5495
|
* Push Docker image to registry
|
|
5402
5496
|
*/
|
|
5403
5497
|
async function pushImage(imageRef) {
|
|
5404
|
-
logger$
|
|
5498
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5405
5499
|
try {
|
|
5406
5500
|
execSync(`docker push ${imageRef}`, {
|
|
5407
5501
|
cwd: process.cwd(),
|
|
5408
5502
|
stdio: "inherit"
|
|
5409
5503
|
});
|
|
5410
|
-
logger$
|
|
5504
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5411
5505
|
} catch (error) {
|
|
5412
5506
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5413
5507
|
}
|
|
@@ -5421,18 +5515,18 @@ async function deployDocker(options) {
|
|
|
5421
5515
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5422
5516
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
5423
5517
|
if (!skipPush) {
|
|
5424
|
-
if (!config.registry) logger$
|
|
5518
|
+
if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
5425
5519
|
else await pushImage(imageRef);
|
|
5426
5520
|
}
|
|
5427
|
-
logger$
|
|
5428
|
-
logger$
|
|
5429
|
-
logger$
|
|
5430
|
-
logger$
|
|
5521
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
5522
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
5523
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
5524
|
+
logger$5.log(` Stage: ${stage}`);
|
|
5431
5525
|
if (masterKey) {
|
|
5432
|
-
logger$
|
|
5433
|
-
logger$
|
|
5434
|
-
logger$
|
|
5435
|
-
logger$
|
|
5526
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
5527
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
5528
|
+
logger$5.log("\n Example docker run:");
|
|
5529
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
5436
5530
|
}
|
|
5437
5531
|
return {
|
|
5438
5532
|
imageRef,
|
|
@@ -6811,7 +6905,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
6811
6905
|
*
|
|
6812
6906
|
* @module deploy
|
|
6813
6907
|
*/
|
|
6814
|
-
const logger$
|
|
6908
|
+
const logger$4 = console;
|
|
6815
6909
|
/**
|
|
6816
6910
|
* Prompt for input
|
|
6817
6911
|
*/
|
|
@@ -6885,7 +6979,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
6885
6979
|
return;
|
|
6886
6980
|
} catch {
|
|
6887
6981
|
if (i < maxRetries - 1) {
|
|
6888
|
-
logger$
|
|
6982
|
+
logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
6889
6983
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
6890
6984
|
}
|
|
6891
6985
|
}
|
|
@@ -6928,10 +7022,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6928
7022
|
break;
|
|
6929
7023
|
} catch (error) {
|
|
6930
7024
|
if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
|
|
6931
|
-
logger$
|
|
7025
|
+
logger$4.log(` Port ${candidate} is taken; trying another...`);
|
|
6932
7026
|
}
|
|
6933
7027
|
if (externalPort === void 0) throw new Error(`Could not publish a port for ${postgres.appName}. In use on this server: ${[...taken].sort((a, b) => a - b).join(", ") || "none reported"}. The role DDL needs to reach the cluster from here.`);
|
|
6934
|
-
logger$
|
|
7028
|
+
logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
|
|
6935
7029
|
}
|
|
6936
7030
|
await api.deployPostgres(postgres.postgresId);
|
|
6937
7031
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
@@ -6955,7 +7049,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6955
7049
|
const unpublish = async () => {
|
|
6956
7050
|
if (!opened) return;
|
|
6957
7051
|
await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
|
|
6958
|
-
logger$
|
|
7052
|
+
logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
|
|
6959
7053
|
});
|
|
6960
7054
|
};
|
|
6961
7055
|
let lastError;
|
|
@@ -6966,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6966
7060
|
lastError = error;
|
|
6967
7061
|
if (attempt === 3) break;
|
|
6968
7062
|
const message = error instanceof Error ? error.message : String(error);
|
|
6969
|
-
logger$
|
|
7063
|
+
logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
|
|
6970
7064
|
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
6971
7065
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
|
|
6972
7066
|
}
|
|
@@ -7041,91 +7135,91 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7041
7135
|
apps: units
|
|
7042
7136
|
} : configured;
|
|
7043
7137
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
7044
|
-
logger$
|
|
7045
|
-
logger$
|
|
7138
|
+
logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
7139
|
+
logger$4.log(` Stage: ${stage}`);
|
|
7046
7140
|
const imageTag = tag ?? generateTag(stage);
|
|
7047
|
-
logger$
|
|
7141
|
+
logger$4.log(` Tag: ${imageTag}`);
|
|
7048
7142
|
const buildOrder = getAppBuildOrder(workspace);
|
|
7049
7143
|
let appsToDeployNames = buildOrder;
|
|
7050
7144
|
if (selectedApps && selectedApps.length > 0) {
|
|
7051
7145
|
const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
|
|
7052
7146
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
7053
7147
|
appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
|
|
7054
|
-
logger$
|
|
7055
|
-
} else logger$
|
|
7148
|
+
logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
7149
|
+
} else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
7056
7150
|
const dokployApps = appsToDeployNames.filter((name) => {
|
|
7057
7151
|
const target = workspace.apps[name].resolvedDeployTarget;
|
|
7058
7152
|
if (!isDeployTargetSupported(target)) {
|
|
7059
|
-
logger$
|
|
7153
|
+
logger$4.log(` ⚠️ Skipping ${name}: ${getDeployTargetError(target, name)}`);
|
|
7060
7154
|
return false;
|
|
7061
7155
|
}
|
|
7062
7156
|
return true;
|
|
7063
7157
|
});
|
|
7064
7158
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7065
7159
|
appsToDeployNames = dokployApps;
|
|
7066
|
-
logger$
|
|
7160
|
+
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7067
7161
|
const stageSecrets = await readStageSecrets(stage, workspace.root);
|
|
7068
7162
|
if (!stageSecrets) {
|
|
7069
|
-
logger$
|
|
7070
|
-
logger$
|
|
7163
|
+
logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
7164
|
+
logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
7071
7165
|
}
|
|
7072
7166
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7073
7167
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
7074
7168
|
if (stageSecrets) {
|
|
7075
7169
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
7076
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
7077
|
-
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$
|
|
7170
|
+
if (report.appsWithSecrets.length > 0) logger$4.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
7171
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$4.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
7078
7172
|
}
|
|
7079
7173
|
let creds = await getDokployCredentials();
|
|
7080
7174
|
if (!creds) {
|
|
7081
|
-
logger$
|
|
7175
|
+
logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
7082
7176
|
const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
|
|
7083
7177
|
try {
|
|
7084
7178
|
new URL(normalizedEndpoint);
|
|
7085
7179
|
} catch {
|
|
7086
7180
|
throw new Error("Invalid URL format");
|
|
7087
7181
|
}
|
|
7088
|
-
logger$
|
|
7182
|
+
logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
7089
7183
|
const token = await prompt("API Token: ", true);
|
|
7090
|
-
logger$
|
|
7184
|
+
logger$4.log("\nValidating credentials...");
|
|
7091
7185
|
if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
|
|
7092
7186
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
7093
7187
|
creds = {
|
|
7094
7188
|
token,
|
|
7095
7189
|
endpoint: normalizedEndpoint
|
|
7096
7190
|
};
|
|
7097
|
-
logger$
|
|
7191
|
+
logger$4.log("✓ Credentials saved");
|
|
7098
7192
|
}
|
|
7099
7193
|
const api = new DokployApi({
|
|
7100
7194
|
baseUrl: creds.endpoint,
|
|
7101
7195
|
token: creds.token
|
|
7102
7196
|
});
|
|
7103
|
-
logger$
|
|
7197
|
+
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
7104
7198
|
const projectName = workspace.name;
|
|
7105
7199
|
let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7106
7200
|
let environmentId;
|
|
7107
7201
|
if (project) {
|
|
7108
|
-
logger$
|
|
7202
|
+
logger$4.log(` Found existing project: ${project.name}`);
|
|
7109
7203
|
const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
7110
7204
|
if (matchingEnv) {
|
|
7111
7205
|
environmentId = matchingEnv.environmentId;
|
|
7112
|
-
logger$
|
|
7206
|
+
logger$4.log(` Using environment: ${matchingEnv.name}`);
|
|
7113
7207
|
} else {
|
|
7114
|
-
logger$
|
|
7208
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7115
7209
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7116
|
-
logger$
|
|
7210
|
+
logger$4.log(` ✓ Created environment: ${stage}`);
|
|
7117
7211
|
}
|
|
7118
7212
|
} else {
|
|
7119
|
-
logger$
|
|
7213
|
+
logger$4.log(` Creating project: ${projectName}`);
|
|
7120
7214
|
const result = await api.createProject(projectName);
|
|
7121
7215
|
project = result.project;
|
|
7122
7216
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
7123
|
-
logger$
|
|
7217
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7124
7218
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7125
7219
|
} else environmentId = result.environment.environmentId;
|
|
7126
|
-
logger$
|
|
7220
|
+
logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
7127
7221
|
}
|
|
7128
|
-
logger$
|
|
7222
|
+
logger$4.log("\n📋 Loading deploy state...");
|
|
7129
7223
|
const stateProvider = await createStateProvider({
|
|
7130
7224
|
config: workspace.state,
|
|
7131
7225
|
workspaceRoot: workspace.root,
|
|
@@ -7133,27 +7227,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7133
7227
|
});
|
|
7134
7228
|
let state = await stateProvider.read(stage);
|
|
7135
7229
|
if (state) {
|
|
7136
|
-
logger$
|
|
7230
|
+
logger$4.log(` Found existing state for stage "${stage}"`);
|
|
7137
7231
|
if (state.projectId !== project.projectId) {
|
|
7138
|
-
logger$
|
|
7232
|
+
logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
7139
7233
|
state.projectId = project.projectId;
|
|
7140
7234
|
}
|
|
7141
7235
|
if (state.environmentId !== environmentId) {
|
|
7142
|
-
logger$
|
|
7236
|
+
logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
7143
7237
|
state.environmentId = environmentId;
|
|
7144
7238
|
}
|
|
7145
7239
|
} else {
|
|
7146
|
-
logger$
|
|
7240
|
+
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
7147
7241
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
7148
7242
|
}
|
|
7149
|
-
logger$
|
|
7243
|
+
logger$4.log("\n🐳 Checking registry...");
|
|
7150
7244
|
let registryId = await getDokployRegistryId();
|
|
7151
7245
|
const registry = workspace.deploy.dokploy?.registry;
|
|
7152
7246
|
if (registryId) try {
|
|
7153
7247
|
const reg = await api.getRegistry(registryId);
|
|
7154
|
-
logger$
|
|
7248
|
+
logger$4.log(` Using registry: ${reg.registryName}`);
|
|
7155
7249
|
} catch {
|
|
7156
|
-
logger$
|
|
7250
|
+
logger$4.log(" ⚠ Stored registry not found, clearing...");
|
|
7157
7251
|
registryId = void 0;
|
|
7158
7252
|
await storeDokployRegistryId("");
|
|
7159
7253
|
}
|
|
@@ -7162,26 +7256,26 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7162
7256
|
if (registries.length > 0) {
|
|
7163
7257
|
registryId = registries[0].registryId;
|
|
7164
7258
|
await storeDokployRegistryId(registryId);
|
|
7165
|
-
logger$
|
|
7259
|
+
logger$4.log(` Using registry: ${registries[0].registryName}`);
|
|
7166
7260
|
} else if (registry) {
|
|
7167
|
-
logger$
|
|
7168
|
-
logger$
|
|
7261
|
+
logger$4.log(" No registries found in Dokploy. Let's create one.");
|
|
7262
|
+
logger$4.log(` Registry URL: ${registry}`);
|
|
7169
7263
|
const username = await prompt("Registry username: ");
|
|
7170
7264
|
const password = await prompt("Registry password/token: ", true);
|
|
7171
7265
|
registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
|
|
7172
7266
|
await storeDokployRegistryId(registryId);
|
|
7173
|
-
logger$
|
|
7174
|
-
} else logger$
|
|
7267
|
+
logger$4.log(` ✓ Registry created: ${registryId}`);
|
|
7268
|
+
} else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
7175
7269
|
}
|
|
7176
7270
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
7177
|
-
if (skippedMobileApps.length > 0) logger$
|
|
7271
|
+
if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
7178
7272
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
7179
7273
|
const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
|
|
7180
7274
|
const dokployConfig = workspace.deploy.dokploy;
|
|
7181
7275
|
let declaredEnv = {};
|
|
7182
7276
|
let declaredClusters = {};
|
|
7183
7277
|
{
|
|
7184
|
-
logger$
|
|
7278
|
+
logger$4.log("\n📦 Provisioning declared constructs...");
|
|
7185
7279
|
const appUrls = {};
|
|
7186
7280
|
for (const appName of backendApps) {
|
|
7187
7281
|
const app = workspace.apps[appName];
|
|
@@ -7199,20 +7293,20 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7199
7293
|
});
|
|
7200
7294
|
declaredEnv = declared.env;
|
|
7201
7295
|
declaredClusters = declared.clusters;
|
|
7202
|
-
if (Object.keys(declaredEnv).length > 0) logger$
|
|
7296
|
+
if (Object.keys(declaredEnv).length > 0) logger$4.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
|
|
7203
7297
|
if (declared.statements.length > 0) {
|
|
7204
7298
|
const serverHostname = getServerHostname(creds.endpoint);
|
|
7205
7299
|
for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
|
|
7206
7300
|
const statements = declared.statements.filter((statement) => statement.database === databaseName);
|
|
7207
7301
|
if (statements.length === 0) continue;
|
|
7208
7302
|
const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
|
|
7209
|
-
logger$
|
|
7303
|
+
logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
|
|
7210
7304
|
}
|
|
7211
7305
|
}
|
|
7212
7306
|
}
|
|
7213
7307
|
const backupCluster = Object.values(declaredClusters)[0];
|
|
7214
7308
|
if (workspace.deploy?.backups && backupCluster) {
|
|
7215
|
-
logger$
|
|
7309
|
+
logger$4.log("\n💾 Provisioning backup destination...");
|
|
7216
7310
|
const { provisionBackupDestination } = await import("./backup-provisioner-DIVCbfee.mjs");
|
|
7217
7311
|
const backupState = await provisionBackupDestination({
|
|
7218
7312
|
api,
|
|
@@ -7221,13 +7315,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7221
7315
|
stage,
|
|
7222
7316
|
config: workspace.deploy.backups,
|
|
7223
7317
|
existingState: getBackupState(state),
|
|
7224
|
-
logger: logger$
|
|
7318
|
+
logger: logger$4
|
|
7225
7319
|
});
|
|
7226
7320
|
setBackupState(state, backupState);
|
|
7227
7321
|
if (!backupState.postgresBackupId) {
|
|
7228
7322
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
7229
7323
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
7230
|
-
logger$
|
|
7324
|
+
logger$4.log(" Creating postgres backup schedule...");
|
|
7231
7325
|
const backup = await api.createPostgresBackup({
|
|
7232
7326
|
schedule: backupSchedule,
|
|
7233
7327
|
prefix: `${stage}/postgres`,
|
|
@@ -7238,8 +7332,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7238
7332
|
keepLatestCount: backupRetention
|
|
7239
7333
|
});
|
|
7240
7334
|
setPostgresBackupId(state, backup.backupId);
|
|
7241
|
-
logger$
|
|
7242
|
-
} else logger$
|
|
7335
|
+
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
7336
|
+
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
7243
7337
|
}
|
|
7244
7338
|
const publicUrls = {};
|
|
7245
7339
|
const results = [];
|
|
@@ -7252,25 +7346,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7252
7346
|
frontendUrls.push(`https://${hostname}`);
|
|
7253
7347
|
}
|
|
7254
7348
|
if (backendApps.length > 0) {
|
|
7255
|
-
logger$
|
|
7349
|
+
logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
7256
7350
|
for (const appName of backendApps) {
|
|
7257
7351
|
const app = workspace.apps[appName];
|
|
7258
|
-
logger$
|
|
7352
|
+
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
7259
7353
|
try {
|
|
7260
7354
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7261
7355
|
let application = null;
|
|
7262
7356
|
const cachedAppId = getApplicationId(state, appName);
|
|
7263
7357
|
if (cachedAppId) {
|
|
7264
|
-
logger$
|
|
7358
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7265
7359
|
application = await api.getApplication(cachedAppId);
|
|
7266
|
-
if (application) logger$
|
|
7267
|
-
else logger$
|
|
7360
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7361
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7268
7362
|
}
|
|
7269
7363
|
if (!application) {
|
|
7270
7364
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7271
7365
|
application = result.application;
|
|
7272
|
-
if (result.created) logger$
|
|
7273
|
-
else logger$
|
|
7366
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7367
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7274
7368
|
}
|
|
7275
7369
|
setApplicationId(state, appName, application.applicationId);
|
|
7276
7370
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -7278,11 +7372,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7278
7372
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
7279
7373
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
7280
7374
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
7281
|
-
logger$
|
|
7375
|
+
logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7282
7376
|
}
|
|
7283
7377
|
const imageName = `${workspace.name}-${appName}`;
|
|
7284
7378
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7285
|
-
logger$
|
|
7379
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7286
7380
|
await deployDocker({
|
|
7287
7381
|
stage,
|
|
7288
7382
|
tag: imageTag,
|
|
@@ -7323,17 +7417,17 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7323
7417
|
...declaredEnv
|
|
7324
7418
|
};
|
|
7325
7419
|
const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
|
|
7326
|
-
if (Object.keys(withDeclared).length > 0) logger$
|
|
7420
|
+
if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
|
|
7327
7421
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7328
7422
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7329
|
-
logger$
|
|
7423
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7330
7424
|
await api.deployApplication(application.applicationId);
|
|
7331
7425
|
const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
|
|
7332
7426
|
if (existingDomain) {
|
|
7333
7427
|
appHostnames.set(appName, backendHost);
|
|
7334
7428
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
7335
7429
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7336
|
-
logger$
|
|
7430
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
7337
7431
|
} else try {
|
|
7338
7432
|
const domain = await api.createDomain({
|
|
7339
7433
|
host: backendHost,
|
|
@@ -7345,10 +7439,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7345
7439
|
appHostnames.set(appName, backendHost);
|
|
7346
7440
|
appDomainIds.set(appName, domain.domainId);
|
|
7347
7441
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7348
|
-
logger$
|
|
7442
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
7349
7443
|
} catch (domainError) {
|
|
7350
7444
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7351
|
-
logger$
|
|
7445
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7352
7446
|
appHostnames.set(appName, backendHost);
|
|
7353
7447
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7354
7448
|
}
|
|
@@ -7359,10 +7453,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7359
7453
|
applicationId: application.applicationId,
|
|
7360
7454
|
imageRef
|
|
7361
7455
|
});
|
|
7362
|
-
logger$
|
|
7456
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7363
7457
|
} catch (error) {
|
|
7364
7458
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7365
|
-
logger$
|
|
7459
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7366
7460
|
results.push({
|
|
7367
7461
|
appName,
|
|
7368
7462
|
type: app.type,
|
|
@@ -7374,25 +7468,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7374
7468
|
}
|
|
7375
7469
|
}
|
|
7376
7470
|
if (frontendApps.length > 0) {
|
|
7377
|
-
logger$
|
|
7471
|
+
logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
7378
7472
|
for (const appName of frontendApps) {
|
|
7379
7473
|
const app = workspace.apps[appName];
|
|
7380
|
-
logger$
|
|
7474
|
+
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
7381
7475
|
try {
|
|
7382
7476
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7383
7477
|
let application = null;
|
|
7384
7478
|
const cachedAppId = getApplicationId(state, appName);
|
|
7385
7479
|
if (cachedAppId) {
|
|
7386
|
-
logger$
|
|
7480
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7387
7481
|
application = await api.getApplication(cachedAppId);
|
|
7388
|
-
if (application) logger$
|
|
7389
|
-
else logger$
|
|
7482
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7483
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7390
7484
|
}
|
|
7391
7485
|
if (!application) {
|
|
7392
7486
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7393
7487
|
application = result.application;
|
|
7394
|
-
if (result.created) logger$
|
|
7395
|
-
else logger$
|
|
7488
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7489
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7396
7490
|
}
|
|
7397
7491
|
setApplicationId(state, appName, application.applicationId);
|
|
7398
7492
|
const dependencyUrls = {};
|
|
@@ -7412,7 +7506,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7412
7506
|
};
|
|
7413
7507
|
const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
|
|
7414
7508
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
7415
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
7509
|
+
if (Object.keys(resolved).length > 0) logger$4.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
7416
7510
|
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
7417
7511
|
const buildArgs = [];
|
|
7418
7512
|
const publicUrlArgNames = [];
|
|
@@ -7422,10 +7516,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7422
7516
|
publicUrlArgNames.push(key);
|
|
7423
7517
|
}
|
|
7424
7518
|
}
|
|
7425
|
-
if (buildArgs.length > 0) logger$
|
|
7519
|
+
if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
7426
7520
|
const imageName = `${workspace.name}-${appName}`;
|
|
7427
7521
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7428
|
-
logger$
|
|
7522
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7429
7523
|
await deployDocker({
|
|
7430
7524
|
stage,
|
|
7431
7525
|
tag: imageTag,
|
|
@@ -7446,14 +7540,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7446
7540
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
7447
7541
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7448
7542
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7449
|
-
logger$
|
|
7543
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7450
7544
|
await api.deployApplication(application.applicationId);
|
|
7451
7545
|
const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
|
|
7452
7546
|
if (existingFrontendDomain) {
|
|
7453
7547
|
appHostnames.set(appName, frontendHost);
|
|
7454
7548
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
7455
7549
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7456
|
-
logger$
|
|
7550
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
7457
7551
|
} else try {
|
|
7458
7552
|
const domain = await api.createDomain({
|
|
7459
7553
|
host: frontendHost,
|
|
@@ -7465,10 +7559,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7465
7559
|
appHostnames.set(appName, frontendHost);
|
|
7466
7560
|
appDomainIds.set(appName, domain.domainId);
|
|
7467
7561
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7468
|
-
logger$
|
|
7562
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
7469
7563
|
} catch (domainError) {
|
|
7470
7564
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7471
|
-
logger$
|
|
7565
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7472
7566
|
appHostnames.set(appName, frontendHost);
|
|
7473
7567
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7474
7568
|
}
|
|
@@ -7479,10 +7573,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7479
7573
|
applicationId: application.applicationId,
|
|
7480
7574
|
imageRef
|
|
7481
7575
|
});
|
|
7482
|
-
logger$
|
|
7576
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7483
7577
|
} catch (error) {
|
|
7484
7578
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7485
|
-
logger$
|
|
7579
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7486
7580
|
results.push({
|
|
7487
7581
|
appName,
|
|
7488
7582
|
type: app.type,
|
|
@@ -7492,9 +7586,9 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7492
7586
|
}
|
|
7493
7587
|
}
|
|
7494
7588
|
}
|
|
7495
|
-
logger$
|
|
7589
|
+
logger$4.log("\n📋 Saving deploy state...");
|
|
7496
7590
|
await stateProvider.write(stage, state);
|
|
7497
|
-
logger$
|
|
7591
|
+
logger$4.log(" ✓ State saved");
|
|
7498
7592
|
const dnsConfig = workspace.deploy.dns;
|
|
7499
7593
|
if (dnsConfig && appHostnames.size > 0) {
|
|
7500
7594
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -7503,27 +7597,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7503
7597
|
await stateProvider.write(stage, state);
|
|
7504
7598
|
}
|
|
7505
7599
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
7506
|
-
logger$
|
|
7600
|
+
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
7507
7601
|
for (const [appName, hostname] of appHostnames) try {
|
|
7508
7602
|
const result = await api.validateDomain(hostname);
|
|
7509
|
-
if (result.isValid) logger$
|
|
7510
|
-
else logger$
|
|
7603
|
+
if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
7604
|
+
else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
7511
7605
|
} catch (validationError) {
|
|
7512
7606
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
7513
|
-
logger$
|
|
7607
|
+
logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
7514
7608
|
}
|
|
7515
7609
|
}
|
|
7516
7610
|
}
|
|
7517
7611
|
const successCount = results.filter((r) => r.success).length;
|
|
7518
7612
|
const failedCount = results.filter((r) => !r.success).length;
|
|
7519
|
-
logger$
|
|
7520
|
-
logger$
|
|
7521
|
-
logger$
|
|
7522
|
-
logger$
|
|
7523
|
-
if (failedCount > 0) logger$
|
|
7613
|
+
logger$4.log(`\n${"─".repeat(50)}`);
|
|
7614
|
+
logger$4.log(`\n✅ Workspace deployment complete!`);
|
|
7615
|
+
logger$4.log(` Project: ${project.projectId}`);
|
|
7616
|
+
logger$4.log(` Successful: ${successCount}`);
|
|
7617
|
+
if (failedCount > 0) logger$4.log(` Failed: ${failedCount}`);
|
|
7524
7618
|
if (Object.keys(publicUrls).length > 0) {
|
|
7525
|
-
logger$
|
|
7526
|
-
for (const [name, url] of Object.entries(publicUrls)) logger$
|
|
7619
|
+
logger$4.log("\n 📡 Deployed URLs:");
|
|
7620
|
+
for (const [name, url] of Object.entries(publicUrls)) logger$4.log(` ${name}: ${url}`);
|
|
7527
7621
|
}
|
|
7528
7622
|
return {
|
|
7529
7623
|
apps: results,
|
|
@@ -7542,7 +7636,7 @@ async function deployCommand(options) {
|
|
|
7542
7636
|
}
|
|
7543
7637
|
//#endregion
|
|
7544
7638
|
//#region src/deploy/init.ts
|
|
7545
|
-
const logger$
|
|
7639
|
+
const logger$3 = console;
|
|
7546
7640
|
/**
|
|
7547
7641
|
* Get the Dokploy API token from stored credentials or environment
|
|
7548
7642
|
*/
|
|
@@ -7588,28 +7682,28 @@ function unsafeToRewrite(content) {
|
|
|
7588
7682
|
async function updateConfig(config, cwd = process.cwd()) {
|
|
7589
7683
|
const configPath = join(cwd, "gkm.config.ts");
|
|
7590
7684
|
if (!existsSync(configPath)) {
|
|
7591
|
-
logger$
|
|
7592
|
-
logger$
|
|
7593
|
-
logger$
|
|
7594
|
-
logger$
|
|
7595
|
-
logger$
|
|
7596
|
-
logger$
|
|
7597
|
-
logger$
|
|
7598
|
-
logger$
|
|
7685
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
7686
|
+
logger$3.log(` providers: {`);
|
|
7687
|
+
logger$3.log(` dokploy: {`);
|
|
7688
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
7689
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
7690
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
7691
|
+
logger$3.log(` },`);
|
|
7692
|
+
logger$3.log(` },`);
|
|
7599
7693
|
return;
|
|
7600
7694
|
}
|
|
7601
7695
|
const content = await readFile(configPath, "utf-8");
|
|
7602
7696
|
if (unsafeToRewrite(content)) {
|
|
7603
|
-
logger$
|
|
7604
|
-
logger$
|
|
7605
|
-
logger$
|
|
7606
|
-
logger$
|
|
7607
|
-
if (config.registryId) logger$
|
|
7697
|
+
logger$3.warn("\n gkm.config.ts holds a dokploy block this cannot safely rewrite —\n it contains an expression or a nested object. Leaving it alone.");
|
|
7698
|
+
logger$3.log(`\n Discovered, if you want them written down:`);
|
|
7699
|
+
logger$3.log(` projectId: '${config.projectId}'`);
|
|
7700
|
+
logger$3.log(` applicationId: '${config.applicationId}'`);
|
|
7701
|
+
if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
|
|
7608
7702
|
return;
|
|
7609
7703
|
}
|
|
7610
7704
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
7611
|
-
logger$
|
|
7612
|
-
logger$
|
|
7705
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
7706
|
+
logger$3.log(" Updating with new values...");
|
|
7613
7707
|
}
|
|
7614
7708
|
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
7615
7709
|
const dokployConfigStr = `dokploy: {
|
|
@@ -7627,7 +7721,7 @@ async function updateConfig(config, cwd = process.cwd()) {
|
|
|
7627
7721
|
},
|
|
7628
7722
|
});`);
|
|
7629
7723
|
await writeFile(configPath, newContent);
|
|
7630
|
-
logger$
|
|
7724
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
7631
7725
|
}
|
|
7632
7726
|
/**
|
|
7633
7727
|
* Initialize Dokploy deployment configuration
|
|
@@ -7636,22 +7730,22 @@ async function deployInitCommand(options) {
|
|
|
7636
7730
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
7637
7731
|
const endpoint = await getEndpoint(options.endpoint);
|
|
7638
7732
|
const api = await createApi(endpoint);
|
|
7639
|
-
logger$
|
|
7640
|
-
logger$
|
|
7733
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
7734
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
7641
7735
|
let projectId;
|
|
7642
7736
|
if (existingProjectId) {
|
|
7643
7737
|
projectId = existingProjectId;
|
|
7644
|
-
logger$
|
|
7738
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
7645
7739
|
} else {
|
|
7646
|
-
logger$
|
|
7740
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
7647
7741
|
const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7648
7742
|
if (existingProject) {
|
|
7649
7743
|
projectId = existingProject.projectId;
|
|
7650
|
-
logger$
|
|
7744
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
7651
7745
|
} else {
|
|
7652
|
-
logger$
|
|
7746
|
+
logger$3.log(` Creating new project...`);
|
|
7653
7747
|
projectId = (await api.createProject(projectName)).project.projectId;
|
|
7654
|
-
logger$
|
|
7748
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
7655
7749
|
}
|
|
7656
7750
|
}
|
|
7657
7751
|
const project = await api.getProject(projectId);
|
|
@@ -7659,22 +7753,22 @@ async function deployInitCommand(options) {
|
|
|
7659
7753
|
const firstEnv = project.environments?.[0];
|
|
7660
7754
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
7661
7755
|
else {
|
|
7662
|
-
logger$
|
|
7756
|
+
logger$3.log(` Creating production environment...`);
|
|
7663
7757
|
environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
|
|
7664
7758
|
}
|
|
7665
|
-
logger$
|
|
7759
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
7666
7760
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
7667
|
-
logger$
|
|
7761
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
7668
7762
|
if (registryId) {
|
|
7669
|
-
logger$
|
|
7763
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
7670
7764
|
await api.updateApplication(application.applicationId, { registryId });
|
|
7671
|
-
logger$
|
|
7765
|
+
logger$3.log(` ✓ Registry configured`);
|
|
7672
7766
|
} else try {
|
|
7673
7767
|
const registries = await api.listRegistries();
|
|
7674
7768
|
if (registries.length > 0) {
|
|
7675
|
-
logger$
|
|
7676
|
-
for (const reg of registries) logger$
|
|
7677
|
-
logger$
|
|
7769
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
7770
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
7771
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
7678
7772
|
}
|
|
7679
7773
|
} catch {}
|
|
7680
7774
|
const config = {
|
|
@@ -7683,14 +7777,14 @@ async function deployInitCommand(options) {
|
|
|
7683
7777
|
applicationId: application.applicationId
|
|
7684
7778
|
};
|
|
7685
7779
|
await updateConfig(config);
|
|
7686
|
-
logger$
|
|
7687
|
-
logger$
|
|
7688
|
-
logger$
|
|
7689
|
-
logger$
|
|
7690
|
-
logger$
|
|
7691
|
-
logger$
|
|
7692
|
-
logger$
|
|
7693
|
-
logger$
|
|
7780
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
7781
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
7782
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
7783
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
7784
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
7785
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
7786
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
7787
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
7694
7788
|
return config;
|
|
7695
7789
|
}
|
|
7696
7790
|
/**
|
|
@@ -7701,31 +7795,31 @@ async function deployListCommand(options) {
|
|
|
7701
7795
|
const api = await createApi(endpoint);
|
|
7702
7796
|
const { resource } = options;
|
|
7703
7797
|
if (resource === "projects") {
|
|
7704
|
-
logger$
|
|
7798
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
7705
7799
|
const projects = await api.listProjects();
|
|
7706
7800
|
if (projects.length === 0) {
|
|
7707
|
-
logger$
|
|
7801
|
+
logger$3.log(" No projects found");
|
|
7708
7802
|
return;
|
|
7709
7803
|
}
|
|
7710
7804
|
for (const project of projects) {
|
|
7711
|
-
logger$
|
|
7712
|
-
if (project.description) logger$
|
|
7805
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
7806
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
7713
7807
|
}
|
|
7714
7808
|
} else if (resource === "registries") {
|
|
7715
|
-
logger$
|
|
7809
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
7716
7810
|
const registries = await api.listRegistries();
|
|
7717
7811
|
if (registries.length === 0) {
|
|
7718
|
-
logger$
|
|
7719
|
-
logger$
|
|
7812
|
+
logger$3.log(" No registries configured");
|
|
7813
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
7720
7814
|
return;
|
|
7721
7815
|
}
|
|
7722
7816
|
const storedRegistryId = await getDokployRegistryId();
|
|
7723
7817
|
for (const registry of registries) {
|
|
7724
7818
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
7725
|
-
logger$
|
|
7726
|
-
logger$
|
|
7727
|
-
logger$
|
|
7728
|
-
if (registry.imagePrefix) logger$
|
|
7819
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
7820
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
7821
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
7822
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
7729
7823
|
}
|
|
7730
7824
|
}
|
|
7731
7825
|
}
|
|
@@ -8586,26 +8680,26 @@ function loadPackageJson() {
|
|
|
8586
8680
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8587
8681
|
*/
|
|
8588
8682
|
const GEEKMIDAS_VERSIONS = {
|
|
8589
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8590
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8591
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8592
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8593
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8594
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8595
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8596
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8597
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8598
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8599
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8600
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8601
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8602
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8603
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8604
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8605
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8606
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8607
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8608
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8683
|
+
"@geekmidas/audit": "~10.0.0-alpha.28",
|
|
8684
|
+
"@geekmidas/auth": "~10.0.0-alpha.28",
|
|
8685
|
+
"@geekmidas/cache": "~10.0.0-alpha.28",
|
|
8686
|
+
"@geekmidas/client": "~10.0.0-alpha.28",
|
|
8687
|
+
"@geekmidas/cloud": "~10.0.0-alpha.28",
|
|
8688
|
+
"@geekmidas/constructs": "~10.0.0-alpha.28",
|
|
8689
|
+
"@geekmidas/db": "~10.0.0-alpha.28",
|
|
8690
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.28",
|
|
8691
|
+
"@geekmidas/envkit": "~10.0.0-alpha.28",
|
|
8692
|
+
"@geekmidas/errors": "~10.0.0-alpha.28",
|
|
8693
|
+
"@geekmidas/events": "~10.0.0-alpha.28",
|
|
8694
|
+
"@geekmidas/logger": "~10.0.0-alpha.28",
|
|
8695
|
+
"@geekmidas/manifest": "~10.0.0-alpha.28",
|
|
8696
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.28",
|
|
8697
|
+
"@geekmidas/schema": "~10.0.0-alpha.28",
|
|
8698
|
+
"@geekmidas/services": "~10.0.0-alpha.28",
|
|
8699
|
+
"@geekmidas/storage": "~10.0.0-alpha.28",
|
|
8700
|
+
"@geekmidas/studio": "~10.0.0-alpha.28",
|
|
8701
|
+
"@geekmidas/telescope": "~10.0.0-alpha.28",
|
|
8702
|
+
"@geekmidas/testkit": "~10.0.0-alpha.28",
|
|
8609
8703
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8610
8704
|
};
|
|
8611
8705
|
//#endregion
|
|
@@ -14684,7 +14778,7 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14684
14778
|
};
|
|
14685
14779
|
//#endregion
|
|
14686
14780
|
//#region src/secrets/index.ts
|
|
14687
|
-
const logger$
|
|
14781
|
+
const logger$2 = console;
|
|
14688
14782
|
/**
|
|
14689
14783
|
* Initialize secrets for a stage.
|
|
14690
14784
|
* Generates secure random passwords for configured services.
|
|
@@ -14692,7 +14786,7 @@ const logger$3 = console;
|
|
|
14692
14786
|
async function secretsInitCommand(options) {
|
|
14693
14787
|
const { stage, force } = options;
|
|
14694
14788
|
if (!force && secretsExist(stage)) {
|
|
14695
|
-
logger$
|
|
14789
|
+
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14696
14790
|
process.exit(1);
|
|
14697
14791
|
}
|
|
14698
14792
|
let projectName;
|
|
@@ -14702,21 +14796,21 @@ async function secretsInitCommand(options) {
|
|
|
14702
14796
|
projectName = loaded.workspace.name;
|
|
14703
14797
|
if (Object.keys(loaded.workspace.apps).length > 1) {
|
|
14704
14798
|
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
14705
|
-
logger$
|
|
14799
|
+
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
14706
14800
|
}
|
|
14707
14801
|
} catch {}
|
|
14708
14802
|
const secrets = createStageSecrets(stage, [], { projectName });
|
|
14709
14803
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14710
14804
|
await writeStageSecrets(secrets);
|
|
14711
|
-
logger$
|
|
14712
|
-
logger$
|
|
14713
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14714
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14715
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14716
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14717
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
14718
|
-
logger$
|
|
14719
|
-
logger$
|
|
14805
|
+
logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
14806
|
+
logger$2.log(` Location: .gkm/secrets/${stage}.json`);
|
|
14807
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14808
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14809
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14810
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14811
|
+
if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
14812
|
+
logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
14813
|
+
logger$2.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
14720
14814
|
}
|
|
14721
14815
|
/**
|
|
14722
14816
|
* Read all data from stdin.
|
|
@@ -14735,21 +14829,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14735
14829
|
let secretValue = value;
|
|
14736
14830
|
if (!secretValue) {
|
|
14737
14831
|
if (process.stdin.isTTY) {
|
|
14738
|
-
logger$
|
|
14739
|
-
logger$
|
|
14832
|
+
logger$2.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
14833
|
+
logger$2.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
14740
14834
|
process.exit(1);
|
|
14741
14835
|
}
|
|
14742
14836
|
secretValue = await readStdin();
|
|
14743
14837
|
if (!secretValue) {
|
|
14744
|
-
logger$
|
|
14838
|
+
logger$2.error("No value received from stdin");
|
|
14745
14839
|
process.exit(1);
|
|
14746
14840
|
}
|
|
14747
14841
|
}
|
|
14748
14842
|
try {
|
|
14749
14843
|
await setCustomSecret(stage, key, secretValue);
|
|
14750
|
-
logger$
|
|
14844
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
14751
14845
|
} catch (error) {
|
|
14752
|
-
logger$
|
|
14846
|
+
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
14753
14847
|
process.exit(1);
|
|
14754
14848
|
}
|
|
14755
14849
|
}
|
|
@@ -14760,33 +14854,33 @@ async function secretsShowCommand(options) {
|
|
|
14760
14854
|
const { stage, reveal } = options;
|
|
14761
14855
|
const secrets = await readStageSecrets(stage);
|
|
14762
14856
|
if (!secrets) {
|
|
14763
|
-
logger$
|
|
14857
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14764
14858
|
process.exit(1);
|
|
14765
14859
|
}
|
|
14766
|
-
logger$
|
|
14767
|
-
logger$
|
|
14768
|
-
logger$
|
|
14769
|
-
logger$
|
|
14860
|
+
logger$2.log(`\nSecrets for stage "${stage}":`);
|
|
14861
|
+
logger$2.log(` Created: ${secrets.createdAt}`);
|
|
14862
|
+
logger$2.log(` Updated: ${secrets.updatedAt}`);
|
|
14863
|
+
logger$2.log("\nService Credentials:");
|
|
14770
14864
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
14771
|
-
logger$
|
|
14772
|
-
logger$
|
|
14773
|
-
logger$
|
|
14774
|
-
logger$
|
|
14775
|
-
logger$
|
|
14776
|
-
if (creds.database) logger$
|
|
14777
|
-
if (creds.vhost) logger$
|
|
14778
|
-
if (creds.bucket) logger$
|
|
14779
|
-
}
|
|
14780
|
-
logger$
|
|
14781
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14782
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14783
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14784
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14865
|
+
logger$2.log(`\n ${service}:`);
|
|
14866
|
+
logger$2.log(` host: ${creds.host}`);
|
|
14867
|
+
logger$2.log(` port: ${creds.port}`);
|
|
14868
|
+
logger$2.log(` username: ${creds.username}`);
|
|
14869
|
+
logger$2.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
|
|
14870
|
+
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
14871
|
+
if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
|
|
14872
|
+
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
14873
|
+
}
|
|
14874
|
+
logger$2.log("\nConnection URLs:");
|
|
14875
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14876
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14877
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14878
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14785
14879
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14786
|
-
logger$
|
|
14787
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
14880
|
+
logger$2.log("\nCustom Secrets:");
|
|
14881
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
|
|
14788
14882
|
}
|
|
14789
|
-
if (!reveal) logger$
|
|
14883
|
+
if (!reveal) logger$2.log("\nUse --reveal to show actual values");
|
|
14790
14884
|
}
|
|
14791
14885
|
/**
|
|
14792
14886
|
* Rotate passwords for services.
|
|
@@ -14795,25 +14889,25 @@ async function secretsRotateCommand(options) {
|
|
|
14795
14889
|
const { stage, service } = options;
|
|
14796
14890
|
const secrets = await readStageSecrets(stage);
|
|
14797
14891
|
if (!secrets) {
|
|
14798
|
-
logger$
|
|
14892
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14799
14893
|
process.exit(1);
|
|
14800
14894
|
}
|
|
14801
14895
|
if (service) {
|
|
14802
14896
|
if (!secrets.services[service]) {
|
|
14803
|
-
logger$
|
|
14897
|
+
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
14804
14898
|
process.exit(1);
|
|
14805
14899
|
}
|
|
14806
14900
|
const updated = rotateServicePassword(secrets, service);
|
|
14807
14901
|
await writeStageSecrets(updated);
|
|
14808
|
-
logger$
|
|
14902
|
+
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
14809
14903
|
} else {
|
|
14810
14904
|
let updated = secrets;
|
|
14811
14905
|
const services = Object.keys(secrets.services);
|
|
14812
14906
|
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
14813
14907
|
await writeStageSecrets(updated);
|
|
14814
|
-
logger$
|
|
14908
|
+
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
14815
14909
|
}
|
|
14816
|
-
logger$
|
|
14910
|
+
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
14817
14911
|
}
|
|
14818
14912
|
/**
|
|
14819
14913
|
* Import secrets from a JSON file.
|
|
@@ -14821,7 +14915,7 @@ async function secretsRotateCommand(options) {
|
|
|
14821
14915
|
async function secretsImportCommand(file, options) {
|
|
14822
14916
|
const { stage, merge = true } = options;
|
|
14823
14917
|
if (!existsSync(file)) {
|
|
14824
|
-
logger$
|
|
14918
|
+
logger$2.error(`File not found: ${file}`);
|
|
14825
14919
|
process.exit(1);
|
|
14826
14920
|
}
|
|
14827
14921
|
let importedSecrets;
|
|
@@ -14831,12 +14925,12 @@ async function secretsImportCommand(file, options) {
|
|
|
14831
14925
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
14832
14926
|
for (const [key, value] of Object.entries(importedSecrets)) if (typeof value !== "string") throw new Error(`Value for "${key}" must be a string, got ${typeof value}`);
|
|
14833
14927
|
} catch (error) {
|
|
14834
|
-
logger$
|
|
14928
|
+
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
14835
14929
|
process.exit(1);
|
|
14836
14930
|
}
|
|
14837
14931
|
const secrets = await readStageSecrets(stage);
|
|
14838
14932
|
if (!secrets) {
|
|
14839
|
-
logger$
|
|
14933
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14840
14934
|
process.exit(1);
|
|
14841
14935
|
}
|
|
14842
14936
|
const updatedCustom = merge ? {
|
|
@@ -14851,10 +14945,10 @@ async function secretsImportCommand(file, options) {
|
|
|
14851
14945
|
await writeStageSecrets(updated);
|
|
14852
14946
|
const importedCount = Object.keys(importedSecrets).length;
|
|
14853
14947
|
const totalCount = Object.keys(updatedCustom).length;
|
|
14854
|
-
logger$
|
|
14855
|
-
if (merge && totalCount > importedCount) logger$
|
|
14856
|
-
logger$
|
|
14857
|
-
for (const key of Object.keys(importedSecrets)) logger$
|
|
14948
|
+
logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
14949
|
+
if (merge && totalCount > importedCount) logger$2.log(` Total custom secrets: ${totalCount}`);
|
|
14950
|
+
logger$2.log("\n Imported keys:");
|
|
14951
|
+
for (const key of Object.keys(importedSecrets)) logger$2.log(` - ${key}`);
|
|
14858
14952
|
}
|
|
14859
14953
|
/**
|
|
14860
14954
|
* Mask password in a URL for display.
|
|
@@ -14869,175 +14963,120 @@ function maskUrl(url) {
|
|
|
14869
14963
|
}
|
|
14870
14964
|
}
|
|
14871
14965
|
//#endregion
|
|
14872
|
-
//#region src/
|
|
14873
|
-
/**
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
* reads none of that: it asks the operating system, so until the root is in the
|
|
14879
|
-
* system store a local `https://` address is "not secure", which is true and
|
|
14880
|
-
* unhelpful.
|
|
14881
|
-
*
|
|
14882
|
-
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
14883
|
-
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
14884
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
14885
|
-
* the line in the same place.
|
|
14886
|
-
*/
|
|
14887
|
-
const logger$2 = console;
|
|
14888
|
-
const run = promisify(execFile);
|
|
14889
|
-
/**
|
|
14890
|
-
* How each platform is told about a new authority.
|
|
14891
|
-
*
|
|
14892
|
-
* Separate rather than shelled through one script because the failure modes
|
|
14893
|
-
* differ and the message should name the real one: macOS refuses without an
|
|
14894
|
-
* admin password, Debian needs the file in a specific directory before its
|
|
14895
|
-
* updater will look at it.
|
|
14896
|
-
*/
|
|
14897
|
-
function installation(platform, certificate) {
|
|
14898
|
-
if (platform === "darwin") return {
|
|
14899
|
-
describe: "the System keychain",
|
|
14900
|
-
steps: [["sudo", [
|
|
14901
|
-
"security",
|
|
14902
|
-
"add-trusted-cert",
|
|
14903
|
-
"-d",
|
|
14904
|
-
"-r",
|
|
14905
|
-
"trustRoot",
|
|
14906
|
-
"-k",
|
|
14907
|
-
"/Library/Keychains/System.keychain",
|
|
14908
|
-
certificate
|
|
14909
|
-
]]]
|
|
14910
|
-
};
|
|
14911
|
-
if (platform === "linux") {
|
|
14912
|
-
const debian = existsSync("/usr/local/share/ca-certificates");
|
|
14913
|
-
return {
|
|
14914
|
-
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
14915
|
-
steps: debian ? [["sudo", [
|
|
14916
|
-
"cp",
|
|
14917
|
-
certificate,
|
|
14918
|
-
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
14919
|
-
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
14920
|
-
"cp",
|
|
14921
|
-
certificate,
|
|
14922
|
-
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
14923
|
-
]], ["sudo", ["update-ca-trust"]]]
|
|
14924
|
-
};
|
|
14966
|
+
//#region src/secrets/store.ts
|
|
14967
|
+
/** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
|
|
14968
|
+
var FileSecretsStore = class {
|
|
14969
|
+
root;
|
|
14970
|
+
constructor(root) {
|
|
14971
|
+
this.root = root;
|
|
14925
14972
|
}
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
/**
|
|
14929
|
-
* Trust the certificate authority this project's edge issues from.
|
|
14930
|
-
*
|
|
14931
|
-
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
14932
|
-
* same entry, so running it twice is not an error.
|
|
14933
|
-
*/
|
|
14934
|
-
async function trustCommand(options = {}) {
|
|
14935
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
14936
|
-
const certificate = join(workspace.root, LOCAL_CA_PATH);
|
|
14937
|
-
if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
|
|
14938
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
14939
|
-
logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
14940
|
-
logger$2.log(` ${certificate}\n`);
|
|
14941
|
-
if (options.dryRun) {
|
|
14942
|
-
for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
|
|
14943
|
-
return;
|
|
14973
|
+
pull(stage) {
|
|
14974
|
+
return readStageSecrets(stage, this.root);
|
|
14944
14975
|
}
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
}
|
|
14976
|
+
push(_stage, secrets) {
|
|
14977
|
+
return writeStageSecrets(secrets, this.root);
|
|
14978
|
+
}
|
|
14979
|
+
};
|
|
14949
14980
|
/**
|
|
14950
|
-
*
|
|
14981
|
+
* The store a stage's secrets live in.
|
|
14951
14982
|
*
|
|
14952
|
-
*
|
|
14953
|
-
*
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
return;
|
|
14966
|
-
}
|
|
14967
|
-
if (await declined(root)) return;
|
|
14968
|
-
const { install } = await prompts({
|
|
14969
|
-
type: "confirm",
|
|
14970
|
-
name: "install",
|
|
14971
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
14972
|
-
initial: false
|
|
14973
|
-
});
|
|
14974
|
-
if (!install) {
|
|
14975
|
-
await recordDeclined(root);
|
|
14976
|
-
logger$2.log(" Skipped. Run \"gkm trust\" later.");
|
|
14977
|
-
return;
|
|
14978
|
-
}
|
|
14979
|
-
}
|
|
14980
|
-
await trustCommand();
|
|
14981
|
-
}
|
|
14982
|
-
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
14983
|
-
const TRUST_STATE = ".gkm/trust.json";
|
|
14984
|
-
async function declined(root) {
|
|
14985
|
-
try {
|
|
14986
|
-
const raw = await readFile(join(root, TRUST_STATE), "utf-8");
|
|
14987
|
-
return JSON.parse(raw).declined === true;
|
|
14988
|
-
} catch {
|
|
14989
|
-
return false;
|
|
14990
|
-
}
|
|
14983
|
+
* The local stage is always the file: its secrets belong to the machine
|
|
14984
|
+
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
14985
|
+
*/
|
|
14986
|
+
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
14987
|
+
const configured = workspace.secrets.store ?? "file";
|
|
14988
|
+
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
|
|
14989
|
+
if (configured.provider !== "ssm") return configured.provider;
|
|
14990
|
+
const { SsmSecretsStore } = await import("./ssm-BKN1Cg4z.mjs");
|
|
14991
|
+
return new SsmSecretsStore({
|
|
14992
|
+
project: workspace.name,
|
|
14993
|
+
region: configured.region,
|
|
14994
|
+
...options.profile ? { profile: options.profile } : {}
|
|
14995
|
+
});
|
|
14991
14996
|
}
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14997
|
+
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
14998
|
+
function isRemoteStore(workspace, stage) {
|
|
14999
|
+
const configured = workspace.secrets.store ?? "file";
|
|
15000
|
+
return stage !== workspace.stages.local && configured !== "file";
|
|
14996
15001
|
}
|
|
15002
|
+
//#endregion
|
|
15003
|
+
//#region src/secrets/transfer.ts
|
|
14997
15004
|
/**
|
|
14998
|
-
*
|
|
15005
|
+
* Moving a deployed stage's secrets between this machine and its store.
|
|
14999
15006
|
*
|
|
15000
|
-
*
|
|
15001
|
-
*
|
|
15002
|
-
*
|
|
15003
|
-
*
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15007
|
+
* `push` sends the encrypted local copy to the stage's store; `pull` brings it
|
|
15008
|
+
* back and writes the local copy a deploy reads — generating a key for it if
|
|
15009
|
+
* the machine has none, which is what a fresh CI runner needs. Both reconcile
|
|
15010
|
+
* the keys the workspace now derives, so a store never lags a new construct.
|
|
15011
|
+
*/
|
|
15012
|
+
var transfer_exports = /* @__PURE__ */ __exportAll({
|
|
15013
|
+
NoLocalSecrets: () => NoLocalSecrets,
|
|
15014
|
+
NoRemoteSecretsStore: () => NoRemoteSecretsStore,
|
|
15015
|
+
NoStoredSecrets: () => NoStoredSecrets,
|
|
15016
|
+
pullStageSecrets: () => pullStageSecrets,
|
|
15017
|
+
pushStageSecrets: () => pushStageSecrets
|
|
15018
|
+
});
|
|
15019
|
+
/** A push or pull for a stage with nowhere to push or pull from. */
|
|
15020
|
+
var NoRemoteSecretsStore = class extends Error {
|
|
15021
|
+
stage;
|
|
15022
|
+
constructor(stage) {
|
|
15023
|
+
super(`Stage "${stage}" keeps its secrets in the local file, so there is nothing to push or pull. Set secrets.store in gkm.config.ts (e.g. { provider: 'ssm', region: 'eu-west-1' }) for deployed stages.`);
|
|
15024
|
+
this.stage = stage;
|
|
15025
|
+
this.name = "NoRemoteSecretsStore";
|
|
15019
15026
|
}
|
|
15020
|
-
}
|
|
15021
|
-
/**
|
|
15022
|
-
var
|
|
15023
|
-
|
|
15024
|
-
constructor(
|
|
15025
|
-
super(`No
|
|
15026
|
-
this.
|
|
15027
|
-
this.name = "
|
|
15027
|
+
};
|
|
15028
|
+
/** A push for a stage whose secrets this machine does not have. */
|
|
15029
|
+
var NoLocalSecrets = class extends Error {
|
|
15030
|
+
stage;
|
|
15031
|
+
constructor(stage) {
|
|
15032
|
+
super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
|
|
15033
|
+
this.stage = stage;
|
|
15034
|
+
this.name = "NoLocalSecrets";
|
|
15028
15035
|
}
|
|
15029
15036
|
};
|
|
15030
|
-
/** A
|
|
15031
|
-
var
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
this.
|
|
15037
|
-
this.certificate = certificate;
|
|
15038
|
-
this.name = "UnsupportedPlatform";
|
|
15037
|
+
/** A pull for a stage the store holds nothing for. */
|
|
15038
|
+
var NoStoredSecrets = class extends Error {
|
|
15039
|
+
stage;
|
|
15040
|
+
constructor(stage) {
|
|
15041
|
+
super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
|
|
15042
|
+
this.stage = stage;
|
|
15043
|
+
this.name = "NoStoredSecrets";
|
|
15039
15044
|
}
|
|
15040
15045
|
};
|
|
15046
|
+
async function reconcile(secrets, workspace, stage) {
|
|
15047
|
+
const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
|
|
15048
|
+
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15049
|
+
return result ? {
|
|
15050
|
+
secrets: result.secrets,
|
|
15051
|
+
addedKeys: result.addedKeys
|
|
15052
|
+
} : {
|
|
15053
|
+
secrets,
|
|
15054
|
+
addedKeys: []
|
|
15055
|
+
};
|
|
15056
|
+
}
|
|
15057
|
+
function remoteOnly(workspace, stage) {
|
|
15058
|
+
assertDeployedStage(workspace.stages, stage);
|
|
15059
|
+
if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
|
|
15060
|
+
}
|
|
15061
|
+
/** Send this machine's secrets for a deployed stage to its store. */
|
|
15062
|
+
async function pushStageSecrets(workspace, stage, options = {}) {
|
|
15063
|
+
remoteOnly(workspace, stage);
|
|
15064
|
+
const local = await readStageSecrets(stage, workspace.root);
|
|
15065
|
+
if (!local) throw new NoLocalSecrets(stage);
|
|
15066
|
+
const result = await reconcile(local, workspace, stage);
|
|
15067
|
+
if (result.addedKeys.length) await writeStageSecrets(result.secrets, workspace.root);
|
|
15068
|
+
await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
|
|
15069
|
+
return result;
|
|
15070
|
+
}
|
|
15071
|
+
/** Bring a deployed stage's secrets from its store into the local copy. */
|
|
15072
|
+
async function pullStageSecrets(workspace, stage, options = {}) {
|
|
15073
|
+
remoteOnly(workspace, stage);
|
|
15074
|
+
const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
|
|
15075
|
+
if (!stored) throw new NoStoredSecrets(stage);
|
|
15076
|
+
const result = await reconcile(stored, workspace, stage);
|
|
15077
|
+
await writeStageSecrets(result.secrets, workspace.root);
|
|
15078
|
+
return result;
|
|
15079
|
+
}
|
|
15041
15080
|
//#endregion
|
|
15042
15081
|
//#region src/setup/index.ts
|
|
15043
15082
|
var setup_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -15106,8 +15145,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15106
15145
|
}
|
|
15107
15146
|
logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
|
|
15108
15147
|
for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
|
|
15109
|
-
const
|
|
15110
|
-
const url = served ? result.env[served.envKey] : void 0;
|
|
15148
|
+
const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
|
|
15111
15149
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
15112
15150
|
}
|
|
15113
15151
|
/**
|
|
@@ -15551,7 +15589,6 @@ async function testCommand(options = {}) {
|
|
|
15551
15589
|
stage,
|
|
15552
15590
|
startDocker: !options.prepare,
|
|
15553
15591
|
secretsFileName: "test-secrets.json",
|
|
15554
|
-
resolveDockerPorts: "full",
|
|
15555
15592
|
reconcileStage: "test"
|
|
15556
15593
|
});
|
|
15557
15594
|
let finalCredentials = { ...result.credentials };
|
|
@@ -15565,14 +15602,13 @@ async function testCommand(options = {}) {
|
|
|
15565
15602
|
console.log(` 🔍 Sniffed ${sniffed.requiredEnvVars.length} required env var(s)` + (result.declaredKeys.length > 0 ? `, kept ${result.declaredKeys.length} declared` : ""));
|
|
15566
15603
|
}
|
|
15567
15604
|
}
|
|
15568
|
-
finalCredentials = rewriteDatabaseUrlForTests(finalCredentials);
|
|
15569
15605
|
console.log("");
|
|
15570
15606
|
await writeFile(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
15571
15607
|
const gkmDir = join(cwd, ".gkm");
|
|
15572
15608
|
const preloadPath = join(gkmDir, "test-credentials-preload.ts");
|
|
15573
15609
|
await createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
15574
15610
|
const workspace = await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
|
|
15575
|
-
const manifestPath = workspace
|
|
15611
|
+
const manifestPath = workspace ? await writeTestHarness({
|
|
15576
15612
|
root: workspace.root,
|
|
15577
15613
|
targets: [workspace.root, ...Object.values(workspace.apps).map((app) => isAbsolute(app.path) ? app.path : join(workspace.root, app.path))],
|
|
15578
15614
|
patterns: constructGlobs(workspace),
|
|
@@ -15616,29 +15652,6 @@ async function testCommand(options = {}) {
|
|
|
15616
15652
|
});
|
|
15617
15653
|
});
|
|
15618
15654
|
}
|
|
15619
|
-
const TEST_DB_SUFFIX = "_test";
|
|
15620
|
-
/**
|
|
15621
|
-
* Rewrite DATABASE_URL to point to a separate test database.
|
|
15622
|
-
* Appends `_test` to the database name (e.g., `app` -> `app_test`).
|
|
15623
|
-
* @internal Exported for testing
|
|
15624
|
-
*/
|
|
15625
|
-
function rewriteDatabaseUrlForTests(env) {
|
|
15626
|
-
const result = { ...env };
|
|
15627
|
-
for (const key of Object.keys(result)) {
|
|
15628
|
-
if (!key.includes("DATABASE_URL")) continue;
|
|
15629
|
-
const value = result[key];
|
|
15630
|
-
try {
|
|
15631
|
-
const url = new URL(value);
|
|
15632
|
-
const dbName = url.pathname.slice(1);
|
|
15633
|
-
if (dbName && !dbName.endsWith(TEST_DB_SUFFIX)) {
|
|
15634
|
-
url.pathname = `/${dbName}${TEST_DB_SUFFIX}`;
|
|
15635
|
-
result[key] = url.toString();
|
|
15636
|
-
console.log(` 🧪 ${key}: using test database "${dbName}${TEST_DB_SUFFIX}"`);
|
|
15637
|
-
}
|
|
15638
|
-
} catch {}
|
|
15639
|
-
}
|
|
15640
|
-
return result;
|
|
15641
|
-
}
|
|
15642
15655
|
/** `gkm test` outside a project that declares stages, with none named. */
|
|
15643
15656
|
var NoStageToTest = class extends Error {
|
|
15644
15657
|
constructor() {
|
|
@@ -16179,7 +16192,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
|
|
|
16179
16192
|
const globalOptions = program.opts();
|
|
16180
16193
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16181
16194
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16182
|
-
const { pushStageSecrets } = await
|
|
16195
|
+
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16183
16196
|
const { workspace } = await loadWorkspaceConfig();
|
|
16184
16197
|
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16185
16198
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16194,7 +16207,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
|
|
|
16194
16207
|
const globalOptions = program.opts();
|
|
16195
16208
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16196
16209
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16197
|
-
const { pullStageSecrets } = await
|
|
16210
|
+
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16198
16211
|
const { workspace } = await loadWorkspaceConfig();
|
|
16199
16212
|
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16200
16213
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16209,7 +16222,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16209
16222
|
const globalOptions = program.opts();
|
|
16210
16223
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16211
16224
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16212
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
16225
|
+
const { reconcileMissingSecrets } = await import("./reconcile-DhbCvh4K.mjs").then((n) => n.n);
|
|
16213
16226
|
const { readStageSecrets, writeStageSecrets } = await import("./storage-TP0x118M.mjs").then((n) => n.a);
|
|
16214
16227
|
const { workspace } = await loadWorkspaceConfig();
|
|
16215
16228
|
const stage = options.stage ?? workspace.stages.local;
|
|
@@ -16218,7 +16231,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16218
16231
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16219
16232
|
process.exit(1);
|
|
16220
16233
|
}
|
|
16221
|
-
const { derivedContainers } = await import("./
|
|
16234
|
+
const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
|
|
16222
16235
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16223
16236
|
if (!result) {
|
|
16224
16237
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16277,7 +16290,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16277
16290
|
try {
|
|
16278
16291
|
const globalOptions = program.opts();
|
|
16279
16292
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16280
|
-
const { deployGithubCommand } = await import("./github-
|
|
16293
|
+
const { deployGithubCommand } = await import("./github-zkzu-ka2.mjs");
|
|
16281
16294
|
await deployGithubCommand(options);
|
|
16282
16295
|
} catch (error) {
|
|
16283
16296
|
console.error(formatError(error));
|
|
@@ -16402,6 +16415,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16402
16415
|
});
|
|
16403
16416
|
program.parse();
|
|
16404
16417
|
//#endregion
|
|
16405
|
-
export { getHostingerToken as n, encryption_exports as t };
|
|
16418
|
+
export { getHostingerToken as i, isRemoteStore as n, encryption_exports as r, pushStageSecrets as t };
|
|
16406
16419
|
|
|
16407
16420
|
//# sourceMappingURL=index.mjs.map
|