@percepta/create 3.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.
Files changed (138) hide show
  1. package/README.md +93 -0
  2. package/dist/chunk-GEVZERMP.js +108 -0
  3. package/dist/chunk-R4FWPE4A.js +49 -0
  4. package/dist/chunk-WMJT7CB5.js +57 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +974 -0
  7. package/dist/init-Z4VGBHAK.js +96 -0
  8. package/dist/status-MITGDLTT.js +76 -0
  9. package/dist/sync-J4SFZHDX.js +136 -0
  10. package/dist/upstream-AQI7P4EU.js +144 -0
  11. package/package.json +58 -0
  12. package/template-versions.json +4 -0
  13. package/templates/library/README.md +30 -0
  14. package/templates/library/eslint.config.js +10 -0
  15. package/templates/library/gitignore.template +18 -0
  16. package/templates/library/package.json.template +29 -0
  17. package/templates/library/src/index.ts +9 -0
  18. package/templates/library/tsconfig.json +19 -0
  19. package/templates/monorepo/README.md +41 -0
  20. package/templates/monorepo/eslint.config.js +10 -0
  21. package/templates/monorepo/gitignore.template +31 -0
  22. package/templates/monorepo/npmrc.template +4 -0
  23. package/templates/monorepo/package.json.template +25 -0
  24. package/templates/monorepo/packages/.gitkeep +0 -0
  25. package/templates/monorepo/pnpm-workspace.yaml +2 -0
  26. package/templates/monorepo/tsconfig.json +16 -0
  27. package/templates/webapp/.claude/commands/sync.md +19 -0
  28. package/templates/webapp/.claude/commands/upstream.md +17 -0
  29. package/templates/webapp/.dockerignore +59 -0
  30. package/templates/webapp/.gitattributes +1 -0
  31. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
  32. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
  33. package/templates/webapp/.github/workflows/ci.yml +149 -0
  34. package/templates/webapp/.node-version +2 -0
  35. package/templates/webapp/.prettierrc.mjs +5 -0
  36. package/templates/webapp/AGENTS.md +240 -0
  37. package/templates/webapp/Dockerfile +64 -0
  38. package/templates/webapp/README.md +200 -0
  39. package/templates/webapp/agent-skills/database.md +140 -0
  40. package/templates/webapp/agent-skills/deploy.md +94 -0
  41. package/templates/webapp/agent-skills/inngest.md +147 -0
  42. package/templates/webapp/agent-skills/langfuse.md +117 -0
  43. package/templates/webapp/agent-skills/oneshot.md +216 -0
  44. package/templates/webapp/agent-skills/ryvn.md +25 -0
  45. package/templates/webapp/deploy/README.md +39 -0
  46. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
  47. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
  48. package/templates/webapp/docker-compose.yml +19 -0
  49. package/templates/webapp/drizzle.config.ts +30 -0
  50. package/templates/webapp/env.example.template +44 -0
  51. package/templates/webapp/eslint.config.mjs +52 -0
  52. package/templates/webapp/gitignore.template +53 -0
  53. package/templates/webapp/next.config.ts +8 -0
  54. package/templates/webapp/npmrc.template +4 -0
  55. package/templates/webapp/package.json.template +122 -0
  56. package/templates/webapp/postcss.config.mjs +5 -0
  57. package/templates/webapp/scripts/create-user.ts +47 -0
  58. package/templates/webapp/scripts/migrate.ts +18 -0
  59. package/templates/webapp/scripts/seed.ts +62 -0
  60. package/templates/webapp/scripts/setup-database.ts +57 -0
  61. package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
  62. package/templates/webapp/scripts/start.sh +52 -0
  63. package/templates/webapp/src/app/(app)/layout.tsx +21 -0
  64. package/templates/webapp/src/app/(app)/page.tsx +30 -0
  65. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
  66. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
  67. package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
  68. package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
  69. package/templates/webapp/src/app/api/healthz/route.ts +10 -0
  70. package/templates/webapp/src/app/api/inngest/route.ts +31 -0
  71. package/templates/webapp/src/app/api/readyz/route.ts +31 -0
  72. package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
  73. package/templates/webapp/src/app/favicon.ico +0 -0
  74. package/templates/webapp/src/app/global-error.tsx +27 -0
  75. package/templates/webapp/src/app/layout.tsx +18 -0
  76. package/templates/webapp/src/components/FaroProvider.tsx +37 -0
  77. package/templates/webapp/src/components/Header.tsx +70 -0
  78. package/templates/webapp/src/components/Providers.tsx +45 -0
  79. package/templates/webapp/src/components/form/FormItem.tsx +82 -0
  80. package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
  81. package/templates/webapp/src/config/getEnvConfig.ts +62 -0
  82. package/templates/webapp/src/config/isDev.ts +7 -0
  83. package/templates/webapp/src/drizzle/db.ts +28 -0
  84. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
  85. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
  86. package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
  87. package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
  88. package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
  89. package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
  90. package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
  91. package/templates/webapp/src/drizzle/schema/index.ts +4 -0
  92. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
  93. package/templates/webapp/src/instrumentation.ts +35 -0
  94. package/templates/webapp/src/lib/auth/index.ts +85 -0
  95. package/templates/webapp/src/lib/auth-client.ts +6 -0
  96. package/templates/webapp/src/lib/trpc.ts +15 -0
  97. package/templates/webapp/src/server/api/root.ts +5 -0
  98. package/templates/webapp/src/server/trpc.ts +61 -0
  99. package/templates/webapp/src/services/AuthContextService.ts +63 -0
  100. package/templates/webapp/src/services/DatabaseService.ts +54 -0
  101. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
  102. package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
  103. package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
  104. package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
  105. package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
  106. package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
  107. package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
  108. package/templates/webapp/src/services/observability/initFaro.ts +22 -0
  109. package/templates/webapp/src/startup-checks.ts +32 -0
  110. package/templates/webapp/src/styles/globals.css +27 -0
  111. package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
  112. package/templates/webapp/src/utils/cn.ts +6 -0
  113. package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
  114. package/templates/webapp/terraform/README.md +147 -0
  115. package/templates/webapp/terraform/deploy.sh +97 -0
  116. package/templates/webapp/terraform/main.tf +101 -0
  117. package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
  118. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
  119. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
  120. package/templates/webapp/terraform/modules/networking/main.tf +118 -0
  121. package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
  122. package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
  123. package/templates/webapp/terraform/modules/rds/main.tf +227 -0
  124. package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
  125. package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
  126. package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
  127. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
  128. package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
  129. package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
  130. package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
  131. package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
  132. package/templates/webapp/terraform/outputs.tf +102 -0
  133. package/templates/webapp/terraform/providers.tf +32 -0
  134. package/templates/webapp/terraform/terraform.tfvars.example +65 -0
  135. package/templates/webapp/terraform/variables.tf +129 -0
  136. package/templates/webapp/tsconfig.json +14 -0
  137. package/templates/webapp/vitest.config.ts +9 -0
  138. package/templates/webapp/vitest.setup.ts +5 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @percepta/create
2
+
3
+ Scaffold and manage Mosaic packages.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx @percepta/create
9
+ ```
10
+
11
+ That's it. The CLI prompts you for the project name and (depending on context) whether it's a webapp. Defaults yield a running app — sign in as `admin@example.com` / `password`.
12
+
13
+ ## Options (mostly for automation)
14
+
15
+ The bare command above is the canonical UX. The flags below exist for tests and other automation, not for routine use:
16
+
17
+ | Option | Description |
18
+ |--------|-------------|
19
+ | `-t, --type <type>` | Package type: `monorepo`, `webapp`, or `library` (skips the type prompt) |
20
+ | `--name <name>` | Project name (skips the name prompt) |
21
+ | `--skip-install` | Skip dependency installation, which also skips the auto-run setup + dev + browser, leaving you with manual next-steps |
22
+ | `-y, --yes` | Skip all prompts; requires `--name` |
23
+
24
+ ## Subcommands
25
+
26
+ - `create` (default) — scaffold a new Mosaic package
27
+ - `status` — show template sync status for the current app
28
+ - `sync` — generate downstream sync context (template → app)
29
+ - `upstream` — generate upstream context (app → template)
30
+ - `init` — add `.mosaic-template.json` to an existing app
31
+
32
+ ## Project types
33
+
34
+ `create` auto-detects whether you're inside an existing pnpm monorepo (by walking up for `pnpm-workspace.yaml`) and changes its prompts accordingly:
35
+
36
+ - **Outside a monorepo** — pick `Monorepo` (default) or `Library`. If `Monorepo`, you're asked "Initialize with a webapp?" (Y/n, default Y). Picking the webapp option scaffolds a monorepo with a webapp inside `packages/<name>/`. Declining gives you an empty monorepo.
37
+ - **Inside a monorepo** — pick `Webapp` (default) or `Library` to add a new package under the workspace pattern.
38
+
39
+ ## Happy-path: zero-friction webapp
40
+
41
+ When you scaffold a webapp (the default flow), `create` automatically runs:
42
+
43
+ 1. `pnpm install` (at the monorepo root)
44
+ 2. `pnpm run setup` — Docker Compose Postgres + Drizzle migrations + seed user
45
+ 3. `pnpm dev` — Next.js dev server
46
+ 4. Opens the served URL in your default browser
47
+
48
+ Sign in as `admin@example.com` / `password` to start building.
49
+
50
+ To bail out of the auto-run and get manual next-steps instead, pass `--skip-install`. Then you can run install / setup / dev yourself when ready.
51
+
52
+ The `webapp` template ships with:
53
+
54
+ - **Next.js 15** with App Router
55
+ - **Authentication** via Better Auth
56
+ - **Database** with PostgreSQL and Drizzle ORM
57
+ - **Type-safe API** with tRPC
58
+ - **Background Jobs** with Inngest
59
+ - **Observability** with OpenTelemetry, Langfuse, and Grafana Faro
60
+ - **Styling** with Tailwind CSS v4
61
+
62
+ The `library` template ships with TypeScript, ESLint, and a minimal `src/index.ts`.
63
+
64
+ 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.
65
+
66
+ ## Development
67
+
68
+ ### Building the CLI
69
+
70
+ ```bash
71
+ pnpm build
72
+ ```
73
+
74
+ ### Testing locally
75
+
76
+ ```bash
77
+ pnpm build
78
+ npm link
79
+ create test-app
80
+ ```
81
+
82
+ ### Syncing template files
83
+
84
+ When the base template is updated, sync the changes:
85
+
86
+ ```bash
87
+ pnpm sync-template
88
+ ```
89
+
90
+ ## Publishing
91
+
92
+ Publishing is automated via changesets on merge to `main`. See the repo-root
93
+ `.changeset/README.md` and `.github/workflows/build-and-publish.yml`.
@@ -0,0 +1,108 @@
1
+ // src/utils/prompts.ts
2
+ import path from "path";
3
+ import inquirer from "inquirer";
4
+
5
+ // src/utils/validate.ts
6
+ import validateNpmPackageName from "validate-npm-package-name";
7
+ function validateProjectName(name) {
8
+ const result = validateNpmPackageName(name);
9
+ if (!result.validForNewPackages) {
10
+ const errors = [...result.errors || [], ...result.warnings || []];
11
+ return {
12
+ valid: false,
13
+ error: errors[0] || "Invalid package name"
14
+ };
15
+ }
16
+ return { valid: true };
17
+ }
18
+
19
+ // src/utils/case-converters.ts
20
+ function toKebabCase(str) {
21
+ return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
22
+ }
23
+ function toTitleCase(str) {
24
+ return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
25
+ }
26
+ function toSnakeCase(str) {
27
+ return str.replace(/-/g, "_");
28
+ }
29
+
30
+ // src/utils/prompts.ts
31
+ var VALID_PROJECT_TYPES = ["monorepo", "webapp", "library"];
32
+ function isValidProjectType(value) {
33
+ return typeof value === "string" && VALID_PROJECT_TYPES.includes(value);
34
+ }
35
+ async function promptName(message) {
36
+ const { name } = await inquirer.prompt([
37
+ {
38
+ type: "input",
39
+ name: "name",
40
+ message,
41
+ filter: toKebabCase,
42
+ validate: (input) => {
43
+ const result = validateProjectName(toKebabCase(input));
44
+ return result.valid || result.error || "Invalid project name";
45
+ }
46
+ }
47
+ ]);
48
+ return name;
49
+ }
50
+ async function promptOutsideMonorepoType() {
51
+ const { webapp } = await inquirer.prompt([
52
+ {
53
+ type: "confirm",
54
+ name: "webapp",
55
+ message: "Initialize with a webapp?",
56
+ default: true
57
+ }
58
+ ]);
59
+ return webapp ? "webapp" : "monorepo";
60
+ }
61
+ async function promptInsideMonorepoType() {
62
+ const { projectType } = await inquirer.prompt([
63
+ {
64
+ type: "rawlist",
65
+ name: "projectType",
66
+ message: "What kind of package?",
67
+ // inquirer v12 / @inquirer/rawlist v5 matches `default` against the
68
+ // choice's `value`, not its index. `default: 0` would be a no-op.
69
+ default: "webapp",
70
+ choices: [
71
+ { name: "Webapp \u2014 A Next.js webapp", value: "webapp" },
72
+ { name: "Library \u2014 A TypeScript library", value: "library" }
73
+ ]
74
+ }
75
+ ]);
76
+ return projectType;
77
+ }
78
+ async function promptProjectDetails(defaults) {
79
+ const inMonorepo = defaults.monorepoContext?.found ?? false;
80
+ let projectType;
81
+ let finalName;
82
+ if (inMonorepo) {
83
+ projectType = defaults.projectType ?? await promptInsideMonorepoType();
84
+ finalName = defaults.name || await promptName("Package name?");
85
+ } else {
86
+ finalName = defaults.name || await promptName("Project name?");
87
+ projectType = defaults.projectType ?? await promptOutsideMonorepoType();
88
+ }
89
+ const finalTitle = finalName ? toTitleCase(finalName) : "";
90
+ const finalDirectory = !inMonorepo && finalName ? path.resolve(process.cwd(), finalName) : "";
91
+ return {
92
+ projectType,
93
+ directory: finalDirectory,
94
+ name: finalName,
95
+ title: finalTitle,
96
+ installDeps: !defaults.skipInstall
97
+ };
98
+ }
99
+
100
+ export {
101
+ validateProjectName,
102
+ toKebabCase,
103
+ toTitleCase,
104
+ toSnakeCase,
105
+ VALID_PROJECT_TYPES,
106
+ isValidProjectType,
107
+ promptProjectDetails
108
+ };
@@ -0,0 +1,49 @@
1
+ // src/utils/git-ops.ts
2
+ import { execFileSync } from "child_process";
3
+ function toGitPath(p) {
4
+ return p.replace(/\\/g, "/");
5
+ }
6
+ function getLatestTemplateTag(type, repoPath) {
7
+ try {
8
+ const tags = execFileSync(
9
+ "git",
10
+ ["tag", "-l", `template/${type}/*`, "--sort=-v:refname"],
11
+ { cwd: repoPath, encoding: "utf-8" }
12
+ ).trim();
13
+ if (!tags) return null;
14
+ return tags.split("\n")[0];
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function getTemplateVersionFromTag(tag) {
20
+ const parts = tag.split("/");
21
+ return parts[parts.length - 1];
22
+ }
23
+ function getTemplateDiff(repoPath, templatePath, fromTag, toTag) {
24
+ return execFileSync(
25
+ "git",
26
+ ["diff", `${fromTag}..${toTag}`, "--", toGitPath(templatePath)],
27
+ {
28
+ cwd: repoPath,
29
+ encoding: "utf-8"
30
+ }
31
+ );
32
+ }
33
+ function getFileAtTag(repoPath, tag, filePath) {
34
+ try {
35
+ return execFileSync("git", ["show", `${tag}:${toGitPath(filePath)}`], {
36
+ cwd: repoPath,
37
+ encoding: "utf-8"
38
+ });
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ export {
45
+ getLatestTemplateTag,
46
+ getTemplateVersionFromTag,
47
+ getTemplateDiff,
48
+ getFileAtTag
49
+ };
@@ -0,0 +1,57 @@
1
+ // src/utils/manifest.ts
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ var MANIFEST_FILENAME = ".mosaic-template.json";
5
+ function getManifestPath(dir) {
6
+ return path.join(dir, MANIFEST_FILENAME);
7
+ }
8
+ async function readManifest(dir) {
9
+ const manifestPath = getManifestPath(dir);
10
+ if (!await fs.pathExists(manifestPath)) {
11
+ throw new Error(
12
+ `No ${MANIFEST_FILENAME} found in ${dir}. Run 'create init' to create one.`
13
+ );
14
+ }
15
+ const content = await fs.readFile(manifestPath, "utf-8");
16
+ try {
17
+ return JSON.parse(content);
18
+ } catch (error) {
19
+ throw new Error(
20
+ `Invalid JSON in ${MANIFEST_FILENAME}: ${error.message}`
21
+ );
22
+ }
23
+ }
24
+ async function writeManifest(dir, manifest) {
25
+ const manifestPath = getManifestPath(dir);
26
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
27
+ }
28
+ async function manifestExists(dir) {
29
+ return fs.pathExists(getManifestPath(dir));
30
+ }
31
+ function derivePlaceholders(appName, appTitle) {
32
+ const nameSnake = appName.replace(/-/g, "_");
33
+ return {
34
+ __APP_NAME__: appName,
35
+ __APP_TITLE__: appTitle,
36
+ __DB_NAME__: nameSnake + "_db",
37
+ __APP_NAME_UPPER__: appName.toUpperCase(),
38
+ __APP_NAME_SNAKE__: nameSnake
39
+ };
40
+ }
41
+ function resolveMosaicTemplatePath(options) {
42
+ if (options.mosaicTemplatePath)
43
+ return path.resolve(options.mosaicTemplatePath);
44
+ if (process.env.MOSAIC_TEMPLATE_PATH)
45
+ return path.resolve(process.env.MOSAIC_TEMPLATE_PATH);
46
+ throw new Error(
47
+ "Mosaic repo path required. Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH."
48
+ );
49
+ }
50
+
51
+ export {
52
+ readManifest,
53
+ writeManifest,
54
+ manifestExists,
55
+ derivePlaceholders,
56
+ resolveMosaicTemplatePath
57
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node