@geekmidas/cli 10.0.0-alpha.45 → 10.0.0-alpha.47
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 +144 -183
- package/dist/{OpenApiTsGenerator-B4Kj_qNU.cjs → OpenApiTsGenerator-BN0PIZxd.cjs} +17 -71
- package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-CEh3EKN3.mjs → OpenApiTsGenerator-BzIGy8KE.mjs} +17 -71
- package/dist/OpenApiTsGenerator-BzIGy8KE.mjs.map +1 -0
- package/dist/backends-B9V2XD2M.cjs +82 -0
- package/dist/backends-B9V2XD2M.cjs.map +1 -0
- package/dist/backends-CSuLl0YP.mjs +59 -0
- package/dist/backends-CSuLl0YP.mjs.map +1 -0
- package/dist/{config-CJ-PAb7M.cjs → config-CsYSyUya.cjs} +3 -3
- package/dist/{config-CJ-PAb7M.cjs.map → config-CsYSyUya.cjs.map} +1 -1
- package/dist/{config-5watB1D0.mjs → config-D8J5rrTb.mjs} +3 -3
- package/dist/{config-5watB1D0.mjs.map → config-D8J5rrTb.mjs.map} +1 -1
- 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/{discover-BLhf7V_J.mjs → discover-Cf2jAcgF.mjs} +4 -56
- package/dist/discover-Cf2jAcgF.mjs.map +1 -0
- package/dist/{discover-B0f_kuNN.cjs → discover-DXNviAh-.cjs} +3 -79
- package/dist/discover-DXNviAh-.cjs.map +1 -0
- package/dist/{emit-Bzq7JsMP.mjs → emit-C1e8qD08.mjs} +12 -43
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-BdoPFjZf.cjs → emit-Dmc1nvdS.cjs} +11 -54
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-C7OGMve_.cjs +91 -0
- package/dist/encryption-C7OGMve_.cjs.map +1 -0
- package/dist/encryption-DyQnPsZN.mjs +90 -0
- package/dist/encryption-DyQnPsZN.mjs.map +1 -0
- package/dist/{github-D5MthUlR.mjs → github-BfpadQWQ.mjs} +3 -3
- package/dist/{github-D5MthUlR.mjs.map → github-BfpadQWQ.mjs.map} +1 -1
- package/dist/{github-BAr8WGkR.cjs → github-BrI1tv6u.cjs} +3 -3
- package/dist/{github-BAr8WGkR.cjs.map → github-BrI1tv6u.cjs.map} +1 -1
- package/dist/{index-B3PeGOQp.d.cts → index-BQhJXpaQ.d.mts} +2 -2
- package/dist/{index-B3PeGOQp.d.cts.map → index-BQhJXpaQ.d.mts.map} +1 -1
- package/dist/{index-BOyaGcCm.d.mts → index-DHJePE0y.d.cts} +2 -2
- package/dist/{index-BOyaGcCm.d.mts.map → index-DHJePE0y.d.cts.map} +1 -1
- package/dist/index.cjs +444 -670
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +442 -668
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-PJi4b1ga.cjs → migrate-BgEY9pdP.cjs} +5 -5
- package/dist/{migrate-PJi4b1ga.cjs.map → migrate-BgEY9pdP.cjs.map} +1 -1
- package/dist/{migrate-BRklCm7b.mjs → migrate-DhxTIA0z.mjs} +5 -5
- package/dist/{migrate-BRklCm7b.mjs.map → migrate-DhxTIA0z.mjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/{ready-ZqOuNvKD.mjs → ready-WfT-eWlm.mjs} +50 -8
- package/dist/ready-WfT-eWlm.mjs.map +1 -0
- package/dist/{ready-3RfHxdA0.cjs → ready-lr2Fm-ep.cjs} +68 -8
- package/dist/ready-lr2Fm-ep.cjs.map +1 -0
- package/dist/reconcile/public.cjs +2 -4
- package/dist/reconcile/public.d.cts +6 -10
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +6 -10
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -3
- package/dist/{types-0R124hrY.d.mts → types-BbrNu83u.d.mts} +14 -109
- package/dist/{types-0R124hrY.d.mts.map → types-BbrNu83u.d.mts.map} +1 -1
- package/dist/{types-DfdYe31u.d.cts → types-DNqEsctD.d.cts} +14 -109
- package/dist/{types-DfdYe31u.d.cts.map → types-DNqEsctD.d.cts.map} +1 -1
- 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-RYSYtHP7.cjs → workspace-DEibcoBe.cjs} +11 -25
- package/dist/{workspace-RYSYtHP7.cjs.map → workspace-DEibcoBe.cjs.map} +1 -1
- package/dist/{workspace-DOmIhiau.mjs → workspace-DNzWgM1J.mjs} +11 -25
- package/dist/{workspace-DOmIhiau.mjs.map → workspace-DNzWgM1J.mjs.map} +1 -1
- package/dist/{workspace-BKytVCzb.mjs → workspace-cb6U29EL.mjs} +59 -81
- package/dist/workspace-cb6U29EL.mjs.map +1 -0
- package/dist/{workspace-dV6f-3vv.cjs → workspace-vafrzZTC.cjs} +65 -105
- package/dist/workspace-vafrzZTC.cjs.map +1 -0
- package/package.json +14 -14
- package/dist/OpenApiTsGenerator-B4Kj_qNU.cjs.map +0 -1
- package/dist/OpenApiTsGenerator-CEh3EKN3.mjs.map +0 -1
- package/dist/discover-B0f_kuNN.cjs.map +0 -1
- package/dist/discover-BLhf7V_J.mjs.map +0 -1
- package/dist/emit-BdoPFjZf.cjs.map +0 -1
- package/dist/emit-Bzq7JsMP.mjs.map +0 -1
- package/dist/ready-3RfHxdA0.cjs.map +0 -1
- package/dist/ready-ZqOuNvKD.mjs.map +0 -1
- package/dist/workspace-BKytVCzb.mjs.map +0 -1
- package/dist/workspace-dV6f-3vv.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-CJ-PAb7M.cjs","names":["kebabCase","existsSync","dirname","parse","join","isWorkspaceConfig","processConfig","allConstructGlobs","discover","sep","resolve","realpathSync","getAppGkmConfig"],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app, and so is every\n * `rest-api` — one surface, one container, at the `path` it declared.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\t// Three kinds are apps: a site, a mobile app and an HTTP surface. A worker\n\t\t// is not one — it names the process that runs a cron or a subscriber, and\n\t\t// that process is the app's server, which already exists. Everything else\n\t\t// is a resource one of them uses.\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. Each says where it lives.\n\t\tconst spec: AppSpec =\n\t\t\tdeclaration.kind === 'site' || declaration.kind === 'mobile-app'\n\t\t\t\t? declaration.app\n\t\t\t\t: {\n\t\t\t\t\t\tpath: declaration.path,\n\t\t\t\t\t\t...(declaration.telescope ? { telescope: true } : {}),\n\t\t\t\t\t};\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype:\n\t\t\t\tdeclaration.kind === 'site'\n\t\t\t\t\t? 'web'\n\t\t\t\t\t: declaration.kind === 'mobile-app'\n\t\t\t\t\t\t? 'mobile'\n\t\t\t\t\t\t: 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? declaration.variant,\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.studio !== undefined ? { studio: spec.studio } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\t// A site names its edges `dependencies`; a surface names them `calls`.\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'rest-api'\n\t\t\t\t? (declaration.calls ?? [])\n\t\t\t\t: (declaration.dependencies ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config-CsYSyUya.cjs","names":["kebabCase","existsSync","dirname","parse","join","isWorkspaceConfig","processConfig","allConstructGlobs","discover","sep","resolve","realpathSync","getAppGkmConfig"],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app, and so is every\n * `rest-api` — one surface, one container, at the `path` it declared.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\t// Three kinds are apps: a site, a mobile app and an HTTP surface. A worker\n\t\t// is not one — it names the process that runs a cron or a subscriber, and\n\t\t// that process is the app's server, which already exists. Everything else\n\t\t// is a resource one of them uses.\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. Each says where it lives.\n\t\tconst spec: AppSpec =\n\t\t\tdeclaration.kind === 'site' || declaration.kind === 'mobile-app'\n\t\t\t\t? declaration.app\n\t\t\t\t: {\n\t\t\t\t\t\tpath: declaration.path,\n\t\t\t\t\t\t...(declaration.telescope ? { telescope: true } : {}),\n\t\t\t\t\t};\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype:\n\t\t\t\tdeclaration.kind === 'site'\n\t\t\t\t\t? 'web'\n\t\t\t\t\t: declaration.kind === 'mobile-app'\n\t\t\t\t\t\t? 'mobile'\n\t\t\t\t\t\t: 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? declaration.variant,\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.studio !== undefined ? { studio: spec.studio } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\t// A site names its edges `dependencies`; a surface names them `calls`.\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'rest-api'\n\t\t\t\t? (declaration.calls ?? [])\n\t\t\t\t: (declaration.dependencies ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as discover } from "./discover-
|
|
2
|
-
import { h as isWorkspaceConfig, l as processConfig, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-
|
|
1
|
+
import { t as discover } from "./discover-Cf2jAcgF.mjs";
|
|
2
|
+
import { h as isWorkspaceConfig, l as processConfig, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-DNzWgM1J.mjs";
|
|
3
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { dirname, join, parse, resolve, sep } from "node:path";
|
|
5
5
|
import { kebabCase } from "@geekmidas/manifest";
|
|
@@ -454,4 +454,4 @@ async function loadWorkspaceAppInfo(cwd = process.cwd()) {
|
|
|
454
454
|
//#endregion
|
|
455
455
|
export { defineConfig as a, loadWorkspaceAppInfo as c, parseModuleConfig as d, appKey as f, WorkspaceDeclaresNoConstructs as i, loadWorkspaceConfig as l, NotABackendApp as n, loadAppConfig as o, derivedApps as p, NotInAnApp as r, loadConfig as s, ConfigNotFound as t, loadWorkspaceSettings as u };
|
|
456
456
|
|
|
457
|
-
//# sourceMappingURL=config-
|
|
457
|
+
//# sourceMappingURL=config-D8J5rrTb.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-5watB1D0.mjs","names":[],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app, and so is every\n * `rest-api` — one surface, one container, at the `path` it declared.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\t// Three kinds are apps: a site, a mobile app and an HTTP surface. A worker\n\t\t// is not one — it names the process that runs a cron or a subscriber, and\n\t\t// that process is the app's server, which already exists. Everything else\n\t\t// is a resource one of them uses.\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. Each says where it lives.\n\t\tconst spec: AppSpec =\n\t\t\tdeclaration.kind === 'site' || declaration.kind === 'mobile-app'\n\t\t\t\t? declaration.app\n\t\t\t\t: {\n\t\t\t\t\t\tpath: declaration.path,\n\t\t\t\t\t\t...(declaration.telescope ? { telescope: true } : {}),\n\t\t\t\t\t};\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype:\n\t\t\t\tdeclaration.kind === 'site'\n\t\t\t\t\t? 'web'\n\t\t\t\t\t: declaration.kind === 'mobile-app'\n\t\t\t\t\t\t? 'mobile'\n\t\t\t\t\t\t: 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? declaration.variant,\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.studio !== undefined ? { studio: spec.studio } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\t// A site names its edges `dependencies`; a surface names them `calls`.\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'rest-api'\n\t\t\t\t? (declaration.calls ?? [])\n\t\t\t\t: (declaration.dependencies ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,aAAwC;CAC7C,QAAQ;CACR,MAAM;CACN,UAAU;AACX;;;;;;;;AASA,SAAgB,OAAO,IAAoB;CAC1C,OAAO,UAAU,EAAE;AACpB;;;;;;;;;AAUA,SAAgB,OACf,UACA,IACqB;CACrB,MAAM,cAAc,SAAS;CAC7B,IAAI,CAAC,eAAe,YAAY,SAAS,YAAY,OAAO,KAAA;CAE5D,OAAO;AACR;;;;;;;;;;AAWA,SAAS,YAAY,MAAiD;CACrE,MAAM,QACL,MACA,QAC8B;EAC9B,IAAI,SAAS,YAAY,IAAI,IAAI,SAAS,QAAQ,IAAI;EACtD,IAAI,OAAO,IAAI;EACf;CACD;CAEA,MAAM,UAAU,OAAO,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,OAAO,OAAO;EACrE,MAAM,CAAC,IAAI,IAAI,MAAM,KAAK,OAAO,CAAC;EAClC,MAAM,CAAC,IAAI,IAAI,MAAM,KAAK,OAAO,CAAC;EAClC,OAAO,KAAK,MAAM,KAAK,MAAM,GAAG,cAAc,EAAE;CACjD,CAAC;CAED,MAAM,QAAQ,IAAI,IACjB,QAAQ,KAAK,GAAG,SAAS,IAAI,IAAI,CAAC,CAAC,QAAQ,MAAmB,CAAC,CAAC,CAAC,CAClE;CAEA,IAAI,OAAO;CACX,KAAK,MAAM,GAAG,QAAQ,SAAS;EAC9B,IAAI,IAAI,MAAM;EACd,OAAO,MAAM,IAAI,IAAI,GAAG,QAAQ;EAChC,IAAI,OAAO;EACX,MAAM,IAAI,IAAI;CACf;AACD;;;;;;;AAQA,SAAS,SAAS,MAAiD;CAClE,MAAM,QAAQ,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,OAAO,EAAE,SAAS,KAAK;CACrE,IAAI,MAAM,WAAW,GAAG;CACxB,IAAI,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,GAAG;CAEnC,MAAM,SACL,MAAM,MAAM,CAAC,UAAU,SAAS,KAAK,MACpC,MAAM,WAAW,IAAI,MAAM,KAAK,KAAA;CAClC,IAAI,QAAQ,OAAO,EAAE,CAAC,OAAO;AAC9B;AAEA,SAAgB,YACf,UACA,WACsC;CACtC,MAAM,OAA4C,CAAC;CACnD,MAAM,gBAA8B,UAAU,QAAQ,WAAW;CAEjE,KAAK,MAAM,CAAC,IAAI,gBAAgB,OAAO,QAAQ,QAAQ,GAAG;EAKzD,IACC,YAAY,SAAS,UACrB,YAAY,SAAS,gBACrB,YAAY,SAAS,YAErB;EAGD,MAAM,OACL,YAAY,SAAS,UAAU,YAAY,SAAS,eACjD,YAAY,MACZ;GACA,MAAM,YAAY;GAClB,GAAI,YAAY,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;EACpD;EAEH,MAAM,OAAO,OAAO,EAAE;EAGtB,MAAM,aAAa,UAAU,KAAK;EAElC,KAAK,QAAQ;GACZ,GAAG;GACH,MACC,YAAY,SAAS,SAClB,QACA,YAAY,SAAS,eACpB,WACA;GACL,MAAM,KAAK;GACX,MAAM,KAAK,QAAQ,YAAY,QAAQ;GACvC,cAAc,CAAC;GACf,sBAAsB,YAAY,wBAAwB;GAC1D,GAAI,YAAY,SAAS,SACtB;IACA,WAAW,YAAY,aAAa,WAAW,YAAY;IAC3D,GAAI,YAAY,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC9C,IACC,CAAC;GACJ,GAAI,YAAY,SAAS,eACtB;IACA,WAAW,YAAY,aAAa,YAAY;IAChD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC9C,IACC,CAAC;GAIJ,GAAI,KAAK,cAAc,KAAA,IAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACpE,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;GAC3D,GAAI,KAAK,YAAY,KAAA,IAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;GAC9D,GAAI,KAAK,YAAY,KAAA,IAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;GAC9D,GAAI,KAAK,QAAQ,KAAA,IAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;GAClD,GAAI,KAAK,UAAU,KAAA,IAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;GACxD,GAAI,YAAY,SACb,EAAE,QAAQ,WAAW,OAA0B,IAC/C,CAAC;EACL;CACD;CAKA,KAAK,MAAM,CAAC,IAAI,gBAAgB,OAAO,QAAQ,QAAQ,GAAG;EACzD,IACC,YAAY,SAAS,UACrB,YAAY,SAAS,gBACrB,YAAY,SAAS,YAErB;EAED,MAAM,MAAM,KAAK,OAAO,EAAE;EAC1B,IAAI,CAAC,KAAK;EAGV,MAAM,QACL,YAAY,SAAS,aACjB,YAAY,SAAS,CAAC,IACtB,YAAY,gBAAgB,CAAC;EAElC,MAAM,wBAAQ,IAAI,IAAY;EAC9B,KAAK,MAAM,QAAQ,OAAO;GACzB,IAAI,KAAK,SAAS,YAAY;GAC9B,MAAM,OAAO,OAAO,UAAU,KAAK,MAAM;GACzC,IAAI,CAAC,MAAM;GACX,MAAM,MAAM,OAAO,IAAI;GACvB,IAAI,QAAQ,OAAO,EAAE,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG;EACnD;EAEA,IAAI,eAAe,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK;CACpC;CAEA,SAAS,IAAI;CACb,YAAY,IAAI;CAKhB,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,UAAU,IAAI,GACtD,IAAI,CAAC,KAAK,OAAO,KAAK,QAAQ;CAG/B,OAAO;AACR;;;;;;;;;;;;;;;;;;;;AC1MA,SAAgB,aAAa,QAA8B;CAC1D,OAAO;AACR;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,kBACf,cACA,cACqB;CACrB,MAAM,QAAQ,aAAa,MAAM,GAAG;CACpC,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,aAAa,MAAM;CAOzB,OAAO;EAAE;EAAM,eANO,CAAC,aACpB,eACA,eAAe,eACd,KAAK,aAAa,MAClB,KAAK,WAAW,MAAM,aAAa;CAEV;AAC9B;;;;;;;;AAcA,SAAS,eAAe,KAAoC;CAC3D,MAAM,QAAQ;EAAC;EAAmB;EAAiB;CAAe;CAGlE,MAAM,gBAAgB,QAAQ,IAAI;CAClC,IAAI,iBAAiB,WAAW,aAAa,GAC5C,OAAO;EACN,YAAY;EACZ,eAAe,QAAQ,aAAa;CACrC;CAID,IAAI,aAAa;CACjB,MAAM,EAAE,SAAS,MAAM,UAAU;CAEjC,OAAO,eAAe,MAAM;EAC3B,KAAK,MAAM,QAAQ,OAAO;GACzB,MAAM,aAAa,KAAK,YAAY,IAAI;GACxC,IAAI,WAAW,UAAU,GACxB,OAAO;IACN;IACA,eAAe;GAChB;EAEF;EACA,aAAa,QAAQ,UAAU;CAChC;CAEA,MAAM,IAAI,eAAe,GAAG;AAC7B;;AAGA,IAAa,iBAAb,cAAoC,MAAM;CACpB;CAArB,YAAY,KAAsB;EACjC,MACC,mHACD;EAHoB,KAAA,MAAA;EAIpB,KAAK,OAAO;CACb;AACD;;;;AAUA,eAAe,cAAc,KAAuC;CACnE,MAAM,EAAE,YAAY,kBAAkB,eAAe,GAAG;CAExD,IAAI;EAEH,OAAO;GACN,SAAQ,MAFY,OAAO,YAAA,CAEZ;GACf;EACD;CACD,SAAS,OAAO;EACf,MAAM,IAAI,MAAM,0BAA2B,MAAgB,SAAS;CACrE;AACD;;;;;;;AAQA,eAAsB,WACrB,MAAc,QAAQ,IAAI,GACL;CACrB,MAAM,EAAE,WAAW,MAAM,cAAc,GAAG;CAG1C,IAAI,kBAAkB,MAAM,GAC3B,MAAM,IAAI,MACT,sEACD;CAGD,OAAO;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,eAAsB,sBACrB,MAAc,QAAQ,IAAI,GACW;CACrC,MAAM,EAAE,QAAQ,kBAAkB,MAAM,cAAc,GAAG;CACzD,OAAO,cAAc,QAAQ,aAAa,CAAC,CAAC;AAC7C;AAEA,eAAsB,oBACrB,MAAc,QAAQ,IAAI,GACF;CACxB,MAAM,EAAE,QAAQ,kBAAkB,MAAM,cAAc,GAAG;CACzD,OAAO,gBAAgB,cAAc,QAAQ,aAAa,CAAC;AAC5D;;;;;;;;;;;;;;;;;;;;AAqBA,eAAe,gBAAgB,QAA6C;CAC3E,MAAM,QAAQ,kBAAkB,OAAO,SAAS;CAChD,IAAI,MAAM,WAAW,GACpB,MAAM,IAAI,8BAA8B,OAAO,UAAU,IAAI;CAG9D,IAAI;EACH,MAAM,WAAW,MAAM,SAAS;GAC/B,UAAU;GACV,KAAK,OAAO,UAAU;EACvB,CAAC;EAED,OAAO;GACN,GAAG;GACH;GACA,WAAW;IACV,GAAG,OAAO;IACV,MAAM,YAAY,UAAU,OAAO,SAAS;GAC7C;EACD;CACD,SAAS,OAAO;EACf,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,QAAQ,KACP,kEAAkE,SACnE;EACA,OAAO;CACR;AACD;;;;;;;;AASA,IAAa,gCAAb,cAAmD,MAAM;CACnC;CAArB,YAAY,MAAuB;EAClC,MACC,oBAAoB,KAAK,8NAI1B;EANoB,KAAA,OAAA;EAOpB,KAAK,OAAO;CACb;AACD;;;;;;;;;;AAoBA,SAAS,oBACR,cACA,KACwD;CACxD,MAAM,OAAO,KAAK,GAAG;CACrB,IAAI;CAEJ,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,aAAa,UAAU,IAAI,GAAG;EACrE,MAAM,MAAM,KAAK,KAAK,aAAa,UAAU,MAAM,IAAI,IAAI,CAAC;EAE5D,KADe,SAAS,OAAO,KAAK,WAAW,GAAG,MAAM,KAAK,OAC9C,CAAC,SAAS,IAAI,SAAS,MAAM,IAAI,SAC/C,QAAQ;GAAE;GAAK;GAAK;EAAI;CAE1B;CAEA,OAAO,SAAS;EAAE,KAAK,MAAM;EAAK,KAAK,MAAM;CAAI;AAClD;;AAGA,SAAS,KAAK,MAAsB;CACnC,MAAM,WAAW,QAAQ,IAAI;CAC7B,OAAO,WAAW,QAAQ,IAAI,aAAa,QAAQ,IAAI;AACxD;;AAGA,IAAa,aAAb,cAAgC,MAAM;CAE3B;CACA;CAFV,YACC,KACA,MACC;EACD,MACC,GAAG,IAAI,yCACN,OAAO,QAAQ,IAAI,CAAC,CAClB,KAAK,CAAC,KAAK,UAAU,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CACxC,KAAK,IAAI,IACX,GACF;EATS,KAAA,MAAA;EACA,KAAA,OAAA;EAST,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,SAAS,cAAoD;CACrE,OAAO,OAAO,YACb,OAAO,QAAQ,aAAa,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAC/D,KACA,IAAI,IACL,CAAC,CACF;AACD;;;;;;;;;;;;;;AAeA,eAAsB,cACrB,MAAc,QAAQ,IAAI,GACC;CAC3B,MAAM,EAAE,SAAS,KAAK,WAAW,kBAChC,MAAM,qBAAqB,GAAG;CAE/B,MAAM,YAAY,gBAAgB,WAAW,OAAO;CACpD,IAAI,CAAC,WAAW,MAAM,IAAI,eAAe,OAAO;CAEhD,OAAO;EACN;EACA;EACA;EACA;EACA;EACA,SAAS,KAAK,eAAe,IAAI,IAAI;CACtC;AACD;;AAGA,IAAa,iBAAb,cAAoC,MAAM;CACpB;CAArB,YAAY,SAA0B;EACrC,MACC,QAAQ,QAAQ,uDACjB;EAHoB,KAAA,UAAA;EAIpB,KAAK,OAAO;CACb;AACD;;;;;;;;AAiBA,eAAsB,qBACrB,MAAc,QAAQ,IAAI,GACE;CAC5B,MAAM,EAAE,QAAQ,kBAAkB,MAAM,cAAc,GAAG;CACzD,MAAM,eAAe,MAAM,gBAC1B,cAAc,QAAQ,aAAa,CACpC;CAEA,MAAM,WAAW,oBAAoB,cAAc,GAAG;CACtD,IAAI,CAAC,UAAU,MAAM,IAAI,WAAW,KAAK,SAAS,YAAY,CAAC;CAE/D,OAAO;EACN,SAAS,SAAS;EAClB,KAAK,SAAS;EACd,WAAW,aAAa;EACxB;CACD;AACD"}
|
|
1
|
+
{"version":3,"file":"config-D8J5rrTb.mjs","names":[],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app, and so is every\n * `rest-api` — one surface, one container, at the `path` it declared.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\t// Three kinds are apps: a site, a mobile app and an HTTP surface. A worker\n\t\t// is not one — it names the process that runs a cron or a subscriber, and\n\t\t// that process is the app's server, which already exists. Everything else\n\t\t// is a resource one of them uses.\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. Each says where it lives.\n\t\tconst spec: AppSpec =\n\t\t\tdeclaration.kind === 'site' || declaration.kind === 'mobile-app'\n\t\t\t\t? declaration.app\n\t\t\t\t: {\n\t\t\t\t\t\tpath: declaration.path,\n\t\t\t\t\t\t...(declaration.telescope ? { telescope: true } : {}),\n\t\t\t\t\t};\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype:\n\t\t\t\tdeclaration.kind === 'site'\n\t\t\t\t\t? 'web'\n\t\t\t\t\t: declaration.kind === 'mobile-app'\n\t\t\t\t\t\t? 'mobile'\n\t\t\t\t\t\t: 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? declaration.variant,\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.studio !== undefined ? { studio: spec.studio } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\t// A site names its edges `dependencies`; a surface names them `calls`.\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'rest-api'\n\t\t\t\t? (declaration.calls ?? [])\n\t\t\t\t: (declaration.dependencies ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|
package/dist/config.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_config = require("./config-
|
|
3
|
-
const require_workspace = require("./workspace-
|
|
2
|
+
const require_config = require("./config-CsYSyUya.cjs");
|
|
3
|
+
const require_workspace = require("./workspace-DEibcoBe.cjs");
|
|
4
4
|
exports.ConfigNotFound = require_config.ConfigNotFound;
|
|
5
5
|
exports.NotABackendApp = require_config.NotABackendApp;
|
|
6
6
|
exports.NotInAnApp = require_config.NotInAnApp;
|
package/dist/config.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as WorkspaceConfig, g as LoadedConfig, r as GkmConfig, v as NormalizedAppConfig, y as NormalizedWorkspace } from "./types-
|
|
2
|
-
import { i as defineWorkspace } from "./index-
|
|
1
|
+
import { S as WorkspaceConfig, g as LoadedConfig, r as GkmConfig, v as NormalizedAppConfig, y as NormalizedWorkspace } from "./types-DNqEsctD.cjs";
|
|
2
|
+
import { i as defineWorkspace } from "./index-DHJePE0y.cjs";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Define GKM configuration with full TypeScript support.
|
package/dist/config.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as WorkspaceConfig, g as LoadedConfig, r as GkmConfig, v as NormalizedAppConfig, y as NormalizedWorkspace } from "./types-
|
|
2
|
-
import { i as defineWorkspace } from "./index-
|
|
1
|
+
import { S as WorkspaceConfig, g as LoadedConfig, r as GkmConfig, v as NormalizedAppConfig, y as NormalizedWorkspace } from "./types-BbrNu83u.mjs";
|
|
2
|
+
import { i as defineWorkspace } from "./index-BQhJXpaQ.mjs";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Define GKM configuration with full TypeScript support.
|
package/dist/config.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as defineConfig, c as loadWorkspaceAppInfo, d as parseModuleConfig, i as WorkspaceDeclaresNoConstructs, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-
|
|
2
|
-
import { i as defineWorkspace } from "./workspace-
|
|
1
|
+
import { a as defineConfig, c as loadWorkspaceAppInfo, d as parseModuleConfig, i as WorkspaceDeclaresNoConstructs, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-D8J5rrTb.mjs";
|
|
2
|
+
import { i as defineWorkspace } from "./workspace-DNzWgM1J.mjs";
|
|
3
3
|
export { ConfigNotFound, NotABackendApp, NotInAnApp, WorkspaceDeclaresNoConstructs, defineConfig, defineWorkspace, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, loadWorkspaceSettings, parseModuleConfig };
|
|
@@ -3,51 +3,6 @@ import { relative } from "node:path";
|
|
|
3
3
|
import { assertDerivations, canonicalId } from "@geekmidas/manifest";
|
|
4
4
|
import fg from "fast-glob";
|
|
5
5
|
import kebabCase$1 from "lodash.kebabcase";
|
|
6
|
-
//#region src/types.ts
|
|
7
|
-
function isPartitionedRoutes(routes) {
|
|
8
|
-
return typeof routes === "object" && routes !== null && !Array.isArray(routes) && "paths" in routes;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* The backend a project gets when it says nothing, by where it deploys.
|
|
12
|
-
*
|
|
13
|
-
* Flat defaults could not be right for both: `upstash` on a single box means a
|
|
14
|
-
* SaaS round-trip for a cache the database could have held, and `minio` on AWS
|
|
15
|
-
* means running a bucket beside S3. So the default is a function of the target,
|
|
16
|
-
* and an app that says nothing gets the obvious answer in both places — which
|
|
17
|
-
* is the whole of what "zero config" can mean once there is more than one
|
|
18
|
-
* target.
|
|
19
|
-
*
|
|
20
|
-
* `server` covers Dokploy and a bare host alike: the question these answer is
|
|
21
|
-
* whether the app is next to a managed cloud or next to its own containers, and
|
|
22
|
-
* on that question the two are the same.
|
|
23
|
-
*
|
|
24
|
-
* A per-construct, per-stage override is the layer above this — see
|
|
25
|
-
* `constructs-outstanding.md` — so two buckets in one app can differ. This is
|
|
26
|
-
* only what happens when nobody has said anything at all.
|
|
27
|
-
*/
|
|
28
|
-
const DEFAULT_CACHE = {
|
|
29
|
-
aws: "upstash",
|
|
30
|
-
server: "db"
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* Which broker carries a declared queue or topic, by target.
|
|
34
|
-
*
|
|
35
|
-
* Whether there are events at all is the manifest's answer — a declared
|
|
36
|
-
* `Topic` or `Queue` — so this only ever answers the second question. AWS
|
|
37
|
-
* deploys them to SNS and SQS, and locally the AWS emulator stands in, so the
|
|
38
|
-
* broker a developer runs is the one production has. A server has a Postgres
|
|
39
|
-
* already, and pg-boss keeps its queues there.
|
|
40
|
-
*/
|
|
41
|
-
const DEFAULT_EVENTS = {
|
|
42
|
-
aws: "sns",
|
|
43
|
-
server: "pgboss"
|
|
44
|
-
};
|
|
45
|
-
/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */
|
|
46
|
-
const DEFAULT_STORAGE = {
|
|
47
|
-
aws: "s3",
|
|
48
|
-
server: "minio"
|
|
49
|
-
};
|
|
50
|
-
//#endregion
|
|
51
6
|
//#region src/generators/Generator.ts
|
|
52
7
|
/**
|
|
53
8
|
* Zod v4 maintains a process-wide registry of schemas registered via
|
|
@@ -71,12 +26,7 @@ var ConstructGenerator = class {
|
|
|
71
26
|
}
|
|
72
27
|
async load(patterns, cwd = process.cwd(), bustCache = false) {
|
|
73
28
|
const logger = console;
|
|
74
|
-
|
|
75
|
-
let partitionFn;
|
|
76
|
-
if (isPartitionedRoutes(patterns)) {
|
|
77
|
-
globPatterns = Array.isArray(patterns.paths) ? patterns.paths : [patterns.paths];
|
|
78
|
-
partitionFn = patterns.partition;
|
|
79
|
-
} else globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
|
|
29
|
+
const globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
|
|
80
30
|
const files = fg.stream(globPatterns, {
|
|
81
31
|
cwd,
|
|
82
32
|
absolute: true
|
|
@@ -86,7 +36,6 @@ var ConstructGenerator = class {
|
|
|
86
36
|
for await (const f of files) try {
|
|
87
37
|
const file = f.toString();
|
|
88
38
|
const module = await (bustCache ? import(`${file}?t=${Date.now()}`) : import(file));
|
|
89
|
-
const partition = partitionFn ? partitionFn(file) : void 0;
|
|
90
39
|
for (const [key, construct] of Object.entries(module)) if (this.isConstruct(construct)) constructs.push({
|
|
91
40
|
key,
|
|
92
41
|
name: kebabCase$1(key),
|
|
@@ -94,8 +43,7 @@ var ConstructGenerator = class {
|
|
|
94
43
|
path: {
|
|
95
44
|
absolute: file,
|
|
96
45
|
relative: relative(process.cwd(), file)
|
|
97
|
-
}
|
|
98
|
-
partition
|
|
46
|
+
}
|
|
99
47
|
});
|
|
100
48
|
} catch (error) {
|
|
101
49
|
const err = error;
|
|
@@ -269,6 +217,6 @@ var DuplicateConstruct = class extends Error {
|
|
|
269
217
|
}
|
|
270
218
|
};
|
|
271
219
|
//#endregion
|
|
272
|
-
export {
|
|
220
|
+
export { ConstructGenerator as i, discover_exports as n, isDeclarable as r, discover as t };
|
|
273
221
|
|
|
274
|
-
//# sourceMappingURL=discover-
|
|
222
|
+
//# sourceMappingURL=discover-Cf2jAcgF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-Cf2jAcgF.mjs","names":["kebabCase"],"sources":["../src/generators/Generator.ts","../src/reconcile/discover.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\nimport fg from 'fast-glob';\nimport kebabCase from 'lodash.kebabcase';\nimport type { BuildContext } from '../build/types';\nimport type { MainProvider, Routes } from '../types';\n\n/**\n * Zod v4 maintains a process-wide registry of schemas registered via\n * `.meta({ id })`. When we cache-bust user module imports (by appending a\n * `?t=timestamp` query), those modules re-execute and try to register the\n * same ids again, which throws `ID X already exists in the registry`.\n *\n * Clearing the registry in the CLI process is safe: the user's running\n * server lives in a subprocess and has its own isolated registry.\n *\n * Exported for tests.\n */\nexport function clearZodGlobalRegistry(): void {\n\tconst registry = (\n\t\tglobalThis as { __zod_globalRegistry?: { clear?: () => void } }\n\t).__zod_globalRegistry;\n\tif (registry && typeof registry.clear === 'function') {\n\t\tregistry.clear();\n\t}\n}\n\nexport interface GeneratorOptions {\n\t/** Where the build deploys: one Lambda per construct, or one process. */\n\ttarget?: MainProvider;\n\t/**\n\t * What a handler path in the manifest is relative to — the workspace root,\n\t * which is where `sst.config.ts` runs and resolves it from.\n\t */\n\troot?: string;\n\t[key: string]: any;\n}\n\nexport abstract class ConstructGenerator<T extends Construct, R = void> {\n\tabstract isConstruct(value: any): value is T;\n\n\tstatic async build<T extends Construct, R = void>(\n\t\tcontext: BuildContext,\n\t\toutputDir: string,\n\t\tgenerator: ConstructGenerator<T, R>,\n\t\tpatterns?: Routes,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R> {\n\t\tconst constructs = await generator.load(patterns);\n\t\treturn generator.build(context, constructs, outputDir, options);\n\t}\n\n\tabstract build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<T>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<R>;\n\n\tasync load(\n\t\tpatterns?: Routes,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t): Promise<GeneratedConstruct<T>[]> {\n\t\tconst logger = console;\n\n\t\tconst globPatterns = Array.isArray(patterns)\n\t\t\t? patterns\n\t\t\t: patterns\n\t\t\t\t? [patterns]\n\t\t\t\t: [];\n\n\t\t// Find all files\n\t\tconst files = fg.stream(globPatterns, {\n\t\t\tcwd,\n\t\t\tabsolute: true,\n\t\t});\n\n\t\t// Cache-busted re-imports re-execute user modules, which would try to\n\t\t// re-register Zod schemas with the same `.meta({ id })`. Clear the\n\t\t// global registry once per load to prevent duplicate-id errors.\n\t\tif (bustCache) {\n\t\t\tclearZodGlobalRegistry();\n\t\t}\n\n\t\t// Load constructs\n\t\tconst constructs: GeneratedConstruct<T>[] = [];\n\n\t\tfor await (const f of files) {\n\t\t\ttry {\n\t\t\t\tconst file = f.toString();\n\t\t\t\t// Append cache-busting query param to force re-import of changed modules\n\t\t\t\tconst importPath = bustCache ? `${file}?t=${Date.now()}` : file;\n\t\t\t\tconst module = await import(importPath);\n\n\t\t\t\t// Check all exports for constructs\n\t\t\t\tfor (const [key, construct] of Object.entries(module)) {\n\t\t\t\t\tif (this.isConstruct(construct)) {\n\t\t\t\t\t\tconstructs.push({\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\tname: kebabCase(key),\n\t\t\t\t\t\t\tconstruct,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tabsolute: file,\n\t\t\t\t\t\t\t\trelative: relative(process.cwd(), file),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst err = error as Error;\n\t\t\t\tlogger.error(`Failed to load ${f}: ${err.message}`);\n\t\t\t\tif (err.stack) {\n\t\t\t\t\tlogger.error(err.stack);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn constructs;\n\t}\n}\n\nexport interface GeneratedConstruct<T extends Construct> {\n\tkey: string;\n\tname: string;\n\tconstruct: T;\n\tpath: {\n\t\tabsolute: string;\n\t\trelative: string;\n\t};\n}\n","/**\n * Discovery — one glob, one structural test.\n *\n * A glob per kind is the same specialness the construct model removes, and\n * resources have no kind to be listed under: a declared `ObjectStorage` would\n * never be found by `routes:` or `crons:`. So there is one `constructs` glob,\n * every export of every matching module is inspected, and the per-kind\n * `isConstruct` checks collapse into a single question — does it have an id and\n * can it declare?\n *\n * Structural rather than `instanceof` on purpose. A construct from a different\n * copy of `@geekmidas/constructs` — a linked workspace, two versions in a\n * lockfile — is still a construct, and `instanceof` is exactly the check that\n * says otherwise.\n */\n\nimport { relative } from 'node:path';\nimport {\n\tassertDerivations,\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype Declaration,\n} from '@geekmidas/manifest';\nimport fg from 'fast-glob';\nimport { clearZodGlobalRegistry } from '../generators/Generator';\n\n/** Matches the rest of the CLI, which logs through `console` directly. */\nconst logger = console;\n\n/** The construct face discovery needs: an id, and the ability to declare. */\ninterface Declarable {\n\tid: string;\n\tdeclare(): Declaration[];\n}\n\n/**\n * Whether a module export is a construct.\n *\n * Deliberately loose about what else it carries — a construct that also has a\n * `service` is still a construct, and requiring one would exclude the kinds that\n * own no address.\n */\nexport function isDeclarable(value: unknown): value is Declarable {\n\tif (typeof value !== 'object' || value === null) return false;\n\n\tconst candidate = value as Partial<Declarable>;\n\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\tcandidate.id.length > 0 &&\n\t\ttypeof candidate.declare === 'function'\n\t);\n}\n\nexport interface DiscoverOptions {\n\t/** The glob(s) to search. One pattern, not one per kind. */\n\tpatterns: string | readonly string[];\n\tcwd?: string;\n\t/**\n\t * Re-import changed modules, for the dev watcher.\n\t *\n\t * A single glob makes discovery more central, not less: a new construct file\n\t * has to be picked up on `add`, not only on `change`.\n\t */\n\tbustCache?: boolean;\n\t/**\n\t * Filled in with where each construct was found, when provided.\n\t *\n\t * Discovery is the only thing that knows: it globbed the files and imported\n\t * them. Everything downstream that needs to *write* an import — the build,\n\t * generating a server entry that reads a surface's logger — would otherwise\n\t * have to be told the path in config, which is the string this removes.\n\t *\n\t * An out-parameter rather than a second return value so that every existing\n\t * caller, which wants the manifest and nothing else, is unchanged.\n\t */\n\tsources?: Record<string, ConstructSource>;\n\t/**\n\t * Filled in, when provided, with the ids each owner's runnables declared an\n\t * edge to — keyed by the surface an endpoint was built from, or the worker\n\t * a cron or subscriber was.\n\t *\n\t * A runnable is not a declaration: a surface's endpoints are found by the\n\t * glob, not listed by the surface, so its node says `endpoints: []` until a\n\t * build folds the routes in. Anything deciding what an app's process reaches\n\t * — which keys its container is given — needs those edges before any build,\n\t * and discovery is already importing the files they are in.\n\t */\n\trunnables?: Record<string, string[]>;\n}\n\n/** Where a construct was declared, and under what name. */\nexport interface ConstructSource {\n\t/** Absolute path to the module that exported it. */\n\tfile: string;\n\t/** The name it was exported as — what an import statement has to say. */\n\texportName: string;\n\t/**\n\t * The exported construct itself.\n\t *\n\t * What a declaration cannot say — whether it serves itself — the object can\n\t * be asked, and discovery already imported it.\n\t */\n\tconstruct: unknown;\n}\n\n/**\n * Build the construct manifest for a project.\n *\n * Runs the manifest's own validation before returning, so an app is wrong here\n * — where the error names a file — rather than at deploy.\n *\n * @throws {DuplicateConstruct} when two exports claim one id.\n */\nexport async function discover(\n\toptions: DiscoverOptions,\n): Promise<ConstructManifest> {\n\tconst {\n\t\tpatterns,\n\t\tcwd = process.cwd(),\n\t\tbustCache = false,\n\t\tsources: out,\n\t} = options;\n\n\t// Re-importing user modules re-executes them, and Zod v4 throws on a\n\t// re-registered `.meta({ id })`. Same reason the generators clear it.\n\tif (bustCache) clearZodGlobalRegistry();\n\n\tconst manifest: Record<string, Declaration> = {};\n\t/** Which file declared each id, for the error when two of them do. */\n\tconst sources: Record<string, string> = {};\n\t/**\n\t * Constructs already seen, by identity.\n\t *\n\t * A re-export is the *same object* reached through a second file — ESM\n\t * bindings are live, so `export * from './database.js'` hands back the\n\t * binding rather than a copy. Without this, a barrel file made every\n\t * construct in it appear declared twice, and `constructs/index.ts` is the\n\t * first thing anyone writes in a shared folder.\n\t *\n\t * Identity rather than file is also the more honest rule: what may not\n\t * happen twice is two *different* constructs claiming one id, and that is\n\t * still an error below.\n\t */\n\tconst seen = new WeakSet<object>();\n\n\tconst globs = Array.isArray(patterns) ? [...patterns] : [patterns as string];\n\tconst files = fg.stream(globs, { cwd, absolute: true });\n\n\t/** Matched files, for the diagnostic below. */\n\tlet matched = 0;\n\n\tfor await (const found of files) {\n\t\tmatched++;\n\t\tconst file = found.toString();\n\t\tconst module = await import(bustCache ? `${file}?t=${Date.now()}` : file);\n\n\t\tfor (const [exportName, exported] of Object.entries(module)) {\n\t\t\tif (!isDeclarable(exported)) {\n\t\t\t\tif (options.runnables) recordRunnable(options.runnables, exported);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// The same construct, reached again through a re-export. Skipped\n\t\t\t// rather than re-declared: it has already claimed its id, from the\n\t\t\t// file that defined it.\n\t\t\tif (seen.has(exported as object)) continue;\n\t\t\tseen.add(exported as object);\n\n\t\t\t// A construct that is also something a process runs — a queue and its\n\t\t\t// consumer, built from a worker — reaches what it depends on through\n\t\t\t// that process, exactly as an endpoint or a cron does.\n\t\t\tif (\n\t\t\t\toptions.runnables &&\n\t\t\t\ttypeof (exported as { owner?: unknown }).owner === 'string'\n\t\t\t) {\n\t\t\t\trecordRunnable(options.runnables, exported);\n\t\t\t}\n\n\t\t\tfor (const declaration of exported.declare()) {\n\t\t\t\t// Canonicalise here too: a construct built by hand rather than through\n\t\t\t\t// a constructor is still subject to the same identity rule.\n\t\t\t\tconst id = canonicalId(declaration.id);\n\t\t\t\tconst source = relative(cwd, file);\n\n\t\t\t\tconst claimed = sources[id];\n\t\t\t\tif (claimed !== undefined && claimed !== source) {\n\t\t\t\t\tthrow new DuplicateConstruct(id, [claimed, source]);\n\t\t\t\t}\n\n\t\t\t\tmanifest[id] = { ...declaration, id };\n\t\t\t\tsources[id] = source;\n\t\t\t\tif (out) out[id] = { file, exportName, construct: exported };\n\t\t\t}\n\t\t}\n\t}\n\n\t// Reference integrity before anyone reads it — a schema tenant naming a\n\t// database that was deleted is a manifest error, not a reconcile failure.\n\tassertDerivations(manifest);\n\n\twarnIfNothingFound(globs, matched, Object.keys(manifest).length);\n\n\treturn manifest;\n}\n\n/**\n * Record a runnable's edges under the construct that owns it — the surface an\n * endpoint was built from, or the worker a cron or subscriber was. Anything\n * with no owner, or that is not a runnable at all, is left alone.\n */\nfunction recordRunnable(\n\trunnables: Record<string, string[]>,\n\texported: unknown,\n): void {\n\tif (typeof exported !== 'object' || exported === null) return;\n\n\tconst runnable = exported as {\n\t\tsurface?: { id?: unknown };\n\t\towner?: unknown;\n\t\tconstructs?: unknown;\n\t};\n\tconst owner =\n\t\ttypeof runnable.surface?.id === 'string'\n\t\t\t? runnable.surface.id\n\t\t\t: typeof runnable.owner === 'string'\n\t\t\t\t? runnable.owner\n\t\t\t\t: undefined;\n\tif (!owner || !Array.isArray(runnable.constructs)) return;\n\n\tconst key = canonicalId(owner);\n\tconst edges = runnables[key] ?? [];\n\trunnables[key] = edges;\n\tfor (const id of runnable.constructs) {\n\t\tif (typeof id !== 'string') continue;\n\t\tconst target = canonicalId(id);\n\t\tif (!edges.includes(target)) edges.push(target);\n\t}\n}\n\n/**\n * Say so when a glob was configured and found nothing.\n *\n * The glob is not a filter over declarations already known — it is *how they\n * are found*, by importing what it matches. So a file it misses is a\n * declaration that does not exist, and every symptom of that appears somewhere\n * other than the cause: no container starts, no env key is written, and the\n * application fails on first use against a resource it can see in its own\n * source.\n *\n * Nothing reported it before, because an unmatched file is indistinguishable\n * from a file nobody wrote. These two cases are the ones that are *not*\n * ambiguous — a glob was configured, so something was expected.\n *\n * Silent when no glob is configured at all: that is a project which has not\n * adopted constructs, and has nothing to be missing.\n */\nfunction warnIfNothingFound(\n\tglobs: readonly string[],\n\tmatched: number,\n\tdeclared: number,\n): void {\n\tif (globs.length === 0) return;\n\n\tif (matched === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched no files, so nothing was declared.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Nothing will be provisioned — no containers, no env keys, no ` +\n\t\t\t\t`resources.\\n` +\n\t\t\t\t` A common cause is depth: '*.ts' matches one level, '**/*.ts' ` +\n\t\t\t\t`matches any.`,\n\t\t);\n\t\treturn;\n\t}\n\n\tif (declared === 0) {\n\t\tlogger.warn(\n\t\t\t`\\n⚠️ The constructs glob matched ${matched} file(s) but none declared ` +\n\t\t\t\t`anything.\\n` +\n\t\t\t\t` Patterns: ${globs.join(', ')}\\n` +\n\t\t\t\t` Discovery keeps exports with an 'id' that can 'declare()'. A ` +\n\t\t\t\t`construct that is\\n` +\n\t\t\t\t` built but never exported is invisible to it.`,\n\t\t);\n\t}\n}\n\n/** Two constructs claiming one id. */\nexport class DuplicateConstruct extends Error {\n\t/** The id claimed twice. */\n\treadonly id: string;\n\t/** The files that claimed it. */\n\treadonly sources: readonly string[];\n\n\tconstructor(id: string, sources: readonly string[]) {\n\t\tsuper('Two constructs declare the same id');\n\t\tthis.name = 'DuplicateConstruct';\n\t\tthis.id = id;\n\t\tthis.sources = sources;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -5,51 +5,6 @@ let fast_glob = require("fast-glob");
|
|
|
5
5
|
fast_glob = require_rolldown_runtime.__toESM(fast_glob, 1);
|
|
6
6
|
let lodash_kebabcase = require("lodash.kebabcase");
|
|
7
7
|
lodash_kebabcase = require_rolldown_runtime.__toESM(lodash_kebabcase, 1);
|
|
8
|
-
//#region src/types.ts
|
|
9
|
-
function isPartitionedRoutes(routes) {
|
|
10
|
-
return typeof routes === "object" && routes !== null && !Array.isArray(routes) && "paths" in routes;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The backend a project gets when it says nothing, by where it deploys.
|
|
14
|
-
*
|
|
15
|
-
* Flat defaults could not be right for both: `upstash` on a single box means a
|
|
16
|
-
* SaaS round-trip for a cache the database could have held, and `minio` on AWS
|
|
17
|
-
* means running a bucket beside S3. So the default is a function of the target,
|
|
18
|
-
* and an app that says nothing gets the obvious answer in both places — which
|
|
19
|
-
* is the whole of what "zero config" can mean once there is more than one
|
|
20
|
-
* target.
|
|
21
|
-
*
|
|
22
|
-
* `server` covers Dokploy and a bare host alike: the question these answer is
|
|
23
|
-
* whether the app is next to a managed cloud or next to its own containers, and
|
|
24
|
-
* on that question the two are the same.
|
|
25
|
-
*
|
|
26
|
-
* A per-construct, per-stage override is the layer above this — see
|
|
27
|
-
* `constructs-outstanding.md` — so two buckets in one app can differ. This is
|
|
28
|
-
* only what happens when nobody has said anything at all.
|
|
29
|
-
*/
|
|
30
|
-
const DEFAULT_CACHE = {
|
|
31
|
-
aws: "upstash",
|
|
32
|
-
server: "db"
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* Which broker carries a declared queue or topic, by target.
|
|
36
|
-
*
|
|
37
|
-
* Whether there are events at all is the manifest's answer — a declared
|
|
38
|
-
* `Topic` or `Queue` — so this only ever answers the second question. AWS
|
|
39
|
-
* deploys them to SNS and SQS, and locally the AWS emulator stands in, so the
|
|
40
|
-
* broker a developer runs is the one production has. A server has a Postgres
|
|
41
|
-
* already, and pg-boss keeps its queues there.
|
|
42
|
-
*/
|
|
43
|
-
const DEFAULT_EVENTS = {
|
|
44
|
-
aws: "sns",
|
|
45
|
-
server: "pgboss"
|
|
46
|
-
};
|
|
47
|
-
/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */
|
|
48
|
-
const DEFAULT_STORAGE = {
|
|
49
|
-
aws: "s3",
|
|
50
|
-
server: "minio"
|
|
51
|
-
};
|
|
52
|
-
//#endregion
|
|
53
8
|
//#region src/generators/Generator.ts
|
|
54
9
|
/**
|
|
55
10
|
* Zod v4 maintains a process-wide registry of schemas registered via
|
|
@@ -73,12 +28,7 @@ var ConstructGenerator = class {
|
|
|
73
28
|
}
|
|
74
29
|
async load(patterns, cwd = process.cwd(), bustCache = false) {
|
|
75
30
|
const logger = console;
|
|
76
|
-
|
|
77
|
-
let partitionFn;
|
|
78
|
-
if (isPartitionedRoutes(patterns)) {
|
|
79
|
-
globPatterns = Array.isArray(patterns.paths) ? patterns.paths : [patterns.paths];
|
|
80
|
-
partitionFn = patterns.partition;
|
|
81
|
-
} else globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
|
|
31
|
+
const globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
|
|
82
32
|
const files = fast_glob.default.stream(globPatterns, {
|
|
83
33
|
cwd,
|
|
84
34
|
absolute: true
|
|
@@ -88,7 +38,6 @@ var ConstructGenerator = class {
|
|
|
88
38
|
for await (const f of files) try {
|
|
89
39
|
const file = f.toString();
|
|
90
40
|
const module = await (bustCache ? import(`${file}?t=${Date.now()}`) : import(file));
|
|
91
|
-
const partition = partitionFn ? partitionFn(file) : void 0;
|
|
92
41
|
for (const [key, construct] of Object.entries(module)) if (this.isConstruct(construct)) constructs.push({
|
|
93
42
|
key,
|
|
94
43
|
name: (0, lodash_kebabcase.default)(key),
|
|
@@ -96,8 +45,7 @@ var ConstructGenerator = class {
|
|
|
96
45
|
path: {
|
|
97
46
|
absolute: file,
|
|
98
47
|
relative: (0, node_path.relative)(process.cwd(), file)
|
|
99
|
-
}
|
|
100
|
-
partition
|
|
48
|
+
}
|
|
101
49
|
});
|
|
102
50
|
} catch (error) {
|
|
103
51
|
const err = error;
|
|
@@ -277,24 +225,6 @@ Object.defineProperty(exports, "ConstructGenerator", {
|
|
|
277
225
|
return ConstructGenerator;
|
|
278
226
|
}
|
|
279
227
|
});
|
|
280
|
-
Object.defineProperty(exports, "DEFAULT_CACHE", {
|
|
281
|
-
enumerable: true,
|
|
282
|
-
get: function() {
|
|
283
|
-
return DEFAULT_CACHE;
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
Object.defineProperty(exports, "DEFAULT_EVENTS", {
|
|
287
|
-
enumerable: true,
|
|
288
|
-
get: function() {
|
|
289
|
-
return DEFAULT_EVENTS;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
Object.defineProperty(exports, "DEFAULT_STORAGE", {
|
|
293
|
-
enumerable: true,
|
|
294
|
-
get: function() {
|
|
295
|
-
return DEFAULT_STORAGE;
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
228
|
Object.defineProperty(exports, "discover", {
|
|
299
229
|
enumerable: true,
|
|
300
230
|
get: function() {
|
|
@@ -313,11 +243,5 @@ Object.defineProperty(exports, "isDeclarable", {
|
|
|
313
243
|
return isDeclarable;
|
|
314
244
|
}
|
|
315
245
|
});
|
|
316
|
-
Object.defineProperty(exports, "isPartitionedRoutes", {
|
|
317
|
-
enumerable: true,
|
|
318
|
-
get: function() {
|
|
319
|
-
return isPartitionedRoutes;
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
246
|
|
|
323
|
-
//# sourceMappingURL=discover-
|
|
247
|
+
//# sourceMappingURL=discover-DXNviAh-.cjs.map
|