@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
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);
|
|
@@ -77,17 +65,6 @@ async function copyTemplate(targetDir, templateType) {
|
|
|
77
65
|
}
|
|
78
66
|
}
|
|
79
67
|
//#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
68
|
//#region src/utils/detect-monorepo.ts
|
|
92
69
|
const NOT_FOUND = {
|
|
93
70
|
found: false,
|
|
@@ -130,11 +107,9 @@ async function detectMonorepo(startDir) {
|
|
|
130
107
|
/**
|
|
131
108
|
* Writes .env.local for the webapp template with real generated values for
|
|
132
109
|
* 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.
|
|
110
|
+
* immediately. The .env.example file remains the documentation source.
|
|
136
111
|
*
|
|
137
|
-
*
|
|
112
|
+
* This is a no-op if .env.local already exists.
|
|
138
113
|
*/
|
|
139
114
|
async function generateEnvLocal(packageDir) {
|
|
140
115
|
const examplePath = path.join(packageDir, ".env.example");
|
|
@@ -146,61 +121,6 @@ async function generateEnvLocal(packageDir) {
|
|
|
146
121
|
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
122
|
await fs.writeFile(localPath, content);
|
|
148
123
|
}
|
|
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
124
|
}
|
|
205
125
|
//#endregion
|
|
206
126
|
//#region src/utils/package-metadata.ts
|
|
@@ -221,16 +141,6 @@ function readCreatePackageMetadata() {
|
|
|
221
141
|
return FALLBACK_METADATA;
|
|
222
142
|
}
|
|
223
143
|
//#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
144
|
//#region src/utils/prompts.ts
|
|
235
145
|
const VALID_PROJECT_TYPES = [
|
|
236
146
|
"monorepo",
|
|
@@ -253,6 +163,25 @@ async function promptName(message) {
|
|
|
253
163
|
}]);
|
|
254
164
|
return name;
|
|
255
165
|
}
|
|
166
|
+
async function promptCustomerSlug(defaultCustomerSlug) {
|
|
167
|
+
const { customerSlug } = await inquirer.prompt([{
|
|
168
|
+
type: "input",
|
|
169
|
+
name: "customerSlug",
|
|
170
|
+
message: "Customer slug?",
|
|
171
|
+
default: defaultCustomerSlug,
|
|
172
|
+
filter: toKebabCase,
|
|
173
|
+
validate: (input) => {
|
|
174
|
+
const result = validateProjectName(toKebabCase(input));
|
|
175
|
+
return result.valid || result.error || "Invalid customer slug";
|
|
176
|
+
}
|
|
177
|
+
}]);
|
|
178
|
+
return customerSlug;
|
|
179
|
+
}
|
|
180
|
+
async function resolveCustomerSlug(repoName, defaults) {
|
|
181
|
+
if (defaults.customerSlug) return defaults.customerSlug;
|
|
182
|
+
if (defaults.projectType && defaults.name) return repoName;
|
|
183
|
+
return promptCustomerSlug(repoName);
|
|
184
|
+
}
|
|
256
185
|
async function promptDesignTheme() {
|
|
257
186
|
const { designTheme } = await inquirer.prompt([{
|
|
258
187
|
type: "rawlist",
|
|
@@ -323,6 +252,7 @@ async function promptProjectDetails(defaults) {
|
|
|
323
252
|
const repoTitle = toTitleCase(repoName);
|
|
324
253
|
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
325
254
|
await defaults.beforeNamePrompt?.(projectType);
|
|
255
|
+
const customerSlug = await resolveCustomerSlug(repoName, defaults);
|
|
326
256
|
if (projectType === "monorepo") {
|
|
327
257
|
finalName = repoName;
|
|
328
258
|
const finalTitle = repoTitle;
|
|
@@ -333,6 +263,7 @@ async function promptProjectDetails(defaults) {
|
|
|
333
263
|
name: finalName,
|
|
334
264
|
title: finalTitle,
|
|
335
265
|
installDeps: !defaults.skipInstall,
|
|
266
|
+
customerSlug,
|
|
336
267
|
monorepoName: repoName,
|
|
337
268
|
monorepoTitle: repoTitle
|
|
338
269
|
};
|
|
@@ -348,6 +279,7 @@ async function promptProjectDetails(defaults) {
|
|
|
348
279
|
name: finalName,
|
|
349
280
|
title: finalTitle,
|
|
350
281
|
installDeps: !defaults.skipInstall,
|
|
282
|
+
customerSlug,
|
|
351
283
|
designTheme,
|
|
352
284
|
monorepoName: repoName,
|
|
353
285
|
monorepoTitle: repoTitle
|
|
@@ -505,30 +437,16 @@ async function replacePlaceholders(targetDir, config) {
|
|
|
505
437
|
return stats;
|
|
506
438
|
}
|
|
507
439
|
//#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
440
|
//#region src/utils/workspace-manifest.ts
|
|
524
441
|
const WORKSPACE_MANIFEST_FILENAME = ".mosaic-workspace.json";
|
|
525
442
|
function getWorkspaceManifestPath(rootDir) {
|
|
526
443
|
return path.join(rootDir, WORKSPACE_MANIFEST_FILENAME);
|
|
527
444
|
}
|
|
528
|
-
function createWorkspaceManifest(createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
445
|
+
function createWorkspaceManifest({ customerSlug, createdAt = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
529
446
|
const createPackage = readCreatePackageMetadata();
|
|
530
447
|
return {
|
|
531
448
|
schemaVersion: 1,
|
|
449
|
+
customerSlug,
|
|
532
450
|
createPackage: createPackage.name,
|
|
533
451
|
createVersion: createPackage.version,
|
|
534
452
|
monorepoTemplateVersion: getTemplateVersion("monorepo"),
|
|
@@ -613,7 +531,7 @@ function printInstallFailureOutput(error) {
|
|
|
613
531
|
console.log(visibleLines.join("\n"));
|
|
614
532
|
}
|
|
615
533
|
/**
|
|
616
|
-
* Runs the monorepo-root `setup` script (
|
|
534
|
+
* Runs the monorepo-root `setup` script (local services + dbs + access + seed).
|
|
617
535
|
* Uses `pnpm run setup` (not `pnpm setup`) because `pnpm setup` is a pnpm builtin that configures
|
|
618
536
|
* PNPM_HOME in the user's shell rc — it ignores the package.json script of the same name.
|
|
619
537
|
*/
|
|
@@ -702,7 +620,7 @@ function openInBrowser(url) {
|
|
|
702
620
|
}
|
|
703
621
|
}
|
|
704
622
|
/**
|
|
705
|
-
* Post-scaffold orchestration for webapps: run root setup (
|
|
623
|
+
* Post-scaffold orchestration for webapps: run root setup (local services + dbs + access + seed),
|
|
706
624
|
* start the dev server, open the served URL in the user's browser, then hand
|
|
707
625
|
* control to the dev server until the user exits with Ctrl+C.
|
|
708
626
|
*
|
|
@@ -716,7 +634,7 @@ function openInBrowser(url) {
|
|
|
716
634
|
async function autoRunWebapp(packageDir, monorepoRoot) {
|
|
717
635
|
const packageManager = PACKAGE_MANAGER;
|
|
718
636
|
console.log();
|
|
719
|
-
console.log(chalk.bold("Running setup (
|
|
637
|
+
console.log(chalk.bold("Running setup (local services, dbs, access, seed)..."));
|
|
720
638
|
console.log();
|
|
721
639
|
try {
|
|
722
640
|
await runWebappSetup(packageManager, monorepoRoot);
|
|
@@ -974,6 +892,14 @@ async function createProject(options) {
|
|
|
974
892
|
process.exit(1);
|
|
975
893
|
}
|
|
976
894
|
}
|
|
895
|
+
const customerSlug = options.customer ? toKebabCase(options.customer) : void 0;
|
|
896
|
+
if (customerSlug) {
|
|
897
|
+
const validation = validateProjectName(customerSlug);
|
|
898
|
+
if (!validation.valid) {
|
|
899
|
+
console.error(chalk.red(`Invalid customer slug: ${validation.error}`));
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
977
903
|
let answers;
|
|
978
904
|
if (options.yes) {
|
|
979
905
|
const projectType = options.type || "webapp";
|
|
@@ -987,6 +913,7 @@ async function createProject(options) {
|
|
|
987
913
|
name: kebabName,
|
|
988
914
|
title: toTitleCase(kebabName),
|
|
989
915
|
installDeps: !options.skipInstall,
|
|
916
|
+
customerSlug: monorepoContext.found ? void 0 : customerSlug ?? kebabRepoName,
|
|
990
917
|
designTheme: projectType === "webapp" ? options.theme ?? "modern" : void 0,
|
|
991
918
|
monorepoName: monorepoContext.found ? void 0 : kebabRepoName,
|
|
992
919
|
monorepoTitle: monorepoContext.found ? void 0 : toTitleCase(kebabRepoName)
|
|
@@ -995,6 +922,7 @@ async function createProject(options) {
|
|
|
995
922
|
answers = await promptProjectDetails({
|
|
996
923
|
projectType: options.type,
|
|
997
924
|
name: projectName ? toKebabCase(projectName) : void 0,
|
|
925
|
+
customerSlug,
|
|
998
926
|
designTheme: options.theme,
|
|
999
927
|
repoName: repoName ? toKebabCase(repoName) : void 0,
|
|
1000
928
|
skipInstall: options.skipInstall,
|
|
@@ -1008,7 +936,9 @@ async function createProject(options) {
|
|
|
1008
936
|
if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) answers.directory = path.join(monorepoContext.packageDir, answers.name);
|
|
1009
937
|
}
|
|
1010
938
|
const monorepoName = answers.monorepoName ?? answers.name;
|
|
1011
|
-
const
|
|
939
|
+
const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
|
|
940
|
+
const newWorkspaceCustomerSlug = answers.customerSlug ?? monorepoName;
|
|
941
|
+
const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle, monorepoName, DEFAULT_MOSAIC_DESIGN_THEME);
|
|
1012
942
|
const configRepoName = monorepoContext.found ? path.basename(monorepoContext.rootDir) : monorepoName;
|
|
1013
943
|
const config = buildAppConfig(answers.name, answers.title, configRepoName, answers.designTheme);
|
|
1014
944
|
const typeLabel = getProjectTypeLabel(answers.projectType);
|
|
@@ -1052,11 +982,13 @@ async function createProject(options) {
|
|
|
1052
982
|
if (isBareMonorepo) {
|
|
1053
983
|
console.log(chalk.dim(" Type:"), typeLabel);
|
|
1054
984
|
console.log(chalk.dim(" Directory:"), monorepoRoot);
|
|
985
|
+
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1055
986
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1056
987
|
console.log(chalk.dim(" Title:"), monorepoConfig.title);
|
|
1057
988
|
} else {
|
|
1058
989
|
console.log(chalk.dim(" Package type:"), typeLabel);
|
|
1059
990
|
console.log(chalk.dim(" Monorepo directory:"), monorepoRoot);
|
|
991
|
+
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1060
992
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1061
993
|
console.log(chalk.dim(" Package:"), `packages/${answers.name}/`);
|
|
1062
994
|
console.log(chalk.dim(" Name:"), config.name);
|
|
@@ -1074,7 +1006,7 @@ async function createProject(options) {
|
|
|
1074
1006
|
}
|
|
1075
1007
|
}
|
|
1076
1008
|
await scaffoldMonorepo(monorepoRoot, monorepoConfig);
|
|
1077
|
-
const newWorkspaceManifest = createWorkspaceManifest();
|
|
1009
|
+
const newWorkspaceManifest = createWorkspaceManifest({ customerSlug: newWorkspaceCustomerSlug });
|
|
1078
1010
|
await writeWorkspaceManifest(monorepoRoot, newWorkspaceManifest);
|
|
1079
1011
|
if (packageDir && answers.projectType !== "monorepo") await addPackageToMonorepo({
|
|
1080
1012
|
packageDir,
|
|
@@ -1248,7 +1180,7 @@ function printNextStepsExisting(answers, packageDir, installNeeded) {
|
|
|
1248
1180
|
//#region src/index.ts
|
|
1249
1181
|
const packageJson = readCreatePackageMetadata();
|
|
1250
1182
|
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);
|
|
1183
|
+
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
1184
|
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
1185
|
let projectType = options.type;
|
|
1254
1186
|
let projectName = options.name;
|
|
@@ -1267,24 +1199,33 @@ program.command("add").description("Add a Mosaic package to the current monorepo
|
|
|
1267
1199
|
addOnly: true
|
|
1268
1200
|
});
|
|
1269
1201
|
});
|
|
1202
|
+
const infra = program.command("infra").description("Manage Mosaic infra glue");
|
|
1203
|
+
infra.command("register-os-blueprint").description("Register this customer monorepo's OS blueprint in infra").action(async () => {
|
|
1204
|
+
const { registerOsBlueprintCommand } = await import("./register-os-blueprint-DGjBUZYa.js");
|
|
1205
|
+
await registerOsBlueprintCommand();
|
|
1206
|
+
});
|
|
1207
|
+
infra.command("register-app").description("Register a webapp database in this customer OS blueprint").argument("<app>", "Webapp package name").action(async (appName) => {
|
|
1208
|
+
const { registerAppCommand } = await import("./register-app-C7ZBpAaZ.js");
|
|
1209
|
+
await registerAppCommand(appName);
|
|
1210
|
+
});
|
|
1270
1211
|
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-
|
|
1212
|
+
const { statusCommand } = await import("./status-BXYaQ4a2.js");
|
|
1272
1213
|
await statusCommand(options);
|
|
1273
1214
|
});
|
|
1274
1215
|
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-
|
|
1216
|
+
const { syncCommand } = await import("./sync-BayU4w1j.js");
|
|
1276
1217
|
await syncCommand(options);
|
|
1277
1218
|
});
|
|
1278
1219
|
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-
|
|
1220
|
+
const { upstreamCommand } = await import("./upstream-CZEzLrS4.js");
|
|
1280
1221
|
await upstreamCommand(options);
|
|
1281
1222
|
});
|
|
1282
1223
|
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-
|
|
1224
|
+
const { initCommand } = await import("./init-COp0nGdk.js");
|
|
1284
1225
|
await initCommand(options);
|
|
1285
1226
|
});
|
|
1286
1227
|
program.parse();
|
|
1287
1228
|
//#endregion
|
|
1288
|
-
export {
|
|
1229
|
+
export { detectMonorepo as i, VALID_PROJECT_TYPES as n, isValidProjectType as r, readWorkspaceManifest as t };
|
|
1289
1230
|
|
|
1290
1231
|
//# sourceMappingURL=index.js.map
|