@percepta/create 3.6.1 → 3.6.3
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 +37 -6
- package/dist/{git-ops-C2CIjuce.js → git-ops-BD7JNnal.js} +1 -1
- package/dist/{git-ops-C2CIjuce.js.map → git-ops-BD7JNnal.js.map} +1 -1
- package/dist/github-RCIMUq70.js +131 -0
- package/dist/github-RCIMUq70.js.map +1 -0
- package/dist/index.js +63 -122
- package/dist/index.js.map +1 -1
- package/dist/{init-sI9aIrkU.js → init-COp0nGdk.js} +4 -2
- package/dist/{init-sI9aIrkU.js.map → init-COp0nGdk.js.map} +1 -1
- package/dist/manifest-CqIDnbgs.js +58 -0
- package/dist/manifest-CqIDnbgs.js.map +1 -0
- package/dist/register-app-C7ZBpAaZ.js +103 -0
- package/dist/register-app-C7ZBpAaZ.js.map +1 -0
- package/dist/register-os-blueprint-DGjBUZYa.js +90 -0
- package/dist/register-os-blueprint-DGjBUZYa.js.map +1 -0
- package/dist/{status-CKe4aKso.js → status-BXYaQ4a2.js} +3 -3
- package/dist/{status-CKe4aKso.js.map → status-BXYaQ4a2.js.map} +1 -1
- package/dist/{sync-D1vkoofl.js → sync-BayU4w1j.js} +3 -3
- package/dist/{sync-D1vkoofl.js.map → sync-BayU4w1j.js.map} +1 -1
- package/dist/template-versions-CEIP9vhl.js +35 -0
- package/dist/template-versions-CEIP9vhl.js.map +1 -0
- package/dist/{upstream-gUHLWSR1.js → upstream-CZEzLrS4.js} +3 -3
- package/dist/{upstream-gUHLWSR1.js.map → upstream-CZEzLrS4.js.map} +1 -1
- package/dist/validate-dssldJAj.js +14 -0
- package/dist/validate-dssldJAj.js.map +1 -0
- package/package.json +1 -1
- package/template-versions.json +2 -2
- package/templates/infra/os.blueprint.yaml.template +138 -0
- package/templates/monorepo/README.md +41 -3
- package/templates/monorepo/auth/README.md +6 -3
- package/templates/monorepo/auth/package.json +2 -4
- package/templates/monorepo/auth/src/config/database.ts +1 -1
- package/templates/{webapp → monorepo}/docker-compose.yml +2 -2
- package/templates/monorepo/package.json.template +5 -2
- package/templates/monorepo/pnpm-workspace.yaml +4 -0
- package/templates/monorepo/scripts/setup-local-databases.mjs +183 -0
- package/templates/webapp/AGENTS.md +13 -20
- package/templates/webapp/README.md +32 -36
- package/templates/webapp/agent-skills/database.md +21 -21
- package/templates/webapp/agent-skills/langfuse.md +7 -7
- package/templates/webapp/agent-skills/llm.md +4 -2
- package/templates/webapp/agent-skills/oneshot.md +7 -6
- package/templates/webapp/agent-skills/ryvn.md +12 -16
- package/templates/webapp/deploy/README.md +10 -51
- package/templates/webapp/drizzle.config.ts +2 -23
- package/templates/webapp/env.example.template +8 -14
- package/templates/webapp/package.json.template +8 -15
- package/templates/webapp/scripts/start.sh +12 -16
- package/templates/webapp/src/config/getEnvConfig.ts +4 -10
- package/templates/webapp/src/drizzle/db.ts +6 -21
- package/templates/webapp/src/startup-checks.ts +28 -7
- package/templates/monorepo/auth/scripts/setup-database.ts +0 -11
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +0 -92
- package/templates/webapp/agent-skills/deploy.md +0 -92
- package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +0 -10
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +0 -11
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +0 -154
- package/templates/webapp/terraform/README.md +0 -147
- package/templates/webapp/terraform/deploy.sh +0 -97
- package/templates/webapp/terraform/main.tf +0 -101
- package/templates/webapp/terraform/modules/cloudtrail/main.tf +0 -27
- package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +0 -10
- package/templates/webapp/terraform/modules/cloudtrail/variables.tf +0 -15
- package/templates/webapp/terraform/modules/networking/main.tf +0 -118
- package/templates/webapp/terraform/modules/networking/outputs.tf +0 -38
- package/templates/webapp/terraform/modules/networking/variables.tf +0 -24
- package/templates/webapp/terraform/modules/rds/main.tf +0 -227
- package/templates/webapp/terraform/modules/rds/outputs.tf +0 -73
- package/templates/webapp/terraform/modules/rds/variables.tf +0 -61
- package/templates/webapp/terraform/modules/s3-logging/main.tf +0 -148
- package/templates/webapp/terraform/modules/s3-logging/outputs.tf +0 -10
- package/templates/webapp/terraform/modules/s3-logging/variables.tf +0 -16
- package/templates/webapp/terraform/modules/secrets/main.tf +0 -39
- package/templates/webapp/terraform/modules/secrets/outputs.tf +0 -9
- package/templates/webapp/terraform/modules/secrets/variables.tf +0 -51
- package/templates/webapp/terraform/outputs.tf +0 -102
- package/templates/webapp/terraform/providers.tf +0 -32
- package/templates/webapp/terraform/schema/main.tf +0 -4
- package/templates/webapp/terraform/schema/outputs.tf +0 -9
- package/templates/webapp/terraform/schema/variables.tf +0 -19
- package/templates/webapp/terraform/schema/versions.tf +0 -38
- package/templates/webapp/terraform/terraform.tfvars.example +0 -65
- package/templates/webapp/terraform/variables.tf +0 -129
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { n as toKebabCase } from "./template-versions-CEIP9vhl.js";
|
|
2
|
+
import { t as validateProjectName } from "./validate-dssldJAj.js";
|
|
3
|
+
import { i as detectMonorepo, t as readWorkspaceManifest } from "./index.js";
|
|
4
|
+
import { a as resolveGitHubToken, i as createOrUpdateInfraPullRequest, n as INFRA_REPOSITORY, r as createInfraGitHubApi, t as INFRA_BASE_BRANCH } from "./github-RCIMUq70.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { isMap, isSeq, parseDocument } from "yaml";
|
|
7
|
+
//#region src/commands/infra/register-app.ts
|
|
8
|
+
async function registerApp(appNameInput, args = {}) {
|
|
9
|
+
const appName = normalizeAppName(appNameInput);
|
|
10
|
+
const monorepoContext = await detectMonorepo(args.cwd ?? process.cwd());
|
|
11
|
+
if (!monorepoContext.found || !monorepoContext.rootDir) throw new Error("Run this command from a Mosaic customer monorepo with a .mosaic-workspace.json file.");
|
|
12
|
+
const customerSlug = (await readWorkspaceManifest(monorepoContext.rootDir))?.customerSlug;
|
|
13
|
+
if (!customerSlug) throw new Error(".mosaic-workspace.json is missing customerSlug. Recreate the monorepo with a current @percepta/create.");
|
|
14
|
+
const github = args.github ?? createInfraGitHubApi(resolveGitHubToken());
|
|
15
|
+
const blueprintName = `${customerSlug}-os`;
|
|
16
|
+
const branchName = `blueberry/register-${customerSlug}-${appName}-database`;
|
|
17
|
+
const targetPath = [
|
|
18
|
+
"ryvn",
|
|
19
|
+
"definitions",
|
|
20
|
+
customerSlug,
|
|
21
|
+
"blueprints",
|
|
22
|
+
`${blueprintName}.blueprint.yaml`
|
|
23
|
+
].join("/");
|
|
24
|
+
const mainFile = await github.getFile(targetPath, INFRA_BASE_BRANCH);
|
|
25
|
+
if (!mainFile) throw new Error(`${targetPath} does not exist in ${INFRA_REPOSITORY}. Run \`pnpm mosaic infra register-os-blueprint\` and merge that infra PR first.`);
|
|
26
|
+
const content = addAppDatabaseToBlueprint(mainFile.content, appName);
|
|
27
|
+
if (content === mainFile.content) return {
|
|
28
|
+
appName,
|
|
29
|
+
blueprintName,
|
|
30
|
+
branchName,
|
|
31
|
+
customerSlug,
|
|
32
|
+
pullRequestUrl: null,
|
|
33
|
+
repository: INFRA_REPOSITORY,
|
|
34
|
+
status: "already_registered",
|
|
35
|
+
targetPath
|
|
36
|
+
};
|
|
37
|
+
const pullRequest = await createOrUpdateInfraPullRequest({
|
|
38
|
+
baseFileSha: mainFile.sha,
|
|
39
|
+
branchName,
|
|
40
|
+
content,
|
|
41
|
+
github,
|
|
42
|
+
message: `Register ${appName} app database`,
|
|
43
|
+
targetPath,
|
|
44
|
+
title: `Register ${appName} app database`,
|
|
45
|
+
body: [
|
|
46
|
+
`Registers the ${appName} app database in ${blueprintName}.`,
|
|
47
|
+
"",
|
|
48
|
+
"Generated by `mosaic infra register-app`."
|
|
49
|
+
].join("\n")
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
appName,
|
|
53
|
+
blueprintName,
|
|
54
|
+
branchName,
|
|
55
|
+
customerSlug,
|
|
56
|
+
pullRequestUrl: pullRequest.pullRequestUrl,
|
|
57
|
+
repository: INFRA_REPOSITORY,
|
|
58
|
+
status: pullRequest.status,
|
|
59
|
+
targetPath
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function registerAppCommand(appName) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await registerApp(appName);
|
|
65
|
+
if (result.status === "already_registered") {
|
|
66
|
+
console.log(chalk.green("✔"), `${result.appName} is already registered in ${result.repository} at`, chalk.cyan(result.targetPath));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const verb = result.status === "created_pr" ? "Created" : "Updated existing";
|
|
70
|
+
console.log(chalk.green("✔"), `${verb} infra PR for ${result.appName}:`, chalk.cyan(result.pullRequestUrl));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red("Error:"), error.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function addAppDatabaseToBlueprint(blueprintContent, appName) {
|
|
77
|
+
const document = parseDocument(blueprintContent);
|
|
78
|
+
if (document.errors.length > 0) throw new Error(`Invalid OS blueprint YAML: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
79
|
+
const spec = document.get("spec", true);
|
|
80
|
+
if (!isMap(spec)) throw new Error("OS blueprint must include a spec map.");
|
|
81
|
+
const inputs = spec.get("inputs", true);
|
|
82
|
+
if (!isSeq(inputs)) throw new Error("OS blueprint spec.inputs must be a sequence.");
|
|
83
|
+
const appDatabasesInput = inputs.items.find((item) => isMap(item) && item.get("name") === "app_databases");
|
|
84
|
+
if (!isMap(appDatabasesInput)) throw new Error("OS blueprint must include an app_databases input.");
|
|
85
|
+
const defaultValue = appDatabasesInput.get("default", true);
|
|
86
|
+
if (!isMap(defaultValue)) throw new Error("OS blueprint app_databases default must be a map.");
|
|
87
|
+
if (defaultValue.has(appName)) return blueprintContent;
|
|
88
|
+
defaultValue.flow = false;
|
|
89
|
+
const appDatabaseValue = document.createNode({});
|
|
90
|
+
if (isMap(appDatabaseValue)) appDatabaseValue.flow = true;
|
|
91
|
+
defaultValue.set(appName, appDatabaseValue);
|
|
92
|
+
return document.toString();
|
|
93
|
+
}
|
|
94
|
+
function normalizeAppName(appNameInput) {
|
|
95
|
+
const appName = toKebabCase(appNameInput);
|
|
96
|
+
const validation = validateProjectName(appName);
|
|
97
|
+
if (!validation.valid) throw new Error(`Invalid app name: ${validation.error}`);
|
|
98
|
+
return appName;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { registerAppCommand };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=register-app-C7ZBpAaZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-app-C7ZBpAaZ.js","names":[],"sources":["../src/commands/infra/register-app.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { isMap, isSeq, parseDocument } from \"yaml\";\nimport { toKebabCase } 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 createOrUpdateInfraPullRequest,\n INFRA_BASE_BRANCH,\n INFRA_REPOSITORY,\n type InfraGitHubApi,\n resolveGitHubToken,\n} from \"./github.js\";\n\nexport interface RegisterAppResult {\n appName: string;\n blueprintName: 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 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}-database`;\n const targetPath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"blueprints\",\n `${blueprintName}.blueprint.yaml`,\n ].join(\"/\");\n\n const mainFile = await github.getFile(targetPath, INFRA_BASE_BRANCH);\n if (!mainFile) {\n throw new Error(\n `${targetPath} does not exist in ${INFRA_REPOSITORY}. Run \\`pnpm mosaic infra register-os-blueprint\\` and merge that infra PR first.`,\n );\n }\n\n const content = addAppDatabaseToBlueprint(mainFile.content, appName);\n if (content === mainFile.content) {\n return {\n appName,\n blueprintName,\n branchName,\n customerSlug,\n pullRequestUrl: null,\n repository: INFRA_REPOSITORY,\n status: \"already_registered\",\n targetPath,\n };\n }\n\n const pullRequest = await createOrUpdateInfraPullRequest({\n baseFileSha: mainFile.sha,\n branchName,\n content,\n github,\n message: `Register ${appName} app database`,\n targetPath,\n title: `Register ${appName} app database`,\n body: [\n `Registers the ${appName} app database in ${blueprintName}.`,\n \"\",\n \"Generated by `mosaic infra register-app`.\",\n ].join(\"\\n\"),\n });\n\n return {\n appName,\n blueprintName,\n branchName,\n customerSlug,\n pullRequestUrl: pullRequest.pullRequestUrl,\n repository: INFRA_REPOSITORY,\n status: pullRequest.status,\n targetPath,\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 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 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 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 blueprintContent;\n\n defaultValue.flow = false;\n const appDatabaseValue = document.createNode({});\n if (isMap(appDatabaseValue)) appDatabaseValue.flow = true;\n defaultValue.set(appName, appDatabaseValue);\n\n return document.toString();\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":";;;;;;;AA0BA,eAAsB,YACpB,cACA,OAGI,EAAE,EACsB;CAC5B,MAAM,UAAU,iBAAiB,aAAa;CAE9C,MAAM,kBAAkB,MAAM,eADlB,KAAK,OAAO,QAAQ,KAAK,CACY;AACjD,KAAI,CAAC,gBAAgB,SAAS,CAAC,gBAAgB,QAC7C,OAAM,IAAI,MACR,uFACD;CAMH,MAAM,gBAAe,MAHW,sBAC9B,gBAAgB,QACjB,GACuC;AACxC,KAAI,CAAC,aACH,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,KAAK,UAAU,qBAAqB,oBAAoB,CAAC;CACxE,MAAM,gBAAgB,GAAG,aAAa;CACtC,MAAM,aAAa,sBAAsB,aAAa,GAAG,QAAQ;CACjE,MAAM,aAAa;EACjB;EACA;EACA;EACA;EACA,GAAG,cAAc;EAClB,CAAC,KAAK,IAAI;CAEX,MAAM,WAAW,MAAM,OAAO,QAAQ,YAAY,kBAAkB;AACpE,KAAI,CAAC,SACH,OAAM,IAAI,MACR,GAAG,WAAW,qBAAqB,iBAAiB,kFACrD;CAGH,MAAM,UAAU,0BAA0B,SAAS,SAAS,QAAQ;AACpE,KAAI,YAAY,SAAS,QACvB,QAAO;EACL;EACA;EACA;EACA;EACA,gBAAgB;EAChB,YAAY;EACZ,QAAQ;EACR;EACD;CAGH,MAAM,cAAc,MAAM,+BAA+B;EACvD,aAAa,SAAS;EACtB;EACA;EACA;EACA,SAAS,YAAY,QAAQ;EAC7B;EACA,OAAO,YAAY,QAAQ;EAC3B,MAAM;GACJ,iBAAiB,QAAQ,mBAAmB,cAAc;GAC1D;GACA;GACD,CAAC,KAAK,KAAK;EACb,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACA;EACA,gBAAgB,YAAY;EAC5B,YAAY;EACZ,QAAQ,YAAY;EACpB;EACD;;AAGH,eAAsB,mBAAmB,SAAgC;AACvE,KAAI;EACF,MAAM,SAAS,MAAM,YAAY,QAAQ;AAEzC,MAAI,OAAO,WAAW,sBAAsB;AAC1C,WAAQ,IACN,MAAM,MAAM,IAAI,EAChB,GAAG,OAAO,QAAQ,4BAA4B,OAAO,WAAW,MAChE,MAAM,KAAK,OAAO,WAAW,CAC9B;AACD;;EAGF,MAAM,OACJ,OAAO,WAAW,eAAe,YAAY;AAC/C,UAAQ,IACN,MAAM,MAAM,IAAI,EAChB,GAAG,KAAK,gBAAgB,OAAO,QAAQ,IACvC,MAAM,KAAK,OAAO,eAAe,CAClC;UACM,OAAO;AACd,UAAQ,MAAM,MAAM,IAAI,SAAS,EAAG,MAAgB,QAAQ;AAC5D,UAAQ,KAAK,EAAE;;;AAInB,SAAgB,0BACd,kBACA,SACQ;CACR,MAAM,WAAW,cAAc,iBAAiB;AAChD,KAAI,SAAS,OAAO,SAAS,EAC3B,OAAM,IAAI,MACR,8BAA8B,SAAS,OAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,KAAK,KAAK,GACvF;CAGH,MAAM,OAAO,SAAS,IAAI,QAAQ,KAAK;AACvC,KAAI,CAAC,MAAM,KAAK,CACd,OAAM,IAAI,MAAM,wCAAwC;CAG1D,MAAM,SAAS,KAAK,IAAI,UAAU,KAAK;AACvC,KAAI,CAAC,MAAM,OAAO,CAChB,OAAM,IAAI,MAAM,+CAA+C;CAGjE,MAAM,oBAAoB,OAAO,MAAM,MACpC,SAAS,MAAM,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,gBAC/C;AACD,KAAI,CAAC,MAAM,kBAAkB,CAC3B,OAAM,IAAI,MAAM,oDAAoD;CAGtE,MAAM,eAAe,kBAAkB,IAAI,WAAW,KAAK;AAC3D,KAAI,CAAC,MAAM,aAAa,CACtB,OAAM,IAAI,MAAM,oDAAoD;AAGtE,KAAI,aAAa,IAAI,QAAQ,CAAE,QAAO;AAEtC,cAAa,OAAO;CACpB,MAAM,mBAAmB,SAAS,WAAW,EAAE,CAAC;AAChD,KAAI,MAAM,iBAAiB,CAAE,kBAAiB,OAAO;AACrD,cAAa,IAAI,SAAS,iBAAiB;AAE3C,QAAO,SAAS,UAAU;;AAG5B,SAAS,iBAAiB,cAA8B;CACtD,MAAM,UAAU,YAAY,aAAa;CACzC,MAAM,aAAa,oBAAoB,QAAQ;AAC/C,KAAI,CAAC,WAAW,MACd,OAAM,IAAI,MAAM,qBAAqB,WAAW,QAAQ;AAE1D,QAAO"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { i as toTitleCase } from "./template-versions-CEIP9vhl.js";
|
|
2
|
+
import { i as detectMonorepo, t as readWorkspaceManifest } from "./index.js";
|
|
3
|
+
import { a as resolveGitHubToken, i as createOrUpdateInfraPullRequest, n as INFRA_REPOSITORY, r as createInfraGitHubApi, t as INFRA_BASE_BRANCH } from "./github-RCIMUq70.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import fs from "fs-extra";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
//#region src/commands/infra/register-os-blueprint.ts
|
|
9
|
+
const OS_BLUEPRINT_TEMPLATE = "os.blueprint.yaml.template";
|
|
10
|
+
async function registerOsBlueprint(args = {}) {
|
|
11
|
+
const monorepoContext = await detectMonorepo(args.cwd ?? process.cwd());
|
|
12
|
+
if (!monorepoContext.found || !monorepoContext.rootDir) throw new Error("Run this command from a Mosaic customer monorepo with a .mosaic-workspace.json file.");
|
|
13
|
+
const customerSlug = (await readWorkspaceManifest(monorepoContext.rootDir))?.customerSlug;
|
|
14
|
+
if (!customerSlug) throw new Error(".mosaic-workspace.json is missing customerSlug. Recreate the monorepo with a current @percepta/create.");
|
|
15
|
+
const github = args.github ?? createInfraGitHubApi(resolveGitHubToken());
|
|
16
|
+
const blueprintName = `${customerSlug}-os`;
|
|
17
|
+
const branchName = `blueberry/register-${blueprintName}-blueprint`;
|
|
18
|
+
const targetPath = [
|
|
19
|
+
"ryvn",
|
|
20
|
+
"definitions",
|
|
21
|
+
customerSlug,
|
|
22
|
+
"blueprints",
|
|
23
|
+
`${blueprintName}.blueprint.yaml`
|
|
24
|
+
].join("/");
|
|
25
|
+
const content = await renderOsBlueprint(customerSlug);
|
|
26
|
+
const mainFile = await github.getFile(targetPath, INFRA_BASE_BRANCH);
|
|
27
|
+
if (mainFile) {
|
|
28
|
+
if (mainFile.content === content) return {
|
|
29
|
+
blueprintName,
|
|
30
|
+
branchName,
|
|
31
|
+
customerSlug,
|
|
32
|
+
pullRequestUrl: null,
|
|
33
|
+
repository: INFRA_REPOSITORY,
|
|
34
|
+
status: "already_registered",
|
|
35
|
+
targetPath
|
|
36
|
+
};
|
|
37
|
+
throw new Error(`${targetPath} already exists in ${INFRA_REPOSITORY}. Not overwriting an existing customer OS blueprint.`);
|
|
38
|
+
}
|
|
39
|
+
const pullRequest = await createOrUpdateInfraPullRequest({
|
|
40
|
+
branchName,
|
|
41
|
+
content,
|
|
42
|
+
github,
|
|
43
|
+
message: `Register ${blueprintName} blueprint`,
|
|
44
|
+
targetPath,
|
|
45
|
+
title: `Register ${blueprintName} blueprint`,
|
|
46
|
+
body: [
|
|
47
|
+
`Registers the ${blueprintName} OS blueprint for ${customerSlug}.`,
|
|
48
|
+
"",
|
|
49
|
+
"Generated by `mosaic infra register-os-blueprint`."
|
|
50
|
+
].join("\n")
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
blueprintName,
|
|
54
|
+
branchName,
|
|
55
|
+
customerSlug,
|
|
56
|
+
pullRequestUrl: pullRequest.pullRequestUrl,
|
|
57
|
+
repository: INFRA_REPOSITORY,
|
|
58
|
+
status: pullRequest.status,
|
|
59
|
+
targetPath
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function registerOsBlueprintCommand() {
|
|
63
|
+
try {
|
|
64
|
+
const result = await registerOsBlueprint();
|
|
65
|
+
if (result.status === "already_registered") {
|
|
66
|
+
console.log(chalk.green("✔"), `${result.blueprintName} is already registered in ${result.repository} at`, chalk.cyan(result.targetPath));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const verb = result.status === "created_pr" ? "Created" : "Updated existing";
|
|
70
|
+
console.log(chalk.green("✔"), `${verb} infra PR for ${result.blueprintName}:`, chalk.cyan(result.pullRequestUrl));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red("Error:"), error.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function renderOsBlueprint(customerSlug) {
|
|
77
|
+
const template = await fs.readFile(getOsBlueprintTemplatePath(), "utf-8");
|
|
78
|
+
const customerTitle = toTitleCase(customerSlug);
|
|
79
|
+
return template.replaceAll("__CUSTOMER_SLUG__", customerSlug).replaceAll("__CUSTOMER_TITLE__", customerTitle);
|
|
80
|
+
}
|
|
81
|
+
function getOsBlueprintTemplatePath() {
|
|
82
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
83
|
+
const candidates = [path.resolve(currentDir, "../templates/infra", OS_BLUEPRINT_TEMPLATE), path.resolve(currentDir, "../../../templates/infra", OS_BLUEPRINT_TEMPLATE)];
|
|
84
|
+
for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
|
|
85
|
+
throw new Error(`OS blueprint template not found. Checked: ${candidates.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { registerOsBlueprintCommand };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=register-os-blueprint-DGjBUZYa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-os-blueprint-DGjBUZYa.js","names":[],"sources":["../src/commands/infra/register-os-blueprint.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport { toTitleCase } from \"../../utils/case-converters.js\";\nimport { detectMonorepo } from \"../../utils/detect-monorepo.js\";\nimport { readWorkspaceManifest } from \"../../utils/workspace-manifest.js\";\nimport {\n createOrUpdateInfraPullRequest,\n createInfraGitHubApi,\n INFRA_BASE_BRANCH,\n INFRA_REPOSITORY,\n type InfraGitHubApi,\n resolveGitHubToken,\n} from \"./github.js\";\n\nconst OS_BLUEPRINT_TEMPLATE = \"os.blueprint.yaml.template\";\n\nexport interface RegisterOsBlueprintResult {\n blueprintName: 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 targetPath: string;\n}\n\nexport async function registerOsBlueprint(\n args: {\n cwd?: string;\n github?: InfraGitHubApi;\n } = {},\n): Promise<RegisterOsBlueprintResult> {\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-${blueprintName}-blueprint`;\n const targetPath = [\n \"ryvn\",\n \"definitions\",\n customerSlug,\n \"blueprints\",\n `${blueprintName}.blueprint.yaml`,\n ].join(\"/\");\n const content = await renderOsBlueprint(customerSlug);\n\n const mainFile = await github.getFile(targetPath, INFRA_BASE_BRANCH);\n if (mainFile) {\n if (mainFile.content === content) {\n return {\n blueprintName,\n branchName,\n customerSlug,\n pullRequestUrl: null,\n repository: INFRA_REPOSITORY,\n status: \"already_registered\",\n targetPath,\n };\n }\n\n throw new Error(\n `${targetPath} already exists in ${INFRA_REPOSITORY}. Not overwriting an existing customer OS blueprint.`,\n );\n }\n\n const pullRequest = await createOrUpdateInfraPullRequest({\n branchName,\n content,\n github,\n message: `Register ${blueprintName} blueprint`,\n targetPath,\n title: `Register ${blueprintName} blueprint`,\n body: [\n `Registers the ${blueprintName} OS blueprint for ${customerSlug}.`,\n \"\",\n \"Generated by `mosaic infra register-os-blueprint`.\",\n ].join(\"\\n\"),\n });\n\n return {\n blueprintName,\n branchName,\n customerSlug,\n pullRequestUrl: pullRequest.pullRequestUrl,\n repository: INFRA_REPOSITORY,\n status: pullRequest.status,\n targetPath,\n };\n}\n\nexport async function registerOsBlueprintCommand(): Promise<void> {\n try {\n const result = await registerOsBlueprint();\n\n if (result.status === \"already_registered\") {\n console.log(\n chalk.green(\"✔\"),\n `${result.blueprintName} 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.blueprintName}:`,\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\nasync function renderOsBlueprint(customerSlug: string): Promise<string> {\n const template = await fs.readFile(getOsBlueprintTemplatePath(), \"utf-8\");\n const customerTitle = toTitleCase(customerSlug);\n\n return template\n .replaceAll(\"__CUSTOMER_SLUG__\", customerSlug)\n .replaceAll(\"__CUSTOMER_TITLE__\", customerTitle);\n}\n\nfunction getOsBlueprintTemplatePath(): string {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(currentDir, \"../templates/infra\", OS_BLUEPRINT_TEMPLATE),\n path.resolve(currentDir, \"../../../templates/infra\", OS_BLUEPRINT_TEMPLATE),\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n throw new Error(\n `OS blueprint template not found. Checked: ${candidates.join(\", \")}`,\n );\n}\n"],"mappings":";;;;;;;;AAgBA,MAAM,wBAAwB;AAY9B,eAAsB,oBACpB,OAGI,EAAE,EAC8B;CAEpC,MAAM,kBAAkB,MAAM,eADlB,KAAK,OAAO,QAAQ,KAAK,CACY;AACjD,KAAI,CAAC,gBAAgB,SAAS,CAAC,gBAAgB,QAC7C,OAAM,IAAI,MACR,uFACD;CAMH,MAAM,gBAAe,MAHW,sBAC9B,gBAAgB,QACjB,GACuC;AACxC,KAAI,CAAC,aACH,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,KAAK,UAAU,qBAAqB,oBAAoB,CAAC;CACxE,MAAM,gBAAgB,GAAG,aAAa;CACtC,MAAM,aAAa,sBAAsB,cAAc;CACvD,MAAM,aAAa;EACjB;EACA;EACA;EACA;EACA,GAAG,cAAc;EAClB,CAAC,KAAK,IAAI;CACX,MAAM,UAAU,MAAM,kBAAkB,aAAa;CAErD,MAAM,WAAW,MAAM,OAAO,QAAQ,YAAY,kBAAkB;AACpE,KAAI,UAAU;AACZ,MAAI,SAAS,YAAY,QACvB,QAAO;GACL;GACA;GACA;GACA,gBAAgB;GAChB,YAAY;GACZ,QAAQ;GACR;GACD;AAGH,QAAM,IAAI,MACR,GAAG,WAAW,qBAAqB,iBAAiB,sDACrD;;CAGH,MAAM,cAAc,MAAM,+BAA+B;EACvD;EACA;EACA;EACA,SAAS,YAAY,cAAc;EACnC;EACA,OAAO,YAAY,cAAc;EACjC,MAAM;GACJ,iBAAiB,cAAc,oBAAoB,aAAa;GAChE;GACA;GACD,CAAC,KAAK,KAAK;EACb,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACA,gBAAgB,YAAY;EAC5B,YAAY;EACZ,QAAQ,YAAY;EACpB;EACD;;AAGH,eAAsB,6BAA4C;AAChE,KAAI;EACF,MAAM,SAAS,MAAM,qBAAqB;AAE1C,MAAI,OAAO,WAAW,sBAAsB;AAC1C,WAAQ,IACN,MAAM,MAAM,IAAI,EAChB,GAAG,OAAO,cAAc,4BAA4B,OAAO,WAAW,MACtE,MAAM,KAAK,OAAO,WAAW,CAC9B;AACD;;EAGF,MAAM,OACJ,OAAO,WAAW,eAAe,YAAY;AAC/C,UAAQ,IACN,MAAM,MAAM,IAAI,EAChB,GAAG,KAAK,gBAAgB,OAAO,cAAc,IAC7C,MAAM,KAAK,OAAO,eAAe,CAClC;UACM,OAAO;AACd,UAAQ,MAAM,MAAM,IAAI,SAAS,EAAG,MAAgB,QAAQ;AAC5D,UAAQ,KAAK,EAAE;;;AAInB,eAAe,kBAAkB,cAAuC;CACtE,MAAM,WAAW,MAAM,GAAG,SAAS,4BAA4B,EAAE,QAAQ;CACzE,MAAM,gBAAgB,YAAY,aAAa;AAE/C,QAAO,SACJ,WAAW,qBAAqB,aAAa,CAC7C,WAAW,sBAAsB,cAAc;;AAGpD,SAAS,6BAAqC;CAC5C,MAAM,aAAa,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CAC/D,MAAM,aAAa,CACjB,KAAK,QAAQ,YAAY,sBAAsB,sBAAsB,EACrE,KAAK,QAAQ,YAAY,4BAA4B,sBAAsB,CAC5E;AAED,MAAK,MAAM,aAAa,WACtB,KAAI,GAAG,WAAW,UAAU,CAAE,QAAO;AAGvC,OAAM,IAAI,MACR,6CAA6C,WAAW,KAAK,KAAK,GACnE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as getTemplateVersionFromTag, n as getLatestTemplateTag } from "./git-ops-
|
|
1
|
+
import { r as readManifest } from "./manifest-CqIDnbgs.js";
|
|
2
|
+
import { i as getTemplateVersionFromTag, n as getLatestTemplateTag } from "./git-ops-BD7JNnal.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
//#region src/commands/status.ts
|
|
@@ -45,4 +45,4 @@ async function statusCommand(options) {
|
|
|
45
45
|
//#endregion
|
|
46
46
|
export { statusCommand };
|
|
47
47
|
|
|
48
|
-
//# sourceMappingURL=status-
|
|
48
|
+
//# sourceMappingURL=status-BXYaQ4a2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-
|
|
1
|
+
{"version":3,"file":"status-BXYaQ4a2.js","names":[],"sources":["../src/commands/status.ts"],"sourcesContent":["import path from \"node:path\";\nimport chalk from \"chalk\";\nimport {\n getLatestTemplateTag,\n getTemplateVersionFromTag,\n} from \"../utils/git-ops.js\";\nimport { readManifest } from \"../utils/manifest.js\";\n\nexport interface StatusOptions {\n mosaicTemplatePath?: string;\n}\n\nexport async function statusCommand(options: StatusOptions): Promise<void> {\n const cwd = process.cwd();\n\n try {\n const manifest = await readManifest(cwd);\n\n console.log();\n console.log(chalk.bold(\"Mosaic Template Status\"));\n console.log();\n console.log(chalk.dim(\" Template type:\"), manifest.templateType);\n console.log(chalk.dim(\" Current version:\"), manifest.templateVersion);\n console.log(chalk.dim(\" Template commit:\"), manifest.templateCommit);\n console.log(chalk.dim(\" Created:\"), manifest.createdAt);\n if (manifest.lastSyncedAt) {\n console.log(chalk.dim(\" Last synced:\"), manifest.lastSyncedAt);\n }\n\n const rawPath =\n options.mosaicTemplatePath || process.env.MOSAIC_TEMPLATE_PATH;\n const mosaicTemplatePath = rawPath ? path.resolve(rawPath) : undefined;\n\n if (mosaicTemplatePath) {\n const latestTag = getLatestTemplateTag(\n manifest.templateType,\n mosaicTemplatePath,\n );\n if (latestTag) {\n const latestVersion = getTemplateVersionFromTag(latestTag);\n console.log(chalk.dim(\" Latest version:\"), latestVersion);\n console.log();\n\n if (latestVersion !== manifest.templateVersion) {\n console.log(\n chalk.yellow(\n ` Update available: ${manifest.templateVersion} → ${latestVersion}`,\n ),\n );\n console.log(\n chalk.dim(\" Run:\"),\n `create sync --mosaic-template-path ${mosaicTemplatePath}`,\n );\n } else {\n console.log(chalk.green(\" Up to date\"));\n }\n } else {\n console.log();\n console.log(chalk.yellow(\" No template tags found in mosaic repo.\"));\n console.log(\n chalk.dim(\" Run:\"),\n `cd ${mosaicTemplatePath} && pnpm template:tag`,\n );\n }\n } else {\n console.log();\n console.log(\n chalk.dim(\n \" Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH to check for updates\",\n ),\n );\n }\n\n console.log();\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n}\n"],"mappings":";;;;;AAYA,eAAsB,cAAc,SAAuC;CACzE,MAAM,MAAM,QAAQ,KAAK;AAEzB,KAAI;EACF,MAAM,WAAW,MAAM,aAAa,IAAI;AAExC,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,yBAAyB,CAAC;AACjD,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,IAAI,mBAAmB,EAAE,SAAS,aAAa;AACjE,UAAQ,IAAI,MAAM,IAAI,qBAAqB,EAAE,SAAS,gBAAgB;AACtE,UAAQ,IAAI,MAAM,IAAI,qBAAqB,EAAE,SAAS,eAAe;AACrE,UAAQ,IAAI,MAAM,IAAI,aAAa,EAAE,SAAS,UAAU;AACxD,MAAI,SAAS,aACX,SAAQ,IAAI,MAAM,IAAI,iBAAiB,EAAE,SAAS,aAAa;EAGjE,MAAM,UACJ,QAAQ,sBAAsB,QAAQ,IAAI;EAC5C,MAAM,qBAAqB,UAAU,KAAK,QAAQ,QAAQ,GAAG,KAAA;AAE7D,MAAI,oBAAoB;GACtB,MAAM,YAAY,qBAChB,SAAS,cACT,mBACD;AACD,OAAI,WAAW;IACb,MAAM,gBAAgB,0BAA0B,UAAU;AAC1D,YAAQ,IAAI,MAAM,IAAI,oBAAoB,EAAE,cAAc;AAC1D,YAAQ,KAAK;AAEb,QAAI,kBAAkB,SAAS,iBAAiB;AAC9C,aAAQ,IACN,MAAM,OACJ,uBAAuB,SAAS,gBAAgB,KAAK,gBACtD,CACF;AACD,aAAQ,IACN,MAAM,IAAI,SAAS,EACnB,sCAAsC,qBACvC;UAED,SAAQ,IAAI,MAAM,MAAM,eAAe,CAAC;UAErC;AACL,YAAQ,KAAK;AACb,YAAQ,IAAI,MAAM,OAAO,2CAA2C,CAAC;AACrE,YAAQ,IACN,MAAM,IAAI,SAAS,EACnB,MAAM,mBAAmB,uBAC1B;;SAEE;AACL,WAAQ,KAAK;AACb,WAAQ,IACN,MAAM,IACJ,gFACD,CACF;;AAGH,UAAQ,KAAK;UACN,OAAO;AACd,UAAQ,MAAM,MAAM,IAAK,MAAgB,QAAQ,CAAC;AAClD,UAAQ,KAAK,EAAE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as getTemplateVersionFromTag, n as getLatestTemplateTag, r as getTemplateDiff } from "./git-ops-
|
|
1
|
+
import { i as resolveMosaicTemplatePath, r as readManifest } from "./manifest-CqIDnbgs.js";
|
|
2
|
+
import { i as getTemplateVersionFromTag, n as getLatestTemplateTag, r as getTemplateDiff } from "./git-ops-BD7JNnal.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import fs from "fs-extra";
|
|
@@ -98,4 +98,4 @@ async function syncCommand(options) {
|
|
|
98
98
|
//#endregion
|
|
99
99
|
export { syncCommand };
|
|
100
100
|
|
|
101
|
-
//# sourceMappingURL=sync-
|
|
101
|
+
//# sourceMappingURL=sync-BayU4w1j.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-
|
|
1
|
+
{"version":3,"file":"sync-BayU4w1j.js","names":[],"sources":["../src/commands/sync.ts"],"sourcesContent":["import path from \"node:path\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport {\n getLatestTemplateTag,\n getTemplateVersionFromTag,\n getTemplateDiff,\n} from \"../utils/git-ops.js\";\nimport {\n readManifest,\n resolveMosaicTemplatePath,\n type MosaicManifest,\n} from \"../utils/manifest.js\";\n\nexport interface SyncOptions {\n mosaicTemplatePath?: string;\n to?: string;\n}\n\nfunction generateSyncContext(\n manifest: MosaicManifest,\n toVersion: string,\n diff: string,\n notes: string,\n): string {\n let content = `# Mosaic Sync Context\n\n## App Info\n- **Template:** ${manifest.templateType}\n- **Current version:** ${manifest.templateVersion}\n- **Target version:** ${toVersion}\n\n## Placeholder Mappings\n\nWhen applying template changes, replace these placeholder tokens with the actual values:\n\n| Placeholder | Value |\n|------------|-------|\n${Object.entries(manifest.placeholders)\n .map(([k, v]) => `| \\`${k}\\` | \\`${v}\\` |`)\n .join(\"\\n\")}\n\n## Template Changes (${manifest.templateVersion} → ${toVersion})\n\n\\`\\`\\`diff\n${diff}\n\\`\\`\\`\n`;\n\n if (notes.trim()) {\n content += `\n## Divergence Notes (from mosaic-template-notes.md)\n\n${notes}\n`;\n }\n\n content += `\n## Instructions\n\n1. Apply the template changes above to this app\n2. When you see placeholder tokens (e.g. \\`__APP_NAME__\\`), replace them with the actual values from the mapping table\n3. Check the divergence notes — preserve intentional divergences\n4. For files not modified locally: apply changes directly\n5. For files modified locally: merge intelligently, preserving local customizations\n6. After applying all changes, run: \\`pnpm install && pnpm build && pnpm lint\\`\n7. Update \\`.mosaic-template.json\\`: set \\`templateVersion\\` to \\`\"${toVersion}\"\\` and update \\`templateCommit\\`\n8. If you made decisions about merge conflicts, add notes to \\`mosaic-template-notes.md\\`\n9. Delete this file (\\`.mosaic-sync-context.md\\`) when done\n`;\n\n return content;\n}\n\nexport async function syncCommand(options: SyncOptions): Promise<void> {\n const cwd = process.cwd();\n\n try {\n const manifest = await readManifest(cwd);\n const mosaicTemplatePath = resolveMosaicTemplatePath(options);\n\n const fromTag = `template/${manifest.templateType}/${manifest.templateVersion}`;\n\n let toTag: string;\n if (options.to) {\n toTag = `template/${manifest.templateType}/${options.to}`;\n } else {\n const latest = getLatestTemplateTag(\n manifest.templateType,\n mosaicTemplatePath,\n );\n if (!latest) {\n console.error(\n chalk.red(\n \"No template tags found. Run 'pnpm template:tag' in the mosaic repo first.\",\n ),\n );\n process.exit(1);\n }\n toTag = latest;\n }\n\n const toVersion = getTemplateVersionFromTag(toTag);\n\n if (toVersion === manifest.templateVersion) {\n console.log(chalk.green(\"Already up to date.\"));\n return;\n }\n\n const diff = getTemplateDiff(\n mosaicTemplatePath,\n manifest.source.templatePath,\n fromTag,\n toTag,\n );\n\n if (!diff.trim()) {\n console.log(chalk.green(\"No template file changes between versions.\"));\n return;\n }\n\n // Read mosaic-template-notes.md if it exists\n const notesPath = path.join(cwd, \"mosaic-template-notes.md\");\n let notes = \"\";\n if (await fs.pathExists(notesPath)) {\n notes = await fs.readFile(notesPath, \"utf-8\");\n }\n\n const context = generateSyncContext(manifest, toVersion, diff, notes);\n const contextPath = path.join(cwd, \".mosaic-sync-context.md\");\n await fs.writeFile(contextPath, context);\n\n console.log();\n console.log(chalk.bold(\"Sync Context Generated\"));\n console.log();\n console.log(chalk.dim(\" From:\"), manifest.templateVersion);\n console.log(chalk.dim(\" To:\"), toVersion);\n console.log(chalk.dim(\" Context file:\"), \".mosaic-sync-context.md\");\n console.log();\n console.log(\"Next steps:\");\n console.log(chalk.dim(\" 1.\"), \"Open Claude Code in this directory\");\n console.log(\n chalk.dim(\" 2.\"),\n 'Tell Claude: \"Read .mosaic-sync-context.md and apply the template changes\"',\n );\n console.log(\n chalk.dim(\" 3.\"),\n \"Review Claude's changes, then delete .mosaic-sync-context.md\",\n );\n console.log();\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;AAmBA,SAAS,oBACP,UACA,WACA,MACA,OACQ;CACR,IAAI,UAAU;;;kBAGE,SAAS,aAAa;yBACf,SAAS,gBAAgB;wBAC1B,UAAU;;;;;;;;EAQhC,OAAO,QAAQ,SAAS,aAAa,CACpC,KAAK,CAAC,GAAG,OAAO,OAAO,EAAE,SAAS,EAAE,MAAM,CAC1C,KAAK,KAAK,CAAC;;uBAES,SAAS,gBAAgB,KAAK,UAAU;;;EAG7D,KAAK;;;AAIL,KAAI,MAAM,MAAM,CACd,YAAW;;;EAGb,MAAM;;AAIN,YAAW;;;;;;;;;qEASwD,UAAU;;;;AAK7E,QAAO;;AAGT,eAAsB,YAAY,SAAqC;CACrE,MAAM,MAAM,QAAQ,KAAK;AAEzB,KAAI;EACF,MAAM,WAAW,MAAM,aAAa,IAAI;EACxC,MAAM,qBAAqB,0BAA0B,QAAQ;EAE7D,MAAM,UAAU,YAAY,SAAS,aAAa,GAAG,SAAS;EAE9D,IAAI;AACJ,MAAI,QAAQ,GACV,SAAQ,YAAY,SAAS,aAAa,GAAG,QAAQ;OAChD;GACL,MAAM,SAAS,qBACb,SAAS,cACT,mBACD;AACD,OAAI,CAAC,QAAQ;AACX,YAAQ,MACN,MAAM,IACJ,4EACD,CACF;AACD,YAAQ,KAAK,EAAE;;AAEjB,WAAQ;;EAGV,MAAM,YAAY,0BAA0B,MAAM;AAElD,MAAI,cAAc,SAAS,iBAAiB;AAC1C,WAAQ,IAAI,MAAM,MAAM,sBAAsB,CAAC;AAC/C;;EAGF,MAAM,OAAO,gBACX,oBACA,SAAS,OAAO,cAChB,SACA,MACD;AAED,MAAI,CAAC,KAAK,MAAM,EAAE;AAChB,WAAQ,IAAI,MAAM,MAAM,6CAA6C,CAAC;AACtE;;EAIF,MAAM,YAAY,KAAK,KAAK,KAAK,2BAA2B;EAC5D,IAAI,QAAQ;AACZ,MAAI,MAAM,GAAG,WAAW,UAAU,CAChC,SAAQ,MAAM,GAAG,SAAS,WAAW,QAAQ;EAG/C,MAAM,UAAU,oBAAoB,UAAU,WAAW,MAAM,MAAM;EACrE,MAAM,cAAc,KAAK,KAAK,KAAK,0BAA0B;AAC7D,QAAM,GAAG,UAAU,aAAa,QAAQ;AAExC,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,yBAAyB,CAAC;AACjD,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,IAAI,UAAU,EAAE,SAAS,gBAAgB;AAC3D,UAAQ,IAAI,MAAM,IAAI,QAAQ,EAAE,UAAU;AAC1C,UAAQ,IAAI,MAAM,IAAI,kBAAkB,EAAE,0BAA0B;AACpE,UAAQ,KAAK;AACb,UAAQ,IAAI,cAAc;AAC1B,UAAQ,IAAI,MAAM,IAAI,OAAO,EAAE,qCAAqC;AACpE,UAAQ,IACN,MAAM,IAAI,OAAO,EACjB,+EACD;AACD,UAAQ,IACN,MAAM,IAAI,OAAO,EACjB,+DACD;AACD,UAAQ,KAAK;UACN,OAAO;AACd,UAAQ,MAAM,MAAM,IAAK,MAAgB,QAAQ,CAAC;AAClD,UAAQ,KAAK,EAAE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/utils/case-converters.ts
|
|
5
|
+
/** Lowercase, hyphenated, npm-package-name-safe form: "My Cool App" → "my-cool-app". */
|
|
6
|
+
function toKebabCase(str) {
|
|
7
|
+
return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
8
|
+
}
|
|
9
|
+
/** Display form derived from a kebab-case name: "my-cool-app" → "My Cool App". */
|
|
10
|
+
function toTitleCase(str) {
|
|
11
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
12
|
+
}
|
|
13
|
+
/** Identifier form for env vars and DB names: "my-cool-app" → "my_cool_app". */
|
|
14
|
+
function toSnakeCase(str) {
|
|
15
|
+
return str.replace(/-/g, "_");
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/utils/template-versions.ts
|
|
19
|
+
const FALLBACK_TEMPLATE_VERSION = "1.0.0";
|
|
20
|
+
function readTemplateVersions() {
|
|
21
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const candidates = [path.resolve(currentDir, "../template-versions.json"), path.resolve(currentDir, "../../template-versions.json")];
|
|
23
|
+
for (const versionsPath of candidates) try {
|
|
24
|
+
const content = fs.readFileSync(versionsPath, "utf-8");
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
} catch {}
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
function getTemplateVersion(templateType) {
|
|
30
|
+
return readTemplateVersions()[templateType] ?? FALLBACK_TEMPLATE_VERSION;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { toTitleCase as i, toKebabCase as n, toSnakeCase as r, getTemplateVersion as t };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=template-versions-CEIP9vhl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-versions-CEIP9vhl.js","names":[],"sources":["../src/utils/case-converters.ts","../src/utils/template-versions.ts"],"sourcesContent":["/** Lowercase, hyphenated, npm-package-name-safe form: \"My Cool App\" → \"my-cool-app\". */\nexport function toKebabCase(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/** Display form derived from a kebab-case name: \"my-cool-app\" → \"My Cool App\". */\nexport function toTitleCase(str: string): string {\n return str\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/** Identifier form for env vars and DB names: \"my-cool-app\" → \"my_cool_app\". */\nexport function toSnakeCase(str: string): string {\n return str.replace(/-/g, \"_\");\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport fs from \"fs-extra\";\n\nconst FALLBACK_TEMPLATE_VERSION = \"1.0.0\";\n\nexport function readTemplateVersions(): Record<string, string> {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(currentDir, \"../template-versions.json\"),\n path.resolve(currentDir, \"../../template-versions.json\"),\n ];\n\n for (const versionsPath of candidates) {\n try {\n const content = fs.readFileSync(versionsPath, \"utf-8\");\n return JSON.parse(content);\n } catch {\n // Try the next path. Source tests and bundled CLI resolve differently.\n }\n }\n\n return {};\n}\n\nexport function getTemplateVersion(templateType: string): string {\n return readTemplateVersions()[templateType] ?? FALLBACK_TEMPLATE_VERSION;\n}\n"],"mappings":";;;;;AACA,SAAgB,YAAY,KAAqB;AAC/C,QAAO,IACJ,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG;;;AAI1B,SAAgB,YAAY,KAAqB;AAC/C,QAAO,IACJ,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,IAAI;;;AAId,SAAgB,YAAY,KAAqB;AAC/C,QAAO,IAAI,QAAQ,MAAM,IAAI;;;;ACf/B,MAAM,4BAA4B;AAElC,SAAgB,uBAA+C;CAC7D,MAAM,aAAa,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CAC/D,MAAM,aAAa,CACjB,KAAK,QAAQ,YAAY,4BAA4B,EACrD,KAAK,QAAQ,YAAY,+BAA+B,CACzD;AAED,MAAK,MAAM,gBAAgB,WACzB,KAAI;EACF,MAAM,UAAU,GAAG,aAAa,cAAc,QAAQ;AACtD,SAAO,KAAK,MAAM,QAAQ;SACpB;AAKV,QAAO,EAAE;;AAGX,SAAgB,mBAAmB,cAA8B;AAC/D,QAAO,sBAAsB,CAAC,iBAAiB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as getFileAtTag } from "./git-ops-
|
|
1
|
+
import { i as resolveMosaicTemplatePath, r as readManifest } from "./manifest-CqIDnbgs.js";
|
|
2
|
+
import { t as getFileAtTag } from "./git-ops-BD7JNnal.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import fs from "fs-extra";
|
|
@@ -82,4 +82,4 @@ async function upstreamCommand(options) {
|
|
|
82
82
|
//#endregion
|
|
83
83
|
export { upstreamCommand };
|
|
84
84
|
|
|
85
|
-
//# sourceMappingURL=upstream-
|
|
85
|
+
//# sourceMappingURL=upstream-CZEzLrS4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstream-
|
|
1
|
+
{"version":3,"file":"upstream-CZEzLrS4.js","names":[],"sources":["../src/commands/upstream.ts"],"sourcesContent":["import path from \"node:path\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport { getFileAtTag } from \"../utils/git-ops.js\";\nimport {\n readManifest,\n resolveMosaicTemplatePath,\n type MosaicManifest,\n} from \"../utils/manifest.js\";\n\nexport interface UpstreamOptions {\n mosaicTemplatePath?: string;\n files?: string[];\n}\n\nasync function generateUpstreamContext(\n manifest: MosaicManifest,\n mosaicTemplatePath: string,\n tag: string,\n appDir: string,\n files: string[],\n): Promise<string> {\n let content = `# Mosaic Upstream Context\n\n## App Info\n- **App name:** ${manifest.placeholders.__APP_NAME__ || \"unknown\"}\n- **Template:** ${manifest.templateType}\n- **Template version:** ${manifest.templateVersion}\n\n## Placeholder Mappings\n\nWhen generalizing app code back to template, replace these values with placeholder tokens:\n\n| Value | Placeholder |\n|-------|------------|\n${Object.entries(manifest.placeholders)\n .sort((a, b) => b[1].length - a[1].length) // longest first to avoid partial matches\n .map(([k, v]) => `| \\`${v}\\` | \\`${k}\\` |`)\n .join(\"\\n\")}\n\n## Files to Review\n\n`;\n\n for (const file of files) {\n const appFilePath = path.resolve(appDir, file);\n const templateRelPath = `${manifest.source.templatePath}/${file}`;\n\n const appContent = (await fs.pathExists(appFilePath))\n ? await fs.readFile(appFilePath, \"utf-8\")\n : null;\n const templateContent = getFileAtTag(\n mosaicTemplatePath,\n tag,\n templateRelPath,\n );\n\n content += `### ${file}\\n\\n`;\n\n if (!templateContent && appContent) {\n content += `**New file** (not in template at ${manifest.templateVersion})\\n\\n`;\n content += `\\`\\`\\`\\n${appContent}\\n\\`\\`\\`\\n\\n`;\n } else if (templateContent && !appContent) {\n content += `**Deleted** (exists in template but not in app)\\n\\n`;\n } else if (appContent && templateContent) {\n content += `**App version:**\\n\\`\\`\\`\\n${appContent}\\n\\`\\`\\`\\n\\n`;\n content += `**Template version (at ${manifest.templateVersion}):**\\n\\`\\`\\`\\n${templateContent}\\n\\`\\`\\`\\n\\n`;\n } else {\n content += `**Not found** (file does not exist in app or template)\\n\\n`;\n }\n }\n\n content += `## Instructions\n\n1. Review each file above\n2. Determine which changes are generalizable (useful for all apps) vs app-specific\n3. For generalizable changes: apply them to the template at \\`${manifest.source.templatePath}/\\`\n4. When applying, replace app-specific values with placeholders using the mapping table above (replace longest values first)\n5. After applying, bump the version in \\`packages/blueberry/template-versions.json\\`\n6. Run \\`pnpm template:tag\\` to create the new version tag\n7. Delete this file (\\`.mosaic-upstream-context.md\\`) when done\n`;\n\n return content;\n}\n\nexport async function upstreamCommand(options: UpstreamOptions): Promise<void> {\n const cwd = process.cwd();\n\n try {\n const manifest = await readManifest(cwd);\n const mosaicTemplatePath = resolveMosaicTemplatePath(options);\n\n if (!options.files || options.files.length === 0) {\n console.error(\n chalk.red(\"Specify files with --files <file1> <file2> ...\"),\n );\n console.log(\n chalk.dim(\n \" Example: create upstream --files src/config/getEnvConfig.ts\",\n ),\n );\n process.exit(1);\n }\n\n const tag = `template/${manifest.templateType}/${manifest.templateVersion}`;\n\n const context = await generateUpstreamContext(\n manifest,\n mosaicTemplatePath,\n tag,\n cwd,\n options.files,\n );\n const contextPath = path.join(cwd, \".mosaic-upstream-context.md\");\n await fs.writeFile(contextPath, context);\n\n console.log();\n console.log(chalk.bold(\"Upstream Context Generated\"));\n console.log();\n console.log(chalk.dim(\" Files:\"), options.files.join(\", \"));\n console.log(chalk.dim(\" Context file:\"), \".mosaic-upstream-context.md\");\n console.log();\n console.log(\"Next steps:\");\n console.log(chalk.dim(\" 1.\"), \"Open Claude Code in the mosaic repo\");\n console.log(\n chalk.dim(\" 2.\"),\n `Tell Claude: \"Read ${path.resolve(cwd, \".mosaic-upstream-context.md\")} and apply generalizable changes to the template\"`,\n );\n console.log(chalk.dim(\" 3.\"), \"Review Claude's changes to the template\");\n console.log();\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;AAeA,eAAe,wBACb,UACA,oBACA,KACA,QACA,OACiB;CACjB,IAAI,UAAU;;;kBAGE,SAAS,aAAa,gBAAgB,UAAU;kBAChD,SAAS,aAAa;0BACd,SAAS,gBAAgB;;;;;;;;EAQjD,OAAO,QAAQ,SAAS,aAAa,CACpC,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,GAAG,OAAO,CACzC,KAAK,CAAC,GAAG,OAAO,OAAO,EAAE,SAAS,EAAE,MAAM,CAC1C,KAAK,KAAK,CAAC;;;;;AAMZ,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,cAAc,KAAK,QAAQ,QAAQ,KAAK;EAC9C,MAAM,kBAAkB,GAAG,SAAS,OAAO,aAAa,GAAG;EAE3D,MAAM,aAAc,MAAM,GAAG,WAAW,YAAY,GAChD,MAAM,GAAG,SAAS,aAAa,QAAQ,GACvC;EACJ,MAAM,kBAAkB,aACtB,oBACA,KACA,gBACD;AAED,aAAW,OAAO,KAAK;AAEvB,MAAI,CAAC,mBAAmB,YAAY;AAClC,cAAW,oCAAoC,SAAS,gBAAgB;AACxE,cAAW,WAAW,WAAW;aACxB,mBAAmB,CAAC,WAC7B,YAAW;WACF,cAAc,iBAAiB;AACxC,cAAW,6BAA6B,WAAW;AACnD,cAAW,0BAA0B,SAAS,gBAAgB,gBAAgB,gBAAgB;QAE9F,YAAW;;AAIf,YAAW;;;;gEAImD,SAAS,OAAO,aAAa;;;;;;AAO3F,QAAO;;AAGT,eAAsB,gBAAgB,SAAyC;CAC7E,MAAM,MAAM,QAAQ,KAAK;AAEzB,KAAI;EACF,MAAM,WAAW,MAAM,aAAa,IAAI;EACxC,MAAM,qBAAqB,0BAA0B,QAAQ;AAE7D,MAAI,CAAC,QAAQ,SAAS,QAAQ,MAAM,WAAW,GAAG;AAChD,WAAQ,MACN,MAAM,IAAI,iDAAiD,CAC5D;AACD,WAAQ,IACN,MAAM,IACJ,gEACD,CACF;AACD,WAAQ,KAAK,EAAE;;EAKjB,MAAM,UAAU,MAAM,wBACpB,UACA,oBACA,YALsB,SAAS,aAAa,GAAG,SAAS,mBAMxD,KACA,QAAQ,MACT;EACD,MAAM,cAAc,KAAK,KAAK,KAAK,8BAA8B;AACjE,QAAM,GAAG,UAAU,aAAa,QAAQ;AAExC,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,6BAA6B,CAAC;AACrD,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,IAAI,WAAW,EAAE,QAAQ,MAAM,KAAK,KAAK,CAAC;AAC5D,UAAQ,IAAI,MAAM,IAAI,kBAAkB,EAAE,8BAA8B;AACxE,UAAQ,KAAK;AACb,UAAQ,IAAI,cAAc;AAC1B,UAAQ,IAAI,MAAM,IAAI,OAAO,EAAE,sCAAsC;AACrE,UAAQ,IACN,MAAM,IAAI,OAAO,EACjB,sBAAsB,KAAK,QAAQ,KAAK,8BAA8B,CAAC,mDACxE;AACD,UAAQ,IAAI,MAAM,IAAI,OAAO,EAAE,0CAA0C;AACzE,UAAQ,KAAK;UACN,OAAO;AACd,UAAQ,MAAM,MAAM,IAAK,MAAgB,QAAQ,CAAC;AAClD,UAAQ,KAAK,EAAE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import validateNpmPackageName from "validate-npm-package-name";
|
|
2
|
+
//#region src/utils/validate.ts
|
|
3
|
+
function validateProjectName(name) {
|
|
4
|
+
const result = validateNpmPackageName(name);
|
|
5
|
+
if (!result.validForNewPackages) return {
|
|
6
|
+
valid: false,
|
|
7
|
+
error: [...result.errors || [], ...result.warnings || []][0] || "Invalid package name"
|
|
8
|
+
};
|
|
9
|
+
return { valid: true };
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { validateProjectName as t };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=validate-dssldJAj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-dssldJAj.js","names":[],"sources":["../src/utils/validate.ts"],"sourcesContent":["import validateNpmPackageName from \"validate-npm-package-name\";\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n const result = validateNpmPackageName(name);\n\n if (!result.validForNewPackages) {\n const errors = [...(result.errors || []), ...(result.warnings || [])];\n return {\n valid: false,\n error: errors[0] || \"Invalid package name\",\n };\n }\n\n return { valid: true };\n}\n"],"mappings":";;AAOA,SAAgB,oBAAoB,MAAgC;CAClE,MAAM,SAAS,uBAAuB,KAAK;AAE3C,KAAI,CAAC,OAAO,oBAEV,QAAO;EACL,OAAO;EACP,OAAO,CAHO,GAAI,OAAO,UAAU,EAAE,EAAG,GAAI,OAAO,YAAY,EAAE,CAGpD,CAAC,MAAM;EACrB;AAGH,QAAO,EAAE,OAAO,MAAM"}
|
package/package.json
CHANGED
package/template-versions.json
CHANGED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://api.ryvn.app/v1/schemas/resources.json
|
|
2
|
+
kind: Blueprint
|
|
3
|
+
metadata:
|
|
4
|
+
name: __CUSTOMER_SLUG__-os
|
|
5
|
+
spec:
|
|
6
|
+
description: "__CUSTOMER_TITLE__-owned infrastructure for the OS monorepo"
|
|
7
|
+
displayName: "__CUSTOMER_TITLE__ OS"
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
- name: app_databases
|
|
11
|
+
type: map
|
|
12
|
+
group: General
|
|
13
|
+
displayName: "App Databases"
|
|
14
|
+
description: "Application database declarations keyed by app name. This default is the __CUSTOMER_TITLE__ OS app registry; BlueprintInstallation inputs should only override it for environment-specific drift. Each value may set database_name, schema_name, username, and secret_name."
|
|
15
|
+
default: {}
|
|
16
|
+
- name: auth_secret_name
|
|
17
|
+
type: string
|
|
18
|
+
group: General
|
|
19
|
+
displayName: "Auth Secret Name"
|
|
20
|
+
description: "Optional Kubernetes Secret name for shared auth database credentials. Defaults to <environment>-auth-postgresql."
|
|
21
|
+
default: ""
|
|
22
|
+
- name: auth_username
|
|
23
|
+
type: string
|
|
24
|
+
group: General
|
|
25
|
+
displayName: "Auth Username"
|
|
26
|
+
description: "Optional shared auth database username. Defaults to <environment>_auth."
|
|
27
|
+
default: ""
|
|
28
|
+
- name: aws_postgresql_cluster_name
|
|
29
|
+
type: string
|
|
30
|
+
group: "AWS PostgreSQL"
|
|
31
|
+
displayName: "AWS PostgreSQL Cluster Name"
|
|
32
|
+
description: "Optional Aurora PostgreSQL cluster name. Defaults to <environment>-postgresql."
|
|
33
|
+
default: ""
|
|
34
|
+
condition: '{{ eq EnvironmentProviderType "aws" }}'
|
|
35
|
+
- name: azure_postgresql_server_name
|
|
36
|
+
type: string
|
|
37
|
+
group: "Azure PostgreSQL"
|
|
38
|
+
displayName: "Azure PostgreSQL Server Name"
|
|
39
|
+
description: "Optional Azure PostgreSQL flexible server name. Defaults to <environment>-postgresql."
|
|
40
|
+
default: ""
|
|
41
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
42
|
+
- name: azure_postgresql_private_dns_zone_id
|
|
43
|
+
type: string
|
|
44
|
+
group: "Azure PostgreSQL"
|
|
45
|
+
displayName: "Azure PostgreSQL Private DNS Zone ID"
|
|
46
|
+
description: "Existing Azure PostgreSQL private DNS zone ID. Leave empty to create one."
|
|
47
|
+
default: ""
|
|
48
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
49
|
+
- name: azure_postgresql_subnet_id
|
|
50
|
+
type: string
|
|
51
|
+
group: "Azure PostgreSQL"
|
|
52
|
+
displayName: "Azure PostgreSQL Subnet ID"
|
|
53
|
+
description: "Existing delegated PostgreSQL subnet ID. Leave empty to create one."
|
|
54
|
+
default: ""
|
|
55
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
56
|
+
- name: azure_postgresql_subnet_address_prefix
|
|
57
|
+
type: string
|
|
58
|
+
group: "Azure PostgreSQL"
|
|
59
|
+
displayName: "Azure PostgreSQL Subnet Address Prefix"
|
|
60
|
+
description: "Address prefix for the delegated PostgreSQL subnet when creating one."
|
|
61
|
+
default: ""
|
|
62
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
63
|
+
- name: azure_postgresql_key_vault_id
|
|
64
|
+
type: string
|
|
65
|
+
group: "Azure PostgreSQL"
|
|
66
|
+
displayName: "Azure PostgreSQL Key Vault ID"
|
|
67
|
+
description: "Optional Key Vault ID for storing generated PostgreSQL admin credentials."
|
|
68
|
+
default: ""
|
|
69
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
70
|
+
- name: azure_postgresql_sku_name
|
|
71
|
+
type: string
|
|
72
|
+
group: "Azure PostgreSQL"
|
|
73
|
+
displayName: "Azure PostgreSQL SKU Name"
|
|
74
|
+
description: "Azure PostgreSQL flexible server SKU."
|
|
75
|
+
default: "B_Standard_B2s"
|
|
76
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
77
|
+
|
|
78
|
+
installations:
|
|
79
|
+
- service: os-postgresql-terraform-aws
|
|
80
|
+
condition: '{{ eq EnvironmentProviderType "aws" }}'
|
|
81
|
+
config: |
|
|
82
|
+
name: {{ EnvironmentName }}
|
|
83
|
+
namespace: {{ EnvironmentNamespace }}
|
|
84
|
+
{{ if ne (input "auth_secret_name") "" }}
|
|
85
|
+
auth_secret_name: '{{ input "auth_secret_name" }}'
|
|
86
|
+
{{ end }}
|
|
87
|
+
{{ if ne (input "auth_username") "" }}
|
|
88
|
+
auth_username: '{{ input "auth_username" }}'
|
|
89
|
+
{{ end }}
|
|
90
|
+
app_databases:
|
|
91
|
+
{{ input "app_databases" | toYaml | nindent 2 }}
|
|
92
|
+
tags:
|
|
93
|
+
managed-by: mosaic
|
|
94
|
+
ryvn-environment: {{ EnvironmentName }}
|
|
95
|
+
|
|
96
|
+
region: {{ .ryvn.env.state.cluster_region }}
|
|
97
|
+
vpc_id: {{ .ryvn.env.state.vpc.id }}
|
|
98
|
+
subnet_ids:
|
|
99
|
+
{{ .ryvn.env.state.vpc.private_subnet_ids | toYaml | nindent 2 }}
|
|
100
|
+
{{ if ne (input "aws_postgresql_cluster_name") "" }}
|
|
101
|
+
postgresql_cluster_name: '{{ input "aws_postgresql_cluster_name" }}'
|
|
102
|
+
{{ end }}
|
|
103
|
+
|
|
104
|
+
- service: os-postgresql-terraform-azure
|
|
105
|
+
condition: '{{ eq EnvironmentProviderType "azure" }}'
|
|
106
|
+
config: |
|
|
107
|
+
name: {{ EnvironmentName }}
|
|
108
|
+
namespace: {{ EnvironmentNamespace }}
|
|
109
|
+
{{ if ne (input "auth_secret_name") "" }}
|
|
110
|
+
auth_secret_name: '{{ input "auth_secret_name" }}'
|
|
111
|
+
{{ end }}
|
|
112
|
+
{{ if ne (input "auth_username") "" }}
|
|
113
|
+
auth_username: '{{ input "auth_username" }}'
|
|
114
|
+
{{ end }}
|
|
115
|
+
app_databases:
|
|
116
|
+
{{ input "app_databases" | toYaml | nindent 2 }}
|
|
117
|
+
tags:
|
|
118
|
+
managed-by: mosaic
|
|
119
|
+
ryvn-environment: {{ EnvironmentName }}
|
|
120
|
+
|
|
121
|
+
resource_group_name: {{ .ryvn.env.state.resource_group.name }}
|
|
122
|
+
virtual_network_id: {{ .ryvn.env.state.vnet.id }}
|
|
123
|
+
{{ if ne (input "azure_postgresql_server_name") "" }}
|
|
124
|
+
postgresql_server_name: '{{ input "azure_postgresql_server_name" }}'
|
|
125
|
+
{{ end }}
|
|
126
|
+
{{ if ne (input "azure_postgresql_private_dns_zone_id") "" }}
|
|
127
|
+
private_dns_zone_id: '{{ input "azure_postgresql_private_dns_zone_id" }}'
|
|
128
|
+
{{ end }}
|
|
129
|
+
{{ if ne (input "azure_postgresql_subnet_id") "" }}
|
|
130
|
+
subnet_id: '{{ input "azure_postgresql_subnet_id" }}'
|
|
131
|
+
{{ end }}
|
|
132
|
+
{{ if ne (input "azure_postgresql_subnet_address_prefix") "" }}
|
|
133
|
+
subnet_address_prefix: '{{ input "azure_postgresql_subnet_address_prefix" }}'
|
|
134
|
+
{{ end }}
|
|
135
|
+
{{ if ne (input "azure_postgresql_key_vault_id") "" }}
|
|
136
|
+
key_vault_id: '{{ input "azure_postgresql_key_vault_id" }}'
|
|
137
|
+
{{ end }}
|
|
138
|
+
postgresql_sku_name: '{{ input "azure_postgresql_sku_name" }}'
|