@geekmidas/cli 10.0.0-alpha.74 → 10.0.0-alpha.76
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/{LegacyStateStore-D_78eaHM.cjs → LegacyStateStore-DwngfJxr.cjs} +2 -2
- package/dist/{LegacyStateStore-D_78eaHM.cjs.map → LegacyStateStore-DwngfJxr.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DTo7z924.mjs → LegacyStateStore-dyq07_Ir.mjs} +2 -2
- package/dist/{LegacyStateStore-DTo7z924.mjs.map → LegacyStateStore-dyq07_Ir.mjs.map} +1 -1
- package/dist/{LocalStateStore-BhrlQIXD.cjs → LocalStateStore-B0NCde8Z.cjs} +2 -2
- package/dist/{LocalStateStore-BhrlQIXD.cjs.map → LocalStateStore-B0NCde8Z.cjs.map} +1 -1
- package/dist/{LocalStateStore-CNjgkRk4.mjs → LocalStateStore-DLxCCatI.mjs} +2 -2
- package/dist/{LocalStateStore-CNjgkRk4.mjs.map → LocalStateStore-DLxCCatI.mjs.map} +1 -1
- package/dist/{S3StateStore-oCxRs6HM.cjs → S3StateStore-BOenb-3o.cjs} +2 -2
- package/dist/{S3StateStore-oCxRs6HM.cjs.map → S3StateStore-BOenb-3o.cjs.map} +1 -1
- package/dist/{S3StateStore-g2P7T_Mo.mjs → S3StateStore-CGK_4hlo.mjs} +2 -2
- package/dist/{S3StateStore-g2P7T_Mo.mjs.map → S3StateStore-CGK_4hlo.mjs.map} +1 -1
- package/dist/{SSMStateStore-CYenunmK.mjs → SSMStateStore-B_5_RqY-.mjs} +2 -2
- package/dist/{SSMStateStore-CYenunmK.mjs.map → SSMStateStore-B_5_RqY-.mjs.map} +1 -1
- package/dist/{SSMStateStore-DXp0m7XI.cjs → SSMStateStore-DYeD__7p.cjs} +2 -2
- package/dist/{SSMStateStore-DXp0m7XI.cjs.map → SSMStateStore-DYeD__7p.cjs.map} +1 -1
- package/dist/{add-BTH7WaH3.mjs → add-B7IMXzou.mjs} +3 -3
- package/dist/{add-BTH7WaH3.mjs.map → add-B7IMXzou.mjs.map} +1 -1
- package/dist/{add-Djrr12Xc.cjs → add-j2yI3cbS.cjs} +3 -3
- package/dist/{add-Djrr12Xc.cjs.map → add-j2yI3cbS.cjs.map} +1 -1
- package/dist/{aws-DojzQnLd.mjs → aws-Bja4zGSs.mjs} +13 -9
- package/dist/aws-Bja4zGSs.mjs.map +1 -0
- package/dist/{aws-DkHdTV-p.cjs → aws-CQAuluiu.cjs} +13 -9
- package/dist/aws-CQAuluiu.cjs.map +1 -0
- package/dist/awsStore-7VmS_cZR.cjs +54 -0
- package/dist/awsStore-7VmS_cZR.cjs.map +1 -0
- package/dist/awsStore-B529csle.mjs +43 -0
- package/dist/awsStore-B529csle.mjs.map +1 -0
- package/dist/{bundler-utHuESvL.cjs → bundler-Cf6OgaOe.cjs} +5 -5
- package/dist/bundler-Cf6OgaOe.cjs.map +1 -0
- package/dist/{bundler-BUojOkof.mjs → bundler-DO6bHhnb.mjs} +5 -5
- package/dist/bundler-DO6bHhnb.mjs.map +1 -0
- package/dist/{cli-CyYceKTS.mjs → cli-BCPOHmg9.mjs} +3 -3
- package/dist/{cli-CyYceKTS.mjs.map → cli-BCPOHmg9.mjs.map} +1 -1
- package/dist/{cli-Bx0sUzxH.cjs → cli-CNanUB7a.cjs} +3 -3
- package/dist/{cli-Bx0sUzxH.cjs.map → cli-CNanUB7a.cjs.map} +1 -1
- package/dist/{compose-DZp8DJ0A.mjs → compose-Dq4VCtu5.mjs} +221 -153
- package/dist/compose-Dq4VCtu5.mjs.map +1 -0
- package/dist/{compose-iuw8wfHk.cjs → compose-h_6oNnHC.cjs} +243 -151
- package/dist/compose-h_6oNnHC.cjs.map +1 -0
- package/dist/{config-B38YWicj.d.cts → config-CWyhLSY1.d.cts} +3 -3
- package/dist/{config-B38YWicj.d.cts.map → config-CWyhLSY1.d.cts.map} +1 -1
- package/dist/{config-DA0kRTQe.d.mts → config-CqAvPP-f.d.mts} +3 -3
- package/dist/{config-DA0kRTQe.d.mts.map → config-CqAvPP-f.d.mts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/deploy/public.cjs +7 -6
- package/dist/deploy/public.d.cts +14 -3
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +14 -3
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +7 -7
- package/dist/{deploy-eWorhvQp.cjs → deploy-CT3R9de9.cjs} +27 -5
- package/dist/deploy-CT3R9de9.cjs.map +1 -0
- package/dist/{deploy-C17K7Imy.mjs → deploy-CVXEx7Nm.mjs} +22 -6
- package/dist/deploy-CVXEx7Nm.mjs.map +1 -0
- package/dist/{dokploy-DKa49Nqm.mjs → dokploy-CYm2ozFs.mjs} +2 -2
- package/dist/{dokploy-DKa49Nqm.mjs.map → dokploy-CYm2ozFs.mjs.map} +1 -1
- package/dist/{dokploy-DKzcI-RL.cjs → dokploy-eSGmlhhj.cjs} +2 -2
- package/dist/{dokploy-DKzcI-RL.cjs.map → dokploy-eSGmlhhj.cjs.map} +1 -1
- package/dist/{encryption-sTMz3aWi.mjs → encryption-DC0rsR1W.mjs} +3 -3
- package/dist/{encryption-sTMz3aWi.mjs.map → encryption-DC0rsR1W.mjs.map} +1 -1
- package/dist/{encryption-DSkJdoyk.cjs → encryption-vS3Qzw73.cjs} +3 -3
- package/dist/{encryption-DSkJdoyk.cjs.map → encryption-vS3Qzw73.cjs.map} +1 -1
- package/dist/{engine-B1b4I34Z.mjs → engine-BOO5TWZd.mjs} +193 -5
- package/dist/engine-BOO5TWZd.mjs.map +1 -0
- package/dist/{engine-Bh76sAHy.cjs → engine-C0N2d1i5.cjs} +196 -8
- package/dist/engine-C0N2d1i5.cjs.map +1 -0
- package/dist/{github-C9ODoyZU.mjs → github-CEaFDL6t.mjs} +6 -6
- package/dist/github-CEaFDL6t.mjs.map +1 -0
- package/dist/{github-BvN3CfxM.cjs → github-ClkdCOL9.cjs} +6 -6
- package/dist/github-ClkdCOL9.cjs.map +1 -0
- package/dist/{index-B-1lLGzy.d.mts → index-Ccu0f_2m.d.cts} +2 -2
- package/dist/{index-B-1lLGzy.d.mts.map → index-Ccu0f_2m.d.cts.map} +1 -1
- package/dist/{index-vMidfqhU.d.cts → index-DOxmDKqv.d.mts} +2 -2
- package/dist/{index-vMidfqhU.d.cts.map → index-DOxmDKqv.d.mts.map} +1 -1
- package/dist/index.cjs +106 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +105 -51
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BRbYtFXG.cjs +93 -0
- package/dist/migrate-BRbYtFXG.cjs.map +1 -0
- package/dist/migrate-D3NAT7Ns.mjs +93 -0
- package/dist/migrate-D3NAT7Ns.mjs.map +1 -0
- package/dist/{migrate-B0FCZNkp.cjs → migrate-D5_-ylI1.cjs} +3 -3
- package/dist/{migrate-B0FCZNkp.cjs.map → migrate-D5_-ylI1.cjs.map} +1 -1
- package/dist/{migrate-CTtJhGV9.mjs → migrate-DrO2p9Rl.mjs} +3 -3
- package/dist/{migrate-CTtJhGV9.mjs.map → migrate-DrO2p9Rl.mjs.map} +1 -1
- package/dist/openapi.cjs +16 -6
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +16 -6
- package/dist/openapi.mjs.map +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-DSAXj_47.mjs → resolve-C1muFos5.mjs} +3 -3
- package/dist/{resolve-DSAXj_47.mjs.map → resolve-C1muFos5.mjs.map} +1 -1
- package/dist/{resolve-D_BU-wKA.cjs → resolve-Fa1Ii0BK.cjs} +3 -3
- package/dist/{resolve-D_BU-wKA.cjs.map → resolve-Fa1Ii0BK.cjs.map} +1 -1
- package/dist/{rollback-DJy3t696.cjs → rollback-C9N5lKVf.cjs} +5 -5
- package/dist/{rollback-DJy3t696.cjs.map → rollback-C9N5lKVf.cjs.map} +1 -1
- package/dist/{rollback-CWYTUhfW.mjs → rollback-DeQzbRqM.mjs} +5 -5
- package/dist/{rollback-CWYTUhfW.mjs.map → rollback-DeQzbRqM.mjs.map} +1 -1
- package/dist/{runtime-BzZD5HHi.d.cts → runtime-BSvzPqXW.d.mts} +2 -2
- package/dist/{runtime-BzZD5HHi.d.cts.map → runtime-BSvzPqXW.d.mts.map} +1 -1
- package/dist/{runtime-Cm6J6L7n.d.mts → runtime-Cm7QA53H.d.cts} +2 -2
- package/dist/{runtime-Cm6J6L7n.d.mts.map → runtime-Cm7QA53H.d.cts.map} +1 -1
- package/dist/secretsManager-DUXW3fQt.cjs +70 -0
- package/dist/secretsManager-DUXW3fQt.cjs.map +1 -0
- package/dist/secretsManager-Ti0oZwsS.mjs +70 -0
- package/dist/secretsManager-Ti0oZwsS.mjs.map +1 -0
- package/dist/{sniffer-BFmdZpZ2.mjs → sniffer-DR8AlRCk.mjs} +67 -15
- package/dist/sniffer-DR8AlRCk.mjs.map +1 -0
- package/dist/{sniffer-C-T7dSMN.cjs → sniffer-Y6GQOCXB.cjs} +83 -13
- package/dist/sniffer-Y6GQOCXB.cjs.map +1 -0
- package/dist/{state-DCwRWyh-.cjs → state-C2qi0iQw.cjs} +241 -334
- package/dist/state-C2qi0iQw.cjs.map +1 -0
- package/dist/{state-DpkxSwzI.mjs → state-CUecOCNJ.mjs} +213 -306
- package/dist/state-CUecOCNJ.mjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{types-TzCChNrA.d.cts → types-BdptGBFS.d.cts} +59 -6
- package/dist/types-BdptGBFS.d.cts.map +1 -0
- package/dist/{types-Dp9lgx_S.d.mts → types-OxE0OAn2.d.mts} +59 -6
- package/dist/types-OxE0OAn2.d.mts.map +1 -0
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-vdrcRI-m.cjs → workspace-DOm81oUo.cjs} +54 -1
- package/dist/workspace-DOm81oUo.cjs.map +1 -0
- package/dist/{workspace-C7NqCsQF.mjs → workspace-QUTqYmVy.mjs} +43 -2
- package/dist/workspace-QUTqYmVy.mjs.map +1 -0
- package/package.json +18 -17
- package/dist/aws-DkHdTV-p.cjs.map +0 -1
- package/dist/aws-DojzQnLd.mjs.map +0 -1
- package/dist/bundler-BUojOkof.mjs.map +0 -1
- package/dist/bundler-utHuESvL.cjs.map +0 -1
- package/dist/compose-DZp8DJ0A.mjs.map +0 -1
- package/dist/compose-iuw8wfHk.cjs.map +0 -1
- package/dist/deploy-C17K7Imy.mjs.map +0 -1
- package/dist/deploy-eWorhvQp.cjs.map +0 -1
- package/dist/engine-B1b4I34Z.mjs.map +0 -1
- package/dist/engine-Bh76sAHy.cjs.map +0 -1
- package/dist/github-BvN3CfxM.cjs.map +0 -1
- package/dist/github-C9ODoyZU.mjs.map +0 -1
- package/dist/sniffer-BFmdZpZ2.mjs.map +0 -1
- package/dist/sniffer-C-T7dSMN.cjs.map +0 -1
- package/dist/state-DCwRWyh-.cjs.map +0 -1
- package/dist/state-DpkxSwzI.mjs.map +0 -1
- package/dist/types-Dp9lgx_S.d.mts.map +0 -1
- package/dist/types-TzCChNrA.d.cts.map +0 -1
- package/dist/workspace-C7NqCsQF.mjs.map +0 -1
- package/dist/workspace-vdrcRI-m.cjs.map +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { i as run$1, s as gkmHome } from "./run-BJLIerMM.mjs";
|
|
3
3
|
import { d as output, r as discover, u as ConstructGenerator } from "./discover-BPelzeGW.mjs";
|
|
4
4
|
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
5
|
-
import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-
|
|
5
|
+
import { F as UnknownSecretsStoreProvider, O as TEST_STAGE, h as getPublicEnvPrefix, m as PUBLIC_ENV_PREFIXES, n as allConstructGlobs } from "./workspace-QUTqYmVy.mjs";
|
|
6
6
|
import { ConfigNotFound, NotInAnApp, loadWorkspaceAppInfo, loadWorkspaceConfig } from "./config.mjs";
|
|
7
7
|
import { n as runtimeFor, r as generateTelemetryModule } from "./EndpointGenerator-DftalLYg.mjs";
|
|
8
|
-
import { c as runtimeOf } from "./resolve-
|
|
8
|
+
import { c as runtimeOf } from "./resolve-C1muFos5.mjs";
|
|
9
9
|
import { a as DEFAULT_STORAGE, i as DEFAULT_EVENTS$1, n as readFakes, o as holdsEveryCache, r as DEFAULT_CACHE, t as SubscriberGenerator } from "./SubscriberGenerator-5looQ3gB.mjs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
11
11
|
import { constants, existsSync, readFileSync } from "node:fs";
|
|
12
|
-
import { appendFile, chmod, copyFile, mkdir,
|
|
13
|
-
import { homedir, networkInterfaces
|
|
12
|
+
import { appendFile, chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
import { homedir, networkInterfaces } from "node:os";
|
|
14
14
|
import { promisify } from "node:util";
|
|
15
15
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
16
16
|
import { Queue } from "@geekmidas/constructs/queue";
|
|
@@ -274,7 +274,7 @@ function applicationName(identity, app) {
|
|
|
274
274
|
* old file stays, so an older CLI on the same machine keeps working.
|
|
275
275
|
*/
|
|
276
276
|
/** Key length for AES-256 encryption */
|
|
277
|
-
const KEY_LENGTH
|
|
277
|
+
const KEY_LENGTH = 32;
|
|
278
278
|
/** A keystore project key that is not `<namespace>/<project>`. */
|
|
279
279
|
var KeystoreProjectInvalid = class extends Error {
|
|
280
280
|
key;
|
|
@@ -367,7 +367,7 @@ async function generateKey(stage, project, flag = "w") {
|
|
|
367
367
|
recursive: true,
|
|
368
368
|
mode: 448
|
|
369
369
|
});
|
|
370
|
-
const key = randomBytes(KEY_LENGTH
|
|
370
|
+
const key = randomBytes(KEY_LENGTH).toString("hex");
|
|
371
371
|
await writeFile(keyPath, key, {
|
|
372
372
|
mode: 384,
|
|
373
373
|
encoding: "utf-8",
|
|
@@ -436,8 +436,8 @@ async function getOrCreateKey(stage, project) {
|
|
|
436
436
|
/** Where the files live, relative to the project root. */
|
|
437
437
|
const SECRETS_DIR = ".gkm/secrets";
|
|
438
438
|
/** AES-256-GCM configuration */
|
|
439
|
-
const ALGORITHM
|
|
440
|
-
const IV_LENGTH
|
|
439
|
+
const ALGORITHM = "aes-256-gcm";
|
|
440
|
+
const IV_LENGTH = 12;
|
|
441
441
|
/**
|
|
442
442
|
* A stage's secrets as an encrypted file on this machine:
|
|
443
443
|
* `.gkm/secrets/<stage>.json`, with its key in the CLI's home at
|
|
@@ -505,8 +505,8 @@ function packageName$1(root) {
|
|
|
505
505
|
}
|
|
506
506
|
function encrypt(secrets, keyHex) {
|
|
507
507
|
const key = Buffer.from(keyHex, "hex");
|
|
508
|
-
const iv = randomBytes(IV_LENGTH
|
|
509
|
-
const cipher = createCipheriv(ALGORITHM
|
|
508
|
+
const iv = randomBytes(IV_LENGTH);
|
|
509
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
510
510
|
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(secrets), "utf-8"), cipher.final()]);
|
|
511
511
|
return {
|
|
512
512
|
version: 1,
|
|
@@ -517,7 +517,7 @@ function encrypt(secrets, keyHex) {
|
|
|
517
517
|
function decrypt(data, keyHex) {
|
|
518
518
|
const key = Buffer.from(keyHex, "hex");
|
|
519
519
|
const combined = Buffer.from(data.encrypted, "base64");
|
|
520
|
-
const decipher = createDecipheriv(ALGORITHM
|
|
520
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(data.iv, "hex"));
|
|
521
521
|
decipher.setAuthTag(combined.subarray(-16));
|
|
522
522
|
const plaintext = Buffer.concat([decipher.update(combined.subarray(0, -16)), decipher.final()]);
|
|
523
523
|
return JSON.parse(plaintext.toString("utf-8"));
|
|
@@ -527,7 +527,8 @@ function decrypt(data, keyHex) {
|
|
|
527
527
|
var store_exports = /* @__PURE__ */ __exportAll({
|
|
528
528
|
FileSecretsStore: () => FileSecretsStore,
|
|
529
529
|
isRemoteStore: () => isRemoteStore,
|
|
530
|
-
secretsStoreFor: () => secretsStoreFor
|
|
530
|
+
secretsStoreFor: () => secretsStoreFor,
|
|
531
|
+
storeFromConfig: () => storeFromConfig
|
|
531
532
|
});
|
|
532
533
|
/**
|
|
533
534
|
* The store a stage's secrets are kept in — what every command reads and
|
|
@@ -539,13 +540,41 @@ var store_exports = /* @__PURE__ */ __exportAll({
|
|
|
539
540
|
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
540
541
|
const configured = workspace.secrets.store ?? "file";
|
|
541
542
|
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root, keystoreProject(workspace, options.home));
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
543
|
+
return storeFromConfig(workspace, configured, options);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* The store a `secrets.store` value describes, for any stage — what
|
|
547
|
+
* {@link secretsStoreFor} answers for a deployed stage, and what
|
|
548
|
+
* `gkm secrets:migrate` builds for the store it copies to.
|
|
549
|
+
*/
|
|
550
|
+
async function storeFromConfig(workspace, config, options = {}) {
|
|
551
|
+
if (config === "file") return new FileSecretsStore(workspace.root, keystoreProject(workspace, options.home));
|
|
552
|
+
const profile = options.profile ? { profile: options.profile } : {};
|
|
553
|
+
switch (config.provider) {
|
|
554
|
+
case "ssm": {
|
|
555
|
+
const { AwsSecretsStore } = await import("./aws-Bja4zGSs.mjs");
|
|
556
|
+
return new AwsSecretsStore({
|
|
557
|
+
project: workspace.name,
|
|
558
|
+
region: config.region,
|
|
559
|
+
...profile
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
case "secrets-manager": {
|
|
563
|
+
const { kmsKeyId } = config;
|
|
564
|
+
const { SecretsManagerSecretsStore } = await import("./secretsManager-Ti0oZwsS.mjs");
|
|
565
|
+
return new SecretsManagerSecretsStore({
|
|
566
|
+
project: workspace.name,
|
|
567
|
+
region: config.region,
|
|
568
|
+
...kmsKeyId ? { kmsKeyId } : {},
|
|
569
|
+
...profile
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
default: {
|
|
573
|
+
const provider = config.provider;
|
|
574
|
+
if (typeof provider === "object" && provider !== null) return provider;
|
|
575
|
+
throw new UnknownSecretsStoreProvider(provider);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
549
578
|
}
|
|
550
579
|
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
551
580
|
function isRemoteStore(workspace, stage) {
|
|
@@ -554,7 +583,7 @@ function isRemoteStore(workspace, stage) {
|
|
|
554
583
|
}
|
|
555
584
|
//#endregion
|
|
556
585
|
//#region src/credentials/index.ts
|
|
557
|
-
const logger$
|
|
586
|
+
const logger$1 = console;
|
|
558
587
|
/**
|
|
559
588
|
* Load environment files
|
|
560
589
|
* @internal Exported for testing
|
|
@@ -605,7 +634,7 @@ async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
|
605
634
|
for (let i = 0; i < maxAttempts; i++) {
|
|
606
635
|
const port = preferredPort + i;
|
|
607
636
|
if (await isPortAvailable(port)) return port;
|
|
608
|
-
logger$
|
|
637
|
+
logger$1.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
609
638
|
}
|
|
610
639
|
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
611
640
|
}
|
|
@@ -640,7 +669,7 @@ async function loadSecretsForApp(store, stage, appName) {
|
|
|
640
669
|
let secrets = {};
|
|
641
670
|
const stageSecrets = await store.read(stage);
|
|
642
671
|
if (stageSecrets) {
|
|
643
|
-
logger$
|
|
672
|
+
logger$1.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
644
673
|
secrets = toEmbeddableSecrets(stageSecrets);
|
|
645
674
|
}
|
|
646
675
|
if (Object.keys(secrets).length === 0) return {};
|
|
@@ -746,7 +775,7 @@ async function prepareEntryCredentials(options) {
|
|
|
746
775
|
free: isPortAvailable,
|
|
747
776
|
holder: holderOf
|
|
748
777
|
});
|
|
749
|
-
for (const { app, from, to, holder } of assigned.moved) logger$
|
|
778
|
+
for (const { app, from, to, holder } of assigned.moved) logger$1.log(`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`);
|
|
750
779
|
workspace = withAppPorts(workspace, assigned.ports);
|
|
751
780
|
workspaceAppPort = assigned.ports[appName] ?? workspaceAppPort;
|
|
752
781
|
}
|
|
@@ -770,7 +799,7 @@ async function prepareEntryCredentials(options) {
|
|
|
770
799
|
});
|
|
771
800
|
Object.assign(credentials, reconciled.env);
|
|
772
801
|
declaredKeys.push(...Object.keys(reconciled.env));
|
|
773
|
-
if (Object.keys(reconciled.env).length > 0) logger$
|
|
802
|
+
if (Object.keys(reconciled.env).length > 0) logger$1.log(`🔌 Resolved ${Object.keys(reconciled.env).length} declared URL(s)`);
|
|
774
803
|
}
|
|
775
804
|
const secretsDir = join(secretsRoot, ".gkm");
|
|
776
805
|
await mkdir(secretsDir, { recursive: true });
|
|
@@ -1022,6 +1051,36 @@ function eventsBackendFor(on) {
|
|
|
1022
1051
|
return DEFAULT_EVENTS$1[on];
|
|
1023
1052
|
}
|
|
1024
1053
|
//#endregion
|
|
1054
|
+
//#region src/exec/imageBuild.ts
|
|
1055
|
+
/**
|
|
1056
|
+
* Set by a site's Dockerfile in its builder stage: this process is building
|
|
1057
|
+
* an image.
|
|
1058
|
+
*
|
|
1059
|
+
* Inside an image there is no stage to load secrets for and no local
|
|
1060
|
+
* container to resolve an address from — the public URLs the bundle inlines
|
|
1061
|
+
* arrived as the Dockerfile's build args, already in the environment. So
|
|
1062
|
+
* `gkm exec -- next build` there injects exactly those, and never the
|
|
1063
|
+
* `localhost` addresses a workspace resolves on a developer's machine.
|
|
1064
|
+
*/
|
|
1065
|
+
const IMAGE_BUILD_ENV = "GKM_IMAGE_BUILD";
|
|
1066
|
+
/** Whether this process is building an image. */
|
|
1067
|
+
function isImageBuild(env = process.env) {
|
|
1068
|
+
return env[IMAGE_BUILD_ENV] === "1";
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* What `gkm exec` injects inside an image build: the public values its build
|
|
1072
|
+
* args set — every `NEXT_PUBLIC_*`, `VITE_*` and `EXPO_PUBLIC_*` — and
|
|
1073
|
+
* nothing read from a secrets store, a file or a container.
|
|
1074
|
+
*/
|
|
1075
|
+
function imageBuildCredentials(env = process.env) {
|
|
1076
|
+
const credentials = {};
|
|
1077
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1078
|
+
if (value === void 0) continue;
|
|
1079
|
+
if (PUBLIC_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) credentials[key] = value;
|
|
1080
|
+
}
|
|
1081
|
+
return credentials;
|
|
1082
|
+
}
|
|
1083
|
+
//#endregion
|
|
1025
1084
|
//#region src/docker/templates.ts
|
|
1026
1085
|
/**
|
|
1027
1086
|
* The turbo every image prunes and builds with when the build root resolves
|
|
@@ -1279,23 +1338,22 @@ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
|
|
|
1279
1338
|
*
|
|
1280
1339
|
* Nothing built on the host reaches the image — `.dockerignore` keeps every
|
|
1281
1340
|
* `dist` out — so a workspace package (a CLI that is a sibling package) is
|
|
1282
|
-
* built here:
|
|
1283
|
-
*
|
|
1341
|
+
* built here: turbo's build of the app's dependencies (`^build`) and nothing
|
|
1342
|
+
* else. The root's own `build` script is never run: the slice holds one app,
|
|
1343
|
+
* and a root `build` that is `gkm build` (what `gkm init` scaffolds) builds
|
|
1344
|
+
* every app the workspace declares, most of which are not in it. The app
|
|
1345
|
+
* itself is built after this, by `gkm build` or its framework.
|
|
1284
1346
|
*/
|
|
1285
1347
|
function builderStage(options, extra = {}) {
|
|
1286
|
-
const {
|
|
1348
|
+
const { turbo, monorepo } = layout(options);
|
|
1287
1349
|
const args = extra.args ?? [];
|
|
1288
1350
|
const argLines = args.length ? `\n# Build-time args: public values the bundler inlines\n${args.map((a) => `ARG ${a}=""`).join("\n")}\n${args.map((a) => `ENV ${a}=$${a}`).join("\n")}\n` : "";
|
|
1289
1351
|
const scopes = [options.turboPackage, ...options.prunePackages ?? []];
|
|
1290
|
-
const dependencies = monorepo ? `\nRUN ${turbo} run build ${scopes.map((s) => `--filter='${s}^...'`).join(" ")}` : "";
|
|
1291
1352
|
return `# Stage 3: Build
|
|
1292
1353
|
FROM deps AS builder
|
|
1293
1354
|
WORKDIR /app
|
|
1294
1355
|
${argLines}
|
|
1295
|
-
COPY --from=pruner /app/out/full/
|
|
1296
|
-
|
|
1297
|
-
# The workspace packages the app depends on, built from source.
|
|
1298
|
-
RUN ${pm.run} --if-present build${dependencies}`;
|
|
1356
|
+
COPY --from=pruner /app/out/full/ ./${monorepo ? `\n# The workspace packages the app depends on, built from source.\nRUN ${turbo} run build ${scopes.map((s) => `--filter='${s}^...'`).join(" ")}` : ""}`;
|
|
1299
1357
|
}
|
|
1300
1358
|
/**
|
|
1301
1359
|
* The PATH a command in the app's directory runs with: the app's binaries,
|
|
@@ -1320,7 +1378,48 @@ function binPath(options) {
|
|
|
1320
1378
|
* were installed.
|
|
1321
1379
|
*/
|
|
1322
1380
|
function gkmCommand(args) {
|
|
1323
|
-
return `GKM_BIN="$(node -e '
|
|
1381
|
+
return `GKM_BIN="$(node -e '${FIND_GKM_BIN}')" && node "$GKM_BIN" ${args}`;
|
|
1382
|
+
}
|
|
1383
|
+
/** Prints the `bin/gkm.mjs` Node resolves from the cwd, or fails saying so. */
|
|
1384
|
+
const FIND_GKM_BIN = `let d=process.cwd();const{existsSync:e}=require("fs"),{dirname:u,join:j}=require("path");for(;;){const p=j(d,"node_modules/@geekmidas/cli/bin/gkm.mjs");if(e(p)){console.log(p);break}if(u(d)===d){console.error("@geekmidas/cli is not installed where the app can resolve it");process.exit(1)}d=u(d)}`;
|
|
1385
|
+
/**
|
|
1386
|
+
* What a site's builder does with the gkm workspace it carries, before its
|
|
1387
|
+
* framework builds it — all of it inside the image, nothing from the host:
|
|
1388
|
+
*
|
|
1389
|
+
* - `gkm` on the PATH: the CLI the workspace installs, so a build script that
|
|
1390
|
+
* is `gkm exec -- next build` runs. Resolved from the gkm root rather than
|
|
1391
|
+
* through `node_modules/.bin`, where a CLI that is a workspace package has
|
|
1392
|
+
* no bin linked.
|
|
1393
|
+
* - `GKM_IMAGE_BUILD`, which has `gkm exec` inject the build args above — the
|
|
1394
|
+
* stage's public URLs — and never load a secret or resolve a local address.
|
|
1395
|
+
* - each backend's typed client, generated from its endpoints at the
|
|
1396
|
+
* workspace root's `.gkm/client/`, where the site's tsconfig paths point.
|
|
1397
|
+
*/
|
|
1398
|
+
function siteWorkspaceSteps(options) {
|
|
1399
|
+
const { gkmRoot } = layout(options);
|
|
1400
|
+
const root = `/app/${gkmRoot}`.replace(/\/\.$/, "");
|
|
1401
|
+
const clients = options.clients ?? [];
|
|
1402
|
+
const generate = clients.length ? `
|
|
1403
|
+
|
|
1404
|
+
# The typed client of each API this site calls, generated from its endpoints
|
|
1405
|
+
# into ${gkmRoot === "." ? "" : `${gkmRoot}/`}.gkm/client/ — offline, with no secret and no container.
|
|
1406
|
+
RUN ${clients.map(({ app, path }) => `cd /app/${path} && gkm openapi --app ${app}`).join(" && \\\n ")}` : "";
|
|
1407
|
+
return `# The gkm CLI the workspace installs, as \`gkm\`
|
|
1408
|
+
RUN cd ${root} && GKM_BIN="$(node -e '${FIND_GKM_BIN}')" && \\
|
|
1409
|
+
{ echo '#!/bin/sh'; echo "exec node \\"$GKM_BIN\\" \\"\\$@\\""; } > /usr/local/bin/gkm && \\
|
|
1410
|
+
chmod +x /usr/local/bin/gkm
|
|
1411
|
+
|
|
1412
|
+
# \`gkm exec\` in this build injects the build args, never a stage's secrets
|
|
1413
|
+
ENV ${IMAGE_BUILD_ENV}=1${generate}`;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Build the site with turbo. Loose env mode, so the task sees what the
|
|
1417
|
+
* Dockerfile set — the build args and `GKM_IMAGE_BUILD` — whatever the
|
|
1418
|
+
* project's turbo.json declares: the image's environment holds nothing else.
|
|
1419
|
+
*/
|
|
1420
|
+
function siteBuild(options) {
|
|
1421
|
+
const { turbo } = layout(options);
|
|
1422
|
+
return `RUN ${turbo} run build --filter='${options.turboPackage}' --env-mode=loose`;
|
|
1324
1423
|
}
|
|
1325
1424
|
/**
|
|
1326
1425
|
* The encrypted credentials, from a build secret — never an ARG, which is
|
|
@@ -1474,8 +1573,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
|
1474
1573
|
* @internal Exported for testing
|
|
1475
1574
|
*/
|
|
1476
1575
|
function generateNextjsDockerfile(options) {
|
|
1477
|
-
const { port, appPath,
|
|
1478
|
-
const { turbo } = layout(options);
|
|
1576
|
+
const { port, appPath, publicUrlArgs = ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_AUTH_URL"] } = options;
|
|
1479
1577
|
return `# syntax=docker/dockerfile:1
|
|
1480
1578
|
# Next.js standalone Dockerfile, built from a turbo-pruned slice
|
|
1481
1579
|
|
|
@@ -1485,13 +1583,15 @@ ${depsStage(options)}
|
|
|
1485
1583
|
|
|
1486
1584
|
${builderStage(options, { args: publicUrlArgs })}
|
|
1487
1585
|
|
|
1586
|
+
${siteWorkspaceSteps(options)}
|
|
1587
|
+
|
|
1488
1588
|
# Ensure public directory exists (may be empty for scaffolded projects)
|
|
1489
1589
|
RUN mkdir -p ${appPath}/public
|
|
1490
1590
|
|
|
1491
1591
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
1492
1592
|
|
|
1493
1593
|
# Build the application
|
|
1494
|
-
|
|
1594
|
+
${siteBuild(options)}
|
|
1495
1595
|
|
|
1496
1596
|
# The runner needs the standalone server; without it there is nothing to run.
|
|
1497
1597
|
RUN if [ ! -d ${appPath}/.next/standalone ]; then \\
|
|
@@ -1679,8 +1779,7 @@ CMD ["node", "index.mjs"]
|
|
|
1679
1779
|
* @internal Exported for testing
|
|
1680
1780
|
*/
|
|
1681
1781
|
function generateNodeWebDockerfile(options) {
|
|
1682
|
-
const { port, appPath
|
|
1683
|
-
const { turbo } = layout(options);
|
|
1782
|
+
const { port, appPath } = options;
|
|
1684
1783
|
return `# syntax=docker/dockerfile:1
|
|
1685
1784
|
# Node SSR web Dockerfile (TanStack Start / Remix), from a turbo-pruned slice
|
|
1686
1785
|
|
|
@@ -1689,7 +1788,10 @@ ${prunerStage(options)}
|
|
|
1689
1788
|
${depsStage(options)}
|
|
1690
1789
|
|
|
1691
1790
|
${builderStage(options, { args: options.publicUrlArgs ?? [] })}
|
|
1692
|
-
|
|
1791
|
+
|
|
1792
|
+
${siteWorkspaceSteps(options)}
|
|
1793
|
+
|
|
1794
|
+
${siteBuild(options)}
|
|
1693
1795
|
|
|
1694
1796
|
FROM ${options.baseImage} AS runner
|
|
1695
1797
|
WORKDIR /app
|
|
@@ -1723,8 +1825,7 @@ CMD ["sh", "-c", "cd ${appPath} && npm start"]
|
|
|
1723
1825
|
* @internal Exported for testing
|
|
1724
1826
|
*/
|
|
1725
1827
|
function generateViteStaticDockerfile(options) {
|
|
1726
|
-
const { port, appPath
|
|
1727
|
-
const { turbo } = layout(options);
|
|
1828
|
+
const { port, appPath } = options;
|
|
1728
1829
|
return `# syntax=docker/dockerfile:1
|
|
1729
1830
|
# Vite SPA Dockerfile — a turbo-pruned slice built to static files, served by Caddy
|
|
1730
1831
|
|
|
@@ -1733,7 +1834,10 @@ ${prunerStage(options)}
|
|
|
1733
1834
|
${depsStage(options)}
|
|
1734
1835
|
|
|
1735
1836
|
${builderStage(options, { args: options.publicUrlArgs ?? [] })}
|
|
1736
|
-
|
|
1837
|
+
|
|
1838
|
+
${siteWorkspaceSteps(options)}
|
|
1839
|
+
|
|
1840
|
+
${siteBuild(options)}
|
|
1737
1841
|
|
|
1738
1842
|
FROM ${STATIC_SITE_IMAGE} AS runner
|
|
1739
1843
|
|
|
@@ -1842,7 +1946,7 @@ function fromBuildRoot(layout, path) {
|
|
|
1842
1946
|
return joined === "" ? "." : joined;
|
|
1843
1947
|
}
|
|
1844
1948
|
/** Everything a template needs about one app, relative to the build root. */
|
|
1845
|
-
function appImageOptions(layout, appName, app, workspaceRoot) {
|
|
1949
|
+
function appImageOptions(layout, appName, app, workspaceRoot, apps = {}) {
|
|
1846
1950
|
const turboPackage = packageName(isAbsolute(app.path) ? app.path : join(workspaceRoot, app.path)) ?? appName;
|
|
1847
1951
|
return {
|
|
1848
1952
|
imageName: appName,
|
|
@@ -1858,10 +1962,49 @@ function appImageOptions(layout, appName, app, workspaceRoot) {
|
|
|
1858
1962
|
...app.type === "backend" ? {
|
|
1859
1963
|
gkmPaths: layout.gkmPaths,
|
|
1860
1964
|
...layout.workspacePackage && layout.workspacePackage !== turboPackage ? { prunePackages: [layout.workspacePackage] } : {}
|
|
1861
|
-
} : {}
|
|
1965
|
+
} : app.type === "web" ? siteWorkspace(layout, app, apps, workspaceRoot, turboPackage) : {}
|
|
1862
1966
|
};
|
|
1863
1967
|
}
|
|
1864
1968
|
/**
|
|
1969
|
+
* What a site's image carries of the gkm workspace: its config and construct
|
|
1970
|
+
* directories, the workspace's own package where it is nested in a monorepo
|
|
1971
|
+
* (it holds the CLI), and each backend the site calls — whose endpoints its
|
|
1972
|
+
* client is generated from, and whose package their imports resolve in.
|
|
1973
|
+
*/
|
|
1974
|
+
function siteWorkspace(layout, site, apps, workspaceRoot, turboPackage) {
|
|
1975
|
+
const clients = siteClients(site, apps);
|
|
1976
|
+
const packages = /* @__PURE__ */ new Set();
|
|
1977
|
+
if (layout.workspacePackage) packages.add(layout.workspacePackage);
|
|
1978
|
+
for (const { app } of clients) {
|
|
1979
|
+
const backend = apps[app];
|
|
1980
|
+
const name = packageName(isAbsolute(backend.path) ? backend.path : join(workspaceRoot, backend.path));
|
|
1981
|
+
if (name) packages.add(name);
|
|
1982
|
+
}
|
|
1983
|
+
packages.delete(turboPackage);
|
|
1984
|
+
return {
|
|
1985
|
+
gkmPaths: layout.gkmPaths,
|
|
1986
|
+
...packages.size > 0 ? { prunePackages: [...packages].sort() } : {},
|
|
1987
|
+
...clients.length > 0 ? { clients: clients.map(({ app }) => ({
|
|
1988
|
+
app,
|
|
1989
|
+
path: fromBuildRoot(layout, apps[app].path)
|
|
1990
|
+
})) } : {}
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* The backends whose clients a site imports: each it depends on that gkm
|
|
1995
|
+
* builds from endpoints — not one with an entry of its own, and not one that
|
|
1996
|
+
* turned OpenAPI off.
|
|
1997
|
+
*/
|
|
1998
|
+
function siteClients(site, apps) {
|
|
1999
|
+
return site.dependencies.filter((name) => {
|
|
2000
|
+
const app = apps[name];
|
|
2001
|
+
if (!app || app.type !== "backend" || app.entry) return false;
|
|
2002
|
+
if (app.openapi === false) return false;
|
|
2003
|
+
if (typeof app.openapi === "object" && app.openapi.enabled === false) return false;
|
|
2004
|
+
return true;
|
|
2005
|
+
}).sort().map((app) => ({ app }));
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
1865
2008
|
* The directories the workspace's construct globs start in, relative to its
|
|
1866
2009
|
* root: `./constructs/**\/*.ts` reads from `constructs`.
|
|
1867
2010
|
*/
|
|
@@ -5291,58 +5434,6 @@ logger.info({ port }, 'Worker started');
|
|
|
5291
5434
|
`;
|
|
5292
5435
|
}
|
|
5293
5436
|
//#endregion
|
|
5294
|
-
//#region src/secrets/encryption.ts
|
|
5295
|
-
var encryption_exports = /* @__PURE__ */ __exportAll({
|
|
5296
|
-
encryptSecrets: () => encryptSecrets,
|
|
5297
|
-
generateDefineOptions: () => generateDefineOptions,
|
|
5298
|
-
keyFingerprint: () => keyFingerprint
|
|
5299
|
-
});
|
|
5300
|
-
/** AES-256-GCM configuration */
|
|
5301
|
-
const ALGORITHM = "aes-256-gcm";
|
|
5302
|
-
const KEY_LENGTH = 32;
|
|
5303
|
-
const IV_LENGTH = 12;
|
|
5304
|
-
/**
|
|
5305
|
-
* Encrypt secrets for embedding in a bundle.
|
|
5306
|
-
* Uses AES-256-GCM with a randomly generated ephemeral key.
|
|
5307
|
-
*
|
|
5308
|
-
* @param secrets - Key-value pairs to encrypt
|
|
5309
|
-
* @returns Encrypted payload with ephemeral master key
|
|
5310
|
-
*/
|
|
5311
|
-
function encryptSecrets(secrets) {
|
|
5312
|
-
const masterKey = randomBytes(KEY_LENGTH);
|
|
5313
|
-
const iv = randomBytes(IV_LENGTH);
|
|
5314
|
-
const plaintext = JSON.stringify(secrets);
|
|
5315
|
-
const cipher = createCipheriv(ALGORITHM, masterKey, iv);
|
|
5316
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
5317
|
-
const authTag = cipher.getAuthTag();
|
|
5318
|
-
return {
|
|
5319
|
-
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
5320
|
-
iv: iv.toString("hex"),
|
|
5321
|
-
masterKey: masterKey.toString("hex")
|
|
5322
|
-
};
|
|
5323
|
-
}
|
|
5324
|
-
/**
|
|
5325
|
-
* Generate the define options for tsdown/esbuild.
|
|
5326
|
-
* These will be injected at build time.
|
|
5327
|
-
*/
|
|
5328
|
-
function generateDefineOptions(payload) {
|
|
5329
|
-
return {
|
|
5330
|
-
__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
|
|
5331
|
-
__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
|
|
5332
|
-
};
|
|
5333
|
-
}
|
|
5334
|
-
/**
|
|
5335
|
-
* A short, one-way name for a master key: the first 8 hex characters of its
|
|
5336
|
-
* SHA-256.
|
|
5337
|
-
*
|
|
5338
|
-
* Printed where the key itself used to be, so a person can tell which key a
|
|
5339
|
-
* build used — and match it against the one they hold — without the output
|
|
5340
|
-
* carrying a value that decrypts every secret of the stage.
|
|
5341
|
-
*/
|
|
5342
|
-
function keyFingerprint(masterKey) {
|
|
5343
|
-
return createHash("sha256").update(masterKey).digest("hex").slice(0, 8);
|
|
5344
|
-
}
|
|
5345
|
-
//#endregion
|
|
5346
5437
|
//#region src/build/workers.ts
|
|
5347
5438
|
/** A worker with background work and no backend app to build it from. */
|
|
5348
5439
|
var WorkerHasNoApp = class extends Error {
|
|
@@ -5830,7 +5921,7 @@ function validateImageRef(ref) {
|
|
|
5830
5921
|
}
|
|
5831
5922
|
//#endregion
|
|
5832
5923
|
//#region src/docker/index.ts
|
|
5833
|
-
const logger
|
|
5924
|
+
const logger = output;
|
|
5834
5925
|
/** `docker build` failed; what docker printed above says why. */
|
|
5835
5926
|
var DockerBuildFailed = class extends Error {
|
|
5836
5927
|
imageRef;
|
|
@@ -5881,7 +5972,7 @@ async function buildDockerImage(app, buildRoot, options) {
|
|
|
5881
5972
|
const tag = options.tag ?? "latest";
|
|
5882
5973
|
const registry = options.registry;
|
|
5883
5974
|
const fullImageName = validateImageRef(registry ? `${registry}/${app.imageName}:${tag}` : `${app.imageName}:${tag}`);
|
|
5884
|
-
logger
|
|
5975
|
+
logger.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5885
5976
|
try {
|
|
5886
5977
|
await run$1("docker", [
|
|
5887
5978
|
"build",
|
|
@@ -5895,7 +5986,7 @@ async function buildDockerImage(app, buildRoot, options) {
|
|
|
5895
5986
|
DOCKER_BUILDKIT: "1"
|
|
5896
5987
|
}
|
|
5897
5988
|
});
|
|
5898
|
-
logger
|
|
5989
|
+
logger.log(`✅ Docker image built: ${fullImageName}`);
|
|
5899
5990
|
} catch (error) {
|
|
5900
5991
|
throw new DockerBuildFailed(fullImageName, error);
|
|
5901
5992
|
}
|
|
@@ -5908,10 +5999,10 @@ async function pushDockerImage(imageName, options, cwd) {
|
|
|
5908
5999
|
const registry = options.registry;
|
|
5909
6000
|
if (!registry) throw new PushNeedsRegistry(imageName);
|
|
5910
6001
|
const fullImageName = validateImageRef(`${registry}/${imageName}:${tag}`);
|
|
5911
|
-
logger
|
|
6002
|
+
logger.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5912
6003
|
try {
|
|
5913
6004
|
await run$1("docker", ["push", fullImageName], { cwd });
|
|
5914
|
-
logger
|
|
6005
|
+
logger.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5915
6006
|
} catch (error) {
|
|
5916
6007
|
throw new DockerPushFailed(fullImageName, error);
|
|
5917
6008
|
}
|
|
@@ -5923,26 +6014,27 @@ async function pushDockerImage(imageName, options, cwd) {
|
|
|
5923
6014
|
async function workspaceDockerCommand(workspace, manifest, imageNames = {}, background = {}) {
|
|
5924
6015
|
const results = [];
|
|
5925
6016
|
const layout = imageLayout(workspace);
|
|
5926
|
-
logger
|
|
5927
|
-
logger
|
|
5928
|
-
logger
|
|
6017
|
+
logger.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
6018
|
+
logger.log(` Build root: ${layout.buildRoot}`);
|
|
6019
|
+
logger.log(` Package manager: ${layout.tools.packageManager}${layout.tools.packageManagerVersion ? `@${layout.tools.packageManagerVersion}` : ""}, turbo@${layout.tools.turboVersion}`);
|
|
5929
6020
|
for (const [appName, app] of Object.entries(workspace.apps)) {
|
|
5930
6021
|
if (app.type === "mobile") {
|
|
5931
|
-
logger
|
|
6022
|
+
logger.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
5932
6023
|
continue;
|
|
5933
6024
|
}
|
|
5934
6025
|
const buildType = app.entry ? "entry" : app.type;
|
|
5935
|
-
logger
|
|
6026
|
+
logger.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
5936
6027
|
const dockerfile = appDockerfile(appName, app, {
|
|
5937
6028
|
layout,
|
|
5938
6029
|
workspaceRoot: workspace.root,
|
|
6030
|
+
apps: workspace.apps,
|
|
5939
6031
|
...manifest ? { manifest } : {}
|
|
5940
6032
|
});
|
|
5941
6033
|
const path = dockerfileOf(appName, app.path);
|
|
5942
6034
|
const dockerfilePath = join(workspace.root, path);
|
|
5943
6035
|
await mkdir(dirname(dockerfilePath), { recursive: true });
|
|
5944
6036
|
await writeFile(dockerfilePath, dockerfile);
|
|
5945
|
-
logger
|
|
6037
|
+
logger.log(` Generated: ${path}`);
|
|
5946
6038
|
results.push({
|
|
5947
6039
|
appName,
|
|
5948
6040
|
type: app.type,
|
|
@@ -5951,7 +6043,7 @@ async function workspaceDockerCommand(workspace, manifest, imageNames = {}, back
|
|
|
5951
6043
|
});
|
|
5952
6044
|
}
|
|
5953
6045
|
for (const worker of manifest ? workerUnits(workspace, manifest, background) : []) {
|
|
5954
|
-
logger
|
|
6046
|
+
logger.log(`\n 📄 Generating Dockerfile for ${worker.name} (worker ${worker.id}, built from ${worker.app})`);
|
|
5955
6047
|
const dockerfile = workerDockerfile(worker, workspace.apps[worker.app], {
|
|
5956
6048
|
layout,
|
|
5957
6049
|
workspaceRoot: workspace.root
|
|
@@ -5960,7 +6052,7 @@ async function workspaceDockerCommand(workspace, manifest, imageNames = {}, back
|
|
|
5960
6052
|
const dockerfilePath = join(workspace.root, path);
|
|
5961
6053
|
await mkdir(dirname(dockerfilePath), { recursive: true });
|
|
5962
6054
|
await writeFile(dockerfilePath, dockerfile);
|
|
5963
|
-
logger
|
|
6055
|
+
logger.log(` Generated: ${path}`);
|
|
5964
6056
|
results.push({
|
|
5965
6057
|
appName: worker.name,
|
|
5966
6058
|
type: "backend",
|
|
@@ -5969,13 +6061,13 @@ async function workspaceDockerCommand(workspace, manifest, imageNames = {}, back
|
|
|
5969
6061
|
});
|
|
5970
6062
|
}
|
|
5971
6063
|
const dockerignorePath = await ensureDockerignore(layout.buildRoot);
|
|
5972
|
-
logger
|
|
6064
|
+
logger.log(`\n Ensured: ${dockerignorePath}`);
|
|
5973
6065
|
const composePath = await writeConstructsCompose(workspace);
|
|
5974
|
-
logger
|
|
5975
|
-
logger
|
|
6066
|
+
logger.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`);
|
|
6067
|
+
logger.log(`\n📋 Build commands (from ${layout.buildRoot}):`);
|
|
5976
6068
|
for (const result of results) {
|
|
5977
6069
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
5978
|
-
logger
|
|
6070
|
+
logger.log(` ${icon} docker build -f ${relative(layout.buildRoot, result.dockerfile)} -t ${result.imageName} .`);
|
|
5979
6071
|
}
|
|
5980
6072
|
printRunInstructions(workspace);
|
|
5981
6073
|
return {
|
|
@@ -5991,7 +6083,7 @@ async function workspaceDockerCommand(workspace, manifest, imageNames = {}, back
|
|
|
5991
6083
|
* framework. The one choice every builder of images makes.
|
|
5992
6084
|
*/
|
|
5993
6085
|
function appDockerfile(appName, app, options) {
|
|
5994
|
-
const image = appImageOptions(options.layout, appName, app, options.workspaceRoot);
|
|
6086
|
+
const image = appImageOptions(options.layout, appName, app, options.workspaceRoot, options.apps);
|
|
5995
6087
|
if (app.type === "web") return siteDockerfile(appName, app, {
|
|
5996
6088
|
image,
|
|
5997
6089
|
...options.manifest ? { manifest: options.manifest } : {},
|
|
@@ -6094,199 +6186,14 @@ async function writeConstructsCompose(workspace) {
|
|
|
6094
6186
|
stage: workspace.stages.local,
|
|
6095
6187
|
start: false
|
|
6096
6188
|
});
|
|
6097
|
-
logger
|
|
6189
|
+
logger.log(` Generated: ${COMPOSE_PATH} (project root)`);
|
|
6098
6190
|
return join(workspace.root, COMPOSE_PATH);
|
|
6099
6191
|
}
|
|
6100
6192
|
function printRunInstructions(workspace) {
|
|
6101
6193
|
const project = existsSync(join(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
|
|
6102
|
-
logger
|
|
6103
|
-
logger
|
|
6104
|
-
logger
|
|
6105
|
-
}
|
|
6106
|
-
//#endregion
|
|
6107
|
-
//#region src/deploy/docker.ts
|
|
6108
|
-
const logger = output;
|
|
6109
|
-
/**
|
|
6110
|
-
* The id the Dockerfile templates mount the encrypted credentials under:
|
|
6111
|
-
* `RUN --mount=type=secret,id=gkm_credentials`.
|
|
6112
|
-
*/
|
|
6113
|
-
const CREDENTIALS_SECRET_ID = "gkm_credentials";
|
|
6114
|
-
/**
|
|
6115
|
-
* Get the full image reference
|
|
6116
|
-
*/
|
|
6117
|
-
function getImageRef(registry, imageName, tag) {
|
|
6118
|
-
if (registry) return `${registry}/${imageName}:${tag}`;
|
|
6119
|
-
return `${imageName}:${tag}`;
|
|
6120
|
-
}
|
|
6121
|
-
/**
|
|
6122
|
-
* The `docker build` argv for an image.
|
|
6123
|
-
*
|
|
6124
|
-
* One element per value, so nothing in a ref or a build arg is read by a
|
|
6125
|
-
* shell, and `--opt=value` rather than `--opt value`, so a value that starts
|
|
6126
|
-
* with `-` cannot be taken for one of docker's own flags.
|
|
6127
|
-
*
|
|
6128
|
-
* @internal Exported for testing
|
|
6129
|
-
*/
|
|
6130
|
-
function dockerBuildArgs(options) {
|
|
6131
|
-
const { dockerfilePath, imageRef, buildArgs = [], credentialsFile } = options;
|
|
6132
|
-
return [
|
|
6133
|
-
"build",
|
|
6134
|
-
"--platform=linux/amd64",
|
|
6135
|
-
`--file=${dockerfilePath}`,
|
|
6136
|
-
`--tag=${imageRef}`,
|
|
6137
|
-
...buildArgs.map((arg) => `--build-arg=${arg}`),
|
|
6138
|
-
...credentialsFile ? [`--secret=id=${CREDENTIALS_SECRET_ID},src=${credentialsFile}`] : [],
|
|
6139
|
-
"."
|
|
6140
|
-
];
|
|
6141
|
-
}
|
|
6142
|
-
/**
|
|
6143
|
-
* The build arg that names which credentials an image embeds: a hash of the
|
|
6144
|
-
* ciphertext, which is not a secret. A BuildKit secret is no part of the
|
|
6145
|
-
* build cache's key, so without it a rebuild with credentials encrypted under
|
|
6146
|
-
* a new key would reuse the layer that embedded the old ones.
|
|
6147
|
-
*/
|
|
6148
|
-
function credentialsBuildArg(credentials) {
|
|
6149
|
-
return `GKM_CIPHERTEXT_HASH=${createHash("sha256").update(`${credentials.encrypted}\n${credentials.iv}`).digest("hex").slice(0, 16)}`;
|
|
6150
|
-
}
|
|
6151
|
-
/** The secret file's content: ciphertext, then IV, one per line. */
|
|
6152
|
-
function credentialsFileContent(credentials) {
|
|
6153
|
-
return `${credentials.encrypted}\n${credentials.iv}\n`;
|
|
6154
|
-
}
|
|
6155
|
-
/**
|
|
6156
|
-
* Write the encrypted credentials where `docker build --secret` reads them.
|
|
6157
|
-
*
|
|
6158
|
-
* Two lines — ciphertext, then IV — so a template can split them with `sed`
|
|
6159
|
-
* in any base image. Owner-only, in a directory of its own, and the returned
|
|
6160
|
-
* cleanup removes both whatever the build did.
|
|
6161
|
-
*
|
|
6162
|
-
* @internal Exported for testing
|
|
6163
|
-
*/
|
|
6164
|
-
async function writeCredentialsFile(credentials) {
|
|
6165
|
-
const dir = await mkdtemp(join(tmpdir(), "gkm-credentials-"));
|
|
6166
|
-
const path = join(dir, CREDENTIALS_SECRET_ID);
|
|
6167
|
-
await writeFile(path, credentialsFileContent(credentials), { mode: 384 });
|
|
6168
|
-
await chmod(path, 384);
|
|
6169
|
-
return {
|
|
6170
|
-
path,
|
|
6171
|
-
cleanup: () => rm(dir, {
|
|
6172
|
-
recursive: true,
|
|
6173
|
-
force: true
|
|
6174
|
-
})
|
|
6175
|
-
};
|
|
6176
|
-
}
|
|
6177
|
-
/**
|
|
6178
|
-
* Build Docker image
|
|
6179
|
-
* @param imageRef - Full image reference (registry/name:tag)
|
|
6180
|
-
* @param buildArgs - Build arguments to pass to docker build
|
|
6181
|
-
* @param credentials - Encrypted credentials, passed as a build secret
|
|
6182
|
-
*/
|
|
6183
|
-
async function buildImage(imageRef, buildArgs, credentials, { cwd, dockerfile, signal, stdio }) {
|
|
6184
|
-
logger.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
6185
|
-
logger.log(" Generating Dockerfile...");
|
|
6186
|
-
const { buildRoot } = await dockerCommand({ cwd });
|
|
6187
|
-
const dockerfilePath = relative(buildRoot, dockerfile);
|
|
6188
|
-
const secret = credentials ? await writeCredentialsFile(credentials) : void 0;
|
|
6189
|
-
try {
|
|
6190
|
-
await run$1("docker", dockerBuildArgs({
|
|
6191
|
-
dockerfilePath,
|
|
6192
|
-
imageRef,
|
|
6193
|
-
buildArgs: [...buildArgs ?? [], ...credentials ? [credentialsBuildArg(credentials)] : []],
|
|
6194
|
-
credentialsFile: secret?.path
|
|
6195
|
-
}), {
|
|
6196
|
-
cwd: buildRoot,
|
|
6197
|
-
env: {
|
|
6198
|
-
...process.env,
|
|
6199
|
-
DOCKER_BUILDKIT: "1"
|
|
6200
|
-
},
|
|
6201
|
-
...signal ? { signal } : {},
|
|
6202
|
-
...stdio ? { stdio } : {}
|
|
6203
|
-
});
|
|
6204
|
-
logger.log(`✅ Image built: ${imageRef}`);
|
|
6205
|
-
} catch (error) {
|
|
6206
|
-
throw new DockerBuildFailed(imageRef, error);
|
|
6207
|
-
} finally {
|
|
6208
|
-
await secret?.cleanup();
|
|
6209
|
-
}
|
|
6210
|
-
}
|
|
6211
|
-
/**
|
|
6212
|
-
* Push Docker image to registry
|
|
6213
|
-
*/
|
|
6214
|
-
async function pushImage(imageRef, { cwd, signal, stdio }) {
|
|
6215
|
-
logger.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
6216
|
-
try {
|
|
6217
|
-
await run$1("docker", ["push", imageRef], {
|
|
6218
|
-
cwd,
|
|
6219
|
-
...signal ? { signal } : {},
|
|
6220
|
-
...stdio ? { stdio } : {}
|
|
6221
|
-
});
|
|
6222
|
-
logger.log(`✅ Image pushed: ${imageRef}`);
|
|
6223
|
-
} catch (error) {
|
|
6224
|
-
throw new DockerPushFailed(imageRef, error);
|
|
6225
|
-
}
|
|
6226
|
-
}
|
|
6227
|
-
/**
|
|
6228
|
-
* The digest the registry gave a pushed image, `sha256:…`.
|
|
6229
|
-
*
|
|
6230
|
-
* A tag can be pushed over — by the next deploy, or by anything else that
|
|
6231
|
-
* shares the repository — and the digest is what says which image a stage
|
|
6232
|
-
* actually ran. `RepoDigests` has one entry per repository the image was
|
|
6233
|
-
* pushed to, so the one for this ref's repository is picked by name.
|
|
6234
|
-
*
|
|
6235
|
-
* @internal Exported for testing
|
|
6236
|
-
*/
|
|
6237
|
-
async function pushedDigest(imageRef, { cwd, signal } = { cwd: process.cwd() }) {
|
|
6238
|
-
const repository = imageRef.split("@")[0].replace(/:[\w][\w.-]*$/, "");
|
|
6239
|
-
const output = await runOutput("docker", [
|
|
6240
|
-
"image",
|
|
6241
|
-
"inspect",
|
|
6242
|
-
"--format={{json .RepoDigests}}",
|
|
6243
|
-
imageRef
|
|
6244
|
-
], {
|
|
6245
|
-
cwd,
|
|
6246
|
-
...signal ? { signal } : {}
|
|
6247
|
-
});
|
|
6248
|
-
let digests;
|
|
6249
|
-
try {
|
|
6250
|
-
digests = JSON.parse(output.trim() || "[]");
|
|
6251
|
-
} catch {
|
|
6252
|
-
return;
|
|
6253
|
-
}
|
|
6254
|
-
if (!Array.isArray(digests)) return void 0;
|
|
6255
|
-
return digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`))?.slice(repository.length + 1);
|
|
6256
|
-
}
|
|
6257
|
-
/**
|
|
6258
|
-
* Deploy using Docker (build and optionally push image)
|
|
6259
|
-
*/
|
|
6260
|
-
async function deployDocker(options) {
|
|
6261
|
-
const { stage, tag, skipPush, masterKey, config, buildArgs, credentials } = options;
|
|
6262
|
-
const imageName = config.imageName;
|
|
6263
|
-
const imageRef = validateImageRef(getImageRef(config.registry, imageName, tag));
|
|
6264
|
-
const cwd = options.cwd ?? process.cwd();
|
|
6265
|
-
const docker = {
|
|
6266
|
-
cwd,
|
|
6267
|
-
dockerfile: join(cwd, options.dockerfile ?? dockerfileOf(config.appName ?? imageName, options.appPath ?? ".")),
|
|
6268
|
-
...options.signal ? { signal: options.signal } : {},
|
|
6269
|
-
...options.stdio ? { stdio: options.stdio } : {}
|
|
6270
|
-
};
|
|
6271
|
-
await buildImage(imageRef, buildArgs, credentials, docker);
|
|
6272
|
-
let digest;
|
|
6273
|
-
if (!skipPush) {
|
|
6274
|
-
if (!config.registry) logger.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
6275
|
-
else {
|
|
6276
|
-
await pushImage(imageRef, docker);
|
|
6277
|
-
digest = await pushedDigest(imageRef, docker);
|
|
6278
|
-
}
|
|
6279
|
-
}
|
|
6280
|
-
logger.log("\n✅ Docker deployment ready!");
|
|
6281
|
-
logger.log(`\n📋 Deployment details:`);
|
|
6282
|
-
logger.log(` Image: ${imageRef}`);
|
|
6283
|
-
logger.log(` Stage: ${stage}`);
|
|
6284
|
-
if (masterKey) logger.log(`\n🔐 Run the container with GKM_MASTER_KEY set to the key with fingerprint ${keyFingerprint(masterKey)}.`);
|
|
6285
|
-
return {
|
|
6286
|
-
imageRef,
|
|
6287
|
-
...digest ? { digest } : {},
|
|
6288
|
-
masterKey
|
|
6289
|
-
};
|
|
6194
|
+
logger.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
|
|
6195
|
+
logger.log(" gkm setup # starts the containers and creates what they hold");
|
|
6196
|
+
logger.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
|
|
6290
6197
|
}
|
|
6291
6198
|
//#endregion
|
|
6292
6199
|
//#region src/deploy/generated.ts
|
|
@@ -6590,6 +6497,6 @@ function setPostgresBackupId(state, backupId) {
|
|
|
6590
6497
|
if (state.backups) state.backups.postgresBackupId = backupId;
|
|
6591
6498
|
}
|
|
6592
6499
|
//#endregion
|
|
6593
|
-
export {
|
|
6500
|
+
export { LOCAL_CA_PATH as $, applicationName as $t, ExternalServicesNotConfigured as A, providerOf as At, suppliedOnly as B, isPortAvailable as Bt, NoDomainForStage as C, WORKER_PORT as Ct, otelEnv as D, isImageBuild as Dt, isOtlpDestination as E, imageBuildCredentials as Et, externalServices as F, portKeys as Ft, QueueGenerator as G, secretsStoreFor as Gt, WorkerGenerator as H, loadSecretsForApp as Ht, manifestServiceDeclarations as I, primaryPortKey as It, backendsOf as J, FileSecretsStore as Jt, CronGenerator as K, storeFromConfig as Kt, parseDevServices as L, createCredentialsPreload as Lt, UnknownDevService as M, DEFAULT_IMAGES as Mt, assertExternalServices as N, describeServices as Nt, DEV_SERVICES as O, cacheBackendFor as Ot, devServicesUsed as P, fakeKey as Pt, workspace_exports as Q, readKey as Qt, reportDevServices as R, createEntryWrapper as Rt, validateImageRef as S, TURBO_VERSION as St, resolveHost as T, findBuildRoot as Tt, workerBundleName as U, prepareEntryCredentials as Ut, workerUnits as V, loadEnvFiles as Vt, workerEntryDir as W, isRemoteStore as Wt, derivedContainers as X, keystoreProject as Xt, constructGlobs as Y, getKeyPath as Yt, reconcileWorkspace as Z, projectKey as Zt, appDockerfile as _, planFor as _t, recordRelease as a, ownershipMarker as an, postgresDatabaseNames as at, workerDockerfile as b, composeBuildPaths as bt, setBackupState as c, maskPassword as cn, composeFor as ct, setGeneratedSecret as d, withCustomSecret as dn, appEnvKeys as dt, deployIdentity as en, forgetState as et, setPostgresBackupId as f, dockerfileOf as ft, DockerPushFailed as g, localRolePassword as gt, DockerBuildFailed as h, envFor as ht, isDnsVerified as i, markerOf as in, bucketPolicies as it, STORAGE_KEY_PAIR as j, storageBackendFor as jt, DevServicesNeedServerTarget as k, eventsBackendFor as kt, setDnsRecord as l, storage_exports as ln, bucketClient as lt, withGeneratedSecrets as m, workerEnvKeys as mt, getBackupState as n, imageRef as nn, applyPolicies as nt, recordRollback as o, projectName as on, postgresStatements as ot, DeployJournal as p, networkEnv as pt, FAKE_ENV as q, store_exports as qt, getGeneratedSecret as r, isOwnedBy as rn, applyPostgres as rt, setApplicationId as s, initStageSecrets as sn, quoteIdentifier as st, createEmptyState as t, imageName as tn, applyBuckets as tt, setDnsVerification as u, toEmbeddableSecrets as un, pgClient as ut, dockerCommand as v, resourceName as vt, isMainFrontendApp as w, detectPackageManager as wt, workerDockerfileOf as x, imageLayout as xt, ensureDockerignore as y, hostFor as yt, requiredStageKeys as z, findAvailablePort as zt };
|
|
6594
6501
|
|
|
6595
|
-
//# sourceMappingURL=state-
|
|
6502
|
+
//# sourceMappingURL=state-CUecOCNJ.mjs.map
|