@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.cjs
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
4
4
|
const require_discover = require("./discover-DXNviAh-.cjs");
|
|
5
|
-
const require_config = require("./config-
|
|
6
|
-
const require_workspace = require("./workspace-
|
|
5
|
+
const require_config = require("./config-CP6wsLHA.cjs");
|
|
6
|
+
const require_workspace = require("./workspace-BpdXIeWV.cjs");
|
|
7
7
|
const require_storage = require("./storage-CSGoC3bK.cjs");
|
|
8
8
|
const require_store = require("./store-DQgxT_WR.cjs");
|
|
9
|
-
const require_workspace$1 = require("./workspace-
|
|
10
|
-
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-
|
|
9
|
+
const require_workspace$1 = require("./workspace-BbPJVCto.cjs");
|
|
10
|
+
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BLRQ7Iu9.cjs");
|
|
11
11
|
const require_openapi = require("./openapi.cjs");
|
|
12
|
-
const require_ready = require("./ready-
|
|
13
|
-
const require_backends = require("./backends-
|
|
12
|
+
const require_ready = require("./ready-MUhVJDHe.cjs");
|
|
13
|
+
const require_backends = require("./backends-_gQdAzBK.cjs");
|
|
14
14
|
const require_emit = require("./emit-Dmc1nvdS.cjs");
|
|
15
15
|
let commander = require("commander");
|
|
16
16
|
let node_process = require("node:process");
|
|
@@ -20,10 +20,10 @@ let node_fs = require("node:fs");
|
|
|
20
20
|
let node_fs_promises = require("node:fs/promises");
|
|
21
21
|
let node_os = require("node:os");
|
|
22
22
|
let node_path = require("node:path");
|
|
23
|
-
let node_child_process = require("node:child_process");
|
|
24
23
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
25
24
|
let fast_glob = require("fast-glob");
|
|
26
25
|
fast_glob = require_rolldown_runtime.__toESM(fast_glob, 1);
|
|
26
|
+
let node_child_process = require("node:child_process");
|
|
27
27
|
let node_module = require("node:module");
|
|
28
28
|
let chokidar = require("chokidar");
|
|
29
29
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
@@ -48,7 +48,7 @@ let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
|
48
48
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
49
49
|
let node_dns_promises = require("node:dns/promises");
|
|
50
50
|
//#region package.json
|
|
51
|
-
var version = "10.0.0-alpha.
|
|
51
|
+
var version = "10.0.0-alpha.56";
|
|
52
52
|
//#endregion
|
|
53
53
|
//#region src/auth/credentials.ts
|
|
54
54
|
/**
|
|
@@ -394,12 +394,12 @@ function withOwningTsconfigJsx(nodeOptions = "") {
|
|
|
394
394
|
* unhelpful.
|
|
395
395
|
*
|
|
396
396
|
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
397
|
-
*
|
|
398
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
399
|
-
* the line in the same place.
|
|
397
|
+
* asks for a password, and a setup that silently escalates on every run is a
|
|
398
|
+
* worse trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
399
|
+
* draws the line in the same place.
|
|
400
400
|
*/
|
|
401
401
|
const logger$11 = console;
|
|
402
|
-
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
402
|
+
const run$1 = (0, node_util.promisify)(node_child_process.execFile);
|
|
403
403
|
/**
|
|
404
404
|
* How each platform is told about a new authority.
|
|
405
405
|
*
|
|
@@ -410,15 +410,14 @@ const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
|
410
410
|
*/
|
|
411
411
|
function installation(platform, certificate) {
|
|
412
412
|
if (platform === "darwin") return {
|
|
413
|
-
describe: "
|
|
414
|
-
|
|
415
|
-
|
|
413
|
+
describe: "your login keychain",
|
|
414
|
+
asks: "macOS asks once — your password or Touch ID — to change trust settings.",
|
|
415
|
+
steps: [["security", [
|
|
416
416
|
"add-trusted-cert",
|
|
417
|
-
"-d",
|
|
418
417
|
"-r",
|
|
419
418
|
"trustRoot",
|
|
420
419
|
"-k",
|
|
421
|
-
"
|
|
420
|
+
(0, node_path.join)((0, node_os.homedir)(), "Library/Keychains/login.keychain-db"),
|
|
422
421
|
certificate
|
|
423
422
|
]]]
|
|
424
423
|
};
|
|
@@ -426,6 +425,7 @@ function installation(platform, certificate) {
|
|
|
426
425
|
const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
|
|
427
426
|
return {
|
|
428
427
|
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
428
|
+
asks: "This needs sudo: the system trust store is the only one.",
|
|
429
429
|
steps: debian ? [["sudo", [
|
|
430
430
|
"cp",
|
|
431
431
|
certificate,
|
|
@@ -449,15 +449,15 @@ async function trustCommand(options = {}) {
|
|
|
449
449
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
450
450
|
const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
|
|
451
451
|
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
452
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
452
|
+
const { describe, asks, steps } = installation(process.platform, certificate);
|
|
453
453
|
logger$11.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
454
454
|
logger$11.log(` ${certificate}\n`);
|
|
455
455
|
if (options.dryRun) {
|
|
456
456
|
for (const [command, args] of steps) logger$11.log(` ${command} ${args.join(" ")}`);
|
|
457
457
|
return;
|
|
458
458
|
}
|
|
459
|
-
logger$11.log(
|
|
460
|
-
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
459
|
+
logger$11.log(` ${asks}\n`);
|
|
460
|
+
for (const [command, args] of steps) await run$1(command, args, { stdio: "inherit" });
|
|
461
461
|
logger$11.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
462
462
|
}
|
|
463
463
|
/**
|
|
@@ -465,7 +465,7 @@ async function trustCommand(options = {}) {
|
|
|
465
465
|
*
|
|
466
466
|
* A machine's answer, not the project's: whether a laptop installs a root
|
|
467
467
|
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
468
|
-
* machine previously answered, then a prompt. Never a silent
|
|
468
|
+
* machine previously answered, then a prompt. Never a silent escalation — that is
|
|
469
469
|
* the one thing a setup command should not do on somebody's behalf.
|
|
470
470
|
*
|
|
471
471
|
* Silent and free when the machine already trusts it, which is what makes it
|
|
@@ -482,7 +482,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
482
482
|
const { install } = await (0, prompts.default)({
|
|
483
483
|
type: "confirm",
|
|
484
484
|
name: "install",
|
|
485
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (
|
|
485
|
+
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (asks for your password)",
|
|
486
486
|
initial: false
|
|
487
487
|
});
|
|
488
488
|
if (!install) {
|
|
@@ -523,7 +523,7 @@ async function recordDeclined(root) {
|
|
|
523
523
|
async function isTrusted(url) {
|
|
524
524
|
const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
|
|
525
525
|
try {
|
|
526
|
-
await run(process.execPath, [
|
|
526
|
+
await run$1(process.execPath, [
|
|
527
527
|
"--use-system-ca",
|
|
528
528
|
"-e",
|
|
529
529
|
`fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
|
|
@@ -1339,30 +1339,6 @@ function normalizeTelescopeConfig(config) {
|
|
|
1339
1339
|
};
|
|
1340
1340
|
}
|
|
1341
1341
|
/**
|
|
1342
|
-
* Normalize studio configuration
|
|
1343
|
-
* @internal Exported for testing
|
|
1344
|
-
*/
|
|
1345
|
-
function normalizeStudioConfig(config) {
|
|
1346
|
-
if (config === false) return;
|
|
1347
|
-
if (typeof config === "string") {
|
|
1348
|
-
const { path: studioPath, importPattern: studioImportPattern } = require_config.parseModuleConfig(config, "studio");
|
|
1349
|
-
return {
|
|
1350
|
-
enabled: true,
|
|
1351
|
-
studioPath,
|
|
1352
|
-
studioImportPattern,
|
|
1353
|
-
path: "/__studio",
|
|
1354
|
-
schema: "public"
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
if (!(config === true || config === void 0 || config.enabled !== false)) return;
|
|
1358
|
-
const studioConfig = typeof config === "object" ? config : {};
|
|
1359
|
-
return {
|
|
1360
|
-
enabled: true,
|
|
1361
|
-
path: studioConfig.path ?? "/__studio",
|
|
1362
|
-
schema: studioConfig.schema ?? "public"
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
1342
|
* Normalize hooks configuration
|
|
1367
1343
|
* @internal Exported for testing
|
|
1368
1344
|
*/
|
|
@@ -1374,10 +1350,10 @@ function normalizeHooksConfig(config, cwd = process.cwd()) {
|
|
|
1374
1350
|
/**
|
|
1375
1351
|
* What a `--production` build is.
|
|
1376
1352
|
*
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1353
|
+
* Background work — queues polled, crons scheduled, subscribers drained — is
|
|
1354
|
+
* included here, and `buildApp` takes it back out when the app serves a
|
|
1355
|
+
* RestApi: an API's image answers HTTP and nothing else, and that work belongs
|
|
1356
|
+
* to a Worker. The build still writes their files on every server build.
|
|
1381
1357
|
*
|
|
1382
1358
|
* @internal Exported for testing
|
|
1383
1359
|
*/
|
|
@@ -1439,7 +1415,6 @@ async function devCommand(options) {
|
|
|
1439
1415
|
if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1440
1416
|
}
|
|
1441
1417
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1442
|
-
const studio = normalizeStudioConfig(config.studio);
|
|
1443
1418
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1444
1419
|
const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled;
|
|
1445
1420
|
const build = (bustCache = false) => quietly(() => buildApp({
|
|
@@ -1450,7 +1425,7 @@ async function devCommand(options) {
|
|
|
1450
1425
|
enableOpenApi,
|
|
1451
1426
|
cacheBackend: require_backends.cacheBackendFor(require_backends.providerOf(workspace ?? config)),
|
|
1452
1427
|
telescope,
|
|
1453
|
-
|
|
1428
|
+
databaseApi: true,
|
|
1454
1429
|
hooks,
|
|
1455
1430
|
skipBundle: true,
|
|
1456
1431
|
bustCache,
|
|
@@ -1483,7 +1458,7 @@ async function devCommand(options) {
|
|
|
1483
1458
|
secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
|
|
1484
1459
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1485
1460
|
}
|
|
1486
|
-
const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope,
|
|
1461
|
+
const devServer = new DevServer(options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, initial.databaseApi, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
|
|
1487
1462
|
devServer.label = workspaceAppName ?? "server";
|
|
1488
1463
|
devServer.publicUrl = publicUrl;
|
|
1489
1464
|
await devServer.start();
|
|
@@ -1771,9 +1746,9 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1771
1746
|
for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1772
1747
|
logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1773
1748
|
if (options.migrate || options.seed) {
|
|
1774
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
1749
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
|
|
1775
1750
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1776
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
1751
|
+
} else await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1777
1752
|
logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1778
1753
|
});
|
|
1779
1754
|
const appUrls = {};
|
|
@@ -2167,7 +2142,7 @@ var DevServer = class {
|
|
|
2167
2142
|
portExplicit;
|
|
2168
2143
|
enableOpenApi;
|
|
2169
2144
|
telescope;
|
|
2170
|
-
|
|
2145
|
+
databaseApi;
|
|
2171
2146
|
runtime;
|
|
2172
2147
|
appRoot;
|
|
2173
2148
|
secretsJsonPath;
|
|
@@ -2176,12 +2151,12 @@ var DevServer = class {
|
|
|
2176
2151
|
isRunning = false;
|
|
2177
2152
|
actualPort;
|
|
2178
2153
|
startTime = Date.now();
|
|
2179
|
-
constructor(requestedPort, portExplicit, enableOpenApi, telescope,
|
|
2154
|
+
constructor(requestedPort, portExplicit, enableOpenApi, telescope, databaseApi, runtime = "node", appRoot = process.cwd(), secretsJsonPath, selfServing = false) {
|
|
2180
2155
|
this.requestedPort = requestedPort;
|
|
2181
2156
|
this.portExplicit = portExplicit;
|
|
2182
2157
|
this.enableOpenApi = enableOpenApi;
|
|
2183
2158
|
this.telescope = telescope;
|
|
2184
|
-
this.
|
|
2159
|
+
this.databaseApi = databaseApi;
|
|
2185
2160
|
this.runtime = runtime;
|
|
2186
2161
|
this.appRoot = appRoot;
|
|
2187
2162
|
this.secretsJsonPath = secretsJsonPath;
|
|
@@ -2238,7 +2213,7 @@ var DevServer = class {
|
|
|
2238
2213
|
const tools = this.selfServing ? [] : [
|
|
2239
2214
|
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
2240
2215
|
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
2241
|
-
...this.
|
|
2216
|
+
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2242
2217
|
];
|
|
2243
2218
|
if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
|
|
2244
2219
|
}
|
|
@@ -2744,18 +2719,167 @@ var TopicGenerator = class extends require_discover.ConstructGenerator {
|
|
|
2744
2719
|
}
|
|
2745
2720
|
};
|
|
2746
2721
|
//#endregion
|
|
2722
|
+
//#region src/run.ts
|
|
2723
|
+
/**
|
|
2724
|
+
* Start a program with an argument array, never a shell string.
|
|
2725
|
+
*
|
|
2726
|
+
* `gkm build`, `gkm docker` and `gkm deploy` pass values that come from the
|
|
2727
|
+
* workspace — image refs, tags, build args, package names — to `docker` and
|
|
2728
|
+
* `turbo`. Joined into a string and handed to a shell, a package named
|
|
2729
|
+
* `x;curl …|sh` or a tag holding `$(…)` became part of the command line, which
|
|
2730
|
+
* matters most where the CLI builds a repository it didn't write: a CI job for a
|
|
2731
|
+
* fork, a shared build runner. Here every value reaches the program as one
|
|
2732
|
+
* `argv` element, whatever it contains.
|
|
2733
|
+
*/
|
|
2734
|
+
/** Long enough for a cold image build; short enough that a hung one ends. */
|
|
2735
|
+
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
2736
|
+
/** How long a timed-out child has to exit on SIGTERM before it is killed. */
|
|
2737
|
+
const KILL_GRACE_MS = 5e3;
|
|
2738
|
+
/** The command line as a person would read it — for messages, never to run. */
|
|
2739
|
+
function display(command, args) {
|
|
2740
|
+
return [command, ...args].join(" ");
|
|
2741
|
+
}
|
|
2742
|
+
/** The program ran and exited unsuccessfully; its own output said why. */
|
|
2743
|
+
var CommandFailed = class extends Error {
|
|
2744
|
+
command;
|
|
2745
|
+
args;
|
|
2746
|
+
exitCode;
|
|
2747
|
+
signal;
|
|
2748
|
+
constructor(command, args, exitCode, signal) {
|
|
2749
|
+
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.`);
|
|
2750
|
+
this.command = command;
|
|
2751
|
+
this.args = args;
|
|
2752
|
+
this.exitCode = exitCode;
|
|
2753
|
+
this.signal = signal;
|
|
2754
|
+
this.name = "CommandFailed";
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
/** The program ran past its timeout and was killed. */
|
|
2758
|
+
var CommandTimedOut = class extends Error {
|
|
2759
|
+
command;
|
|
2760
|
+
args;
|
|
2761
|
+
timeoutMs;
|
|
2762
|
+
constructor(command, args, timeoutMs) {
|
|
2763
|
+
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.`);
|
|
2764
|
+
this.command = command;
|
|
2765
|
+
this.args = args;
|
|
2766
|
+
this.timeoutMs = timeoutMs;
|
|
2767
|
+
this.name = "CommandTimedOut";
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
/**
|
|
2771
|
+
* Run `command` with `args`, resolving when it exits 0.
|
|
2772
|
+
*
|
|
2773
|
+
* Rejects with {@link CommandFailed} on any other exit, {@link CommandTimedOut}
|
|
2774
|
+
* once `timeoutMs` passes (after the child is gone), and with the spawn error
|
|
2775
|
+
* itself when the program cannot be started at all — `ENOENT` already names it.
|
|
2776
|
+
*/
|
|
2777
|
+
function run(command, args, options = {}) {
|
|
2778
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, ...spawnOptions } = options;
|
|
2779
|
+
return new Promise((resolve, reject) => {
|
|
2780
|
+
const child = (0, node_child_process.spawn)(command, [...args], {
|
|
2781
|
+
stdio: "inherit",
|
|
2782
|
+
...spawnOptions,
|
|
2783
|
+
shell: false
|
|
2784
|
+
});
|
|
2785
|
+
let timedOut = false;
|
|
2786
|
+
let killer;
|
|
2787
|
+
const timer = setTimeout(() => {
|
|
2788
|
+
timedOut = true;
|
|
2789
|
+
child.kill("SIGTERM");
|
|
2790
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
2791
|
+
killer.unref?.();
|
|
2792
|
+
}, timeoutMs);
|
|
2793
|
+
timer.unref?.();
|
|
2794
|
+
const settle = () => {
|
|
2795
|
+
clearTimeout(timer);
|
|
2796
|
+
if (killer) clearTimeout(killer);
|
|
2797
|
+
};
|
|
2798
|
+
child.on("error", (error) => {
|
|
2799
|
+
settle();
|
|
2800
|
+
reject(error);
|
|
2801
|
+
});
|
|
2802
|
+
child.on("close", (code, signal) => {
|
|
2803
|
+
settle();
|
|
2804
|
+
if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
|
|
2805
|
+
else if (code === 0) resolve();
|
|
2806
|
+
else reject(new CommandFailed(command, args, code, signal));
|
|
2807
|
+
});
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
//#endregion
|
|
2811
|
+
//#region src/secrets/encryption.ts
|
|
2812
|
+
var encryption_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
2813
|
+
encryptSecrets: () => encryptSecrets,
|
|
2814
|
+
generateDefineOptions: () => generateDefineOptions,
|
|
2815
|
+
keyFingerprint: () => keyFingerprint
|
|
2816
|
+
});
|
|
2817
|
+
/** AES-256-GCM configuration */
|
|
2818
|
+
const ALGORITHM = "aes-256-gcm";
|
|
2819
|
+
const KEY_LENGTH = 32;
|
|
2820
|
+
const IV_LENGTH = 12;
|
|
2821
|
+
/**
|
|
2822
|
+
* Encrypt secrets for embedding in a bundle.
|
|
2823
|
+
* Uses AES-256-GCM with a randomly generated ephemeral key.
|
|
2824
|
+
*
|
|
2825
|
+
* @param secrets - Key-value pairs to encrypt
|
|
2826
|
+
* @returns Encrypted payload with ephemeral master key
|
|
2827
|
+
*/
|
|
2828
|
+
function encryptSecrets(secrets) {
|
|
2829
|
+
const masterKey = (0, node_crypto.randomBytes)(KEY_LENGTH);
|
|
2830
|
+
const iv = (0, node_crypto.randomBytes)(IV_LENGTH);
|
|
2831
|
+
const plaintext = JSON.stringify(secrets);
|
|
2832
|
+
const cipher = (0, node_crypto.createCipheriv)(ALGORITHM, masterKey, iv);
|
|
2833
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
2834
|
+
const authTag = cipher.getAuthTag();
|
|
2835
|
+
return {
|
|
2836
|
+
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
2837
|
+
iv: iv.toString("hex"),
|
|
2838
|
+
masterKey: masterKey.toString("hex")
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Generate the define options for tsdown/esbuild.
|
|
2843
|
+
* These will be injected at build time.
|
|
2844
|
+
*/
|
|
2845
|
+
function generateDefineOptions(payload) {
|
|
2846
|
+
return {
|
|
2847
|
+
__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
|
|
2848
|
+
__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* A short, one-way name for a master key: the first 8 hex characters of its
|
|
2853
|
+
* SHA-256.
|
|
2854
|
+
*
|
|
2855
|
+
* Printed where the key itself used to be, so a person can tell which key a
|
|
2856
|
+
* build used — and match it against the one they hold — without the output
|
|
2857
|
+
* carrying a value that decrypts every secret of the stage.
|
|
2858
|
+
*/
|
|
2859
|
+
function keyFingerprint(masterKey) {
|
|
2860
|
+
return (0, node_crypto.createHash)("sha256").update(masterKey).digest("hex").slice(0, 8);
|
|
2861
|
+
}
|
|
2862
|
+
//#endregion
|
|
2863
|
+
//#region src/build/types.ts
|
|
2864
|
+
/**
|
|
2865
|
+
* Where `gkm dev` serves the database's read-only JSON API
|
|
2866
|
+
* (`@geekmidas/db/introspect`). Under `/__gkm` with the rest of what the dev
|
|
2867
|
+
* server adds, so it cannot collide with an application route.
|
|
2868
|
+
*/
|
|
2869
|
+
const DEV_DATABASE_API_PATH = "/__gkm/db";
|
|
2870
|
+
//#endregion
|
|
2747
2871
|
//#region src/build/owners.ts
|
|
2748
2872
|
/**
|
|
2749
2873
|
* What a generated entry imports its runtime from, read off the discovered
|
|
2750
2874
|
* constructs: the surface it serves, every construct a runnable can be built
|
|
2751
|
-
* from, and the database
|
|
2875
|
+
* from, and the database the dev server's JSON API reads.
|
|
2752
2876
|
*
|
|
2753
2877
|
* `gkm build` and `gkm dev` both write entries that import the surface for its
|
|
2754
2878
|
* logger and environment parser, so both ask here. A dev server that skipped it
|
|
2755
2879
|
* had nothing to import them from.
|
|
2756
2880
|
*/
|
|
2757
2881
|
function ownersContext(options) {
|
|
2758
|
-
const { declared, sources, workspaceRoot, appRoot,
|
|
2882
|
+
const { declared, sources, workspaceRoot, appRoot, databaseApi } = options;
|
|
2759
2883
|
const surfaces = restApis(declared);
|
|
2760
2884
|
const primary = servedSurface(declared, workspaceRoot, appRoot) ?? surfaces.find((d) => d.endpoints.length === 0) ?? surfaces[0];
|
|
2761
2885
|
const browsable = Object.entries(declared).find(([, d]) => d.kind === "database");
|
|
@@ -2782,13 +2906,13 @@ function ownersContext(options) {
|
|
|
2782
2906
|
exportName: primarySource.exportName
|
|
2783
2907
|
} } : {}
|
|
2784
2908
|
} } : {},
|
|
2785
|
-
|
|
2786
|
-
|
|
2909
|
+
...databaseApi && browsableSource ? { databaseApi: {
|
|
2910
|
+
path: DEV_DATABASE_API_PATH,
|
|
2787
2911
|
database: {
|
|
2788
2912
|
specifier: browsableSource.file,
|
|
2789
2913
|
exportName: browsableSource.exportName
|
|
2790
2914
|
}
|
|
2791
|
-
} :
|
|
2915
|
+
} } : {}
|
|
2792
2916
|
};
|
|
2793
2917
|
}
|
|
2794
2918
|
/**
|
|
@@ -2874,6 +2998,11 @@ export default app;
|
|
|
2874
2998
|
//#region src/build/index.ts
|
|
2875
2999
|
const logger$8 = console;
|
|
2876
3000
|
/**
|
|
3001
|
+
* Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
|
|
3002
|
+
* with, beside the bundle in `.gkm/server/` and readable by its owner alone.
|
|
3003
|
+
*/
|
|
3004
|
+
const MASTER_KEY_FILE = "master.key";
|
|
3005
|
+
/**
|
|
2877
3006
|
* What a build run at the workspace root reads.
|
|
2878
3007
|
*
|
|
2879
3008
|
* One backend app there, and it is that app's config, so its own `openapi`
|
|
@@ -2966,8 +3095,6 @@ async function buildOneApp(input) {
|
|
|
2966
3095
|
logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
2967
3096
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2968
3097
|
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2969
|
-
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2970
|
-
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2971
3098
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
2972
3099
|
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2973
3100
|
return buildApp({
|
|
@@ -2979,7 +3106,6 @@ async function buildOneApp(input) {
|
|
|
2979
3106
|
cacheBackend,
|
|
2980
3107
|
production,
|
|
2981
3108
|
telescope,
|
|
2982
|
-
studio,
|
|
2983
3109
|
hooks,
|
|
2984
3110
|
markOptional: options.markOptional ?? false,
|
|
2985
3111
|
skipBundle: options.skipBundle ?? false,
|
|
@@ -3022,7 +3148,7 @@ async function writeManifest(input) {
|
|
|
3022
3148
|
* dev did not, and failed on exactly the apps the build had learned to handle.
|
|
3023
3149
|
*/
|
|
3024
3150
|
async function buildApp(input) {
|
|
3025
|
-
const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope,
|
|
3151
|
+
const { config, workspaceRoot, appRoot, target, enableOpenApi, cacheBackend, production, telescope, databaseApi, hooks, bustCache = false } = input;
|
|
3026
3152
|
const constructGlobs = config.constructs;
|
|
3027
3153
|
const constructSources = {};
|
|
3028
3154
|
const declared = constructGlobs ? await require_discover.discover({
|
|
@@ -3047,7 +3173,7 @@ async function buildApp(input) {
|
|
|
3047
3173
|
sources: constructSources,
|
|
3048
3174
|
workspaceRoot,
|
|
3049
3175
|
appRoot,
|
|
3050
|
-
|
|
3176
|
+
databaseApi
|
|
3051
3177
|
});
|
|
3052
3178
|
const buildContext = {
|
|
3053
3179
|
...derived,
|
|
@@ -3089,12 +3215,27 @@ async function buildApp(input) {
|
|
|
3089
3215
|
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
3090
3216
|
return {};
|
|
3091
3217
|
}
|
|
3218
|
+
if (production?.subscribers === "include" && derived.surface) {
|
|
3219
|
+
buildContext.production = {
|
|
3220
|
+
...production,
|
|
3221
|
+
subscribers: "exclude"
|
|
3222
|
+
};
|
|
3223
|
+
const left = [
|
|
3224
|
+
[allCrons.length, "cron"],
|
|
3225
|
+
[allQueues.length, "queue consumer"],
|
|
3226
|
+
[allSubscribers.length, "subscriber"]
|
|
3227
|
+
].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
|
|
3228
|
+
if (left.length > 0) logger$8.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
|
|
3229
|
+
}
|
|
3092
3230
|
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);
|
|
3093
3231
|
await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), {
|
|
3094
3232
|
openapi: config.openapi,
|
|
3095
3233
|
root: workspaceRoot
|
|
3096
3234
|
});
|
|
3097
|
-
return
|
|
3235
|
+
return {
|
|
3236
|
+
...result,
|
|
3237
|
+
...buildContext.databaseApi ? { databaseApi: buildContext.databaseApi.path } : {}
|
|
3238
|
+
};
|
|
3098
3239
|
}
|
|
3099
3240
|
async function buildForTarget(target, context, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
|
|
3100
3241
|
const outputDir = (0, node_path.join)(appRoot, ".gkm", target);
|
|
@@ -3166,8 +3307,11 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3166
3307
|
})).masterKey;
|
|
3167
3308
|
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3168
3309
|
if (masterKey) {
|
|
3310
|
+
const keyPath = (0, node_path.join)(outputDir, MASTER_KEY_FILE);
|
|
3311
|
+
await (0, node_fs_promises.writeFile)(keyPath, `${masterKey}\n`, { mode: 384 });
|
|
3312
|
+
await (0, node_fs_promises.chmod)(keyPath, 384);
|
|
3169
3313
|
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
3170
|
-
logger$8.log(`
|
|
3314
|
+
logger$8.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${keyFingerprint(masterKey)})`);
|
|
3171
3315
|
}
|
|
3172
3316
|
}
|
|
3173
3317
|
return {
|
|
@@ -3190,7 +3334,7 @@ function detectPackageManager$2() {
|
|
|
3190
3334
|
return "npm";
|
|
3191
3335
|
}
|
|
3192
3336
|
/**
|
|
3193
|
-
*
|
|
3337
|
+
* The program and argv that run turbo's build.
|
|
3194
3338
|
*
|
|
3195
3339
|
* `filters` names the packages to build. Passing none lets turbo infer its own
|
|
3196
3340
|
* scope from the working directory, which for a workspace root means the
|
|
@@ -3198,14 +3342,24 @@ function detectPackageManager$2() {
|
|
|
3198
3342
|
* `gkm build`, inferring is how you get a build that runs itself forever.
|
|
3199
3343
|
* `workspaceBuildCommand` always names the apps.
|
|
3200
3344
|
*
|
|
3345
|
+
* An array, not a command string: the filters are package names read from the
|
|
3346
|
+
* workspace, and each must reach turbo as one argument whatever it holds. Each
|
|
3347
|
+
* is `--filter=<name>`, so even a name starting with `-` stays a filter value.
|
|
3348
|
+
*
|
|
3201
3349
|
* @internal Exported for testing
|
|
3202
3350
|
*/
|
|
3203
|
-
function
|
|
3204
|
-
const
|
|
3351
|
+
function getTurboArgs(pm, filters = [], { only = false } = {}) {
|
|
3352
|
+
const turbo = [
|
|
3353
|
+
"turbo",
|
|
3354
|
+
"run",
|
|
3355
|
+
"build",
|
|
3356
|
+
...(typeof filters === "string" ? [filters] : filters).map((f) => `--filter=${f}`),
|
|
3357
|
+
...only ? ["--only"] : []
|
|
3358
|
+
];
|
|
3205
3359
|
switch (pm) {
|
|
3206
|
-
case "pnpm": return
|
|
3207
|
-
case "yarn": return
|
|
3208
|
-
case "npm": return
|
|
3360
|
+
case "pnpm": return ["pnpm", ["exec", ...turbo]];
|
|
3361
|
+
case "yarn": return ["yarn", turbo];
|
|
3362
|
+
case "npm": return ["npx", turbo];
|
|
3209
3363
|
}
|
|
3210
3364
|
}
|
|
3211
3365
|
/**
|
|
@@ -3294,27 +3448,16 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3294
3448
|
const { filters, unpackaged } = turboFilters(workspace, { exclude: "backend" });
|
|
3295
3449
|
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.`);
|
|
3296
3450
|
const pm = detectPackageManager$2();
|
|
3297
|
-
const
|
|
3451
|
+
const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
|
|
3298
3452
|
if (filters.length > 0) {
|
|
3299
3453
|
logger$8.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
|
|
3300
|
-
logger$8.log(`Running: ${
|
|
3301
|
-
await
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
...process.env,
|
|
3308
|
-
NODE_ENV: options.production ? "production" : "development"
|
|
3309
|
-
}
|
|
3310
|
-
});
|
|
3311
|
-
child.on("close", (code) => {
|
|
3312
|
-
if (code === 0) resolve();
|
|
3313
|
-
else reject(/* @__PURE__ */ new Error(`Turbo build failed with exit code ${code}`));
|
|
3314
|
-
});
|
|
3315
|
-
child.on("error", (err) => {
|
|
3316
|
-
reject(err);
|
|
3317
|
-
});
|
|
3454
|
+
logger$8.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
|
|
3455
|
+
await run(turboBin, turboArgs, {
|
|
3456
|
+
cwd: workspace.root,
|
|
3457
|
+
env: {
|
|
3458
|
+
...process.env,
|
|
3459
|
+
NODE_ENV: options.production ? "production" : "development"
|
|
3460
|
+
}
|
|
3318
3461
|
});
|
|
3319
3462
|
}
|
|
3320
3463
|
for (const [appName, app] of apps) {
|
|
@@ -4667,6 +4810,39 @@ function getPublicUrlArgNames(app) {
|
|
|
4667
4810
|
return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
|
|
4668
4811
|
}
|
|
4669
4812
|
//#endregion
|
|
4813
|
+
//#region src/docker/imageRef.ts
|
|
4814
|
+
/**
|
|
4815
|
+
* An image reference, checked before it reaches `docker`.
|
|
4816
|
+
*
|
|
4817
|
+
* The parts come from the workspace — a registry in `gkm.config.ts`, an image
|
|
4818
|
+
* name from the project, a tag from `--tag` — and an argument array already
|
|
4819
|
+
* keeps them out of a shell. What it cannot do is stop `docker` reading a value
|
|
4820
|
+
* that starts with `-` as one of its own flags, so a ref is held to Docker's
|
|
4821
|
+
* own grammar (distribution/reference) and anything else is refused by name
|
|
4822
|
+
* before a build starts.
|
|
4823
|
+
*/
|
|
4824
|
+
const DOMAIN_COMPONENT = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
|
|
4825
|
+
const DOMAIN = `${DOMAIN_COMPONENT}(?:\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;
|
|
4826
|
+
const PATH_COMPONENT = "[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*";
|
|
4827
|
+
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,})?$`);
|
|
4828
|
+
/** Docker refuses a repository name longer than this. */
|
|
4829
|
+
const MAX_NAME_LENGTH = 255;
|
|
4830
|
+
/** A value that is not an image reference was about to be built or pushed. */
|
|
4831
|
+
var ImageRefInvalid = class extends Error {
|
|
4832
|
+
ref;
|
|
4833
|
+
constructor(ref) {
|
|
4834
|
+
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.`);
|
|
4835
|
+
this.ref = ref;
|
|
4836
|
+
this.name = "ImageRefInvalid";
|
|
4837
|
+
}
|
|
4838
|
+
};
|
|
4839
|
+
/** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */
|
|
4840
|
+
function validateImageRef(ref) {
|
|
4841
|
+
const name = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
|
|
4842
|
+
if (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) throw new ImageRefInvalid(ref);
|
|
4843
|
+
return ref;
|
|
4844
|
+
}
|
|
4845
|
+
//#endregion
|
|
4670
4846
|
//#region src/docker/templates.ts
|
|
4671
4847
|
const LOCKFILES = [
|
|
4672
4848
|
["pnpm-lock.yaml", "pnpm"],
|
|
@@ -5020,6 +5196,10 @@ node_modules
|
|
|
5020
5196
|
.gkm/server/*.ts
|
|
5021
5197
|
!.gkm/server/dist
|
|
5022
5198
|
|
|
5199
|
+
# The key gkm build --stage encrypted secrets with: runtime env only, never
|
|
5200
|
+
# the build context
|
|
5201
|
+
.gkm/server/master.key
|
|
5202
|
+
|
|
5023
5203
|
# IDE and editor
|
|
5024
5204
|
.idea
|
|
5025
5205
|
.vscode
|
|
@@ -5234,10 +5414,6 @@ FROM deps AS builder
|
|
|
5234
5414
|
|
|
5235
5415
|
WORKDIR /app
|
|
5236
5416
|
|
|
5237
|
-
# Build-time args for encrypted secrets
|
|
5238
|
-
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
5239
|
-
ARG GKM_CREDENTIALS_IV=""
|
|
5240
|
-
|
|
5241
5417
|
# Copy pruned source
|
|
5242
5418
|
COPY --from=pruner /app/out/full/ ./
|
|
5243
5419
|
|
|
@@ -5246,11 +5422,15 @@ COPY --from=pruner /app/out/full/ ./
|
|
|
5246
5422
|
COPY --from=pruner /app/gkm.config.* ./
|
|
5247
5423
|
COPY --from=pruner /app/tsconfig.* ./
|
|
5248
5424
|
|
|
5249
|
-
# Write encrypted credentials for gkm build to embed
|
|
5250
|
-
|
|
5425
|
+
# Write encrypted credentials for gkm build to embed.
|
|
5426
|
+
# A build secret (\`docker build --secret id=gkm_credentials,src=…\`), not an
|
|
5427
|
+
# ARG: an ARG is recorded in the image history. Two lines, ciphertext then IV;
|
|
5428
|
+
# absent when the app has no secrets.
|
|
5429
|
+
RUN --mount=type=secret,id=gkm_credentials,required=false \
|
|
5430
|
+
if [ -s /run/secrets/gkm_credentials ]; then \
|
|
5251
5431
|
mkdir -p ${appPath}/.gkm && \
|
|
5252
|
-
|
|
5253
|
-
|
|
5432
|
+
sed -n 1p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.enc && \
|
|
5433
|
+
sed -n 2p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.iv; \
|
|
5254
5434
|
fi
|
|
5255
5435
|
|
|
5256
5436
|
# Build any workspace packages the app depends on.
|
|
@@ -5337,10 +5517,6 @@ FROM deps AS builder
|
|
|
5337
5517
|
|
|
5338
5518
|
WORKDIR /app
|
|
5339
5519
|
|
|
5340
|
-
# Build-time args for encrypted secrets
|
|
5341
|
-
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
5342
|
-
ARG GKM_CREDENTIALS_IV=""
|
|
5343
|
-
|
|
5344
5520
|
# Copy pruned source
|
|
5345
5521
|
COPY --from=pruner /app/out/full/ ./
|
|
5346
5522
|
|
|
@@ -5348,20 +5524,17 @@ COPY --from=pruner /app/out/full/ ./
|
|
|
5348
5524
|
# Using wildcard to make it optional for single-app projects
|
|
5349
5525
|
COPY --from=pruner /app/tsconfig.* ./
|
|
5350
5526
|
|
|
5351
|
-
# Write encrypted credentials for tsdown to embed via define
|
|
5352
|
-
RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
|
|
5353
|
-
mkdir -p ${appPath}/.gkm && \
|
|
5354
|
-
echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
|
|
5355
|
-
echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
|
|
5356
|
-
fi
|
|
5357
|
-
|
|
5358
5527
|
# Bundle entry point with esbuild (outputs to dist/index.mjs)
|
|
5359
5528
|
# Creates a fully standalone bundle with all dependencies included
|
|
5360
|
-
# Use define to embed credentials if present
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5529
|
+
# Use define to embed credentials if present.
|
|
5530
|
+
# They arrive as a build secret (\`docker build --secret id=gkm_credentials,src=…\`),
|
|
5531
|
+
# not an ARG: an ARG is recorded in the image history. Two lines, ciphertext
|
|
5532
|
+
# then IV; absent when the app has no secrets.
|
|
5533
|
+
RUN --mount=type=secret,id=gkm_credentials,required=false \
|
|
5534
|
+
cd ${appPath} && \
|
|
5535
|
+
if [ -s /run/secrets/gkm_credentials ]; then \
|
|
5536
|
+
CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \
|
|
5537
|
+
IV=$(sed -n 2p /run/secrets/gkm_credentials) && \
|
|
5365
5538
|
npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
|
|
5366
5539
|
--outfile=dist/index.mjs --packages=bundle \
|
|
5367
5540
|
--banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);' \
|
|
@@ -5512,6 +5685,33 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5512
5685
|
//#endregion
|
|
5513
5686
|
//#region src/docker/index.ts
|
|
5514
5687
|
const logger$6 = console;
|
|
5688
|
+
/** `docker build` failed; what docker printed above says why. */
|
|
5689
|
+
var DockerBuildFailed = class extends Error {
|
|
5690
|
+
imageRef;
|
|
5691
|
+
constructor(imageRef, cause) {
|
|
5692
|
+
super(`Failed to build Docker image: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
5693
|
+
this.imageRef = imageRef;
|
|
5694
|
+
this.name = "DockerBuildFailed";
|
|
5695
|
+
}
|
|
5696
|
+
};
|
|
5697
|
+
/** `docker push` failed — usually the registry login or its permissions. */
|
|
5698
|
+
var DockerPushFailed = class extends Error {
|
|
5699
|
+
imageRef;
|
|
5700
|
+
constructor(imageRef, cause) {
|
|
5701
|
+
super(`Failed to push Docker image: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
5702
|
+
this.imageRef = imageRef;
|
|
5703
|
+
this.name = "DockerPushFailed";
|
|
5704
|
+
}
|
|
5705
|
+
};
|
|
5706
|
+
/** `gkm docker --push` was asked for with nowhere to push to. */
|
|
5707
|
+
var PushNeedsRegistry = class extends Error {
|
|
5708
|
+
imageName;
|
|
5709
|
+
constructor(imageName) {
|
|
5710
|
+
super(`Registry is required to push Docker image '${imageName}'. Use --registry or configure docker.registry in gkm.config.ts`);
|
|
5711
|
+
this.imageName = imageName;
|
|
5712
|
+
this.name = "PushNeedsRegistry";
|
|
5713
|
+
}
|
|
5714
|
+
};
|
|
5515
5715
|
/**
|
|
5516
5716
|
* Docker command implementation
|
|
5517
5717
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5615,14 +5815,18 @@ function ensureLockfile(cwd) {
|
|
|
5615
5815
|
async function buildDockerImage(imageName, options) {
|
|
5616
5816
|
const tag = options.tag ?? "latest";
|
|
5617
5817
|
const registry = options.registry;
|
|
5618
|
-
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}
|
|
5818
|
+
const fullImageName = validateImageRef(registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`);
|
|
5619
5819
|
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5620
5820
|
const cwd = process.cwd();
|
|
5621
5821
|
const cleanup = ensureLockfile(cwd);
|
|
5622
5822
|
try {
|
|
5623
|
-
|
|
5823
|
+
await run("docker", [
|
|
5824
|
+
"build",
|
|
5825
|
+
"--file=.gkm/docker/Dockerfile",
|
|
5826
|
+
`--tag=${fullImageName}`,
|
|
5827
|
+
"."
|
|
5828
|
+
], {
|
|
5624
5829
|
cwd,
|
|
5625
|
-
stdio: "inherit",
|
|
5626
5830
|
env: {
|
|
5627
5831
|
...process.env,
|
|
5628
5832
|
DOCKER_BUILDKIT: "1"
|
|
@@ -5630,7 +5834,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5630
5834
|
});
|
|
5631
5835
|
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5632
5836
|
} catch (error) {
|
|
5633
|
-
throw new
|
|
5837
|
+
throw new DockerBuildFailed(fullImageName, error);
|
|
5634
5838
|
} finally {
|
|
5635
5839
|
cleanup?.();
|
|
5636
5840
|
}
|
|
@@ -5641,17 +5845,14 @@ async function buildDockerImage(imageName, options) {
|
|
|
5641
5845
|
async function pushDockerImage(imageName, options) {
|
|
5642
5846
|
const tag = options.tag ?? "latest";
|
|
5643
5847
|
const registry = options.registry;
|
|
5644
|
-
if (!registry) throw new
|
|
5645
|
-
const fullImageName = `${registry}/${imageName}:${tag}
|
|
5848
|
+
if (!registry) throw new PushNeedsRegistry(imageName);
|
|
5849
|
+
const fullImageName = validateImageRef(`${registry}/${imageName}:${tag}`);
|
|
5646
5850
|
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5647
5851
|
try {
|
|
5648
|
-
|
|
5649
|
-
cwd: process.cwd(),
|
|
5650
|
-
stdio: "inherit"
|
|
5651
|
-
});
|
|
5852
|
+
await run("docker", ["push", fullImageName], { cwd: process.cwd() });
|
|
5652
5853
|
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5653
5854
|
} catch (error) {
|
|
5654
|
-
throw new
|
|
5855
|
+
throw new DockerPushFailed(fullImageName, error);
|
|
5655
5856
|
}
|
|
5656
5857
|
}
|
|
5657
5858
|
/**
|
|
@@ -5783,6 +5984,11 @@ function printRunInstructions(workspace) {
|
|
|
5783
5984
|
//#region src/deploy/docker.ts
|
|
5784
5985
|
const logger$5 = console;
|
|
5785
5986
|
/**
|
|
5987
|
+
* The id the Dockerfile templates mount the encrypted credentials under:
|
|
5988
|
+
* `RUN --mount=type=secret,id=gkm_credentials`.
|
|
5989
|
+
*/
|
|
5990
|
+
const CREDENTIALS_SECRET_ID = "gkm_credentials";
|
|
5991
|
+
/**
|
|
5786
5992
|
* Get the full image reference
|
|
5787
5993
|
*/
|
|
5788
5994
|
function getImageRef(registry, imageName, tag) {
|
|
@@ -5790,31 +5996,69 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5790
5996
|
return `${imageName}:${tag}`;
|
|
5791
5997
|
}
|
|
5792
5998
|
/**
|
|
5999
|
+
* The `docker build` argv for an image.
|
|
6000
|
+
*
|
|
6001
|
+
* One element per value, so nothing in a ref or a build arg is read by a
|
|
6002
|
+
* shell, and `--opt=value` rather than `--opt value`, so a value that starts
|
|
6003
|
+
* with `-` cannot be taken for one of docker's own flags.
|
|
6004
|
+
*
|
|
6005
|
+
* @internal Exported for testing
|
|
6006
|
+
*/
|
|
6007
|
+
function dockerBuildArgs(options) {
|
|
6008
|
+
const { dockerfilePath, imageRef, buildArgs = [], credentialsFile } = options;
|
|
6009
|
+
return [
|
|
6010
|
+
"build",
|
|
6011
|
+
"--platform=linux/amd64",
|
|
6012
|
+
`--file=${dockerfilePath}`,
|
|
6013
|
+
`--tag=${imageRef}`,
|
|
6014
|
+
...buildArgs.map((arg) => `--build-arg=${arg}`),
|
|
6015
|
+
...credentialsFile ? [`--secret=id=${CREDENTIALS_SECRET_ID},src=${credentialsFile}`] : [],
|
|
6016
|
+
"."
|
|
6017
|
+
];
|
|
6018
|
+
}
|
|
6019
|
+
/**
|
|
6020
|
+
* Write the encrypted credentials where `docker build --secret` reads them.
|
|
6021
|
+
*
|
|
6022
|
+
* Two lines — ciphertext, then IV — so a template can split them with `sed`
|
|
6023
|
+
* in any base image. Owner-only, in a directory of its own, and the returned
|
|
6024
|
+
* cleanup removes both whatever the build did.
|
|
6025
|
+
*
|
|
6026
|
+
* @internal Exported for testing
|
|
6027
|
+
*/
|
|
6028
|
+
async function writeCredentialsFile(credentials) {
|
|
6029
|
+
const dir = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "gkm-credentials-"));
|
|
6030
|
+
const path = (0, node_path.join)(dir, CREDENTIALS_SECRET_ID);
|
|
6031
|
+
await (0, node_fs_promises.writeFile)(path, `${credentials.encrypted}\n${credentials.iv}\n`, { mode: 384 });
|
|
6032
|
+
await (0, node_fs_promises.chmod)(path, 384);
|
|
6033
|
+
return {
|
|
6034
|
+
path,
|
|
6035
|
+
cleanup: () => (0, node_fs_promises.rm)(dir, {
|
|
6036
|
+
recursive: true,
|
|
6037
|
+
force: true
|
|
6038
|
+
})
|
|
6039
|
+
};
|
|
6040
|
+
}
|
|
6041
|
+
/**
|
|
5793
6042
|
* Build Docker image
|
|
5794
6043
|
* @param imageRef - Full image reference (registry/name:tag)
|
|
5795
|
-
* @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
|
|
5796
6044
|
* @param buildArgs - Build arguments to pass to docker build
|
|
6045
|
+
* @param credentials - Encrypted credentials, passed as a build secret
|
|
5797
6046
|
*/
|
|
5798
|
-
async function buildImage(imageRef,
|
|
6047
|
+
async function buildImage(imageRef, buildArgs, credentials) {
|
|
5799
6048
|
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5800
6049
|
const cwd = process.cwd();
|
|
5801
6050
|
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5802
6051
|
await dockerCommand({ slim: true });
|
|
5803
6052
|
const dockerfilePath = (0, node_path.join)(cwd, ".gkm", "docker", "Dockerfile");
|
|
5804
|
-
const
|
|
5805
|
-
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
6053
|
+
const secret = credentials ? await writeCredentialsFile(credentials) : void 0;
|
|
5806
6054
|
try {
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
].filter(Boolean).join(" ");
|
|
5815
|
-
(0, node_child_process.execSync)(cmd, {
|
|
5816
|
-
cwd: buildCwd,
|
|
5817
|
-
stdio: "inherit",
|
|
6055
|
+
await run("docker", dockerBuildArgs({
|
|
6056
|
+
dockerfilePath,
|
|
6057
|
+
imageRef,
|
|
6058
|
+
buildArgs,
|
|
6059
|
+
credentialsFile: secret?.path
|
|
6060
|
+
}), {
|
|
6061
|
+
cwd,
|
|
5818
6062
|
env: {
|
|
5819
6063
|
...process.env,
|
|
5820
6064
|
DOCKER_BUILDKIT: "1"
|
|
@@ -5822,7 +6066,9 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5822
6066
|
});
|
|
5823
6067
|
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5824
6068
|
} catch (error) {
|
|
5825
|
-
throw new
|
|
6069
|
+
throw new DockerBuildFailed(imageRef, error);
|
|
6070
|
+
} finally {
|
|
6071
|
+
await secret?.cleanup();
|
|
5826
6072
|
}
|
|
5827
6073
|
}
|
|
5828
6074
|
/**
|
|
@@ -5831,23 +6077,20 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5831
6077
|
async function pushImage(imageRef) {
|
|
5832
6078
|
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5833
6079
|
try {
|
|
5834
|
-
|
|
5835
|
-
cwd: process.cwd(),
|
|
5836
|
-
stdio: "inherit"
|
|
5837
|
-
});
|
|
6080
|
+
await run("docker", ["push", imageRef], { cwd: process.cwd() });
|
|
5838
6081
|
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5839
6082
|
} catch (error) {
|
|
5840
|
-
throw new
|
|
6083
|
+
throw new DockerPushFailed(imageRef, error);
|
|
5841
6084
|
}
|
|
5842
6085
|
}
|
|
5843
6086
|
/**
|
|
5844
6087
|
* Deploy using Docker (build and optionally push image)
|
|
5845
6088
|
*/
|
|
5846
6089
|
async function deployDocker(options) {
|
|
5847
|
-
const { stage, tag, skipPush, masterKey, config, buildArgs } = options;
|
|
6090
|
+
const { stage, tag, skipPush, masterKey, config, buildArgs, credentials } = options;
|
|
5848
6091
|
const imageName = config.imageName;
|
|
5849
|
-
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5850
|
-
await buildImage(imageRef,
|
|
6092
|
+
const imageRef = validateImageRef(getImageRef(config.registry, imageName, tag));
|
|
6093
|
+
await buildImage(imageRef, buildArgs, credentials);
|
|
5851
6094
|
if (!skipPush) {
|
|
5852
6095
|
if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
5853
6096
|
else await pushImage(imageRef);
|
|
@@ -5856,12 +6099,7 @@ async function deployDocker(options) {
|
|
|
5856
6099
|
logger$5.log(`\n📋 Deployment details:`);
|
|
5857
6100
|
logger$5.log(` Image: ${imageRef}`);
|
|
5858
6101
|
logger$5.log(` Stage: ${stage}`);
|
|
5859
|
-
if (masterKey) {
|
|
5860
|
-
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
5861
|
-
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
5862
|
-
logger$5.log("\n Example docker run:");
|
|
5863
|
-
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
5864
|
-
}
|
|
6102
|
+
if (masterKey) logger$5.log(`\n🔐 Run the container with GKM_MASTER_KEY set to the key with fingerprint ${keyFingerprint(masterKey)}.`);
|
|
5865
6103
|
return {
|
|
5866
6104
|
imageRef,
|
|
5867
6105
|
masterKey
|
|
@@ -5886,9 +6124,53 @@ function applicationName(stage, project, app) {
|
|
|
5886
6124
|
//#endregion
|
|
5887
6125
|
//#region src/deploy/dokploy-api.ts
|
|
5888
6126
|
var dokploy_api_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
6127
|
+
DEFAULT_DOKPLOY_TIMEOUT_MS: () => DEFAULT_DOKPLOY_TIMEOUT_MS,
|
|
5889
6128
|
DokployApi: () => DokployApi,
|
|
5890
|
-
DokployApiError: () => DokployApiError
|
|
6129
|
+
DokployApiError: () => DokployApiError,
|
|
6130
|
+
DokployRequestTimedOut: () => DokployRequestTimedOut
|
|
5891
6131
|
});
|
|
6132
|
+
/** Per request, so a stalled server fails the deploy, not the CI job. */
|
|
6133
|
+
const DEFAULT_DOKPLOY_TIMEOUT_MS = 3e4;
|
|
6134
|
+
/**
|
|
6135
|
+
* Dokploy accepted the connection but did not answer in time.
|
|
6136
|
+
*
|
|
6137
|
+
* Not retried: a request that arrived and stalled may still be acted on, and
|
|
6138
|
+
* sending it again could repeat a deploy or a create the server is already
|
|
6139
|
+
* doing.
|
|
6140
|
+
*/
|
|
6141
|
+
var DokployRequestTimedOut = class extends Error {
|
|
6142
|
+
baseUrl;
|
|
6143
|
+
endpoint;
|
|
6144
|
+
timeoutMs;
|
|
6145
|
+
constructor(baseUrl, endpoint, timeoutMs) {
|
|
6146
|
+
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.`);
|
|
6147
|
+
this.baseUrl = baseUrl;
|
|
6148
|
+
this.endpoint = endpoint;
|
|
6149
|
+
this.timeoutMs = timeoutMs;
|
|
6150
|
+
this.name = "DokployRequestTimedOut";
|
|
6151
|
+
}
|
|
6152
|
+
};
|
|
6153
|
+
/**
|
|
6154
|
+
* Waits `ms` between retries, or rejects with the signal's reason the moment
|
|
6155
|
+
* it aborts — a caller who gave up should not sit through the backoff.
|
|
6156
|
+
*/
|
|
6157
|
+
function pause(ms, signal) {
|
|
6158
|
+
return new Promise((resolve, reject) => {
|
|
6159
|
+
if (signal?.aborted) {
|
|
6160
|
+
reject(signal.reason);
|
|
6161
|
+
return;
|
|
6162
|
+
}
|
|
6163
|
+
const onAbort = () => {
|
|
6164
|
+
clearTimeout(timer);
|
|
6165
|
+
reject(signal?.reason);
|
|
6166
|
+
};
|
|
6167
|
+
const timer = setTimeout(() => {
|
|
6168
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6169
|
+
resolve();
|
|
6170
|
+
}, ms);
|
|
6171
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
5892
6174
|
var DokployApiError = class extends Error {
|
|
5893
6175
|
status;
|
|
5894
6176
|
statusText;
|
|
@@ -5907,9 +6189,13 @@ var DokployApiError = class extends Error {
|
|
|
5907
6189
|
var DokployApi = class {
|
|
5908
6190
|
baseUrl;
|
|
5909
6191
|
token;
|
|
6192
|
+
timeoutMs;
|
|
6193
|
+
signal;
|
|
5910
6194
|
constructor(options) {
|
|
5911
6195
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
5912
6196
|
this.token = options.token;
|
|
6197
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
6198
|
+
this.signal = options.signal;
|
|
5913
6199
|
}
|
|
5914
6200
|
/**
|
|
5915
6201
|
* Make a GET request to the Dokploy API
|
|
@@ -5929,26 +6215,51 @@ var DokployApi = class {
|
|
|
5929
6215
|
async request(method, endpoint, body) {
|
|
5930
6216
|
const url = `${this.baseUrl}/api/${endpoint}`;
|
|
5931
6217
|
let response;
|
|
6218
|
+
let timeout;
|
|
5932
6219
|
let lastError;
|
|
5933
|
-
for (let attempt = 1; attempt <= 3; attempt++)
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
6220
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
6221
|
+
this.signal?.throwIfAborted();
|
|
6222
|
+
timeout = AbortSignal.timeout(this.timeoutMs);
|
|
6223
|
+
try {
|
|
6224
|
+
response = await fetch(url, {
|
|
6225
|
+
method,
|
|
6226
|
+
headers: {
|
|
6227
|
+
"Content-Type": "application/json",
|
|
6228
|
+
"x-api-key": this.token
|
|
6229
|
+
},
|
|
6230
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
6231
|
+
signal: this.signal ? AbortSignal.any([this.signal, timeout]) : timeout
|
|
6232
|
+
});
|
|
6233
|
+
break;
|
|
6234
|
+
} catch (error) {
|
|
6235
|
+
const stopped = this.stopped(timeout, endpoint);
|
|
6236
|
+
if (stopped) throw stopped;
|
|
6237
|
+
lastError = error;
|
|
6238
|
+
if (attempt === 3) break;
|
|
6239
|
+
await pause(attempt * 750, this.signal);
|
|
6240
|
+
}
|
|
5947
6241
|
}
|
|
5948
|
-
if (!response) {
|
|
6242
|
+
if (!response || !timeout) {
|
|
5949
6243
|
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
5950
6244
|
throw new DokployApiError(`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`, 0, "Network error");
|
|
5951
6245
|
}
|
|
6246
|
+
try {
|
|
6247
|
+
return await this.read(response);
|
|
6248
|
+
} catch (error) {
|
|
6249
|
+
throw this.stopped(timeout, endpoint) ?? error;
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
/**
|
|
6253
|
+
* What a request cut short should reject with: the caller's own reason
|
|
6254
|
+
* when they aborted, `DokployRequestTimedOut` when the deadline passed,
|
|
6255
|
+
* nothing when it was neither.
|
|
6256
|
+
*/
|
|
6257
|
+
stopped(timeout, endpoint) {
|
|
6258
|
+
if (this.signal?.aborted) return this.signal.reason;
|
|
6259
|
+
if (timeout.aborted) return new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);
|
|
6260
|
+
}
|
|
6261
|
+
/** Turns a response into its JSON body, or a `DokployApiError`. */
|
|
6262
|
+
async read(response) {
|
|
5952
6263
|
if (!response.ok) {
|
|
5953
6264
|
let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
|
|
5954
6265
|
let issues;
|
|
@@ -6737,7 +7048,11 @@ function isStateProvider(value) {
|
|
|
6737
7048
|
*
|
|
6738
7049
|
* - 'local': LocalStateProvider (default)
|
|
6739
7050
|
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
7051
|
+
* - 's3': S3StateStore behind the provider interface
|
|
6740
7052
|
* - Custom: Use provided StateProvider implementation
|
|
7053
|
+
*
|
|
7054
|
+
* Deploy moves to `createStateStore` (locks, versions, per-resource records);
|
|
7055
|
+
* this stays for the call sites that read and write whole states.
|
|
6741
7056
|
*/
|
|
6742
7057
|
async function createStateProvider(options) {
|
|
6743
7058
|
const { config, workspaceRoot, workspaceName } = options;
|
|
@@ -6745,6 +7060,11 @@ async function createStateProvider(options) {
|
|
|
6745
7060
|
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-7HR6SS2t.cjs"));
|
|
6746
7061
|
return new LocalStateProvider(workspaceRoot);
|
|
6747
7062
|
}
|
|
7063
|
+
const { isStateStore } = await Promise.resolve().then(() => StateStore_exports);
|
|
7064
|
+
if (isStateStore(config.provider)) {
|
|
7065
|
+
const { StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7066
|
+
return new StateStoreProvider(config.provider);
|
|
7067
|
+
}
|
|
6748
7068
|
if (isStateProvider(config.provider)) return config.provider;
|
|
6749
7069
|
const provider = config.provider;
|
|
6750
7070
|
if (provider === "local") {
|
|
@@ -6752,7 +7072,10 @@ async function createStateProvider(options) {
|
|
|
6752
7072
|
return new LocalStateProvider(workspaceRoot);
|
|
6753
7073
|
}
|
|
6754
7074
|
if (provider === "ssm") {
|
|
6755
|
-
if (!workspaceName)
|
|
7075
|
+
if (!workspaceName) {
|
|
7076
|
+
const { StateStoreNeedsWorkspaceName } = await Promise.resolve().then(() => StateStore_exports);
|
|
7077
|
+
throw new StateStoreNeedsWorkspaceName("ssm");
|
|
7078
|
+
}
|
|
6756
7079
|
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-7HR6SS2t.cjs"));
|
|
6757
7080
|
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-VrQ-yeqd.cjs"));
|
|
6758
7081
|
const { CachedStateProvider } = await Promise.resolve().then(() => CachedStateProvider_exports);
|
|
@@ -6764,47 +7087,12 @@ async function createStateProvider(options) {
|
|
|
6764
7087
|
profile: ssmConfig.profile
|
|
6765
7088
|
}), local);
|
|
6766
7089
|
}
|
|
6767
|
-
|
|
6768
|
-
}
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
generateDefineOptions: () => generateDefineOptions
|
|
6774
|
-
});
|
|
6775
|
-
/** AES-256-GCM configuration */
|
|
6776
|
-
const ALGORITHM = "aes-256-gcm";
|
|
6777
|
-
const KEY_LENGTH = 32;
|
|
6778
|
-
const IV_LENGTH = 12;
|
|
6779
|
-
/**
|
|
6780
|
-
* Encrypt secrets for embedding in a bundle.
|
|
6781
|
-
* Uses AES-256-GCM with a randomly generated ephemeral key.
|
|
6782
|
-
*
|
|
6783
|
-
* @param secrets - Key-value pairs to encrypt
|
|
6784
|
-
* @returns Encrypted payload with ephemeral master key
|
|
6785
|
-
*/
|
|
6786
|
-
function encryptSecrets(secrets) {
|
|
6787
|
-
const masterKey = (0, node_crypto.randomBytes)(KEY_LENGTH);
|
|
6788
|
-
const iv = (0, node_crypto.randomBytes)(IV_LENGTH);
|
|
6789
|
-
const plaintext = JSON.stringify(secrets);
|
|
6790
|
-
const cipher = (0, node_crypto.createCipheriv)(ALGORITHM, masterKey, iv);
|
|
6791
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
6792
|
-
const authTag = cipher.getAuthTag();
|
|
6793
|
-
return {
|
|
6794
|
-
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
6795
|
-
iv: iv.toString("hex"),
|
|
6796
|
-
masterKey: masterKey.toString("hex")
|
|
6797
|
-
};
|
|
6798
|
-
}
|
|
6799
|
-
/**
|
|
6800
|
-
* Generate the define options for tsdown/esbuild.
|
|
6801
|
-
* These will be injected at build time.
|
|
6802
|
-
*/
|
|
6803
|
-
function generateDefineOptions(payload) {
|
|
6804
|
-
return {
|
|
6805
|
-
__GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
|
|
6806
|
-
__GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
|
|
6807
|
-
};
|
|
7090
|
+
if (provider === "s3") {
|
|
7091
|
+
const { createStateStore, StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7092
|
+
return new StateStoreProvider(await createStateStore(options));
|
|
7093
|
+
}
|
|
7094
|
+
const { UnknownStateProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7095
|
+
throw new UnknownStateProvider(config);
|
|
6808
7096
|
}
|
|
6809
7097
|
//#endregion
|
|
6810
7098
|
//#region src/deploy/secrets.ts
|
|
@@ -7762,12 +8050,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7762
8050
|
}
|
|
7763
8051
|
setApplicationId(state, appName, application.applicationId);
|
|
7764
8052
|
const appSecrets = encryptedSecrets.get(appName);
|
|
7765
|
-
const
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
}
|
|
8053
|
+
const credentials = appSecrets && appSecrets.secretCount > 0 ? {
|
|
8054
|
+
encrypted: appSecrets.payload.encrypted,
|
|
8055
|
+
iv: appSecrets.payload.iv
|
|
8056
|
+
} : void 0;
|
|
8057
|
+
if (appSecrets && credentials) logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7771
8058
|
const imageName = `${workspace.name}-${appName}`;
|
|
7772
8059
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7773
8060
|
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
@@ -7780,7 +8067,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7780
8067
|
imageName,
|
|
7781
8068
|
appName
|
|
7782
8069
|
},
|
|
7783
|
-
|
|
8070
|
+
credentials
|
|
7784
8071
|
});
|
|
7785
8072
|
const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
|
|
7786
8073
|
const dependencyUrls = {};
|
|
@@ -8162,6 +8449,11 @@ async function deployListCommand(options) {
|
|
|
8162
8449
|
var CachedStateProvider_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ CachedStateProvider: () => CachedStateProvider });
|
|
8163
8450
|
/**
|
|
8164
8451
|
* Cached state provider that wraps a remote provider with local cache.
|
|
8452
|
+
*
|
|
8453
|
+
* @deprecated for deploys: a read prefers the local copy, so a stale one wins
|
|
8454
|
+
* over the remote without warning, and a write overwrites the remote blindly.
|
|
8455
|
+
* Deploy uses `createStateStore`, which reads SSM directly and writes
|
|
8456
|
+
* conditionally. Kept for `gkm state:pull | push | diff`.
|
|
8165
8457
|
*/
|
|
8166
8458
|
var CachedStateProvider = class {
|
|
8167
8459
|
remote;
|
|
@@ -8212,29 +8504,438 @@ var CachedStateProvider = class {
|
|
|
8212
8504
|
}
|
|
8213
8505
|
};
|
|
8214
8506
|
//#endregion
|
|
8215
|
-
//#region src/deploy/
|
|
8507
|
+
//#region src/deploy/StateStore.ts
|
|
8216
8508
|
/**
|
|
8217
|
-
*
|
|
8509
|
+
* Deploy state store.
|
|
8218
8510
|
*
|
|
8219
|
-
*
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
*
|
|
8223
|
-
*
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8511
|
+
* `StateProvider` reads and writes a whole stage at once, with nothing to stop
|
|
8512
|
+
* two runs of the same stage from overwriting each other and nowhere to record
|
|
8513
|
+
* a resource the moment it exists. A `StateStore` adds the three things a
|
|
8514
|
+
* deploy needs to be safe against crashes and concurrent runs:
|
|
8515
|
+
*
|
|
8516
|
+
* - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the
|
|
8517
|
+
* holder, instead of racing the first.
|
|
8518
|
+
* - Versioned `read` and conditional `write`: a write names the version it was
|
|
8519
|
+
* based on, and raises `StateVersionConflict` when the stored state moved on
|
|
8520
|
+
* underneath it, so nothing is lost silently even without the lock.
|
|
8521
|
+
* - Per-resource records: `putResource` journals one resource at a time, so a
|
|
8522
|
+
* run that dies half way leaves behind the ids of everything it created.
|
|
8523
|
+
*
|
|
8524
|
+
* Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
|
|
8525
|
+
* custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
|
|
8526
|
+
* working but cannot lock.
|
|
8527
|
+
*/
|
|
8528
|
+
var StateStore_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
8529
|
+
DocumentStateStore: () => DocumentStateStore,
|
|
8530
|
+
STATE_SCHEMA_VERSION: () => 2,
|
|
8531
|
+
StageStateMissing: () => StageStateMissing,
|
|
8532
|
+
StateLocked: () => StateLocked,
|
|
8533
|
+
StateSchemaTooNew: () => StateSchemaTooNew,
|
|
8534
|
+
StateStoreBusy: () => StateStoreBusy,
|
|
8535
|
+
StateStoreNeedsWorkspaceName: () => StateStoreNeedsWorkspaceName,
|
|
8536
|
+
StateStoreProvider: () => StateStoreProvider,
|
|
8537
|
+
StateStoreWithoutLocking: () => StateStoreWithoutLocking,
|
|
8538
|
+
StateUnreadable: () => StateUnreadable,
|
|
8539
|
+
StateVersionConflict: () => StateVersionConflict,
|
|
8540
|
+
UnknownStateProvider: () => UnknownStateProvider,
|
|
8541
|
+
contentVersion: () => contentVersion,
|
|
8542
|
+
createStateStore: () => createStateStore,
|
|
8543
|
+
decodeDocument: () => decodeDocument,
|
|
8544
|
+
encodeDocument: () => encodeDocument,
|
|
8545
|
+
isStateStore: () => isStateStore,
|
|
8546
|
+
migrateV1: () => migrateV1,
|
|
8547
|
+
newLockHolder: () => newLockHolder
|
|
8548
|
+
});
|
|
8549
|
+
var StateLocked = class extends Error {
|
|
8550
|
+
stage;
|
|
8551
|
+
holder;
|
|
8552
|
+
location;
|
|
8553
|
+
constructor(stage, holder, location) {
|
|
8554
|
+
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";
|
|
8555
|
+
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}\`.`);
|
|
8556
|
+
this.stage = stage;
|
|
8557
|
+
this.holder = holder;
|
|
8558
|
+
this.location = location;
|
|
8559
|
+
this.name = "StateLocked";
|
|
8232
8560
|
}
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8561
|
+
};
|
|
8562
|
+
var StateVersionConflict = class extends Error {
|
|
8563
|
+
stage;
|
|
8564
|
+
expectedVersion;
|
|
8565
|
+
actualVersion;
|
|
8566
|
+
constructor(stage, expectedVersion, actualVersion) {
|
|
8567
|
+
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.`);
|
|
8568
|
+
this.stage = stage;
|
|
8569
|
+
this.expectedVersion = expectedVersion;
|
|
8570
|
+
this.actualVersion = actualVersion;
|
|
8571
|
+
this.name = "StateVersionConflict";
|
|
8572
|
+
}
|
|
8573
|
+
};
|
|
8574
|
+
var StageStateMissing = class extends Error {
|
|
8575
|
+
stage;
|
|
8576
|
+
constructor(stage) {
|
|
8577
|
+
super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
|
|
8578
|
+
this.stage = stage;
|
|
8579
|
+
this.name = "StageStateMissing";
|
|
8580
|
+
}
|
|
8581
|
+
};
|
|
8582
|
+
var StateUnreadable = class extends Error {
|
|
8583
|
+
stage;
|
|
8584
|
+
location;
|
|
8585
|
+
constructor(stage, location, cause) {
|
|
8586
|
+
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 });
|
|
8587
|
+
this.stage = stage;
|
|
8588
|
+
this.location = location;
|
|
8589
|
+
this.name = "StateUnreadable";
|
|
8590
|
+
}
|
|
8591
|
+
};
|
|
8592
|
+
var StateSchemaTooNew = class extends Error {
|
|
8593
|
+
stage;
|
|
8594
|
+
schemaVersion;
|
|
8595
|
+
constructor(stage, schemaVersion) {
|
|
8596
|
+
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.`);
|
|
8597
|
+
this.stage = stage;
|
|
8598
|
+
this.schemaVersion = schemaVersion;
|
|
8599
|
+
this.name = "StateSchemaTooNew";
|
|
8600
|
+
}
|
|
8601
|
+
};
|
|
8602
|
+
/**
|
|
8603
|
+
* Not thrown: emitted (once per store) when a custom `StateProvider` stands
|
|
8604
|
+
* in for a store, so a deploy that cannot be protected from a concurrent one
|
|
8605
|
+
* says so.
|
|
8606
|
+
*/
|
|
8607
|
+
var StateStoreWithoutLocking = class extends Error {
|
|
8608
|
+
provider;
|
|
8609
|
+
constructor(provider) {
|
|
8610
|
+
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.`);
|
|
8611
|
+
this.provider = provider;
|
|
8612
|
+
this.name = "StateStoreWithoutLocking";
|
|
8613
|
+
}
|
|
8614
|
+
};
|
|
8615
|
+
var StateStoreNeedsWorkspaceName = class extends Error {
|
|
8616
|
+
provider;
|
|
8617
|
+
constructor(provider) {
|
|
8618
|
+
super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
|
|
8619
|
+
this.provider = provider;
|
|
8620
|
+
this.name = "StateStoreNeedsWorkspaceName";
|
|
8621
|
+
}
|
|
8622
|
+
};
|
|
8623
|
+
var UnknownStateProvider = class extends Error {
|
|
8624
|
+
config;
|
|
8625
|
+
constructor(config) {
|
|
8626
|
+
super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
|
|
8627
|
+
this.config = config;
|
|
8628
|
+
this.name = "UnknownStateProvider";
|
|
8629
|
+
}
|
|
8630
|
+
};
|
|
8631
|
+
/** A lock or write that could not get at the state backend in time. */
|
|
8632
|
+
var StateStoreBusy = class extends Error {
|
|
8633
|
+
stage;
|
|
8634
|
+
location;
|
|
8635
|
+
constructor(stage, location) {
|
|
8636
|
+
super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
|
|
8637
|
+
this.stage = stage;
|
|
8638
|
+
this.location = location;
|
|
8639
|
+
this.name = "StateStoreBusy";
|
|
8640
|
+
}
|
|
8641
|
+
};
|
|
8642
|
+
function decodeDocument(stage, body, location) {
|
|
8643
|
+
let parsed;
|
|
8644
|
+
try {
|
|
8645
|
+
parsed = JSON.parse(body);
|
|
8646
|
+
} catch (error) {
|
|
8647
|
+
throw new StateUnreadable(stage, location, error);
|
|
8648
|
+
}
|
|
8649
|
+
if (typeof parsed !== "object" || parsed === null) throw new StateUnreadable(stage, location, parsed);
|
|
8650
|
+
const schemaVersion = parsed.schemaVersion;
|
|
8651
|
+
if (schemaVersion === void 0) return {
|
|
8652
|
+
schemaVersion: 1,
|
|
8653
|
+
state: parsed
|
|
8654
|
+
};
|
|
8655
|
+
if (schemaVersion === 2) return {
|
|
8656
|
+
schemaVersion: 2,
|
|
8657
|
+
document: parsed
|
|
8658
|
+
};
|
|
8659
|
+
throw new StateSchemaTooNew(stage, schemaVersion);
|
|
8660
|
+
}
|
|
8661
|
+
function encodeDocument(document) {
|
|
8662
|
+
return JSON.stringify(document, null, 2);
|
|
8663
|
+
}
|
|
8664
|
+
/**
|
|
8665
|
+
* v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
|
|
8666
|
+
* so the first journaled deploy after an upgrade adopts them rather than
|
|
8667
|
+
* treating them as unknown.
|
|
8668
|
+
*/
|
|
8669
|
+
function migrateV1(stage, state) {
|
|
8670
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8671
|
+
const resources = {};
|
|
8672
|
+
const seed = (type, name, id) => {
|
|
8673
|
+
if (!id) return;
|
|
8674
|
+
const key = name ? `${type}:${name}` : type;
|
|
8675
|
+
resources[key] = {
|
|
8676
|
+
key,
|
|
8677
|
+
type,
|
|
8678
|
+
id,
|
|
8679
|
+
status: "ready",
|
|
8680
|
+
updatedAt: now
|
|
8681
|
+
};
|
|
8682
|
+
};
|
|
8683
|
+
seed("project", null, state.projectId);
|
|
8684
|
+
seed("environment", null, state.environmentId);
|
|
8685
|
+
for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
|
|
8686
|
+
seed("postgres", null, state.services?.postgresId);
|
|
8687
|
+
seed("redis", null, state.services?.redisId);
|
|
8688
|
+
seed("backup-destination", null, state.backups?.destinationId);
|
|
8689
|
+
return {
|
|
8690
|
+
schemaVersion: 2,
|
|
8691
|
+
stage,
|
|
8692
|
+
serial: 1,
|
|
8693
|
+
state,
|
|
8694
|
+
resources,
|
|
8695
|
+
updatedAt: now
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
function newLockHolder(options) {
|
|
8699
|
+
let owner = "unknown";
|
|
8700
|
+
try {
|
|
8701
|
+
owner = (0, node_os.userInfo)().username;
|
|
8702
|
+
} catch {}
|
|
8703
|
+
return {
|
|
8704
|
+
id: (0, node_crypto.randomUUID)(),
|
|
8705
|
+
owner,
|
|
8706
|
+
host: (0, node_os.hostname)(),
|
|
8707
|
+
pid: process.pid,
|
|
8708
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8709
|
+
operation: options?.operation
|
|
8710
|
+
};
|
|
8711
|
+
}
|
|
8712
|
+
function contentVersion(body) {
|
|
8713
|
+
return (0, node_crypto.createHash)("sha256").update(body).digest("hex").slice(0, 16);
|
|
8714
|
+
}
|
|
8715
|
+
/**
|
|
8716
|
+
* A store over a backend that can read and conditionally write one blob per
|
|
8717
|
+
* stage, and create a lock object only if it does not exist. Migration and
|
|
8718
|
+
* resource records are the same for every such backend, so they live here.
|
|
8719
|
+
*/
|
|
8720
|
+
var DocumentStateStore = class {
|
|
8721
|
+
async lock(stage, options) {
|
|
8722
|
+
const holder = newLockHolder(options);
|
|
8723
|
+
await this.createLock(stage, holder);
|
|
8724
|
+
let released = false;
|
|
8725
|
+
return {
|
|
8726
|
+
stage,
|
|
8727
|
+
holder,
|
|
8728
|
+
release: async () => {
|
|
8729
|
+
if (released) return;
|
|
8730
|
+
released = true;
|
|
8731
|
+
await this.removeLock(stage, holder);
|
|
8732
|
+
}
|
|
8733
|
+
};
|
|
8734
|
+
}
|
|
8735
|
+
async forceUnlock(stage) {
|
|
8736
|
+
const holder = await this.readLock(stage);
|
|
8737
|
+
await this.removeLock(stage, null);
|
|
8738
|
+
return holder;
|
|
8739
|
+
}
|
|
8740
|
+
async read(stage) {
|
|
8741
|
+
const current = await this.readDocument(stage);
|
|
8742
|
+
if (!current) return null;
|
|
8743
|
+
return {
|
|
8744
|
+
state: current.document.state,
|
|
8745
|
+
resources: current.document.resources,
|
|
8746
|
+
version: current.version
|
|
8747
|
+
};
|
|
8748
|
+
}
|
|
8749
|
+
async write(stage, state, options) {
|
|
8750
|
+
if (options.expectedVersion === null) {
|
|
8751
|
+
const document = {
|
|
8752
|
+
schemaVersion: 2,
|
|
8753
|
+
stage,
|
|
8754
|
+
serial: 1,
|
|
8755
|
+
state,
|
|
8756
|
+
resources: {},
|
|
8757
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8758
|
+
};
|
|
8759
|
+
return this.writeRaw(stage, encodeDocument(document), null);
|
|
8760
|
+
}
|
|
8761
|
+
return this.mutate(stage, options.expectedVersion, (document) => ({
|
|
8762
|
+
...document,
|
|
8763
|
+
state
|
|
8764
|
+
}));
|
|
8765
|
+
}
|
|
8766
|
+
async putResource(stage, record, options) {
|
|
8767
|
+
return this.mutate(stage, options?.expectedVersion, (document) => ({
|
|
8768
|
+
...document,
|
|
8769
|
+
resources: {
|
|
8770
|
+
...document.resources,
|
|
8771
|
+
[record.key]: {
|
|
8772
|
+
...record,
|
|
8773
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
}));
|
|
8777
|
+
}
|
|
8778
|
+
async deleteResource(stage, key, options) {
|
|
8779
|
+
return this.mutate(stage, options?.expectedVersion, (document) => {
|
|
8780
|
+
const { [key]: _removed, ...resources } = document.resources;
|
|
8781
|
+
return {
|
|
8782
|
+
...document,
|
|
8783
|
+
resources
|
|
8784
|
+
};
|
|
8785
|
+
});
|
|
8786
|
+
}
|
|
8787
|
+
async mutate(stage, expectedVersion, change) {
|
|
8788
|
+
const current = await this.readDocument(stage);
|
|
8789
|
+
if (!current) {
|
|
8790
|
+
if (expectedVersion !== void 0) throw new StateVersionConflict(stage, expectedVersion, null);
|
|
8791
|
+
throw new StageStateMissing(stage);
|
|
8792
|
+
}
|
|
8793
|
+
if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
|
|
8794
|
+
const next = change(current.document);
|
|
8795
|
+
next.serial = current.document.serial + 1;
|
|
8796
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8797
|
+
return this.writeRaw(stage, encodeDocument(next), current.version);
|
|
8798
|
+
}
|
|
8799
|
+
/**
|
|
8800
|
+
* Reads the stage's document, migrating v1 in place on the way. The
|
|
8801
|
+
* migration is itself a conditional write: if another run migrated (or
|
|
8802
|
+
* wrote) first, its result is read instead.
|
|
8803
|
+
*/
|
|
8804
|
+
async readDocument(stage, attempt = 0) {
|
|
8805
|
+
const raw = await this.readRaw(stage);
|
|
8806
|
+
if (!raw) return null;
|
|
8807
|
+
const decoded = decodeDocument(stage, raw.body, this.location(stage));
|
|
8808
|
+
if (decoded.schemaVersion === 2) return {
|
|
8809
|
+
document: decoded.document,
|
|
8810
|
+
version: raw.version
|
|
8811
|
+
};
|
|
8812
|
+
await this.writeV1Backup(stage, raw.body);
|
|
8813
|
+
const document = migrateV1(stage, decoded.state);
|
|
8814
|
+
try {
|
|
8815
|
+
return {
|
|
8816
|
+
document,
|
|
8817
|
+
version: await this.writeRaw(stage, encodeDocument(document), raw.version)
|
|
8818
|
+
};
|
|
8819
|
+
} catch (error) {
|
|
8820
|
+
if (error instanceof StateVersionConflict && attempt < 2) return this.readDocument(stage, attempt + 1);
|
|
8821
|
+
throw error;
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
};
|
|
8825
|
+
function isStateStore(value) {
|
|
8826
|
+
return typeof value === "object" && value !== null && typeof value.lock === "function" && typeof value.read === "function" && typeof value.write === "function" && typeof value.putResource === "function";
|
|
8827
|
+
}
|
|
8828
|
+
/**
|
|
8829
|
+
* The store for a workspace's `state` config — the same config
|
|
8830
|
+
* `createStateProvider` reads:
|
|
8831
|
+
*
|
|
8832
|
+
* - none / `'local'`: `LocalStateStore` under `.gkm/`
|
|
8833
|
+
* - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
|
|
8834
|
+
* a stale local copy can never win over it
|
|
8835
|
+
* - `'s3'`: `S3StateStore`
|
|
8836
|
+
* - a `StateStore` object: used as is
|
|
8837
|
+
* - a `StateProvider` object: wrapped by `LegacyStateStore`
|
|
8838
|
+
*/
|
|
8839
|
+
async function createStateStore(options) {
|
|
8840
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
8841
|
+
if (!config || config.provider === "local") {
|
|
8842
|
+
const { LocalStateStore } = await Promise.resolve().then(() => require("./LocalStateStore-DDDU0HkI.cjs"));
|
|
8843
|
+
return new LocalStateStore(workspaceRoot);
|
|
8844
|
+
}
|
|
8845
|
+
if (isStateStore(config.provider)) return config.provider;
|
|
8846
|
+
if (isStateProvider(config.provider)) {
|
|
8847
|
+
const { LegacyStateStore } = await Promise.resolve().then(() => require("./LegacyStateStore-C_OFs1gN.cjs"));
|
|
8848
|
+
return new LegacyStateStore(config.provider, { warn: options.warn });
|
|
8849
|
+
}
|
|
8850
|
+
if (config.provider === "ssm") {
|
|
8851
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
|
|
8852
|
+
const { SSMStateStore } = await Promise.resolve().then(() => require("./SSMStateStore-DD7jKrCO.cjs"));
|
|
8853
|
+
const ssm = config;
|
|
8854
|
+
return SSMStateStore.create({
|
|
8855
|
+
workspaceName,
|
|
8856
|
+
region: ssm.region,
|
|
8857
|
+
profile: ssm.profile
|
|
8858
|
+
});
|
|
8859
|
+
}
|
|
8860
|
+
if (config.provider === "s3") {
|
|
8861
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("s3");
|
|
8862
|
+
const { S3StateStore } = await Promise.resolve().then(() => require("./S3StateStore-C7eBae0G.cjs"));
|
|
8863
|
+
const s3 = config;
|
|
8864
|
+
return S3StateStore.create({
|
|
8865
|
+
workspaceName,
|
|
8866
|
+
bucket: s3.bucket,
|
|
8867
|
+
prefix: s3.prefix,
|
|
8868
|
+
region: s3.region,
|
|
8869
|
+
profile: s3.profile
|
|
8870
|
+
});
|
|
8871
|
+
}
|
|
8872
|
+
throw new UnknownStateProvider(config);
|
|
8873
|
+
}
|
|
8874
|
+
/**
|
|
8875
|
+
* A store behind the `StateProvider` interface, for the call sites that still
|
|
8876
|
+
* read and write whole states (deploy, until it journals). A write is still
|
|
8877
|
+
* conditional on the version this provider last read.
|
|
8878
|
+
*/
|
|
8879
|
+
var StateStoreProvider = class {
|
|
8880
|
+
store;
|
|
8881
|
+
versions = /* @__PURE__ */ new Map();
|
|
8882
|
+
constructor(store) {
|
|
8883
|
+
this.store = store;
|
|
8884
|
+
}
|
|
8885
|
+
async read(stage) {
|
|
8886
|
+
const stored = await this.store.read(stage);
|
|
8887
|
+
this.versions.set(stage, stored?.version ?? null);
|
|
8888
|
+
return stored?.state ?? null;
|
|
8889
|
+
}
|
|
8890
|
+
async write(stage, state) {
|
|
8891
|
+
const expectedVersion = this.versions.has(stage) ? this.versions.get(stage) ?? null : (await this.store.read(stage))?.version ?? null;
|
|
8892
|
+
state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8893
|
+
const version = await this.store.write(stage, state, { expectedVersion });
|
|
8894
|
+
this.versions.set(stage, version);
|
|
8895
|
+
}
|
|
8896
|
+
};
|
|
8897
|
+
//#endregion
|
|
8898
|
+
//#region src/deploy/state-commands.ts
|
|
8899
|
+
/**
|
|
8900
|
+
* State Management CLI Commands
|
|
8901
|
+
*
|
|
8902
|
+
* Commands for managing deployment state across local and remote providers.
|
|
8903
|
+
*/
|
|
8904
|
+
/** What `state:show` prints in place of a secret. */
|
|
8905
|
+
const MASKED = "********";
|
|
8906
|
+
/**
|
|
8907
|
+
* A copy of `state` with every secret replaced by `MASKED`: database
|
|
8908
|
+
* passwords, generated secrets and the backup IAM keys. `state:show` is run
|
|
8909
|
+
* to look up ids, in terminals and CI logs; nothing it prints should be
|
|
8910
|
+
* usable as a credential.
|
|
8911
|
+
*/
|
|
8912
|
+
function maskStateSecrets(state) {
|
|
8913
|
+
const masked = structuredClone(state);
|
|
8914
|
+
for (const credentials of Object.values(masked.appCredentials ?? {})) credentials.dbPassword = MASKED;
|
|
8915
|
+
for (const secrets of Object.values(masked.generatedSecrets ?? {})) for (const name of Object.keys(secrets)) secrets[name] = MASKED;
|
|
8916
|
+
if (masked.backups) {
|
|
8917
|
+
masked.backups.iamAccessKeyId = MASKED;
|
|
8918
|
+
masked.backups.iamSecretAccessKey = MASKED;
|
|
8919
|
+
}
|
|
8920
|
+
return masked;
|
|
8921
|
+
}
|
|
8922
|
+
/**
|
|
8923
|
+
* Pull state from remote to local.
|
|
8924
|
+
* `gkm state:pull --stage=<stage>`
|
|
8925
|
+
*/
|
|
8926
|
+
async function statePullCommand(options) {
|
|
8927
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
8928
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
8929
|
+
console.error("No remote state provider configured.");
|
|
8930
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
8931
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
8932
|
+
process.exit(1);
|
|
8933
|
+
}
|
|
8934
|
+
const provider = await createStateProvider({
|
|
8935
|
+
config: workspace.state,
|
|
8936
|
+
workspaceRoot: workspace.root,
|
|
8937
|
+
workspaceName: workspace.name
|
|
8938
|
+
});
|
|
8238
8939
|
if (!(provider instanceof CachedStateProvider)) {
|
|
8239
8940
|
console.error("State provider does not support pull operation.");
|
|
8240
8941
|
process.exit(1);
|
|
@@ -8280,19 +8981,41 @@ async function statePushCommand(options) {
|
|
|
8280
8981
|
*/
|
|
8281
8982
|
async function stateShowCommand(options) {
|
|
8282
8983
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
8283
|
-
const
|
|
8984
|
+
const stored = await (await createStateProvider({
|
|
8284
8985
|
config: workspace.state,
|
|
8285
8986
|
workspaceRoot: workspace.root,
|
|
8286
8987
|
workspaceName: workspace.name
|
|
8287
8988
|
})).read(options.stage);
|
|
8288
|
-
if (!
|
|
8989
|
+
if (!stored) {
|
|
8289
8990
|
console.log(`No state found for stage: ${options.stage}`);
|
|
8290
8991
|
return;
|
|
8291
8992
|
}
|
|
8993
|
+
const state = maskStateSecrets(stored);
|
|
8292
8994
|
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
8293
8995
|
else printStateDetails(state);
|
|
8294
8996
|
}
|
|
8295
8997
|
/**
|
|
8998
|
+
* Release a stage's deploy lock, whoever holds it.
|
|
8999
|
+
* `gkm state:unlock --stage=<stage>`
|
|
9000
|
+
*
|
|
9001
|
+
* For a run that crashed with the lock held. Releasing a lock a live run
|
|
9002
|
+
* holds lets a second run race it, so the holder is printed for the caller
|
|
9003
|
+
* to check.
|
|
9004
|
+
*/
|
|
9005
|
+
async function stateUnlockCommand(options) {
|
|
9006
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9007
|
+
const holder = await (await createStateStore({
|
|
9008
|
+
config: workspace.state,
|
|
9009
|
+
workspaceRoot: workspace.root,
|
|
9010
|
+
workspaceName: workspace.name
|
|
9011
|
+
})).forceUnlock(options.stage);
|
|
9012
|
+
if (!holder) {
|
|
9013
|
+
console.log(`Stage ${options.stage} was not locked.`);
|
|
9014
|
+
return;
|
|
9015
|
+
}
|
|
9016
|
+
console.log(`Released the lock on stage ${options.stage}, held by ${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}.`);
|
|
9017
|
+
}
|
|
9018
|
+
/**
|
|
8296
9019
|
* Compare local and remote state.
|
|
8297
9020
|
* `gkm state:diff --stage=<stage>`
|
|
8298
9021
|
*/
|
|
@@ -8376,6 +9099,26 @@ function printStateDetails(state) {
|
|
|
8376
9099
|
if (state.services.postgresId) console.log(` Postgres: ${state.services.postgresId}`);
|
|
8377
9100
|
if (state.services.redisId) console.log(` Redis: ${state.services.redisId}`);
|
|
8378
9101
|
}
|
|
9102
|
+
const credentials = Object.entries(state.appCredentials ?? {});
|
|
9103
|
+
if (credentials.length > 0) {
|
|
9104
|
+
console.log("");
|
|
9105
|
+
console.log("Database Credentials:");
|
|
9106
|
+
for (const [app, { dbUser, dbPassword }] of credentials) console.log(` ${app}: ${dbUser} / ${dbPassword}`);
|
|
9107
|
+
}
|
|
9108
|
+
const generated = Object.entries(state.generatedSecrets ?? {});
|
|
9109
|
+
if (generated.length > 0) {
|
|
9110
|
+
console.log("");
|
|
9111
|
+
console.log("Generated Secrets:");
|
|
9112
|
+
for (const [app, secrets] of generated) for (const [name, value] of Object.entries(secrets)) console.log(` ${app}.${name}: ${value}`);
|
|
9113
|
+
}
|
|
9114
|
+
if (state.backups) {
|
|
9115
|
+
console.log("");
|
|
9116
|
+
console.log("Backups:");
|
|
9117
|
+
console.log(` Bucket: ${state.backups.bucketName}`);
|
|
9118
|
+
console.log(` IAM User: ${state.backups.iamUserName}`);
|
|
9119
|
+
console.log(` IAM Access Key: ${state.backups.iamAccessKeyId}`);
|
|
9120
|
+
console.log(` IAM Secret Key: ${state.backups.iamSecretAccessKey}`);
|
|
9121
|
+
}
|
|
8379
9122
|
if (state.dnsVerified && Object.keys(state.dnsVerified).length > 0) {
|
|
8380
9123
|
console.log("");
|
|
8381
9124
|
console.log("DNS Verified:");
|
|
@@ -8772,7 +9515,7 @@ opened.
|
|
|
8772
9515
|
`;
|
|
8773
9516
|
}
|
|
8774
9517
|
function agentsContent(options, template) {
|
|
8775
|
-
const { name, monorepo, telescope,
|
|
9518
|
+
const { name, monorepo, telescope, packageManager } = options;
|
|
8776
9519
|
const { database } = options.constructs;
|
|
8777
9520
|
const constructsGlob = monorepo ? WORKSPACE_CONSTRUCTS_GLOB : CONSTRUCTS_GLOB;
|
|
8778
9521
|
const isWorker = template.name === "worker";
|
|
@@ -8867,13 +9610,13 @@ export const config = envParser
|
|
|
8867
9610
|
as \`undefined\` in exactly the environments that matter.`);
|
|
8868
9611
|
sections.push(conventionsSection());
|
|
8869
9612
|
sections.push(testingSection(database));
|
|
8870
|
-
if (telescope ||
|
|
8871
|
-
const tools = [telescope && "**Telescope** —
|
|
9613
|
+
if (telescope || database) {
|
|
9614
|
+
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);
|
|
8872
9615
|
sections.push(`## Dev tools
|
|
8873
9616
|
|
|
8874
9617
|
${tools.map((t) => `- ${t}`).join("\n")}
|
|
8875
9618
|
|
|
8876
|
-
|
|
9619
|
+
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.`);
|
|
8877
9620
|
}
|
|
8878
9621
|
sections.push(`## Before you say it works
|
|
8879
9622
|
|
|
@@ -9316,11 +10059,7 @@ const TOOLCHAIN_VERSIONS = {
|
|
|
9316
10059
|
esbuild: "~0.28.2",
|
|
9317
10060
|
vitest: "~5.0.2",
|
|
9318
10061
|
vite: "~8.3.1",
|
|
9319
|
-
"@vitejs/plugin-react": "^6.1.1"
|
|
9320
|
-
storybook: "^10.6.0",
|
|
9321
|
-
"@storybook/addon-docs": "^10.6.0",
|
|
9322
|
-
"@storybook/addon-a11y": "^10.6.0",
|
|
9323
|
-
"@storybook/react-vite": "^10.6.0"
|
|
10062
|
+
"@vitejs/plugin-react": "^6.1.1"
|
|
9324
10063
|
};
|
|
9325
10064
|
/**
|
|
9326
10065
|
* The Expo app's packages, which track an Expo SDK release (57) rather than
|
|
@@ -9380,26 +10119,25 @@ function loadPackageJson() {
|
|
|
9380
10119
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
9381
10120
|
*/
|
|
9382
10121
|
const GEEKMIDAS_VERSIONS = {
|
|
9383
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
9384
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
9385
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
9386
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
9387
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
9388
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
9389
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
9390
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
9391
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
9392
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
9393
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
9394
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
9395
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
9396
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
9397
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
9398
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
9399
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
9400
|
-
"@geekmidas/
|
|
9401
|
-
"@geekmidas/
|
|
9402
|
-
"@geekmidas/testkit": "~10.0.0-alpha.54",
|
|
10122
|
+
"@geekmidas/audit": "~10.0.0-alpha.56",
|
|
10123
|
+
"@geekmidas/auth": "~10.0.0-alpha.56",
|
|
10124
|
+
"@geekmidas/cache": "~10.0.0-alpha.56",
|
|
10125
|
+
"@geekmidas/client": "~10.0.0-alpha.56",
|
|
10126
|
+
"@geekmidas/cloud": "~10.0.0-alpha.56",
|
|
10127
|
+
"@geekmidas/constructs": "~10.0.0-alpha.56",
|
|
10128
|
+
"@geekmidas/db": "~10.0.0-alpha.56",
|
|
10129
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.56",
|
|
10130
|
+
"@geekmidas/envkit": "~10.0.0-alpha.56",
|
|
10131
|
+
"@geekmidas/errors": "~10.0.0-alpha.56",
|
|
10132
|
+
"@geekmidas/events": "~10.0.0-alpha.56",
|
|
10133
|
+
"@geekmidas/logger": "~10.0.0-alpha.56",
|
|
10134
|
+
"@geekmidas/manifest": "~10.0.0-alpha.56",
|
|
10135
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.56",
|
|
10136
|
+
"@geekmidas/schema": "~10.0.0-alpha.56",
|
|
10137
|
+
"@geekmidas/services": "~10.0.0-alpha.56",
|
|
10138
|
+
"@geekmidas/storage": "~10.0.0-alpha.56",
|
|
10139
|
+
"@geekmidas/telescope": "~10.0.0-alpha.56",
|
|
10140
|
+
"@geekmidas/testkit": "~10.0.0-alpha.56",
|
|
9403
10141
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
9404
10142
|
};
|
|
9405
10143
|
//#endregion
|
|
@@ -9522,14 +10260,13 @@ export default defineConfig({
|
|
|
9522
10260
|
* Generate configuration files (gkm.config.ts, tsconfig.json, biome.json, turbo.json)
|
|
9523
10261
|
*/
|
|
9524
10262
|
function generateConfigFiles(options, template) {
|
|
9525
|
-
const { telescope,
|
|
10263
|
+
const { telescope, routesStructure } = options;
|
|
9526
10264
|
const isServerless = template.name === "serverless";
|
|
9527
10265
|
const hasWorker = template.name === "worker";
|
|
9528
10266
|
const isFullstack = options.template === "fullstack";
|
|
9529
10267
|
const getRoutesGlob = () => routesGlob(routesStructure);
|
|
9530
10268
|
if (isFullstack) return generateSingleAppConfigFiles(options, template, {
|
|
9531
10269
|
telescope,
|
|
9532
|
-
studio,
|
|
9533
10270
|
routesStructure,
|
|
9534
10271
|
isServerless,
|
|
9535
10272
|
hasWorker,
|
|
@@ -9555,8 +10292,6 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
|
|
|
9555
10292
|
enabled: true,
|
|
9556
10293
|
path: '/__telescope',
|
|
9557
10294
|
},`;
|
|
9558
|
-
if (studio) gkmConfig += `
|
|
9559
|
-
studio: './src/config/studio#studio',`;
|
|
9560
10295
|
gkmConfig += `
|
|
9561
10296
|
openapi: {
|
|
9562
10297
|
enabled: true,
|
|
@@ -11105,7 +11840,6 @@ function generateMonorepoFiles(options, _template) {
|
|
|
11105
11840
|
lint: "biome lint .",
|
|
11106
11841
|
fmt: "biome format . --write",
|
|
11107
11842
|
"fmt:check": "biome format .",
|
|
11108
|
-
...isFullstack ? { storybook: "pnpm --filter ./packages/ui storybook" } : {},
|
|
11109
11843
|
...deploy.scripts
|
|
11110
11844
|
},
|
|
11111
11845
|
dependencies: {
|
|
@@ -11646,14 +12380,13 @@ export const cache = new Cache('${kv.id}');
|
|
|
11646
12380
|
* Generate package.json with dependencies based on template and options
|
|
11647
12381
|
*/
|
|
11648
12382
|
function generatePackageJson(options, template) {
|
|
11649
|
-
const { name, telescope,
|
|
12383
|
+
const { name, telescope, monorepo } = options;
|
|
11650
12384
|
const { database, cache, uploads, mail } = options.constructs;
|
|
11651
12385
|
const dependencies = { ...template.dependencies };
|
|
11652
12386
|
const devDependencies = { ...template.devDependencies };
|
|
11653
12387
|
const scripts = { ...template.scripts };
|
|
11654
12388
|
if (options.loggerType === "pino") dependencies.pino = DEPENDENCY_VERSIONS.pino;
|
|
11655
12389
|
if (telescope) dependencies["@geekmidas/telescope"] = GEEKMIDAS_VERSIONS["@geekmidas/telescope"];
|
|
11656
|
-
if (studio) dependencies["@geekmidas/studio"] = GEEKMIDAS_VERSIONS["@geekmidas/studio"];
|
|
11657
12390
|
const declares = !monorepo;
|
|
11658
12391
|
if (declares && (database || uploads || cache || mail)) dependencies["@geekmidas/manifest"] = GEEKMIDAS_VERSIONS["@geekmidas/manifest"];
|
|
11659
12392
|
if (declares && uploads) dependencies["@geekmidas/storage"] = GEEKMIDAS_VERSIONS["@geekmidas/storage"];
|
|
@@ -11794,158 +12527,33 @@ export type Factory = ReturnType<typeof createFactory>;
|
|
|
11794
12527
|
}];
|
|
11795
12528
|
}
|
|
11796
12529
|
//#endregion
|
|
11797
|
-
//#region src/init/generators/
|
|
12530
|
+
//#region src/init/generators/shadcn.ts
|
|
11798
12531
|
/**
|
|
11799
|
-
*
|
|
11800
|
-
*
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
"build:storybook": "storybook build -o dist/storybook"
|
|
11819
|
-
},
|
|
11820
|
-
dependencies: {
|
|
11821
|
-
"@radix-ui/react-dialog": DEPENDENCY_VERSIONS["@radix-ui/react-dialog"],
|
|
11822
|
-
"@radix-ui/react-label": DEPENDENCY_VERSIONS["@radix-ui/react-label"],
|
|
11823
|
-
"@radix-ui/react-separator": DEPENDENCY_VERSIONS["@radix-ui/react-separator"],
|
|
11824
|
-
"@radix-ui/react-slot": DEPENDENCY_VERSIONS["@radix-ui/react-slot"],
|
|
11825
|
-
"@radix-ui/react-tabs": DEPENDENCY_VERSIONS["@radix-ui/react-tabs"],
|
|
11826
|
-
"@radix-ui/react-tooltip": DEPENDENCY_VERSIONS["@radix-ui/react-tooltip"],
|
|
11827
|
-
"class-variance-authority": DEPENDENCY_VERSIONS["class-variance-authority"],
|
|
11828
|
-
clsx: DEPENDENCY_VERSIONS["clsx"],
|
|
11829
|
-
"lucide-react": DEPENDENCY_VERSIONS["lucide-react"],
|
|
11830
|
-
"tailwind-merge": DEPENDENCY_VERSIONS["tailwind-merge"]
|
|
11831
|
-
},
|
|
11832
|
-
devDependencies: {
|
|
11833
|
-
"@storybook/addon-a11y": TOOLCHAIN_VERSIONS["@storybook/addon-a11y"],
|
|
11834
|
-
"@storybook/addon-docs": TOOLCHAIN_VERSIONS["@storybook/addon-docs"],
|
|
11835
|
-
"@storybook/react-vite": TOOLCHAIN_VERSIONS["@storybook/react-vite"],
|
|
11836
|
-
"@tailwindcss/vite": DEPENDENCY_VERSIONS["@tailwindcss/vite"],
|
|
11837
|
-
"@types/react": DEPENDENCY_VERSIONS["@types/react"],
|
|
11838
|
-
"@types/react-dom": DEPENDENCY_VERSIONS["@types/react-dom"],
|
|
11839
|
-
react: DEPENDENCY_VERSIONS["react"],
|
|
11840
|
-
"react-dom": DEPENDENCY_VERSIONS["react-dom"],
|
|
11841
|
-
storybook: TOOLCHAIN_VERSIONS["storybook"],
|
|
11842
|
-
tailwindcss: DEPENDENCY_VERSIONS["tailwindcss"],
|
|
11843
|
-
typescript: TOOLCHAIN_VERSIONS["typescript"],
|
|
11844
|
-
vite: TOOLCHAIN_VERSIONS["vite"]
|
|
11845
|
-
},
|
|
11846
|
-
peerDependencies: {
|
|
11847
|
-
react: ">=18.0.0",
|
|
11848
|
-
"react-dom": ">=18.0.0",
|
|
11849
|
-
tailwindcss: ">=4.0.0"
|
|
11850
|
-
}
|
|
11851
|
-
};
|
|
11852
|
-
const tsConfig = {
|
|
11853
|
-
extends: "../../tsconfig.json",
|
|
11854
|
-
compilerOptions: {
|
|
11855
|
-
jsx: "react-jsx",
|
|
11856
|
-
lib: [
|
|
11857
|
-
"ES2023",
|
|
11858
|
-
"DOM",
|
|
11859
|
-
"DOM.Iterable"
|
|
11860
|
-
],
|
|
11861
|
-
noEmit: true,
|
|
11862
|
-
allowImportingTsExtensions: true,
|
|
11863
|
-
paths: { "~/*": ["./src/*"] }
|
|
11864
|
-
},
|
|
11865
|
-
include: ["src/**/*"],
|
|
11866
|
-
exclude: [
|
|
11867
|
-
"node_modules",
|
|
11868
|
-
"dist",
|
|
11869
|
-
"**/*.stories.tsx"
|
|
11870
|
-
]
|
|
11871
|
-
};
|
|
11872
|
-
const componentsJson = {
|
|
11873
|
-
$schema: "https://ui.shadcn.com/schema.json",
|
|
11874
|
-
style: "new-york",
|
|
11875
|
-
rsc: false,
|
|
11876
|
-
tsx: true,
|
|
11877
|
-
tailwind: {
|
|
11878
|
-
config: "",
|
|
11879
|
-
css: "src/styles/globals.css",
|
|
11880
|
-
baseColor: "neutral",
|
|
11881
|
-
cssVariables: true,
|
|
11882
|
-
prefix: ""
|
|
11883
|
-
},
|
|
11884
|
-
aliases: {
|
|
11885
|
-
components: "~/components",
|
|
11886
|
-
utils: "~/lib/utils",
|
|
11887
|
-
ui: "~/components/ui",
|
|
11888
|
-
lib: "~/lib",
|
|
11889
|
-
hooks: "~/hooks"
|
|
11890
|
-
},
|
|
11891
|
-
iconLibrary: "lucide"
|
|
11892
|
-
};
|
|
11893
|
-
const storybookMain = `import type { StorybookConfig } from '@storybook/react-vite';
|
|
11894
|
-
|
|
11895
|
-
const config: StorybookConfig = {
|
|
11896
|
-
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
|
11897
|
-
// Controls, actions, backgrounds, viewport and interactions are part of
|
|
11898
|
-
// \`storybook\` itself; docs and a11y are add-ons.
|
|
11899
|
-
addons: ['@storybook/addon-docs', '@storybook/addon-a11y'],
|
|
11900
|
-
framework: {
|
|
11901
|
-
name: '@storybook/react-vite',
|
|
11902
|
-
options: {},
|
|
11903
|
-
},
|
|
11904
|
-
// \`react-docgen\` parses components itself. The TypeScript-based docgen
|
|
11905
|
-
// calls into TypeScript's JavaScript API, which TypeScript 7 no longer ships.
|
|
11906
|
-
typescript: {
|
|
11907
|
-
reactDocgen: 'react-docgen',
|
|
11908
|
-
},
|
|
11909
|
-
viteFinal: async (config) => {
|
|
11910
|
-
// Add Tailwind CSS v4 plugin
|
|
11911
|
-
const tailwindcss = await import('@tailwindcss/vite');
|
|
11912
|
-
config.plugins = config.plugins || [];
|
|
11913
|
-
config.plugins.push(tailwindcss.default());
|
|
11914
|
-
return config;
|
|
11915
|
-
},
|
|
11916
|
-
};
|
|
11917
|
-
|
|
11918
|
-
export default config;
|
|
11919
|
-
`;
|
|
11920
|
-
const storybookPreview = `import type { Preview } from '@storybook/react-vite';
|
|
11921
|
-
import '../src/styles/globals.css';
|
|
11922
|
-
|
|
11923
|
-
const preview: Preview = {
|
|
11924
|
-
// The starting background is a global, not a parameter.
|
|
11925
|
-
initialGlobals: {
|
|
11926
|
-
backgrounds: { value: 'dark' },
|
|
11927
|
-
},
|
|
11928
|
-
parameters: {
|
|
11929
|
-
backgrounds: {
|
|
11930
|
-
options: {
|
|
11931
|
-
dark: { name: 'dark', value: '#171717' },
|
|
11932
|
-
surface: { name: 'surface', value: '#1c1c1c' },
|
|
11933
|
-
light: { name: 'light', value: '#fafafa' },
|
|
11934
|
-
},
|
|
11935
|
-
},
|
|
11936
|
-
controls: {
|
|
11937
|
-
matchers: {
|
|
11938
|
-
color: /(background|color)$/i,
|
|
11939
|
-
date: /Date$/i,
|
|
11940
|
-
},
|
|
11941
|
-
},
|
|
11942
|
-
layout: 'centered',
|
|
11943
|
-
},
|
|
12532
|
+
* shadcn/ui components, written into the web app that uses them.
|
|
12533
|
+
*
|
|
12534
|
+
* shadcn is copy-in by design: the components are the app's own source, to
|
|
12535
|
+
* edit and add to with `npx shadcn@latest add`. A shared workspace package of
|
|
12536
|
+
* them was one more package to build and version for code only the web app
|
|
12537
|
+
* imported.
|
|
12538
|
+
*/
|
|
12539
|
+
/** What the components below import. */
|
|
12540
|
+
const SHADCN_DEPENDENCIES = {
|
|
12541
|
+
"@radix-ui/react-dialog": DEPENDENCY_VERSIONS["@radix-ui/react-dialog"],
|
|
12542
|
+
"@radix-ui/react-label": DEPENDENCY_VERSIONS["@radix-ui/react-label"],
|
|
12543
|
+
"@radix-ui/react-separator": DEPENDENCY_VERSIONS["@radix-ui/react-separator"],
|
|
12544
|
+
"@radix-ui/react-slot": DEPENDENCY_VERSIONS["@radix-ui/react-slot"],
|
|
12545
|
+
"@radix-ui/react-tabs": DEPENDENCY_VERSIONS["@radix-ui/react-tabs"],
|
|
12546
|
+
"@radix-ui/react-tooltip": DEPENDENCY_VERSIONS["@radix-ui/react-tooltip"],
|
|
12547
|
+
"class-variance-authority": DEPENDENCY_VERSIONS["class-variance-authority"],
|
|
12548
|
+
clsx: DEPENDENCY_VERSIONS["clsx"],
|
|
12549
|
+
"lucide-react": DEPENDENCY_VERSIONS["lucide-react"],
|
|
12550
|
+
"tailwind-merge": DEPENDENCY_VERSIONS["tailwind-merge"]
|
|
11944
12551
|
};
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
12552
|
+
/**
|
|
12553
|
+
* Tailwind v4 with the shadcn theme tokens — the stylesheet the app imports
|
|
12554
|
+
* once, at its root.
|
|
12555
|
+
*/
|
|
12556
|
+
const SHADCN_GLOBALS_CSS = `@import "tailwindcss";
|
|
11949
12557
|
|
|
11950
12558
|
@theme {
|
|
11951
12559
|
--color-background: hsl(var(--background));
|
|
@@ -12029,14 +12637,56 @@ export default preview;
|
|
|
12029
12637
|
}
|
|
12030
12638
|
}
|
|
12031
12639
|
`;
|
|
12032
|
-
|
|
12640
|
+
/**
|
|
12641
|
+
* The components, `cn`, and the `components.json` that tells the shadcn CLI
|
|
12642
|
+
* where they live.
|
|
12643
|
+
*
|
|
12644
|
+
* @param appDir the web app, relative to the workspace root (`apps/web`)
|
|
12645
|
+
* @param css the stylesheet holding {@link SHADCN_GLOBALS_CSS}, relative to
|
|
12646
|
+
* the app
|
|
12647
|
+
* @param options.rsc whether the app renders React Server Components, so
|
|
12648
|
+
* `shadcn add` marks what it writes `'use client'`. The radix-based
|
|
12649
|
+
* components here carry the directive either way; outside RSC it is inert.
|
|
12650
|
+
*/
|
|
12651
|
+
function shadcnFiles(appDir, css, options) {
|
|
12652
|
+
const componentsJson = {
|
|
12653
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
12654
|
+
style: "new-york",
|
|
12655
|
+
rsc: options.rsc,
|
|
12656
|
+
tsx: true,
|
|
12657
|
+
tailwind: {
|
|
12658
|
+
config: "",
|
|
12659
|
+
css,
|
|
12660
|
+
baseColor: "neutral",
|
|
12661
|
+
cssVariables: true,
|
|
12662
|
+
prefix: ""
|
|
12663
|
+
},
|
|
12664
|
+
aliases: {
|
|
12665
|
+
components: "~/components",
|
|
12666
|
+
utils: "~/lib/utils",
|
|
12667
|
+
ui: "~/components/ui",
|
|
12668
|
+
lib: "~/lib",
|
|
12669
|
+
hooks: "~/hooks"
|
|
12670
|
+
},
|
|
12671
|
+
iconLibrary: "lucide"
|
|
12672
|
+
};
|
|
12673
|
+
return [
|
|
12674
|
+
{
|
|
12675
|
+
path: `${appDir}/components.json`,
|
|
12676
|
+
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
12677
|
+
},
|
|
12678
|
+
{
|
|
12679
|
+
path: `${appDir}/src/lib/utils.ts`,
|
|
12680
|
+
content: `import { type ClassValue, clsx } from 'clsx';
|
|
12033
12681
|
import { twMerge } from 'tailwind-merge';
|
|
12034
12682
|
|
|
12035
12683
|
export function cn(...inputs: ClassValue[]) {
|
|
12036
12684
|
return twMerge(clsx(inputs));
|
|
12037
12685
|
}
|
|
12038
|
-
|
|
12039
|
-
|
|
12686
|
+
`
|
|
12687
|
+
},
|
|
12688
|
+
...Object.entries({
|
|
12689
|
+
button: `import { Slot } from '@radix-ui/react-slot';
|
|
12040
12690
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
12041
12691
|
import * as React from 'react';
|
|
12042
12692
|
|
|
@@ -12093,73 +12743,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
12093
12743
|
Button.displayName = 'Button';
|
|
12094
12744
|
|
|
12095
12745
|
export { Button, buttonVariants };
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
import { Button } from '~/components/ui/button';
|
|
12099
|
-
|
|
12100
|
-
const meta: Meta<typeof Button> = {
|
|
12101
|
-
title: 'Components/Button',
|
|
12102
|
-
component: Button,
|
|
12103
|
-
tags: ['autodocs'],
|
|
12104
|
-
argTypes: {
|
|
12105
|
-
variant: {
|
|
12106
|
-
control: 'select',
|
|
12107
|
-
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
|
|
12108
|
-
},
|
|
12109
|
-
size: {
|
|
12110
|
-
control: 'select',
|
|
12111
|
-
options: ['default', 'sm', 'lg', 'icon'],
|
|
12112
|
-
},
|
|
12113
|
-
},
|
|
12114
|
-
};
|
|
12115
|
-
|
|
12116
|
-
export default meta;
|
|
12117
|
-
type Story = StoryObj<typeof Button>;
|
|
12118
|
-
|
|
12119
|
-
export const Default: Story = {
|
|
12120
|
-
args: {
|
|
12121
|
-
children: 'Button',
|
|
12122
|
-
variant: 'default',
|
|
12123
|
-
size: 'default',
|
|
12124
|
-
},
|
|
12125
|
-
};
|
|
12126
|
-
|
|
12127
|
-
export const Secondary: Story = {
|
|
12128
|
-
args: {
|
|
12129
|
-
children: 'Secondary',
|
|
12130
|
-
variant: 'secondary',
|
|
12131
|
-
},
|
|
12132
|
-
};
|
|
12133
|
-
|
|
12134
|
-
export const Destructive: Story = {
|
|
12135
|
-
args: {
|
|
12136
|
-
children: 'Destructive',
|
|
12137
|
-
variant: 'destructive',
|
|
12138
|
-
},
|
|
12139
|
-
};
|
|
12140
|
-
|
|
12141
|
-
export const Outline: Story = {
|
|
12142
|
-
args: {
|
|
12143
|
-
children: 'Outline',
|
|
12144
|
-
variant: 'outline',
|
|
12145
|
-
},
|
|
12146
|
-
};
|
|
12147
|
-
|
|
12148
|
-
export const Ghost: Story = {
|
|
12149
|
-
args: {
|
|
12150
|
-
children: 'Ghost',
|
|
12151
|
-
variant: 'ghost',
|
|
12152
|
-
},
|
|
12153
|
-
};
|
|
12154
|
-
|
|
12155
|
-
export const Link: Story = {
|
|
12156
|
-
args: {
|
|
12157
|
-
children: 'Link',
|
|
12158
|
-
variant: 'link',
|
|
12159
|
-
},
|
|
12160
|
-
};
|
|
12161
|
-
`;
|
|
12162
|
-
const inputTsx = `import * as React from 'react';
|
|
12746
|
+
`,
|
|
12747
|
+
input: `import * as React from 'react';
|
|
12163
12748
|
|
|
12164
12749
|
import { cn } from '~/lib/utils';
|
|
12165
12750
|
|
|
@@ -12181,8 +12766,8 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
|
|
12181
12766
|
Input.displayName = 'Input';
|
|
12182
12767
|
|
|
12183
12768
|
export { Input };
|
|
12184
|
-
|
|
12185
|
-
|
|
12769
|
+
`,
|
|
12770
|
+
card: `import * as React from 'react';
|
|
12186
12771
|
|
|
12187
12772
|
import { cn } from '~/lib/utils';
|
|
12188
12773
|
|
|
@@ -12258,124 +12843,10 @@ const CardFooter = React.forwardRef<
|
|
|
12258
12843
|
CardFooter.displayName = 'CardFooter';
|
|
12259
12844
|
|
|
12260
12845
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
import { Input } from '~/components/ui/input';
|
|
12264
|
-
|
|
12265
|
-
const meta: Meta<typeof Input> = {
|
|
12266
|
-
title: 'Components/Input',
|
|
12267
|
-
component: Input,
|
|
12268
|
-
tags: ['autodocs'],
|
|
12269
|
-
argTypes: {
|
|
12270
|
-
type: {
|
|
12271
|
-
control: 'select',
|
|
12272
|
-
options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'],
|
|
12273
|
-
},
|
|
12274
|
-
disabled: {
|
|
12275
|
-
control: 'boolean',
|
|
12276
|
-
},
|
|
12277
|
-
},
|
|
12278
|
-
};
|
|
12279
|
-
|
|
12280
|
-
export default meta;
|
|
12281
|
-
type Story = StoryObj<typeof Input>;
|
|
12282
|
-
|
|
12283
|
-
export const Default: Story = {
|
|
12284
|
-
args: {
|
|
12285
|
-
placeholder: 'Enter text...',
|
|
12286
|
-
},
|
|
12287
|
-
};
|
|
12288
|
-
|
|
12289
|
-
export const Email: Story = {
|
|
12290
|
-
args: {
|
|
12291
|
-
type: 'email',
|
|
12292
|
-
placeholder: 'Enter email...',
|
|
12293
|
-
},
|
|
12294
|
-
};
|
|
12295
|
-
|
|
12296
|
-
export const Password: Story = {
|
|
12297
|
-
args: {
|
|
12298
|
-
type: 'password',
|
|
12299
|
-
placeholder: 'Enter password...',
|
|
12300
|
-
},
|
|
12301
|
-
};
|
|
12302
|
-
|
|
12303
|
-
export const Disabled: Story = {
|
|
12304
|
-
args: {
|
|
12305
|
-
placeholder: 'Disabled input',
|
|
12306
|
-
disabled: true,
|
|
12307
|
-
},
|
|
12308
|
-
};
|
|
12309
|
-
|
|
12310
|
-
export const WithValue: Story = {
|
|
12311
|
-
args: {
|
|
12312
|
-
defaultValue: 'Hello World',
|
|
12313
|
-
},
|
|
12314
|
-
};
|
|
12315
|
-
`;
|
|
12316
|
-
const cardStories = `import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
12317
|
-
import { Button } from '~/components/ui/button';
|
|
12318
|
-
import {
|
|
12319
|
-
Card,
|
|
12320
|
-
CardContent,
|
|
12321
|
-
CardDescription,
|
|
12322
|
-
CardFooter,
|
|
12323
|
-
CardHeader,
|
|
12324
|
-
CardTitle,
|
|
12325
|
-
} from '~/components/ui/card';
|
|
12326
|
-
import { Input } from '~/components/ui/input';
|
|
12327
|
-
|
|
12328
|
-
const meta: Meta<typeof Card> = {
|
|
12329
|
-
title: 'Components/Card',
|
|
12330
|
-
component: Card,
|
|
12331
|
-
tags: ['autodocs'],
|
|
12332
|
-
};
|
|
12333
|
-
|
|
12334
|
-
export default meta;
|
|
12335
|
-
type Story = StoryObj<typeof Card>;
|
|
12336
|
-
|
|
12337
|
-
export const Default: Story = {
|
|
12338
|
-
render: () => (
|
|
12339
|
-
<Card className="w-[350px]">
|
|
12340
|
-
<CardHeader>
|
|
12341
|
-
<CardTitle>Card Title</CardTitle>
|
|
12342
|
-
<CardDescription>Card description goes here.</CardDescription>
|
|
12343
|
-
</CardHeader>
|
|
12344
|
-
<CardContent>
|
|
12345
|
-
<p>Card content goes here.</p>
|
|
12346
|
-
</CardContent>
|
|
12347
|
-
</Card>
|
|
12348
|
-
),
|
|
12349
|
-
};
|
|
12350
|
-
|
|
12351
|
-
export const WithFooter: Story = {
|
|
12352
|
-
render: () => (
|
|
12353
|
-
<Card className="w-[350px]">
|
|
12354
|
-
<CardHeader>
|
|
12355
|
-
<CardTitle>Create Account</CardTitle>
|
|
12356
|
-
<CardDescription>Enter your details below.</CardDescription>
|
|
12357
|
-
</CardHeader>
|
|
12358
|
-
<CardContent className="space-y-4">
|
|
12359
|
-
<Input placeholder="Email" type="email" />
|
|
12360
|
-
<Input placeholder="Password" type="password" />
|
|
12361
|
-
</CardContent>
|
|
12362
|
-
<CardFooter className="flex justify-between">
|
|
12363
|
-
<Button variant="outline">Cancel</Button>
|
|
12364
|
-
<Button>Create</Button>
|
|
12365
|
-
</CardFooter>
|
|
12366
|
-
</Card>
|
|
12367
|
-
),
|
|
12368
|
-
};
|
|
12846
|
+
`,
|
|
12847
|
+
label: `'use client';
|
|
12369
12848
|
|
|
12370
|
-
|
|
12371
|
-
render: () => (
|
|
12372
|
-
<Card className="w-[350px] p-6">
|
|
12373
|
-
<p>Simple card with just content.</p>
|
|
12374
|
-
</Card>
|
|
12375
|
-
),
|
|
12376
|
-
};
|
|
12377
|
-
`;
|
|
12378
|
-
const labelTsx = `import * as LabelPrimitive from '@radix-ui/react-label';
|
|
12849
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
12379
12850
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
12380
12851
|
import * as React from 'react';
|
|
12381
12852
|
|
|
@@ -12399,47 +12870,8 @@ const Label = React.forwardRef<
|
|
|
12399
12870
|
Label.displayName = LabelPrimitive.Root.displayName;
|
|
12400
12871
|
|
|
12401
12872
|
export { Label };
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
import { Input } from '~/components/ui/input';
|
|
12405
|
-
import { Label } from '~/components/ui/label';
|
|
12406
|
-
|
|
12407
|
-
const meta: Meta<typeof Label> = {
|
|
12408
|
-
title: 'Components/Label',
|
|
12409
|
-
component: Label,
|
|
12410
|
-
tags: ['autodocs'],
|
|
12411
|
-
};
|
|
12412
|
-
|
|
12413
|
-
export default meta;
|
|
12414
|
-
type Story = StoryObj<typeof Label>;
|
|
12415
|
-
|
|
12416
|
-
export const Default: Story = {
|
|
12417
|
-
args: {
|
|
12418
|
-
children: 'Label',
|
|
12419
|
-
},
|
|
12420
|
-
};
|
|
12421
|
-
|
|
12422
|
-
export const WithInput: Story = {
|
|
12423
|
-
render: () => (
|
|
12424
|
-
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
12425
|
-
<Label htmlFor="email">Email</Label>
|
|
12426
|
-
<Input type="email" id="email" placeholder="Email" />
|
|
12427
|
-
</div>
|
|
12428
|
-
),
|
|
12429
|
-
};
|
|
12430
|
-
|
|
12431
|
-
export const Disabled: Story = {
|
|
12432
|
-
render: () => (
|
|
12433
|
-
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
12434
|
-
<Label htmlFor="disabled" className="peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
12435
|
-
Disabled
|
|
12436
|
-
</Label>
|
|
12437
|
-
<Input type="text" id="disabled" placeholder="Disabled" disabled className="peer" />
|
|
12438
|
-
</div>
|
|
12439
|
-
),
|
|
12440
|
-
};
|
|
12441
|
-
`;
|
|
12442
|
-
const badgeTsx = `import { cva, type VariantProps } from 'class-variance-authority';
|
|
12873
|
+
`,
|
|
12874
|
+
badge: `import { cva, type VariantProps } from 'class-variance-authority';
|
|
12443
12875
|
import * as React from 'react';
|
|
12444
12876
|
|
|
12445
12877
|
import { cn } from '~/lib/utils';
|
|
@@ -12475,54 +12907,10 @@ function Badge({ className, variant, ...props }: BadgeProps) {
|
|
|
12475
12907
|
}
|
|
12476
12908
|
|
|
12477
12909
|
export { Badge, badgeVariants };
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
import { Badge } from '~/components/ui/badge';
|
|
12481
|
-
|
|
12482
|
-
const meta: Meta<typeof Badge> = {
|
|
12483
|
-
title: 'Components/Badge',
|
|
12484
|
-
component: Badge,
|
|
12485
|
-
tags: ['autodocs'],
|
|
12486
|
-
argTypes: {
|
|
12487
|
-
variant: {
|
|
12488
|
-
control: 'select',
|
|
12489
|
-
options: ['default', 'secondary', 'destructive', 'outline'],
|
|
12490
|
-
},
|
|
12491
|
-
},
|
|
12492
|
-
};
|
|
12493
|
-
|
|
12494
|
-
export default meta;
|
|
12495
|
-
type Story = StoryObj<typeof Badge>;
|
|
12496
|
-
|
|
12497
|
-
export const Default: Story = {
|
|
12498
|
-
args: {
|
|
12499
|
-
children: 'Badge',
|
|
12500
|
-
variant: 'default',
|
|
12501
|
-
},
|
|
12502
|
-
};
|
|
12503
|
-
|
|
12504
|
-
export const Secondary: Story = {
|
|
12505
|
-
args: {
|
|
12506
|
-
children: 'Secondary',
|
|
12507
|
-
variant: 'secondary',
|
|
12508
|
-
},
|
|
12509
|
-
};
|
|
12510
|
-
|
|
12511
|
-
export const Destructive: Story = {
|
|
12512
|
-
args: {
|
|
12513
|
-
children: 'Destructive',
|
|
12514
|
-
variant: 'destructive',
|
|
12515
|
-
},
|
|
12516
|
-
};
|
|
12910
|
+
`,
|
|
12911
|
+
separator: `'use client';
|
|
12517
12912
|
|
|
12518
|
-
|
|
12519
|
-
args: {
|
|
12520
|
-
children: 'Outline',
|
|
12521
|
-
variant: 'outline',
|
|
12522
|
-
},
|
|
12523
|
-
};
|
|
12524
|
-
`;
|
|
12525
|
-
const separatorTsx = `import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
12913
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
12526
12914
|
import * as React from 'react';
|
|
12527
12915
|
|
|
12528
12916
|
import { cn } from '~/lib/utils';
|
|
@@ -12551,59 +12939,10 @@ const Separator = React.forwardRef<
|
|
|
12551
12939
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
12552
12940
|
|
|
12553
12941
|
export { Separator };
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
import { Separator } from '~/components/ui/separator';
|
|
12557
|
-
|
|
12558
|
-
const meta: Meta<typeof Separator> = {
|
|
12559
|
-
title: 'Components/Separator',
|
|
12560
|
-
component: Separator,
|
|
12561
|
-
tags: ['autodocs'],
|
|
12562
|
-
argTypes: {
|
|
12563
|
-
orientation: {
|
|
12564
|
-
control: 'select',
|
|
12565
|
-
options: ['horizontal', 'vertical'],
|
|
12566
|
-
},
|
|
12567
|
-
},
|
|
12568
|
-
};
|
|
12569
|
-
|
|
12570
|
-
export default meta;
|
|
12571
|
-
type Story = StoryObj<typeof Separator>;
|
|
12572
|
-
|
|
12573
|
-
export const Horizontal: Story = {
|
|
12574
|
-
render: () => (
|
|
12575
|
-
<div className="w-[300px]">
|
|
12576
|
-
<div className="space-y-1">
|
|
12577
|
-
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
|
|
12578
|
-
<p className="text-sm text-muted-foreground">
|
|
12579
|
-
An open-source UI component library.
|
|
12580
|
-
</p>
|
|
12581
|
-
</div>
|
|
12582
|
-
<Separator className="my-4" />
|
|
12583
|
-
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
12584
|
-
<div>Blog</div>
|
|
12585
|
-
<Separator orientation="vertical" />
|
|
12586
|
-
<div>Docs</div>
|
|
12587
|
-
<Separator orientation="vertical" />
|
|
12588
|
-
<div>Source</div>
|
|
12589
|
-
</div>
|
|
12590
|
-
</div>
|
|
12591
|
-
),
|
|
12592
|
-
};
|
|
12942
|
+
`,
|
|
12943
|
+
tabs: `'use client';
|
|
12593
12944
|
|
|
12594
|
-
|
|
12595
|
-
render: () => (
|
|
12596
|
-
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
12597
|
-
<div>Blog</div>
|
|
12598
|
-
<Separator orientation="vertical" />
|
|
12599
|
-
<div>Docs</div>
|
|
12600
|
-
<Separator orientation="vertical" />
|
|
12601
|
-
<div>Source</div>
|
|
12602
|
-
</div>
|
|
12603
|
-
),
|
|
12604
|
-
};
|
|
12605
|
-
`;
|
|
12606
|
-
const tabsTsx = `import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
12945
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
12607
12946
|
import * as React from 'react';
|
|
12608
12947
|
|
|
12609
12948
|
import { cn } from '~/lib/utils';
|
|
@@ -12656,81 +12995,10 @@ const TabsContent = React.forwardRef<
|
|
|
12656
12995
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
12657
12996
|
|
|
12658
12997
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs';
|
|
12662
|
-
import { Button } from '~/components/ui/button';
|
|
12663
|
-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card';
|
|
12664
|
-
import { Input } from '~/components/ui/input';
|
|
12665
|
-
import { Label } from '~/components/ui/label';
|
|
12666
|
-
|
|
12667
|
-
const meta: Meta<typeof Tabs> = {
|
|
12668
|
-
title: 'Components/Tabs',
|
|
12669
|
-
component: Tabs,
|
|
12670
|
-
tags: ['autodocs'],
|
|
12671
|
-
};
|
|
12998
|
+
`,
|
|
12999
|
+
tooltip: `'use client';
|
|
12672
13000
|
|
|
12673
|
-
|
|
12674
|
-
type Story = StoryObj<typeof Tabs>;
|
|
12675
|
-
|
|
12676
|
-
export const Default: Story = {
|
|
12677
|
-
render: () => (
|
|
12678
|
-
<Tabs defaultValue="account" className="w-[400px]">
|
|
12679
|
-
<TabsList>
|
|
12680
|
-
<TabsTrigger value="account">Account</TabsTrigger>
|
|
12681
|
-
<TabsTrigger value="password">Password</TabsTrigger>
|
|
12682
|
-
</TabsList>
|
|
12683
|
-
<TabsContent value="account">
|
|
12684
|
-
<Card>
|
|
12685
|
-
<CardHeader>
|
|
12686
|
-
<CardTitle>Account</CardTitle>
|
|
12687
|
-
<CardDescription>
|
|
12688
|
-
Make changes to your account here. Click save when you're done.
|
|
12689
|
-
</CardDescription>
|
|
12690
|
-
</CardHeader>
|
|
12691
|
-
<CardContent className="space-y-2">
|
|
12692
|
-
<div className="space-y-1">
|
|
12693
|
-
<Label htmlFor="name">Name</Label>
|
|
12694
|
-
<Input id="name" defaultValue="Pedro Duarte" />
|
|
12695
|
-
</div>
|
|
12696
|
-
<div className="space-y-1">
|
|
12697
|
-
<Label htmlFor="username">Username</Label>
|
|
12698
|
-
<Input id="username" defaultValue="@peduarte" />
|
|
12699
|
-
</div>
|
|
12700
|
-
</CardContent>
|
|
12701
|
-
<CardFooter>
|
|
12702
|
-
<Button>Save changes</Button>
|
|
12703
|
-
</CardFooter>
|
|
12704
|
-
</Card>
|
|
12705
|
-
</TabsContent>
|
|
12706
|
-
<TabsContent value="password">
|
|
12707
|
-
<Card>
|
|
12708
|
-
<CardHeader>
|
|
12709
|
-
<CardTitle>Password</CardTitle>
|
|
12710
|
-
<CardDescription>
|
|
12711
|
-
Change your password here. After saving, you'll be logged out.
|
|
12712
|
-
</CardDescription>
|
|
12713
|
-
</CardHeader>
|
|
12714
|
-
<CardContent className="space-y-2">
|
|
12715
|
-
<div className="space-y-1">
|
|
12716
|
-
<Label htmlFor="current">Current password</Label>
|
|
12717
|
-
<Input id="current" type="password" />
|
|
12718
|
-
</div>
|
|
12719
|
-
<div className="space-y-1">
|
|
12720
|
-
<Label htmlFor="new">New password</Label>
|
|
12721
|
-
<Input id="new" type="password" />
|
|
12722
|
-
</div>
|
|
12723
|
-
</CardContent>
|
|
12724
|
-
<CardFooter>
|
|
12725
|
-
<Button>Save password</Button>
|
|
12726
|
-
</CardFooter>
|
|
12727
|
-
</Card>
|
|
12728
|
-
</TabsContent>
|
|
12729
|
-
</Tabs>
|
|
12730
|
-
),
|
|
12731
|
-
};
|
|
12732
|
-
`;
|
|
12733
|
-
const tooltipTsx = `import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
13001
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
12734
13002
|
import * as React from 'react';
|
|
12735
13003
|
|
|
12736
13004
|
import { cn } from '~/lib/utils';
|
|
@@ -12760,80 +13028,10 @@ const TooltipContent = React.forwardRef<
|
|
|
12760
13028
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
12761
13029
|
|
|
12762
13030
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';
|
|
12766
|
-
import { Button } from '~/components/ui/button';
|
|
12767
|
-
|
|
12768
|
-
const meta: Meta<typeof Tooltip> = {
|
|
12769
|
-
title: 'Components/Tooltip',
|
|
12770
|
-
component: Tooltip,
|
|
12771
|
-
tags: ['autodocs'],
|
|
12772
|
-
decorators: [
|
|
12773
|
-
(Story) => (
|
|
12774
|
-
<TooltipProvider>
|
|
12775
|
-
<Story />
|
|
12776
|
-
</TooltipProvider>
|
|
12777
|
-
),
|
|
12778
|
-
],
|
|
12779
|
-
};
|
|
12780
|
-
|
|
12781
|
-
export default meta;
|
|
12782
|
-
type Story = StoryObj<typeof Tooltip>;
|
|
12783
|
-
|
|
12784
|
-
export const Default: Story = {
|
|
12785
|
-
render: () => (
|
|
12786
|
-
<Tooltip>
|
|
12787
|
-
<TooltipTrigger asChild>
|
|
12788
|
-
<Button variant="outline">Hover me</Button>
|
|
12789
|
-
</TooltipTrigger>
|
|
12790
|
-
<TooltipContent>
|
|
12791
|
-
<p>Add to library</p>
|
|
12792
|
-
</TooltipContent>
|
|
12793
|
-
</Tooltip>
|
|
12794
|
-
),
|
|
12795
|
-
};
|
|
13031
|
+
`,
|
|
13032
|
+
dialog: `'use client';
|
|
12796
13033
|
|
|
12797
|
-
|
|
12798
|
-
render: () => (
|
|
12799
|
-
<div className="flex gap-4">
|
|
12800
|
-
<Tooltip>
|
|
12801
|
-
<TooltipTrigger asChild>
|
|
12802
|
-
<Button variant="outline">Top</Button>
|
|
12803
|
-
</TooltipTrigger>
|
|
12804
|
-
<TooltipContent side="top">
|
|
12805
|
-
<p>Top tooltip</p>
|
|
12806
|
-
</TooltipContent>
|
|
12807
|
-
</Tooltip>
|
|
12808
|
-
<Tooltip>
|
|
12809
|
-
<TooltipTrigger asChild>
|
|
12810
|
-
<Button variant="outline">Bottom</Button>
|
|
12811
|
-
</TooltipTrigger>
|
|
12812
|
-
<TooltipContent side="bottom">
|
|
12813
|
-
<p>Bottom tooltip</p>
|
|
12814
|
-
</TooltipContent>
|
|
12815
|
-
</Tooltip>
|
|
12816
|
-
<Tooltip>
|
|
12817
|
-
<TooltipTrigger asChild>
|
|
12818
|
-
<Button variant="outline">Left</Button>
|
|
12819
|
-
</TooltipTrigger>
|
|
12820
|
-
<TooltipContent side="left">
|
|
12821
|
-
<p>Left tooltip</p>
|
|
12822
|
-
</TooltipContent>
|
|
12823
|
-
</Tooltip>
|
|
12824
|
-
<Tooltip>
|
|
12825
|
-
<TooltipTrigger asChild>
|
|
12826
|
-
<Button variant="outline">Right</Button>
|
|
12827
|
-
</TooltipTrigger>
|
|
12828
|
-
<TooltipContent side="right">
|
|
12829
|
-
<p>Right tooltip</p>
|
|
12830
|
-
</TooltipContent>
|
|
12831
|
-
</Tooltip>
|
|
12832
|
-
</div>
|
|
12833
|
-
),
|
|
12834
|
-
};
|
|
12835
|
-
`;
|
|
12836
|
-
const dialogTsx = `import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
13034
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
12837
13035
|
import { X } from 'lucide-react';
|
|
12838
13036
|
import * as React from 'react';
|
|
12839
13037
|
|
|
@@ -12953,255 +13151,11 @@ export {
|
|
|
12953
13151
|
DialogTitle,
|
|
12954
13152
|
DialogDescription,
|
|
12955
13153
|
};
|
|
12956
|
-
`;
|
|
12957
|
-
const dialogStories = `import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
12958
|
-
import {
|
|
12959
|
-
Dialog,
|
|
12960
|
-
DialogContent,
|
|
12961
|
-
DialogDescription,
|
|
12962
|
-
DialogFooter,
|
|
12963
|
-
DialogHeader,
|
|
12964
|
-
DialogTitle,
|
|
12965
|
-
DialogTrigger,
|
|
12966
|
-
} from '~/components/ui/dialog';
|
|
12967
|
-
import { Button } from '~/components/ui/button';
|
|
12968
|
-
import { Input } from '~/components/ui/input';
|
|
12969
|
-
import { Label } from '~/components/ui/label';
|
|
12970
|
-
|
|
12971
|
-
const meta: Meta<typeof Dialog> = {
|
|
12972
|
-
title: 'Components/Dialog',
|
|
12973
|
-
component: Dialog,
|
|
12974
|
-
tags: ['autodocs'],
|
|
12975
|
-
};
|
|
12976
|
-
|
|
12977
|
-
export default meta;
|
|
12978
|
-
type Story = StoryObj<typeof Dialog>;
|
|
12979
|
-
|
|
12980
|
-
export const Default: Story = {
|
|
12981
|
-
render: () => (
|
|
12982
|
-
<Dialog>
|
|
12983
|
-
<DialogTrigger asChild>
|
|
12984
|
-
<Button variant="outline">Edit Profile</Button>
|
|
12985
|
-
</DialogTrigger>
|
|
12986
|
-
<DialogContent className="sm:max-w-[425px]">
|
|
12987
|
-
<DialogHeader>
|
|
12988
|
-
<DialogTitle>Edit profile</DialogTitle>
|
|
12989
|
-
<DialogDescription>
|
|
12990
|
-
Make changes to your profile here. Click save when you're done.
|
|
12991
|
-
</DialogDescription>
|
|
12992
|
-
</DialogHeader>
|
|
12993
|
-
<div className="grid gap-4 py-4">
|
|
12994
|
-
<div className="grid grid-cols-4 items-center gap-4">
|
|
12995
|
-
<Label htmlFor="name" className="text-right">
|
|
12996
|
-
Name
|
|
12997
|
-
</Label>
|
|
12998
|
-
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
|
12999
|
-
</div>
|
|
13000
|
-
<div className="grid grid-cols-4 items-center gap-4">
|
|
13001
|
-
<Label htmlFor="username" className="text-right">
|
|
13002
|
-
Username
|
|
13003
|
-
</Label>
|
|
13004
|
-
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
|
|
13005
|
-
</div>
|
|
13006
|
-
</div>
|
|
13007
|
-
<DialogFooter>
|
|
13008
|
-
<Button type="submit">Save changes</Button>
|
|
13009
|
-
</DialogFooter>
|
|
13010
|
-
</DialogContent>
|
|
13011
|
-
</Dialog>
|
|
13012
|
-
),
|
|
13013
|
-
};
|
|
13014
|
-
|
|
13015
|
-
export const Alert: Story = {
|
|
13016
|
-
render: () => (
|
|
13017
|
-
<Dialog>
|
|
13018
|
-
<DialogTrigger asChild>
|
|
13019
|
-
<Button variant="destructive">Delete Account</Button>
|
|
13020
|
-
</DialogTrigger>
|
|
13021
|
-
<DialogContent>
|
|
13022
|
-
<DialogHeader>
|
|
13023
|
-
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
13024
|
-
<DialogDescription>
|
|
13025
|
-
This action cannot be undone. This will permanently delete your
|
|
13026
|
-
account and remove your data from our servers.
|
|
13027
|
-
</DialogDescription>
|
|
13028
|
-
</DialogHeader>
|
|
13029
|
-
<DialogFooter>
|
|
13030
|
-
<Button variant="outline">Cancel</Button>
|
|
13031
|
-
<Button variant="destructive">Delete</Button>
|
|
13032
|
-
</DialogFooter>
|
|
13033
|
-
</DialogContent>
|
|
13034
|
-
</Dialog>
|
|
13035
|
-
),
|
|
13036
|
-
};
|
|
13037
|
-
`;
|
|
13038
|
-
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button/index.tsx';
|
|
13039
|
-
export { Input } from './input/index.tsx';
|
|
13040
|
-
export {
|
|
13041
|
-
Card,
|
|
13042
|
-
CardHeader,
|
|
13043
|
-
CardFooter,
|
|
13044
|
-
CardTitle,
|
|
13045
|
-
CardDescription,
|
|
13046
|
-
CardContent,
|
|
13047
|
-
} from './card/index.tsx';
|
|
13048
|
-
export { Label } from './label/index.tsx';
|
|
13049
|
-
export { Badge, type BadgeProps, badgeVariants } from './badge/index.tsx';
|
|
13050
|
-
export { Separator } from './separator/index.tsx';
|
|
13051
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs/index.tsx';
|
|
13052
|
-
export {
|
|
13053
|
-
Tooltip,
|
|
13054
|
-
TooltipTrigger,
|
|
13055
|
-
TooltipContent,
|
|
13056
|
-
TooltipProvider,
|
|
13057
|
-
} from './tooltip/index.tsx';
|
|
13058
|
-
export {
|
|
13059
|
-
Dialog,
|
|
13060
|
-
DialogPortal,
|
|
13061
|
-
DialogOverlay,
|
|
13062
|
-
DialogTrigger,
|
|
13063
|
-
DialogClose,
|
|
13064
|
-
DialogContent,
|
|
13065
|
-
DialogHeader,
|
|
13066
|
-
DialogFooter,
|
|
13067
|
-
DialogTitle,
|
|
13068
|
-
DialogDescription,
|
|
13069
|
-
} from './dialog/index.tsx';
|
|
13070
|
-
`;
|
|
13071
|
-
const buttonIndexTsx = buttonTsx;
|
|
13072
|
-
const inputIndexTsx = inputTsx;
|
|
13073
|
-
const cardIndexTsx = cardTsx;
|
|
13074
|
-
const componentsIndex = `export * from './ui/index.ts';
|
|
13075
|
-
`;
|
|
13076
|
-
const indexTs = `// @${options.name}/ui - Shared UI component library
|
|
13077
|
-
|
|
13078
|
-
// shadcn/ui components
|
|
13079
|
-
export * from './components/index.ts';
|
|
13080
|
-
|
|
13081
|
-
// Utilities
|
|
13082
|
-
export { cn } from './lib/utils.ts';
|
|
13083
|
-
`;
|
|
13084
|
-
return [
|
|
13085
|
-
{
|
|
13086
|
-
path: "packages/ui/package.json",
|
|
13087
|
-
content: `${JSON.stringify(packageJson, null, 2)}\n`
|
|
13088
|
-
},
|
|
13089
|
-
{
|
|
13090
|
-
path: "packages/ui/tsconfig.json",
|
|
13091
|
-
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
13092
|
-
},
|
|
13093
|
-
{
|
|
13094
|
-
path: "packages/ui/components.json",
|
|
13095
|
-
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
13096
|
-
},
|
|
13097
|
-
{
|
|
13098
|
-
path: "packages/ui/.storybook/main.ts",
|
|
13099
|
-
content: storybookMain
|
|
13100
|
-
},
|
|
13101
|
-
{
|
|
13102
|
-
path: "packages/ui/.storybook/preview.ts",
|
|
13103
|
-
content: storybookPreview
|
|
13104
|
-
},
|
|
13105
|
-
{
|
|
13106
|
-
path: "packages/ui/src/styles/globals.css",
|
|
13107
|
-
content: globalsCss
|
|
13108
|
-
},
|
|
13109
|
-
{
|
|
13110
|
-
path: "packages/ui/src/lib/utils.ts",
|
|
13111
|
-
content: utilsTs
|
|
13112
|
-
},
|
|
13113
|
-
{
|
|
13114
|
-
path: "packages/ui/src/components/ui/button/index.tsx",
|
|
13115
|
-
content: buttonIndexTsx
|
|
13116
|
-
},
|
|
13117
|
-
{
|
|
13118
|
-
path: "packages/ui/src/components/ui/button/button.stories.tsx",
|
|
13119
|
-
content: buttonStories
|
|
13120
|
-
},
|
|
13121
|
-
{
|
|
13122
|
-
path: "packages/ui/src/components/ui/input/index.tsx",
|
|
13123
|
-
content: inputIndexTsx
|
|
13124
|
-
},
|
|
13125
|
-
{
|
|
13126
|
-
path: "packages/ui/src/components/ui/input/input.stories.tsx",
|
|
13127
|
-
content: inputStories
|
|
13128
|
-
},
|
|
13129
|
-
{
|
|
13130
|
-
path: "packages/ui/src/components/ui/card/index.tsx",
|
|
13131
|
-
content: cardIndexTsx
|
|
13132
|
-
},
|
|
13133
|
-
{
|
|
13134
|
-
path: "packages/ui/src/components/ui/card/card.stories.tsx",
|
|
13135
|
-
content: cardStories
|
|
13136
|
-
},
|
|
13137
|
-
{
|
|
13138
|
-
path: "packages/ui/src/components/ui/label/index.tsx",
|
|
13139
|
-
content: labelTsx
|
|
13140
|
-
},
|
|
13141
|
-
{
|
|
13142
|
-
path: "packages/ui/src/components/ui/label/label.stories.tsx",
|
|
13143
|
-
content: labelStories
|
|
13144
|
-
},
|
|
13145
|
-
{
|
|
13146
|
-
path: "packages/ui/src/components/ui/badge/index.tsx",
|
|
13147
|
-
content: badgeTsx
|
|
13148
|
-
},
|
|
13149
|
-
{
|
|
13150
|
-
path: "packages/ui/src/components/ui/badge/badge.stories.tsx",
|
|
13151
|
-
content: badgeStories
|
|
13152
|
-
},
|
|
13153
|
-
{
|
|
13154
|
-
path: "packages/ui/src/components/ui/separator/index.tsx",
|
|
13155
|
-
content: separatorTsx
|
|
13156
|
-
},
|
|
13157
|
-
{
|
|
13158
|
-
path: "packages/ui/src/components/ui/separator/separator.stories.tsx",
|
|
13159
|
-
content: separatorStories
|
|
13160
|
-
},
|
|
13161
|
-
{
|
|
13162
|
-
path: "packages/ui/src/components/ui/tabs/index.tsx",
|
|
13163
|
-
content: tabsTsx
|
|
13164
|
-
},
|
|
13165
|
-
{
|
|
13166
|
-
path: "packages/ui/src/components/ui/tabs/tabs.stories.tsx",
|
|
13167
|
-
content: tabsStories
|
|
13168
|
-
},
|
|
13169
|
-
{
|
|
13170
|
-
path: "packages/ui/src/components/ui/tooltip/index.tsx",
|
|
13171
|
-
content: tooltipTsx
|
|
13172
|
-
},
|
|
13173
|
-
{
|
|
13174
|
-
path: "packages/ui/src/components/ui/tooltip/tooltip.stories.tsx",
|
|
13175
|
-
content: tooltipStories
|
|
13176
|
-
},
|
|
13177
|
-
{
|
|
13178
|
-
path: "packages/ui/src/components/ui/dialog/index.tsx",
|
|
13179
|
-
content: dialogTsx
|
|
13180
|
-
},
|
|
13181
|
-
{
|
|
13182
|
-
path: "packages/ui/src/components/ui/dialog/dialog.stories.tsx",
|
|
13183
|
-
content: dialogStories
|
|
13184
|
-
},
|
|
13185
|
-
{
|
|
13186
|
-
path: "packages/ui/src/components/ui/index.ts",
|
|
13187
|
-
content: componentsUiIndex
|
|
13188
|
-
},
|
|
13189
|
-
{
|
|
13190
|
-
path: "packages/ui/src/components/index.ts",
|
|
13191
|
-
content: componentsIndex
|
|
13192
|
-
},
|
|
13193
|
-
{
|
|
13194
|
-
path: "packages/ui/src/index.ts",
|
|
13195
|
-
content: indexTs
|
|
13196
|
-
},
|
|
13197
|
-
{
|
|
13198
|
-
path: "packages/ui/.gitignore",
|
|
13199
|
-
content: `node_modules/
|
|
13200
|
-
dist/
|
|
13201
|
-
storybook-static/
|
|
13202
|
-
*.log
|
|
13203
13154
|
`
|
|
13204
|
-
}
|
|
13155
|
+
}).map(([name, content]) => ({
|
|
13156
|
+
path: `${appDir}/src/components/ui/${name}.tsx`,
|
|
13157
|
+
content
|
|
13158
|
+
}))
|
|
13205
13159
|
];
|
|
13206
13160
|
}
|
|
13207
13161
|
//#endregion
|
|
@@ -13213,7 +13167,6 @@ function generateWebAppFiles(options) {
|
|
|
13213
13167
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
13214
13168
|
const packageName = `@${options.name}/web`;
|
|
13215
13169
|
const modelsPackage = `@${options.name}/models`;
|
|
13216
|
-
const uiPackage = `@${options.name}/ui`;
|
|
13217
13170
|
const packageJson = {
|
|
13218
13171
|
name: packageName,
|
|
13219
13172
|
version: "0.0.1",
|
|
@@ -13227,7 +13180,7 @@ function generateWebAppFiles(options) {
|
|
|
13227
13180
|
},
|
|
13228
13181
|
dependencies: {
|
|
13229
13182
|
[modelsPackage]: "workspace:*",
|
|
13230
|
-
|
|
13183
|
+
...SHADCN_DEPENDENCIES,
|
|
13231
13184
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
13232
13185
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
13233
13186
|
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
@@ -13252,7 +13205,7 @@ function generateWebAppFiles(options) {
|
|
|
13252
13205
|
const nextConfig: NextConfig = {
|
|
13253
13206
|
output: 'standalone',
|
|
13254
13207
|
reactStrictMode: true,
|
|
13255
|
-
transpilePackages: ['${modelsPackage}'
|
|
13208
|
+
transpilePackages: ['${modelsPackage}'],
|
|
13256
13209
|
};
|
|
13257
13210
|
|
|
13258
13211
|
export default nextConfig;
|
|
@@ -13285,12 +13238,10 @@ export default nextConfig;
|
|
|
13285
13238
|
incremental: true,
|
|
13286
13239
|
plugins: [{ name: "next" }],
|
|
13287
13240
|
paths: {
|
|
13288
|
-
"~/*": ["./src/*"
|
|
13241
|
+
"~/*": ["./src/*"],
|
|
13289
13242
|
[`@${options.name}/client/*`]: ["../../.gkm/client/*"],
|
|
13290
13243
|
[`${modelsPackage}`]: ["../../packages/models/src"],
|
|
13291
|
-
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
13292
|
-
[`${uiPackage}`]: ["../../packages/ui/src"],
|
|
13293
|
-
[`${uiPackage}/*`]: ["../../packages/ui/src/*"]
|
|
13244
|
+
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
13294
13245
|
}
|
|
13295
13246
|
},
|
|
13296
13247
|
include: [
|
|
@@ -13386,8 +13337,6 @@ export const api = createApi({
|
|
|
13386
13337
|
baseURL: clientConfig.apiUrl,
|
|
13387
13338
|
queryClient: getQueryClient(),
|
|
13388
13339
|
});
|
|
13389
|
-
`;
|
|
13390
|
-
const globalsCss = `@import '${uiPackage}/styles';
|
|
13391
13340
|
`;
|
|
13392
13341
|
const layoutTsx = `import type { Metadata } from 'next';
|
|
13393
13342
|
import { Providers } from './providers.tsx';
|
|
@@ -13413,7 +13362,14 @@ export default function RootLayout({
|
|
|
13413
13362
|
}
|
|
13414
13363
|
`;
|
|
13415
13364
|
const pageTsx = `import { api } from '~/api/index.ts';
|
|
13416
|
-
import { Button
|
|
13365
|
+
import { Button } from '~/components/ui/button.tsx';
|
|
13366
|
+
import {
|
|
13367
|
+
Card,
|
|
13368
|
+
CardContent,
|
|
13369
|
+
CardDescription,
|
|
13370
|
+
CardHeader,
|
|
13371
|
+
CardTitle,
|
|
13372
|
+
} from '~/components/ui/card.tsx';
|
|
13417
13373
|
|
|
13418
13374
|
export default async function Home() {
|
|
13419
13375
|
// Type-safe API call using the generated client
|
|
@@ -13453,7 +13409,7 @@ export default async function Home() {
|
|
|
13453
13409
|
<li>Run <code className="rounded bg-muted px-1">gkm openapi</code> to generate typed API client</li>
|
|
13454
13410
|
<li>Edit <code className="rounded bg-muted px-1">apps/web/src/app/page.tsx</code> to customize this page</li>
|
|
13455
13411
|
<li>Add API routes in <code className="rounded bg-muted px-1">apps/api/src/endpoints/</code></li>
|
|
13456
|
-
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in
|
|
13412
|
+
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in apps/web</li>
|
|
13457
13413
|
</ul>
|
|
13458
13414
|
<div className="flex gap-4">
|
|
13459
13415
|
<Button>Get Started</Button>
|
|
@@ -13465,6 +13421,11 @@ export default async function Home() {
|
|
|
13465
13421
|
</main>
|
|
13466
13422
|
);
|
|
13467
13423
|
}
|
|
13424
|
+
`;
|
|
13425
|
+
const gitignore = `.next/
|
|
13426
|
+
node_modules/
|
|
13427
|
+
.env.local
|
|
13428
|
+
*.log
|
|
13468
13429
|
`;
|
|
13469
13430
|
return [
|
|
13470
13431
|
{
|
|
@@ -13485,8 +13446,9 @@ export default async function Home() {
|
|
|
13485
13446
|
},
|
|
13486
13447
|
{
|
|
13487
13448
|
path: "apps/web/src/app/globals.css",
|
|
13488
|
-
content:
|
|
13449
|
+
content: SHADCN_GLOBALS_CSS
|
|
13489
13450
|
},
|
|
13451
|
+
...shadcnFiles("apps/web", "src/app/globals.css", { rsc: true }),
|
|
13490
13452
|
{
|
|
13491
13453
|
path: "apps/web/src/app/layout.tsx",
|
|
13492
13454
|
content: layoutTsx
|
|
@@ -13521,11 +13483,7 @@ export default async function Home() {
|
|
|
13521
13483
|
},
|
|
13522
13484
|
{
|
|
13523
13485
|
path: "apps/web/.gitignore",
|
|
13524
|
-
content:
|
|
13525
|
-
node_modules/
|
|
13526
|
-
.env.local
|
|
13527
|
-
*.log
|
|
13528
|
-
`
|
|
13486
|
+
content: gitignore
|
|
13529
13487
|
}
|
|
13530
13488
|
];
|
|
13531
13489
|
}
|
|
@@ -13541,7 +13499,6 @@ function generateTanStackWebFiles(options) {
|
|
|
13541
13499
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
13542
13500
|
const packageName = `@${options.name}/web`;
|
|
13543
13501
|
const modelsPackage = `@${options.name}/models`;
|
|
13544
|
-
const uiPackage = `@${options.name}/ui`;
|
|
13545
13502
|
const packageJson = {
|
|
13546
13503
|
name: packageName,
|
|
13547
13504
|
version: "0.0.1",
|
|
@@ -13555,7 +13512,7 @@ function generateTanStackWebFiles(options) {
|
|
|
13555
13512
|
},
|
|
13556
13513
|
dependencies: {
|
|
13557
13514
|
[modelsPackage]: "workspace:*",
|
|
13558
|
-
|
|
13515
|
+
...SHADCN_DEPENDENCIES,
|
|
13559
13516
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
13560
13517
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
13561
13518
|
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
@@ -13622,11 +13579,9 @@ export default defineConfig({
|
|
|
13622
13579
|
incremental: true,
|
|
13623
13580
|
types: ["vite/client"],
|
|
13624
13581
|
paths: {
|
|
13625
|
-
"~/*": ["./src/*"
|
|
13582
|
+
"~/*": ["./src/*"],
|
|
13626
13583
|
[modelsPackage]: ["../../packages/models/src"],
|
|
13627
13584
|
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
13628
|
-
[uiPackage]: ["../../packages/ui/src"],
|
|
13629
|
-
[`${uiPackage}/*`]: ["../../packages/ui/src/*"],
|
|
13630
13585
|
[`@${options.name}/client/*`]: ["../../.gkm/client/*"]
|
|
13631
13586
|
}
|
|
13632
13587
|
},
|
|
@@ -13829,10 +13784,15 @@ function HomeComponent() {
|
|
|
13829
13784
|
);
|
|
13830
13785
|
}
|
|
13831
13786
|
`;
|
|
13832
|
-
const globalsCss =
|
|
13833
|
-
|
|
13787
|
+
const globalsCss = `${SHADCN_GLOBALS_CSS}
|
|
13834
13788
|
@source "../..";
|
|
13835
|
-
|
|
13789
|
+
`;
|
|
13790
|
+
const gitignore = `.output/
|
|
13791
|
+
.vinxi/
|
|
13792
|
+
node_modules/
|
|
13793
|
+
.env.local
|
|
13794
|
+
*.log
|
|
13795
|
+
src/routeTree.gen.ts
|
|
13836
13796
|
`;
|
|
13837
13797
|
return [
|
|
13838
13798
|
{
|
|
@@ -13887,15 +13847,10 @@ function HomeComponent() {
|
|
|
13887
13847
|
path: "apps/web/src/styles/globals.css",
|
|
13888
13848
|
content: globalsCss
|
|
13889
13849
|
},
|
|
13850
|
+
...shadcnFiles("apps/web", "src/styles/globals.css", { rsc: false }),
|
|
13890
13851
|
{
|
|
13891
13852
|
path: "apps/web/.gitignore",
|
|
13892
|
-
content:
|
|
13893
|
-
.vinxi/
|
|
13894
|
-
node_modules/
|
|
13895
|
-
.env.local
|
|
13896
|
-
*.log
|
|
13897
|
-
src/routeTree.gen.ts
|
|
13898
|
-
`
|
|
13853
|
+
content: gitignore
|
|
13899
13854
|
}
|
|
13900
13855
|
];
|
|
13901
13856
|
}
|
|
@@ -13944,7 +13899,7 @@ const apiTemplate = {
|
|
|
13944
13899
|
const { cache, uploads, mail: sendsMail } = options.constructs;
|
|
13945
13900
|
const bucket = storageFor();
|
|
13946
13901
|
const kv = cacheFor();
|
|
13947
|
-
|
|
13902
|
+
databaseFor();
|
|
13948
13903
|
const mail = emailFor();
|
|
13949
13904
|
const src = (path) => `~/${path}`;
|
|
13950
13905
|
const declares = !monorepo;
|
|
@@ -14243,41 +14198,6 @@ export const telescope = new Telescope({
|
|
|
14243
14198
|
storage: new InMemoryStorage({ maxEntries: 100 }),
|
|
14244
14199
|
enabled: process.env.NODE_ENV === 'development',
|
|
14245
14200
|
});
|
|
14246
|
-
`
|
|
14247
|
-
});
|
|
14248
|
-
if (options.studio && options.constructs.database) files.push({
|
|
14249
|
-
path: "src/config/studio.ts",
|
|
14250
|
-
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
14251
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
14252
|
-
import pg from 'pg';
|
|
14253
|
-
import { type Database, database } from '${declares ? src("constructs/database.ts") : constructsImport("database")}';
|
|
14254
|
-
import { envParser } from '${src("config/env.ts")}';
|
|
14255
|
-
|
|
14256
|
-
const studioConfig = envParser
|
|
14257
|
-
.create((get) => ({
|
|
14258
|
-
databaseUrl: get('${db.urlKey}').string(),
|
|
14259
|
-
}))
|
|
14260
|
-
.parse();
|
|
14261
|
-
|
|
14262
|
-
// Studio's own client, built the way the construct builds its own — the same
|
|
14263
|
-
// plugins — so the browser shows what the app's queries see.
|
|
14264
|
-
const db = new Kysely<Database>({
|
|
14265
|
-
...database.clientConfig,
|
|
14266
|
-
dialect: new PostgresDialect({
|
|
14267
|
-
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
14268
|
-
}),
|
|
14269
|
-
});
|
|
14270
|
-
|
|
14271
|
-
export const studio = new Studio<Database>({
|
|
14272
|
-
monitoring: {
|
|
14273
|
-
storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
|
|
14274
|
-
},
|
|
14275
|
-
data: {
|
|
14276
|
-
db,
|
|
14277
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
14278
|
-
},
|
|
14279
|
-
enabled: process.env.NODE_ENV === 'development',
|
|
14280
|
-
});
|
|
14281
14201
|
`
|
|
14282
14202
|
});
|
|
14283
14203
|
return files;
|
|
@@ -14325,7 +14245,7 @@ const templates = {
|
|
|
14325
14245
|
},
|
|
14326
14246
|
files: (options) => {
|
|
14327
14247
|
const { loggerType, routesStructure, name } = options;
|
|
14328
|
-
|
|
14248
|
+
databaseFor();
|
|
14329
14249
|
const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
|
|
14330
14250
|
|
|
14331
14251
|
export const logger = createLogger();
|
|
@@ -14407,40 +14327,6 @@ export const telescope = new Telescope({
|
|
|
14407
14327
|
storage: new InMemoryStorage({ maxEntries: 100 }),
|
|
14408
14328
|
enabled: process.env.NODE_ENV === 'development',
|
|
14409
14329
|
});
|
|
14410
|
-
`
|
|
14411
|
-
});
|
|
14412
|
-
if (options.studio && options.constructs.database) files.push({
|
|
14413
|
-
path: "src/config/studio.ts",
|
|
14414
|
-
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
14415
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
14416
|
-
import pg from 'pg';
|
|
14417
|
-
import type { Database } from '~/constructs/database.ts';
|
|
14418
|
-
import { envParser } from '~/config/env.ts';
|
|
14419
|
-
|
|
14420
|
-
// The key the database construct publishes — not a hand-written DATABASE_URL.
|
|
14421
|
-
const studioConfig = envParser
|
|
14422
|
-
.create((get) => ({
|
|
14423
|
-
databaseUrl: get('${db.urlKey}').string(),
|
|
14424
|
-
}))
|
|
14425
|
-
.parse();
|
|
14426
|
-
|
|
14427
|
-
// Create a Kysely instance for Studio
|
|
14428
|
-
const db = new Kysely<Database>({
|
|
14429
|
-
dialect: new PostgresDialect({
|
|
14430
|
-
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
14431
|
-
}),
|
|
14432
|
-
});
|
|
14433
|
-
|
|
14434
|
-
export const studio = new Studio<Database>({
|
|
14435
|
-
monitoring: {
|
|
14436
|
-
storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
|
|
14437
|
-
},
|
|
14438
|
-
data: {
|
|
14439
|
-
db,
|
|
14440
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
14441
|
-
},
|
|
14442
|
-
enabled: process.env.NODE_ENV === 'development',
|
|
14443
|
-
});
|
|
14444
14330
|
`
|
|
14445
14331
|
});
|
|
14446
14332
|
return files;
|
|
@@ -15161,7 +15047,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
15161
15047
|
{
|
|
15162
15048
|
type: options.yes ? null : "confirm",
|
|
15163
15049
|
name: "telescope",
|
|
15164
|
-
message: "Include Telescope (
|
|
15050
|
+
message: "Include Telescope (request, exception and log recording)?",
|
|
15165
15051
|
initial: true
|
|
15166
15052
|
},
|
|
15167
15053
|
{
|
|
@@ -15216,13 +15102,11 @@ async function initCommand(projectName, options = {}) {
|
|
|
15216
15102
|
};
|
|
15217
15103
|
const pkgManager = options.pm ? options.pm : options.yes ? "pnpm" : answers.packageManager ?? detectedPkgManager;
|
|
15218
15104
|
const deployTarget = options.deploy ?? (options.yes ? "none" : answers.deployTarget ?? "none");
|
|
15219
|
-
const database = constructs.database;
|
|
15220
15105
|
const frontendFramework = isFullstack ? options.yes ? "nextjs" : answers.frontendFramework ?? "nextjs" : void 0;
|
|
15221
15106
|
const templateOptions = {
|
|
15222
15107
|
name,
|
|
15223
15108
|
template,
|
|
15224
15109
|
telescope: options.yes ? true : answers.telescope ?? true,
|
|
15225
|
-
studio: database,
|
|
15226
15110
|
loggerType: options.yes ? "pino" : answers.loggerType ?? "pino",
|
|
15227
15111
|
routesStructure: options.yes ? "centralized-endpoints" : answers.routesStructure ?? "centralized-endpoints",
|
|
15228
15112
|
monorepo,
|
|
@@ -15259,7 +15143,6 @@ async function initCommand(projectName, options = {}) {
|
|
|
15259
15143
|
] : [];
|
|
15260
15144
|
const webAppFiles = isFullstack ? frontendFramework === "tanstack-start" ? generateTanStackWebFiles(templateOptions) : frontendFramework === "expo" ? generateExpoAppFiles(templateOptions) : generateWebAppFiles(templateOptions) : [];
|
|
15261
15145
|
const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
|
|
15262
|
-
const uiPackageFiles = isFullstack ? generateUiPackageFiles(templateOptions) : [];
|
|
15263
15146
|
for (const { path, content } of rootFiles) {
|
|
15264
15147
|
const fullPath = (0, node_path.join)(targetDir, path);
|
|
15265
15148
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
@@ -15280,11 +15163,6 @@ async function initCommand(projectName, options = {}) {
|
|
|
15280
15163
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
15281
15164
|
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
15282
15165
|
}
|
|
15283
|
-
for (const { path, content } of uiPackageFiles) {
|
|
15284
|
-
const fullPath = (0, node_path.join)(targetDir, path);
|
|
15285
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
15286
|
-
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
15287
|
-
}
|
|
15288
15166
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
15289
15167
|
const local = templateOptions.stages.local;
|
|
15290
15168
|
const devSecrets = createStageSecrets(local, [], { projectName: name });
|
|
@@ -15387,8 +15265,7 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
15387
15265
|
}
|
|
15388
15266
|
}
|
|
15389
15267
|
console.log(` ├── packages/`);
|
|
15390
|
-
console.log(` │
|
|
15391
|
-
if (isFullstackTemplate(options.template)) console.log(` │ └── ui/ # Shared UI components`);
|
|
15268
|
+
console.log(` │ └── models/ # Shared Zod schemas`);
|
|
15392
15269
|
console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
|
|
15393
15270
|
console.log(` ├── gkm.config.ts # Workspace config`);
|
|
15394
15271
|
console.log(` └── turbo.json # Turbo config`);
|
|
@@ -16142,7 +16019,7 @@ function testDatabases(reconciled) {
|
|
|
16142
16019
|
* starts without the reference data the app needs.
|
|
16143
16020
|
*/
|
|
16144
16021
|
async function migrateTestStage(workspace, env) {
|
|
16145
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
16022
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
|
|
16146
16023
|
const { discover } = await Promise.resolve().then(() => require("./discover-DXNviAh-.cjs")).then((n) => n.discover_exports);
|
|
16147
16024
|
const sources = {};
|
|
16148
16025
|
const manifest = await discover({
|
|
@@ -16573,7 +16450,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
16573
16450
|
try {
|
|
16574
16451
|
const globalOptions = program.opts();
|
|
16575
16452
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16576
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16453
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
|
|
16577
16454
|
await migrateCommand({
|
|
16578
16455
|
...options,
|
|
16579
16456
|
...construct ? { construct } : {}
|
|
@@ -16587,7 +16464,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16587
16464
|
try {
|
|
16588
16465
|
const globalOptions = program.opts();
|
|
16589
16466
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16590
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16467
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
|
|
16591
16468
|
await seedCommand({
|
|
16592
16469
|
...options,
|
|
16593
16470
|
...construct ? { construct } : {}
|
|
@@ -16601,7 +16478,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16601
16478
|
try {
|
|
16602
16479
|
const globalOptions = program.opts();
|
|
16603
16480
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16604
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16481
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
|
|
16605
16482
|
await migrationCommand({
|
|
16606
16483
|
construct,
|
|
16607
16484
|
...name ? { name } : {}
|
|
@@ -16699,7 +16576,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
16699
16576
|
try {
|
|
16700
16577
|
const globalOptions = program.opts();
|
|
16701
16578
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16702
|
-
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
16579
|
+
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-10Srqiix.cjs"));
|
|
16703
16580
|
await encryptionRotateCommand(construct, options);
|
|
16704
16581
|
} catch (error) {
|
|
16705
16582
|
console.error(formatError(error));
|
|
@@ -16710,7 +16587,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
16710
16587
|
try {
|
|
16711
16588
|
const globalOptions = program.opts();
|
|
16712
16589
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16713
|
-
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
16590
|
+
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-10Srqiix.cjs"));
|
|
16714
16591
|
await encryptionRetireCommand(construct, key, options);
|
|
16715
16592
|
} catch (error) {
|
|
16716
16593
|
console.error(formatError(error));
|
|
@@ -16762,7 +16639,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16762
16639
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16763
16640
|
process.exit(1);
|
|
16764
16641
|
}
|
|
16765
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
16642
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-BbPJVCto.cjs")).then((n) => n.workspace_exports);
|
|
16766
16643
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16767
16644
|
if (!result) {
|
|
16768
16645
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16821,7 +16698,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16821
16698
|
try {
|
|
16822
16699
|
const globalOptions = program.opts();
|
|
16823
16700
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16824
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16701
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-DtRUkHN8.cjs"));
|
|
16825
16702
|
await deployGithubCommand(options);
|
|
16826
16703
|
} catch (error) {
|
|
16827
16704
|
console.error(formatError(error));
|
|
@@ -16924,6 +16801,16 @@ program.command("state:show").description("Show deployment state for a stage").r
|
|
|
16924
16801
|
process.exit(1);
|
|
16925
16802
|
}
|
|
16926
16803
|
});
|
|
16804
|
+
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) => {
|
|
16805
|
+
try {
|
|
16806
|
+
const globalOptions = program.opts();
|
|
16807
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16808
|
+
await stateUnlockCommand(options);
|
|
16809
|
+
} catch (error) {
|
|
16810
|
+
console.error(formatError(error));
|
|
16811
|
+
process.exit(1);
|
|
16812
|
+
}
|
|
16813
|
+
});
|
|
16927
16814
|
program.command("state:diff").description("Compare local and remote deployment state").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
16928
16815
|
try {
|
|
16929
16816
|
const globalOptions = program.opts();
|
|
@@ -16946,12 +16833,19 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16946
16833
|
});
|
|
16947
16834
|
program.parse();
|
|
16948
16835
|
//#endregion
|
|
16836
|
+
exports.DocumentStateStore = DocumentStateStore;
|
|
16837
|
+
exports.StageStateMissing = StageStateMissing;
|
|
16838
|
+
exports.StateLocked = StateLocked;
|
|
16839
|
+
exports.StateStoreBusy = StateStoreBusy;
|
|
16840
|
+
exports.StateStoreWithoutLocking = StateStoreWithoutLocking;
|
|
16841
|
+
exports.StateVersionConflict = StateVersionConflict;
|
|
16949
16842
|
Object.defineProperty(exports, "appPorts_exports", {
|
|
16950
16843
|
enumerable: true,
|
|
16951
16844
|
get: function() {
|
|
16952
16845
|
return appPorts_exports;
|
|
16953
16846
|
}
|
|
16954
16847
|
});
|
|
16848
|
+
exports.contentVersion = contentVersion;
|
|
16955
16849
|
Object.defineProperty(exports, "encryption_exports", {
|
|
16956
16850
|
enumerable: true,
|
|
16957
16851
|
get: function() {
|
|
@@ -16960,6 +16854,7 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
16960
16854
|
});
|
|
16961
16855
|
exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
|
|
16962
16856
|
exports.getHostingerToken = getHostingerToken;
|
|
16857
|
+
exports.newLockHolder = newLockHolder;
|
|
16963
16858
|
exports.slug = slug;
|
|
16964
16859
|
exports.stamp = stamp;
|
|
16965
16860
|
|