@percepta/create 4.1.8 → 4.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +80 -15
- package/dist/index.js.map +1 -1
- package/dist/{register-app-mNc1oYVK.js → register-app-B9vKTkoI.js} +87 -33
- package/dist/register-app-B9vKTkoI.js.map +1 -0
- package/package.json +1 -1
- package/templates/infra/os.blueprint.yaml.template +13 -0
- package/templates/monorepo/auth/README.md +2 -2
- package/templates/monorepo/auth/package.json +1 -1
- package/templates/monorepo/pnpm-workspace.yaml +8 -0
- package/templates/webapp/AGENTS.md +1 -1
- package/templates/webapp/README.md +2 -1
- package/templates/webapp/agent-skills/access-control.md +3 -3
- package/templates/webapp/agent-skills/database.md +2 -1
- package/templates/webapp/next.config.ts +1 -1
- package/templates/webapp/package.json.template +2 -2
- package/templates/webapp/scripts/seed.ts +3 -3
- package/templates/webapp/src/app/(settings)/settings/page.tsx +1 -1
- package/templates/webapp/src/drizzle/schema/index.ts +1 -1
- package/templates/webapp/src/lib/auth/index.ts +2 -2
- package/templates/webapp/src/startup-checks.ts +1 -1
- package/dist/register-app-mNc1oYVK.js.map +0 -1
|
@@ -141,6 +141,8 @@ function updateBlueprint(blueprintContent, appName, options) {
|
|
|
141
141
|
changed = addAppInput(document, inputs, renderBetterAuthSecretInput(appName)) || changed;
|
|
142
142
|
changed = addAppInput(document, inputs, renderLangfusePublicKeyInput()) || changed;
|
|
143
143
|
changed = addAppInput(document, inputs, renderLangfuseSecretKeyInput()) || changed;
|
|
144
|
+
changed = addAppInput(document, inputs, renderInngestEventKeyInput()) || changed;
|
|
145
|
+
changed = addAppInput(document, inputs, renderInngestSigningKeyInput()) || changed;
|
|
144
146
|
}
|
|
145
147
|
if (options.appDatabase) changed = addAppDatabase(document, inputs, appName) || changed;
|
|
146
148
|
if (options.appInstallation) {
|
|
@@ -190,8 +192,7 @@ function addAppDatabase(document, inputs, appName) {
|
|
|
190
192
|
if (!isMap(defaultValue)) throw new Error("OS blueprint app_databases default must be a map.");
|
|
191
193
|
if (defaultValue.has(appName)) return false;
|
|
192
194
|
defaultValue.flow = false;
|
|
193
|
-
const appDatabaseValue = document.createNode({});
|
|
194
|
-
if (isMap(appDatabaseValue)) appDatabaseValue.flow = true;
|
|
195
|
+
const appDatabaseValue = document.createNode({ schema_name: toSnakeCase(appName) });
|
|
195
196
|
defaultValue.set(appName, appDatabaseValue);
|
|
196
197
|
return true;
|
|
197
198
|
}
|
|
@@ -250,8 +251,28 @@ function renderLangfuseSecretKeyInput() {
|
|
|
250
251
|
condition: `{{ ne (input "${langfusePublicKeyInputName()}") "" }}`
|
|
251
252
|
};
|
|
252
253
|
}
|
|
254
|
+
function renderInngestEventKeyInput() {
|
|
255
|
+
return {
|
|
256
|
+
name: inngestEventKeyInputName(),
|
|
257
|
+
type: "string",
|
|
258
|
+
isSecret: true,
|
|
259
|
+
group: "applications",
|
|
260
|
+
displayName: "Inngest Event Key",
|
|
261
|
+
description: "Shared Inngest event key for generated OS webapps."
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function renderInngestSigningKeyInput() {
|
|
265
|
+
return {
|
|
266
|
+
name: inngestSigningKeyInputName(),
|
|
267
|
+
type: "string",
|
|
268
|
+
isSecret: true,
|
|
269
|
+
group: "applications",
|
|
270
|
+
displayName: "Inngest Signing Key",
|
|
271
|
+
description: "Shared Inngest signing key for generated OS webapps."
|
|
272
|
+
};
|
|
273
|
+
}
|
|
253
274
|
function renderAppInstallationEnv(appName) {
|
|
254
|
-
const
|
|
275
|
+
const appInternalEndpoint = `http://${appName}-web-server.{{ .ryvn.env.name }}.svc.cluster.local:3000/api/inngest`;
|
|
255
276
|
return [
|
|
256
277
|
{
|
|
257
278
|
key: "DATABASE_URL",
|
|
@@ -269,13 +290,25 @@ function renderAppInstallationEnv(appName) {
|
|
|
269
290
|
name: "auth_database_url"
|
|
270
291
|
}
|
|
271
292
|
},
|
|
293
|
+
{
|
|
294
|
+
key: "DATABASE_SCHEMA",
|
|
295
|
+
value: toSnakeCase(appName)
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
key: "INGRESS_DOMAIN",
|
|
299
|
+
valueFromInput: { name: ingressDomainInputName() }
|
|
300
|
+
},
|
|
272
301
|
{
|
|
273
302
|
key: "APP_BASE_URL",
|
|
274
|
-
value: `https://${
|
|
303
|
+
value: `https://${appName}.$(INGRESS_DOMAIN)`
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
key: "BETTER_AUTH_URL",
|
|
307
|
+
value: `https://${appName}.$(INGRESS_DOMAIN)`
|
|
275
308
|
},
|
|
276
309
|
{
|
|
277
310
|
key: "DEPLOYMENT_ENVIRONMENT",
|
|
278
|
-
value: "{{
|
|
311
|
+
value: "{{ .ryvn.env.name }}"
|
|
279
312
|
},
|
|
280
313
|
{
|
|
281
314
|
key: "BETTER_AUTH_SECRET",
|
|
@@ -284,11 +317,35 @@ function renderAppInstallationEnv(appName) {
|
|
|
284
317
|
},
|
|
285
318
|
{
|
|
286
319
|
key: "INNGEST_BASE_URL",
|
|
287
|
-
|
|
320
|
+
valueFromOutput: {
|
|
321
|
+
blueprintInstallation: "mosaic",
|
|
322
|
+
name: "inngest_base_url"
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
key: "INNGEST_EVENT_KEY",
|
|
327
|
+
isSecret: true,
|
|
328
|
+
valueFromInput: { name: inngestEventKeyInputName() }
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
key: "INNGEST_SIGNING_KEY",
|
|
332
|
+
isSecret: true,
|
|
333
|
+
valueFromInput: { name: inngestSigningKeyInputName() }
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
key: "INNGEST_APP_URL",
|
|
337
|
+
value: appInternalEndpoint
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
key: "INNGEST_SERVE_HOST",
|
|
341
|
+
value: appInternalEndpoint
|
|
288
342
|
},
|
|
289
343
|
{
|
|
290
344
|
key: "LANGFUSE_BASE_URL",
|
|
291
|
-
|
|
345
|
+
valueFromOutput: {
|
|
346
|
+
blueprintInstallation: "mosaic",
|
|
347
|
+
name: "langfuse_internal_url"
|
|
348
|
+
}
|
|
292
349
|
},
|
|
293
350
|
{
|
|
294
351
|
key: "LANGFUSE_PUBLIC_KEY",
|
|
@@ -301,20 +358,32 @@ function renderAppInstallationEnv(appName) {
|
|
|
301
358
|
},
|
|
302
359
|
{
|
|
303
360
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
304
|
-
|
|
361
|
+
valueFromOutput: {
|
|
362
|
+
blueprintInstallation: "mosaic",
|
|
363
|
+
name: "otel_exporter_otlp_endpoint"
|
|
364
|
+
}
|
|
305
365
|
},
|
|
306
366
|
{
|
|
307
367
|
key: "SPICEDB_ENDPOINT",
|
|
308
|
-
|
|
368
|
+
valueFromOutput: {
|
|
369
|
+
blueprintInstallation: "mosaic",
|
|
370
|
+
name: "spicedb_endpoint"
|
|
371
|
+
}
|
|
309
372
|
},
|
|
310
373
|
{
|
|
311
374
|
key: "SPICEDB_PRESHARED_KEY",
|
|
312
375
|
isSecret: true,
|
|
313
|
-
|
|
376
|
+
valueFromOutput: {
|
|
377
|
+
blueprintInstallation: "mosaic",
|
|
378
|
+
name: "spicedb_preshared_key"
|
|
379
|
+
}
|
|
314
380
|
},
|
|
315
381
|
{
|
|
316
382
|
key: "SPICEDB_INSECURE",
|
|
317
|
-
|
|
383
|
+
valueFromOutput: {
|
|
384
|
+
blueprintInstallation: "mosaic",
|
|
385
|
+
name: "spicedb_insecure"
|
|
386
|
+
}
|
|
318
387
|
}
|
|
319
388
|
];
|
|
320
389
|
}
|
|
@@ -330,18 +399,6 @@ function renderAppInstallationConfig(appName) {
|
|
|
330
399
|
"readinessEnabled: true",
|
|
331
400
|
"startupEnabled: true",
|
|
332
401
|
"",
|
|
333
|
-
"env:",
|
|
334
|
-
" - name: INNGEST_EVENT_KEY",
|
|
335
|
-
" valueFrom:",
|
|
336
|
-
" secretKeyRef:",
|
|
337
|
-
` name: '{{ (blueprintInstallation "mosaic").outputs.${mosaicInngestKeysSecretNameOutput()} }}'`,
|
|
338
|
-
` key: '{{ (blueprintInstallation "mosaic").outputs.${mosaicInngestEventKeySecretKeyOutput()} }}'`,
|
|
339
|
-
" - name: INNGEST_SIGNING_KEY",
|
|
340
|
-
" valueFrom:",
|
|
341
|
-
" secretKeyRef:",
|
|
342
|
-
` name: '{{ (blueprintInstallation "mosaic").outputs.${mosaicInngestKeysSecretNameOutput()} }}'`,
|
|
343
|
-
` key: '{{ (blueprintInstallation "mosaic").outputs.${mosaicInngestSigningKeySecretKeyOutput()} }}'`,
|
|
344
|
-
"",
|
|
345
402
|
"resources:",
|
|
346
403
|
" requests:",
|
|
347
404
|
" cpu: \"100m\"",
|
|
@@ -387,21 +444,18 @@ function ingressDomainInputName() {
|
|
|
387
444
|
function betterAuthSecretInputName(appName) {
|
|
388
445
|
return `${toSnakeCase(appName)}_better_auth_secret`;
|
|
389
446
|
}
|
|
390
|
-
function mosaicInngestKeysSecretNameOutput() {
|
|
391
|
-
return "inngest_keys_secret_name";
|
|
392
|
-
}
|
|
393
|
-
function mosaicInngestEventKeySecretKeyOutput() {
|
|
394
|
-
return "inngest_event_key_secret_key";
|
|
395
|
-
}
|
|
396
|
-
function mosaicInngestSigningKeySecretKeyOutput() {
|
|
397
|
-
return "inngest_signing_key_secret_key";
|
|
398
|
-
}
|
|
399
447
|
function langfusePublicKeyInputName() {
|
|
400
448
|
return "langfuse_public_key";
|
|
401
449
|
}
|
|
402
450
|
function langfuseSecretKeyInputName() {
|
|
403
451
|
return "langfuse_secret_key";
|
|
404
452
|
}
|
|
453
|
+
function inngestEventKeyInputName() {
|
|
454
|
+
return "inngest_event_key";
|
|
455
|
+
}
|
|
456
|
+
function inngestSigningKeyInputName() {
|
|
457
|
+
return "inngest_signing_key";
|
|
458
|
+
}
|
|
405
459
|
function normalizeAppName(appNameInput) {
|
|
406
460
|
const appName = toKebabCase(appNameInput);
|
|
407
461
|
const validation = validateProjectName(appName);
|
|
@@ -411,4 +465,4 @@ function normalizeAppName(appNameInput) {
|
|
|
411
465
|
//#endregion
|
|
412
466
|
export { registerAppCommand };
|
|
413
467
|
|
|
414
|
-
//# sourceMappingURL=register-app-
|
|
468
|
+
//# sourceMappingURL=register-app-B9vKTkoI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-app-B9vKTkoI.js","names":[],"sources":["../src/commands/infra/register-app.ts"],"sourcesContent":["import path from \"node:path\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport { isMap, isSeq, parseDocument } from \"yaml\";\nimport {\n toKebabCase,\n toSnakeCase,\n toTitleCase,\n} from \"../../utils/case-converters.js\";\nimport { detectMonorepo } from \"../../utils/detect-monorepo.js\";\nimport { validateProjectName } from \"../../utils/validate.js\";\nimport { readWorkspaceManifest } from \"../../utils/workspace-manifest.js\";\nimport {\n createInfraGitHubApi,\n createOrUpdateInfraPullRequestFiles,\n INFRA_BASE_BRANCH,\n INFRA_REPOSITORY,\n type InfraGitHubApi,\n type InfraPullRequestFile,\n resolveGitHubToken,\n} from \"./github.js\";\n\nconst OS_POSTGRESQL_TERRAFORM_ALIAS = \"os-postgresql-terraform\";\nconst OS_POSTGRESQL_TERRAFORM_SERVICES = new Set([\n \"os-postgresql-terraform-aws\",\n \"os-postgresql-terraform-azure\",\n]);\nconst OS_BLUEPRINT_INPUT_GROUPS = [\n {\n name: \"general\",\n displayName: \"General\",\n description: \"Shared OS infrastructure settings.\",\n },\n {\n name: \"applications\",\n displayName: \"Applications\",\n description: \"Generated OS webapp settings.\",\n },\n {\n name: \"aws_postgresql\",\n displayName: \"AWS PostgreSQL\",\n description: \"AWS Aurora PostgreSQL settings.\",\n condition: '{{ eq EnvironmentProviderType \"aws\" }}',\n },\n {\n name: \"azure_postgresql\",\n displayName: \"Azure PostgreSQL\",\n description: \"Azure PostgreSQL Flexible Server settings.\",\n condition: '{{ eq EnvironmentProviderType \"azure\" }}',\n },\n];\n\nexport interface RegisterAppResult {\n appName: string;\n blueprintName: string;\n blueprintPath: string;\n branchName: string;\n customerSlug: string;\n pullRequestUrl: string | null;\n repository: typeof INFRA_REPOSITORY;\n status: \"already_registered\" | \"created_pr\" | \"updated_pr\";\n servicePath: string;\n targetPath: string;\n}\n\nexport async function registerApp(\n appNameInput: string,\n args: {\n cwd?: string;\n github?: InfraGitHubApi;\n } = {},\n): Promise<RegisterAppResult> {\n const appName = normalizeAppName(appNameInput);\n const cwd = args.cwd ?? process.cwd();\n const monorepoContext = await detectMonorepo(cwd);\n if (!monorepoContext.found || !monorepoContext.rootDir) {\n throw new Error(\n \"Run this command from a Mosaic customer monorepo with a .mosaic-workspace.json file.\",\n );\n }\n\n const workspaceManifest = await readWorkspaceManifest(\n monorepoContext.rootDir,\n );\n const customerSlug = workspaceManifest?.customerSlug;\n if (!customerSlug) {\n throw new Error(\n \".mosaic-workspace.json is missing customerSlug. Recreate the monorepo with a current @percepta/create.\",\n );\n }\n\n const github = args.github ?? createInfraGitHubApi(resolveGitHubToken());\n const blueprintName = `${customerSlug}-os`;\n const branchName = `blueberry/register-${customerSlug}-${appName}`;\n const blueprintPath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"blueprints\",\n `${blueprintName}.blueprint.yaml`,\n ].join(\"/\");\n const servicePath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"services\",\n `${appName}.service.yaml`,\n ].join(\"/\");\n\n const mainBlueprintFile = await github.getFile(\n blueprintPath,\n INFRA_BASE_BRANCH,\n );\n if (!mainBlueprintFile) {\n throw new Error(\n `${blueprintPath} does not exist in ${INFRA_REPOSITORY}. Run \\`pnpm mosaic infra register-os-blueprint\\` and merge that infra PR first.`,\n );\n }\n\n const mainServiceFile = await github.getFile(servicePath, INFRA_BASE_BRANCH);\n const serviceContent =\n mainServiceFile == null\n ? await readLocalServiceDefinition(monorepoContext.rootDir, appName)\n : null;\n const blueprintContent = registerAppInBlueprint(\n mainBlueprintFile.content,\n appName,\n );\n\n const files: InfraPullRequestFile[] = [];\n if (blueprintContent !== mainBlueprintFile.content) {\n files.push({\n baseFileSha: mainBlueprintFile.sha,\n content: blueprintContent,\n message: `Register ${appName} in ${blueprintName}`,\n path: blueprintPath,\n });\n }\n if (serviceContent != null) {\n files.push({\n content: serviceContent,\n message: `Register ${appName} service`,\n path: servicePath,\n });\n }\n\n if (files.length === 0) {\n return {\n appName,\n blueprintName,\n blueprintPath,\n branchName,\n customerSlug,\n pullRequestUrl: null,\n repository: INFRA_REPOSITORY,\n status: \"already_registered\",\n servicePath,\n targetPath: blueprintPath,\n };\n }\n\n const pullRequest = await createOrUpdateInfraPullRequestFiles({\n branchName,\n github,\n files,\n title: `Register ${appName} app`,\n body: [\n `Registers the ${appName} service and deployment in ${blueprintName}.`,\n \"\",\n \"Generated by `mosaic infra register-app`.\",\n ].join(\"\\n\"),\n });\n\n return {\n appName,\n blueprintName,\n blueprintPath,\n branchName,\n customerSlug,\n pullRequestUrl: pullRequest.pullRequestUrl,\n repository: INFRA_REPOSITORY,\n status: pullRequest.status,\n servicePath,\n targetPath: blueprintPath,\n };\n}\n\nexport async function registerAppCommand(appName: string): Promise<void> {\n try {\n const result = await registerApp(appName);\n\n if (result.status === \"already_registered\") {\n console.log(\n chalk.green(\"✔\"),\n `${result.appName} is already registered in ${result.repository} at`,\n chalk.cyan(result.targetPath),\n );\n return;\n }\n\n const verb =\n result.status === \"created_pr\" ? \"Created\" : \"Updated existing\";\n console.log(\n chalk.green(\"✔\"),\n `${verb} infra PR for ${result.appName}:`,\n chalk.cyan(result.pullRequestUrl),\n );\n } catch (error) {\n console.error(chalk.red(\"Error:\"), (error as Error).message);\n process.exit(1);\n }\n}\n\nexport function addAppDatabaseToBlueprint(\n blueprintContent: string,\n appName: string,\n): string {\n return updateBlueprint(blueprintContent, appName, {\n appDatabase: true,\n appInstallation: false,\n appInputs: false,\n });\n}\n\nexport function registerAppInBlueprint(\n blueprintContent: string,\n appName: string,\n): string {\n return updateBlueprint(blueprintContent, appName, {\n appDatabase: true,\n appInstallation: true,\n appInputs: true,\n });\n}\n\nfunction updateBlueprint(\n blueprintContent: string,\n appName: string,\n options: {\n appDatabase: boolean;\n appInstallation: boolean;\n appInputs: boolean;\n },\n): string {\n const document = parseDocument(blueprintContent);\n if (document.errors.length > 0) {\n throw new Error(\n `Invalid OS blueprint YAML: ${document.errors.map((error) => error.message).join(\"; \")}`,\n );\n }\n\n const spec = document.get(\"spec\", true);\n if (!isMap(spec)) {\n throw new Error(\"OS blueprint must include a spec map.\");\n }\n\n let changed = false;\n const inputs = spec.get(\"inputs\", true);\n if (!isSeq(inputs)) {\n throw new Error(\"OS blueprint spec.inputs must be a sequence.\");\n }\n\n changed = ensureInputGroups(document, spec) || changed;\n\n if (options.appInputs) {\n changed =\n addAppInput(document, inputs, renderIngressDomainInput()) || changed;\n changed =\n addAppInput(document, inputs, renderBetterAuthSecretInput(appName)) ||\n changed;\n changed =\n addAppInput(document, inputs, renderLangfusePublicKeyInput()) || changed;\n changed =\n addAppInput(document, inputs, renderLangfuseSecretKeyInput()) || changed;\n changed =\n addAppInput(document, inputs, renderInngestEventKeyInput()) || changed;\n changed =\n addAppInput(document, inputs, renderInngestSigningKeyInput()) || changed;\n }\n\n if (options.appDatabase) {\n changed = addAppDatabase(document, inputs, appName) || changed;\n }\n\n if (options.appInstallation) {\n const installations = spec.get(\"installations\", true);\n if (!isSeq(installations)) {\n throw new Error(\"OS blueprint spec.installations must be a sequence.\");\n }\n changed = ensureOsPostgresqlInstallationAlias(installations) || changed;\n changed = addAppInstallation(document, installations, appName) || changed;\n }\n\n return changed ? document.toString() : blueprintContent;\n}\n\nfunction ensureInputGroups(\n document: ReturnType<typeof parseDocument>,\n spec: {\n get(key: string, keepScalar?: true): unknown;\n set(key: string, value: unknown): void;\n },\n): boolean {\n let changed = false;\n const inputGroups = spec.get(\"inputGroups\", true);\n\n if (inputGroups == null) {\n spec.set(\"inputGroups\", document.createNode(OS_BLUEPRINT_INPUT_GROUPS));\n return true;\n }\n\n if (!isSeq(inputGroups)) {\n throw new Error(\"OS blueprint spec.inputGroups must be a sequence.\");\n }\n\n for (const group of OS_BLUEPRINT_INPUT_GROUPS) {\n const exists = inputGroups.items.some(\n (item) => isMap(item) && item.get(\"name\") === group.name,\n );\n if (exists) continue;\n\n inputGroups.add(document.createNode(group));\n changed = true;\n }\n\n return changed;\n}\n\nfunction ensureOsPostgresqlInstallationAlias(installations: {\n items: unknown[];\n}): boolean {\n let changed = false;\n\n for (const installation of installations.items) {\n if (!isMap(installation)) continue;\n\n const service = installation.get(\"service\");\n if (\n typeof service !== \"string\" ||\n !OS_POSTGRESQL_TERRAFORM_SERVICES.has(service)\n ) {\n continue;\n }\n\n if (installation.get(\"name\") === OS_POSTGRESQL_TERRAFORM_ALIAS) continue;\n\n installation.set(\"name\", OS_POSTGRESQL_TERRAFORM_ALIAS);\n changed = true;\n }\n\n return changed;\n}\n\nfunction addAppInput(\n document: ReturnType<typeof parseDocument>,\n inputs: { add(value: unknown): void; items: unknown[] },\n input: Record<string, unknown> & { name: string },\n): boolean {\n if (\n inputs.items.some((item) => isMap(item) && item.get(\"name\") === input.name)\n ) {\n return false;\n }\n\n inputs.add(document.createNode(input));\n return true;\n}\n\nfunction addAppDatabase(\n document: ReturnType<typeof parseDocument>,\n inputs: { items: unknown[] },\n appName: string,\n): boolean {\n const appDatabasesInput = inputs.items.find(\n (item) => isMap(item) && item.get(\"name\") === \"app_databases\",\n );\n if (!isMap(appDatabasesInput)) {\n throw new Error(\"OS blueprint must include an app_databases input.\");\n }\n\n const defaultValue = appDatabasesInput.get(\"default\", true);\n if (!isMap(defaultValue)) {\n throw new Error(\"OS blueprint app_databases default must be a map.\");\n }\n\n if (defaultValue.has(appName)) return false;\n\n defaultValue.flow = false;\n const appDatabaseValue = document.createNode({\n schema_name: toSnakeCase(appName),\n });\n defaultValue.set(appName, appDatabaseValue);\n return true;\n}\n\nfunction addAppInstallation(\n document: ReturnType<typeof parseDocument>,\n installations: { add(value: unknown): void; items: unknown[] },\n appName: string,\n): boolean {\n if (\n installations.items.some(\n (item) => isMap(item) && item.get(\"service\") === appName,\n )\n ) {\n return false;\n }\n\n installations.add(\n document.createNode({\n service: appName,\n env: renderAppInstallationEnv(appName),\n config: renderAppInstallationConfig(appName),\n }),\n );\n return true;\n}\n\nfunction renderIngressDomainInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: ingressDomainInputName(),\n type: \"string\",\n group: \"applications\",\n displayName: \"Ingress Domain\",\n description: \"Shared ingress domain for generated OS webapps.\",\n default: '{{ default \"example.local\" .ryvn.env.state.public_domain.name }}',\n };\n}\n\nfunction renderBetterAuthSecretInput(\n appName: string,\n): Record<string, unknown> & { name: string } {\n return {\n name: betterAuthSecretInputName(appName),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: `${toTitleCase(appName)} Better Auth Secret`,\n description: `Generated Better Auth signing secret for ${appName}.`,\n hidden: true,\n generated: {\n type: \"random-bytes\",\n length: 32,\n },\n };\n}\n\nfunction renderLangfusePublicKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: langfusePublicKeyInputName(),\n type: \"string\",\n group: \"applications\",\n displayName: \"Langfuse Public Key\",\n description:\n \"Shared Langfuse public key for generated OS webapps. Leave empty to disable Langfuse export.\",\n default: \"\",\n };\n}\n\nfunction renderLangfuseSecretKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: langfuseSecretKeyInputName(),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: \"Langfuse Secret Key\",\n description:\n \"Shared Langfuse secret key for generated OS webapps. Leave unset to disable Langfuse export.\",\n condition: `{{ ne (input \"${langfusePublicKeyInputName()}\") \"\" }}`,\n };\n}\n\nfunction renderInngestEventKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: inngestEventKeyInputName(),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: \"Inngest Event Key\",\n description: \"Shared Inngest event key for generated OS webapps.\",\n };\n}\n\nfunction renderInngestSigningKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: inngestSigningKeyInputName(),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: \"Inngest Signing Key\",\n description: \"Shared Inngest signing key for generated OS webapps.\",\n };\n}\n\nfunction renderAppInstallationEnv(\n appName: string,\n): Array<Record<string, unknown>> {\n const appInternalEndpoint = `http://${appName}-web-server.{{ .ryvn.env.name }}.svc.cluster.local:3000/api/inngest`;\n\n return [\n {\n key: \"DATABASE_URL\",\n isSecret: true,\n valueFromOutput: {\n serviceInstallation: \"os-postgresql-terraform\",\n name: `app_database_urls.${appName}`,\n },\n },\n {\n key: \"AUTH_DATABASE_URL\",\n isSecret: true,\n valueFromOutput: {\n serviceInstallation: \"os-postgresql-terraform\",\n name: \"auth_database_url\",\n },\n },\n {\n key: \"DATABASE_SCHEMA\",\n value: toSnakeCase(appName),\n },\n {\n key: \"INGRESS_DOMAIN\",\n valueFromInput: {\n name: ingressDomainInputName(),\n },\n },\n {\n key: \"APP_BASE_URL\",\n value: `https://${appName}.$(INGRESS_DOMAIN)`,\n },\n {\n key: \"BETTER_AUTH_URL\",\n value: `https://${appName}.$(INGRESS_DOMAIN)`,\n },\n {\n key: \"DEPLOYMENT_ENVIRONMENT\",\n value: \"{{ .ryvn.env.name }}\",\n },\n {\n key: \"BETTER_AUTH_SECRET\",\n isSecret: true,\n valueFromInput: {\n name: betterAuthSecretInputName(appName),\n },\n },\n {\n key: \"INNGEST_BASE_URL\",\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"inngest_base_url\",\n },\n },\n {\n key: \"INNGEST_EVENT_KEY\",\n isSecret: true,\n valueFromInput: {\n name: inngestEventKeyInputName(),\n },\n },\n {\n key: \"INNGEST_SIGNING_KEY\",\n isSecret: true,\n valueFromInput: {\n name: inngestSigningKeyInputName(),\n },\n },\n {\n key: \"INNGEST_APP_URL\",\n value: appInternalEndpoint,\n },\n {\n key: \"INNGEST_SERVE_HOST\",\n value: appInternalEndpoint,\n },\n {\n key: \"LANGFUSE_BASE_URL\",\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"langfuse_internal_url\",\n },\n },\n {\n key: \"LANGFUSE_PUBLIC_KEY\",\n valueFromInput: {\n name: langfusePublicKeyInputName(),\n },\n },\n {\n key: \"LANGFUSE_SECRET_KEY\",\n isSecret: true,\n valueFromInput: {\n name: langfuseSecretKeyInputName(),\n },\n },\n {\n key: \"OTEL_EXPORTER_OTLP_ENDPOINT\",\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"otel_exporter_otlp_endpoint\",\n },\n },\n {\n key: \"SPICEDB_ENDPOINT\",\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"spicedb_endpoint\",\n },\n },\n {\n key: \"SPICEDB_PRESHARED_KEY\",\n isSecret: true,\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"spicedb_preshared_key\",\n },\n },\n {\n key: \"SPICEDB_INSECURE\",\n valueFromOutput: {\n blueprintInstallation: \"mosaic\",\n name: \"spicedb_insecure\",\n },\n },\n ];\n}\n\nfunction renderAppInstallationConfig(appName: string): string {\n const appHost = `${appName}.{{ input \"${ingressDomainInputName()}\" }}`;\n\n return [\n \"replicaCount: 1\",\n \"\",\n \"service:\",\n \" port: 3000\",\n \"\",\n \"livenessEnabled: true\",\n \"readinessEnabled: true\",\n \"startupEnabled: true\",\n \"\",\n \"resources:\",\n \" requests:\",\n ' cpu: \"100m\"',\n \" memory: 256Mi\",\n \" limits:\",\n ' cpu: \"500m\"',\n \" memory: 512Mi\",\n \"\",\n \"ingress:\",\n \" enabled: true\",\n \" className: external-nginx\",\n \" annotations:\",\n \" cert-manager.io/cluster-issuer: external-issuer\",\n ' nginx.ingress.kubernetes.io/ssl-redirect: \"true\"',\n \" hosts:\",\n ` - host: '${appHost}'`,\n \" paths:\",\n \" - path: /\",\n \" pathType: Prefix\",\n \" tls:\",\n ` - secretName: ${appName}-tls`,\n \" hosts:\",\n ` - '${appHost}'`,\n \"\",\n ].join(\"\\n\");\n}\n\nasync function readLocalServiceDefinition(\n monorepoRoot: string,\n appName: string,\n): Promise<string> {\n const serviceDefinitionPath = path.join(\n monorepoRoot,\n \"packages\",\n appName,\n \"deploy\",\n \"ryvn\",\n `${appName}.service.yaml`,\n );\n if (!(await fs.pathExists(serviceDefinitionPath))) {\n throw new Error(\n `${serviceDefinitionPath} does not exist. Add the app's Ryvn service definition before registering it in infra.`,\n );\n }\n\n const content = await fs.readFile(serviceDefinitionPath, \"utf-8\");\n validateLocalServiceDefinition(content, appName, serviceDefinitionPath);\n return content.endsWith(\"\\n\") ? content : `${content}\\n`;\n}\n\nfunction validateLocalServiceDefinition(\n content: string,\n appName: string,\n serviceDefinitionPath: string,\n): void {\n const document = parseDocument(content);\n if (document.errors.length > 0) {\n throw new Error(\n `Invalid Ryvn service YAML at ${serviceDefinitionPath}: ${document.errors.map((error) => error.message).join(\"; \")}`,\n );\n }\n\n const service = document.toJS() as {\n kind?: unknown;\n metadata?: { name?: unknown };\n };\n if (service.kind !== \"Service\" || service.metadata?.name !== appName) {\n throw new Error(\n `${serviceDefinitionPath} must define kind: Service with metadata.name: ${appName}.`,\n );\n }\n}\n\nfunction ingressDomainInputName(): string {\n return \"ingress_domain\";\n}\n\nfunction betterAuthSecretInputName(appName: string): string {\n return `${toSnakeCase(appName)}_better_auth_secret`;\n}\n\nfunction langfusePublicKeyInputName(): string {\n return \"langfuse_public_key\";\n}\n\nfunction langfuseSecretKeyInputName(): string {\n return \"langfuse_secret_key\";\n}\n\nfunction inngestEventKeyInputName(): string {\n return \"inngest_event_key\";\n}\n\nfunction inngestSigningKeyInputName(): string {\n return \"inngest_signing_key\";\n}\n\nfunction normalizeAppName(appNameInput: string): string {\n const appName = toKebabCase(appNameInput);\n const validation = validateProjectName(appName);\n if (!validation.valid) {\n throw new Error(`Invalid app name: ${validation.error}`);\n }\n return appName;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -48,6 +48,18 @@ spec:
|
|
|
48
48
|
displayName: "Langfuse Secret Key"
|
|
49
49
|
description: "Shared Langfuse secret key for generated OS webapps. Leave unset to disable Langfuse export."
|
|
50
50
|
condition: '{{ ne (input "langfuse_public_key") "" }}'
|
|
51
|
+
- name: inngest_event_key
|
|
52
|
+
type: string
|
|
53
|
+
isSecret: true
|
|
54
|
+
group: applications
|
|
55
|
+
displayName: "Inngest Event Key"
|
|
56
|
+
description: "Shared Inngest event key for generated OS webapps."
|
|
57
|
+
- name: inngest_signing_key
|
|
58
|
+
type: string
|
|
59
|
+
isSecret: true
|
|
60
|
+
group: applications
|
|
61
|
+
displayName: "Inngest Signing Key"
|
|
62
|
+
description: "Shared Inngest signing key for generated OS webapps."
|
|
51
63
|
- name: auth_secret_name
|
|
52
64
|
type: string
|
|
53
65
|
group: general
|
|
@@ -131,6 +143,7 @@ spec:
|
|
|
131
143
|
|
|
132
144
|
region: {{ .ryvn.env.state.cluster_region }}
|
|
133
145
|
vpc_id: {{ .ryvn.env.state.vpc.id }}
|
|
146
|
+
sslmode: require
|
|
134
147
|
subnet_ids:
|
|
135
148
|
{{ .ryvn.env.state.vpc.private_subnet_ids | toYaml | nindent 2 }}
|
|
136
149
|
{{ if ne (input "aws_postgresql_cluster_name") "" }}
|
|
@@ -5,8 +5,8 @@ This workspace wires the customer-global Better Auth database schema from
|
|
|
5
5
|
session validation and user / group table references instead of creating
|
|
6
6
|
app-local auth tables.
|
|
7
7
|
|
|
8
|
-
Import auth as `@
|
|
9
|
-
`@
|
|
8
|
+
Import auth as `@__REPO_NAME__/auth`, the database handle as
|
|
9
|
+
`@__REPO_NAME__/auth/db`, and table definitions as `@__REPO_NAME__/auth/schema`
|
|
10
10
|
from app packages.
|
|
11
11
|
|
|
12
12
|
The important identity invariant is:
|
|
@@ -6,6 +6,14 @@ packages:
|
|
|
6
6
|
# user's pnpm config enforces minimumReleaseAge.
|
|
7
7
|
minimumReleaseAgeExclude:
|
|
8
8
|
- "@percepta/*"
|
|
9
|
+
- "better-auth"
|
|
10
|
+
- "@better-auth/*"
|
|
11
|
+
- "oxfmt"
|
|
12
|
+
- "@oxfmt/*"
|
|
13
|
+
- "oxlint"
|
|
14
|
+
- "@oxlint/*"
|
|
15
|
+
- "oxlint-tsgolint"
|
|
16
|
+
- "@oxlint-tsgolint/*"
|
|
9
17
|
|
|
10
18
|
overrides:
|
|
11
19
|
fast-xml-parser@<=5.5.6: 5.5.6
|
|
@@ -241,7 +241,7 @@ Client-side usage via `src/lib/trpc.ts`.
|
|
|
241
241
|
|
|
242
242
|
### Authentication
|
|
243
243
|
|
|
244
|
-
Better Auth is configured in the customer monorepo's shared `@
|
|
244
|
+
Better Auth is configured in the customer monorepo's shared `@__REPO_NAME__/auth` package. The app imports it through `src/lib/auth/` for local session validation. `DATABASE_URL` is this app's database only; deployed auth should use `AUTH_DATABASE_URL` from the monorepo auth Secret.
|
|
245
245
|
|
|
246
246
|
- **Server-side**: `auth.api.getSession({ headers: await headers() })` — get session in server components or tRPC context
|
|
247
247
|
- **Client-side**: `authClient.useSession()` — React hook from `src/lib/auth-client.ts`
|
|
@@ -145,7 +145,7 @@ logger.error({ safe: { documentId } }, "Processing failed", error);
|
|
|
145
145
|
|
|
146
146
|
## Authentication
|
|
147
147
|
|
|
148
|
-
This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@
|
|
148
|
+
This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@__REPO_NAME__/auth`. The app still serves local development auth routes, but the users, sessions, accounts, groups, and group memberships live in the shared customer auth database. Deployed apps should receive that shared database through `AUTH_DATABASE_URL` from the monorepo auth Secret; `DATABASE_URL` is reserved for this app's own database.
|
|
149
149
|
|
|
150
150
|
Required auth environment variables:
|
|
151
151
|
|
|
@@ -186,6 +186,7 @@ App permissions are authored in `src/access/schema.zed`; `src/access/access.mani
|
|
|
186
186
|
|
|
187
187
|
### App Database
|
|
188
188
|
|
|
189
|
+
<!-- prettier-ignore -->
|
|
189
190
|
| Variable | Description | Default |
|
|
190
191
|
| -------------- | ----------------------------- | ------------------------------------------------------------- |
|
|
191
192
|
| `DATABASE_URL` | App PostgreSQL connection URL | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` |
|
|
@@ -257,13 +257,13 @@ await getAccessControl().app.assignAppRole(
|
|
|
257
257
|
|
|
258
258
|
## Shared Auth Boundary
|
|
259
259
|
|
|
260
|
-
This app imports Better Auth from `@
|
|
260
|
+
This app imports Better Auth from `@__REPO_NAME__/auth` via `src/lib/auth/index.ts`. The app does not own `users`, `groups`, `group_members`, sessions, accounts, or verification tables.
|
|
261
261
|
|
|
262
262
|
When app code needs users for display, import from the shared auth package:
|
|
263
263
|
|
|
264
264
|
```ts
|
|
265
|
-
import { db as authDb } from "@
|
|
266
|
-
import { users } from "@
|
|
265
|
+
import { db as authDb } from "@__REPO_NAME__/auth/db";
|
|
266
|
+
import { users } from "@__REPO_NAME__/auth/schema";
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
Use `users.id` in SpiceDB refs. `users.external_id`, email, and group external IDs are ingestion lookup keys only.
|
|
@@ -10,7 +10,7 @@ Create a new schema file alongside the existing ones:
|
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
13
|
-
import { users } from "@
|
|
13
|
+
import { users } from "@__REPO_NAME__/auth/schema";
|
|
14
14
|
|
|
15
15
|
export const documents = pgTable("documents", {
|
|
16
16
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
@@ -135,6 +135,7 @@ pnpm --dir ../.. run docker:down
|
|
|
135
135
|
|
|
136
136
|
## Environment Variables
|
|
137
137
|
|
|
138
|
+
<!-- prettier-ignore -->
|
|
138
139
|
| Variable | Default | Description |
|
|
139
140
|
| -------------- | ------------------------------------------------------------- | ----------------------------- |
|
|
140
141
|
| `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` | App PostgreSQL connection URL |
|
|
@@ -9,7 +9,7 @@ const nextConfig: NextConfig = {
|
|
|
9
9
|
// Enable standalone output for Docker:
|
|
10
10
|
output: "standalone",
|
|
11
11
|
outputFileTracingRoot: monorepoRoot,
|
|
12
|
-
transpilePackages: ["@
|
|
12
|
+
transpilePackages: ["@__REPO_NAME__/auth"],
|
|
13
13
|
turbopack: {
|
|
14
14
|
root: monorepoRoot,
|
|
15
15
|
},
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"@opentelemetry/auto-instrumentations-node": "^0.75.0",
|
|
44
44
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.217.0",
|
|
45
45
|
"@opentelemetry/sdk-node": "^0.217.0",
|
|
46
|
+
"@__REPO_NAME__/auth": "workspace:*",
|
|
46
47
|
"@percepta/access-control": "^1.0.0",
|
|
47
48
|
"@percepta/ai": "^0.1.0",
|
|
48
|
-
"@
|
|
49
|
-
"@percepta/database": "^0.1.2",
|
|
49
|
+
"@percepta/database": "0.1.3",
|
|
50
50
|
"@percepta/design": "^0.4.1",
|
|
51
51
|
"@percepta/inngest": "^0.1.0",
|
|
52
52
|
"@percepta/logger": "^0.1.0",
|
|
@@ -52,9 +52,9 @@ async function main(): Promise<void> {
|
|
|
52
52
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
53
53
|
(globalThis as any).AsyncLocalStorage = AsyncLocalStorage;
|
|
54
54
|
|
|
55
|
-
const { auth } = await import("@
|
|
56
|
-
const { db: authDb } = await import("@
|
|
57
|
-
const { users } = await import("@
|
|
55
|
+
const { auth } = await import("@__REPO_NAME__/auth");
|
|
56
|
+
const { db: authDb } = await import("@__REPO_NAME__/auth/db");
|
|
57
|
+
const { users } = await import("@__REPO_NAME__/auth/schema");
|
|
58
58
|
const { getAccessControl, toUserSubject } =
|
|
59
59
|
await import("../src/services/access/AppAccessControl");
|
|
60
60
|
const { getEnvConfig } = await import("../src/config/getEnvConfig");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { listPrincipals } from "@__REPO_NAME__/auth/principals";
|
|
1
2
|
import {
|
|
2
3
|
type AccessRoleDefinition,
|
|
3
4
|
type ApplicationGrant,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
PrincipalMultiInput,
|
|
9
10
|
type PrincipalOption,
|
|
10
11
|
} from "@percepta/access-control/react";
|
|
11
|
-
import { listPrincipals } from "@__CUSTOMER_SLUG__/auth/principals";
|
|
12
12
|
import {
|
|
13
13
|
Badge,
|
|
14
14
|
Table,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { auth } from "@
|
|
1
|
+
import { auth } from "@__REPO_NAME__/auth";
|
|
2
2
|
import { headers } from "next/headers";
|
|
3
3
|
|
|
4
|
-
export { auth, type BetterAuthSession } from "@
|
|
4
|
+
export { auth, type BetterAuthSession } from "@__REPO_NAME__/auth";
|
|
5
5
|
|
|
6
6
|
export async function getServerSession() {
|
|
7
7
|
return auth.api.getSession({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { client as authClient } from "@
|
|
1
|
+
import { client as authClient } from "@__REPO_NAME__/auth/db";
|
|
2
2
|
import { getEnvConfig } from "./config/getEnvConfig";
|
|
3
3
|
import { client } from "./drizzle/db";
|
|
4
4
|
import { getLogger } from "./services/logger/AppLogger";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"register-app-mNc1oYVK.js","names":[],"sources":["../src/commands/infra/register-app.ts"],"sourcesContent":["import path from \"node:path\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport { isMap, isSeq, parseDocument } from \"yaml\";\nimport {\n toKebabCase,\n toSnakeCase,\n toTitleCase,\n} from \"../../utils/case-converters.js\";\nimport { detectMonorepo } from \"../../utils/detect-monorepo.js\";\nimport { validateProjectName } from \"../../utils/validate.js\";\nimport { readWorkspaceManifest } from \"../../utils/workspace-manifest.js\";\nimport {\n createInfraGitHubApi,\n createOrUpdateInfraPullRequestFiles,\n INFRA_BASE_BRANCH,\n INFRA_REPOSITORY,\n type InfraGitHubApi,\n type InfraPullRequestFile,\n resolveGitHubToken,\n} from \"./github.js\";\n\nconst OS_POSTGRESQL_TERRAFORM_ALIAS = \"os-postgresql-terraform\";\nconst OS_POSTGRESQL_TERRAFORM_SERVICES = new Set([\n \"os-postgresql-terraform-aws\",\n \"os-postgresql-terraform-azure\",\n]);\nconst OS_BLUEPRINT_INPUT_GROUPS = [\n {\n name: \"general\",\n displayName: \"General\",\n description: \"Shared OS infrastructure settings.\",\n },\n {\n name: \"applications\",\n displayName: \"Applications\",\n description: \"Generated OS webapp settings.\",\n },\n {\n name: \"aws_postgresql\",\n displayName: \"AWS PostgreSQL\",\n description: \"AWS Aurora PostgreSQL settings.\",\n condition: '{{ eq EnvironmentProviderType \"aws\" }}',\n },\n {\n name: \"azure_postgresql\",\n displayName: \"Azure PostgreSQL\",\n description: \"Azure PostgreSQL Flexible Server settings.\",\n condition: '{{ eq EnvironmentProviderType \"azure\" }}',\n },\n];\n\nexport interface RegisterAppResult {\n appName: string;\n blueprintName: string;\n blueprintPath: string;\n branchName: string;\n customerSlug: string;\n pullRequestUrl: string | null;\n repository: typeof INFRA_REPOSITORY;\n status: \"already_registered\" | \"created_pr\" | \"updated_pr\";\n servicePath: string;\n targetPath: string;\n}\n\nexport async function registerApp(\n appNameInput: string,\n args: {\n cwd?: string;\n github?: InfraGitHubApi;\n } = {},\n): Promise<RegisterAppResult> {\n const appName = normalizeAppName(appNameInput);\n const cwd = args.cwd ?? process.cwd();\n const monorepoContext = await detectMonorepo(cwd);\n if (!monorepoContext.found || !monorepoContext.rootDir) {\n throw new Error(\n \"Run this command from a Mosaic customer monorepo with a .mosaic-workspace.json file.\",\n );\n }\n\n const workspaceManifest = await readWorkspaceManifest(\n monorepoContext.rootDir,\n );\n const customerSlug = workspaceManifest?.customerSlug;\n if (!customerSlug) {\n throw new Error(\n \".mosaic-workspace.json is missing customerSlug. Recreate the monorepo with a current @percepta/create.\",\n );\n }\n\n const github = args.github ?? createInfraGitHubApi(resolveGitHubToken());\n const blueprintName = `${customerSlug}-os`;\n const branchName = `blueberry/register-${customerSlug}-${appName}`;\n const blueprintPath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"blueprints\",\n `${blueprintName}.blueprint.yaml`,\n ].join(\"/\");\n const servicePath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"services\",\n `${appName}.service.yaml`,\n ].join(\"/\");\n\n const mainBlueprintFile = await github.getFile(\n blueprintPath,\n INFRA_BASE_BRANCH,\n );\n if (!mainBlueprintFile) {\n throw new Error(\n `${blueprintPath} does not exist in ${INFRA_REPOSITORY}. Run \\`pnpm mosaic infra register-os-blueprint\\` and merge that infra PR first.`,\n );\n }\n\n const mainServiceFile = await github.getFile(servicePath, INFRA_BASE_BRANCH);\n const serviceContent =\n mainServiceFile == null\n ? await readLocalServiceDefinition(monorepoContext.rootDir, appName)\n : null;\n const blueprintContent = registerAppInBlueprint(\n mainBlueprintFile.content,\n appName,\n );\n\n const files: InfraPullRequestFile[] = [];\n if (blueprintContent !== mainBlueprintFile.content) {\n files.push({\n baseFileSha: mainBlueprintFile.sha,\n content: blueprintContent,\n message: `Register ${appName} in ${blueprintName}`,\n path: blueprintPath,\n });\n }\n if (serviceContent != null) {\n files.push({\n content: serviceContent,\n message: `Register ${appName} service`,\n path: servicePath,\n });\n }\n\n if (files.length === 0) {\n return {\n appName,\n blueprintName,\n blueprintPath,\n branchName,\n customerSlug,\n pullRequestUrl: null,\n repository: INFRA_REPOSITORY,\n status: \"already_registered\",\n servicePath,\n targetPath: blueprintPath,\n };\n }\n\n const pullRequest = await createOrUpdateInfraPullRequestFiles({\n branchName,\n github,\n files,\n title: `Register ${appName} app`,\n body: [\n `Registers the ${appName} service and deployment in ${blueprintName}.`,\n \"\",\n \"Generated by `mosaic infra register-app`.\",\n ].join(\"\\n\"),\n });\n\n return {\n appName,\n blueprintName,\n blueprintPath,\n branchName,\n customerSlug,\n pullRequestUrl: pullRequest.pullRequestUrl,\n repository: INFRA_REPOSITORY,\n status: pullRequest.status,\n servicePath,\n targetPath: blueprintPath,\n };\n}\n\nexport async function registerAppCommand(appName: string): Promise<void> {\n try {\n const result = await registerApp(appName);\n\n if (result.status === \"already_registered\") {\n console.log(\n chalk.green(\"✔\"),\n `${result.appName} is already registered in ${result.repository} at`,\n chalk.cyan(result.targetPath),\n );\n return;\n }\n\n const verb =\n result.status === \"created_pr\" ? \"Created\" : \"Updated existing\";\n console.log(\n chalk.green(\"✔\"),\n `${verb} infra PR for ${result.appName}:`,\n chalk.cyan(result.pullRequestUrl),\n );\n } catch (error) {\n console.error(chalk.red(\"Error:\"), (error as Error).message);\n process.exit(1);\n }\n}\n\nexport function addAppDatabaseToBlueprint(\n blueprintContent: string,\n appName: string,\n): string {\n return updateBlueprint(blueprintContent, appName, {\n appDatabase: true,\n appInstallation: false,\n appInputs: false,\n });\n}\n\nexport function registerAppInBlueprint(\n blueprintContent: string,\n appName: string,\n): string {\n return updateBlueprint(blueprintContent, appName, {\n appDatabase: true,\n appInstallation: true,\n appInputs: true,\n });\n}\n\nfunction updateBlueprint(\n blueprintContent: string,\n appName: string,\n options: {\n appDatabase: boolean;\n appInstallation: boolean;\n appInputs: boolean;\n },\n): string {\n const document = parseDocument(blueprintContent);\n if (document.errors.length > 0) {\n throw new Error(\n `Invalid OS blueprint YAML: ${document.errors.map((error) => error.message).join(\"; \")}`,\n );\n }\n\n const spec = document.get(\"spec\", true);\n if (!isMap(spec)) {\n throw new Error(\"OS blueprint must include a spec map.\");\n }\n\n let changed = false;\n const inputs = spec.get(\"inputs\", true);\n if (!isSeq(inputs)) {\n throw new Error(\"OS blueprint spec.inputs must be a sequence.\");\n }\n\n changed = ensureInputGroups(document, spec) || changed;\n\n if (options.appInputs) {\n changed =\n addAppInput(document, inputs, renderIngressDomainInput()) || changed;\n changed =\n addAppInput(document, inputs, renderBetterAuthSecretInput(appName)) ||\n changed;\n changed =\n addAppInput(document, inputs, renderLangfusePublicKeyInput()) || changed;\n changed =\n addAppInput(document, inputs, renderLangfuseSecretKeyInput()) || changed;\n }\n\n if (options.appDatabase) {\n changed = addAppDatabase(document, inputs, appName) || changed;\n }\n\n if (options.appInstallation) {\n const installations = spec.get(\"installations\", true);\n if (!isSeq(installations)) {\n throw new Error(\"OS blueprint spec.installations must be a sequence.\");\n }\n changed = ensureOsPostgresqlInstallationAlias(installations) || changed;\n changed = addAppInstallation(document, installations, appName) || changed;\n }\n\n return changed ? document.toString() : blueprintContent;\n}\n\nfunction ensureInputGroups(\n document: ReturnType<typeof parseDocument>,\n spec: {\n get(key: string, keepScalar?: true): unknown;\n set(key: string, value: unknown): void;\n },\n): boolean {\n let changed = false;\n const inputGroups = spec.get(\"inputGroups\", true);\n\n if (inputGroups == null) {\n spec.set(\"inputGroups\", document.createNode(OS_BLUEPRINT_INPUT_GROUPS));\n return true;\n }\n\n if (!isSeq(inputGroups)) {\n throw new Error(\"OS blueprint spec.inputGroups must be a sequence.\");\n }\n\n for (const group of OS_BLUEPRINT_INPUT_GROUPS) {\n const exists = inputGroups.items.some(\n (item) => isMap(item) && item.get(\"name\") === group.name,\n );\n if (exists) continue;\n\n inputGroups.add(document.createNode(group));\n changed = true;\n }\n\n return changed;\n}\n\nfunction ensureOsPostgresqlInstallationAlias(installations: {\n items: unknown[];\n}): boolean {\n let changed = false;\n\n for (const installation of installations.items) {\n if (!isMap(installation)) continue;\n\n const service = installation.get(\"service\");\n if (\n typeof service !== \"string\" ||\n !OS_POSTGRESQL_TERRAFORM_SERVICES.has(service)\n ) {\n continue;\n }\n\n if (installation.get(\"name\") === OS_POSTGRESQL_TERRAFORM_ALIAS) continue;\n\n installation.set(\"name\", OS_POSTGRESQL_TERRAFORM_ALIAS);\n changed = true;\n }\n\n return changed;\n}\n\nfunction addAppInput(\n document: ReturnType<typeof parseDocument>,\n inputs: { add(value: unknown): void; items: unknown[] },\n input: Record<string, unknown> & { name: string },\n): boolean {\n if (\n inputs.items.some((item) => isMap(item) && item.get(\"name\") === input.name)\n ) {\n return false;\n }\n\n inputs.add(document.createNode(input));\n return true;\n}\n\nfunction addAppDatabase(\n document: ReturnType<typeof parseDocument>,\n inputs: { items: unknown[] },\n appName: string,\n): boolean {\n const appDatabasesInput = inputs.items.find(\n (item) => isMap(item) && item.get(\"name\") === \"app_databases\",\n );\n if (!isMap(appDatabasesInput)) {\n throw new Error(\"OS blueprint must include an app_databases input.\");\n }\n\n const defaultValue = appDatabasesInput.get(\"default\", true);\n if (!isMap(defaultValue)) {\n throw new Error(\"OS blueprint app_databases default must be a map.\");\n }\n\n if (defaultValue.has(appName)) return false;\n\n defaultValue.flow = false;\n const appDatabaseValue = document.createNode({});\n if (isMap(appDatabaseValue)) appDatabaseValue.flow = true;\n defaultValue.set(appName, appDatabaseValue);\n return true;\n}\n\nfunction addAppInstallation(\n document: ReturnType<typeof parseDocument>,\n installations: { add(value: unknown): void; items: unknown[] },\n appName: string,\n): boolean {\n if (\n installations.items.some(\n (item) => isMap(item) && item.get(\"service\") === appName,\n )\n ) {\n return false;\n }\n\n installations.add(\n document.createNode({\n service: appName,\n env: renderAppInstallationEnv(appName),\n config: renderAppInstallationConfig(appName),\n }),\n );\n return true;\n}\n\nfunction renderIngressDomainInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: ingressDomainInputName(),\n type: \"string\",\n group: \"applications\",\n displayName: \"Ingress Domain\",\n description: \"Shared ingress domain for generated OS webapps.\",\n default: '{{ default \"example.local\" .ryvn.env.state.public_domain.name }}',\n };\n}\n\nfunction renderBetterAuthSecretInput(\n appName: string,\n): Record<string, unknown> & { name: string } {\n return {\n name: betterAuthSecretInputName(appName),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: `${toTitleCase(appName)} Better Auth Secret`,\n description: `Generated Better Auth signing secret for ${appName}.`,\n hidden: true,\n generated: {\n type: \"random-bytes\",\n length: 32,\n },\n };\n}\n\nfunction renderLangfusePublicKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: langfusePublicKeyInputName(),\n type: \"string\",\n group: \"applications\",\n displayName: \"Langfuse Public Key\",\n description:\n \"Shared Langfuse public key for generated OS webapps. Leave empty to disable Langfuse export.\",\n default: \"\",\n };\n}\n\nfunction renderLangfuseSecretKeyInput(): Record<string, unknown> & {\n name: string;\n} {\n return {\n name: langfuseSecretKeyInputName(),\n type: \"string\",\n isSecret: true,\n group: \"applications\",\n displayName: \"Langfuse Secret Key\",\n description:\n \"Shared Langfuse secret key for generated OS webapps. Leave unset to disable Langfuse export.\",\n condition: `{{ ne (input \"${langfusePublicKeyInputName()}\") \"\" }}`,\n };\n}\n\nfunction renderAppInstallationEnv(\n appName: string,\n): Array<Record<string, unknown>> {\n const appHost = `${appName}.{{ input \"${ingressDomainInputName()}\" }}`;\n\n return [\n {\n key: \"DATABASE_URL\",\n isSecret: true,\n valueFromOutput: {\n serviceInstallation: \"os-postgresql-terraform\",\n name: `app_database_urls.${appName}`,\n },\n },\n {\n key: \"AUTH_DATABASE_URL\",\n isSecret: true,\n valueFromOutput: {\n serviceInstallation: \"os-postgresql-terraform\",\n name: \"auth_database_url\",\n },\n },\n {\n key: \"APP_BASE_URL\",\n value: `https://${appHost}`,\n },\n {\n key: \"DEPLOYMENT_ENVIRONMENT\",\n value: \"{{ EnvironmentName }}\",\n },\n {\n key: \"BETTER_AUTH_SECRET\",\n isSecret: true,\n valueFromInput: {\n name: betterAuthSecretInputName(appName),\n },\n },\n {\n key: \"INNGEST_BASE_URL\",\n value: '{{ (blueprintInstallation \"mosaic\").outputs.inngest_base_url }}',\n },\n {\n key: \"LANGFUSE_BASE_URL\",\n value: '{{ (blueprintInstallation \"mosaic\").outputs.langfuse_base_url }}',\n },\n {\n key: \"LANGFUSE_PUBLIC_KEY\",\n valueFromInput: {\n name: langfusePublicKeyInputName(),\n },\n },\n {\n key: \"LANGFUSE_SECRET_KEY\",\n isSecret: true,\n valueFromInput: {\n name: langfuseSecretKeyInputName(),\n },\n },\n {\n key: \"OTEL_EXPORTER_OTLP_ENDPOINT\",\n value:\n '{{ (blueprintInstallation \"mosaic\").outputs.otel_exporter_otlp_endpoint }}',\n },\n {\n key: \"SPICEDB_ENDPOINT\",\n value: '{{ (blueprintInstallation \"mosaic\").outputs.spicedb_endpoint }}',\n },\n {\n key: \"SPICEDB_PRESHARED_KEY\",\n isSecret: true,\n value:\n '{{ (blueprintInstallation \"mosaic\").outputs.spicedb_preshared_key }}',\n },\n {\n key: \"SPICEDB_INSECURE\",\n value: '{{ (blueprintInstallation \"mosaic\").outputs.spicedb_insecure }}',\n },\n ];\n}\n\nfunction renderAppInstallationConfig(appName: string): string {\n const appHost = `${appName}.{{ input \"${ingressDomainInputName()}\" }}`;\n\n return [\n \"replicaCount: 1\",\n \"\",\n \"service:\",\n \" port: 3000\",\n \"\",\n \"livenessEnabled: true\",\n \"readinessEnabled: true\",\n \"startupEnabled: true\",\n \"\",\n \"env:\",\n \" - name: INNGEST_EVENT_KEY\",\n \" valueFrom:\",\n \" secretKeyRef:\",\n ` name: '{{ (blueprintInstallation \"mosaic\").outputs.${mosaicInngestKeysSecretNameOutput()} }}'`,\n ` key: '{{ (blueprintInstallation \"mosaic\").outputs.${mosaicInngestEventKeySecretKeyOutput()} }}'`,\n \" - name: INNGEST_SIGNING_KEY\",\n \" valueFrom:\",\n \" secretKeyRef:\",\n ` name: '{{ (blueprintInstallation \"mosaic\").outputs.${mosaicInngestKeysSecretNameOutput()} }}'`,\n ` key: '{{ (blueprintInstallation \"mosaic\").outputs.${mosaicInngestSigningKeySecretKeyOutput()} }}'`,\n \"\",\n \"resources:\",\n \" requests:\",\n ' cpu: \"100m\"',\n \" memory: 256Mi\",\n \" limits:\",\n ' cpu: \"500m\"',\n \" memory: 512Mi\",\n \"\",\n \"ingress:\",\n \" enabled: true\",\n \" className: external-nginx\",\n \" annotations:\",\n \" cert-manager.io/cluster-issuer: external-issuer\",\n ' nginx.ingress.kubernetes.io/ssl-redirect: \"true\"',\n \" hosts:\",\n ` - host: '${appHost}'`,\n \" paths:\",\n \" - path: /\",\n \" pathType: Prefix\",\n \" tls:\",\n ` - secretName: ${appName}-tls`,\n \" hosts:\",\n ` - '${appHost}'`,\n \"\",\n ].join(\"\\n\");\n}\n\nasync function readLocalServiceDefinition(\n monorepoRoot: string,\n appName: string,\n): Promise<string> {\n const serviceDefinitionPath = path.join(\n monorepoRoot,\n \"packages\",\n appName,\n \"deploy\",\n \"ryvn\",\n `${appName}.service.yaml`,\n );\n if (!(await fs.pathExists(serviceDefinitionPath))) {\n throw new Error(\n `${serviceDefinitionPath} does not exist. Add the app's Ryvn service definition before registering it in infra.`,\n );\n }\n\n const content = await fs.readFile(serviceDefinitionPath, \"utf-8\");\n validateLocalServiceDefinition(content, appName, serviceDefinitionPath);\n return content.endsWith(\"\\n\") ? content : `${content}\\n`;\n}\n\nfunction validateLocalServiceDefinition(\n content: string,\n appName: string,\n serviceDefinitionPath: string,\n): void {\n const document = parseDocument(content);\n if (document.errors.length > 0) {\n throw new Error(\n `Invalid Ryvn service YAML at ${serviceDefinitionPath}: ${document.errors.map((error) => error.message).join(\"; \")}`,\n );\n }\n\n const service = document.toJS() as {\n kind?: unknown;\n metadata?: { name?: unknown };\n };\n if (service.kind !== \"Service\" || service.metadata?.name !== appName) {\n throw new Error(\n `${serviceDefinitionPath} must define kind: Service with metadata.name: ${appName}.`,\n );\n }\n}\n\nfunction ingressDomainInputName(): string {\n return \"ingress_domain\";\n}\n\nfunction betterAuthSecretInputName(appName: string): string {\n return `${toSnakeCase(appName)}_better_auth_secret`;\n}\n\nfunction mosaicInngestKeysSecretNameOutput(): string {\n return \"inngest_keys_secret_name\";\n}\n\nfunction mosaicInngestEventKeySecretKeyOutput(): string {\n return \"inngest_event_key_secret_key\";\n}\n\nfunction mosaicInngestSigningKeySecretKeyOutput(): string {\n return \"inngest_signing_key_secret_key\";\n}\n\nfunction langfusePublicKeyInputName(): string {\n return \"langfuse_public_key\";\n}\n\nfunction langfuseSecretKeyInputName(): string {\n return \"langfuse_secret_key\";\n}\n\nfunction normalizeAppName(appNameInput: string): string {\n const appName = toKebabCase(appNameInput);\n const validation = validateProjectName(appName);\n if (!validation.valid) {\n throw new Error(`Invalid app name: ${validation.error}`);\n }\n return appName;\n}\n"],"mappings":";;;;;;;AAsBA,MAAM,gCAAgC;AACtC,MAAM,mCAAmC,IAAI,IAAI,CAC/C,+BACA,+BACF,CAAC;AACD,MAAM,4BAA4B;CAChC;EACE,MAAM;EACN,aAAa;EACb,aAAa;CACf;CACA;EACE,MAAM;EACN,aAAa;EACb,aAAa;CACf;CACA;EACE,MAAM;EACN,aAAa;EACb,aAAa;EACb,WAAW;CACb;CACA;EACE,MAAM;EACN,aAAa;EACb,aAAa;EACb,WAAW;CACb;AACF;AAeA,eAAsB,YACpB,cACA,OAGI,CAAC,GACuB;CAC5B,MAAM,UAAU,iBAAiB,YAAY;CAE7C,MAAM,kBAAkB,MAAM,eADlB,KAAK,OAAO,QAAQ,IAAI,CACY;CAChD,IAAI,CAAC,gBAAgB,SAAS,CAAC,gBAAgB,SAC7C,MAAM,IAAI,MACR,sFACF;CAMF,MAAM,gBAAe,MAHW,sBAC9B,gBAAgB,OAClB,IACwC;CACxC,IAAI,CAAC,cACH,MAAM,IAAI,MACR,wGACF;CAGF,MAAM,SAAS,KAAK,UAAU,qBAAqB,mBAAmB,CAAC;CACvE,MAAM,gBAAgB,GAAG,aAAa;CACtC,MAAM,aAAa,sBAAsB,aAAa,GAAG;CACzD,MAAM,gBAAgB;EACpB;EACA;EACA;EACA;EACA,GAAG,cAAc;CACnB,EAAE,KAAK,GAAG;CACV,MAAM,cAAc;EAClB;EACA;EACA;EACA;EACA,GAAG,QAAQ;CACb,EAAE,KAAK,GAAG;CAEV,MAAM,oBAAoB,MAAM,OAAO,QACrC,eACA,iBACF;CACA,IAAI,CAAC,mBACH,MAAM,IAAI,MACR,GAAG,cAAc,qBAAqB,iBAAiB,iFACzD;CAIF,MAAM,iBACJ,MAF4B,OAAO,QAAQ,aAAA,MAA8B,KAEtD,OACf,MAAM,2BAA2B,gBAAgB,SAAS,OAAO,IACjE;CACN,MAAM,mBAAmB,uBACvB,kBAAkB,SAClB,OACF;CAEA,MAAM,QAAgC,CAAC;CACvC,IAAI,qBAAqB,kBAAkB,SACzC,MAAM,KAAK;EACT,aAAa,kBAAkB;EAC/B,SAAS;EACT,SAAS,YAAY,QAAQ,MAAM;EACnC,MAAM;CACR,CAAC;CAEH,IAAI,kBAAkB,MACpB,MAAM,KAAK;EACT,SAAS;EACT,SAAS,YAAY,QAAQ;EAC7B,MAAM;CACR,CAAC;CAGH,IAAI,MAAM,WAAW,GACnB,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB,YAAY;EACZ,QAAQ;EACR;EACA,YAAY;CACd;CAGF,MAAM,cAAc,MAAM,oCAAoC;EAC5D;EACA;EACA;EACA,OAAO,YAAY,QAAQ;EAC3B,MAAM;GACJ,iBAAiB,QAAQ,6BAA6B,cAAc;GACpE;GACA;EACF,EAAE,KAAK,IAAI;CACb,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,gBAAgB,YAAY;EAC5B,YAAY;EACZ,QAAQ,YAAY;EACpB;EACA,YAAY;CACd;AACF;AAEA,eAAsB,mBAAmB,SAAgC;CACvE,IAAI;EACF,MAAM,SAAS,MAAM,YAAY,OAAO;EAExC,IAAI,OAAO,WAAW,sBAAsB;GAC1C,QAAQ,IACN,MAAM,MAAM,GAAG,GACf,GAAG,OAAO,QAAQ,4BAA4B,OAAO,WAAW,MAChE,MAAM,KAAK,OAAO,UAAU,CAC9B;GACA;EACF;EAEA,MAAM,OACJ,OAAO,WAAW,eAAe,YAAY;EAC/C,QAAQ,IACN,MAAM,MAAM,GAAG,GACf,GAAG,KAAK,gBAAgB,OAAO,QAAQ,IACvC,MAAM,KAAK,OAAO,cAAc,CAClC;CACF,SAAS,OAAO;EACd,QAAQ,MAAM,MAAM,IAAI,QAAQ,GAAI,MAAgB,OAAO;EAC3D,QAAQ,KAAK,CAAC;CAChB;AACF;AAaA,SAAgB,uBACd,kBACA,SACQ;CACR,OAAO,gBAAgB,kBAAkB,SAAS;EAChD,aAAa;EACb,iBAAiB;EACjB,WAAW;CACb,CAAC;AACH;AAEA,SAAS,gBACP,kBACA,SACA,SAKQ;CACR,MAAM,WAAW,cAAc,gBAAgB;CAC/C,IAAI,SAAS,OAAO,SAAS,GAC3B,MAAM,IAAI,MACR,8BAA8B,SAAS,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,KAAK,IAAI,GACvF;CAGF,MAAM,OAAO,SAAS,IAAI,QAAQ,IAAI;CACtC,IAAI,CAAC,MAAM,IAAI,GACb,MAAM,IAAI,MAAM,uCAAuC;CAGzD,IAAI,UAAU;CACd,MAAM,SAAS,KAAK,IAAI,UAAU,IAAI;CACtC,IAAI,CAAC,MAAM,MAAM,GACf,MAAM,IAAI,MAAM,8CAA8C;CAGhE,UAAU,kBAAkB,UAAU,IAAI,KAAK;CAE/C,IAAI,QAAQ,WAAW;EACrB,UACE,YAAY,UAAU,QAAQ,yBAAyB,CAAC,KAAK;EAC/D,UACE,YAAY,UAAU,QAAQ,4BAA4B,OAAO,CAAC,KAClE;EACF,UACE,YAAY,UAAU,QAAQ,6BAA6B,CAAC,KAAK;EACnE,UACE,YAAY,UAAU,QAAQ,6BAA6B,CAAC,KAAK;CACrE;CAEA,IAAI,QAAQ,aACV,UAAU,eAAe,UAAU,QAAQ,OAAO,KAAK;CAGzD,IAAI,QAAQ,iBAAiB;EAC3B,MAAM,gBAAgB,KAAK,IAAI,iBAAiB,IAAI;EACpD,IAAI,CAAC,MAAM,aAAa,GACtB,MAAM,IAAI,MAAM,qDAAqD;EAEvE,UAAU,oCAAoC,aAAa,KAAK;EAChE,UAAU,mBAAmB,UAAU,eAAe,OAAO,KAAK;CACpE;CAEA,OAAO,UAAU,SAAS,SAAS,IAAI;AACzC;AAEA,SAAS,kBACP,UACA,MAIS;CACT,IAAI,UAAU;CACd,MAAM,cAAc,KAAK,IAAI,eAAe,IAAI;CAEhD,IAAI,eAAe,MAAM;EACvB,KAAK,IAAI,eAAe,SAAS,WAAW,yBAAyB,CAAC;EACtE,OAAO;CACT;CAEA,IAAI,CAAC,MAAM,WAAW,GACpB,MAAM,IAAI,MAAM,mDAAmD;CAGrE,KAAK,MAAM,SAAS,2BAA2B;EAI7C,IAHe,YAAY,MAAM,MAC9B,SAAS,MAAM,IAAI,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,IAE7C,GAAG;EAEZ,YAAY,IAAI,SAAS,WAAW,KAAK,CAAC;EAC1C,UAAU;CACZ;CAEA,OAAO;AACT;AAEA,SAAS,oCAAoC,eAEjC;CACV,IAAI,UAAU;CAEd,KAAK,MAAM,gBAAgB,cAAc,OAAO;EAC9C,IAAI,CAAC,MAAM,YAAY,GAAG;EAE1B,MAAM,UAAU,aAAa,IAAI,SAAS;EAC1C,IACE,OAAO,YAAY,YACnB,CAAC,iCAAiC,IAAI,OAAO,GAE7C;EAGF,IAAI,aAAa,IAAI,MAAM,MAAM,+BAA+B;EAEhE,aAAa,IAAI,QAAQ,6BAA6B;EACtD,UAAU;CACZ;CAEA,OAAO;AACT;AAEA,SAAS,YACP,UACA,QACA,OACS;CACT,IACE,OAAO,MAAM,MAAM,SAAS,MAAM,IAAI,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,IAAI,GAE1E,OAAO;CAGT,OAAO,IAAI,SAAS,WAAW,KAAK,CAAC;CACrC,OAAO;AACT;AAEA,SAAS,eACP,UACA,QACA,SACS;CACT,MAAM,oBAAoB,OAAO,MAAM,MACpC,SAAS,MAAM,IAAI,KAAK,KAAK,IAAI,MAAM,MAAM,eAChD;CACA,IAAI,CAAC,MAAM,iBAAiB,GAC1B,MAAM,IAAI,MAAM,mDAAmD;CAGrE,MAAM,eAAe,kBAAkB,IAAI,WAAW,IAAI;CAC1D,IAAI,CAAC,MAAM,YAAY,GACrB,MAAM,IAAI,MAAM,mDAAmD;CAGrE,IAAI,aAAa,IAAI,OAAO,GAAG,OAAO;CAEtC,aAAa,OAAO;CACpB,MAAM,mBAAmB,SAAS,WAAW,CAAC,CAAC;CAC/C,IAAI,MAAM,gBAAgB,GAAG,iBAAiB,OAAO;CACrD,aAAa,IAAI,SAAS,gBAAgB;CAC1C,OAAO;AACT;AAEA,SAAS,mBACP,UACA,eACA,SACS;CACT,IACE,cAAc,MAAM,MACjB,SAAS,MAAM,IAAI,KAAK,KAAK,IAAI,SAAS,MAAM,OACnD,GAEA,OAAO;CAGT,cAAc,IACZ,SAAS,WAAW;EAClB,SAAS;EACT,KAAK,yBAAyB,OAAO;EACrC,QAAQ,4BAA4B,OAAO;CAC7C,CAAC,CACH;CACA,OAAO;AACT;AAEA,SAAS,2BAEP;CACA,OAAO;EACL,MAAM,uBAAuB;EAC7B,MAAM;EACN,OAAO;EACP,aAAa;EACb,aAAa;EACb,SAAS;CACX;AACF;AAEA,SAAS,4BACP,SAC4C;CAC5C,OAAO;EACL,MAAM,0BAA0B,OAAO;EACvC,MAAM;EACN,UAAU;EACV,OAAO;EACP,aAAa,GAAG,YAAY,OAAO,EAAE;EACrC,aAAa,4CAA4C,QAAQ;EACjE,QAAQ;EACR,WAAW;GACT,MAAM;GACN,QAAQ;EACV;CACF;AACF;AAEA,SAAS,+BAEP;CACA,OAAO;EACL,MAAM,2BAA2B;EACjC,MAAM;EACN,OAAO;EACP,aAAa;EACb,aACE;EACF,SAAS;CACX;AACF;AAEA,SAAS,+BAEP;CACA,OAAO;EACL,MAAM,2BAA2B;EACjC,MAAM;EACN,UAAU;EACV,OAAO;EACP,aAAa;EACb,aACE;EACF,WAAW,iBAAiB,2BAA2B,EAAE;CAC3D;AACF;AAEA,SAAS,yBACP,SACgC;CAChC,MAAM,UAAU,GAAG,QAAQ,aAAa,uBAAuB,EAAE;CAEjE,OAAO;EACL;GACE,KAAK;GACL,UAAU;GACV,iBAAiB;IACf,qBAAqB;IACrB,MAAM,qBAAqB;GAC7B;EACF;EACA;GACE,KAAK;GACL,UAAU;GACV,iBAAiB;IACf,qBAAqB;IACrB,MAAM;GACR;EACF;EACA;GACE,KAAK;GACL,OAAO,WAAW;EACpB;EACA;GACE,KAAK;GACL,OAAO;EACT;EACA;GACE,KAAK;GACL,UAAU;GACV,gBAAgB,EACd,MAAM,0BAA0B,OAAO,EACzC;EACF;EACA;GACE,KAAK;GACL,OAAO;EACT;EACA;GACE,KAAK;GACL,OAAO;EACT;EACA;GACE,KAAK;GACL,gBAAgB,EACd,MAAM,2BAA2B,EACnC;EACF;EACA;GACE,KAAK;GACL,UAAU;GACV,gBAAgB,EACd,MAAM,2BAA2B,EACnC;EACF;EACA;GACE,KAAK;GACL,OACE;EACJ;EACA;GACE,KAAK;GACL,OAAO;EACT;EACA;GACE,KAAK;GACL,UAAU;GACV,OACE;EACJ;EACA;GACE,KAAK;GACL,OAAO;EACT;CACF;AACF;AAEA,SAAS,4BAA4B,SAAyB;CAC5D,MAAM,UAAU,GAAG,QAAQ,aAAa,uBAAuB,EAAE;CAEjE,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,8DAA8D,kCAAkC,EAAE;EAClG,6DAA6D,qCAAqC,EAAE;EACpG;EACA;EACA;EACA,8DAA8D,kCAAkC,EAAE;EAClG,6DAA6D,uCAAuC,EAAE;EACtG;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAgB,QAAQ;EACxB;EACA;EACA;EACA;EACA,qBAAqB,QAAQ;EAC7B;EACA,cAAc,QAAQ;EACtB;CACF,EAAE,KAAK,IAAI;AACb;AAEA,eAAe,2BACb,cACA,SACiB;CACjB,MAAM,wBAAwB,KAAK,KACjC,cACA,YACA,SACA,UACA,QACA,GAAG,QAAQ,cACb;CACA,IAAI,CAAE,MAAM,GAAG,WAAW,qBAAqB,GAC7C,MAAM,IAAI,MACR,GAAG,sBAAsB,uFAC3B;CAGF,MAAM,UAAU,MAAM,GAAG,SAAS,uBAAuB,OAAO;CAChE,+BAA+B,SAAS,SAAS,qBAAqB;CACtE,OAAO,QAAQ,SAAS,IAAI,IAAI,UAAU,GAAG,QAAQ;AACvD;AAEA,SAAS,+BACP,SACA,SACA,uBACM;CACN,MAAM,WAAW,cAAc,OAAO;CACtC,IAAI,SAAS,OAAO,SAAS,GAC3B,MAAM,IAAI,MACR,gCAAgC,sBAAsB,IAAI,SAAS,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,KAAK,IAAI,GACnH;CAGF,MAAM,UAAU,SAAS,KAAK;CAI9B,IAAI,QAAQ,SAAS,aAAa,QAAQ,UAAU,SAAS,SAC3D,MAAM,IAAI,MACR,GAAG,sBAAsB,iDAAiD,QAAQ,EACpF;AAEJ;AAEA,SAAS,yBAAiC;CACxC,OAAO;AACT;AAEA,SAAS,0BAA0B,SAAyB;CAC1D,OAAO,GAAG,YAAY,OAAO,EAAE;AACjC;AAEA,SAAS,oCAA4C;CACnD,OAAO;AACT;AAEA,SAAS,uCAA+C;CACtD,OAAO;AACT;AAEA,SAAS,yCAAiD;CACxD,OAAO;AACT;AAEA,SAAS,6BAAqC;CAC5C,OAAO;AACT;AAEA,SAAS,6BAAqC;CAC5C,OAAO;AACT;AAEA,SAAS,iBAAiB,cAA8B;CACtD,MAAM,UAAU,YAAY,YAAY;CACxC,MAAM,aAAa,oBAAoB,OAAO;CAC9C,IAAI,CAAC,WAAW,OACd,MAAM,IAAI,MAAM,qBAAqB,WAAW,OAAO;CAEzD,OAAO;AACT"}
|