@geekmidas/cli 10.0.0-alpha.54 → 10.0.0-alpha.56
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/README.md +11 -10
- package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-cL-6oQ5r.mjs} +2 -2
- package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-cL-6oQ5r.mjs.map} +1 -1
- package/dist/LegacyStateStore-ByRWQ9s1.mjs +89 -0
- package/dist/LegacyStateStore-ByRWQ9s1.mjs.map +1 -0
- package/dist/LegacyStateStore-C_OFs1gN.cjs +89 -0
- package/dist/LegacyStateStore-C_OFs1gN.cjs.map +1 -0
- package/dist/LocalStateStore-BupdV-VN.mjs +163 -0
- package/dist/LocalStateStore-BupdV-VN.mjs.map +1 -0
- package/dist/LocalStateStore-DDDU0HkI.cjs +163 -0
- package/dist/LocalStateStore-DDDU0HkI.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-BN0PIZxd.cjs → OpenApiTsGenerator-BLRQ7Iu9.cjs} +58 -59
- package/dist/OpenApiTsGenerator-BLRQ7Iu9.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs → OpenApiTsGenerator-NThANByQ.mjs} +58 -59
- package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs.map → OpenApiTsGenerator-NThANByQ.mjs.map} +1 -1
- package/dist/S3StateStore-C7eBae0G.cjs +160 -0
- package/dist/S3StateStore-C7eBae0G.cjs.map +1 -0
- package/dist/S3StateStore-FhzyENrN.mjs +161 -0
- package/dist/S3StateStore-FhzyENrN.mjs.map +1 -0
- package/dist/SSMStateStore-DD7jKrCO.cjs +146 -0
- package/dist/SSMStateStore-DD7jKrCO.cjs.map +1 -0
- package/dist/SSMStateStore-oda9xOGc.mjs +147 -0
- package/dist/SSMStateStore-oda9xOGc.mjs.map +1 -0
- package/dist/{backends-Dfeu5Dgc.mjs → backends-BcHL7Gy_.mjs} +2 -2
- package/dist/{backends-Dfeu5Dgc.mjs.map → backends-BcHL7Gy_.mjs.map} +1 -1
- package/dist/{backends-D5aDWxuA.cjs → backends-_gQdAzBK.cjs} +2 -2
- package/dist/{backends-D5aDWxuA.cjs.map → backends-_gQdAzBK.cjs.map} +1 -1
- package/dist/{bundler--fyybf1N.mjs → bundler-URImKj-A.mjs} +2 -2
- package/dist/{bundler--fyybf1N.mjs.map → bundler-URImKj-A.mjs.map} +1 -1
- package/dist/{config-SOkDH-n1.mjs → config-20V08LZp.mjs} +2 -3
- package/dist/config-20V08LZp.mjs.map +1 -0
- package/dist/{config-Cvr5fCbn.cjs → config-CP6wsLHA.cjs} +2 -3
- package/dist/config-CP6wsLHA.cjs.map +1 -0
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/{encryption-tB4LqLKN.cjs → encryption-10Srqiix.cjs} +4 -4
- package/dist/{encryption-tB4LqLKN.cjs.map → encryption-10Srqiix.cjs.map} +1 -1
- package/dist/{encryption-O-XdeNxe.mjs → encryption-KQTYFEzU.mjs} +4 -4
- package/dist/{encryption-O-XdeNxe.mjs.map → encryption-KQTYFEzU.mjs.map} +1 -1
- package/dist/{github-BGx4U61b.cjs → github-DtRUkHN8.cjs} +3 -3
- package/dist/{github-BGx4U61b.cjs.map → github-DtRUkHN8.cjs.map} +1 -1
- package/dist/{github-U_frvZgD.mjs → github-y7OEj59M.mjs} +3 -3
- package/dist/{github-U_frvZgD.mjs.map → github-y7OEj59M.mjs.map} +1 -1
- package/dist/{index-fU3qHy7c.d.cts → index-DMcjpvd-.d.cts} +2 -2
- package/dist/{index-DYh5TrsZ.d.mts.map → index-DMcjpvd-.d.cts.map} +1 -1
- package/dist/{index-DYh5TrsZ.d.mts → index-RM92lx8A.d.mts} +2 -2
- package/dist/{index-fU3qHy7c.d.cts.map → index-RM92lx8A.d.mts.map} +1 -1
- package/dist/index.cjs +1176 -1281
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1174 -1287
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-DWDaaBfo.mjs → migrate-CS3DkO6y.mjs} +4 -4
- package/dist/{migrate-DWDaaBfo.mjs.map → migrate-CS3DkO6y.mjs.map} +1 -1
- package/dist/{migrate-DN5VM5vy.cjs → migrate-DQ54y2WD.cjs} +4 -4
- package/dist/{migrate-DN5VM5vy.cjs.map → migrate-DQ54y2WD.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{ready-C8wG_d0v.mjs → ready-BSpUZnNo.mjs} +2 -2
- package/dist/ready-BSpUZnNo.mjs.map +1 -0
- package/dist/{ready-VkNwfJu7.cjs → ready-MUhVJDHe.cjs} +2 -2
- package/dist/ready-MUhVJDHe.cjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{types-CT0ymlBk.d.mts → types-BIyIQUR-.d.cts} +138 -33
- package/dist/types-BIyIQUR-.d.cts.map +1 -0
- package/dist/{types-DYDT4FfE.d.cts → types-CmQl6zaP.d.mts} +137 -34
- package/dist/types-CmQl6zaP.d.mts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- 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-DFYu-iRP.cjs → workspace-BbPJVCto.cjs} +6 -6
- package/dist/{workspace-DFYu-iRP.cjs.map → workspace-BbPJVCto.cjs.map} +1 -1
- package/dist/{workspace-BcCiLzTO.cjs → workspace-BpdXIeWV.cjs} +22 -18
- package/dist/{workspace-BcCiLzTO.cjs.map → workspace-BpdXIeWV.cjs.map} +1 -1
- package/dist/{workspace-bOsks1uk.mjs → workspace-C2mFV53i.mjs} +22 -18
- package/dist/{workspace-bOsks1uk.mjs.map → workspace-C2mFV53i.mjs.map} +1 -1
- package/dist/{workspace-Bwl0728I.mjs → workspace-CgWcE_Yr.mjs} +8 -8
- package/dist/{workspace-Bwl0728I.mjs.map → workspace-CgWcE_Yr.mjs.map} +1 -1
- package/package.json +13 -14
- package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +0 -1
- package/dist/config-Cvr5fCbn.cjs.map +0 -1
- package/dist/config-SOkDH-n1.mjs.map +0 -1
- package/dist/ready-C8wG_d0v.mjs.map +0 -1
- package/dist/ready-VkNwfJu7.cjs.map +0 -1
- package/dist/types-CT0ymlBk.d.mts.map +0 -1
- package/dist/types-DYDT4FfE.d.cts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
3
|
import { i as ConstructGenerator, t as discover } from "./discover-Cf2jAcgF.mjs";
|
|
4
|
-
import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-
|
|
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-
|
|
4
|
+
import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-20V08LZp.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-C2mFV53i.mjs";
|
|
6
6
|
import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
|
|
7
7
|
import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
|
|
8
|
-
import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-
|
|
9
|
-
import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-
|
|
8
|
+
import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-CgWcE_Yr.mjs";
|
|
9
|
+
import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-NThANByQ.mjs";
|
|
10
10
|
import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
|
|
11
|
-
import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-
|
|
12
|
-
import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-
|
|
11
|
+
import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-BSpUZnNo.mjs";
|
|
12
|
+
import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-BcHL7Gy_.mjs";
|
|
13
13
|
import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import { Command } from "commander";
|
|
16
16
|
import { stdin, stdout } from "node:process";
|
|
17
17
|
import * as readline from "node:readline/promises";
|
|
18
18
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
19
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
|
-
import { homedir } from "node:os";
|
|
19
|
+
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
|
+
import { homedir, hostname, tmpdir, userInfo } from "node:os";
|
|
21
21
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
22
|
-
import { execFile, execFileSync, execSync, spawn } from "node:child_process";
|
|
23
22
|
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, externalApiUrl, kebabCase, migrationFolder, mobileOrigins, provideKey, provisionOrder, schemeBase, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
24
23
|
import fg from "fast-glob";
|
|
24
|
+
import { execFile, execFileSync, execSync, spawn } from "node:child_process";
|
|
25
25
|
import chokidar from "chokidar";
|
|
26
|
-
import { createCipheriv, createHash, randomBytes } from "node:crypto";
|
|
26
|
+
import { createCipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
27
27
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
28
28
|
import { generateKeyring } from "@geekmidas/constructs/encryption";
|
|
29
29
|
import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
|
|
@@ -42,7 +42,7 @@ import { Queue } from "@geekmidas/constructs/queue";
|
|
|
42
42
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
43
43
|
import { lookup } from "node:dns/promises";
|
|
44
44
|
//#region package.json
|
|
45
|
-
var version = "10.0.0-alpha.
|
|
45
|
+
var version = "10.0.0-alpha.56";
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/auth/credentials.ts
|
|
48
48
|
/**
|
|
@@ -388,12 +388,12 @@ function withOwningTsconfigJsx(nodeOptions = "") {
|
|
|
388
388
|
* unhelpful.
|
|
389
389
|
*
|
|
390
390
|
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
391
|
-
*
|
|
392
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
393
|
-
* the line in the same place.
|
|
391
|
+
* asks for a password, and a setup that silently escalates on every run is a
|
|
392
|
+
* worse trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
393
|
+
* draws the line in the same place.
|
|
394
394
|
*/
|
|
395
395
|
const logger$11 = console;
|
|
396
|
-
const run = promisify(execFile);
|
|
396
|
+
const run$1 = promisify(execFile);
|
|
397
397
|
/**
|
|
398
398
|
* How each platform is told about a new authority.
|
|
399
399
|
*
|
|
@@ -404,15 +404,14 @@ const run = promisify(execFile);
|
|
|
404
404
|
*/
|
|
405
405
|
function installation(platform, certificate) {
|
|
406
406
|
if (platform === "darwin") return {
|
|
407
|
-
describe: "
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
describe: "your login keychain",
|
|
408
|
+
asks: "macOS asks once — your password or Touch ID — to change trust settings.",
|
|
409
|
+
steps: [["security", [
|
|
410
410
|
"add-trusted-cert",
|
|
411
|
-
"-d",
|
|
412
411
|
"-r",
|
|
413
412
|
"trustRoot",
|
|
414
413
|
"-k",
|
|
415
|
-
"
|
|
414
|
+
join(homedir(), "Library/Keychains/login.keychain-db"),
|
|
416
415
|
certificate
|
|
417
416
|
]]]
|
|
418
417
|
};
|
|
@@ -420,6 +419,7 @@ function installation(platform, certificate) {
|
|
|
420
419
|
const debian = existsSync("/usr/local/share/ca-certificates");
|
|
421
420
|
return {
|
|
422
421
|
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
422
|
+
asks: "This needs sudo: the system trust store is the only one.",
|
|
423
423
|
steps: debian ? [["sudo", [
|
|
424
424
|
"cp",
|
|
425
425
|
certificate,
|
|
@@ -443,15 +443,15 @@ async function trustCommand(options = {}) {
|
|
|
443
443
|
const { workspace } = await loadWorkspaceConfig();
|
|
444
444
|
const certificate = join(workspace.root, LOCAL_CA_PATH);
|
|
445
445
|
if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
|
|
446
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
446
|
+
const { describe, asks, steps } = installation(process.platform, certificate);
|
|
447
447
|
logger$11.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
448
448
|
logger$11.log(` ${certificate}\n`);
|
|
449
449
|
if (options.dryRun) {
|
|
450
450
|
for (const [command, args] of steps) logger$11.log(` ${command} ${args.join(" ")}`);
|
|
451
451
|
return;
|
|
452
452
|
}
|
|
453
|
-
logger$11.log(
|
|
454
|
-
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
453
|
+
logger$11.log(` ${asks}\n`);
|
|
454
|
+
for (const [command, args] of steps) await run$1(command, args, { stdio: "inherit" });
|
|
455
455
|
logger$11.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
456
456
|
}
|
|
457
457
|
/**
|
|
@@ -459,7 +459,7 @@ async function trustCommand(options = {}) {
|
|
|
459
459
|
*
|
|
460
460
|
* A machine's answer, not the project's: whether a laptop installs a root
|
|
461
461
|
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
462
|
-
* machine previously answered, then a prompt. Never a silent
|
|
462
|
+
* machine previously answered, then a prompt. Never a silent escalation — that is
|
|
463
463
|
* the one thing a setup command should not do on somebody's behalf.
|
|
464
464
|
*
|
|
465
465
|
* Silent and free when the machine already trusts it, which is what makes it
|
|
@@ -476,7 +476,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
476
476
|
const { install } = await prompts({
|
|
477
477
|
type: "confirm",
|
|
478
478
|
name: "install",
|
|
479
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (
|
|
479
|
+
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (asks for your password)",
|
|
480
480
|
initial: false
|
|
481
481
|
});
|
|
482
482
|
if (!install) {
|
|
@@ -517,7 +517,7 @@ async function recordDeclined(root) {
|
|
|
517
517
|
async function isTrusted(url) {
|
|
518
518
|
const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
|
|
519
519
|
try {
|
|
520
|
-
await run(process.execPath, [
|
|
520
|
+
await run$1(process.execPath, [
|
|
521
521
|
"--use-system-ca",
|
|
522
522
|
"-e",
|
|
523
523
|
`fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
|
|
@@ -1333,30 +1333,6 @@ function normalizeTelescopeConfig(config) {
|
|
|
1333
1333
|
};
|
|
1334
1334
|
}
|
|
1335
1335
|
/**
|
|
1336
|
-
* Normalize studio configuration
|
|
1337
|
-
* @internal Exported for testing
|
|
1338
|
-
*/
|
|
1339
|
-
function normalizeStudioConfig(config) {
|
|
1340
|
-
if (config === false) return;
|
|
1341
|
-
if (typeof config === "string") {
|
|
1342
|
-
const { path: studioPath, importPattern: studioImportPattern } = parseModuleConfig(config, "studio");
|
|
1343
|
-
return {
|
|
1344
|
-
enabled: true,
|
|
1345
|
-
studioPath,
|
|
1346
|
-
studioImportPattern,
|
|
1347
|
-
path: "/__studio",
|
|
1348
|
-
schema: "public"
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
if (!(config === true || config === void 0 || config.enabled !== false)) return;
|
|
1352
|
-
const studioConfig = typeof config === "object" ? config : {};
|
|
1353
|
-
return {
|
|
1354
|
-
enabled: true,
|
|
1355
|
-
path: studioConfig.path ?? "/__studio",
|
|
1356
|
-
schema: studioConfig.schema ?? "public"
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
1336
|
* Normalize hooks configuration
|
|
1361
1337
|
* @internal Exported for testing
|
|
1362
1338
|
*/
|
|
@@ -1368,10 +1344,10 @@ function normalizeHooksConfig(config, cwd = process.cwd()) {
|
|
|
1368
1344
|
/**
|
|
1369
1345
|
* What a `--production` build is.
|
|
1370
1346
|
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1347
|
+
* Background work — queues polled, crons scheduled, subscribers drained — is
|
|
1348
|
+
* included here, and `buildApp` takes it back out when the app serves a
|
|
1349
|
+
* RestApi: an API's image answers HTTP and nothing else, and that work belongs
|
|
1350
|
+
* to a Worker. The build still writes their files on every server build.
|
|
1375
1351
|
*
|
|
1376
1352
|
* @internal Exported for testing
|
|
1377
1353
|
*/
|
|
@@ -1433,7 +1409,6 @@ async function devCommand(options) {
|
|
|
1433
1409
|
if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1434
1410
|
}
|
|
1435
1411
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1436
|
-
const studio = normalizeStudioConfig(config.studio);
|
|
1437
1412
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1438
1413
|
const enableOpenApi = resolveOpenApiConfig(config).enabled;
|
|
1439
1414
|
const build = (bustCache = false) => quietly(() => buildApp({
|
|
@@ -1444,7 +1419,7 @@ async function devCommand(options) {
|
|
|
1444
1419
|
enableOpenApi,
|
|
1445
1420
|
cacheBackend: cacheBackendFor(providerOf(workspace ?? config)),
|
|
1446
1421
|
telescope,
|
|
1447
|
-
|
|
1422
|
+
databaseApi: true,
|
|
1448
1423
|
hooks,
|
|
1449
1424
|
skipBundle: true,
|
|
1450
1425
|
bustCache,
|
|
@@ -1477,7 +1452,7 @@ async function devCommand(options) {
|
|
|
1477
1452
|
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
1478
1453
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1479
1454
|
}
|
|
1480
|
-
const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope,
|
|
1455
|
+
const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, initial.databaseApi, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
|
|
1481
1456
|
devServer.label = workspaceAppName ?? "server";
|
|
1482
1457
|
devServer.publicUrl = publicUrl;
|
|
1483
1458
|
await devServer.start();
|
|
@@ -1765,9 +1740,9 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1765
1740
|
for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1766
1741
|
logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1767
1742
|
if (options.migrate || options.seed) {
|
|
1768
|
-
const { prepareDevDatabases } = await import("./migrate-
|
|
1743
|
+
const { prepareDevDatabases } = await import("./migrate-CS3DkO6y.mjs");
|
|
1769
1744
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1770
|
-
} else await import("./migrate-
|
|
1745
|
+
} else await import("./migrate-CS3DkO6y.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1771
1746
|
logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1772
1747
|
});
|
|
1773
1748
|
const appUrls = {};
|
|
@@ -2161,7 +2136,7 @@ var DevServer = class {
|
|
|
2161
2136
|
portExplicit;
|
|
2162
2137
|
enableOpenApi;
|
|
2163
2138
|
telescope;
|
|
2164
|
-
|
|
2139
|
+
databaseApi;
|
|
2165
2140
|
runtime;
|
|
2166
2141
|
appRoot;
|
|
2167
2142
|
secretsJsonPath;
|
|
@@ -2170,12 +2145,12 @@ var DevServer = class {
|
|
|
2170
2145
|
isRunning = false;
|
|
2171
2146
|
actualPort;
|
|
2172
2147
|
startTime = Date.now();
|
|
2173
|
-
constructor(requestedPort, portExplicit, enableOpenApi, telescope,
|
|
2148
|
+
constructor(requestedPort, portExplicit, enableOpenApi, telescope, databaseApi, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
|
|
2174
2149
|
this.requestedPort = requestedPort;
|
|
2175
2150
|
this.portExplicit = portExplicit;
|
|
2176
2151
|
this.enableOpenApi = enableOpenApi;
|
|
2177
2152
|
this.telescope = telescope;
|
|
2178
|
-
this.
|
|
2153
|
+
this.databaseApi = databaseApi;
|
|
2179
2154
|
this.runtime = runtime;
|
|
2180
2155
|
this.appRoot = appRoot;
|
|
2181
2156
|
this.secretsJsonPath = secretsJsonPath;
|
|
@@ -2232,7 +2207,7 @@ var DevServer = class {
|
|
|
2232
2207
|
const tools = this.selfServing ? [] : [
|
|
2233
2208
|
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
2234
2209
|
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
2235
|
-
...this.
|
|
2210
|
+
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2236
2211
|
];
|
|
2237
2212
|
if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
|
|
2238
2213
|
}
|
|
@@ -2738,18 +2713,167 @@ var TopicGenerator = class extends ConstructGenerator {
|
|
|
2738
2713
|
}
|
|
2739
2714
|
};
|
|
2740
2715
|
//#endregion
|
|
2716
|
+
//#region src/run.ts
|
|
2717
|
+
/**
|
|
2718
|
+
* Start a program with an argument array, never a shell string.
|
|
2719
|
+
*
|
|
2720
|
+
* `gkm build`, `gkm docker` and `gkm deploy` pass values that come from the
|
|
2721
|
+
* workspace — image refs, tags, build args, package names — to `docker` and
|
|
2722
|
+
* `turbo`. Joined into a string and handed to a shell, a package named
|
|
2723
|
+
* `x;curl …|sh` or a tag holding `$(…)` became part of the command line, which
|
|
2724
|
+
* matters most where the CLI builds a repository it didn't write: a CI job for a
|
|
2725
|
+
* fork, a shared build runner. Here every value reaches the program as one
|
|
2726
|
+
* `argv` element, whatever it contains.
|
|
2727
|
+
*/
|
|
2728
|
+
/** Long enough for a cold image build; short enough that a hung one ends. */
|
|
2729
|
+
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
2730
|
+
/** How long a timed-out child has to exit on SIGTERM before it is killed. */
|
|
2731
|
+
const KILL_GRACE_MS = 5e3;
|
|
2732
|
+
/** The command line as a person would read it — for messages, never to run. */
|
|
2733
|
+
function display(command, args) {
|
|
2734
|
+
return [command, ...args].join(" ");
|
|
2735
|
+
}
|
|
2736
|
+
/** The program ran and exited unsuccessfully; its own output said why. */
|
|
2737
|
+
var CommandFailed = class extends Error {
|
|
2738
|
+
command;
|
|
2739
|
+
args;
|
|
2740
|
+
exitCode;
|
|
2741
|
+
signal;
|
|
2742
|
+
constructor(command, args, exitCode, signal) {
|
|
2743
|
+
super(`\`${display(command, args)}\` ${exitCode === null ? `was stopped by ${signal ?? "a signal"}` : `exited with code ${exitCode}`}. Its output above says why; run it by hand to see it again.`);
|
|
2744
|
+
this.command = command;
|
|
2745
|
+
this.args = args;
|
|
2746
|
+
this.exitCode = exitCode;
|
|
2747
|
+
this.signal = signal;
|
|
2748
|
+
this.name = "CommandFailed";
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
/** The program ran past its timeout and was killed. */
|
|
2752
|
+
var CommandTimedOut = class extends Error {
|
|
2753
|
+
command;
|
|
2754
|
+
args;
|
|
2755
|
+
timeoutMs;
|
|
2756
|
+
constructor(command, args, timeoutMs) {
|
|
2757
|
+
super(`\`${display(command, args)}\` was still running after ${Math.round(timeoutMs / 1e3)}s and was stopped. Check what it was waiting on — a registry, the Docker daemon, a prompt — and run it again.`);
|
|
2758
|
+
this.command = command;
|
|
2759
|
+
this.args = args;
|
|
2760
|
+
this.timeoutMs = timeoutMs;
|
|
2761
|
+
this.name = "CommandTimedOut";
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
/**
|
|
2765
|
+
* Run `command` with `args`, resolving when it exits 0.
|
|
2766
|
+
*
|
|
2767
|
+
* Rejects with {@link CommandFailed} on any other exit, {@link CommandTimedOut}
|
|
2768
|
+
* once `timeoutMs` passes (after the child is gone), and with the spawn error
|
|
2769
|
+
* itself when the program cannot be started at all — `ENOENT` already names it.
|
|
2770
|
+
*/
|
|
2771
|
+
function run(command, args, options = {}) {
|
|
2772
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, ...spawnOptions } = options;
|
|
2773
|
+
return new Promise((resolve, reject) => {
|
|
2774
|
+
const child = spawn(command, [...args], {
|
|
2775
|
+
stdio: "inherit",
|
|
2776
|
+
...spawnOptions,
|
|
2777
|
+
shell: false
|
|
2778
|
+
});
|
|
2779
|
+
let timedOut = false;
|
|
2780
|
+
let killer;
|
|
2781
|
+
const timer = setTimeout(() => {
|
|
2782
|
+
timedOut = true;
|
|
2783
|
+
child.kill("SIGTERM");
|
|
2784
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
2785
|
+
killer.unref?.();
|
|
2786
|
+
}, timeoutMs);
|
|
2787
|
+
timer.unref?.();
|
|
2788
|
+
const settle = () => {
|
|
2789
|
+
clearTimeout(timer);
|
|
2790
|
+
if (killer) clearTimeout(killer);
|
|
2791
|
+
};
|
|
2792
|
+
child.on("error", (error) => {
|
|
2793
|
+
settle();
|
|
2794
|
+
reject(error);
|
|
2795
|
+
});
|
|
2796
|
+
child.on("close", (code, signal) => {
|
|
2797
|
+
settle();
|
|
2798
|
+
if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
|
|
2799
|
+
else if (code === 0) resolve();
|
|
2800
|
+
else reject(new CommandFailed(command, args, code, signal));
|
|
2801
|
+
});
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
//#endregion
|
|
2805
|
+
//#region src/secrets/encryption.ts
|
|
2806
|
+
var encryption_exports = /* @__PURE__ */ __exportAll({
|
|
2807
|
+
encryptSecrets: () => encryptSecrets,
|
|
2808
|
+
generateDefineOptions: () => generateDefineOptions,
|
|
2809
|
+
keyFingerprint: () => keyFingerprint
|
|
2810
|
+
});
|
|
2811
|
+
/** AES-256-GCM configuration */
|
|
2812
|
+
const ALGORITHM = "aes-256-gcm";
|
|
2813
|
+
const KEY_LENGTH = 32;
|
|
2814
|
+
const IV_LENGTH = 12;
|
|
2815
|
+
/**
|
|
2816
|
+
* Encrypt secrets for embedding in a bundle.
|
|
2817
|
+
* Uses AES-256-GCM with a randomly generated ephemeral key.
|
|
2818
|
+
*
|
|
2819
|
+
* @param secrets - Key-value pairs to encrypt
|
|
2820
|
+
* @returns Encrypted payload with ephemeral master key
|
|
2821
|
+
*/
|
|
2822
|
+
function encryptSecrets(secrets) {
|
|
2823
|
+
const masterKey = randomBytes(KEY_LENGTH);
|
|
2824
|
+
const iv = randomBytes(IV_LENGTH);
|
|
2825
|
+
const plaintext = JSON.stringify(secrets);
|
|
2826
|
+
const cipher = createCipheriv(ALGORITHM, masterKey, iv);
|
|
2827
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
2828
|
+
const authTag = cipher.getAuthTag();
|
|
2829
|
+
return {
|
|
2830
|
+
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
2831
|
+
iv: iv.toString("hex"),
|
|
2832
|
+
masterKey: masterKey.toString("hex")
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Generate the define options for tsdown/esbuild.
|
|
2837
|
+
* These will be injected at build time.
|
|
2838
|
+
*/
|
|
2839
|
+
function generateDefineOptions(payload) {
|
|
2840
|
+
return {
|
|
2841
|
+
__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
|
|
2842
|
+
__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* A short, one-way name for a master key: the first 8 hex characters of its
|
|
2847
|
+
* SHA-256.
|
|
2848
|
+
*
|
|
2849
|
+
* Printed where the key itself used to be, so a person can tell which key a
|
|
2850
|
+
* build used — and match it against the one they hold — without the output
|
|
2851
|
+
* carrying a value that decrypts every secret of the stage.
|
|
2852
|
+
*/
|
|
2853
|
+
function keyFingerprint(masterKey) {
|
|
2854
|
+
return createHash("sha256").update(masterKey).digest("hex").slice(0, 8);
|
|
2855
|
+
}
|
|
2856
|
+
//#endregion
|
|
2857
|
+
//#region src/build/types.ts
|
|
2858
|
+
/**
|
|
2859
|
+
* Where `gkm dev` serves the database's read-only JSON API
|
|
2860
|
+
* (`@geekmidas/db/introspect`). Under `/__gkm` with the rest of what the dev
|
|
2861
|
+
* server adds, so it cannot collide with an application route.
|
|
2862
|
+
*/
|
|
2863
|
+
const DEV_DATABASE_API_PATH = "/__gkm/db";
|
|
2864
|
+
//#endregion
|
|
2741
2865
|
//#region src/build/owners.ts
|
|
2742
2866
|
/**
|
|
2743
2867
|
* What a generated entry imports its runtime from, read off the discovered
|
|
2744
2868
|
* constructs: the surface it serves, every construct a runnable can be built
|
|
2745
|
-
* from, and the database
|
|
2869
|
+
* from, and the database the dev server's JSON API reads.
|
|
2746
2870
|
*
|
|
2747
2871
|
* `gkm build` and `gkm dev` both write entries that import the surface for its
|
|
2748
2872
|
* logger and environment parser, so both ask here. A dev server that skipped it
|
|
2749
2873
|
* had nothing to import them from.
|
|
2750
2874
|
*/
|
|
2751
2875
|
function ownersContext(options) {
|
|
2752
|
-
const { declared, sources, workspaceRoot, appRoot,
|
|
2876
|
+
const { declared, sources, workspaceRoot, appRoot, databaseApi } = options;
|
|
2753
2877
|
const surfaces = restApis(declared);
|
|
2754
2878
|
const primary = servedSurface(declared, workspaceRoot, appRoot) ?? surfaces.find((d) => d.endpoints.length === 0) ?? surfaces[0];
|
|
2755
2879
|
const browsable = Object.entries(declared).find(([, d]) => d.kind === "database");
|
|
@@ -2776,13 +2900,13 @@ function ownersContext(options) {
|
|
|
2776
2900
|
exportName: primarySource.exportName
|
|
2777
2901
|
} } : {}
|
|
2778
2902
|
} } : {},
|
|
2779
|
-
|
|
2780
|
-
|
|
2903
|
+
...databaseApi && browsableSource ? { databaseApi: {
|
|
2904
|
+
path: DEV_DATABASE_API_PATH,
|
|
2781
2905
|
database: {
|
|
2782
2906
|
specifier: browsableSource.file,
|
|
2783
2907
|
exportName: browsableSource.exportName
|
|
2784
2908
|
}
|
|
2785
|
-
} :
|
|
2909
|
+
} } : {}
|
|
2786
2910
|
};
|
|
2787
2911
|
}
|
|
2788
2912
|
/**
|
|
@@ -2868,6 +2992,11 @@ export default app;
|
|
|
2868
2992
|
//#region src/build/index.ts
|
|
2869
2993
|
const logger$8 = console;
|
|
2870
2994
|
/**
|
|
2995
|
+
* Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
|
|
2996
|
+
* with, beside the bundle in `.gkm/server/` and readable by its owner alone.
|
|
2997
|
+
*/
|
|
2998
|
+
const MASTER_KEY_FILE = "master.key";
|
|
2999
|
+
/**
|
|
2871
3000
|
* What a build run at the workspace root reads.
|
|
2872
3001
|
*
|
|
2873
3002
|
* One backend app there, and it is that app's config, so its own `openapi`
|
|
@@ -2960,8 +3089,6 @@ async function buildOneApp(input) {
|
|
|
2960
3089
|
logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
2961
3090
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2962
3091
|
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2963
|
-
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2964
|
-
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2965
3092
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
2966
3093
|
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2967
3094
|
return buildApp({
|
|
@@ -2973,7 +3100,6 @@ async function buildOneApp(input) {
|
|
|
2973
3100
|
cacheBackend,
|
|
2974
3101
|
production,
|
|
2975
3102
|
telescope,
|
|
2976
|
-
studio,
|
|
2977
3103
|
hooks,
|
|
2978
3104
|
markOptional: options.markOptional ?? false,
|
|
2979
3105
|
skipBundle: options.skipBundle ?? false,
|
|
@@ -3016,7 +3142,7 @@ async function writeManifest(input) {
|
|
|
3016
3142
|
* dev did not, and failed on exactly the apps the build had learned to handle.
|
|
3017
3143
|
*/
|
|
3018
3144
|
async function buildApp(input) {
|
|
3019
|
-
const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope,
|
|
3145
|
+
const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope, databaseApi, hooks, bustCache = false } = input;
|
|
3020
3146
|
const constructGlobs = config.constructs;
|
|
3021
3147
|
const constructSources = {};
|
|
3022
3148
|
const declared = constructGlobs ? await discover({
|
|
@@ -3041,7 +3167,7 @@ async function buildApp(input) {
|
|
|
3041
3167
|
sources: constructSources,
|
|
3042
3168
|
workspaceRoot,
|
|
3043
3169
|
appRoot,
|
|
3044
|
-
|
|
3170
|
+
databaseApi
|
|
3045
3171
|
});
|
|
3046
3172
|
const buildContext = {
|
|
3047
3173
|
...derived,
|
|
@@ -3083,12 +3209,27 @@ async function buildApp(input) {
|
|
|
3083
3209
|
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
3084
3210
|
return {};
|
|
3085
3211
|
}
|
|
3212
|
+
if (production?.subscribers === "include" && derived.surface) {
|
|
3213
|
+
buildContext.production = {
|
|
3214
|
+
...production,
|
|
3215
|
+
subscribers: "exclude"
|
|
3216
|
+
};
|
|
3217
|
+
const left = [
|
|
3218
|
+
[allCrons.length, "cron"],
|
|
3219
|
+
[allQueues.length, "queue consumer"],
|
|
3220
|
+
[allSubscribers.length, "subscriber"]
|
|
3221
|
+
].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
|
|
3222
|
+
if (left.length > 0) logger$8.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
|
|
3223
|
+
}
|
|
3086
3224
|
const result = await buildForTarget(target, buildContext, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
|
|
3087
3225
|
await generateOpenApi(allEndpoints.map(({ construct }) => construct), {
|
|
3088
3226
|
openapi: config.openapi,
|
|
3089
3227
|
root: workspaceRoot
|
|
3090
3228
|
});
|
|
3091
|
-
return
|
|
3229
|
+
return {
|
|
3230
|
+
...result,
|
|
3231
|
+
...buildContext.databaseApi ? { databaseApi: buildContext.databaseApi.path } : {}
|
|
3232
|
+
};
|
|
3092
3233
|
}
|
|
3093
3234
|
async function buildForTarget(target, context, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
|
|
3094
3235
|
const outputDir = join(appRoot, ".gkm", target);
|
|
@@ -3141,7 +3282,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3141
3282
|
let masterKey;
|
|
3142
3283
|
if (context.production?.bundle && !skipBundle) {
|
|
3143
3284
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
3144
|
-
const { bundleServer } = await import("./bundler
|
|
3285
|
+
const { bundleServer } = await import("./bundler-URImKj-A.mjs");
|
|
3145
3286
|
const allConstructs = [
|
|
3146
3287
|
...endpoints.map((e) => e.construct),
|
|
3147
3288
|
...functions.map((f) => f.construct),
|
|
@@ -3160,8 +3301,11 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3160
3301
|
})).masterKey;
|
|
3161
3302
|
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3162
3303
|
if (masterKey) {
|
|
3304
|
+
const keyPath = join(outputDir, MASTER_KEY_FILE);
|
|
3305
|
+
await writeFile(keyPath, `${masterKey}\n`, { mode: 384 });
|
|
3306
|
+
await chmod(keyPath, 384);
|
|
3163
3307
|
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
3164
|
-
logger$8.log(`
|
|
3308
|
+
logger$8.log(` Run with GKM_MASTER_KEY set to the key in ${relative(appRoot, keyPath)} (fingerprint ${keyFingerprint(masterKey)})`);
|
|
3165
3309
|
}
|
|
3166
3310
|
}
|
|
3167
3311
|
return {
|
|
@@ -3184,7 +3328,7 @@ function detectPackageManager$2() {
|
|
|
3184
3328
|
return "npm";
|
|
3185
3329
|
}
|
|
3186
3330
|
/**
|
|
3187
|
-
*
|
|
3331
|
+
* The program and argv that run turbo's build.
|
|
3188
3332
|
*
|
|
3189
3333
|
* `filters` names the packages to build. Passing none lets turbo infer its own
|
|
3190
3334
|
* scope from the working directory, which for a workspace root means the
|
|
@@ -3192,14 +3336,24 @@ function detectPackageManager$2() {
|
|
|
3192
3336
|
* `gkm build`, inferring is how you get a build that runs itself forever.
|
|
3193
3337
|
* `workspaceBuildCommand` always names the apps.
|
|
3194
3338
|
*
|
|
3339
|
+
* An array, not a command string: the filters are package names read from the
|
|
3340
|
+
* workspace, and each must reach turbo as one argument whatever it holds. Each
|
|
3341
|
+
* is `--filter=<name>`, so even a name starting with `-` stays a filter value.
|
|
3342
|
+
*
|
|
3195
3343
|
* @internal Exported for testing
|
|
3196
3344
|
*/
|
|
3197
|
-
function
|
|
3198
|
-
const
|
|
3345
|
+
function getTurboArgs(pm, filters = [], { only = false } = {}) {
|
|
3346
|
+
const turbo = [
|
|
3347
|
+
"turbo",
|
|
3348
|
+
"run",
|
|
3349
|
+
"build",
|
|
3350
|
+
...(typeof filters === "string" ? [filters] : filters).map((f) => `--filter=${f}`),
|
|
3351
|
+
...only ? ["--only"] : []
|
|
3352
|
+
];
|
|
3199
3353
|
switch (pm) {
|
|
3200
|
-
case "pnpm": return
|
|
3201
|
-
case "yarn": return
|
|
3202
|
-
case "npm": return
|
|
3354
|
+
case "pnpm": return ["pnpm", ["exec", ...turbo]];
|
|
3355
|
+
case "yarn": return ["yarn", turbo];
|
|
3356
|
+
case "npm": return ["npx", turbo];
|
|
3203
3357
|
}
|
|
3204
3358
|
}
|
|
3205
3359
|
/**
|
|
@@ -3288,27 +3442,16 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3288
3442
|
const { filters, unpackaged } = turboFilters(workspace, { exclude: "backend" });
|
|
3289
3443
|
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.`);
|
|
3290
3444
|
const pm = detectPackageManager$2();
|
|
3291
|
-
const
|
|
3445
|
+
const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
|
|
3292
3446
|
if (filters.length > 0) {
|
|
3293
3447
|
logger$8.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
|
|
3294
|
-
logger$8.log(`Running: ${
|
|
3295
|
-
await
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
...process.env,
|
|
3302
|
-
NODE_ENV: options.production ? "production" : "development"
|
|
3303
|
-
}
|
|
3304
|
-
});
|
|
3305
|
-
child.on("close", (code) => {
|
|
3306
|
-
if (code === 0) resolve();
|
|
3307
|
-
else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
|
|
3308
|
-
});
|
|
3309
|
-
child.on("error", (err) => {
|
|
3310
|
-
reject(err);
|
|
3311
|
-
});
|
|
3448
|
+
logger$8.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
|
|
3449
|
+
await run(turboBin, turboArgs, {
|
|
3450
|
+
cwd: workspace.root,
|
|
3451
|
+
env: {
|
|
3452
|
+
...process.env,
|
|
3453
|
+
NODE_ENV: options.production ? "production" : "development"
|
|
3454
|
+
}
|
|
3312
3455
|
});
|
|
3313
3456
|
}
|
|
3314
3457
|
for (const [appName, app] of apps) {
|
|
@@ -4238,7 +4381,7 @@ async function createDnsProvider(options) {
|
|
|
4238
4381
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
4239
4382
|
const provider = config.provider;
|
|
4240
4383
|
if (provider === "hostinger") {
|
|
4241
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
4384
|
+
const { HostingerProvider } = await import("./HostingerProvider-cL-6oQ5r.mjs");
|
|
4242
4385
|
return new HostingerProvider();
|
|
4243
4386
|
}
|
|
4244
4387
|
if (provider === "route53") {
|
|
@@ -4655,6 +4798,39 @@ function getPublicUrlArgNames(app) {
|
|
|
4655
4798
|
return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
|
|
4656
4799
|
}
|
|
4657
4800
|
//#endregion
|
|
4801
|
+
//#region src/docker/imageRef.ts
|
|
4802
|
+
/**
|
|
4803
|
+
* An image reference, checked before it reaches `docker`.
|
|
4804
|
+
*
|
|
4805
|
+
* The parts come from the workspace — a registry in `gkm.config.ts`, an image
|
|
4806
|
+
* name from the project, a tag from `--tag` — and an argument array already
|
|
4807
|
+
* keeps them out of a shell. What it cannot do is stop `docker` reading a value
|
|
4808
|
+
* that starts with `-` as one of its own flags, so a ref is held to Docker's
|
|
4809
|
+
* own grammar (distribution/reference) and anything else is refused by name
|
|
4810
|
+
* before a build starts.
|
|
4811
|
+
*/
|
|
4812
|
+
const DOMAIN_COMPONENT = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
|
|
4813
|
+
const DOMAIN = `${DOMAIN_COMPONENT}(?:\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;
|
|
4814
|
+
const PATH_COMPONENT = "[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*";
|
|
4815
|
+
const REFERENCE = new RegExp(`^(?:${DOMAIN}/)?${PATH_COMPONENT}(?:/${PATH_COMPONENT})*(?::[\\w][\\w.-]{0,127})?(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,})?$`);
|
|
4816
|
+
/** Docker refuses a repository name longer than this. */
|
|
4817
|
+
const MAX_NAME_LENGTH = 255;
|
|
4818
|
+
/** A value that is not an image reference was about to be built or pushed. */
|
|
4819
|
+
var ImageRefInvalid = class extends Error {
|
|
4820
|
+
ref;
|
|
4821
|
+
constructor(ref) {
|
|
4822
|
+
super(`'${ref}' is not a valid image reference. An image is \`[registry/]name[:tag]\`: the name lowercase letters, digits and . _ - separators, the tag up to 128 of letters, digits, _ . and -, not starting with . or -. Check docker.registry in gkm.config.ts and the --tag passed.`);
|
|
4823
|
+
this.ref = ref;
|
|
4824
|
+
this.name = "ImageRefInvalid";
|
|
4825
|
+
}
|
|
4826
|
+
};
|
|
4827
|
+
/** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */
|
|
4828
|
+
function validateImageRef(ref) {
|
|
4829
|
+
const name = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
|
|
4830
|
+
if (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) throw new ImageRefInvalid(ref);
|
|
4831
|
+
return ref;
|
|
4832
|
+
}
|
|
4833
|
+
//#endregion
|
|
4658
4834
|
//#region src/docker/templates.ts
|
|
4659
4835
|
const LOCKFILES = [
|
|
4660
4836
|
["pnpm-lock.yaml", "pnpm"],
|
|
@@ -5008,6 +5184,10 @@ node_modules
|
|
|
5008
5184
|
.gkm/server/*.ts
|
|
5009
5185
|
!.gkm/server/dist
|
|
5010
5186
|
|
|
5187
|
+
# The key gkm build --stage encrypted secrets with: runtime env only, never
|
|
5188
|
+
# the build context
|
|
5189
|
+
.gkm/server/master.key
|
|
5190
|
+
|
|
5011
5191
|
# IDE and editor
|
|
5012
5192
|
.idea
|
|
5013
5193
|
.vscode
|
|
@@ -5222,10 +5402,6 @@ FROM deps AS builder
|
|
|
5222
5402
|
|
|
5223
5403
|
WORKDIR /app
|
|
5224
5404
|
|
|
5225
|
-
# Build-time args for encrypted secrets
|
|
5226
|
-
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
5227
|
-
ARG GKM_CREDENTIALS_IV=""
|
|
5228
|
-
|
|
5229
5405
|
# Copy pruned source
|
|
5230
5406
|
COPY --from=pruner /app/out/full/ ./
|
|
5231
5407
|
|
|
@@ -5234,11 +5410,15 @@ COPY --from=pruner /app/out/full/ ./
|
|
|
5234
5410
|
COPY --from=pruner /app/gkm.config.* ./
|
|
5235
5411
|
COPY --from=pruner /app/tsconfig.* ./
|
|
5236
5412
|
|
|
5237
|
-
# Write encrypted credentials for gkm build to embed
|
|
5238
|
-
|
|
5413
|
+
# Write encrypted credentials for gkm build to embed.
|
|
5414
|
+
# A build secret (\`docker build --secret id=gkm_credentials,src=…\`), not an
|
|
5415
|
+
# ARG: an ARG is recorded in the image history. Two lines, ciphertext then IV;
|
|
5416
|
+
# absent when the app has no secrets.
|
|
5417
|
+
RUN --mount=type=secret,id=gkm_credentials,required=false \
|
|
5418
|
+
if [ -s /run/secrets/gkm_credentials ]; then \
|
|
5239
5419
|
mkdir -p ${appPath}/.gkm && \
|
|
5240
|
-
|
|
5241
|
-
|
|
5420
|
+
sed -n 1p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.enc && \
|
|
5421
|
+
sed -n 2p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.iv; \
|
|
5242
5422
|
fi
|
|
5243
5423
|
|
|
5244
5424
|
# Build any workspace packages the app depends on.
|
|
@@ -5325,10 +5505,6 @@ FROM deps AS builder
|
|
|
5325
5505
|
|
|
5326
5506
|
WORKDIR /app
|
|
5327
5507
|
|
|
5328
|
-
# Build-time args for encrypted secrets
|
|
5329
|
-
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
5330
|
-
ARG GKM_CREDENTIALS_IV=""
|
|
5331
|
-
|
|
5332
5508
|
# Copy pruned source
|
|
5333
5509
|
COPY --from=pruner /app/out/full/ ./
|
|
5334
5510
|
|
|
@@ -5336,20 +5512,17 @@ COPY --from=pruner /app/out/full/ ./
|
|
|
5336
5512
|
# Using wildcard to make it optional for single-app projects
|
|
5337
5513
|
COPY --from=pruner /app/tsconfig.* ./
|
|
5338
5514
|
|
|
5339
|
-
# Write encrypted credentials for tsdown to embed via define
|
|
5340
|
-
RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
|
|
5341
|
-
mkdir -p ${appPath}/.gkm && \
|
|
5342
|
-
echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
|
|
5343
|
-
echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
|
|
5344
|
-
fi
|
|
5345
|
-
|
|
5346
5515
|
# Bundle entry point with esbuild (outputs to dist/index.mjs)
|
|
5347
5516
|
# Creates a fully standalone bundle with all dependencies included
|
|
5348
|
-
# Use define to embed credentials if present
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5517
|
+
# Use define to embed credentials if present.
|
|
5518
|
+
# They arrive as a build secret (\`docker build --secret id=gkm_credentials,src=…\`),
|
|
5519
|
+
# not an ARG: an ARG is recorded in the image history. Two lines, ciphertext
|
|
5520
|
+
# then IV; absent when the app has no secrets.
|
|
5521
|
+
RUN --mount=type=secret,id=gkm_credentials,required=false \
|
|
5522
|
+
cd ${appPath} && \
|
|
5523
|
+
if [ -s /run/secrets/gkm_credentials ]; then \
|
|
5524
|
+
CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \
|
|
5525
|
+
IV=$(sed -n 2p /run/secrets/gkm_credentials) && \
|
|
5353
5526
|
npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
|
|
5354
5527
|
--outfile=dist/index.mjs --packages=bundle \
|
|
5355
5528
|
--banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);' \
|
|
@@ -5500,6 +5673,33 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5500
5673
|
//#endregion
|
|
5501
5674
|
//#region src/docker/index.ts
|
|
5502
5675
|
const logger$6 = console;
|
|
5676
|
+
/** `docker build` failed; what docker printed above says why. */
|
|
5677
|
+
var DockerBuildFailed = class extends Error {
|
|
5678
|
+
imageRef;
|
|
5679
|
+
constructor(imageRef, cause) {
|
|
5680
|
+
super(`Failed to build Docker image: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
5681
|
+
this.imageRef = imageRef;
|
|
5682
|
+
this.name = "DockerBuildFailed";
|
|
5683
|
+
}
|
|
5684
|
+
};
|
|
5685
|
+
/** `docker push` failed — usually the registry login or its permissions. */
|
|
5686
|
+
var DockerPushFailed = class extends Error {
|
|
5687
|
+
imageRef;
|
|
5688
|
+
constructor(imageRef, cause) {
|
|
5689
|
+
super(`Failed to push Docker image: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
5690
|
+
this.imageRef = imageRef;
|
|
5691
|
+
this.name = "DockerPushFailed";
|
|
5692
|
+
}
|
|
5693
|
+
};
|
|
5694
|
+
/** `gkm docker --push` was asked for with nowhere to push to. */
|
|
5695
|
+
var PushNeedsRegistry = class extends Error {
|
|
5696
|
+
imageName;
|
|
5697
|
+
constructor(imageName) {
|
|
5698
|
+
super(`Registry is required to push Docker image '${imageName}'. Use --registry or configure docker.registry in gkm.config.ts`);
|
|
5699
|
+
this.imageName = imageName;
|
|
5700
|
+
this.name = "PushNeedsRegistry";
|
|
5701
|
+
}
|
|
5702
|
+
};
|
|
5503
5703
|
/**
|
|
5504
5704
|
* Docker command implementation
|
|
5505
5705
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5603,14 +5803,18 @@ function ensureLockfile(cwd) {
|
|
|
5603
5803
|
async function buildDockerImage(imageName, options) {
|
|
5604
5804
|
const tag = options.tag ?? "latest";
|
|
5605
5805
|
const registry = options.registry;
|
|
5606
|
-
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}
|
|
5806
|
+
const fullImageName = validateImageRef(registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`);
|
|
5607
5807
|
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5608
5808
|
const cwd = process.cwd();
|
|
5609
5809
|
const cleanup = ensureLockfile(cwd);
|
|
5610
5810
|
try {
|
|
5611
|
-
|
|
5811
|
+
await run("docker", [
|
|
5812
|
+
"build",
|
|
5813
|
+
"--file=.gkm/docker/Dockerfile",
|
|
5814
|
+
`--tag=${fullImageName}`,
|
|
5815
|
+
"."
|
|
5816
|
+
], {
|
|
5612
5817
|
cwd,
|
|
5613
|
-
stdio: "inherit",
|
|
5614
5818
|
env: {
|
|
5615
5819
|
...process.env,
|
|
5616
5820
|
DOCKER_BUILDKIT: "1"
|
|
@@ -5618,7 +5822,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5618
5822
|
});
|
|
5619
5823
|
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5620
5824
|
} catch (error) {
|
|
5621
|
-
throw new
|
|
5825
|
+
throw new DockerBuildFailed(fullImageName, error);
|
|
5622
5826
|
} finally {
|
|
5623
5827
|
cleanup?.();
|
|
5624
5828
|
}
|
|
@@ -5629,17 +5833,14 @@ async function buildDockerImage(imageName, options) {
|
|
|
5629
5833
|
async function pushDockerImage(imageName, options) {
|
|
5630
5834
|
const tag = options.tag ?? "latest";
|
|
5631
5835
|
const registry = options.registry;
|
|
5632
|
-
if (!registry) throw new
|
|
5633
|
-
const fullImageName = `${registry}/${imageName}:${tag}
|
|
5836
|
+
if (!registry) throw new PushNeedsRegistry(imageName);
|
|
5837
|
+
const fullImageName = validateImageRef(`${registry}/${imageName}:${tag}`);
|
|
5634
5838
|
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5635
5839
|
try {
|
|
5636
|
-
|
|
5637
|
-
cwd: process.cwd(),
|
|
5638
|
-
stdio: "inherit"
|
|
5639
|
-
});
|
|
5840
|
+
await run("docker", ["push", fullImageName], { cwd: process.cwd() });
|
|
5640
5841
|
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5641
5842
|
} catch (error) {
|
|
5642
|
-
throw new
|
|
5843
|
+
throw new DockerPushFailed(fullImageName, error);
|
|
5643
5844
|
}
|
|
5644
5845
|
}
|
|
5645
5846
|
/**
|
|
@@ -5771,6 +5972,11 @@ function printRunInstructions(workspace) {
|
|
|
5771
5972
|
//#region src/deploy/docker.ts
|
|
5772
5973
|
const logger$5 = console;
|
|
5773
5974
|
/**
|
|
5975
|
+
* The id the Dockerfile templates mount the encrypted credentials under:
|
|
5976
|
+
* `RUN --mount=type=secret,id=gkm_credentials`.
|
|
5977
|
+
*/
|
|
5978
|
+
const CREDENTIALS_SECRET_ID = "gkm_credentials";
|
|
5979
|
+
/**
|
|
5774
5980
|
* Get the full image reference
|
|
5775
5981
|
*/
|
|
5776
5982
|
function getImageRef(registry, imageName, tag) {
|
|
@@ -5778,31 +5984,69 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5778
5984
|
return `${imageName}:${tag}`;
|
|
5779
5985
|
}
|
|
5780
5986
|
/**
|
|
5987
|
+
* The `docker build` argv for an image.
|
|
5988
|
+
*
|
|
5989
|
+
* One element per value, so nothing in a ref or a build arg is read by a
|
|
5990
|
+
* shell, and `--opt=value` rather than `--opt value`, so a value that starts
|
|
5991
|
+
* with `-` cannot be taken for one of docker's own flags.
|
|
5992
|
+
*
|
|
5993
|
+
* @internal Exported for testing
|
|
5994
|
+
*/
|
|
5995
|
+
function dockerBuildArgs(options) {
|
|
5996
|
+
const { dockerfilePath, imageRef, buildArgs = [], credentialsFile } = options;
|
|
5997
|
+
return [
|
|
5998
|
+
"build",
|
|
5999
|
+
"--platform=linux/amd64",
|
|
6000
|
+
`--file=${dockerfilePath}`,
|
|
6001
|
+
`--tag=${imageRef}`,
|
|
6002
|
+
...buildArgs.map((arg) => `--build-arg=${arg}`),
|
|
6003
|
+
...credentialsFile ? [`--secret=id=${CREDENTIALS_SECRET_ID},src=${credentialsFile}`] : [],
|
|
6004
|
+
"."
|
|
6005
|
+
];
|
|
6006
|
+
}
|
|
6007
|
+
/**
|
|
6008
|
+
* Write the encrypted credentials where `docker build --secret` reads them.
|
|
6009
|
+
*
|
|
6010
|
+
* Two lines — ciphertext, then IV — so a template can split them with `sed`
|
|
6011
|
+
* in any base image. Owner-only, in a directory of its own, and the returned
|
|
6012
|
+
* cleanup removes both whatever the build did.
|
|
6013
|
+
*
|
|
6014
|
+
* @internal Exported for testing
|
|
6015
|
+
*/
|
|
6016
|
+
async function writeCredentialsFile(credentials) {
|
|
6017
|
+
const dir = await mkdtemp(join(tmpdir(), "gkm-credentials-"));
|
|
6018
|
+
const path = join(dir, CREDENTIALS_SECRET_ID);
|
|
6019
|
+
await writeFile(path, `${credentials.encrypted}\n${credentials.iv}\n`, { mode: 384 });
|
|
6020
|
+
await chmod(path, 384);
|
|
6021
|
+
return {
|
|
6022
|
+
path,
|
|
6023
|
+
cleanup: () => rm(dir, {
|
|
6024
|
+
recursive: true,
|
|
6025
|
+
force: true
|
|
6026
|
+
})
|
|
6027
|
+
};
|
|
6028
|
+
}
|
|
6029
|
+
/**
|
|
5781
6030
|
* Build Docker image
|
|
5782
6031
|
* @param imageRef - Full image reference (registry/name:tag)
|
|
5783
|
-
* @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
|
|
5784
6032
|
* @param buildArgs - Build arguments to pass to docker build
|
|
6033
|
+
* @param credentials - Encrypted credentials, passed as a build secret
|
|
5785
6034
|
*/
|
|
5786
|
-
async function buildImage(imageRef,
|
|
6035
|
+
async function buildImage(imageRef, buildArgs, credentials) {
|
|
5787
6036
|
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5788
6037
|
const cwd = process.cwd();
|
|
5789
6038
|
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5790
6039
|
await dockerCommand({ slim: true });
|
|
5791
6040
|
const dockerfilePath = join(cwd, ".gkm", "docker", "Dockerfile");
|
|
5792
|
-
const
|
|
5793
|
-
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
6041
|
+
const secret = credentials ? await writeCredentialsFile(credentials) : void 0;
|
|
5794
6042
|
try {
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
].filter(Boolean).join(" ");
|
|
5803
|
-
execSync(cmd, {
|
|
5804
|
-
cwd: buildCwd,
|
|
5805
|
-
stdio: "inherit",
|
|
6043
|
+
await run("docker", dockerBuildArgs({
|
|
6044
|
+
dockerfilePath,
|
|
6045
|
+
imageRef,
|
|
6046
|
+
buildArgs,
|
|
6047
|
+
credentialsFile: secret?.path
|
|
6048
|
+
}), {
|
|
6049
|
+
cwd,
|
|
5806
6050
|
env: {
|
|
5807
6051
|
...process.env,
|
|
5808
6052
|
DOCKER_BUILDKIT: "1"
|
|
@@ -5810,7 +6054,9 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5810
6054
|
});
|
|
5811
6055
|
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5812
6056
|
} catch (error) {
|
|
5813
|
-
throw new
|
|
6057
|
+
throw new DockerBuildFailed(imageRef, error);
|
|
6058
|
+
} finally {
|
|
6059
|
+
await secret?.cleanup();
|
|
5814
6060
|
}
|
|
5815
6061
|
}
|
|
5816
6062
|
/**
|
|
@@ -5819,23 +6065,20 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5819
6065
|
async function pushImage(imageRef) {
|
|
5820
6066
|
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5821
6067
|
try {
|
|
5822
|
-
|
|
5823
|
-
cwd: process.cwd(),
|
|
5824
|
-
stdio: "inherit"
|
|
5825
|
-
});
|
|
6068
|
+
await run("docker", ["push", imageRef], { cwd: process.cwd() });
|
|
5826
6069
|
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5827
6070
|
} catch (error) {
|
|
5828
|
-
throw new
|
|
6071
|
+
throw new DockerPushFailed(imageRef, error);
|
|
5829
6072
|
}
|
|
5830
6073
|
}
|
|
5831
6074
|
/**
|
|
5832
6075
|
* Deploy using Docker (build and optionally push image)
|
|
5833
6076
|
*/
|
|
5834
6077
|
async function deployDocker(options) {
|
|
5835
|
-
const { stage, tag, skipPush, masterKey, config, buildArgs } = options;
|
|
6078
|
+
const { stage, tag, skipPush, masterKey, config, buildArgs, credentials } = options;
|
|
5836
6079
|
const imageName = config.imageName;
|
|
5837
|
-
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5838
|
-
await buildImage(imageRef,
|
|
6080
|
+
const imageRef = validateImageRef(getImageRef(config.registry, imageName, tag));
|
|
6081
|
+
await buildImage(imageRef, buildArgs, credentials);
|
|
5839
6082
|
if (!skipPush) {
|
|
5840
6083
|
if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
5841
6084
|
else await pushImage(imageRef);
|
|
@@ -5844,12 +6087,7 @@ async function deployDocker(options) {
|
|
|
5844
6087
|
logger$5.log(`\n📋 Deployment details:`);
|
|
5845
6088
|
logger$5.log(` Image: ${imageRef}`);
|
|
5846
6089
|
logger$5.log(` Stage: ${stage}`);
|
|
5847
|
-
if (masterKey) {
|
|
5848
|
-
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
5849
|
-
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
5850
|
-
logger$5.log("\n Example docker run:");
|
|
5851
|
-
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
5852
|
-
}
|
|
6090
|
+
if (masterKey) logger$5.log(`\n🔐 Run the container with GKM_MASTER_KEY set to the key with fingerprint ${keyFingerprint(masterKey)}.`);
|
|
5853
6091
|
return {
|
|
5854
6092
|
imageRef,
|
|
5855
6093
|
masterKey
|
|
@@ -5874,9 +6112,53 @@ function applicationName(stage, project, app) {
|
|
|
5874
6112
|
//#endregion
|
|
5875
6113
|
//#region src/deploy/dokploy-api.ts
|
|
5876
6114
|
var dokploy_api_exports = /* @__PURE__ */ __exportAll({
|
|
6115
|
+
DEFAULT_DOKPLOY_TIMEOUT_MS: () => DEFAULT_DOKPLOY_TIMEOUT_MS,
|
|
5877
6116
|
DokployApi: () => DokployApi,
|
|
5878
|
-
DokployApiError: () => DokployApiError
|
|
6117
|
+
DokployApiError: () => DokployApiError,
|
|
6118
|
+
DokployRequestTimedOut: () => DokployRequestTimedOut
|
|
5879
6119
|
});
|
|
6120
|
+
/** Per request, so a stalled server fails the deploy, not the CI job. */
|
|
6121
|
+
const DEFAULT_DOKPLOY_TIMEOUT_MS = 3e4;
|
|
6122
|
+
/**
|
|
6123
|
+
* Dokploy accepted the connection but did not answer in time.
|
|
6124
|
+
*
|
|
6125
|
+
* Not retried: a request that arrived and stalled may still be acted on, and
|
|
6126
|
+
* sending it again could repeat a deploy or a create the server is already
|
|
6127
|
+
* doing.
|
|
6128
|
+
*/
|
|
6129
|
+
var DokployRequestTimedOut = class extends Error {
|
|
6130
|
+
baseUrl;
|
|
6131
|
+
endpoint;
|
|
6132
|
+
timeoutMs;
|
|
6133
|
+
constructor(baseUrl, endpoint, timeoutMs) {
|
|
6134
|
+
super(`Dokploy at ${baseUrl} did not answer ${endpoint} within ${timeoutMs}ms. Check that the Dokploy server is healthy, or raise \`timeoutMs\` if this call is known to be slow.`);
|
|
6135
|
+
this.baseUrl = baseUrl;
|
|
6136
|
+
this.endpoint = endpoint;
|
|
6137
|
+
this.timeoutMs = timeoutMs;
|
|
6138
|
+
this.name = "DokployRequestTimedOut";
|
|
6139
|
+
}
|
|
6140
|
+
};
|
|
6141
|
+
/**
|
|
6142
|
+
* Waits `ms` between retries, or rejects with the signal's reason the moment
|
|
6143
|
+
* it aborts — a caller who gave up should not sit through the backoff.
|
|
6144
|
+
*/
|
|
6145
|
+
function pause(ms, signal) {
|
|
6146
|
+
return new Promise((resolve, reject) => {
|
|
6147
|
+
if (signal?.aborted) {
|
|
6148
|
+
reject(signal.reason);
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
const onAbort = () => {
|
|
6152
|
+
clearTimeout(timer);
|
|
6153
|
+
reject(signal?.reason);
|
|
6154
|
+
};
|
|
6155
|
+
const timer = setTimeout(() => {
|
|
6156
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6157
|
+
resolve();
|
|
6158
|
+
}, ms);
|
|
6159
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6160
|
+
});
|
|
6161
|
+
}
|
|
5880
6162
|
var DokployApiError = class extends Error {
|
|
5881
6163
|
status;
|
|
5882
6164
|
statusText;
|
|
@@ -5895,9 +6177,13 @@ var DokployApiError = class extends Error {
|
|
|
5895
6177
|
var DokployApi = class {
|
|
5896
6178
|
baseUrl;
|
|
5897
6179
|
token;
|
|
6180
|
+
timeoutMs;
|
|
6181
|
+
signal;
|
|
5898
6182
|
constructor(options) {
|
|
5899
6183
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
5900
6184
|
this.token = options.token;
|
|
6185
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
6186
|
+
this.signal = options.signal;
|
|
5901
6187
|
}
|
|
5902
6188
|
/**
|
|
5903
6189
|
* Make a GET request to the Dokploy API
|
|
@@ -5917,26 +6203,51 @@ var DokployApi = class {
|
|
|
5917
6203
|
async request(method, endpoint, body) {
|
|
5918
6204
|
const url = `${this.baseUrl}/api/${endpoint}`;
|
|
5919
6205
|
let response;
|
|
6206
|
+
let timeout;
|
|
5920
6207
|
let lastError;
|
|
5921
|
-
for (let attempt = 1; attempt <= 3; attempt++)
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6208
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
6209
|
+
this.signal?.throwIfAborted();
|
|
6210
|
+
timeout = AbortSignal.timeout(this.timeoutMs);
|
|
6211
|
+
try {
|
|
6212
|
+
response = await fetch(url, {
|
|
6213
|
+
method,
|
|
6214
|
+
headers: {
|
|
6215
|
+
"Content-Type": "application/json",
|
|
6216
|
+
"x-api-key": this.token
|
|
6217
|
+
},
|
|
6218
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
6219
|
+
signal: this.signal ? AbortSignal.any([this.signal, timeout]) : timeout
|
|
6220
|
+
});
|
|
6221
|
+
break;
|
|
6222
|
+
} catch (error) {
|
|
6223
|
+
const stopped = this.stopped(timeout, endpoint);
|
|
6224
|
+
if (stopped) throw stopped;
|
|
6225
|
+
lastError = error;
|
|
6226
|
+
if (attempt === 3) break;
|
|
6227
|
+
await pause(attempt * 750, this.signal);
|
|
6228
|
+
}
|
|
5935
6229
|
}
|
|
5936
|
-
if (!response) {
|
|
6230
|
+
if (!response || !timeout) {
|
|
5937
6231
|
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
5938
6232
|
throw new DokployApiError(`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`, 0, "Network error");
|
|
5939
6233
|
}
|
|
6234
|
+
try {
|
|
6235
|
+
return await this.read(response);
|
|
6236
|
+
} catch (error) {
|
|
6237
|
+
throw this.stopped(timeout, endpoint) ?? error;
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
/**
|
|
6241
|
+
* What a request cut short should reject with: the caller's own reason
|
|
6242
|
+
* when they aborted, `DokployRequestTimedOut` when the deadline passed,
|
|
6243
|
+
* nothing when it was neither.
|
|
6244
|
+
*/
|
|
6245
|
+
stopped(timeout, endpoint) {
|
|
6246
|
+
if (this.signal?.aborted) return this.signal.reason;
|
|
6247
|
+
if (timeout.aborted) return new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);
|
|
6248
|
+
}
|
|
6249
|
+
/** Turns a response into its JSON body, or a `DokployApiError`. */
|
|
6250
|
+
async read(response) {
|
|
5940
6251
|
if (!response.ok) {
|
|
5941
6252
|
let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
|
|
5942
6253
|
let issues;
|
|
@@ -6725,7 +7036,11 @@ function isStateProvider(value) {
|
|
|
6725
7036
|
*
|
|
6726
7037
|
* - 'local': LocalStateProvider (default)
|
|
6727
7038
|
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
7039
|
+
* - 's3': S3StateStore behind the provider interface
|
|
6728
7040
|
* - Custom: Use provided StateProvider implementation
|
|
7041
|
+
*
|
|
7042
|
+
* Deploy moves to `createStateStore` (locks, versions, per-resource records);
|
|
7043
|
+
* this stays for the call sites that read and write whole states.
|
|
6729
7044
|
*/
|
|
6730
7045
|
async function createStateProvider(options) {
|
|
6731
7046
|
const { config, workspaceRoot, workspaceName } = options;
|
|
@@ -6733,6 +7048,11 @@ async function createStateProvider(options) {
|
|
|
6733
7048
|
const { LocalStateProvider } = await import("./LocalStateProvider-D7QyJoLM.mjs");
|
|
6734
7049
|
return new LocalStateProvider(workspaceRoot);
|
|
6735
7050
|
}
|
|
7051
|
+
const { isStateStore } = await Promise.resolve().then(() => StateStore_exports);
|
|
7052
|
+
if (isStateStore(config.provider)) {
|
|
7053
|
+
const { StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7054
|
+
return new StateStoreProvider(config.provider);
|
|
7055
|
+
}
|
|
6736
7056
|
if (isStateProvider(config.provider)) return config.provider;
|
|
6737
7057
|
const provider = config.provider;
|
|
6738
7058
|
if (provider === "local") {
|
|
@@ -6740,7 +7060,10 @@ async function createStateProvider(options) {
|
|
|
6740
7060
|
return new LocalStateProvider(workspaceRoot);
|
|
6741
7061
|
}
|
|
6742
7062
|
if (provider === "ssm") {
|
|
6743
|
-
if (!workspaceName)
|
|
7063
|
+
if (!workspaceName) {
|
|
7064
|
+
const { StateStoreNeedsWorkspaceName } = await Promise.resolve().then(() => StateStore_exports);
|
|
7065
|
+
throw new StateStoreNeedsWorkspaceName("ssm");
|
|
7066
|
+
}
|
|
6744
7067
|
const { LocalStateProvider } = await import("./LocalStateProvider-D7QyJoLM.mjs");
|
|
6745
7068
|
const { SSMStateProvider } = await import("./SSMStateProvider-B0Hu1MKZ.mjs");
|
|
6746
7069
|
const { CachedStateProvider } = await Promise.resolve().then(() => CachedStateProvider_exports);
|
|
@@ -6752,47 +7075,12 @@ async function createStateProvider(options) {
|
|
|
6752
7075
|
profile: ssmConfig.profile
|
|
6753
7076
|
}), local);
|
|
6754
7077
|
}
|
|
6755
|
-
|
|
6756
|
-
}
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
generateDefineOptions: () => generateDefineOptions
|
|
6762
|
-
});
|
|
6763
|
-
/** AES-256-GCM configuration */
|
|
6764
|
-
const ALGORITHM = "aes-256-gcm";
|
|
6765
|
-
const KEY_LENGTH = 32;
|
|
6766
|
-
const IV_LENGTH = 12;
|
|
6767
|
-
/**
|
|
6768
|
-
* Encrypt secrets for embedding in a bundle.
|
|
6769
|
-
* Uses AES-256-GCM with a randomly generated ephemeral key.
|
|
6770
|
-
*
|
|
6771
|
-
* @param secrets - Key-value pairs to encrypt
|
|
6772
|
-
* @returns Encrypted payload with ephemeral master key
|
|
6773
|
-
*/
|
|
6774
|
-
function encryptSecrets(secrets) {
|
|
6775
|
-
const masterKey = randomBytes(KEY_LENGTH);
|
|
6776
|
-
const iv = randomBytes(IV_LENGTH);
|
|
6777
|
-
const plaintext = JSON.stringify(secrets);
|
|
6778
|
-
const cipher = createCipheriv(ALGORITHM, masterKey, iv);
|
|
6779
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
6780
|
-
const authTag = cipher.getAuthTag();
|
|
6781
|
-
return {
|
|
6782
|
-
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
6783
|
-
iv: iv.toString("hex"),
|
|
6784
|
-
masterKey: masterKey.toString("hex")
|
|
6785
|
-
};
|
|
6786
|
-
}
|
|
6787
|
-
/**
|
|
6788
|
-
* Generate the define options for tsdown/esbuild.
|
|
6789
|
-
* These will be injected at build time.
|
|
6790
|
-
*/
|
|
6791
|
-
function generateDefineOptions(payload) {
|
|
6792
|
-
return {
|
|
6793
|
-
__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
|
|
6794
|
-
__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
|
|
6795
|
-
};
|
|
7078
|
+
if (provider === "s3") {
|
|
7079
|
+
const { createStateStore, StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7080
|
+
return new StateStoreProvider(await createStateStore(options));
|
|
7081
|
+
}
|
|
7082
|
+
const { UnknownStateProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7083
|
+
throw new UnknownStateProvider(config);
|
|
6796
7084
|
}
|
|
6797
7085
|
//#endregion
|
|
6798
7086
|
//#region src/deploy/secrets.ts
|
|
@@ -7750,12 +8038,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7750
8038
|
}
|
|
7751
8039
|
setApplicationId(state, appName, application.applicationId);
|
|
7752
8040
|
const appSecrets = encryptedSecrets.get(appName);
|
|
7753
|
-
const
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
}
|
|
8041
|
+
const credentials = appSecrets && appSecrets.secretCount > 0 ? {
|
|
8042
|
+
encrypted: appSecrets.payload.encrypted,
|
|
8043
|
+
iv: appSecrets.payload.iv
|
|
8044
|
+
} : void 0;
|
|
8045
|
+
if (appSecrets && credentials) logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7759
8046
|
const imageName = `${workspace.name}-${appName}`;
|
|
7760
8047
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7761
8048
|
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
@@ -7768,7 +8055,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7768
8055
|
imageName,
|
|
7769
8056
|
appName
|
|
7770
8057
|
},
|
|
7771
|
-
|
|
8058
|
+
credentials
|
|
7772
8059
|
});
|
|
7773
8060
|
const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
|
|
7774
8061
|
const dependencyUrls = {};
|
|
@@ -8150,6 +8437,11 @@ async function deployListCommand(options) {
|
|
|
8150
8437
|
var CachedStateProvider_exports = /* @__PURE__ */ __exportAll({ CachedStateProvider: () => CachedStateProvider });
|
|
8151
8438
|
/**
|
|
8152
8439
|
* Cached state provider that wraps a remote provider with local cache.
|
|
8440
|
+
*
|
|
8441
|
+
* @deprecated for deploys: a read prefers the local copy, so a stale one wins
|
|
8442
|
+
* over the remote without warning, and a write overwrites the remote blindly.
|
|
8443
|
+
* Deploy uses `createStateStore`, which reads SSM directly and writes
|
|
8444
|
+
* conditionally. Kept for `gkm state:pull | push | diff`.
|
|
8153
8445
|
*/
|
|
8154
8446
|
var CachedStateProvider = class {
|
|
8155
8447
|
remote;
|
|
@@ -8200,29 +8492,438 @@ var CachedStateProvider = class {
|
|
|
8200
8492
|
}
|
|
8201
8493
|
};
|
|
8202
8494
|
//#endregion
|
|
8203
|
-
//#region src/deploy/
|
|
8495
|
+
//#region src/deploy/StateStore.ts
|
|
8204
8496
|
/**
|
|
8205
|
-
*
|
|
8497
|
+
* Deploy state store.
|
|
8206
8498
|
*
|
|
8207
|
-
*
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
*
|
|
8211
|
-
*
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8499
|
+
* `StateProvider` reads and writes a whole stage at once, with nothing to stop
|
|
8500
|
+
* two runs of the same stage from overwriting each other and nowhere to record
|
|
8501
|
+
* a resource the moment it exists. A `StateStore` adds the three things a
|
|
8502
|
+
* deploy needs to be safe against crashes and concurrent runs:
|
|
8503
|
+
*
|
|
8504
|
+
* - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the
|
|
8505
|
+
* holder, instead of racing the first.
|
|
8506
|
+
* - Versioned `read` and conditional `write`: a write names the version it was
|
|
8507
|
+
* based on, and raises `StateVersionConflict` when the stored state moved on
|
|
8508
|
+
* underneath it, so nothing is lost silently even without the lock.
|
|
8509
|
+
* - Per-resource records: `putResource` journals one resource at a time, so a
|
|
8510
|
+
* run that dies half way leaves behind the ids of everything it created.
|
|
8511
|
+
*
|
|
8512
|
+
* Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
|
|
8513
|
+
* custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
|
|
8514
|
+
* working but cannot lock.
|
|
8515
|
+
*/
|
|
8516
|
+
var StateStore_exports = /* @__PURE__ */ __exportAll({
|
|
8517
|
+
DocumentStateStore: () => DocumentStateStore,
|
|
8518
|
+
STATE_SCHEMA_VERSION: () => 2,
|
|
8519
|
+
StageStateMissing: () => StageStateMissing,
|
|
8520
|
+
StateLocked: () => StateLocked,
|
|
8521
|
+
StateSchemaTooNew: () => StateSchemaTooNew,
|
|
8522
|
+
StateStoreBusy: () => StateStoreBusy,
|
|
8523
|
+
StateStoreNeedsWorkspaceName: () => StateStoreNeedsWorkspaceName,
|
|
8524
|
+
StateStoreProvider: () => StateStoreProvider,
|
|
8525
|
+
StateStoreWithoutLocking: () => StateStoreWithoutLocking,
|
|
8526
|
+
StateUnreadable: () => StateUnreadable,
|
|
8527
|
+
StateVersionConflict: () => StateVersionConflict,
|
|
8528
|
+
UnknownStateProvider: () => UnknownStateProvider,
|
|
8529
|
+
contentVersion: () => contentVersion,
|
|
8530
|
+
createStateStore: () => createStateStore,
|
|
8531
|
+
decodeDocument: () => decodeDocument,
|
|
8532
|
+
encodeDocument: () => encodeDocument,
|
|
8533
|
+
isStateStore: () => isStateStore,
|
|
8534
|
+
migrateV1: () => migrateV1,
|
|
8535
|
+
newLockHolder: () => newLockHolder
|
|
8536
|
+
});
|
|
8537
|
+
var StateLocked = class extends Error {
|
|
8538
|
+
stage;
|
|
8539
|
+
holder;
|
|
8540
|
+
location;
|
|
8541
|
+
constructor(stage, holder, location) {
|
|
8542
|
+
const by = holder ? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ""}` : "a run that did not record who it was";
|
|
8543
|
+
super(`Deploy state for stage '${stage}' is locked by ${by} (${location}). Wait for that run to finish; if it crashed, release the lock with \`gkm state:unlock --stage ${stage}\`.`);
|
|
8544
|
+
this.stage = stage;
|
|
8545
|
+
this.holder = holder;
|
|
8546
|
+
this.location = location;
|
|
8547
|
+
this.name = "StateLocked";
|
|
8220
8548
|
}
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8549
|
+
};
|
|
8550
|
+
var StateVersionConflict = class extends Error {
|
|
8551
|
+
stage;
|
|
8552
|
+
expectedVersion;
|
|
8553
|
+
actualVersion;
|
|
8554
|
+
constructor(stage, expectedVersion, actualVersion) {
|
|
8555
|
+
super(`Deploy state for stage '${stage}' changed since it was read (expected ${expectedVersion ? `version ${expectedVersion}` : "no state"}, found ${actualVersion ? `version ${actualVersion}` : "no state"}). Another run wrote it: read the state again and re-apply the change, and hold the stage's lock so runs take turns.`);
|
|
8556
|
+
this.stage = stage;
|
|
8557
|
+
this.expectedVersion = expectedVersion;
|
|
8558
|
+
this.actualVersion = actualVersion;
|
|
8559
|
+
this.name = "StateVersionConflict";
|
|
8560
|
+
}
|
|
8561
|
+
};
|
|
8562
|
+
var StageStateMissing = class extends Error {
|
|
8563
|
+
stage;
|
|
8564
|
+
constructor(stage) {
|
|
8565
|
+
super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
|
|
8566
|
+
this.stage = stage;
|
|
8567
|
+
this.name = "StageStateMissing";
|
|
8568
|
+
}
|
|
8569
|
+
};
|
|
8570
|
+
var StateUnreadable = class extends Error {
|
|
8571
|
+
stage;
|
|
8572
|
+
location;
|
|
8573
|
+
constructor(stage, location, cause) {
|
|
8574
|
+
super(`Deploy state for stage '${stage}' at ${location} is not valid JSON. Restore it from a backup (or the provider's history) rather than deploying without it — a deploy without state recreates every resource.`, { cause });
|
|
8575
|
+
this.stage = stage;
|
|
8576
|
+
this.location = location;
|
|
8577
|
+
this.name = "StateUnreadable";
|
|
8578
|
+
}
|
|
8579
|
+
};
|
|
8580
|
+
var StateSchemaTooNew = class extends Error {
|
|
8581
|
+
stage;
|
|
8582
|
+
schemaVersion;
|
|
8583
|
+
constructor(stage, schemaVersion) {
|
|
8584
|
+
super(`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, newer than this @geekmidas/cli understands (2). Upgrade @geekmidas/cli to the version that wrote it.`);
|
|
8585
|
+
this.stage = stage;
|
|
8586
|
+
this.schemaVersion = schemaVersion;
|
|
8587
|
+
this.name = "StateSchemaTooNew";
|
|
8588
|
+
}
|
|
8589
|
+
};
|
|
8590
|
+
/**
|
|
8591
|
+
* Not thrown: emitted (once per store) when a custom `StateProvider` stands
|
|
8592
|
+
* in for a store, so a deploy that cannot be protected from a concurrent one
|
|
8593
|
+
* says so.
|
|
8594
|
+
*/
|
|
8595
|
+
var StateStoreWithoutLocking = class extends Error {
|
|
8596
|
+
provider;
|
|
8597
|
+
constructor(provider) {
|
|
8598
|
+
super(`The custom state provider '${provider}' has no lock and no versions, so two deploys of the same stage can still overwrite each other. Implement StateStore (lock, versioned read, conditional write) or use the 'local', 'ssm' or 's3' state provider.`);
|
|
8599
|
+
this.provider = provider;
|
|
8600
|
+
this.name = "StateStoreWithoutLocking";
|
|
8601
|
+
}
|
|
8602
|
+
};
|
|
8603
|
+
var StateStoreNeedsWorkspaceName = class extends Error {
|
|
8604
|
+
provider;
|
|
8605
|
+
constructor(provider) {
|
|
8606
|
+
super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
|
|
8607
|
+
this.provider = provider;
|
|
8608
|
+
this.name = "StateStoreNeedsWorkspaceName";
|
|
8609
|
+
}
|
|
8610
|
+
};
|
|
8611
|
+
var UnknownStateProvider = class extends Error {
|
|
8612
|
+
config;
|
|
8613
|
+
constructor(config) {
|
|
8614
|
+
super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
|
|
8615
|
+
this.config = config;
|
|
8616
|
+
this.name = "UnknownStateProvider";
|
|
8617
|
+
}
|
|
8618
|
+
};
|
|
8619
|
+
/** A lock or write that could not get at the state backend in time. */
|
|
8620
|
+
var StateStoreBusy = class extends Error {
|
|
8621
|
+
stage;
|
|
8622
|
+
location;
|
|
8623
|
+
constructor(stage, location) {
|
|
8624
|
+
super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
|
|
8625
|
+
this.stage = stage;
|
|
8626
|
+
this.location = location;
|
|
8627
|
+
this.name = "StateStoreBusy";
|
|
8628
|
+
}
|
|
8629
|
+
};
|
|
8630
|
+
function decodeDocument(stage, body, location) {
|
|
8631
|
+
let parsed;
|
|
8632
|
+
try {
|
|
8633
|
+
parsed = JSON.parse(body);
|
|
8634
|
+
} catch (error) {
|
|
8635
|
+
throw new StateUnreadable(stage, location, error);
|
|
8636
|
+
}
|
|
8637
|
+
if (typeof parsed !== "object" || parsed === null) throw new StateUnreadable(stage, location, parsed);
|
|
8638
|
+
const schemaVersion = parsed.schemaVersion;
|
|
8639
|
+
if (schemaVersion === void 0) return {
|
|
8640
|
+
schemaVersion: 1,
|
|
8641
|
+
state: parsed
|
|
8642
|
+
};
|
|
8643
|
+
if (schemaVersion === 2) return {
|
|
8644
|
+
schemaVersion: 2,
|
|
8645
|
+
document: parsed
|
|
8646
|
+
};
|
|
8647
|
+
throw new StateSchemaTooNew(stage, schemaVersion);
|
|
8648
|
+
}
|
|
8649
|
+
function encodeDocument(document) {
|
|
8650
|
+
return JSON.stringify(document, null, 2);
|
|
8651
|
+
}
|
|
8652
|
+
/**
|
|
8653
|
+
* v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
|
|
8654
|
+
* so the first journaled deploy after an upgrade adopts them rather than
|
|
8655
|
+
* treating them as unknown.
|
|
8656
|
+
*/
|
|
8657
|
+
function migrateV1(stage, state) {
|
|
8658
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8659
|
+
const resources = {};
|
|
8660
|
+
const seed = (type, name, id) => {
|
|
8661
|
+
if (!id) return;
|
|
8662
|
+
const key = name ? `${type}:${name}` : type;
|
|
8663
|
+
resources[key] = {
|
|
8664
|
+
key,
|
|
8665
|
+
type,
|
|
8666
|
+
id,
|
|
8667
|
+
status: "ready",
|
|
8668
|
+
updatedAt: now
|
|
8669
|
+
};
|
|
8670
|
+
};
|
|
8671
|
+
seed("project", null, state.projectId);
|
|
8672
|
+
seed("environment", null, state.environmentId);
|
|
8673
|
+
for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
|
|
8674
|
+
seed("postgres", null, state.services?.postgresId);
|
|
8675
|
+
seed("redis", null, state.services?.redisId);
|
|
8676
|
+
seed("backup-destination", null, state.backups?.destinationId);
|
|
8677
|
+
return {
|
|
8678
|
+
schemaVersion: 2,
|
|
8679
|
+
stage,
|
|
8680
|
+
serial: 1,
|
|
8681
|
+
state,
|
|
8682
|
+
resources,
|
|
8683
|
+
updatedAt: now
|
|
8684
|
+
};
|
|
8685
|
+
}
|
|
8686
|
+
function newLockHolder(options) {
|
|
8687
|
+
let owner = "unknown";
|
|
8688
|
+
try {
|
|
8689
|
+
owner = userInfo().username;
|
|
8690
|
+
} catch {}
|
|
8691
|
+
return {
|
|
8692
|
+
id: randomUUID(),
|
|
8693
|
+
owner,
|
|
8694
|
+
host: hostname(),
|
|
8695
|
+
pid: process.pid,
|
|
8696
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8697
|
+
operation: options?.operation
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8700
|
+
function contentVersion(body) {
|
|
8701
|
+
return createHash("sha256").update(body).digest("hex").slice(0, 16);
|
|
8702
|
+
}
|
|
8703
|
+
/**
|
|
8704
|
+
* A store over a backend that can read and conditionally write one blob per
|
|
8705
|
+
* stage, and create a lock object only if it does not exist. Migration and
|
|
8706
|
+
* resource records are the same for every such backend, so they live here.
|
|
8707
|
+
*/
|
|
8708
|
+
var DocumentStateStore = class {
|
|
8709
|
+
async lock(stage, options) {
|
|
8710
|
+
const holder = newLockHolder(options);
|
|
8711
|
+
await this.createLock(stage, holder);
|
|
8712
|
+
let released = false;
|
|
8713
|
+
return {
|
|
8714
|
+
stage,
|
|
8715
|
+
holder,
|
|
8716
|
+
release: async () => {
|
|
8717
|
+
if (released) return;
|
|
8718
|
+
released = true;
|
|
8719
|
+
await this.removeLock(stage, holder);
|
|
8720
|
+
}
|
|
8721
|
+
};
|
|
8722
|
+
}
|
|
8723
|
+
async forceUnlock(stage) {
|
|
8724
|
+
const holder = await this.readLock(stage);
|
|
8725
|
+
await this.removeLock(stage, null);
|
|
8726
|
+
return holder;
|
|
8727
|
+
}
|
|
8728
|
+
async read(stage) {
|
|
8729
|
+
const current = await this.readDocument(stage);
|
|
8730
|
+
if (!current) return null;
|
|
8731
|
+
return {
|
|
8732
|
+
state: current.document.state,
|
|
8733
|
+
resources: current.document.resources,
|
|
8734
|
+
version: current.version
|
|
8735
|
+
};
|
|
8736
|
+
}
|
|
8737
|
+
async write(stage, state, options) {
|
|
8738
|
+
if (options.expectedVersion === null) {
|
|
8739
|
+
const document = {
|
|
8740
|
+
schemaVersion: 2,
|
|
8741
|
+
stage,
|
|
8742
|
+
serial: 1,
|
|
8743
|
+
state,
|
|
8744
|
+
resources: {},
|
|
8745
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8746
|
+
};
|
|
8747
|
+
return this.writeRaw(stage, encodeDocument(document), null);
|
|
8748
|
+
}
|
|
8749
|
+
return this.mutate(stage, options.expectedVersion, (document) => ({
|
|
8750
|
+
...document,
|
|
8751
|
+
state
|
|
8752
|
+
}));
|
|
8753
|
+
}
|
|
8754
|
+
async putResource(stage, record, options) {
|
|
8755
|
+
return this.mutate(stage, options?.expectedVersion, (document) => ({
|
|
8756
|
+
...document,
|
|
8757
|
+
resources: {
|
|
8758
|
+
...document.resources,
|
|
8759
|
+
[record.key]: {
|
|
8760
|
+
...record,
|
|
8761
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
}));
|
|
8765
|
+
}
|
|
8766
|
+
async deleteResource(stage, key, options) {
|
|
8767
|
+
return this.mutate(stage, options?.expectedVersion, (document) => {
|
|
8768
|
+
const { [key]: _removed, ...resources } = document.resources;
|
|
8769
|
+
return {
|
|
8770
|
+
...document,
|
|
8771
|
+
resources
|
|
8772
|
+
};
|
|
8773
|
+
});
|
|
8774
|
+
}
|
|
8775
|
+
async mutate(stage, expectedVersion, change) {
|
|
8776
|
+
const current = await this.readDocument(stage);
|
|
8777
|
+
if (!current) {
|
|
8778
|
+
if (expectedVersion !== void 0) throw new StateVersionConflict(stage, expectedVersion, null);
|
|
8779
|
+
throw new StageStateMissing(stage);
|
|
8780
|
+
}
|
|
8781
|
+
if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
|
|
8782
|
+
const next = change(current.document);
|
|
8783
|
+
next.serial = current.document.serial + 1;
|
|
8784
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8785
|
+
return this.writeRaw(stage, encodeDocument(next), current.version);
|
|
8786
|
+
}
|
|
8787
|
+
/**
|
|
8788
|
+
* Reads the stage's document, migrating v1 in place on the way. The
|
|
8789
|
+
* migration is itself a conditional write: if another run migrated (or
|
|
8790
|
+
* wrote) first, its result is read instead.
|
|
8791
|
+
*/
|
|
8792
|
+
async readDocument(stage, attempt = 0) {
|
|
8793
|
+
const raw = await this.readRaw(stage);
|
|
8794
|
+
if (!raw) return null;
|
|
8795
|
+
const decoded = decodeDocument(stage, raw.body, this.location(stage));
|
|
8796
|
+
if (decoded.schemaVersion === 2) return {
|
|
8797
|
+
document: decoded.document,
|
|
8798
|
+
version: raw.version
|
|
8799
|
+
};
|
|
8800
|
+
await this.writeV1Backup(stage, raw.body);
|
|
8801
|
+
const document = migrateV1(stage, decoded.state);
|
|
8802
|
+
try {
|
|
8803
|
+
return {
|
|
8804
|
+
document,
|
|
8805
|
+
version: await this.writeRaw(stage, encodeDocument(document), raw.version)
|
|
8806
|
+
};
|
|
8807
|
+
} catch (error) {
|
|
8808
|
+
if (error instanceof StateVersionConflict && attempt < 2) return this.readDocument(stage, attempt + 1);
|
|
8809
|
+
throw error;
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
};
|
|
8813
|
+
function isStateStore(value) {
|
|
8814
|
+
return typeof value === "object" && value !== null && typeof value.lock === "function" && typeof value.read === "function" && typeof value.write === "function" && typeof value.putResource === "function";
|
|
8815
|
+
}
|
|
8816
|
+
/**
|
|
8817
|
+
* The store for a workspace's `state` config — the same config
|
|
8818
|
+
* `createStateProvider` reads:
|
|
8819
|
+
*
|
|
8820
|
+
* - none / `'local'`: `LocalStateStore` under `.gkm/`
|
|
8821
|
+
* - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
|
|
8822
|
+
* a stale local copy can never win over it
|
|
8823
|
+
* - `'s3'`: `S3StateStore`
|
|
8824
|
+
* - a `StateStore` object: used as is
|
|
8825
|
+
* - a `StateProvider` object: wrapped by `LegacyStateStore`
|
|
8826
|
+
*/
|
|
8827
|
+
async function createStateStore(options) {
|
|
8828
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
8829
|
+
if (!config || config.provider === "local") {
|
|
8830
|
+
const { LocalStateStore } = await import("./LocalStateStore-BupdV-VN.mjs");
|
|
8831
|
+
return new LocalStateStore(workspaceRoot);
|
|
8832
|
+
}
|
|
8833
|
+
if (isStateStore(config.provider)) return config.provider;
|
|
8834
|
+
if (isStateProvider(config.provider)) {
|
|
8835
|
+
const { LegacyStateStore } = await import("./LegacyStateStore-ByRWQ9s1.mjs");
|
|
8836
|
+
return new LegacyStateStore(config.provider, { warn: options.warn });
|
|
8837
|
+
}
|
|
8838
|
+
if (config.provider === "ssm") {
|
|
8839
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
|
|
8840
|
+
const { SSMStateStore } = await import("./SSMStateStore-oda9xOGc.mjs");
|
|
8841
|
+
const ssm = config;
|
|
8842
|
+
return SSMStateStore.create({
|
|
8843
|
+
workspaceName,
|
|
8844
|
+
region: ssm.region,
|
|
8845
|
+
profile: ssm.profile
|
|
8846
|
+
});
|
|
8847
|
+
}
|
|
8848
|
+
if (config.provider === "s3") {
|
|
8849
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("s3");
|
|
8850
|
+
const { S3StateStore } = await import("./S3StateStore-FhzyENrN.mjs");
|
|
8851
|
+
const s3 = config;
|
|
8852
|
+
return S3StateStore.create({
|
|
8853
|
+
workspaceName,
|
|
8854
|
+
bucket: s3.bucket,
|
|
8855
|
+
prefix: s3.prefix,
|
|
8856
|
+
region: s3.region,
|
|
8857
|
+
profile: s3.profile
|
|
8858
|
+
});
|
|
8859
|
+
}
|
|
8860
|
+
throw new UnknownStateProvider(config);
|
|
8861
|
+
}
|
|
8862
|
+
/**
|
|
8863
|
+
* A store behind the `StateProvider` interface, for the call sites that still
|
|
8864
|
+
* read and write whole states (deploy, until it journals). A write is still
|
|
8865
|
+
* conditional on the version this provider last read.
|
|
8866
|
+
*/
|
|
8867
|
+
var StateStoreProvider = class {
|
|
8868
|
+
store;
|
|
8869
|
+
versions = /* @__PURE__ */ new Map();
|
|
8870
|
+
constructor(store) {
|
|
8871
|
+
this.store = store;
|
|
8872
|
+
}
|
|
8873
|
+
async read(stage) {
|
|
8874
|
+
const stored = await this.store.read(stage);
|
|
8875
|
+
this.versions.set(stage, stored?.version ?? null);
|
|
8876
|
+
return stored?.state ?? null;
|
|
8877
|
+
}
|
|
8878
|
+
async write(stage, state) {
|
|
8879
|
+
const expectedVersion = this.versions.has(stage) ? this.versions.get(stage) ?? null : (await this.store.read(stage))?.version ?? null;
|
|
8880
|
+
state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8881
|
+
const version = await this.store.write(stage, state, { expectedVersion });
|
|
8882
|
+
this.versions.set(stage, version);
|
|
8883
|
+
}
|
|
8884
|
+
};
|
|
8885
|
+
//#endregion
|
|
8886
|
+
//#region src/deploy/state-commands.ts
|
|
8887
|
+
/**
|
|
8888
|
+
* State Management CLI Commands
|
|
8889
|
+
*
|
|
8890
|
+
* Commands for managing deployment state across local and remote providers.
|
|
8891
|
+
*/
|
|
8892
|
+
/** What `state:show` prints in place of a secret. */
|
|
8893
|
+
const MASKED = "********";
|
|
8894
|
+
/**
|
|
8895
|
+
* A copy of `state` with every secret replaced by `MASKED`: database
|
|
8896
|
+
* passwords, generated secrets and the backup IAM keys. `state:show` is run
|
|
8897
|
+
* to look up ids, in terminals and CI logs; nothing it prints should be
|
|
8898
|
+
* usable as a credential.
|
|
8899
|
+
*/
|
|
8900
|
+
function maskStateSecrets(state) {
|
|
8901
|
+
const masked = structuredClone(state);
|
|
8902
|
+
for (const credentials of Object.values(masked.appCredentials ?? {})) credentials.dbPassword = MASKED;
|
|
8903
|
+
for (const secrets of Object.values(masked.generatedSecrets ?? {})) for (const name of Object.keys(secrets)) secrets[name] = MASKED;
|
|
8904
|
+
if (masked.backups) {
|
|
8905
|
+
masked.backups.iamAccessKeyId = MASKED;
|
|
8906
|
+
masked.backups.iamSecretAccessKey = MASKED;
|
|
8907
|
+
}
|
|
8908
|
+
return masked;
|
|
8909
|
+
}
|
|
8910
|
+
/**
|
|
8911
|
+
* Pull state from remote to local.
|
|
8912
|
+
* `gkm state:pull --stage=<stage>`
|
|
8913
|
+
*/
|
|
8914
|
+
async function statePullCommand(options) {
|
|
8915
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
8916
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
8917
|
+
console.error("No remote state provider configured.");
|
|
8918
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
8919
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
8920
|
+
process.exit(1);
|
|
8921
|
+
}
|
|
8922
|
+
const provider = await createStateProvider({
|
|
8923
|
+
config: workspace.state,
|
|
8924
|
+
workspaceRoot: workspace.root,
|
|
8925
|
+
workspaceName: workspace.name
|
|
8926
|
+
});
|
|
8226
8927
|
if (!(provider instanceof CachedStateProvider)) {
|
|
8227
8928
|
console.error("State provider does not support pull operation.");
|
|
8228
8929
|
process.exit(1);
|
|
@@ -8268,19 +8969,41 @@ async function statePushCommand(options) {
|
|
|
8268
8969
|
*/
|
|
8269
8970
|
async function stateShowCommand(options) {
|
|
8270
8971
|
const { workspace } = await loadWorkspaceConfig();
|
|
8271
|
-
const
|
|
8972
|
+
const stored = await (await createStateProvider({
|
|
8272
8973
|
config: workspace.state,
|
|
8273
8974
|
workspaceRoot: workspace.root,
|
|
8274
8975
|
workspaceName: workspace.name
|
|
8275
8976
|
})).read(options.stage);
|
|
8276
|
-
if (!
|
|
8977
|
+
if (!stored) {
|
|
8277
8978
|
console.log(`No state found for stage: ${options.stage}`);
|
|
8278
8979
|
return;
|
|
8279
8980
|
}
|
|
8981
|
+
const state = maskStateSecrets(stored);
|
|
8280
8982
|
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
8281
8983
|
else printStateDetails(state);
|
|
8282
8984
|
}
|
|
8283
8985
|
/**
|
|
8986
|
+
* Release a stage's deploy lock, whoever holds it.
|
|
8987
|
+
* `gkm state:unlock --stage=<stage>`
|
|
8988
|
+
*
|
|
8989
|
+
* For a run that crashed with the lock held. Releasing a lock a live run
|
|
8990
|
+
* holds lets a second run race it, so the holder is printed for the caller
|
|
8991
|
+
* to check.
|
|
8992
|
+
*/
|
|
8993
|
+
async function stateUnlockCommand(options) {
|
|
8994
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
8995
|
+
const holder = await (await createStateStore({
|
|
8996
|
+
config: workspace.state,
|
|
8997
|
+
workspaceRoot: workspace.root,
|
|
8998
|
+
workspaceName: workspace.name
|
|
8999
|
+
})).forceUnlock(options.stage);
|
|
9000
|
+
if (!holder) {
|
|
9001
|
+
console.log(`Stage ${options.stage} was not locked.`);
|
|
9002
|
+
return;
|
|
9003
|
+
}
|
|
9004
|
+
console.log(`Released the lock on stage ${options.stage}, held by ${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}.`);
|
|
9005
|
+
}
|
|
9006
|
+
/**
|
|
8284
9007
|
* Compare local and remote state.
|
|
8285
9008
|
* `gkm state:diff --stage=<stage>`
|
|
8286
9009
|
*/
|
|
@@ -8364,6 +9087,26 @@ function printStateDetails(state) {
|
|
|
8364
9087
|
if (state.services.postgresId) console.log(` Postgres: ${state.services.postgresId}`);
|
|
8365
9088
|
if (state.services.redisId) console.log(` Redis: ${state.services.redisId}`);
|
|
8366
9089
|
}
|
|
9090
|
+
const credentials = Object.entries(state.appCredentials ?? {});
|
|
9091
|
+
if (credentials.length > 0) {
|
|
9092
|
+
console.log("");
|
|
9093
|
+
console.log("Database Credentials:");
|
|
9094
|
+
for (const [app, { dbUser, dbPassword }] of credentials) console.log(` ${app}: ${dbUser} / ${dbPassword}`);
|
|
9095
|
+
}
|
|
9096
|
+
const generated = Object.entries(state.generatedSecrets ?? {});
|
|
9097
|
+
if (generated.length > 0) {
|
|
9098
|
+
console.log("");
|
|
9099
|
+
console.log("Generated Secrets:");
|
|
9100
|
+
for (const [app, secrets] of generated) for (const [name, value] of Object.entries(secrets)) console.log(` ${app}.${name}: ${value}`);
|
|
9101
|
+
}
|
|
9102
|
+
if (state.backups) {
|
|
9103
|
+
console.log("");
|
|
9104
|
+
console.log("Backups:");
|
|
9105
|
+
console.log(` Bucket: ${state.backups.bucketName}`);
|
|
9106
|
+
console.log(` IAM User: ${state.backups.iamUserName}`);
|
|
9107
|
+
console.log(` IAM Access Key: ${state.backups.iamAccessKeyId}`);
|
|
9108
|
+
console.log(` IAM Secret Key: ${state.backups.iamSecretAccessKey}`);
|
|
9109
|
+
}
|
|
8367
9110
|
if (state.dnsVerified && Object.keys(state.dnsVerified).length > 0) {
|
|
8368
9111
|
console.log("");
|
|
8369
9112
|
console.log("DNS Verified:");
|
|
@@ -8760,7 +9503,7 @@ opened.
|
|
|
8760
9503
|
`;
|
|
8761
9504
|
}
|
|
8762
9505
|
function agentsContent(options, template) {
|
|
8763
|
-
const { name, monorepo, telescope,
|
|
9506
|
+
const { name, monorepo, telescope, packageManager } = options;
|
|
8764
9507
|
const { database } = options.constructs;
|
|
8765
9508
|
const constructsGlob = monorepo ? WORKSPACE_CONSTRUCTS_GLOB : CONSTRUCTS_GLOB;
|
|
8766
9509
|
const isWorker = template.name === "worker";
|
|
@@ -8855,13 +9598,13 @@ export const config = envParser
|
|
|
8855
9598
|
as \`undefined\` in exactly the environments that matter.`);
|
|
8856
9599
|
sections.push(conventionsSection());
|
|
8857
9600
|
sections.push(testingSection(database));
|
|
8858
|
-
if (telescope ||
|
|
8859
|
-
const tools = [telescope && "**Telescope** —
|
|
9601
|
+
if (telescope || database) {
|
|
9602
|
+
const tools = [telescope && "**Telescope** — requests, exceptions and logs as JSON at `/__telescope/api`", database && "**Database API** — `gkm dev` serves the declared database, read-only, as JSON at `/__gkm/db`"].filter(Boolean);
|
|
8860
9603
|
sections.push(`## Dev tools
|
|
8861
9604
|
|
|
8862
9605
|
${tools.map((t) => `- ${t}`).join("\n")}
|
|
8863
9606
|
|
|
8864
|
-
|
|
9607
|
+
Derived from declared constructs and served only by \`gkm dev\`: nothing to wire up by hand, and no UI — JSON for whatever tool you point at it.`);
|
|
8865
9608
|
}
|
|
8866
9609
|
sections.push(`## Before you say it works
|
|
8867
9610
|
|
|
@@ -9304,11 +10047,7 @@ const TOOLCHAIN_VERSIONS = {
|
|
|
9304
10047
|
esbuild: "~0.28.2",
|
|
9305
10048
|
vitest: "~5.0.2",
|
|
9306
10049
|
vite: "~8.3.1",
|
|
9307
|
-
"@vitejs/plugin-react": "^6.1.1"
|
|
9308
|
-
storybook: "^10.6.0",
|
|
9309
|
-
"@storybook/addon-docs": "^10.6.0",
|
|
9310
|
-
"@storybook/addon-a11y": "^10.6.0",
|
|
9311
|
-
"@storybook/react-vite": "^10.6.0"
|
|
10050
|
+
"@vitejs/plugin-react": "^6.1.1"
|
|
9312
10051
|
};
|
|
9313
10052
|
/**
|
|
9314
10053
|
* The Expo app's packages, which track an Expo SDK release (57) rather than
|
|
@@ -9368,26 +10107,25 @@ function loadPackageJson() {
|
|
|
9368
10107
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
9369
10108
|
*/
|
|
9370
10109
|
const GEEKMIDAS_VERSIONS = {
|
|
9371
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
9372
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
9373
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
9374
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
9375
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
9376
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
9377
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
9378
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
9379
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
9380
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
9381
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
9382
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
9383
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
9384
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
9385
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
9386
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
9387
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
9388
|
-
"@geekmidas/
|
|
9389
|
-
"@geekmidas/
|
|
9390
|
-
"@geekmidas/testkit": "~10.0.0-alpha.54",
|
|
10110
|
+
"@geekmidas/audit": "~10.0.0-alpha.56",
|
|
10111
|
+
"@geekmidas/auth": "~10.0.0-alpha.56",
|
|
10112
|
+
"@geekmidas/cache": "~10.0.0-alpha.56",
|
|
10113
|
+
"@geekmidas/client": "~10.0.0-alpha.56",
|
|
10114
|
+
"@geekmidas/cloud": "~10.0.0-alpha.56",
|
|
10115
|
+
"@geekmidas/constructs": "~10.0.0-alpha.56",
|
|
10116
|
+
"@geekmidas/db": "~10.0.0-alpha.56",
|
|
10117
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.56",
|
|
10118
|
+
"@geekmidas/envkit": "~10.0.0-alpha.56",
|
|
10119
|
+
"@geekmidas/errors": "~10.0.0-alpha.56",
|
|
10120
|
+
"@geekmidas/events": "~10.0.0-alpha.56",
|
|
10121
|
+
"@geekmidas/logger": "~10.0.0-alpha.56",
|
|
10122
|
+
"@geekmidas/manifest": "~10.0.0-alpha.56",
|
|
10123
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.56",
|
|
10124
|
+
"@geekmidas/schema": "~10.0.0-alpha.56",
|
|
10125
|
+
"@geekmidas/services": "~10.0.0-alpha.56",
|
|
10126
|
+
"@geekmidas/storage": "~10.0.0-alpha.56",
|
|
10127
|
+
"@geekmidas/telescope": "~10.0.0-alpha.56",
|
|
10128
|
+
"@geekmidas/testkit": "~10.0.0-alpha.56",
|
|
9391
10129
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
9392
10130
|
};
|
|
9393
10131
|
//#endregion
|
|
@@ -9510,14 +10248,13 @@ export default defineConfig({
|
|
|
9510
10248
|
* Generate configuration files (gkm.config.ts, tsconfig.json, biome.json, turbo.json)
|
|
9511
10249
|
*/
|
|
9512
10250
|
function generateConfigFiles(options, template) {
|
|
9513
|
-
const { telescope,
|
|
10251
|
+
const { telescope, routesStructure } = options;
|
|
9514
10252
|
const isServerless = template.name === "serverless";
|
|
9515
10253
|
const hasWorker = template.name === "worker";
|
|
9516
10254
|
const isFullstack = options.template === "fullstack";
|
|
9517
10255
|
const getRoutesGlob = () => routesGlob(routesStructure);
|
|
9518
10256
|
if (isFullstack) return generateSingleAppConfigFiles(options, template, {
|
|
9519
10257
|
telescope,
|
|
9520
|
-
studio,
|
|
9521
10258
|
routesStructure,
|
|
9522
10259
|
isServerless,
|
|
9523
10260
|
hasWorker,
|
|
@@ -9543,8 +10280,6 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
9543
10280
|
enabled: true,
|
|
9544
10281
|
path: '/__telescope',
|
|
9545
10282
|
},`;
|
|
9546
|
-
if (studio) gkmConfig += `
|
|
9547
|
-
studio: './src/config/studio#studio',`;
|
|
9548
10283
|
gkmConfig += `
|
|
9549
10284
|
openapi: {
|
|
9550
10285
|
enabled: true,
|
|
@@ -11093,7 +11828,6 @@ function generateMonorepoFiles(options, _template) {
|
|
|
11093
11828
|
lint: "biome lint .",
|
|
11094
11829
|
fmt: "biome format . --write",
|
|
11095
11830
|
"fmt:check": "biome format .",
|
|
11096
|
-
...isFullstack ? { storybook: "pnpm --filter ./packages/ui storybook" } : {},
|
|
11097
11831
|
...deploy.scripts
|
|
11098
11832
|
},
|
|
11099
11833
|
dependencies: {
|
|
@@ -11634,14 +12368,13 @@ export const cache = new Cache('${kv.id}');
|
|
|
11634
12368
|
* Generate package.json with dependencies based on template and options
|
|
11635
12369
|
*/
|
|
11636
12370
|
function generatePackageJson(options, template) {
|
|
11637
|
-
const { name, telescope,
|
|
12371
|
+
const { name, telescope, monorepo } = options;
|
|
11638
12372
|
const { database, cache, uploads, mail } = options.constructs;
|
|
11639
12373
|
const dependencies = { ...template.dependencies };
|
|
11640
12374
|
const devDependencies = { ...template.devDependencies };
|
|
11641
12375
|
const scripts = { ...template.scripts };
|
|
11642
12376
|
if (options.loggerType === "pino") dependencies.pino = DEPENDENCY_VERSIONS.pino;
|
|
11643
12377
|
if (telescope) dependencies["@geekmidas/telescope"] = GEEKMIDAS_VERSIONS["@geekmidas/telescope"];
|
|
11644
|
-
if (studio) dependencies["@geekmidas/studio"] = GEEKMIDAS_VERSIONS["@geekmidas/studio"];
|
|
11645
12378
|
const declares = !monorepo;
|
|
11646
12379
|
if (declares && (database || uploads || cache || mail)) dependencies["@geekmidas/manifest"] = GEEKMIDAS_VERSIONS["@geekmidas/manifest"];
|
|
11647
12380
|
if (declares && uploads) dependencies["@geekmidas/storage"] = GEEKMIDAS_VERSIONS["@geekmidas/storage"];
|
|
@@ -11782,158 +12515,33 @@ export type Factory = ReturnType<typeof createFactory>;
|
|
|
11782
12515
|
}];
|
|
11783
12516
|
}
|
|
11784
12517
|
//#endregion
|
|
11785
|
-
//#region src/init/generators/
|
|
12518
|
+
//#region src/init/generators/shadcn.ts
|
|
11786
12519
|
/**
|
|
11787
|
-
*
|
|
11788
|
-
*
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
"build:storybook": "storybook build -o dist/storybook"
|
|
11807
|
-
},
|
|
11808
|
-
dependencies: {
|
|
11809
|
-
"@radix-ui/react-dialog": DEPENDENCY_VERSIONS["@radix-ui/react-dialog"],
|
|
11810
|
-
"@radix-ui/react-label": DEPENDENCY_VERSIONS["@radix-ui/react-label"],
|
|
11811
|
-
"@radix-ui/react-separator": DEPENDENCY_VERSIONS["@radix-ui/react-separator"],
|
|
11812
|
-
"@radix-ui/react-slot": DEPENDENCY_VERSIONS["@radix-ui/react-slot"],
|
|
11813
|
-
"@radix-ui/react-tabs": DEPENDENCY_VERSIONS["@radix-ui/react-tabs"],
|
|
11814
|
-
"@radix-ui/react-tooltip": DEPENDENCY_VERSIONS["@radix-ui/react-tooltip"],
|
|
11815
|
-
"class-variance-authority": DEPENDENCY_VERSIONS["class-variance-authority"],
|
|
11816
|
-
clsx: DEPENDENCY_VERSIONS["clsx"],
|
|
11817
|
-
"lucide-react": DEPENDENCY_VERSIONS["lucide-react"],
|
|
11818
|
-
"tailwind-merge": DEPENDENCY_VERSIONS["tailwind-merge"]
|
|
11819
|
-
},
|
|
11820
|
-
devDependencies: {
|
|
11821
|
-
"@storybook/addon-a11y": TOOLCHAIN_VERSIONS["@storybook/addon-a11y"],
|
|
11822
|
-
"@storybook/addon-docs": TOOLCHAIN_VERSIONS["@storybook/addon-docs"],
|
|
11823
|
-
"@storybook/react-vite": TOOLCHAIN_VERSIONS["@storybook/react-vite"],
|
|
11824
|
-
"@tailwindcss/vite": DEPENDENCY_VERSIONS["@tailwindcss/vite"],
|
|
11825
|
-
"@types/react": DEPENDENCY_VERSIONS["@types/react"],
|
|
11826
|
-
"@types/react-dom": DEPENDENCY_VERSIONS["@types/react-dom"],
|
|
11827
|
-
react: DEPENDENCY_VERSIONS["react"],
|
|
11828
|
-
"react-dom": DEPENDENCY_VERSIONS["react-dom"],
|
|
11829
|
-
storybook: TOOLCHAIN_VERSIONS["storybook"],
|
|
11830
|
-
tailwindcss: DEPENDENCY_VERSIONS["tailwindcss"],
|
|
11831
|
-
typescript: TOOLCHAIN_VERSIONS["typescript"],
|
|
11832
|
-
vite: TOOLCHAIN_VERSIONS["vite"]
|
|
11833
|
-
},
|
|
11834
|
-
peerDependencies: {
|
|
11835
|
-
react: ">=18.0.0",
|
|
11836
|
-
"react-dom": ">=18.0.0",
|
|
11837
|
-
tailwindcss: ">=4.0.0"
|
|
11838
|
-
}
|
|
11839
|
-
};
|
|
11840
|
-
const tsConfig = {
|
|
11841
|
-
extends: "../../tsconfig.json",
|
|
11842
|
-
compilerOptions: {
|
|
11843
|
-
jsx: "react-jsx",
|
|
11844
|
-
lib: [
|
|
11845
|
-
"ES2023",
|
|
11846
|
-
"DOM",
|
|
11847
|
-
"DOM.Iterable"
|
|
11848
|
-
],
|
|
11849
|
-
noEmit: true,
|
|
11850
|
-
allowImportingTsExtensions: true,
|
|
11851
|
-
paths: { "~/*": ["./src/*"] }
|
|
11852
|
-
},
|
|
11853
|
-
include: ["src/**/*"],
|
|
11854
|
-
exclude: [
|
|
11855
|
-
"node_modules",
|
|
11856
|
-
"dist",
|
|
11857
|
-
"**/*.stories.tsx"
|
|
11858
|
-
]
|
|
11859
|
-
};
|
|
11860
|
-
const componentsJson = {
|
|
11861
|
-
$schema: "https://ui.shadcn.com/schema.json",
|
|
11862
|
-
style: "new-york",
|
|
11863
|
-
rsc: false,
|
|
11864
|
-
tsx: true,
|
|
11865
|
-
tailwind: {
|
|
11866
|
-
config: "",
|
|
11867
|
-
css: "src/styles/globals.css",
|
|
11868
|
-
baseColor: "neutral",
|
|
11869
|
-
cssVariables: true,
|
|
11870
|
-
prefix: ""
|
|
11871
|
-
},
|
|
11872
|
-
aliases: {
|
|
11873
|
-
components: "~/components",
|
|
11874
|
-
utils: "~/lib/utils",
|
|
11875
|
-
ui: "~/components/ui",
|
|
11876
|
-
lib: "~/lib",
|
|
11877
|
-
hooks: "~/hooks"
|
|
11878
|
-
},
|
|
11879
|
-
iconLibrary: "lucide"
|
|
11880
|
-
};
|
|
11881
|
-
const storybookMain = `import type { StorybookConfig } from '@storybook/react-vite';
|
|
11882
|
-
|
|
11883
|
-
const config: StorybookConfig = {
|
|
11884
|
-
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
|
11885
|
-
// Controls, actions, backgrounds, viewport and interactions are part of
|
|
11886
|
-
// \`storybook\` itself; docs and a11y are add-ons.
|
|
11887
|
-
addons: ['@storybook/addon-docs', '@storybook/addon-a11y'],
|
|
11888
|
-
framework: {
|
|
11889
|
-
name: '@storybook/react-vite',
|
|
11890
|
-
options: {},
|
|
11891
|
-
},
|
|
11892
|
-
// \`react-docgen\` parses components itself. The TypeScript-based docgen
|
|
11893
|
-
// calls into TypeScript's JavaScript API, which TypeScript 7 no longer ships.
|
|
11894
|
-
typescript: {
|
|
11895
|
-
reactDocgen: 'react-docgen',
|
|
11896
|
-
},
|
|
11897
|
-
viteFinal: async (config) => {
|
|
11898
|
-
// Add Tailwind CSS v4 plugin
|
|
11899
|
-
const tailwindcss = await import('@tailwindcss/vite');
|
|
11900
|
-
config.plugins = config.plugins || [];
|
|
11901
|
-
config.plugins.push(tailwindcss.default());
|
|
11902
|
-
return config;
|
|
11903
|
-
},
|
|
11904
|
-
};
|
|
11905
|
-
|
|
11906
|
-
export default config;
|
|
11907
|
-
`;
|
|
11908
|
-
const storybookPreview = `import type { Preview } from '@storybook/react-vite';
|
|
11909
|
-
import '../src/styles/globals.css';
|
|
11910
|
-
|
|
11911
|
-
const preview: Preview = {
|
|
11912
|
-
// The starting background is a global, not a parameter.
|
|
11913
|
-
initialGlobals: {
|
|
11914
|
-
backgrounds: { value: 'dark' },
|
|
11915
|
-
},
|
|
11916
|
-
parameters: {
|
|
11917
|
-
backgrounds: {
|
|
11918
|
-
options: {
|
|
11919
|
-
dark: { name: 'dark', value: '#171717' },
|
|
11920
|
-
surface: { name: 'surface', value: '#1c1c1c' },
|
|
11921
|
-
light: { name: 'light', value: '#fafafa' },
|
|
11922
|
-
},
|
|
11923
|
-
},
|
|
11924
|
-
controls: {
|
|
11925
|
-
matchers: {
|
|
11926
|
-
color: /(background|color)$/i,
|
|
11927
|
-
date: /Date$/i,
|
|
11928
|
-
},
|
|
11929
|
-
},
|
|
11930
|
-
layout: 'centered',
|
|
11931
|
-
},
|
|
12520
|
+
* shadcn/ui components, written into the web app that uses them.
|
|
12521
|
+
*
|
|
12522
|
+
* shadcn is copy-in by design: the components are the app's own source, to
|
|
12523
|
+
* edit and add to with `npx shadcn@latest add`. A shared workspace package of
|
|
12524
|
+
* them was one more package to build and version for code only the web app
|
|
12525
|
+
* imported.
|
|
12526
|
+
*/
|
|
12527
|
+
/** What the components below import. */
|
|
12528
|
+
const SHADCN_DEPENDENCIES = {
|
|
12529
|
+
"@radix-ui/react-dialog": DEPENDENCY_VERSIONS["@radix-ui/react-dialog"],
|
|
12530
|
+
"@radix-ui/react-label": DEPENDENCY_VERSIONS["@radix-ui/react-label"],
|
|
12531
|
+
"@radix-ui/react-separator": DEPENDENCY_VERSIONS["@radix-ui/react-separator"],
|
|
12532
|
+
"@radix-ui/react-slot": DEPENDENCY_VERSIONS["@radix-ui/react-slot"],
|
|
12533
|
+
"@radix-ui/react-tabs": DEPENDENCY_VERSIONS["@radix-ui/react-tabs"],
|
|
12534
|
+
"@radix-ui/react-tooltip": DEPENDENCY_VERSIONS["@radix-ui/react-tooltip"],
|
|
12535
|
+
"class-variance-authority": DEPENDENCY_VERSIONS["class-variance-authority"],
|
|
12536
|
+
clsx: DEPENDENCY_VERSIONS["clsx"],
|
|
12537
|
+
"lucide-react": DEPENDENCY_VERSIONS["lucide-react"],
|
|
12538
|
+
"tailwind-merge": DEPENDENCY_VERSIONS["tailwind-merge"]
|
|
11932
12539
|
};
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
12540
|
+
/**
|
|
12541
|
+
* Tailwind v4 with the shadcn theme tokens — the stylesheet the app imports
|
|
12542
|
+
* once, at its root.
|
|
12543
|
+
*/
|
|
12544
|
+
const SHADCN_GLOBALS_CSS = `@import "tailwindcss";
|
|
11937
12545
|
|
|
11938
12546
|
@theme {
|
|
11939
12547
|
--color-background: hsl(var(--background));
|
|
@@ -12017,14 +12625,56 @@ export default preview;
|
|
|
12017
12625
|
}
|
|
12018
12626
|
}
|
|
12019
12627
|
`;
|
|
12020
|
-
|
|
12628
|
+
/**
|
|
12629
|
+
* The components, `cn`, and the `components.json` that tells the shadcn CLI
|
|
12630
|
+
* where they live.
|
|
12631
|
+
*
|
|
12632
|
+
* @param appDir the web app, relative to the workspace root (`apps/web`)
|
|
12633
|
+
* @param css the stylesheet holding {@link SHADCN_GLOBALS_CSS}, relative to
|
|
12634
|
+
* the app
|
|
12635
|
+
* @param options.rsc whether the app renders React Server Components, so
|
|
12636
|
+
* `shadcn add` marks what it writes `'use client'`. The radix-based
|
|
12637
|
+
* components here carry the directive either way; outside RSC it is inert.
|
|
12638
|
+
*/
|
|
12639
|
+
function shadcnFiles(appDir, css, options) {
|
|
12640
|
+
const componentsJson = {
|
|
12641
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
12642
|
+
style: "new-york",
|
|
12643
|
+
rsc: options.rsc,
|
|
12644
|
+
tsx: true,
|
|
12645
|
+
tailwind: {
|
|
12646
|
+
config: "",
|
|
12647
|
+
css,
|
|
12648
|
+
baseColor: "neutral",
|
|
12649
|
+
cssVariables: true,
|
|
12650
|
+
prefix: ""
|
|
12651
|
+
},
|
|
12652
|
+
aliases: {
|
|
12653
|
+
components: "~/components",
|
|
12654
|
+
utils: "~/lib/utils",
|
|
12655
|
+
ui: "~/components/ui",
|
|
12656
|
+
lib: "~/lib",
|
|
12657
|
+
hooks: "~/hooks"
|
|
12658
|
+
},
|
|
12659
|
+
iconLibrary: "lucide"
|
|
12660
|
+
};
|
|
12661
|
+
return [
|
|
12662
|
+
{
|
|
12663
|
+
path: `${appDir}/components.json`,
|
|
12664
|
+
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
12665
|
+
},
|
|
12666
|
+
{
|
|
12667
|
+
path: `${appDir}/src/lib/utils.ts`,
|
|
12668
|
+
content: `import { type ClassValue, clsx } from 'clsx';
|
|
12021
12669
|
import { twMerge } from 'tailwind-merge';
|
|
12022
12670
|
|
|
12023
12671
|
export function cn(...inputs: ClassValue[]) {
|
|
12024
12672
|
return twMerge(clsx(inputs));
|
|
12025
12673
|
}
|
|
12026
|
-
|
|
12027
|
-
|
|
12674
|
+
`
|
|
12675
|
+
},
|
|
12676
|
+
...Object.entries({
|
|
12677
|
+
button: `import { Slot } from '@radix-ui/react-slot';
|
|
12028
12678
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
12029
12679
|
import * as React from 'react';
|
|
12030
12680
|
|
|
@@ -12081,73 +12731,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
12081
12731
|
Button.displayName = 'Button';
|
|
12082
12732
|
|
|
12083
12733
|
export { Button, buttonVariants };
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
import { Button } from '~/components/ui/button';
|
|
12087
|
-
|
|
12088
|
-
const meta: Meta<typeof Button> = {
|
|
12089
|
-
title: 'Components/Button',
|
|
12090
|
-
component: Button,
|
|
12091
|
-
tags: ['autodocs'],
|
|
12092
|
-
argTypes: {
|
|
12093
|
-
variant: {
|
|
12094
|
-
control: 'select',
|
|
12095
|
-
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
|
|
12096
|
-
},
|
|
12097
|
-
size: {
|
|
12098
|
-
control: 'select',
|
|
12099
|
-
options: ['default', 'sm', 'lg', 'icon'],
|
|
12100
|
-
},
|
|
12101
|
-
},
|
|
12102
|
-
};
|
|
12103
|
-
|
|
12104
|
-
export default meta;
|
|
12105
|
-
type Story = StoryObj<typeof Button>;
|
|
12106
|
-
|
|
12107
|
-
export const Default: Story = {
|
|
12108
|
-
args: {
|
|
12109
|
-
children: 'Button',
|
|
12110
|
-
variant: 'default',
|
|
12111
|
-
size: 'default',
|
|
12112
|
-
},
|
|
12113
|
-
};
|
|
12114
|
-
|
|
12115
|
-
export const Secondary: Story = {
|
|
12116
|
-
args: {
|
|
12117
|
-
children: 'Secondary',
|
|
12118
|
-
variant: 'secondary',
|
|
12119
|
-
},
|
|
12120
|
-
};
|
|
12121
|
-
|
|
12122
|
-
export const Destructive: Story = {
|
|
12123
|
-
args: {
|
|
12124
|
-
children: 'Destructive',
|
|
12125
|
-
variant: 'destructive',
|
|
12126
|
-
},
|
|
12127
|
-
};
|
|
12128
|
-
|
|
12129
|
-
export const Outline: Story = {
|
|
12130
|
-
args: {
|
|
12131
|
-
children: 'Outline',
|
|
12132
|
-
variant: 'outline',
|
|
12133
|
-
},
|
|
12134
|
-
};
|
|
12135
|
-
|
|
12136
|
-
export const Ghost: Story = {
|
|
12137
|
-
args: {
|
|
12138
|
-
children: 'Ghost',
|
|
12139
|
-
variant: 'ghost',
|
|
12140
|
-
},
|
|
12141
|
-
};
|
|
12142
|
-
|
|
12143
|
-
export const Link: Story = {
|
|
12144
|
-
args: {
|
|
12145
|
-
children: 'Link',
|
|
12146
|
-
variant: 'link',
|
|
12147
|
-
},
|
|
12148
|
-
};
|
|
12149
|
-
`;
|
|
12150
|
-
const inputTsx = `import * as React from 'react';
|
|
12734
|
+
`,
|
|
12735
|
+
input: `import * as React from 'react';
|
|
12151
12736
|
|
|
12152
12737
|
import { cn } from '~/lib/utils';
|
|
12153
12738
|
|
|
@@ -12169,8 +12754,8 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
|
|
12169
12754
|
Input.displayName = 'Input';
|
|
12170
12755
|
|
|
12171
12756
|
export { Input };
|
|
12172
|
-
|
|
12173
|
-
|
|
12757
|
+
`,
|
|
12758
|
+
card: `import * as React from 'react';
|
|
12174
12759
|
|
|
12175
12760
|
import { cn } from '~/lib/utils';
|
|
12176
12761
|
|
|
@@ -12246,124 +12831,10 @@ const CardFooter = React.forwardRef<
|
|
|
12246
12831
|
CardFooter.displayName = 'CardFooter';
|
|
12247
12832
|
|
|
12248
12833
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
import { Input } from '~/components/ui/input';
|
|
12252
|
-
|
|
12253
|
-
const meta: Meta<typeof Input> = {
|
|
12254
|
-
title: 'Components/Input',
|
|
12255
|
-
component: Input,
|
|
12256
|
-
tags: ['autodocs'],
|
|
12257
|
-
argTypes: {
|
|
12258
|
-
type: {
|
|
12259
|
-
control: 'select',
|
|
12260
|
-
options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'],
|
|
12261
|
-
},
|
|
12262
|
-
disabled: {
|
|
12263
|
-
control: 'boolean',
|
|
12264
|
-
},
|
|
12265
|
-
},
|
|
12266
|
-
};
|
|
12267
|
-
|
|
12268
|
-
export default meta;
|
|
12269
|
-
type Story = StoryObj<typeof Input>;
|
|
12270
|
-
|
|
12271
|
-
export const Default: Story = {
|
|
12272
|
-
args: {
|
|
12273
|
-
placeholder: 'Enter text...',
|
|
12274
|
-
},
|
|
12275
|
-
};
|
|
12276
|
-
|
|
12277
|
-
export const Email: Story = {
|
|
12278
|
-
args: {
|
|
12279
|
-
type: 'email',
|
|
12280
|
-
placeholder: 'Enter email...',
|
|
12281
|
-
},
|
|
12282
|
-
};
|
|
12283
|
-
|
|
12284
|
-
export const Password: Story = {
|
|
12285
|
-
args: {
|
|
12286
|
-
type: 'password',
|
|
12287
|
-
placeholder: 'Enter password...',
|
|
12288
|
-
},
|
|
12289
|
-
};
|
|
12290
|
-
|
|
12291
|
-
export const Disabled: Story = {
|
|
12292
|
-
args: {
|
|
12293
|
-
placeholder: 'Disabled input',
|
|
12294
|
-
disabled: true,
|
|
12295
|
-
},
|
|
12296
|
-
};
|
|
12297
|
-
|
|
12298
|
-
export const WithValue: Story = {
|
|
12299
|
-
args: {
|
|
12300
|
-
defaultValue: 'Hello World',
|
|
12301
|
-
},
|
|
12302
|
-
};
|
|
12303
|
-
`;
|
|
12304
|
-
const cardStories = `import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
12305
|
-
import { Button } from '~/components/ui/button';
|
|
12306
|
-
import {
|
|
12307
|
-
Card,
|
|
12308
|
-
CardContent,
|
|
12309
|
-
CardDescription,
|
|
12310
|
-
CardFooter,
|
|
12311
|
-
CardHeader,
|
|
12312
|
-
CardTitle,
|
|
12313
|
-
} from '~/components/ui/card';
|
|
12314
|
-
import { Input } from '~/components/ui/input';
|
|
12315
|
-
|
|
12316
|
-
const meta: Meta<typeof Card> = {
|
|
12317
|
-
title: 'Components/Card',
|
|
12318
|
-
component: Card,
|
|
12319
|
-
tags: ['autodocs'],
|
|
12320
|
-
};
|
|
12321
|
-
|
|
12322
|
-
export default meta;
|
|
12323
|
-
type Story = StoryObj<typeof Card>;
|
|
12324
|
-
|
|
12325
|
-
export const Default: Story = {
|
|
12326
|
-
render: () => (
|
|
12327
|
-
<Card className="w-[350px]">
|
|
12328
|
-
<CardHeader>
|
|
12329
|
-
<CardTitle>Card Title</CardTitle>
|
|
12330
|
-
<CardDescription>Card description goes here.</CardDescription>
|
|
12331
|
-
</CardHeader>
|
|
12332
|
-
<CardContent>
|
|
12333
|
-
<p>Card content goes here.</p>
|
|
12334
|
-
</CardContent>
|
|
12335
|
-
</Card>
|
|
12336
|
-
),
|
|
12337
|
-
};
|
|
12338
|
-
|
|
12339
|
-
export const WithFooter: Story = {
|
|
12340
|
-
render: () => (
|
|
12341
|
-
<Card className="w-[350px]">
|
|
12342
|
-
<CardHeader>
|
|
12343
|
-
<CardTitle>Create Account</CardTitle>
|
|
12344
|
-
<CardDescription>Enter your details below.</CardDescription>
|
|
12345
|
-
</CardHeader>
|
|
12346
|
-
<CardContent className="space-y-4">
|
|
12347
|
-
<Input placeholder="Email" type="email" />
|
|
12348
|
-
<Input placeholder="Password" type="password" />
|
|
12349
|
-
</CardContent>
|
|
12350
|
-
<CardFooter className="flex justify-between">
|
|
12351
|
-
<Button variant="outline">Cancel</Button>
|
|
12352
|
-
<Button>Create</Button>
|
|
12353
|
-
</CardFooter>
|
|
12354
|
-
</Card>
|
|
12355
|
-
),
|
|
12356
|
-
};
|
|
12834
|
+
`,
|
|
12835
|
+
label: `'use client';
|
|
12357
12836
|
|
|
12358
|
-
|
|
12359
|
-
render: () => (
|
|
12360
|
-
<Card className="w-[350px] p-6">
|
|
12361
|
-
<p>Simple card with just content.</p>
|
|
12362
|
-
</Card>
|
|
12363
|
-
),
|
|
12364
|
-
};
|
|
12365
|
-
`;
|
|
12366
|
-
const labelTsx = `import * as LabelPrimitive from '@radix-ui/react-label';
|
|
12837
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
12367
12838
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
12368
12839
|
import * as React from 'react';
|
|
12369
12840
|
|
|
@@ -12387,47 +12858,8 @@ const Label = React.forwardRef<
|
|
|
12387
12858
|
Label.displayName = LabelPrimitive.Root.displayName;
|
|
12388
12859
|
|
|
12389
12860
|
export { Label };
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
import { Input } from '~/components/ui/input';
|
|
12393
|
-
import { Label } from '~/components/ui/label';
|
|
12394
|
-
|
|
12395
|
-
const meta: Meta<typeof Label> = {
|
|
12396
|
-
title: 'Components/Label',
|
|
12397
|
-
component: Label,
|
|
12398
|
-
tags: ['autodocs'],
|
|
12399
|
-
};
|
|
12400
|
-
|
|
12401
|
-
export default meta;
|
|
12402
|
-
type Story = StoryObj<typeof Label>;
|
|
12403
|
-
|
|
12404
|
-
export const Default: Story = {
|
|
12405
|
-
args: {
|
|
12406
|
-
children: 'Label',
|
|
12407
|
-
},
|
|
12408
|
-
};
|
|
12409
|
-
|
|
12410
|
-
export const WithInput: Story = {
|
|
12411
|
-
render: () => (
|
|
12412
|
-
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
12413
|
-
<Label htmlFor="email">Email</Label>
|
|
12414
|
-
<Input type="email" id="email" placeholder="Email" />
|
|
12415
|
-
</div>
|
|
12416
|
-
),
|
|
12417
|
-
};
|
|
12418
|
-
|
|
12419
|
-
export const Disabled: Story = {
|
|
12420
|
-
render: () => (
|
|
12421
|
-
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
12422
|
-
<Label htmlFor="disabled" className="peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
12423
|
-
Disabled
|
|
12424
|
-
</Label>
|
|
12425
|
-
<Input type="text" id="disabled" placeholder="Disabled" disabled className="peer" />
|
|
12426
|
-
</div>
|
|
12427
|
-
),
|
|
12428
|
-
};
|
|
12429
|
-
`;
|
|
12430
|
-
const badgeTsx = `import { cva, type VariantProps } from 'class-variance-authority';
|
|
12861
|
+
`,
|
|
12862
|
+
badge: `import { cva, type VariantProps } from 'class-variance-authority';
|
|
12431
12863
|
import * as React from 'react';
|
|
12432
12864
|
|
|
12433
12865
|
import { cn } from '~/lib/utils';
|
|
@@ -12463,54 +12895,10 @@ function Badge({ className, variant, ...props }: BadgeProps) {
|
|
|
12463
12895
|
}
|
|
12464
12896
|
|
|
12465
12897
|
export { Badge, badgeVariants };
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
import { Badge } from '~/components/ui/badge';
|
|
12469
|
-
|
|
12470
|
-
const meta: Meta<typeof Badge> = {
|
|
12471
|
-
title: 'Components/Badge',
|
|
12472
|
-
component: Badge,
|
|
12473
|
-
tags: ['autodocs'],
|
|
12474
|
-
argTypes: {
|
|
12475
|
-
variant: {
|
|
12476
|
-
control: 'select',
|
|
12477
|
-
options: ['default', 'secondary', 'destructive', 'outline'],
|
|
12478
|
-
},
|
|
12479
|
-
},
|
|
12480
|
-
};
|
|
12481
|
-
|
|
12482
|
-
export default meta;
|
|
12483
|
-
type Story = StoryObj<typeof Badge>;
|
|
12484
|
-
|
|
12485
|
-
export const Default: Story = {
|
|
12486
|
-
args: {
|
|
12487
|
-
children: 'Badge',
|
|
12488
|
-
variant: 'default',
|
|
12489
|
-
},
|
|
12490
|
-
};
|
|
12491
|
-
|
|
12492
|
-
export const Secondary: Story = {
|
|
12493
|
-
args: {
|
|
12494
|
-
children: 'Secondary',
|
|
12495
|
-
variant: 'secondary',
|
|
12496
|
-
},
|
|
12497
|
-
};
|
|
12498
|
-
|
|
12499
|
-
export const Destructive: Story = {
|
|
12500
|
-
args: {
|
|
12501
|
-
children: 'Destructive',
|
|
12502
|
-
variant: 'destructive',
|
|
12503
|
-
},
|
|
12504
|
-
};
|
|
12898
|
+
`,
|
|
12899
|
+
separator: `'use client';
|
|
12505
12900
|
|
|
12506
|
-
|
|
12507
|
-
args: {
|
|
12508
|
-
children: 'Outline',
|
|
12509
|
-
variant: 'outline',
|
|
12510
|
-
},
|
|
12511
|
-
};
|
|
12512
|
-
`;
|
|
12513
|
-
const separatorTsx = `import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
12901
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
12514
12902
|
import * as React from 'react';
|
|
12515
12903
|
|
|
12516
12904
|
import { cn } from '~/lib/utils';
|
|
@@ -12539,59 +12927,10 @@ const Separator = React.forwardRef<
|
|
|
12539
12927
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
12540
12928
|
|
|
12541
12929
|
export { Separator };
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
import { Separator } from '~/components/ui/separator';
|
|
12545
|
-
|
|
12546
|
-
const meta: Meta<typeof Separator> = {
|
|
12547
|
-
title: 'Components/Separator',
|
|
12548
|
-
component: Separator,
|
|
12549
|
-
tags: ['autodocs'],
|
|
12550
|
-
argTypes: {
|
|
12551
|
-
orientation: {
|
|
12552
|
-
control: 'select',
|
|
12553
|
-
options: ['horizontal', 'vertical'],
|
|
12554
|
-
},
|
|
12555
|
-
},
|
|
12556
|
-
};
|
|
12557
|
-
|
|
12558
|
-
export default meta;
|
|
12559
|
-
type Story = StoryObj<typeof Separator>;
|
|
12560
|
-
|
|
12561
|
-
export const Horizontal: Story = {
|
|
12562
|
-
render: () => (
|
|
12563
|
-
<div className="w-[300px]">
|
|
12564
|
-
<div className="space-y-1">
|
|
12565
|
-
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
|
|
12566
|
-
<p className="text-sm text-muted-foreground">
|
|
12567
|
-
An open-source UI component library.
|
|
12568
|
-
</p>
|
|
12569
|
-
</div>
|
|
12570
|
-
<Separator className="my-4" />
|
|
12571
|
-
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
12572
|
-
<div>Blog</div>
|
|
12573
|
-
<Separator orientation="vertical" />
|
|
12574
|
-
<div>Docs</div>
|
|
12575
|
-
<Separator orientation="vertical" />
|
|
12576
|
-
<div>Source</div>
|
|
12577
|
-
</div>
|
|
12578
|
-
</div>
|
|
12579
|
-
),
|
|
12580
|
-
};
|
|
12930
|
+
`,
|
|
12931
|
+
tabs: `'use client';
|
|
12581
12932
|
|
|
12582
|
-
|
|
12583
|
-
render: () => (
|
|
12584
|
-
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
12585
|
-
<div>Blog</div>
|
|
12586
|
-
<Separator orientation="vertical" />
|
|
12587
|
-
<div>Docs</div>
|
|
12588
|
-
<Separator orientation="vertical" />
|
|
12589
|
-
<div>Source</div>
|
|
12590
|
-
</div>
|
|
12591
|
-
),
|
|
12592
|
-
};
|
|
12593
|
-
`;
|
|
12594
|
-
const tabsTsx = `import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
12933
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
12595
12934
|
import * as React from 'react';
|
|
12596
12935
|
|
|
12597
12936
|
import { cn } from '~/lib/utils';
|
|
@@ -12644,81 +12983,10 @@ const TabsContent = React.forwardRef<
|
|
|
12644
12983
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
12645
12984
|
|
|
12646
12985
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs';
|
|
12650
|
-
import { Button } from '~/components/ui/button';
|
|
12651
|
-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card';
|
|
12652
|
-
import { Input } from '~/components/ui/input';
|
|
12653
|
-
import { Label } from '~/components/ui/label';
|
|
12654
|
-
|
|
12655
|
-
const meta: Meta<typeof Tabs> = {
|
|
12656
|
-
title: 'Components/Tabs',
|
|
12657
|
-
component: Tabs,
|
|
12658
|
-
tags: ['autodocs'],
|
|
12659
|
-
};
|
|
12986
|
+
`,
|
|
12987
|
+
tooltip: `'use client';
|
|
12660
12988
|
|
|
12661
|
-
|
|
12662
|
-
type Story = StoryObj<typeof Tabs>;
|
|
12663
|
-
|
|
12664
|
-
export const Default: Story = {
|
|
12665
|
-
render: () => (
|
|
12666
|
-
<Tabs defaultValue="account" className="w-[400px]">
|
|
12667
|
-
<TabsList>
|
|
12668
|
-
<TabsTrigger value="account">Account</TabsTrigger>
|
|
12669
|
-
<TabsTrigger value="password">Password</TabsTrigger>
|
|
12670
|
-
</TabsList>
|
|
12671
|
-
<TabsContent value="account">
|
|
12672
|
-
<Card>
|
|
12673
|
-
<CardHeader>
|
|
12674
|
-
<CardTitle>Account</CardTitle>
|
|
12675
|
-
<CardDescription>
|
|
12676
|
-
Make changes to your account here. Click save when you're done.
|
|
12677
|
-
</CardDescription>
|
|
12678
|
-
</CardHeader>
|
|
12679
|
-
<CardContent className="space-y-2">
|
|
12680
|
-
<div className="space-y-1">
|
|
12681
|
-
<Label htmlFor="name">Name</Label>
|
|
12682
|
-
<Input id="name" defaultValue="Pedro Duarte" />
|
|
12683
|
-
</div>
|
|
12684
|
-
<div className="space-y-1">
|
|
12685
|
-
<Label htmlFor="username">Username</Label>
|
|
12686
|
-
<Input id="username" defaultValue="@peduarte" />
|
|
12687
|
-
</div>
|
|
12688
|
-
</CardContent>
|
|
12689
|
-
<CardFooter>
|
|
12690
|
-
<Button>Save changes</Button>
|
|
12691
|
-
</CardFooter>
|
|
12692
|
-
</Card>
|
|
12693
|
-
</TabsContent>
|
|
12694
|
-
<TabsContent value="password">
|
|
12695
|
-
<Card>
|
|
12696
|
-
<CardHeader>
|
|
12697
|
-
<CardTitle>Password</CardTitle>
|
|
12698
|
-
<CardDescription>
|
|
12699
|
-
Change your password here. After saving, you'll be logged out.
|
|
12700
|
-
</CardDescription>
|
|
12701
|
-
</CardHeader>
|
|
12702
|
-
<CardContent className="space-y-2">
|
|
12703
|
-
<div className="space-y-1">
|
|
12704
|
-
<Label htmlFor="current">Current password</Label>
|
|
12705
|
-
<Input id="current" type="password" />
|
|
12706
|
-
</div>
|
|
12707
|
-
<div className="space-y-1">
|
|
12708
|
-
<Label htmlFor="new">New password</Label>
|
|
12709
|
-
<Input id="new" type="password" />
|
|
12710
|
-
</div>
|
|
12711
|
-
</CardContent>
|
|
12712
|
-
<CardFooter>
|
|
12713
|
-
<Button>Save password</Button>
|
|
12714
|
-
</CardFooter>
|
|
12715
|
-
</Card>
|
|
12716
|
-
</TabsContent>
|
|
12717
|
-
</Tabs>
|
|
12718
|
-
),
|
|
12719
|
-
};
|
|
12720
|
-
`;
|
|
12721
|
-
const tooltipTsx = `import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
12989
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
12722
12990
|
import * as React from 'react';
|
|
12723
12991
|
|
|
12724
12992
|
import { cn } from '~/lib/utils';
|
|
@@ -12748,80 +13016,10 @@ const TooltipContent = React.forwardRef<
|
|
|
12748
13016
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
12749
13017
|
|
|
12750
13018
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';
|
|
12754
|
-
import { Button } from '~/components/ui/button';
|
|
12755
|
-
|
|
12756
|
-
const meta: Meta<typeof Tooltip> = {
|
|
12757
|
-
title: 'Components/Tooltip',
|
|
12758
|
-
component: Tooltip,
|
|
12759
|
-
tags: ['autodocs'],
|
|
12760
|
-
decorators: [
|
|
12761
|
-
(Story) => (
|
|
12762
|
-
<TooltipProvider>
|
|
12763
|
-
<Story />
|
|
12764
|
-
</TooltipProvider>
|
|
12765
|
-
),
|
|
12766
|
-
],
|
|
12767
|
-
};
|
|
12768
|
-
|
|
12769
|
-
export default meta;
|
|
12770
|
-
type Story = StoryObj<typeof Tooltip>;
|
|
12771
|
-
|
|
12772
|
-
export const Default: Story = {
|
|
12773
|
-
render: () => (
|
|
12774
|
-
<Tooltip>
|
|
12775
|
-
<TooltipTrigger asChild>
|
|
12776
|
-
<Button variant="outline">Hover me</Button>
|
|
12777
|
-
</TooltipTrigger>
|
|
12778
|
-
<TooltipContent>
|
|
12779
|
-
<p>Add to library</p>
|
|
12780
|
-
</TooltipContent>
|
|
12781
|
-
</Tooltip>
|
|
12782
|
-
),
|
|
12783
|
-
};
|
|
13019
|
+
`,
|
|
13020
|
+
dialog: `'use client';
|
|
12784
13021
|
|
|
12785
|
-
|
|
12786
|
-
render: () => (
|
|
12787
|
-
<div className="flex gap-4">
|
|
12788
|
-
<Tooltip>
|
|
12789
|
-
<TooltipTrigger asChild>
|
|
12790
|
-
<Button variant="outline">Top</Button>
|
|
12791
|
-
</TooltipTrigger>
|
|
12792
|
-
<TooltipContent side="top">
|
|
12793
|
-
<p>Top tooltip</p>
|
|
12794
|
-
</TooltipContent>
|
|
12795
|
-
</Tooltip>
|
|
12796
|
-
<Tooltip>
|
|
12797
|
-
<TooltipTrigger asChild>
|
|
12798
|
-
<Button variant="outline">Bottom</Button>
|
|
12799
|
-
</TooltipTrigger>
|
|
12800
|
-
<TooltipContent side="bottom">
|
|
12801
|
-
<p>Bottom tooltip</p>
|
|
12802
|
-
</TooltipContent>
|
|
12803
|
-
</Tooltip>
|
|
12804
|
-
<Tooltip>
|
|
12805
|
-
<TooltipTrigger asChild>
|
|
12806
|
-
<Button variant="outline">Left</Button>
|
|
12807
|
-
</TooltipTrigger>
|
|
12808
|
-
<TooltipContent side="left">
|
|
12809
|
-
<p>Left tooltip</p>
|
|
12810
|
-
</TooltipContent>
|
|
12811
|
-
</Tooltip>
|
|
12812
|
-
<Tooltip>
|
|
12813
|
-
<TooltipTrigger asChild>
|
|
12814
|
-
<Button variant="outline">Right</Button>
|
|
12815
|
-
</TooltipTrigger>
|
|
12816
|
-
<TooltipContent side="right">
|
|
12817
|
-
<p>Right tooltip</p>
|
|
12818
|
-
</TooltipContent>
|
|
12819
|
-
</Tooltip>
|
|
12820
|
-
</div>
|
|
12821
|
-
),
|
|
12822
|
-
};
|
|
12823
|
-
`;
|
|
12824
|
-
const dialogTsx = `import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
13022
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
12825
13023
|
import { X } from 'lucide-react';
|
|
12826
13024
|
import * as React from 'react';
|
|
12827
13025
|
|
|
@@ -12941,255 +13139,11 @@ export {
|
|
|
12941
13139
|
DialogTitle,
|
|
12942
13140
|
DialogDescription,
|
|
12943
13141
|
};
|
|
12944
|
-
`;
|
|
12945
|
-
const dialogStories = `import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
12946
|
-
import {
|
|
12947
|
-
Dialog,
|
|
12948
|
-
DialogContent,
|
|
12949
|
-
DialogDescription,
|
|
12950
|
-
DialogFooter,
|
|
12951
|
-
DialogHeader,
|
|
12952
|
-
DialogTitle,
|
|
12953
|
-
DialogTrigger,
|
|
12954
|
-
} from '~/components/ui/dialog';
|
|
12955
|
-
import { Button } from '~/components/ui/button';
|
|
12956
|
-
import { Input } from '~/components/ui/input';
|
|
12957
|
-
import { Label } from '~/components/ui/label';
|
|
12958
|
-
|
|
12959
|
-
const meta: Meta<typeof Dialog> = {
|
|
12960
|
-
title: 'Components/Dialog',
|
|
12961
|
-
component: Dialog,
|
|
12962
|
-
tags: ['autodocs'],
|
|
12963
|
-
};
|
|
12964
|
-
|
|
12965
|
-
export default meta;
|
|
12966
|
-
type Story = StoryObj<typeof Dialog>;
|
|
12967
|
-
|
|
12968
|
-
export const Default: Story = {
|
|
12969
|
-
render: () => (
|
|
12970
|
-
<Dialog>
|
|
12971
|
-
<DialogTrigger asChild>
|
|
12972
|
-
<Button variant="outline">Edit Profile</Button>
|
|
12973
|
-
</DialogTrigger>
|
|
12974
|
-
<DialogContent className="sm:max-w-[425px]">
|
|
12975
|
-
<DialogHeader>
|
|
12976
|
-
<DialogTitle>Edit profile</DialogTitle>
|
|
12977
|
-
<DialogDescription>
|
|
12978
|
-
Make changes to your profile here. Click save when you're done.
|
|
12979
|
-
</DialogDescription>
|
|
12980
|
-
</DialogHeader>
|
|
12981
|
-
<div className="grid gap-4 py-4">
|
|
12982
|
-
<div className="grid grid-cols-4 items-center gap-4">
|
|
12983
|
-
<Label htmlFor="name" className="text-right">
|
|
12984
|
-
Name
|
|
12985
|
-
</Label>
|
|
12986
|
-
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
|
12987
|
-
</div>
|
|
12988
|
-
<div className="grid grid-cols-4 items-center gap-4">
|
|
12989
|
-
<Label htmlFor="username" className="text-right">
|
|
12990
|
-
Username
|
|
12991
|
-
</Label>
|
|
12992
|
-
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
|
|
12993
|
-
</div>
|
|
12994
|
-
</div>
|
|
12995
|
-
<DialogFooter>
|
|
12996
|
-
<Button type="submit">Save changes</Button>
|
|
12997
|
-
</DialogFooter>
|
|
12998
|
-
</DialogContent>
|
|
12999
|
-
</Dialog>
|
|
13000
|
-
),
|
|
13001
|
-
};
|
|
13002
|
-
|
|
13003
|
-
export const Alert: Story = {
|
|
13004
|
-
render: () => (
|
|
13005
|
-
<Dialog>
|
|
13006
|
-
<DialogTrigger asChild>
|
|
13007
|
-
<Button variant="destructive">Delete Account</Button>
|
|
13008
|
-
</DialogTrigger>
|
|
13009
|
-
<DialogContent>
|
|
13010
|
-
<DialogHeader>
|
|
13011
|
-
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
13012
|
-
<DialogDescription>
|
|
13013
|
-
This action cannot be undone. This will permanently delete your
|
|
13014
|
-
account and remove your data from our servers.
|
|
13015
|
-
</DialogDescription>
|
|
13016
|
-
</DialogHeader>
|
|
13017
|
-
<DialogFooter>
|
|
13018
|
-
<Button variant="outline">Cancel</Button>
|
|
13019
|
-
<Button variant="destructive">Delete</Button>
|
|
13020
|
-
</DialogFooter>
|
|
13021
|
-
</DialogContent>
|
|
13022
|
-
</Dialog>
|
|
13023
|
-
),
|
|
13024
|
-
};
|
|
13025
|
-
`;
|
|
13026
|
-
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button/index.tsx';
|
|
13027
|
-
export { Input } from './input/index.tsx';
|
|
13028
|
-
export {
|
|
13029
|
-
Card,
|
|
13030
|
-
CardHeader,
|
|
13031
|
-
CardFooter,
|
|
13032
|
-
CardTitle,
|
|
13033
|
-
CardDescription,
|
|
13034
|
-
CardContent,
|
|
13035
|
-
} from './card/index.tsx';
|
|
13036
|
-
export { Label } from './label/index.tsx';
|
|
13037
|
-
export { Badge, type BadgeProps, badgeVariants } from './badge/index.tsx';
|
|
13038
|
-
export { Separator } from './separator/index.tsx';
|
|
13039
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs/index.tsx';
|
|
13040
|
-
export {
|
|
13041
|
-
Tooltip,
|
|
13042
|
-
TooltipTrigger,
|
|
13043
|
-
TooltipContent,
|
|
13044
|
-
TooltipProvider,
|
|
13045
|
-
} from './tooltip/index.tsx';
|
|
13046
|
-
export {
|
|
13047
|
-
Dialog,
|
|
13048
|
-
DialogPortal,
|
|
13049
|
-
DialogOverlay,
|
|
13050
|
-
DialogTrigger,
|
|
13051
|
-
DialogClose,
|
|
13052
|
-
DialogContent,
|
|
13053
|
-
DialogHeader,
|
|
13054
|
-
DialogFooter,
|
|
13055
|
-
DialogTitle,
|
|
13056
|
-
DialogDescription,
|
|
13057
|
-
} from './dialog/index.tsx';
|
|
13058
|
-
`;
|
|
13059
|
-
const buttonIndexTsx = buttonTsx;
|
|
13060
|
-
const inputIndexTsx = inputTsx;
|
|
13061
|
-
const cardIndexTsx = cardTsx;
|
|
13062
|
-
const componentsIndex = `export * from './ui/index.ts';
|
|
13063
|
-
`;
|
|
13064
|
-
const indexTs = `// @${options.name}/ui - Shared UI component library
|
|
13065
|
-
|
|
13066
|
-
// shadcn/ui components
|
|
13067
|
-
export * from './components/index.ts';
|
|
13068
|
-
|
|
13069
|
-
// Utilities
|
|
13070
|
-
export { cn } from './lib/utils.ts';
|
|
13071
|
-
`;
|
|
13072
|
-
return [
|
|
13073
|
-
{
|
|
13074
|
-
path: "packages/ui/package.json",
|
|
13075
|
-
content: `${JSON.stringify(packageJson, null, 2)}\n`
|
|
13076
|
-
},
|
|
13077
|
-
{
|
|
13078
|
-
path: "packages/ui/tsconfig.json",
|
|
13079
|
-
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
13080
|
-
},
|
|
13081
|
-
{
|
|
13082
|
-
path: "packages/ui/components.json",
|
|
13083
|
-
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
13084
|
-
},
|
|
13085
|
-
{
|
|
13086
|
-
path: "packages/ui/.storybook/main.ts",
|
|
13087
|
-
content: storybookMain
|
|
13088
|
-
},
|
|
13089
|
-
{
|
|
13090
|
-
path: "packages/ui/.storybook/preview.ts",
|
|
13091
|
-
content: storybookPreview
|
|
13092
|
-
},
|
|
13093
|
-
{
|
|
13094
|
-
path: "packages/ui/src/styles/globals.css",
|
|
13095
|
-
content: globalsCss
|
|
13096
|
-
},
|
|
13097
|
-
{
|
|
13098
|
-
path: "packages/ui/src/lib/utils.ts",
|
|
13099
|
-
content: utilsTs
|
|
13100
|
-
},
|
|
13101
|
-
{
|
|
13102
|
-
path: "packages/ui/src/components/ui/button/index.tsx",
|
|
13103
|
-
content: buttonIndexTsx
|
|
13104
|
-
},
|
|
13105
|
-
{
|
|
13106
|
-
path: "packages/ui/src/components/ui/button/button.stories.tsx",
|
|
13107
|
-
content: buttonStories
|
|
13108
|
-
},
|
|
13109
|
-
{
|
|
13110
|
-
path: "packages/ui/src/components/ui/input/index.tsx",
|
|
13111
|
-
content: inputIndexTsx
|
|
13112
|
-
},
|
|
13113
|
-
{
|
|
13114
|
-
path: "packages/ui/src/components/ui/input/input.stories.tsx",
|
|
13115
|
-
content: inputStories
|
|
13116
|
-
},
|
|
13117
|
-
{
|
|
13118
|
-
path: "packages/ui/src/components/ui/card/index.tsx",
|
|
13119
|
-
content: cardIndexTsx
|
|
13120
|
-
},
|
|
13121
|
-
{
|
|
13122
|
-
path: "packages/ui/src/components/ui/card/card.stories.tsx",
|
|
13123
|
-
content: cardStories
|
|
13124
|
-
},
|
|
13125
|
-
{
|
|
13126
|
-
path: "packages/ui/src/components/ui/label/index.tsx",
|
|
13127
|
-
content: labelTsx
|
|
13128
|
-
},
|
|
13129
|
-
{
|
|
13130
|
-
path: "packages/ui/src/components/ui/label/label.stories.tsx",
|
|
13131
|
-
content: labelStories
|
|
13132
|
-
},
|
|
13133
|
-
{
|
|
13134
|
-
path: "packages/ui/src/components/ui/badge/index.tsx",
|
|
13135
|
-
content: badgeTsx
|
|
13136
|
-
},
|
|
13137
|
-
{
|
|
13138
|
-
path: "packages/ui/src/components/ui/badge/badge.stories.tsx",
|
|
13139
|
-
content: badgeStories
|
|
13140
|
-
},
|
|
13141
|
-
{
|
|
13142
|
-
path: "packages/ui/src/components/ui/separator/index.tsx",
|
|
13143
|
-
content: separatorTsx
|
|
13144
|
-
},
|
|
13145
|
-
{
|
|
13146
|
-
path: "packages/ui/src/components/ui/separator/separator.stories.tsx",
|
|
13147
|
-
content: separatorStories
|
|
13148
|
-
},
|
|
13149
|
-
{
|
|
13150
|
-
path: "packages/ui/src/components/ui/tabs/index.tsx",
|
|
13151
|
-
content: tabsTsx
|
|
13152
|
-
},
|
|
13153
|
-
{
|
|
13154
|
-
path: "packages/ui/src/components/ui/tabs/tabs.stories.tsx",
|
|
13155
|
-
content: tabsStories
|
|
13156
|
-
},
|
|
13157
|
-
{
|
|
13158
|
-
path: "packages/ui/src/components/ui/tooltip/index.tsx",
|
|
13159
|
-
content: tooltipTsx
|
|
13160
|
-
},
|
|
13161
|
-
{
|
|
13162
|
-
path: "packages/ui/src/components/ui/tooltip/tooltip.stories.tsx",
|
|
13163
|
-
content: tooltipStories
|
|
13164
|
-
},
|
|
13165
|
-
{
|
|
13166
|
-
path: "packages/ui/src/components/ui/dialog/index.tsx",
|
|
13167
|
-
content: dialogTsx
|
|
13168
|
-
},
|
|
13169
|
-
{
|
|
13170
|
-
path: "packages/ui/src/components/ui/dialog/dialog.stories.tsx",
|
|
13171
|
-
content: dialogStories
|
|
13172
|
-
},
|
|
13173
|
-
{
|
|
13174
|
-
path: "packages/ui/src/components/ui/index.ts",
|
|
13175
|
-
content: componentsUiIndex
|
|
13176
|
-
},
|
|
13177
|
-
{
|
|
13178
|
-
path: "packages/ui/src/components/index.ts",
|
|
13179
|
-
content: componentsIndex
|
|
13180
|
-
},
|
|
13181
|
-
{
|
|
13182
|
-
path: "packages/ui/src/index.ts",
|
|
13183
|
-
content: indexTs
|
|
13184
|
-
},
|
|
13185
|
-
{
|
|
13186
|
-
path: "packages/ui/.gitignore",
|
|
13187
|
-
content: `node_modules/
|
|
13188
|
-
dist/
|
|
13189
|
-
storybook-static/
|
|
13190
|
-
*.log
|
|
13191
13142
|
`
|
|
13192
|
-
}
|
|
13143
|
+
}).map(([name, content]) => ({
|
|
13144
|
+
path: `${appDir}/src/components/ui/${name}.tsx`,
|
|
13145
|
+
content
|
|
13146
|
+
}))
|
|
13193
13147
|
];
|
|
13194
13148
|
}
|
|
13195
13149
|
//#endregion
|
|
@@ -13201,7 +13155,6 @@ function generateWebAppFiles(options) {
|
|
|
13201
13155
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
13202
13156
|
const packageName = `@${options.name}/web`;
|
|
13203
13157
|
const modelsPackage = `@${options.name}/models`;
|
|
13204
|
-
const uiPackage = `@${options.name}/ui`;
|
|
13205
13158
|
const packageJson = {
|
|
13206
13159
|
name: packageName,
|
|
13207
13160
|
version: "0.0.1",
|
|
@@ -13215,7 +13168,7 @@ function generateWebAppFiles(options) {
|
|
|
13215
13168
|
},
|
|
13216
13169
|
dependencies: {
|
|
13217
13170
|
[modelsPackage]: "workspace:*",
|
|
13218
|
-
|
|
13171
|
+
...SHADCN_DEPENDENCIES,
|
|
13219
13172
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
13220
13173
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
13221
13174
|
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
@@ -13240,7 +13193,7 @@ function generateWebAppFiles(options) {
|
|
|
13240
13193
|
const nextConfig: NextConfig = {
|
|
13241
13194
|
output: 'standalone',
|
|
13242
13195
|
reactStrictMode: true,
|
|
13243
|
-
transpilePackages: ['${modelsPackage}'
|
|
13196
|
+
transpilePackages: ['${modelsPackage}'],
|
|
13244
13197
|
};
|
|
13245
13198
|
|
|
13246
13199
|
export default nextConfig;
|
|
@@ -13273,12 +13226,10 @@ export default nextConfig;
|
|
|
13273
13226
|
incremental: true,
|
|
13274
13227
|
plugins: [{ name: "next" }],
|
|
13275
13228
|
paths: {
|
|
13276
|
-
"~/*": ["./src/*"
|
|
13229
|
+
"~/*": ["./src/*"],
|
|
13277
13230
|
[`@${options.name}/client/*`]: ["../../.gkm/client/*"],
|
|
13278
13231
|
[`${modelsPackage}`]: ["../../packages/models/src"],
|
|
13279
|
-
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
13280
|
-
[`${uiPackage}`]: ["../../packages/ui/src"],
|
|
13281
|
-
[`${uiPackage}/*`]: ["../../packages/ui/src/*"]
|
|
13232
|
+
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
13282
13233
|
}
|
|
13283
13234
|
},
|
|
13284
13235
|
include: [
|
|
@@ -13374,8 +13325,6 @@ export const api = createApi({
|
|
|
13374
13325
|
baseURL: clientConfig.apiUrl,
|
|
13375
13326
|
queryClient: getQueryClient(),
|
|
13376
13327
|
});
|
|
13377
|
-
`;
|
|
13378
|
-
const globalsCss = `@import '${uiPackage}/styles';
|
|
13379
13328
|
`;
|
|
13380
13329
|
const layoutTsx = `import type { Metadata } from 'next';
|
|
13381
13330
|
import { Providers } from './providers.tsx';
|
|
@@ -13401,7 +13350,14 @@ export default function RootLayout({
|
|
|
13401
13350
|
}
|
|
13402
13351
|
`;
|
|
13403
13352
|
const pageTsx = `import { api } from '~/api/index.ts';
|
|
13404
|
-
import { Button
|
|
13353
|
+
import { Button } from '~/components/ui/button.tsx';
|
|
13354
|
+
import {
|
|
13355
|
+
Card,
|
|
13356
|
+
CardContent,
|
|
13357
|
+
CardDescription,
|
|
13358
|
+
CardHeader,
|
|
13359
|
+
CardTitle,
|
|
13360
|
+
} from '~/components/ui/card.tsx';
|
|
13405
13361
|
|
|
13406
13362
|
export default async function Home() {
|
|
13407
13363
|
// Type-safe API call using the generated client
|
|
@@ -13441,7 +13397,7 @@ export default async function Home() {
|
|
|
13441
13397
|
<li>Run <code className="rounded bg-muted px-1">gkm openapi</code> to generate typed API client</li>
|
|
13442
13398
|
<li>Edit <code className="rounded bg-muted px-1">apps/web/src/app/page.tsx</code> to customize this page</li>
|
|
13443
13399
|
<li>Add API routes in <code className="rounded bg-muted px-1">apps/api/src/endpoints/</code></li>
|
|
13444
|
-
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in
|
|
13400
|
+
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in apps/web</li>
|
|
13445
13401
|
</ul>
|
|
13446
13402
|
<div className="flex gap-4">
|
|
13447
13403
|
<Button>Get Started</Button>
|
|
@@ -13453,6 +13409,11 @@ export default async function Home() {
|
|
|
13453
13409
|
</main>
|
|
13454
13410
|
);
|
|
13455
13411
|
}
|
|
13412
|
+
`;
|
|
13413
|
+
const gitignore = `.next/
|
|
13414
|
+
node_modules/
|
|
13415
|
+
.env.local
|
|
13416
|
+
*.log
|
|
13456
13417
|
`;
|
|
13457
13418
|
return [
|
|
13458
13419
|
{
|
|
@@ -13473,8 +13434,9 @@ export default async function Home() {
|
|
|
13473
13434
|
},
|
|
13474
13435
|
{
|
|
13475
13436
|
path: "apps/web/src/app/globals.css",
|
|
13476
|
-
content:
|
|
13437
|
+
content: SHADCN_GLOBALS_CSS
|
|
13477
13438
|
},
|
|
13439
|
+
...shadcnFiles("apps/web", "src/app/globals.css", { rsc: true }),
|
|
13478
13440
|
{
|
|
13479
13441
|
path: "apps/web/src/app/layout.tsx",
|
|
13480
13442
|
content: layoutTsx
|
|
@@ -13509,11 +13471,7 @@ export default async function Home() {
|
|
|
13509
13471
|
},
|
|
13510
13472
|
{
|
|
13511
13473
|
path: "apps/web/.gitignore",
|
|
13512
|
-
content:
|
|
13513
|
-
node_modules/
|
|
13514
|
-
.env.local
|
|
13515
|
-
*.log
|
|
13516
|
-
`
|
|
13474
|
+
content: gitignore
|
|
13517
13475
|
}
|
|
13518
13476
|
];
|
|
13519
13477
|
}
|
|
@@ -13529,7 +13487,6 @@ function generateTanStackWebFiles(options) {
|
|
|
13529
13487
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
13530
13488
|
const packageName = `@${options.name}/web`;
|
|
13531
13489
|
const modelsPackage = `@${options.name}/models`;
|
|
13532
|
-
const uiPackage = `@${options.name}/ui`;
|
|
13533
13490
|
const packageJson = {
|
|
13534
13491
|
name: packageName,
|
|
13535
13492
|
version: "0.0.1",
|
|
@@ -13543,7 +13500,7 @@ function generateTanStackWebFiles(options) {
|
|
|
13543
13500
|
},
|
|
13544
13501
|
dependencies: {
|
|
13545
13502
|
[modelsPackage]: "workspace:*",
|
|
13546
|
-
|
|
13503
|
+
...SHADCN_DEPENDENCIES,
|
|
13547
13504
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
13548
13505
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
13549
13506
|
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
@@ -13610,11 +13567,9 @@ export default defineConfig({
|
|
|
13610
13567
|
incremental: true,
|
|
13611
13568
|
types: ["vite/client"],
|
|
13612
13569
|
paths: {
|
|
13613
|
-
"~/*": ["./src/*"
|
|
13570
|
+
"~/*": ["./src/*"],
|
|
13614
13571
|
[modelsPackage]: ["../../packages/models/src"],
|
|
13615
13572
|
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
13616
|
-
[uiPackage]: ["../../packages/ui/src"],
|
|
13617
|
-
[`${uiPackage}/*`]: ["../../packages/ui/src/*"],
|
|
13618
13573
|
[`@${options.name}/client/*`]: ["../../.gkm/client/*"]
|
|
13619
13574
|
}
|
|
13620
13575
|
},
|
|
@@ -13817,10 +13772,15 @@ function HomeComponent() {
|
|
|
13817
13772
|
);
|
|
13818
13773
|
}
|
|
13819
13774
|
`;
|
|
13820
|
-
const globalsCss =
|
|
13821
|
-
|
|
13775
|
+
const globalsCss = `${SHADCN_GLOBALS_CSS}
|
|
13822
13776
|
@source "../..";
|
|
13823
|
-
|
|
13777
|
+
`;
|
|
13778
|
+
const gitignore = `.output/
|
|
13779
|
+
.vinxi/
|
|
13780
|
+
node_modules/
|
|
13781
|
+
.env.local
|
|
13782
|
+
*.log
|
|
13783
|
+
src/routeTree.gen.ts
|
|
13824
13784
|
`;
|
|
13825
13785
|
return [
|
|
13826
13786
|
{
|
|
@@ -13875,15 +13835,10 @@ function HomeComponent() {
|
|
|
13875
13835
|
path: "apps/web/src/styles/globals.css",
|
|
13876
13836
|
content: globalsCss
|
|
13877
13837
|
},
|
|
13838
|
+
...shadcnFiles("apps/web", "src/styles/globals.css", { rsc: false }),
|
|
13878
13839
|
{
|
|
13879
13840
|
path: "apps/web/.gitignore",
|
|
13880
|
-
content:
|
|
13881
|
-
.vinxi/
|
|
13882
|
-
node_modules/
|
|
13883
|
-
.env.local
|
|
13884
|
-
*.log
|
|
13885
|
-
src/routeTree.gen.ts
|
|
13886
|
-
`
|
|
13841
|
+
content: gitignore
|
|
13887
13842
|
}
|
|
13888
13843
|
];
|
|
13889
13844
|
}
|
|
@@ -13932,7 +13887,7 @@ const apiTemplate = {
|
|
|
13932
13887
|
const { cache, uploads, mail: sendsMail } = options.constructs;
|
|
13933
13888
|
const bucket = storageFor();
|
|
13934
13889
|
const kv = cacheFor();
|
|
13935
|
-
|
|
13890
|
+
databaseFor();
|
|
13936
13891
|
const mail = emailFor();
|
|
13937
13892
|
const src = (path) => `~/${path}`;
|
|
13938
13893
|
const declares = !monorepo;
|
|
@@ -14231,41 +14186,6 @@ export const telescope = new Telescope({
|
|
|
14231
14186
|
storage: new InMemoryStorage({ maxEntries: 100 }),
|
|
14232
14187
|
enabled: process.env.NODE_ENV === 'development',
|
|
14233
14188
|
});
|
|
14234
|
-
`
|
|
14235
|
-
});
|
|
14236
|
-
if (options.studio && options.constructs.database) files.push({
|
|
14237
|
-
path: "src/config/studio.ts",
|
|
14238
|
-
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
14239
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
14240
|
-
import pg from 'pg';
|
|
14241
|
-
import { type Database, database } from '${declares ? src("constructs/database.ts") : constructsImport("database")}';
|
|
14242
|
-
import { envParser } from '${src("config/env.ts")}';
|
|
14243
|
-
|
|
14244
|
-
const studioConfig = envParser
|
|
14245
|
-
.create((get) => ({
|
|
14246
|
-
databaseUrl: get('${db.urlKey}').string(),
|
|
14247
|
-
}))
|
|
14248
|
-
.parse();
|
|
14249
|
-
|
|
14250
|
-
// Studio's own client, built the way the construct builds its own — the same
|
|
14251
|
-
// plugins — so the browser shows what the app's queries see.
|
|
14252
|
-
const db = new Kysely<Database>({
|
|
14253
|
-
...database.clientConfig,
|
|
14254
|
-
dialect: new PostgresDialect({
|
|
14255
|
-
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
14256
|
-
}),
|
|
14257
|
-
});
|
|
14258
|
-
|
|
14259
|
-
export const studio = new Studio<Database>({
|
|
14260
|
-
monitoring: {
|
|
14261
|
-
storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
|
|
14262
|
-
},
|
|
14263
|
-
data: {
|
|
14264
|
-
db,
|
|
14265
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
14266
|
-
},
|
|
14267
|
-
enabled: process.env.NODE_ENV === 'development',
|
|
14268
|
-
});
|
|
14269
14189
|
`
|
|
14270
14190
|
});
|
|
14271
14191
|
return files;
|
|
@@ -14313,7 +14233,7 @@ const templates = {
|
|
|
14313
14233
|
},
|
|
14314
14234
|
files: (options) => {
|
|
14315
14235
|
const { loggerType, routesStructure, name } = options;
|
|
14316
|
-
|
|
14236
|
+
databaseFor();
|
|
14317
14237
|
const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
|
|
14318
14238
|
|
|
14319
14239
|
export const logger = createLogger();
|
|
@@ -14395,40 +14315,6 @@ export const telescope = new Telescope({
|
|
|
14395
14315
|
storage: new InMemoryStorage({ maxEntries: 100 }),
|
|
14396
14316
|
enabled: process.env.NODE_ENV === 'development',
|
|
14397
14317
|
});
|
|
14398
|
-
`
|
|
14399
|
-
});
|
|
14400
|
-
if (options.studio && options.constructs.database) files.push({
|
|
14401
|
-
path: "src/config/studio.ts",
|
|
14402
|
-
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
14403
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
14404
|
-
import pg from 'pg';
|
|
14405
|
-
import type { Database } from '~/constructs/database.ts';
|
|
14406
|
-
import { envParser } from '~/config/env.ts';
|
|
14407
|
-
|
|
14408
|
-
// The key the database construct publishes — not a hand-written DATABASE_URL.
|
|
14409
|
-
const studioConfig = envParser
|
|
14410
|
-
.create((get) => ({
|
|
14411
|
-
databaseUrl: get('${db.urlKey}').string(),
|
|
14412
|
-
}))
|
|
14413
|
-
.parse();
|
|
14414
|
-
|
|
14415
|
-
// Create a Kysely instance for Studio
|
|
14416
|
-
const db = new Kysely<Database>({
|
|
14417
|
-
dialect: new PostgresDialect({
|
|
14418
|
-
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
14419
|
-
}),
|
|
14420
|
-
});
|
|
14421
|
-
|
|
14422
|
-
export const studio = new Studio<Database>({
|
|
14423
|
-
monitoring: {
|
|
14424
|
-
storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
|
|
14425
|
-
},
|
|
14426
|
-
data: {
|
|
14427
|
-
db,
|
|
14428
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
14429
|
-
},
|
|
14430
|
-
enabled: process.env.NODE_ENV === 'development',
|
|
14431
|
-
});
|
|
14432
14318
|
`
|
|
14433
14319
|
});
|
|
14434
14320
|
return files;
|
|
@@ -15149,7 +15035,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
15149
15035
|
{
|
|
15150
15036
|
type: options.yes ? null : "confirm",
|
|
15151
15037
|
name: "telescope",
|
|
15152
|
-
message: "Include Telescope (
|
|
15038
|
+
message: "Include Telescope (request, exception and log recording)?",
|
|
15153
15039
|
initial: true
|
|
15154
15040
|
},
|
|
15155
15041
|
{
|
|
@@ -15204,13 +15090,11 @@ async function initCommand(projectName, options = {}) {
|
|
|
15204
15090
|
};
|
|
15205
15091
|
const pkgManager = options.pm ? options.pm : options.yes ? "pnpm" : answers.packageManager ?? detectedPkgManager;
|
|
15206
15092
|
const deployTarget = options.deploy ?? (options.yes ? "none" : answers.deployTarget ?? "none");
|
|
15207
|
-
const database = constructs.database;
|
|
15208
15093
|
const frontendFramework = isFullstack ? options.yes ? "nextjs" : answers.frontendFramework ?? "nextjs" : void 0;
|
|
15209
15094
|
const templateOptions = {
|
|
15210
15095
|
name,
|
|
15211
15096
|
template,
|
|
15212
15097
|
telescope: options.yes ? true : answers.telescope ?? true,
|
|
15213
|
-
studio: database,
|
|
15214
15098
|
loggerType: options.yes ? "pino" : answers.loggerType ?? "pino",
|
|
15215
15099
|
routesStructure: options.yes ? "centralized-endpoints" : answers.routesStructure ?? "centralized-endpoints",
|
|
15216
15100
|
monorepo,
|
|
@@ -15247,7 +15131,6 @@ async function initCommand(projectName, options = {}) {
|
|
|
15247
15131
|
] : [];
|
|
15248
15132
|
const webAppFiles = isFullstack ? frontendFramework === "tanstack-start" ? generateTanStackWebFiles(templateOptions) : frontendFramework === "expo" ? generateExpoAppFiles(templateOptions) : generateWebAppFiles(templateOptions) : [];
|
|
15249
15133
|
const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
|
|
15250
|
-
const uiPackageFiles = isFullstack ? generateUiPackageFiles(templateOptions) : [];
|
|
15251
15134
|
for (const { path, content } of rootFiles) {
|
|
15252
15135
|
const fullPath = join(targetDir, path);
|
|
15253
15136
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
@@ -15268,11 +15151,6 @@ async function initCommand(projectName, options = {}) {
|
|
|
15268
15151
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
15269
15152
|
await writeFile(fullPath, content);
|
|
15270
15153
|
}
|
|
15271
|
-
for (const { path, content } of uiPackageFiles) {
|
|
15272
|
-
const fullPath = join(targetDir, path);
|
|
15273
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
15274
|
-
await writeFile(fullPath, content);
|
|
15275
|
-
}
|
|
15276
15154
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
15277
15155
|
const local = templateOptions.stages.local;
|
|
15278
15156
|
const devSecrets = createStageSecrets(local, [], { projectName: name });
|
|
@@ -15375,8 +15253,7 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
15375
15253
|
}
|
|
15376
15254
|
}
|
|
15377
15255
|
console.log(` ├── packages/`);
|
|
15378
|
-
console.log(` │
|
|
15379
|
-
if (isFullstackTemplate(options.template)) console.log(` │ └── ui/ # Shared UI components`);
|
|
15256
|
+
console.log(` │ └── models/ # Shared Zod schemas`);
|
|
15380
15257
|
console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
|
|
15381
15258
|
console.log(` ├── gkm.config.ts # Workspace config`);
|
|
15382
15259
|
console.log(` └── turbo.json # Turbo config`);
|
|
@@ -16130,7 +16007,7 @@ function testDatabases(reconciled) {
|
|
|
16130
16007
|
* starts without the reference data the app needs.
|
|
16131
16008
|
*/
|
|
16132
16009
|
async function migrateTestStage(workspace, env) {
|
|
16133
|
-
const { migrateAndSeed } = await import("./migrate-
|
|
16010
|
+
const { migrateAndSeed } = await import("./migrate-CS3DkO6y.mjs");
|
|
16134
16011
|
const { discover } = await import("./discover-Cf2jAcgF.mjs").then((n) => n.n);
|
|
16135
16012
|
const sources = {};
|
|
16136
16013
|
const manifest = await discover({
|
|
@@ -16561,7 +16438,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
16561
16438
|
try {
|
|
16562
16439
|
const globalOptions = program.opts();
|
|
16563
16440
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16564
|
-
const { migrateCommand } = await import("./migrate-
|
|
16441
|
+
const { migrateCommand } = await import("./migrate-CS3DkO6y.mjs");
|
|
16565
16442
|
await migrateCommand({
|
|
16566
16443
|
...options,
|
|
16567
16444
|
...construct ? { construct } : {}
|
|
@@ -16575,7 +16452,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16575
16452
|
try {
|
|
16576
16453
|
const globalOptions = program.opts();
|
|
16577
16454
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16578
|
-
const { seedCommand } = await import("./migrate-
|
|
16455
|
+
const { seedCommand } = await import("./migrate-CS3DkO6y.mjs");
|
|
16579
16456
|
await seedCommand({
|
|
16580
16457
|
...options,
|
|
16581
16458
|
...construct ? { construct } : {}
|
|
@@ -16589,7 +16466,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16589
16466
|
try {
|
|
16590
16467
|
const globalOptions = program.opts();
|
|
16591
16468
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16592
|
-
const { migrationCommand } = await import("./migrate-
|
|
16469
|
+
const { migrationCommand } = await import("./migrate-CS3DkO6y.mjs");
|
|
16593
16470
|
await migrationCommand({
|
|
16594
16471
|
construct,
|
|
16595
16472
|
...name ? { name } : {}
|
|
@@ -16687,7 +16564,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
16687
16564
|
try {
|
|
16688
16565
|
const globalOptions = program.opts();
|
|
16689
16566
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16690
|
-
const { encryptionRotateCommand } = await import("./encryption-
|
|
16567
|
+
const { encryptionRotateCommand } = await import("./encryption-KQTYFEzU.mjs");
|
|
16691
16568
|
await encryptionRotateCommand(construct, options);
|
|
16692
16569
|
} catch (error) {
|
|
16693
16570
|
console.error(formatError(error));
|
|
@@ -16698,7 +16575,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
16698
16575
|
try {
|
|
16699
16576
|
const globalOptions = program.opts();
|
|
16700
16577
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16701
|
-
const { encryptionRetireCommand } = await import("./encryption-
|
|
16578
|
+
const { encryptionRetireCommand } = await import("./encryption-KQTYFEzU.mjs");
|
|
16702
16579
|
await encryptionRetireCommand(construct, key, options);
|
|
16703
16580
|
} catch (error) {
|
|
16704
16581
|
console.error(formatError(error));
|
|
@@ -16750,7 +16627,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16750
16627
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16751
16628
|
process.exit(1);
|
|
16752
16629
|
}
|
|
16753
|
-
const { derivedContainers } = await import("./workspace-
|
|
16630
|
+
const { derivedContainers } = await import("./workspace-CgWcE_Yr.mjs").then((n) => n.o);
|
|
16754
16631
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16755
16632
|
if (!result) {
|
|
16756
16633
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16809,7 +16686,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16809
16686
|
try {
|
|
16810
16687
|
const globalOptions = program.opts();
|
|
16811
16688
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16812
|
-
const { deployGithubCommand } = await import("./github-
|
|
16689
|
+
const { deployGithubCommand } = await import("./github-y7OEj59M.mjs");
|
|
16813
16690
|
await deployGithubCommand(options);
|
|
16814
16691
|
} catch (error) {
|
|
16815
16692
|
console.error(formatError(error));
|
|
@@ -16912,6 +16789,16 @@ program.command("state:show").description("Show deployment state for a stage").r
|
|
|
16912
16789
|
process.exit(1);
|
|
16913
16790
|
}
|
|
16914
16791
|
});
|
|
16792
|
+
program.command("state:unlock").description("Release a stage deploy lock left behind by a run that crashed").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
16793
|
+
try {
|
|
16794
|
+
const globalOptions = program.opts();
|
|
16795
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16796
|
+
await stateUnlockCommand(options);
|
|
16797
|
+
} catch (error) {
|
|
16798
|
+
console.error(formatError(error));
|
|
16799
|
+
process.exit(1);
|
|
16800
|
+
}
|
|
16801
|
+
});
|
|
16915
16802
|
program.command("state:diff").description("Compare local and remote deployment state").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
16916
16803
|
try {
|
|
16917
16804
|
const globalOptions = program.opts();
|
|
@@ -16934,6 +16821,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16934
16821
|
});
|
|
16935
16822
|
program.parse();
|
|
16936
16823
|
//#endregion
|
|
16937
|
-
export {
|
|
16824
|
+
export { StageStateMissing as a, StateStoreWithoutLocking as c, newLockHolder as d, encryption_exports as f, DocumentStateStore as i, StateVersionConflict as l, getHostingerToken as m, slug as n, StateLocked as o, appPorts_exports as p, stamp as r, StateStoreBusy as s, generateFullstackCustomSecrets as t, contentVersion as u };
|
|
16938
16825
|
|
|
16939
16826
|
//# sourceMappingURL=index.mjs.map
|