@percepta/create 3.6.2 → 4.0.0
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 +68 -125
- 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/library/README.md +5 -2
- package/templates/library/gitignore.template +1 -0
- package/templates/library/package.json.template +17 -13
- package/templates/library/src/index.test.ts +8 -0
- package/templates/library/tsconfig.json +1 -17
- package/templates/library/tsdown.config.ts +3 -0
- package/templates/library/vitest.config.ts +3 -0
- package/templates/monorepo/.dockerignore +1 -0
- package/templates/monorepo/.github/CODEOWNERS +67 -0
- package/templates/monorepo/.github/actions/ci/action.yml +56 -0
- package/templates/monorepo/.github/workflows/build-and-publish.yml +22 -0
- package/templates/monorepo/.github/workflows/pr-build.yml +21 -0
- package/templates/monorepo/.node-version +1 -0
- package/templates/monorepo/README.md +41 -3
- package/templates/monorepo/auth/README.md +6 -3
- package/templates/monorepo/auth/package.json +5 -7
- package/templates/monorepo/auth/src/auth.ts +0 -1
- package/templates/monorepo/auth/src/config/database.ts +1 -1
- package/templates/monorepo/auth/tsconfig.json +1 -10
- package/templates/{webapp → monorepo}/docker-compose.yml +2 -2
- package/templates/monorepo/gitignore.template +1 -0
- package/templates/monorepo/oxfmt.config.ts.template +3 -0
- package/templates/monorepo/oxlint.config.ts.template +3 -0
- package/templates/monorepo/package.json.template +22 -11
- package/templates/monorepo/scripts/setup-local-databases.mjs +183 -0
- package/templates/monorepo/turbo.json +20 -0
- package/templates/webapp/.node-version +0 -1
- package/templates/webapp/AGENTS.md +33 -35
- package/templates/webapp/README.md +34 -38
- 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/globals.d.ts +1 -0
- package/templates/webapp/oxfmt.config.ts.template +5 -0
- package/templates/webapp/package.json.template +18 -33
- package/templates/webapp/scripts/seed.ts +1 -1
- package/templates/webapp/scripts/start.sh +12 -16
- package/templates/webapp/src/app/global-error.tsx +1 -1
- package/templates/webapp/src/config/getEnvConfig.ts +4 -10
- package/templates/webapp/src/config/isDev.ts +0 -2
- package/templates/webapp/src/drizzle/db.ts +6 -21
- package/templates/webapp/src/lib/auth-client.ts +6 -3
- package/templates/webapp/src/startup-checks.ts +28 -7
- package/templates/webapp/tsconfig.json +1 -12
- package/templates/webapp/vitest.config.ts +3 -7
- package/templates/library/eslint.config.js +0 -10
- package/templates/monorepo/auth/scripts/setup-database.ts +0 -11
- package/templates/monorepo/eslint.config.js +0 -10
- package/templates/monorepo/tsconfig.json +0 -16
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +0 -92
- package/templates/webapp/.github/workflows/ci.yml +0 -149
- package/templates/webapp/.prettierrc.mjs +0 -5
- 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/eslint.config.mjs +0 -100
- package/templates/webapp/npmrc.template +0 -4
- 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
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ Scaffold and manage Mosaic packages.
|
|
|
8
8
|
npx @percepta/create
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
That's it. The CLI prompts you for the package type, repo name,
|
|
11
|
+
That's it. The CLI prompts you for the package type, repo name, customer slug,
|
|
12
|
+
and package name as needed. Defaults yield a running app — sign in as
|
|
13
|
+
`app-admin@example.com` / `password`.
|
|
12
14
|
|
|
13
15
|
## Options (mostly for automation)
|
|
14
16
|
|
|
@@ -18,6 +20,7 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
18
20
|
| -------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
19
21
|
| `-t, --type <type>` | Package type: `monorepo`, `webapp`, or `library` (skips the type prompt) |
|
|
20
22
|
| `--name <name>` | Package/app name (skips the package name prompt) |
|
|
23
|
+
| `--customer <slug>` | Customer slug stored in the generated monorepo workspace manifest |
|
|
21
24
|
| `--repo-name <name>` | Repo name when creating a new monorepo (skips the repo name prompt) |
|
|
22
25
|
| `--cwd <dir>` | Run as if the CLI was started from `<dir>` |
|
|
23
26
|
| `--skip-install` | Skip dependency installation, which also skips the auto-run setup + dev + browser, leaving you with manual next-steps |
|
|
@@ -27,6 +30,8 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
27
30
|
|
|
28
31
|
- `create` (default) — scaffold a new Mosaic package
|
|
29
32
|
- `add` — add a webapp or library to the current monorepo
|
|
33
|
+
- `infra register-os-blueprint` — open or update an infra PR for this customer monorepo's OS blueprint
|
|
34
|
+
- `infra register-app <app>` — open or update an infra PR for a webapp database
|
|
30
35
|
- `status` — show template sync status for the current app
|
|
31
36
|
- `sync` — generate downstream sync context (template → app)
|
|
32
37
|
- `upstream` — generate upstream context (app → template)
|
|
@@ -36,11 +41,12 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
36
41
|
|
|
37
42
|
`create` auto-detects whether you're inside an existing pnpm monorepo (by walking up for `pnpm-workspace.yaml`) and changes its prompts accordingly:
|
|
38
43
|
|
|
39
|
-
- **Outside a monorepo** — you're asked
|
|
44
|
+
- **Outside a monorepo** — you're asked for the repo name, whether to initialize with a webapp (Y/n, default Y), and the customer slug stored in `.mosaic-workspace.json`. Picking the webapp option also asks for the webapp name and scaffolds it inside `packages/<webapp-name>/`. Declining gives you an empty monorepo.
|
|
40
45
|
- **Inside a monorepo** — pick `Webapp` (default) or `Library` to add a new package under the workspace pattern.
|
|
41
46
|
|
|
42
|
-
Generated monorepos include a root `.mosaic-workspace.json`
|
|
43
|
-
|
|
47
|
+
Generated monorepos include a root `.mosaic-workspace.json` with the customer
|
|
48
|
+
slug plus template compatibility metadata, and a pinned `pnpm mosaic` script.
|
|
49
|
+
Prefer the workspace-owned command when adding packages:
|
|
44
50
|
|
|
45
51
|
```bash
|
|
46
52
|
pnpm mosaic add webapp my-app
|
|
@@ -56,7 +62,7 @@ newer scaffold.
|
|
|
56
62
|
When you scaffold a webapp (the default flow), `create` automatically runs:
|
|
57
63
|
|
|
58
64
|
1. `pnpm install` (at the monorepo root)
|
|
59
|
-
2. `pnpm run setup` — Docker Compose
|
|
65
|
+
2. `pnpm run setup` — root Docker Compose services + local database creation + Drizzle migrations + seed users
|
|
60
66
|
3. `pnpm dev` — Next.js dev server
|
|
61
67
|
4. Opens the served URL in your default browser
|
|
62
68
|
|
|
@@ -76,7 +82,32 @@ The `webapp` template ships with:
|
|
|
76
82
|
|
|
77
83
|
The `library` template ships with TypeScript, ESLint, and a minimal `src/index.ts`.
|
|
78
84
|
|
|
79
|
-
The `monorepo` template ships with `pnpm-workspace.yaml`, root scripts (`dev`, `build`, `lint`, `test`), strict `tsconfig.json`, flat-config ESLint, and an empty `packages/` directory.
|
|
85
|
+
The `monorepo` template ships with `pnpm-workspace.yaml`, root local Postgres/SpiceDB Docker Compose, root scripts (`setup`, `dev`, `build`, `lint`, `test`), strict `tsconfig.json`, flat-config ESLint, and an empty `packages/` directory.
|
|
86
|
+
|
|
87
|
+
## Customer OS blueprint
|
|
88
|
+
|
|
89
|
+
After creating a customer monorepo, register the customer OS blueprint in
|
|
90
|
+
`Percepta-Core/infra`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm mosaic infra register-os-blueprint
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The command reads `customerSlug` from `.mosaic-workspace.json`, opens or
|
|
97
|
+
updates an infra PR that writes
|
|
98
|
+
`ryvn/definitions/<customer>/blueprints/<customer>-os.blueprint.yaml`, and does
|
|
99
|
+
not create environment installations. It authenticates with `GITHUB_TOKEN`,
|
|
100
|
+
`GH_TOKEN`, or the GitHub CLI's `gh auth token`.
|
|
101
|
+
|
|
102
|
+
After adding a webapp, register its app database in that customer OS blueprint:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pnpm mosaic infra register-app my-app
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This opens or updates an infra PR that adds `my-app: {}` to the customer OS
|
|
109
|
+
blueprint's `app_databases` default. Merge the OS blueprint PR before
|
|
110
|
+
registering apps.
|
|
80
111
|
|
|
81
112
|
## Development
|
|
82
113
|
|
|
@@ -48,4 +48,4 @@ function getFileAtTag(repoPath, tag, filePath) {
|
|
|
48
48
|
//#endregion
|
|
49
49
|
export { getTemplateVersionFromTag as i, getLatestTemplateTag as n, getTemplateDiff as r, getFileAtTag as t };
|
|
50
50
|
|
|
51
|
-
//# sourceMappingURL=git-ops-
|
|
51
|
+
//# sourceMappingURL=git-ops-BD7JNnal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-ops-
|
|
1
|
+
{"version":3,"file":"git-ops-BD7JNnal.js","names":[],"sources":["../src/utils/git-ops.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\n\n// Git requires forward slashes for in-repo paths regardless of OS\nfunction toGitPath(p: string): string {\n return p.replace(/\\\\/g, \"/\");\n}\n\nexport function getLatestTemplateTag(\n type: string,\n repoPath: string,\n): string | null {\n try {\n const tags = execFileSync(\n \"git\",\n [\"tag\", \"-l\", `template/${type}/*`, \"--sort=-v:refname\"],\n { cwd: repoPath, encoding: \"utf-8\" },\n ).trim();\n if (!tags) return null;\n return tags.split(\"\\n\")[0] ?? null;\n } catch {\n return null;\n }\n}\n\nexport function getTemplateVersionFromTag(tag: string): string {\n const parts = tag.split(\"/\");\n return parts[parts.length - 1] ?? \"\";\n}\n\nexport function getTemplateDiff(\n repoPath: string,\n templatePath: string,\n fromTag: string,\n toTag: string,\n): string {\n return execFileSync(\n \"git\",\n [\"diff\", `${fromTag}..${toTag}`, \"--\", toGitPath(templatePath)],\n {\n cwd: repoPath,\n encoding: \"utf-8\",\n },\n );\n}\n\nexport function getFileAtTag(\n repoPath: string,\n tag: string,\n filePath: string,\n): string | null {\n try {\n return execFileSync(\"git\", [\"show\", `${tag}:${toGitPath(filePath)}`], {\n cwd: repoPath,\n encoding: \"utf-8\",\n });\n } catch {\n return null;\n }\n}\n"],"mappings":";;AAGA,SAAS,UAAU,GAAmB;AACpC,QAAO,EAAE,QAAQ,OAAO,IAAI;;AAG9B,SAAgB,qBACd,MACA,UACe;AACf,KAAI;EACF,MAAM,OAAO,aACX,OACA;GAAC;GAAO;GAAM,YAAY,KAAK;GAAK;GAAoB,EACxD;GAAE,KAAK;GAAU,UAAU;GAAS,CACrC,CAAC,MAAM;AACR,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,MAAM,KAAK,CAAC,MAAM;SACxB;AACN,SAAO;;;AAIX,SAAgB,0BAA0B,KAAqB;CAC7D,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAO,MAAM,MAAM,SAAS,MAAM;;AAGpC,SAAgB,gBACd,UACA,cACA,SACA,OACQ;AACR,QAAO,aACL,OACA;EAAC;EAAQ,GAAG,QAAQ,IAAI;EAAS;EAAM,UAAU,aAAa;EAAC,EAC/D;EACE,KAAK;EACL,UAAU;EACX,CACF;;AAGH,SAAgB,aACd,UACA,KACA,UACe;AACf,KAAI;AACF,SAAO,aAAa,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,UAAU,SAAS,GAAG,EAAE;GACpE,KAAK;GACL,UAAU;GACX,CAAC;SACI;AACN,SAAO"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
//#region src/commands/infra/github.ts
|
|
3
|
+
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
4
|
+
const INFRA_REPOSITORY = "Percepta-Core/infra";
|
|
5
|
+
const INFRA_REPOSITORY_OWNER = "Percepta-Core";
|
|
6
|
+
const INFRA_BASE_BRANCH = "main";
|
|
7
|
+
async function createOrUpdateInfraPullRequest(args) {
|
|
8
|
+
const mainSha = await args.github.getBranchSha(INFRA_BASE_BRANCH);
|
|
9
|
+
if (!mainSha) throw new Error(`Could not find ${INFRA_REPOSITORY}@${INFRA_BASE_BRANCH}.`);
|
|
10
|
+
if (!await args.github.getBranchSha(args.branchName)) await args.github.createBranch(args.branchName, mainSha);
|
|
11
|
+
const branchFile = await args.github.getFile(args.targetPath, args.branchName);
|
|
12
|
+
const existingPullRequest = await args.github.findOpenPullRequest(args.branchName);
|
|
13
|
+
if (branchFile?.content !== args.content) await args.github.putFile({
|
|
14
|
+
path: args.targetPath,
|
|
15
|
+
branch: args.branchName,
|
|
16
|
+
content: args.content,
|
|
17
|
+
message: args.message,
|
|
18
|
+
sha: branchFile?.sha ?? args.baseFileSha
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
pullRequestUrl: (existingPullRequest ?? await args.github.createPullRequest({
|
|
22
|
+
branch: args.branchName,
|
|
23
|
+
title: args.title,
|
|
24
|
+
body: args.body
|
|
25
|
+
})).url,
|
|
26
|
+
status: existingPullRequest ? "updated_pr" : "created_pr"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createInfraGitHubApi(token) {
|
|
30
|
+
return {
|
|
31
|
+
async getFile(filePath, ref) {
|
|
32
|
+
const response = await requestGitHub(token, "GET", `/repos/${INFRA_REPOSITORY}/contents/${encodePathSegments(filePath)}?ref=${encodeURIComponent(ref)}`);
|
|
33
|
+
if (response == null) return null;
|
|
34
|
+
if (typeof response.sha !== "string" || typeof response.content !== "string" || response.type !== "file") throw new Error(`Unexpected GitHub content response for ${filePath} in ${INFRA_REPOSITORY}.`);
|
|
35
|
+
return {
|
|
36
|
+
sha: response.sha,
|
|
37
|
+
content: Buffer.from(response.content.replace(/\s/g, ""), "base64").toString("utf-8")
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
async getBranchSha(branch) {
|
|
41
|
+
const response = await requestGitHub(token, "GET", `/repos/${INFRA_REPOSITORY}/git/ref/heads/${branch}`);
|
|
42
|
+
if (response == null) return null;
|
|
43
|
+
const sha = response.object?.sha;
|
|
44
|
+
if (typeof sha !== "string") throw new Error(`Unexpected GitHub ref response for ${branch}.`);
|
|
45
|
+
return sha;
|
|
46
|
+
},
|
|
47
|
+
async createBranch(branch, sha) {
|
|
48
|
+
await requestGitHub(token, "POST", `/repos/${INFRA_REPOSITORY}/git/refs`, {
|
|
49
|
+
ref: `refs/heads/${branch}`,
|
|
50
|
+
sha
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
async putFile({ path: filePath, branch, content, message, sha }) {
|
|
54
|
+
await requestGitHub(token, "PUT", `/repos/${INFRA_REPOSITORY}/contents/${encodePathSegments(filePath)}`, {
|
|
55
|
+
branch,
|
|
56
|
+
content: Buffer.from(content, "utf-8").toString("base64"),
|
|
57
|
+
message,
|
|
58
|
+
sha
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
async findOpenPullRequest(branch) {
|
|
62
|
+
const pullRequest = (await requestGitHub(token, "GET", `/repos/${INFRA_REPOSITORY}/pulls?${new URLSearchParams({
|
|
63
|
+
base: INFRA_BASE_BRANCH,
|
|
64
|
+
head: `${INFRA_REPOSITORY_OWNER}:${branch}`,
|
|
65
|
+
state: "open"
|
|
66
|
+
})}`))?.[0];
|
|
67
|
+
if (!pullRequest) return null;
|
|
68
|
+
if (typeof pullRequest.html_url !== "string" || typeof pullRequest.number !== "number") throw new Error(`Unexpected GitHub pull request response.`);
|
|
69
|
+
return {
|
|
70
|
+
number: pullRequest.number,
|
|
71
|
+
url: pullRequest.html_url
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
async createPullRequest({ branch, title, body }) {
|
|
75
|
+
const response = await requestGitHub(token, "POST", `/repos/${INFRA_REPOSITORY}/pulls`, {
|
|
76
|
+
base: INFRA_BASE_BRANCH,
|
|
77
|
+
body,
|
|
78
|
+
head: branch,
|
|
79
|
+
maintainer_can_modify: true,
|
|
80
|
+
title
|
|
81
|
+
});
|
|
82
|
+
if (!response || typeof response.html_url !== "string" || typeof response.number !== "number") throw new Error(`Unexpected GitHub pull request response.`);
|
|
83
|
+
return {
|
|
84
|
+
number: response.number,
|
|
85
|
+
url: response.html_url
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function resolveGitHubToken() {
|
|
91
|
+
const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
92
|
+
if (envToken) return envToken;
|
|
93
|
+
try {
|
|
94
|
+
const token = execFileSync("gh", ["auth", "token"], {
|
|
95
|
+
encoding: "utf-8",
|
|
96
|
+
stdio: [
|
|
97
|
+
"ignore",
|
|
98
|
+
"pipe",
|
|
99
|
+
"ignore"
|
|
100
|
+
]
|
|
101
|
+
}).trim();
|
|
102
|
+
if (token) return token;
|
|
103
|
+
} catch {}
|
|
104
|
+
throw new Error("GitHub auth is required. Set GITHUB_TOKEN or GH_TOKEN, or run `gh auth login`.");
|
|
105
|
+
}
|
|
106
|
+
async function requestGitHub(token, method, path, body) {
|
|
107
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}${path}`, {
|
|
108
|
+
method,
|
|
109
|
+
headers: {
|
|
110
|
+
Accept: "application/vnd.github+json",
|
|
111
|
+
Authorization: `Bearer ${token}`,
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
114
|
+
},
|
|
115
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
116
|
+
});
|
|
117
|
+
if (response.status === 404) return null;
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const responseText = await response.text();
|
|
120
|
+
throw new Error(`GitHub ${method} ${path} failed with ${response.status}: ${responseText}`);
|
|
121
|
+
}
|
|
122
|
+
if (response.status === 204) return null;
|
|
123
|
+
return await response.json();
|
|
124
|
+
}
|
|
125
|
+
function encodePathSegments(filePath) {
|
|
126
|
+
return filePath.split("/").map(encodeURIComponent).join("/");
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { resolveGitHubToken as a, createOrUpdateInfraPullRequest as i, INFRA_REPOSITORY as n, createInfraGitHubApi as r, INFRA_BASE_BRANCH as t };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=github-RCIMUq70.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-RCIMUq70.js","names":[],"sources":["../src/commands/infra/github.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\n\nconst GITHUB_API_BASE_URL = \"https://api.github.com\";\n\nexport const INFRA_REPOSITORY = \"Percepta-Core/infra\";\nexport const INFRA_REPOSITORY_OWNER = \"Percepta-Core\";\nexport const INFRA_BASE_BRANCH = \"main\";\n\nexport interface GitHubFile {\n sha: string;\n content: string;\n}\n\nexport interface GitHubPullRequest {\n number: number;\n url: string;\n}\n\nexport interface InfraGitHubApi {\n getFile(path: string, ref: string): Promise<GitHubFile | null>;\n getBranchSha(branch: string): Promise<string | null>;\n createBranch(branch: string, sha: string): Promise<void>;\n putFile(args: {\n path: string;\n branch: string;\n content: string;\n message: string;\n sha?: string;\n }): Promise<void>;\n findOpenPullRequest(branch: string): Promise<GitHubPullRequest | null>;\n createPullRequest(args: {\n branch: string;\n title: string;\n body: string;\n }): Promise<GitHubPullRequest>;\n}\n\nexport interface InfraPullRequestResult {\n pullRequestUrl: string;\n status: \"created_pr\" | \"updated_pr\";\n}\n\nexport async function createOrUpdateInfraPullRequest(args: {\n baseFileSha?: string;\n body: string;\n branchName: string;\n content: string;\n github: InfraGitHubApi;\n message: string;\n targetPath: string;\n title: string;\n}): Promise<InfraPullRequestResult> {\n const mainSha = await args.github.getBranchSha(INFRA_BASE_BRANCH);\n if (!mainSha) {\n throw new Error(`Could not find ${INFRA_REPOSITORY}@${INFRA_BASE_BRANCH}.`);\n }\n\n if (!(await args.github.getBranchSha(args.branchName))) {\n await args.github.createBranch(args.branchName, mainSha);\n }\n\n const branchFile = await args.github.getFile(\n args.targetPath,\n args.branchName,\n );\n const existingPullRequest = await args.github.findOpenPullRequest(\n args.branchName,\n );\n if (branchFile?.content !== args.content) {\n await args.github.putFile({\n path: args.targetPath,\n branch: args.branchName,\n content: args.content,\n message: args.message,\n sha: branchFile?.sha ?? args.baseFileSha,\n });\n }\n\n const pullRequest =\n existingPullRequest ??\n (await args.github.createPullRequest({\n branch: args.branchName,\n title: args.title,\n body: args.body,\n }));\n\n return {\n pullRequestUrl: pullRequest.url,\n status: existingPullRequest ? \"updated_pr\" : \"created_pr\",\n };\n}\n\nexport function createInfraGitHubApi(token: string): InfraGitHubApi {\n return {\n async getFile(filePath, ref) {\n const encodedPath = encodePathSegments(filePath);\n const response = await requestGitHub<{\n content?: unknown;\n sha?: unknown;\n type?: unknown;\n }>(\n token,\n \"GET\",\n `/repos/${INFRA_REPOSITORY}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`,\n );\n if (response == null) return null;\n if (\n typeof response.sha !== \"string\" ||\n typeof response.content !== \"string\" ||\n response.type !== \"file\"\n ) {\n throw new Error(\n `Unexpected GitHub content response for ${filePath} in ${INFRA_REPOSITORY}.`,\n );\n }\n\n return {\n sha: response.sha,\n content: Buffer.from(\n response.content.replace(/\\s/g, \"\"),\n \"base64\",\n ).toString(\"utf-8\"),\n };\n },\n\n async getBranchSha(branch) {\n const response = await requestGitHub<{\n object?: { sha?: unknown };\n }>(token, \"GET\", `/repos/${INFRA_REPOSITORY}/git/ref/heads/${branch}`);\n if (response == null) return null;\n const sha = response.object?.sha;\n if (typeof sha !== \"string\") {\n throw new Error(`Unexpected GitHub ref response for ${branch}.`);\n }\n return sha;\n },\n\n async createBranch(branch, sha) {\n await requestGitHub(\n token,\n \"POST\",\n `/repos/${INFRA_REPOSITORY}/git/refs`,\n {\n ref: `refs/heads/${branch}`,\n sha,\n },\n );\n },\n\n async putFile({ path: filePath, branch, content, message, sha }) {\n const encodedPath = encodePathSegments(filePath);\n await requestGitHub(\n token,\n \"PUT\",\n `/repos/${INFRA_REPOSITORY}/contents/${encodedPath}`,\n {\n branch,\n content: Buffer.from(content, \"utf-8\").toString(\"base64\"),\n message,\n sha,\n },\n );\n },\n\n async findOpenPullRequest(branch) {\n const params = new URLSearchParams({\n base: INFRA_BASE_BRANCH,\n head: `${INFRA_REPOSITORY_OWNER}:${branch}`,\n state: \"open\",\n });\n const response = await requestGitHub<\n Array<{ html_url?: unknown; number?: unknown }>\n >(token, \"GET\", `/repos/${INFRA_REPOSITORY}/pulls?${params}`);\n const pullRequest = response?.[0];\n if (!pullRequest) return null;\n if (\n typeof pullRequest.html_url !== \"string\" ||\n typeof pullRequest.number !== \"number\"\n ) {\n throw new Error(`Unexpected GitHub pull request response.`);\n }\n return {\n number: pullRequest.number,\n url: pullRequest.html_url,\n };\n },\n\n async createPullRequest({ branch, title, body }) {\n const response = await requestGitHub<{\n html_url?: unknown;\n number?: unknown;\n }>(token, \"POST\", `/repos/${INFRA_REPOSITORY}/pulls`, {\n base: INFRA_BASE_BRANCH,\n body,\n head: branch,\n maintainer_can_modify: true,\n title,\n });\n if (\n !response ||\n typeof response.html_url !== \"string\" ||\n typeof response.number !== \"number\"\n ) {\n throw new Error(`Unexpected GitHub pull request response.`);\n }\n return {\n number: response.number,\n url: response.html_url,\n };\n },\n };\n}\n\nexport function resolveGitHubToken(): string {\n const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;\n if (envToken) return envToken;\n\n try {\n const token = execFileSync(\"gh\", [\"auth\", \"token\"], {\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n if (token) return token;\n } catch {\n // Fall through to the actionable error below.\n }\n\n throw new Error(\n \"GitHub auth is required. Set GITHUB_TOKEN or GH_TOKEN, or run `gh auth login`.\",\n );\n}\n\nasync function requestGitHub<T>(\n token: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T | null> {\n const response = await fetch(`${GITHUB_API_BASE_URL}${path}`, {\n method,\n headers: {\n Accept: \"application/vnd.github+json\",\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (response.status === 404) return null;\n\n if (!response.ok) {\n const responseText = await response.text();\n throw new Error(\n `GitHub ${method} ${path} failed with ${response.status}: ${responseText}`,\n );\n }\n\n if (response.status === 204) return null;\n return (await response.json()) as T;\n}\n\nfunction encodePathSegments(filePath: string): string {\n return filePath.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { i as toTitleCase, n as toKebabCase, r as toSnakeCase, t as getTemplateVersion } from "./template-versions-CEIP9vhl.js";
|
|
3
|
+
import { a as writeManifest, c as isValidMosaicDesignTheme, o as DEFAULT_MOSAIC_DESIGN_THEME, s as VALID_MOSAIC_DESIGN_THEMES, t as derivePlaceholders } from "./manifest-CqIDnbgs.js";
|
|
4
|
+
import { t as validateProjectName } from "./validate-dssldJAj.js";
|
|
2
5
|
import { program } from "commander";
|
|
3
6
|
import { execSync, spawn } from "node:child_process";
|
|
4
7
|
import path from "node:path";
|
|
@@ -9,21 +12,6 @@ import { fileURLToPath } from "node:url";
|
|
|
9
12
|
import { parse } from "yaml";
|
|
10
13
|
import { randomBytes } from "node:crypto";
|
|
11
14
|
import inquirer from "inquirer";
|
|
12
|
-
import validateNpmPackageName from "validate-npm-package-name";
|
|
13
|
-
//#region src/utils/case-converters.ts
|
|
14
|
-
/** Lowercase, hyphenated, npm-package-name-safe form: "My Cool App" → "my-cool-app". */
|
|
15
|
-
function toKebabCase(str) {
|
|
16
|
-
return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
17
|
-
}
|
|
18
|
-
/** Display form derived from a kebab-case name: "my-cool-app" → "My Cool App". */
|
|
19
|
-
function toTitleCase(str) {
|
|
20
|
-
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
21
|
-
}
|
|
22
|
-
/** Identifier form for env vars and DB names: "my-cool-app" → "my_cool_app". */
|
|
23
|
-
function toSnakeCase(str) {
|
|
24
|
-
return str.replace(/-/g, "_");
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
15
|
//#region src/utils/copy-template.ts
|
|
28
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
17
|
const __dirname = path.dirname(__filename);
|
|
@@ -46,7 +34,9 @@ const TEMPLATE_FILE_MAPPINGS = {
|
|
|
46
34
|
"package.json.template": "package.json",
|
|
47
35
|
"gitignore.template": ".gitignore",
|
|
48
36
|
"env.example.template": ".env.example",
|
|
49
|
-
"npmrc.template": ".npmrc"
|
|
37
|
+
"npmrc.template": ".npmrc",
|
|
38
|
+
"oxlint.config.ts.template": "oxlint.config.ts",
|
|
39
|
+
"oxfmt.config.ts.template": "oxfmt.config.ts"
|
|
50
40
|
};
|
|
51
41
|
function shouldSkip(src) {
|
|
52
42
|
const basename = path.basename(src);
|
|
@@ -77,17 +67,6 @@ async function copyTemplate(targetDir, templateType) {
|
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
69
|
//#endregion
|
|
80
|
-
//#region src/utils/design-theme.ts
|
|
81
|
-
const VALID_MOSAIC_DESIGN_THEMES = [
|
|
82
|
-
"paper",
|
|
83
|
-
"modern",
|
|
84
|
-
"dense"
|
|
85
|
-
];
|
|
86
|
-
const DEFAULT_MOSAIC_DESIGN_THEME = "modern";
|
|
87
|
-
function isValidMosaicDesignTheme(value) {
|
|
88
|
-
return typeof value === "string" && VALID_MOSAIC_DESIGN_THEMES.includes(value);
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
70
|
//#region src/utils/detect-monorepo.ts
|
|
92
71
|
const NOT_FOUND = {
|
|
93
72
|
found: false,
|
|
@@ -130,11 +109,9 @@ async function detectMonorepo(startDir) {
|
|
|
130
109
|
/**
|
|
131
110
|
* Writes .env.local for the webapp template with real generated values for
|
|
132
111
|
* BETTER_AUTH_SECRET and ENCRYPTION_SECRET_KEY so the user can run dev/seed
|
|
133
|
-
* immediately.
|
|
134
|
-
* generated values that can be imported into Ryvn for the deployed installation.
|
|
135
|
-
* The .env.example file remains the documentation source.
|
|
112
|
+
* immediately. The .env.example file remains the documentation source.
|
|
136
113
|
*
|
|
137
|
-
*
|
|
114
|
+
* This is a no-op if .env.local already exists.
|
|
138
115
|
*/
|
|
139
116
|
async function generateEnvLocal(packageDir) {
|
|
140
117
|
const examplePath = path.join(packageDir, ".env.example");
|
|
@@ -146,61 +123,6 @@ async function generateEnvLocal(packageDir) {
|
|
|
146
123
|
const content = (await fs.readFile(examplePath, "utf-8")).replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${authSecret}`).replace(/^ENCRYPTION_SECRET_KEY=.*$/m, `ENCRYPTION_SECRET_KEY=${encKey}`);
|
|
147
124
|
await fs.writeFile(localPath, content);
|
|
148
125
|
}
|
|
149
|
-
const ryvnSecretsPath = path.join(packageDir, "deploy", "ryvn", "percepta-test.secrets.env");
|
|
150
|
-
if (await fs.pathExists(ryvnSecretsPath)) return;
|
|
151
|
-
const deployAuthSecret = randomBytes(32).toString("base64");
|
|
152
|
-
const deployEncKey = randomBytes(16).toString("hex");
|
|
153
|
-
const deploySecrets = [
|
|
154
|
-
`BETTER_AUTH_SECRET=${deployAuthSecret}`,
|
|
155
|
-
`ENCRYPTION_SECRET_KEY=${deployEncKey}`,
|
|
156
|
-
"",
|
|
157
|
-
"# Langfuse and LLM demo credentials are inherited from the demos-commons Ryvn variable group.",
|
|
158
|
-
""
|
|
159
|
-
].join("\n");
|
|
160
|
-
await fs.ensureDir(path.dirname(ryvnSecretsPath));
|
|
161
|
-
await fs.writeFile(ryvnSecretsPath, deploySecrets);
|
|
162
|
-
}
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/utils/manifest.ts
|
|
165
|
-
const MANIFEST_FILENAME = ".mosaic-template.json";
|
|
166
|
-
function getManifestPath(dir) {
|
|
167
|
-
return path.join(dir, MANIFEST_FILENAME);
|
|
168
|
-
}
|
|
169
|
-
async function readManifest(dir) {
|
|
170
|
-
const manifestPath = getManifestPath(dir);
|
|
171
|
-
if (!await fs.pathExists(manifestPath)) throw new Error(`No ${MANIFEST_FILENAME} found in ${dir}. Run 'create init' to create one.`);
|
|
172
|
-
const content = await fs.readFile(manifestPath, "utf-8");
|
|
173
|
-
try {
|
|
174
|
-
return JSON.parse(content);
|
|
175
|
-
} catch (error) {
|
|
176
|
-
throw new Error(`Invalid JSON in ${MANIFEST_FILENAME}: ${error.message}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
async function writeManifest(dir, manifest) {
|
|
180
|
-
const manifestPath = getManifestPath(dir);
|
|
181
|
-
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
182
|
-
}
|
|
183
|
-
async function manifestExists(dir) {
|
|
184
|
-
return fs.pathExists(getManifestPath(dir));
|
|
185
|
-
}
|
|
186
|
-
function derivePlaceholders(appName, appTitle, repoName = appName, designTheme = DEFAULT_MOSAIC_DESIGN_THEME) {
|
|
187
|
-
const nameSnake = appName.replace(/-/g, "_");
|
|
188
|
-
const repoNameSnake = repoName.replace(/-/g, "_");
|
|
189
|
-
return {
|
|
190
|
-
__APP_NAME__: appName,
|
|
191
|
-
__APP_TITLE__: appTitle,
|
|
192
|
-
__DB_NAME__: nameSnake + "_db",
|
|
193
|
-
__APP_NAME_UPPER__: appName.toUpperCase(),
|
|
194
|
-
__APP_NAME_SNAKE__: nameSnake,
|
|
195
|
-
__REPO_NAME__: repoName,
|
|
196
|
-
__REPO_NAME_SNAKE__: repoNameSnake,
|
|
197
|
-
__MOSAIC_DESIGN_THEME__: designTheme
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
function resolveMosaicTemplatePath(options) {
|
|
201
|
-
if (options.mosaicTemplatePath) return path.resolve(options.mosaicTemplatePath);
|
|
202
|
-
if (process.env.MOSAIC_TEMPLATE_PATH) return path.resolve(process.env.MOSAIC_TEMPLATE_PATH);
|
|
203
|
-
throw new Error("Mosaic repo path required. Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH.");
|
|
204
126
|
}
|
|
205
127
|
//#endregion
|
|
206
128
|
//#region src/utils/package-metadata.ts
|
|
@@ -221,16 +143,6 @@ function readCreatePackageMetadata() {
|
|
|
221
143
|
return FALLBACK_METADATA;
|
|
222
144
|
}
|
|
223
145
|
//#endregion
|
|
224
|
-
//#region src/utils/validate.ts
|
|
225
|
-
function validateProjectName(name) {
|
|
226
|
-
const result = validateNpmPackageName(name);
|
|
227
|
-
if (!result.validForNewPackages) return {
|
|
228
|
-
valid: false,
|
|
229
|
-
error: [...result.errors || [], ...result.warnings || []][0] || "Invalid package name"
|
|
230
|
-
};
|
|
231
|
-
return { valid: true };
|
|
232
|
-
}
|
|
233
|
-
//#endregion
|
|
234
146
|
//#region src/utils/prompts.ts
|
|
235
147
|
const VALID_PROJECT_TYPES = [
|
|
236
148
|
"monorepo",
|
|
@@ -253,6 +165,25 @@ async function promptName(message) {
|
|
|
253
165
|
}]);
|
|
254
166
|
return name;
|
|
255
167
|
}
|
|
168
|
+
async function promptCustomerSlug(defaultCustomerSlug) {
|
|
169
|
+
const { customerSlug } = await inquirer.prompt([{
|
|
170
|
+
type: "input",
|
|
171
|
+
name: "customerSlug",
|
|
172
|
+
message: "Customer slug?",
|
|
173
|
+
default: defaultCustomerSlug,
|
|
174
|
+
filter: toKebabCase,
|
|
175
|
+
validate: (input) => {
|
|
176
|
+
const result = validateProjectName(toKebabCase(input));
|
|
177
|
+
return result.valid || result.error || "Invalid customer slug";
|
|
178
|
+
}
|
|
179
|
+
}]);
|
|
180
|
+
return customerSlug;
|
|
181
|
+
}
|
|
182
|
+
async function resolveCustomerSlug(repoName, defaults) {
|
|
183
|
+
if (defaults.customerSlug) return defaults.customerSlug;
|
|
184
|
+
if (defaults.projectType && defaults.name) return repoName;
|
|
185
|
+
return promptCustomerSlug(repoName);
|
|
186
|
+
}
|
|
256
187
|
async function promptDesignTheme() {
|
|
257
188
|
const { designTheme } = await inquirer.prompt([{
|
|
258
189
|
type: "rawlist",
|
|
@@ -323,6 +254,7 @@ async function promptProjectDetails(defaults) {
|
|
|
323
254
|
const repoTitle = toTitleCase(repoName);
|
|
324
255
|
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
325
256
|
await defaults.beforeNamePrompt?.(projectType);
|
|
257
|
+
const customerSlug = await resolveCustomerSlug(repoName, defaults);
|
|
326
258
|
if (projectType === "monorepo") {
|
|
327
259
|
finalName = repoName;
|
|
328
260
|
const finalTitle = repoTitle;
|
|
@@ -333,6 +265,7 @@ async function promptProjectDetails(defaults) {
|
|
|
333
265
|
name: finalName,
|
|
334
266
|
title: finalTitle,
|
|
335
267
|
installDeps: !defaults.skipInstall,
|
|
268
|
+
customerSlug,
|
|
336
269
|
monorepoName: repoName,
|
|
337
270
|
monorepoTitle: repoTitle
|
|
338
271
|
};
|
|
@@ -348,6 +281,7 @@ async function promptProjectDetails(defaults) {
|
|
|
348
281
|
name: finalName,
|
|
349
282
|
title: finalTitle,
|
|
350
283
|
installDeps: !defaults.skipInstall,
|
|
284
|
+
customerSlug,
|
|
351
285
|
designTheme,
|
|
352
286
|
monorepoName: repoName,
|
|
353
287
|
monorepoTitle: repoTitle
|
|
@@ -372,8 +306,8 @@ async function promptProjectDetails(defaults) {
|
|
|
372
306
|
* directory to the monorepo root, where GitHub Actions actually picks them up.
|
|
373
307
|
*
|
|
374
308
|
* Only moves files whose names start with the app name (e.g.
|
|
375
|
-
* `myapp-ryvn-release.yaml`).
|
|
376
|
-
* place —
|
|
309
|
+
* `myapp-ryvn-release.yaml`). Any other (generic) workflow a package happens
|
|
310
|
+
* to ship is left in place — CI lives at the monorepo root, not per-package.
|
|
377
311
|
*
|
|
378
312
|
* Cleans up an empty `.github/workflows` (and empty parent `.github`) after
|
|
379
313
|
* the move.
|
|
@@ -505,30 +439,16 @@ async function replacePlaceholders(targetDir, config) {
|
|
|
505
439
|
return stats;
|
|
506
440
|
}
|
|
507
441
|
//#endregion
|
|
508
|
-
//#region src/utils/template-versions.ts
|
|
509
|
-
const FALLBACK_TEMPLATE_VERSION = "1.0.0";
|
|
510
|
-
function readTemplateVersions() {
|
|
511
|
-
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
512
|
-
const candidates = [path.resolve(currentDir, "../template-versions.json"), path.resolve(currentDir, "../../template-versions.json")];
|
|
513
|
-
for (const versionsPath of candidates) try {
|
|
514
|
-
const content = fs.readFileSync(versionsPath, "utf-8");
|
|
515
|
-
return JSON.parse(content);
|
|
516
|
-
} catch {}
|
|
517
|
-
return {};
|
|
518
|
-
}
|
|
519
|
-
function getTemplateVersion(templateType) {
|
|
520
|
-
return readTemplateVersions()[templateType] ?? FALLBACK_TEMPLATE_VERSION;
|
|
521
|
-
}
|
|
522
|
-
//#endregion
|
|
523
442
|
//#region src/utils/workspace-manifest.ts
|
|
524
443
|
const WORKSPACE_MANIFEST_FILENAME = ".mosaic-workspace.json";
|
|
525
444
|
function getWorkspaceManifestPath(rootDir) {
|
|
526
445
|
return path.join(rootDir, WORKSPACE_MANIFEST_FILENAME);
|
|
527
446
|
}
|
|
528
|
-
function createWorkspaceManifest(createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
447
|
+
function createWorkspaceManifest({ customerSlug, createdAt = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
529
448
|
const createPackage = readCreatePackageMetadata();
|
|
530
449
|
return {
|
|
531
450
|
schemaVersion: 1,
|
|
451
|
+
customerSlug,
|
|
532
452
|
createPackage: createPackage.name,
|
|
533
453
|
createVersion: createPackage.version,
|
|
534
454
|
monorepoTemplateVersion: getTemplateVersion("monorepo"),
|
|
@@ -613,7 +533,7 @@ function printInstallFailureOutput(error) {
|
|
|
613
533
|
console.log(visibleLines.join("\n"));
|
|
614
534
|
}
|
|
615
535
|
/**
|
|
616
|
-
* Runs the monorepo-root `setup` script (
|
|
536
|
+
* Runs the monorepo-root `setup` script (local services + dbs + access + seed).
|
|
617
537
|
* Uses `pnpm run setup` (not `pnpm setup`) because `pnpm setup` is a pnpm builtin that configures
|
|
618
538
|
* PNPM_HOME in the user's shell rc — it ignores the package.json script of the same name.
|
|
619
539
|
*/
|
|
@@ -702,7 +622,7 @@ function openInBrowser(url) {
|
|
|
702
622
|
}
|
|
703
623
|
}
|
|
704
624
|
/**
|
|
705
|
-
* Post-scaffold orchestration for webapps: run root setup (
|
|
625
|
+
* Post-scaffold orchestration for webapps: run root setup (local services + dbs + access + seed),
|
|
706
626
|
* start the dev server, open the served URL in the user's browser, then hand
|
|
707
627
|
* control to the dev server until the user exits with Ctrl+C.
|
|
708
628
|
*
|
|
@@ -716,7 +636,7 @@ function openInBrowser(url) {
|
|
|
716
636
|
async function autoRunWebapp(packageDir, monorepoRoot) {
|
|
717
637
|
const packageManager = PACKAGE_MANAGER;
|
|
718
638
|
console.log();
|
|
719
|
-
console.log(chalk.bold("Running setup (
|
|
639
|
+
console.log(chalk.bold("Running setup (local services, dbs, access, seed)..."));
|
|
720
640
|
console.log();
|
|
721
641
|
try {
|
|
722
642
|
await runWebappSetup(packageManager, monorepoRoot);
|
|
@@ -974,6 +894,14 @@ async function createProject(options) {
|
|
|
974
894
|
process.exit(1);
|
|
975
895
|
}
|
|
976
896
|
}
|
|
897
|
+
const customerSlug = options.customer ? toKebabCase(options.customer) : void 0;
|
|
898
|
+
if (customerSlug) {
|
|
899
|
+
const validation = validateProjectName(customerSlug);
|
|
900
|
+
if (!validation.valid) {
|
|
901
|
+
console.error(chalk.red(`Invalid customer slug: ${validation.error}`));
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
977
905
|
let answers;
|
|
978
906
|
if (options.yes) {
|
|
979
907
|
const projectType = options.type || "webapp";
|
|
@@ -987,6 +915,7 @@ async function createProject(options) {
|
|
|
987
915
|
name: kebabName,
|
|
988
916
|
title: toTitleCase(kebabName),
|
|
989
917
|
installDeps: !options.skipInstall,
|
|
918
|
+
customerSlug: monorepoContext.found ? void 0 : customerSlug ?? kebabRepoName,
|
|
990
919
|
designTheme: projectType === "webapp" ? options.theme ?? "modern" : void 0,
|
|
991
920
|
monorepoName: monorepoContext.found ? void 0 : kebabRepoName,
|
|
992
921
|
monorepoTitle: monorepoContext.found ? void 0 : toTitleCase(kebabRepoName)
|
|
@@ -995,6 +924,7 @@ async function createProject(options) {
|
|
|
995
924
|
answers = await promptProjectDetails({
|
|
996
925
|
projectType: options.type,
|
|
997
926
|
name: projectName ? toKebabCase(projectName) : void 0,
|
|
927
|
+
customerSlug,
|
|
998
928
|
designTheme: options.theme,
|
|
999
929
|
repoName: repoName ? toKebabCase(repoName) : void 0,
|
|
1000
930
|
skipInstall: options.skipInstall,
|
|
@@ -1008,7 +938,9 @@ async function createProject(options) {
|
|
|
1008
938
|
if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) answers.directory = path.join(monorepoContext.packageDir, answers.name);
|
|
1009
939
|
}
|
|
1010
940
|
const monorepoName = answers.monorepoName ?? answers.name;
|
|
1011
|
-
const
|
|
941
|
+
const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
|
|
942
|
+
const newWorkspaceCustomerSlug = answers.customerSlug ?? monorepoName;
|
|
943
|
+
const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle, monorepoName, DEFAULT_MOSAIC_DESIGN_THEME);
|
|
1012
944
|
const configRepoName = monorepoContext.found ? path.basename(monorepoContext.rootDir) : monorepoName;
|
|
1013
945
|
const config = buildAppConfig(answers.name, answers.title, configRepoName, answers.designTheme);
|
|
1014
946
|
const typeLabel = getProjectTypeLabel(answers.projectType);
|
|
@@ -1052,11 +984,13 @@ async function createProject(options) {
|
|
|
1052
984
|
if (isBareMonorepo) {
|
|
1053
985
|
console.log(chalk.dim(" Type:"), typeLabel);
|
|
1054
986
|
console.log(chalk.dim(" Directory:"), monorepoRoot);
|
|
987
|
+
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1055
988
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1056
989
|
console.log(chalk.dim(" Title:"), monorepoConfig.title);
|
|
1057
990
|
} else {
|
|
1058
991
|
console.log(chalk.dim(" Package type:"), typeLabel);
|
|
1059
992
|
console.log(chalk.dim(" Monorepo directory:"), monorepoRoot);
|
|
993
|
+
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1060
994
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1061
995
|
console.log(chalk.dim(" Package:"), `packages/${answers.name}/`);
|
|
1062
996
|
console.log(chalk.dim(" Name:"), config.name);
|
|
@@ -1074,7 +1008,7 @@ async function createProject(options) {
|
|
|
1074
1008
|
}
|
|
1075
1009
|
}
|
|
1076
1010
|
await scaffoldMonorepo(monorepoRoot, monorepoConfig);
|
|
1077
|
-
const newWorkspaceManifest = createWorkspaceManifest();
|
|
1011
|
+
const newWorkspaceManifest = createWorkspaceManifest({ customerSlug: newWorkspaceCustomerSlug });
|
|
1078
1012
|
await writeWorkspaceManifest(monorepoRoot, newWorkspaceManifest);
|
|
1079
1013
|
if (packageDir && answers.projectType !== "monorepo") await addPackageToMonorepo({
|
|
1080
1014
|
packageDir,
|
|
@@ -1248,7 +1182,7 @@ function printNextStepsExisting(answers, packageDir, installNeeded) {
|
|
|
1248
1182
|
//#region src/index.ts
|
|
1249
1183
|
const packageJson = readCreatePackageMetadata();
|
|
1250
1184
|
program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
|
|
1251
|
-
program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Package/app name").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--repo-name <name>", "Repository name when creating a new monorepo").option("--cwd <dir>", "Run create as if started from this directory").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(createProject);
|
|
1185
|
+
program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Package/app name").option("--customer <slug>", "Customer slug for the new monorepo").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--repo-name <name>", "Repository name when creating a new monorepo").option("--cwd <dir>", "Run create as if started from this directory").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(createProject);
|
|
1252
1186
|
program.command("add").description("Add a Mosaic package to the current monorepo").argument("[typeOrName]", "Package type (webapp/library) or package name").argument("[name]", "Package/app name").option("-t, --type <type>", "Package type: webapp or library").option("--name <name>", "Package/app name").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (typeOrName, name, options) => {
|
|
1253
1187
|
let projectType = options.type;
|
|
1254
1188
|
let projectName = options.name;
|
|
@@ -1267,24 +1201,33 @@ program.command("add").description("Add a Mosaic package to the current monorepo
|
|
|
1267
1201
|
addOnly: true
|
|
1268
1202
|
});
|
|
1269
1203
|
});
|
|
1204
|
+
const infra = program.command("infra").description("Manage Mosaic infra glue");
|
|
1205
|
+
infra.command("register-os-blueprint").description("Register this customer monorepo's OS blueprint in infra").action(async () => {
|
|
1206
|
+
const { registerOsBlueprintCommand } = await import("./register-os-blueprint-DGjBUZYa.js");
|
|
1207
|
+
await registerOsBlueprintCommand();
|
|
1208
|
+
});
|
|
1209
|
+
infra.command("register-app").description("Register a webapp database in this customer OS blueprint").argument("<app>", "Webapp package name").action(async (appName) => {
|
|
1210
|
+
const { registerAppCommand } = await import("./register-app-C7ZBpAaZ.js");
|
|
1211
|
+
await registerAppCommand(appName);
|
|
1212
|
+
});
|
|
1270
1213
|
program.command("status").description("Show template sync status for current app").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").action(async (options) => {
|
|
1271
|
-
const { statusCommand } = await import("./status-
|
|
1214
|
+
const { statusCommand } = await import("./status-BXYaQ4a2.js");
|
|
1272
1215
|
await statusCommand(options);
|
|
1273
1216
|
});
|
|
1274
1217
|
program.command("sync").description("Generate downstream sync context (template → app)").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").option("--to <version>", "Target template version (default: latest)").action(async (options) => {
|
|
1275
|
-
const { syncCommand } = await import("./sync-
|
|
1218
|
+
const { syncCommand } = await import("./sync-BayU4w1j.js");
|
|
1276
1219
|
await syncCommand(options);
|
|
1277
1220
|
});
|
|
1278
1221
|
program.command("upstream").description("Generate upstream context (app → template)").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
|
|
1279
|
-
const { upstreamCommand } = await import("./upstream-
|
|
1222
|
+
const { upstreamCommand } = await import("./upstream-CZEzLrS4.js");
|
|
1280
1223
|
await upstreamCommand(options);
|
|
1281
1224
|
});
|
|
1282
1225
|
program.command("init").description("Add .mosaic-template.json to an existing app").option("-t, --type <type>", "Template type (e.g., webapp, library)").option("--template-version <version>", "Template version to set").action(async (options) => {
|
|
1283
|
-
const { initCommand } = await import("./init-
|
|
1226
|
+
const { initCommand } = await import("./init-COp0nGdk.js");
|
|
1284
1227
|
await initCommand(options);
|
|
1285
1228
|
});
|
|
1286
1229
|
program.parse();
|
|
1287
1230
|
//#endregion
|
|
1288
|
-
export {
|
|
1231
|
+
export { detectMonorepo as i, VALID_PROJECT_TYPES as n, isValidProjectType as r, readWorkspaceManifest as t };
|
|
1289
1232
|
|
|
1290
1233
|
//# sourceMappingURL=index.js.map
|