@percepta/create 3.1.0 → 3.1.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 +15 -10
- package/dist/{chunk-7NPWSTCY.js → chunk-CO3YWUD6.js} +31 -2
- package/dist/{chunk-WMJT7CB5.js → chunk-V5EJIUBJ.js} +5 -2
- package/dist/index.js +93 -73
- package/dist/{init-NP6GRXLL.js → init-EQZ2TCSJ.js} +2 -2
- package/dist/{status-BTHGN6QH.js → status-QW5TQDYY.js} +1 -1
- package/dist/{sync-3Q27L7XZ.js → sync-RLBZDOFB.js} +1 -1
- package/dist/{upstream-C5KFAHVR.js → upstream-TQFVPMEG.js} +1 -1
- package/package.json +3 -2
- package/templates/monorepo/.dockerignore +18 -0
- package/templates/monorepo/gitignore.template +1 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +6 -2
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +98 -0
- package/templates/webapp/AGENTS.md +17 -5
- package/templates/webapp/Dockerfile +16 -7
- package/templates/webapp/README.md +64 -2
- package/templates/webapp/agent-skills/deploy.md +50 -51
- package/templates/webapp/agent-skills/inngest.md +4 -4
- package/templates/webapp/agent-skills/langfuse.md +15 -14
- package/templates/webapp/agent-skills/llm.md +59 -0
- package/templates/webapp/agent-skills/oneshot.md +14 -1
- package/templates/webapp/agent-skills/ryvn.md +1 -1
- package/templates/webapp/deploy/README.md +41 -16
- package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +10 -0
- package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +2 -2
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +11 -0
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +60 -11
- package/templates/webapp/env.example.template +20 -2
- package/templates/webapp/eslint.config.mjs +7 -0
- package/templates/webapp/gitignore.template +1 -0
- package/templates/webapp/next.config.ts +9 -0
- package/templates/webapp/package.json.template +6 -2
- package/templates/webapp/scripts/deploy-percepta-test.ts +837 -0
- package/templates/webapp/scripts/migrate.ts +3 -0
- package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +152 -32
- package/templates/webapp/scripts/seed.ts +1 -1
- package/templates/webapp/scripts/setup-database.ts +2 -1
- package/templates/webapp/scripts/start.sh +3 -2
- package/templates/webapp/scripts/with-local-env.ts +75 -0
- package/templates/webapp/src/app/(app)/layout.tsx +1 -5
- package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +11 -1
- package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +113 -0
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +30 -0
- package/templates/webapp/src/app/global-error.tsx +1 -1
- package/templates/webapp/src/components/FaroProvider.tsx +2 -4
- package/templates/webapp/src/components/form/FormItem.tsx +2 -2
- package/templates/webapp/src/config/getEnvConfig.ts +14 -0
- package/templates/webapp/src/drizzle/db.ts +2 -1
- package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +3 -3
- package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +7 -19
- package/templates/webapp/src/drizzle/ssl.ts +5 -0
- package/templates/webapp/src/instrumentation.ts +102 -10
- package/templates/webapp/src/lib/auth/index.ts +1 -1
- package/templates/webapp/src/lib/auth-client.ts +1 -1
- package/templates/webapp/src/services/llm/LLMService.ts +88 -0
- package/templates/webapp/src/services/llm/LlmProviderService.ts +85 -0
- package/templates/webapp/src/services/observability/initFaro.ts +1 -1
- package/templates/webapp/terraform/schema/main.tf +4 -0
- package/templates/webapp/terraform/schema/outputs.tf +9 -0
- package/templates/webapp/terraform/schema/variables.tf +19 -0
- package/templates/webapp/terraform/schema/versions.tf +38 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +0 -28
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ 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 and
|
|
11
|
+
That's it. The CLI prompts you for the package type, repo name, and package name as needed. Defaults yield a running app — sign in as `admin@example.com` / `password`.
|
|
12
12
|
|
|
13
13
|
## Options (mostly for automation)
|
|
14
14
|
|
|
@@ -17,7 +17,9 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
17
17
|
| Option | Description |
|
|
18
18
|
|--------|-------------|
|
|
19
19
|
| `-t, --type <type>` | Package type: `monorepo`, `webapp`, or `library` (skips the type prompt) |
|
|
20
|
-
| `--name <name>` |
|
|
20
|
+
| `--name <name>` | Package/app name (skips the package name prompt) |
|
|
21
|
+
| `--repo-name <name>` | Repo name when creating a new monorepo (skips the repo name prompt) |
|
|
22
|
+
| `--cwd <dir>` | Run as if the CLI was started from `<dir>` |
|
|
21
23
|
| `--skip-install` | Skip dependency installation, which also skips the auto-run setup + dev + browser, leaving you with manual next-steps |
|
|
22
24
|
| `-y, --yes` | Skip all prompts; requires `--name` |
|
|
23
25
|
|
|
@@ -33,7 +35,7 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
33
35
|
|
|
34
36
|
`create` auto-detects whether you're inside an existing pnpm monorepo (by walking up for `pnpm-workspace.yaml`) and changes its prompts accordingly:
|
|
35
37
|
|
|
36
|
-
- **Outside a monorepo** — you're asked "Initialize with a webapp?" (Y/n, default Y)
|
|
38
|
+
- **Outside a monorepo** — you're asked "Initialize with a webapp?" (Y/n, default Y), then for the repo name. Picking the webapp option also asks for the webapp name and scaffolds it inside `packages/<webapp-name>/`. Declining gives you an empty monorepo.
|
|
37
39
|
- **Inside a monorepo** — pick `Webapp` (default) or `Library` to add a new package under the workspace pattern.
|
|
38
40
|
|
|
39
41
|
## Happy-path: zero-friction webapp
|
|
@@ -41,10 +43,9 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
41
43
|
When you scaffold a webapp (the default flow), `create` automatically runs:
|
|
42
44
|
|
|
43
45
|
1. `pnpm install` (at the monorepo root)
|
|
44
|
-
2. `pnpm
|
|
45
|
-
3. `pnpm
|
|
46
|
-
4.
|
|
47
|
-
5. Opens the served URL in your default browser
|
|
46
|
+
2. `pnpm run setup` — Docker Compose Postgres + Drizzle migrations + seed user
|
|
47
|
+
3. `pnpm dev` — Next.js dev server
|
|
48
|
+
4. Opens the served URL in your default browser
|
|
48
49
|
|
|
49
50
|
Sign in as `admin@example.com` / `password` to start building.
|
|
50
51
|
|
|
@@ -75,9 +76,13 @@ pnpm build
|
|
|
75
76
|
### Testing locally
|
|
76
77
|
|
|
77
78
|
```bash
|
|
78
|
-
pnpm
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
pnpm create:local --cwd /tmp --name test-app --yes --skip-install
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
From the repo root, the same script can be run with a filter:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm --filter @percepta/create create:local --cwd /tmp --name test-app --yes --skip-install
|
|
81
86
|
```
|
|
82
87
|
|
|
83
88
|
### Syncing template files
|
|
@@ -77,6 +77,7 @@ async function promptInsideMonorepoType() {
|
|
|
77
77
|
}
|
|
78
78
|
async function promptProjectDetails(defaults) {
|
|
79
79
|
const inMonorepo = defaults.monorepoContext?.found ?? false;
|
|
80
|
+
const cwd = defaults.cwd ?? process.cwd();
|
|
80
81
|
let projectType;
|
|
81
82
|
let finalName;
|
|
82
83
|
if (inMonorepo) {
|
|
@@ -84,12 +85,40 @@ async function promptProjectDetails(defaults) {
|
|
|
84
85
|
await defaults.beforeNamePrompt?.(projectType);
|
|
85
86
|
finalName = defaults.name || await promptName("Package name?");
|
|
86
87
|
} else {
|
|
88
|
+
const repoName = defaults.repoName || (defaults.projectType === "monorepo" ? defaults.name : void 0) || await promptName("Repo name?");
|
|
89
|
+
const repoTitle = toTitleCase(repoName);
|
|
87
90
|
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
88
91
|
await defaults.beforeNamePrompt?.(projectType);
|
|
89
|
-
|
|
92
|
+
if (projectType === "monorepo") {
|
|
93
|
+
finalName = repoName;
|
|
94
|
+
const finalTitle3 = repoTitle;
|
|
95
|
+
const finalDirectory3 = path.resolve(cwd, repoName);
|
|
96
|
+
return {
|
|
97
|
+
projectType,
|
|
98
|
+
directory: finalDirectory3,
|
|
99
|
+
name: finalName,
|
|
100
|
+
title: finalTitle3,
|
|
101
|
+
installDeps: !defaults.skipInstall,
|
|
102
|
+
monorepoName: repoName,
|
|
103
|
+
monorepoTitle: repoTitle
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const packageNamePrompt = projectType === "webapp" ? "Webapp name?" : "Library name?";
|
|
107
|
+
finalName = defaults.name || await promptName(packageNamePrompt);
|
|
108
|
+
const finalTitle2 = toTitleCase(finalName);
|
|
109
|
+
const finalDirectory2 = path.resolve(cwd, repoName);
|
|
110
|
+
return {
|
|
111
|
+
projectType,
|
|
112
|
+
directory: finalDirectory2,
|
|
113
|
+
name: finalName,
|
|
114
|
+
title: finalTitle2,
|
|
115
|
+
installDeps: !defaults.skipInstall,
|
|
116
|
+
monorepoName: repoName,
|
|
117
|
+
monorepoTitle: repoTitle
|
|
118
|
+
};
|
|
90
119
|
}
|
|
91
120
|
const finalTitle = finalName ? toTitleCase(finalName) : "";
|
|
92
|
-
const finalDirectory = !inMonorepo && finalName ? path.resolve(
|
|
121
|
+
const finalDirectory = !inMonorepo && finalName ? path.resolve(cwd, finalName) : "";
|
|
93
122
|
return {
|
|
94
123
|
projectType,
|
|
95
124
|
directory: finalDirectory,
|
|
@@ -28,14 +28,17 @@ async function writeManifest(dir, manifest) {
|
|
|
28
28
|
async function manifestExists(dir) {
|
|
29
29
|
return fs.pathExists(getManifestPath(dir));
|
|
30
30
|
}
|
|
31
|
-
function derivePlaceholders(appName, appTitle) {
|
|
31
|
+
function derivePlaceholders(appName, appTitle, repoName = appName) {
|
|
32
32
|
const nameSnake = appName.replace(/-/g, "_");
|
|
33
|
+
const repoNameSnake = repoName.replace(/-/g, "_");
|
|
33
34
|
return {
|
|
34
35
|
__APP_NAME__: appName,
|
|
35
36
|
__APP_TITLE__: appTitle,
|
|
36
37
|
__DB_NAME__: nameSnake + "_db",
|
|
37
38
|
__APP_NAME_UPPER__: appName.toUpperCase(),
|
|
38
|
-
__APP_NAME_SNAKE__: nameSnake
|
|
39
|
+
__APP_NAME_SNAKE__: nameSnake,
|
|
40
|
+
__REPO_NAME__: repoName,
|
|
41
|
+
__REPO_NAME_SNAKE__: repoNameSnake
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
function resolveMosaicTemplatePath(options) {
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
toSnakeCase,
|
|
8
8
|
toTitleCase,
|
|
9
9
|
validateProjectName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CO3YWUD6.js";
|
|
11
11
|
import {
|
|
12
12
|
derivePlaceholders,
|
|
13
13
|
writeManifest
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-V5EJIUBJ.js";
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
import { program } from "commander";
|
|
@@ -98,7 +98,9 @@ var PLACEHOLDERS = {
|
|
|
98
98
|
__APP_TITLE__: "title",
|
|
99
99
|
__DB_NAME__: "dbName",
|
|
100
100
|
__APP_NAME_UPPER__: "nameUpper",
|
|
101
|
-
__APP_NAME_SNAKE__: "nameSnake"
|
|
101
|
+
__APP_NAME_SNAKE__: "nameSnake",
|
|
102
|
+
__REPO_NAME__: "repoName",
|
|
103
|
+
__REPO_NAME_SNAKE__: "repoNameSnake"
|
|
102
104
|
};
|
|
103
105
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
104
106
|
"node_modules",
|
|
@@ -262,11 +264,30 @@ async function generateEnvLocal(packageDir) {
|
|
|
262
264
|
const examplePath = path4.join(packageDir, ".env.example");
|
|
263
265
|
const localPath = path4.join(packageDir, ".env.local");
|
|
264
266
|
if (!await fs4.pathExists(examplePath)) return;
|
|
265
|
-
if (await fs4.pathExists(localPath))
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
if (!await fs4.pathExists(localPath)) {
|
|
268
|
+
const authSecret = randomBytes(32).toString("base64");
|
|
269
|
+
const encKey = randomBytes(16).toString("hex");
|
|
270
|
+
const content = (await fs4.readFile(examplePath, "utf-8")).replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${authSecret}`).replace(/^ENCRYPTION_SECRET_KEY=.*$/m, `ENCRYPTION_SECRET_KEY=${encKey}`);
|
|
271
|
+
await fs4.writeFile(localPath, content);
|
|
272
|
+
}
|
|
273
|
+
const ryvnSecretsPath = path4.join(
|
|
274
|
+
packageDir,
|
|
275
|
+
"deploy",
|
|
276
|
+
"ryvn",
|
|
277
|
+
"percepta-test.secrets.env"
|
|
278
|
+
);
|
|
279
|
+
if (await fs4.pathExists(ryvnSecretsPath)) return;
|
|
280
|
+
const deployAuthSecret = randomBytes(32).toString("base64");
|
|
281
|
+
const deployEncKey = randomBytes(16).toString("hex");
|
|
282
|
+
const deploySecrets = [
|
|
283
|
+
`BETTER_AUTH_SECRET=${deployAuthSecret}`,
|
|
284
|
+
`ENCRYPTION_SECRET_KEY=${deployEncKey}`,
|
|
285
|
+
"",
|
|
286
|
+
"# Langfuse and LLM demo credentials are inherited from the demos-commons Ryvn variable group.",
|
|
287
|
+
""
|
|
288
|
+
].join("\n");
|
|
289
|
+
await fs4.ensureDir(path4.dirname(ryvnSecretsPath));
|
|
290
|
+
await fs4.writeFile(ryvnSecretsPath, deploySecrets);
|
|
270
291
|
}
|
|
271
292
|
|
|
272
293
|
// src/utils/relocate-workflows.ts
|
|
@@ -509,7 +530,7 @@ async function writeMosaicFiles(packageDir, config, projectType) {
|
|
|
509
530
|
templateVersion: templateVersions[projectType] || "1.0.0",
|
|
510
531
|
templateCommit: "npm",
|
|
511
532
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
512
|
-
placeholders: derivePlaceholders(config.name, config.title),
|
|
533
|
+
placeholders: derivePlaceholders(config.name, config.title, config.repoName),
|
|
513
534
|
source: {
|
|
514
535
|
templatePath: `packages/create-mosaic-module/templates/${projectType}`
|
|
515
536
|
}
|
|
@@ -529,6 +550,17 @@ _None yet \u2014 freshly created from template._
|
|
|
529
550
|
`
|
|
530
551
|
);
|
|
531
552
|
}
|
|
553
|
+
function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
|
|
554
|
+
return {
|
|
555
|
+
name,
|
|
556
|
+
title,
|
|
557
|
+
dbName: `${toSnakeCase(name)}_db`,
|
|
558
|
+
nameUpper: name.toUpperCase(),
|
|
559
|
+
nameSnake: toSnakeCase(name),
|
|
560
|
+
repoName,
|
|
561
|
+
repoNameSnake: toSnakeCase(repoName)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
532
564
|
async function scaffoldMonorepo(targetDir, config) {
|
|
533
565
|
const monoSpinner = ora("Copying monorepo template...").start();
|
|
534
566
|
try {
|
|
@@ -631,27 +663,6 @@ async function installAtMonorepoRoot(monorepoRoot, installDeps) {
|
|
|
631
663
|
return false;
|
|
632
664
|
}
|
|
633
665
|
}
|
|
634
|
-
async function installAtWebappPackage(packageDir, projectType, installDeps) {
|
|
635
|
-
if (!installDeps || !packageDir || projectType !== "webapp") {
|
|
636
|
-
return projectType !== "webapp";
|
|
637
|
-
}
|
|
638
|
-
const spinner = ora(
|
|
639
|
-
`Generating package lockfile with ${PACKAGE_MANAGER}...`
|
|
640
|
-
).start();
|
|
641
|
-
try {
|
|
642
|
-
await runPackageManagerInstall(PACKAGE_MANAGER, packageDir, [
|
|
643
|
-
"install",
|
|
644
|
-
"--ignore-workspace"
|
|
645
|
-
]);
|
|
646
|
-
spinner.succeed("Generated package lockfile");
|
|
647
|
-
return true;
|
|
648
|
-
} catch {
|
|
649
|
-
spinner.warn(
|
|
650
|
-
`Failed to generate package lockfile. Run '${PACKAGE_MANAGER} install --ignore-workspace' from ${packageDir}.`
|
|
651
|
-
);
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
666
|
async function maybeAutoRunWebapp(packageDir, projectType, installSucceeded) {
|
|
656
667
|
if (!packageDir || projectType !== "webapp" || !installSucceeded) return false;
|
|
657
668
|
return autoRunWebapp(packageDir);
|
|
@@ -700,6 +711,7 @@ function requireNpmTokenForWebappInstall(projectType, installDeps) {
|
|
|
700
711
|
process.exit(1);
|
|
701
712
|
}
|
|
702
713
|
async function createProject(options) {
|
|
714
|
+
const cwd = await resolveCreateCwd(options.cwd);
|
|
703
715
|
if (options.type !== void 0 && !isValidProjectType(options.type)) {
|
|
704
716
|
console.error(
|
|
705
717
|
chalk.red(
|
|
@@ -708,7 +720,6 @@ async function createProject(options) {
|
|
|
708
720
|
);
|
|
709
721
|
process.exit(1);
|
|
710
722
|
}
|
|
711
|
-
const cwd = process.cwd();
|
|
712
723
|
console.log();
|
|
713
724
|
console.log(chalk.bold("Creating a new Mosaic package..."));
|
|
714
725
|
console.log();
|
|
@@ -735,6 +746,7 @@ async function createProject(options) {
|
|
|
735
746
|
}
|
|
736
747
|
console.log();
|
|
737
748
|
const projectName = options.name;
|
|
749
|
+
const repoName = options.repoName;
|
|
738
750
|
if (options.yes && !projectName) {
|
|
739
751
|
console.error(
|
|
740
752
|
chalk.red("Error: --name is required when using --yes flag")
|
|
@@ -748,38 +760,48 @@ async function createProject(options) {
|
|
|
748
760
|
process.exit(1);
|
|
749
761
|
}
|
|
750
762
|
}
|
|
763
|
+
if (repoName) {
|
|
764
|
+
const validation = validateProjectName(toKebabCase(repoName));
|
|
765
|
+
if (!validation.valid) {
|
|
766
|
+
console.error(chalk.red(`Invalid repo name: ${validation.error}`));
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
751
770
|
let answers;
|
|
752
771
|
if (options.yes) {
|
|
753
772
|
const projectType = options.type || "webapp";
|
|
754
773
|
requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
|
|
755
774
|
const kebabName = toKebabCase(projectName);
|
|
756
|
-
const
|
|
775
|
+
const kebabRepoName = repoName ? toKebabCase(repoName) : kebabName;
|
|
776
|
+
const directory = monorepoContext.found && monorepoContext.packageDir ? path7.join(monorepoContext.packageDir, kebabName) : path7.resolve(cwd, kebabRepoName);
|
|
757
777
|
answers = {
|
|
758
778
|
projectType,
|
|
759
779
|
directory,
|
|
760
780
|
name: kebabName,
|
|
761
781
|
title: toTitleCase(kebabName),
|
|
762
|
-
installDeps: !options.skipInstall
|
|
782
|
+
installDeps: !options.skipInstall,
|
|
783
|
+
monorepoName: monorepoContext.found ? void 0 : kebabRepoName,
|
|
784
|
+
monorepoTitle: monorepoContext.found ? void 0 : toTitleCase(kebabRepoName)
|
|
763
785
|
};
|
|
764
786
|
} else {
|
|
765
787
|
answers = await promptProjectDetails({
|
|
766
788
|
projectType: options.type,
|
|
767
789
|
name: projectName ? toKebabCase(projectName) : void 0,
|
|
790
|
+
repoName: repoName ? toKebabCase(repoName) : void 0,
|
|
768
791
|
skipInstall: options.skipInstall,
|
|
769
792
|
monorepoContext,
|
|
793
|
+
cwd,
|
|
770
794
|
beforeNamePrompt: (projectType) => requireNpmTokenForWebappInstall(projectType, !options.skipInstall)
|
|
771
795
|
});
|
|
772
796
|
if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) {
|
|
773
797
|
answers.directory = path7.join(monorepoContext.packageDir, answers.name);
|
|
774
798
|
}
|
|
775
799
|
}
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
nameSnake: toSnakeCase(answers.name)
|
|
782
|
-
};
|
|
800
|
+
const monorepoName = answers.monorepoName ?? answers.name;
|
|
801
|
+
const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
|
|
802
|
+
const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle);
|
|
803
|
+
const configRepoName = monorepoContext.found ? path7.basename(monorepoContext.rootDir) : monorepoName;
|
|
804
|
+
const config = buildAppConfig(answers.name, answers.title, configRepoName);
|
|
783
805
|
const typeLabel = getProjectTypeLabel(answers.projectType);
|
|
784
806
|
if (monorepoContext.found) {
|
|
785
807
|
const monorepoRoot = monorepoContext.rootDir;
|
|
@@ -810,16 +832,10 @@ async function createProject(options) {
|
|
|
810
832
|
});
|
|
811
833
|
}
|
|
812
834
|
await warnIfMissingRootNpmrc(monorepoRoot);
|
|
813
|
-
const
|
|
835
|
+
const installSucceeded = await installAtMonorepoRoot(
|
|
814
836
|
monorepoRoot,
|
|
815
837
|
answers.installDeps
|
|
816
838
|
);
|
|
817
|
-
const packageInstallSucceeded = await installAtWebappPackage(
|
|
818
|
-
packageDir,
|
|
819
|
-
answers.projectType,
|
|
820
|
-
answers.installDeps
|
|
821
|
-
);
|
|
822
|
-
const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
|
|
823
839
|
console.log();
|
|
824
840
|
console.log(
|
|
825
841
|
chalk.green("\u2714"),
|
|
@@ -833,7 +849,7 @@ async function createProject(options) {
|
|
|
833
849
|
installSucceeded
|
|
834
850
|
);
|
|
835
851
|
if (devStarted) return;
|
|
836
|
-
printNextStepsExisting(answers,
|
|
852
|
+
printNextStepsExisting(answers, packageDir);
|
|
837
853
|
} else {
|
|
838
854
|
const isBareMonorepo = answers.projectType === "monorepo";
|
|
839
855
|
const monorepoRoot = answers.directory;
|
|
@@ -841,11 +857,12 @@ async function createProject(options) {
|
|
|
841
857
|
if (isBareMonorepo) {
|
|
842
858
|
console.log(chalk.dim(" Type:"), typeLabel);
|
|
843
859
|
console.log(chalk.dim(" Directory:"), monorepoRoot);
|
|
844
|
-
console.log(chalk.dim("
|
|
845
|
-
console.log(chalk.dim(" Title:"),
|
|
860
|
+
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
861
|
+
console.log(chalk.dim(" Title:"), monorepoConfig.title);
|
|
846
862
|
} else {
|
|
847
863
|
console.log(chalk.dim(" Package type:"), typeLabel);
|
|
848
864
|
console.log(chalk.dim(" Monorepo directory:"), monorepoRoot);
|
|
865
|
+
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
849
866
|
console.log(chalk.dim(" Package:"), `packages/${answers.name}/`);
|
|
850
867
|
console.log(chalk.dim(" Name:"), config.name);
|
|
851
868
|
console.log(chalk.dim(" Title:"), config.title);
|
|
@@ -863,7 +880,7 @@ async function createProject(options) {
|
|
|
863
880
|
process.exit(1);
|
|
864
881
|
}
|
|
865
882
|
}
|
|
866
|
-
await scaffoldMonorepo(monorepoRoot,
|
|
883
|
+
await scaffoldMonorepo(monorepoRoot, monorepoConfig);
|
|
867
884
|
if (packageDir && answers.projectType !== "monorepo") {
|
|
868
885
|
await addPackageToMonorepo({
|
|
869
886
|
packageDir,
|
|
@@ -873,16 +890,10 @@ async function createProject(options) {
|
|
|
873
890
|
});
|
|
874
891
|
}
|
|
875
892
|
initGitRepo(monorepoRoot);
|
|
876
|
-
const
|
|
893
|
+
const installSucceeded = await installAtMonorepoRoot(
|
|
877
894
|
monorepoRoot,
|
|
878
895
|
answers.installDeps
|
|
879
896
|
);
|
|
880
|
-
const packageInstallSucceeded = await installAtWebappPackage(
|
|
881
|
-
packageDir,
|
|
882
|
-
answers.projectType,
|
|
883
|
-
answers.installDeps
|
|
884
|
-
);
|
|
885
|
-
const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
|
|
886
897
|
console.log();
|
|
887
898
|
console.log(
|
|
888
899
|
chalk.green("\u2714"),
|
|
@@ -903,8 +914,23 @@ async function createProject(options) {
|
|
|
903
914
|
installSucceeded
|
|
904
915
|
);
|
|
905
916
|
if (devStarted) return;
|
|
906
|
-
printNextStepsNew(answers,
|
|
917
|
+
printNextStepsNew(answers, monorepoRoot);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async function resolveCreateCwd(cwdOption) {
|
|
921
|
+
const cwd = cwdOption ? path7.resolve(cwdOption) : process.cwd();
|
|
922
|
+
let stat;
|
|
923
|
+
try {
|
|
924
|
+
stat = await fs7.stat(cwd);
|
|
925
|
+
} catch {
|
|
926
|
+
console.error(chalk.red(`Error: --cwd directory does not exist: ${cwd}`));
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
if (!stat.isDirectory()) {
|
|
930
|
+
console.error(chalk.red(`Error: --cwd is not a directory: ${cwd}`));
|
|
931
|
+
process.exit(1);
|
|
907
932
|
}
|
|
933
|
+
return cwd;
|
|
908
934
|
}
|
|
909
935
|
function printWebappNextSteps(params) {
|
|
910
936
|
const {
|
|
@@ -916,14 +942,12 @@ function printWebappNextSteps(params) {
|
|
|
916
942
|
} = params;
|
|
917
943
|
const repoRel = shPath(monorepoRelativePath) || ".";
|
|
918
944
|
const pkgFromRoot = `packages/${answers.name}`;
|
|
919
|
-
const packageInstallStep = `${pm} install --ignore-workspace`;
|
|
920
945
|
const pnpmSteps = ["pnpm run setup", "pnpm dev"];
|
|
921
946
|
if (variant === "new") {
|
|
922
947
|
const oneLinerParts2 = [];
|
|
923
948
|
if (repoRel !== ".") oneLinerParts2.push(`cd ${repoRel}`);
|
|
924
949
|
if (!answers.installDeps) oneLinerParts2.push(`${pm} install`);
|
|
925
950
|
oneLinerParts2.push(`cd ${pkgFromRoot}`);
|
|
926
|
-
if (!answers.installDeps) oneLinerParts2.push(packageInstallStep);
|
|
927
951
|
oneLinerParts2.push(...pnpmSteps);
|
|
928
952
|
console.log(chalk.bold("Copy-paste (from your current directory):"));
|
|
929
953
|
console.log();
|
|
@@ -939,9 +963,6 @@ function printWebappNextSteps(params) {
|
|
|
939
963
|
console.log(chalk.dim(` ${step2++}.`), `${pm} install`);
|
|
940
964
|
}
|
|
941
965
|
console.log(chalk.dim(` ${step2++}.`), `cd ${pkgFromRoot}`);
|
|
942
|
-
if (!answers.installDeps) {
|
|
943
|
-
console.log(chalk.dim(` ${step2++}.`), packageInstallStep);
|
|
944
|
-
}
|
|
945
966
|
for (const cmd of pnpmSteps) {
|
|
946
967
|
console.log(chalk.dim(` ${step2++}.`), cmd);
|
|
947
968
|
}
|
|
@@ -951,7 +972,7 @@ function printWebappNextSteps(params) {
|
|
|
951
972
|
const oneLinerParts = [];
|
|
952
973
|
if (!answers.installDeps) {
|
|
953
974
|
if (repoRel !== ".") oneLinerParts.push(`cd ${repoRel}`);
|
|
954
|
-
oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}
|
|
975
|
+
oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}`);
|
|
955
976
|
} else if (pkgRel !== ".") {
|
|
956
977
|
oneLinerParts.push(`cd ${pkgRel}`);
|
|
957
978
|
}
|
|
@@ -969,7 +990,6 @@ function printWebappNextSteps(params) {
|
|
|
969
990
|
}
|
|
970
991
|
console.log(chalk.dim(` ${step++}.`), `${pm} install`);
|
|
971
992
|
console.log(chalk.dim(` ${step++}.`), `cd ${pkgFromRoot}`);
|
|
972
|
-
console.log(chalk.dim(` ${step++}.`), packageInstallStep);
|
|
973
993
|
} else if (pkgRel !== ".") {
|
|
974
994
|
console.log(chalk.dim(` ${step++}.`), `cd ${pkgRel}`);
|
|
975
995
|
}
|
|
@@ -1006,7 +1026,7 @@ async function warnIfMissingRootNpmrc(rootDir) {
|
|
|
1006
1026
|
);
|
|
1007
1027
|
console.log();
|
|
1008
1028
|
}
|
|
1009
|
-
function printNextStepsNew(answers,
|
|
1029
|
+
function printNextStepsNew(answers, targetDir) {
|
|
1010
1030
|
const pm = PACKAGE_MANAGER;
|
|
1011
1031
|
const relativePath = path7.relative(process.cwd(), targetDir) || ".";
|
|
1012
1032
|
console.log("Next steps:");
|
|
@@ -1062,7 +1082,7 @@ function printNextStepsNew(answers, options, targetDir) {
|
|
|
1062
1082
|
);
|
|
1063
1083
|
console.log();
|
|
1064
1084
|
}
|
|
1065
|
-
function printNextStepsExisting(answers,
|
|
1085
|
+
function printNextStepsExisting(answers, packageDir) {
|
|
1066
1086
|
const pm = PACKAGE_MANAGER;
|
|
1067
1087
|
const packageRelativePath = path7.relative(process.cwd(), packageDir) || ".";
|
|
1068
1088
|
const monorepoRoot = path7.dirname(path7.dirname(packageDir));
|
|
@@ -1106,30 +1126,30 @@ var packageJson = {
|
|
|
1106
1126
|
version: "1.0.0"
|
|
1107
1127
|
};
|
|
1108
1128
|
program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
|
|
1109
|
-
program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "
|
|
1129
|
+
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("--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);
|
|
1110
1130
|
program.command("status").description("Show template sync status for current app").option(
|
|
1111
1131
|
"--mosaic-template-path <path>",
|
|
1112
1132
|
"Path to local mosaic repo checkout"
|
|
1113
1133
|
).action(async (options) => {
|
|
1114
|
-
const { statusCommand } = await import("./status-
|
|
1134
|
+
const { statusCommand } = await import("./status-QW5TQDYY.js");
|
|
1115
1135
|
await statusCommand(options);
|
|
1116
1136
|
});
|
|
1117
1137
|
program.command("sync").description("Generate downstream sync context (template \u2192 app)").option(
|
|
1118
1138
|
"--mosaic-template-path <path>",
|
|
1119
1139
|
"Path to local mosaic repo checkout"
|
|
1120
1140
|
).option("--to <version>", "Target template version (default: latest)").action(async (options) => {
|
|
1121
|
-
const { syncCommand } = await import("./sync-
|
|
1141
|
+
const { syncCommand } = await import("./sync-RLBZDOFB.js");
|
|
1122
1142
|
await syncCommand(options);
|
|
1123
1143
|
});
|
|
1124
1144
|
program.command("upstream").description("Generate upstream context (app \u2192 template)").option(
|
|
1125
1145
|
"--mosaic-template-path <path>",
|
|
1126
1146
|
"Path to local mosaic repo checkout"
|
|
1127
1147
|
).option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
|
|
1128
|
-
const { upstreamCommand } = await import("./upstream-
|
|
1148
|
+
const { upstreamCommand } = await import("./upstream-TQFVPMEG.js");
|
|
1129
1149
|
await upstreamCommand(options);
|
|
1130
1150
|
});
|
|
1131
1151
|
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) => {
|
|
1132
|
-
const { initCommand } = await import("./init-
|
|
1152
|
+
const { initCommand } = await import("./init-EQZ2TCSJ.js");
|
|
1133
1153
|
await initCommand(options);
|
|
1134
1154
|
});
|
|
1135
1155
|
program.parse();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VALID_PROJECT_TYPES,
|
|
3
3
|
isValidProjectType
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CO3YWUD6.js";
|
|
5
5
|
import {
|
|
6
6
|
derivePlaceholders,
|
|
7
7
|
manifestExists,
|
|
8
8
|
writeManifest
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-V5EJIUBJ.js";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
12
|
import path from "path";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percepta/create",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Scaffold a new Mosaic package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"tsup": "^8.4.0",
|
|
28
28
|
"typescript": "^5.7.3",
|
|
29
29
|
"vitest": "^4.0.0",
|
|
30
|
-
"@percepta/build": "0.4.
|
|
30
|
+
"@percepta/build": "0.4.1"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=18.0.0"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
50
|
+
"create:local": "pnpm build && node dist/index.js",
|
|
50
51
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
51
52
|
"typecheck": "tsc --noEmit",
|
|
52
53
|
"sync-template": "tsx scripts/sync-template.ts",
|
|
@@ -9,7 +9,9 @@ on:
|
|
|
9
9
|
- "packages/__APP_NAME__/scripts/**"
|
|
10
10
|
- "packages/__APP_NAME__/Dockerfile"
|
|
11
11
|
- "packages/__APP_NAME__/package.json"
|
|
12
|
-
- "
|
|
12
|
+
- "package.json"
|
|
13
|
+
- "pnpm-lock.yaml"
|
|
14
|
+
- "pnpm-workspace.yaml"
|
|
13
15
|
- ".github/workflows/__APP_NAME__-ryvn-release.yaml"
|
|
14
16
|
pull_request:
|
|
15
17
|
branches:
|
|
@@ -19,7 +21,9 @@ on:
|
|
|
19
21
|
- "packages/__APP_NAME__/scripts/**"
|
|
20
22
|
- "packages/__APP_NAME__/Dockerfile"
|
|
21
23
|
- "packages/__APP_NAME__/package.json"
|
|
22
|
-
- "
|
|
24
|
+
- "package.json"
|
|
25
|
+
- "pnpm-lock.yaml"
|
|
26
|
+
- "pnpm-workspace.yaml"
|
|
23
27
|
workflow_dispatch:
|
|
24
28
|
|
|
25
29
|
env:
|