@percepta/create 3.1.2 → 3.1.4
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 +3 -4
- package/dist/{chunk-CG7IJSB4.js → chunk-CO3YWUD6.js} +2 -2
- package/dist/{chunk-WMJT7CB5.js → chunk-V5EJIUBJ.js} +5 -2
- package/dist/index.js +21 -53
- package/dist/{init-XDWSYHYK.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 +1 -1
- package/templates/monorepo/.dockerignore +18 -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 +18 -6
- package/templates/webapp/Dockerfile +16 -7
- package/templates/webapp/README.md +65 -3
- package/templates/webapp/agent-skills/database.md +5 -1
- package/templates/webapp/agent-skills/deploy.md +49 -64
- package/templates/webapp/agent-skills/inngest.md +17 -12
- 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 +15 -2
- package/templates/webapp/agent-skills/ryvn.md +1 -1
- package/templates/webapp/deploy/README.md +34 -33
- 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 +45 -9
- package/templates/webapp/env.example.template +20 -2
- package/templates/webapp/eslint.config.mjs +6 -0
- package/templates/webapp/next.config.ts +9 -0
- package/templates/webapp/package.json.template +8 -4
- package/templates/webapp/scripts/deploy-percepta-test.ts +1112 -0
- package/templates/webapp/scripts/generate-migrations.ts +28 -0
- package/templates/webapp/scripts/migrate.ts +3 -0
- package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +5 -3
- package/templates/webapp/scripts/with-local-env.ts +75 -0
- package/templates/webapp/src/config/getEnvConfig.ts +14 -0
- package/templates/webapp/src/drizzle/__tests__/migrationSql.test.ts +24 -0
- package/templates/webapp/src/drizzle/migrationSql.ts +8 -0
- package/templates/webapp/src/instrumentation.ts +102 -10
- package/templates/webapp/src/services/inngest/AppWorkflowService.ts +19 -0
- package/templates/webapp/src/services/inngest/__tests__/AppWorkflowService.test.ts +19 -0
- package/templates/webapp/src/services/inngest/events/AppEvents.ts +7 -13
- package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +1 -3
- package/templates/webapp/src/services/llm/LLMService.ts +88 -0
- package/templates/webapp/src/services/llm/LlmProviderService.ts +85 -0
- 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
|
@@ -43,10 +43,9 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
43
43
|
When you scaffold a webapp (the default flow), `create` automatically runs:
|
|
44
44
|
|
|
45
45
|
1. `pnpm install` (at the monorepo root)
|
|
46
|
-
2. `pnpm
|
|
47
|
-
3. `pnpm
|
|
48
|
-
4.
|
|
49
|
-
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
|
|
50
49
|
|
|
51
50
|
Sign in as `admin@example.com` / `password` to start building.
|
|
52
51
|
|
|
@@ -85,10 +85,10 @@ async function promptProjectDetails(defaults) {
|
|
|
85
85
|
await defaults.beforeNamePrompt?.(projectType);
|
|
86
86
|
finalName = defaults.name || await promptName("Package name?");
|
|
87
87
|
} else {
|
|
88
|
+
const repoName = defaults.repoName || (defaults.projectType === "monorepo" ? defaults.name : void 0) || await promptName("Repo name?");
|
|
89
|
+
const repoTitle = toTitleCase(repoName);
|
|
88
90
|
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
89
91
|
await defaults.beforeNamePrompt?.(projectType);
|
|
90
|
-
const repoName = defaults.repoName || (projectType === "monorepo" ? defaults.name : void 0) || await promptName("Repo name?");
|
|
91
|
-
const repoTitle = toTitleCase(repoName);
|
|
92
92
|
if (projectType === "monorepo") {
|
|
93
93
|
finalName = repoName;
|
|
94
94
|
const finalTitle3 = repoTitle;
|
|
@@ -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",
|
|
@@ -280,6 +282,8 @@ async function generateEnvLocal(packageDir) {
|
|
|
280
282
|
const deploySecrets = [
|
|
281
283
|
`BETTER_AUTH_SECRET=${deployAuthSecret}`,
|
|
282
284
|
`ENCRYPTION_SECRET_KEY=${deployEncKey}`,
|
|
285
|
+
"",
|
|
286
|
+
"# Langfuse and LLM demo credentials are inherited from the demos-commons Ryvn variable group.",
|
|
283
287
|
""
|
|
284
288
|
].join("\n");
|
|
285
289
|
await fs4.ensureDir(path4.dirname(ryvnSecretsPath));
|
|
@@ -526,7 +530,7 @@ async function writeMosaicFiles(packageDir, config, projectType) {
|
|
|
526
530
|
templateVersion: templateVersions[projectType] || "1.0.0",
|
|
527
531
|
templateCommit: "npm",
|
|
528
532
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
529
|
-
placeholders: derivePlaceholders(config.name, config.title),
|
|
533
|
+
placeholders: derivePlaceholders(config.name, config.title, config.repoName),
|
|
530
534
|
source: {
|
|
531
535
|
templatePath: `packages/create-mosaic-module/templates/${projectType}`
|
|
532
536
|
}
|
|
@@ -546,13 +550,15 @@ _None yet \u2014 freshly created from template._
|
|
|
546
550
|
`
|
|
547
551
|
);
|
|
548
552
|
}
|
|
549
|
-
function buildAppConfig(name, title = toTitleCase(name)) {
|
|
553
|
+
function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
|
|
550
554
|
return {
|
|
551
555
|
name,
|
|
552
556
|
title,
|
|
553
557
|
dbName: `${toSnakeCase(name)}_db`,
|
|
554
558
|
nameUpper: name.toUpperCase(),
|
|
555
|
-
nameSnake: toSnakeCase(name)
|
|
559
|
+
nameSnake: toSnakeCase(name),
|
|
560
|
+
repoName,
|
|
561
|
+
repoNameSnake: toSnakeCase(repoName)
|
|
556
562
|
};
|
|
557
563
|
}
|
|
558
564
|
async function scaffoldMonorepo(targetDir, config) {
|
|
@@ -657,27 +663,6 @@ async function installAtMonorepoRoot(monorepoRoot, installDeps) {
|
|
|
657
663
|
return false;
|
|
658
664
|
}
|
|
659
665
|
}
|
|
660
|
-
async function installAtWebappPackage(packageDir, projectType, installDeps) {
|
|
661
|
-
if (!installDeps || !packageDir || projectType !== "webapp") {
|
|
662
|
-
return projectType !== "webapp";
|
|
663
|
-
}
|
|
664
|
-
const spinner = ora(
|
|
665
|
-
`Generating package lockfile with ${PACKAGE_MANAGER}...`
|
|
666
|
-
).start();
|
|
667
|
-
try {
|
|
668
|
-
await runPackageManagerInstall(PACKAGE_MANAGER, packageDir, [
|
|
669
|
-
"install",
|
|
670
|
-
"--ignore-workspace"
|
|
671
|
-
]);
|
|
672
|
-
spinner.succeed("Generated package lockfile");
|
|
673
|
-
return true;
|
|
674
|
-
} catch {
|
|
675
|
-
spinner.warn(
|
|
676
|
-
`Failed to generate package lockfile. Run '${PACKAGE_MANAGER} install --ignore-workspace' from ${packageDir}.`
|
|
677
|
-
);
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
666
|
async function maybeAutoRunWebapp(packageDir, projectType, installSucceeded) {
|
|
682
667
|
if (!packageDir || projectType !== "webapp" || !installSucceeded) return false;
|
|
683
668
|
return autoRunWebapp(packageDir);
|
|
@@ -812,10 +797,11 @@ async function createProject(options) {
|
|
|
812
797
|
answers.directory = path7.join(monorepoContext.packageDir, answers.name);
|
|
813
798
|
}
|
|
814
799
|
}
|
|
815
|
-
const config = buildAppConfig(answers.name, answers.title);
|
|
816
800
|
const monorepoName = answers.monorepoName ?? answers.name;
|
|
817
801
|
const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
|
|
818
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);
|
|
819
805
|
const typeLabel = getProjectTypeLabel(answers.projectType);
|
|
820
806
|
if (monorepoContext.found) {
|
|
821
807
|
const monorepoRoot = monorepoContext.rootDir;
|
|
@@ -846,16 +832,10 @@ async function createProject(options) {
|
|
|
846
832
|
});
|
|
847
833
|
}
|
|
848
834
|
await warnIfMissingRootNpmrc(monorepoRoot);
|
|
849
|
-
const
|
|
835
|
+
const installSucceeded = await installAtMonorepoRoot(
|
|
850
836
|
monorepoRoot,
|
|
851
837
|
answers.installDeps
|
|
852
838
|
);
|
|
853
|
-
const packageInstallSucceeded = await installAtWebappPackage(
|
|
854
|
-
packageDir,
|
|
855
|
-
answers.projectType,
|
|
856
|
-
answers.installDeps
|
|
857
|
-
);
|
|
858
|
-
const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
|
|
859
839
|
console.log();
|
|
860
840
|
console.log(
|
|
861
841
|
chalk.green("\u2714"),
|
|
@@ -910,16 +890,10 @@ async function createProject(options) {
|
|
|
910
890
|
});
|
|
911
891
|
}
|
|
912
892
|
initGitRepo(monorepoRoot);
|
|
913
|
-
const
|
|
893
|
+
const installSucceeded = await installAtMonorepoRoot(
|
|
914
894
|
monorepoRoot,
|
|
915
895
|
answers.installDeps
|
|
916
896
|
);
|
|
917
|
-
const packageInstallSucceeded = await installAtWebappPackage(
|
|
918
|
-
packageDir,
|
|
919
|
-
answers.projectType,
|
|
920
|
-
answers.installDeps
|
|
921
|
-
);
|
|
922
|
-
const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
|
|
923
897
|
console.log();
|
|
924
898
|
console.log(
|
|
925
899
|
chalk.green("\u2714"),
|
|
@@ -968,14 +942,12 @@ function printWebappNextSteps(params) {
|
|
|
968
942
|
} = params;
|
|
969
943
|
const repoRel = shPath(monorepoRelativePath) || ".";
|
|
970
944
|
const pkgFromRoot = `packages/${answers.name}`;
|
|
971
|
-
const packageInstallStep = `${pm} install --ignore-workspace`;
|
|
972
945
|
const pnpmSteps = ["pnpm run setup", "pnpm dev"];
|
|
973
946
|
if (variant === "new") {
|
|
974
947
|
const oneLinerParts2 = [];
|
|
975
948
|
if (repoRel !== ".") oneLinerParts2.push(`cd ${repoRel}`);
|
|
976
949
|
if (!answers.installDeps) oneLinerParts2.push(`${pm} install`);
|
|
977
950
|
oneLinerParts2.push(`cd ${pkgFromRoot}`);
|
|
978
|
-
if (!answers.installDeps) oneLinerParts2.push(packageInstallStep);
|
|
979
951
|
oneLinerParts2.push(...pnpmSteps);
|
|
980
952
|
console.log(chalk.bold("Copy-paste (from your current directory):"));
|
|
981
953
|
console.log();
|
|
@@ -991,9 +963,6 @@ function printWebappNextSteps(params) {
|
|
|
991
963
|
console.log(chalk.dim(` ${step2++}.`), `${pm} install`);
|
|
992
964
|
}
|
|
993
965
|
console.log(chalk.dim(` ${step2++}.`), `cd ${pkgFromRoot}`);
|
|
994
|
-
if (!answers.installDeps) {
|
|
995
|
-
console.log(chalk.dim(` ${step2++}.`), packageInstallStep);
|
|
996
|
-
}
|
|
997
966
|
for (const cmd of pnpmSteps) {
|
|
998
967
|
console.log(chalk.dim(` ${step2++}.`), cmd);
|
|
999
968
|
}
|
|
@@ -1003,7 +972,7 @@ function printWebappNextSteps(params) {
|
|
|
1003
972
|
const oneLinerParts = [];
|
|
1004
973
|
if (!answers.installDeps) {
|
|
1005
974
|
if (repoRel !== ".") oneLinerParts.push(`cd ${repoRel}`);
|
|
1006
|
-
oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}
|
|
975
|
+
oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}`);
|
|
1007
976
|
} else if (pkgRel !== ".") {
|
|
1008
977
|
oneLinerParts.push(`cd ${pkgRel}`);
|
|
1009
978
|
}
|
|
@@ -1021,7 +990,6 @@ function printWebappNextSteps(params) {
|
|
|
1021
990
|
}
|
|
1022
991
|
console.log(chalk.dim(` ${step++}.`), `${pm} install`);
|
|
1023
992
|
console.log(chalk.dim(` ${step++}.`), `cd ${pkgFromRoot}`);
|
|
1024
|
-
console.log(chalk.dim(` ${step++}.`), packageInstallStep);
|
|
1025
993
|
} else if (pkgRel !== ".") {
|
|
1026
994
|
console.log(chalk.dim(` ${step++}.`), `cd ${pkgRel}`);
|
|
1027
995
|
}
|
|
@@ -1163,25 +1131,25 @@ program.command("status").description("Show template sync status for current app
|
|
|
1163
1131
|
"--mosaic-template-path <path>",
|
|
1164
1132
|
"Path to local mosaic repo checkout"
|
|
1165
1133
|
).action(async (options) => {
|
|
1166
|
-
const { statusCommand } = await import("./status-
|
|
1134
|
+
const { statusCommand } = await import("./status-QW5TQDYY.js");
|
|
1167
1135
|
await statusCommand(options);
|
|
1168
1136
|
});
|
|
1169
1137
|
program.command("sync").description("Generate downstream sync context (template \u2192 app)").option(
|
|
1170
1138
|
"--mosaic-template-path <path>",
|
|
1171
1139
|
"Path to local mosaic repo checkout"
|
|
1172
1140
|
).option("--to <version>", "Target template version (default: latest)").action(async (options) => {
|
|
1173
|
-
const { syncCommand } = await import("./sync-
|
|
1141
|
+
const { syncCommand } = await import("./sync-RLBZDOFB.js");
|
|
1174
1142
|
await syncCommand(options);
|
|
1175
1143
|
});
|
|
1176
1144
|
program.command("upstream").description("Generate upstream context (app \u2192 template)").option(
|
|
1177
1145
|
"--mosaic-template-path <path>",
|
|
1178
1146
|
"Path to local mosaic repo checkout"
|
|
1179
1147
|
).option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
|
|
1180
|
-
const { upstreamCommand } = await import("./upstream-
|
|
1148
|
+
const { upstreamCommand } = await import("./upstream-TQFVPMEG.js");
|
|
1181
1149
|
await upstreamCommand(options);
|
|
1182
1150
|
});
|
|
1183
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) => {
|
|
1184
|
-
const { initCommand } = await import("./init-
|
|
1152
|
+
const { initCommand } = await import("./init-EQZ2TCSJ.js");
|
|
1185
1153
|
await initCommand(options);
|
|
1186
1154
|
});
|
|
1187
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
|
@@ -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:
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: Build & Release __APP_NAME__-terraform
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- "main"
|
|
7
|
+
paths:
|
|
8
|
+
- "packages/__APP_NAME__/terraform/schema/**"
|
|
9
|
+
- ".github/workflows/__APP_NAME__-terraform-ryvn-release.yaml"
|
|
10
|
+
pull_request:
|
|
11
|
+
branches:
|
|
12
|
+
- "main"
|
|
13
|
+
paths:
|
|
14
|
+
- "packages/__APP_NAME__/terraform/schema/**"
|
|
15
|
+
- ".github/workflows/__APP_NAME__-terraform-ryvn-release.yaml"
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
SERVICE_NAME: __APP_NAME__-terraform
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build-and-release:
|
|
23
|
+
name: Build and Release
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
id-token: write
|
|
28
|
+
|
|
29
|
+
steps:
|
|
30
|
+
- name: Checkout code
|
|
31
|
+
uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 0
|
|
34
|
+
|
|
35
|
+
- name: Install Ryvn CLI
|
|
36
|
+
uses: ryvn-technologies/install-ryvn-cli@v1.0.0
|
|
37
|
+
|
|
38
|
+
- name: Generate Release Tag
|
|
39
|
+
id: generate-tag
|
|
40
|
+
env:
|
|
41
|
+
RYVN_CLIENT_ID: ${{ secrets.RYVN_CLIENT_ID }}
|
|
42
|
+
RYVN_CLIENT_SECRET: ${{ secrets.RYVN_CLIENT_SECRET }}
|
|
43
|
+
run: |
|
|
44
|
+
tag_info=$(ryvn generate-release-tag "$SERVICE_NAME" --prefix="${SERVICE_NAME}@" -o json --default-bump-minor)
|
|
45
|
+
|
|
46
|
+
version=$(echo "$tag_info" | jq -r '.version')
|
|
47
|
+
new_tag=$(echo "$tag_info" | jq -r '.tag')
|
|
48
|
+
channel=$(echo "$tag_info" | jq -r '.channel')
|
|
49
|
+
isPreview=$(echo "$tag_info" | jq -r '.isPreview')
|
|
50
|
+
|
|
51
|
+
echo "version=$version" >> $GITHUB_OUTPUT
|
|
52
|
+
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
|
|
53
|
+
echo "channel=$channel" >> $GITHUB_OUTPUT
|
|
54
|
+
echo "isPreview=$isPreview" >> $GITHUB_OUTPUT
|
|
55
|
+
|
|
56
|
+
- name: Create Ryvn Release
|
|
57
|
+
if: |
|
|
58
|
+
!contains(github.event.head_commit.message, '[skip-release]') &&
|
|
59
|
+
!contains(github.event.pull_request.title, '[skip-release]') &&
|
|
60
|
+
(steps.generate-tag.outputs.isPreview == 'true' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
|
61
|
+
env:
|
|
62
|
+
RYVN_CLIENT_ID: ${{ secrets.RYVN_CLIENT_ID }}
|
|
63
|
+
RYVN_CLIENT_SECRET: ${{ secrets.RYVN_CLIENT_SECRET }}
|
|
64
|
+
run: |
|
|
65
|
+
version="${{ steps.generate-tag.outputs.new_tag }}"
|
|
66
|
+
version="${version#"${SERVICE_NAME}@"}"
|
|
67
|
+
version="${version#@}"
|
|
68
|
+
channel="${{ steps.generate-tag.outputs.channel }}"
|
|
69
|
+
|
|
70
|
+
if [ -n "$channel" ] && [ "$channel" != "null" ]; then
|
|
71
|
+
ryvn create release "$SERVICE_NAME" "$version" --channel "$channel"
|
|
72
|
+
else
|
|
73
|
+
ryvn create release "$SERVICE_NAME" "$version"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
- name: Create GitHub Tag
|
|
77
|
+
if: |
|
|
78
|
+
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
|
|
79
|
+
!contains(github.event.head_commit.message, '[skip-release]') &&
|
|
80
|
+
!contains(github.event.pull_request.title, '[skip-release]')
|
|
81
|
+
run: |
|
|
82
|
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
83
|
+
git config --global user.name "github-actions[bot]"
|
|
84
|
+
git tag "${{ steps.generate-tag.outputs.new_tag }}"
|
|
85
|
+
git push origin "${{ steps.generate-tag.outputs.new_tag }}"
|
|
86
|
+
|
|
87
|
+
- name: Create GitHub Release
|
|
88
|
+
if: |
|
|
89
|
+
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
|
|
90
|
+
!contains(github.event.head_commit.message, '[skip-release]') &&
|
|
91
|
+
!contains(github.event.pull_request.title, '[skip-release]')
|
|
92
|
+
uses: softprops/action-gh-release@v1
|
|
93
|
+
with:
|
|
94
|
+
tag_name: ${{ steps.generate-tag.outputs.new_tag }}
|
|
95
|
+
name: ${{ steps.generate-tag.outputs.new_tag }}
|
|
96
|
+
generate_release_notes: true
|
|
97
|
+
draft: false
|
|
98
|
+
prerelease: false
|
|
@@ -8,7 +8,8 @@ Next.js 15 full-stack application scaffolded from the Mosaic webapp template via
|
|
|
8
8
|
- `pnpm build` — production build
|
|
9
9
|
- `pnpm lint` — run ESLint
|
|
10
10
|
- `pnpm test` — run Vitest tests
|
|
11
|
-
- `pnpm docker:up` / `pnpm docker:down` — start/stop PostgreSQL
|
|
11
|
+
- `pnpm docker:up` / `pnpm docker:down` — start PostgreSQL and wait for health / stop PostgreSQL
|
|
12
|
+
- `pnpm inngest:dev` — start local Inngest dev server when working on background jobs
|
|
12
13
|
- `pnpm db:generate` — generate Drizzle migrations
|
|
13
14
|
- `pnpm db:migrate` — apply migrations
|
|
14
15
|
- `pnpm db:setup-and-migrate` — create DB + migrate
|
|
@@ -17,6 +18,8 @@ Next.js 15 full-stack application scaffolded from the Mosaic webapp template via
|
|
|
17
18
|
|
|
18
19
|
**Package manager**: Always use `pnpm`, never `npm` or `yarn`.
|
|
19
20
|
|
|
21
|
+
Local development only requires Postgres by default. Run Inngest locally when a workflow needs it. Do not run a local LGTM/Langfuse stack unless you are specifically debugging telemetry; those are wired by the Ryvn environment for deploys. If local LLM calls are needed, `pnpm dev` loads shared provider keys from `~/.config/percepta/create.env` when it exists.
|
|
22
|
+
|
|
20
23
|
## Code Style
|
|
21
24
|
|
|
22
25
|
- Double quotes for strings
|
|
@@ -44,14 +47,18 @@ src/ # Application source
|
|
|
44
47
|
├── services/
|
|
45
48
|
│ ├── inngest/ # Background job definitions
|
|
46
49
|
│ ├── langfuse/ # LLM observability
|
|
50
|
+
│ ├── llm/ # LLM provider selection and call helpers
|
|
47
51
|
│ ├── logger/ # App logger setup (wraps @percepta/logger)
|
|
48
52
|
│ └── observability/ # OpenTelemetry setup
|
|
49
53
|
└── utils/ # Helpers (cn, pathEncryption, etc.)
|
|
50
54
|
|
|
51
55
|
deploy/ # Infrastructure-as-code for Ryvn deployments
|
|
52
56
|
└── ryvn/
|
|
53
|
-
├── __APP_NAME__.service.yaml # Ryvn
|
|
54
|
-
|
|
57
|
+
├── __APP_NAME__.service.yaml # Ryvn web service
|
|
58
|
+
├── __APP_NAME__-terraform.service.yaml # Ryvn schema service
|
|
59
|
+
└── environments/<env>/installations/
|
|
60
|
+
├── __APP_NAME__.env.<env>.serviceinstallation.yaml
|
|
61
|
+
└── __APP_NAME__-terraform.env.<env>.serviceinstallation.yaml
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
## @percepta Packages
|
|
@@ -184,6 +191,7 @@ Detailed how-to guides for each major stack component. Read the relevant guide w
|
|
|
184
191
|
| Guide | File | When to read |
|
|
185
192
|
|-------|------|-------------|
|
|
186
193
|
| Background Jobs (Inngest) | [agent-skills/inngest.md](agent-skills/inngest.md) | Adding async tasks, scheduled jobs, or agent workflows |
|
|
194
|
+
| LLM Calls | [agent-skills/llm.md](agent-skills/llm.md) | Adding backend model calls, streaming, or structured generation |
|
|
187
195
|
| LLM Observability (Langfuse) | [agent-skills/langfuse.md](agent-skills/langfuse.md) | App uses LLMs and needs trace/eval monitoring |
|
|
188
196
|
| Database (Drizzle) | [agent-skills/database.md](agent-skills/database.md) | Adding tables, writing migrations, querying data |
|
|
189
197
|
| Deployment (Ryvn) | [agent-skills/ryvn.md](agent-skills/ryvn.md) | Ryvn overview and Percepta environment context |
|
|
@@ -220,19 +228,23 @@ Better Auth configured in `src/lib/auth/`. Email/password credentials enabled by
|
|
|
220
228
|
|
|
221
229
|
Inngest for async task processing. Define functions in `src/services/inngest/`. Configure via `INNGEST_*` env vars.
|
|
222
230
|
|
|
231
|
+
### LLM Calls
|
|
232
|
+
|
|
233
|
+
Use `LLMService` from `src/services/llm/LLMService.ts` for backend model calls. It chooses the configured provider, enables AI SDK telemetry, and attaches provider/model metadata for Langfuse.
|
|
234
|
+
|
|
223
235
|
### Database
|
|
224
236
|
|
|
225
237
|
PostgreSQL with Drizzle ORM. Schema in `src/drizzle/schema/`, migrations in `src/drizzle/migrations/`. Connection managed by `src/services/DatabaseService.ts`.
|
|
226
238
|
|
|
227
239
|
### Observability
|
|
228
240
|
|
|
229
|
-
OpenTelemetry initialized in `src/instrumentation.ts`. Langfuse for LLM tracking in `src/services/langfuse
|
|
241
|
+
OpenTelemetry initialized in `src/instrumentation.ts`. Server traces and metrics export to the configured OTEL collector; platform logs are collected from stdout. Langfuse for LLM tracking in `src/services/langfuse/` activates when `LANGFUSE_*` env vars are configured, but only AI SDK spans are forwarded to Langfuse by default. Faro for frontend monitoring via `@percepta/next-utils/faro`.
|
|
230
242
|
|
|
231
243
|
## Deployment
|
|
232
244
|
|
|
233
|
-
To deploy this app to percepta-test, follow [agent-skills/deploy.md](agent-skills/deploy.md). The
|
|
245
|
+
To deploy this app to percepta-test, follow [agent-skills/deploy.md](agent-skills/deploy.md). The direct deploy helper treats percepta-test as an existing platform environment: it preflights the shared Postgres, Inngest, OTEL collector, and LGTM installations, then creates/updates this app's Ryvn services and installations.
|
|
234
246
|
|
|
235
|
-
The release CI/CD
|
|
247
|
+
The release CI/CD workflows are already included under `.github/workflows/`.
|
|
236
248
|
|
|
237
249
|
For Ryvn CLI operations, use the `/use-ryvn` skill.
|
|
238
250
|
|
|
@@ -6,6 +6,8 @@ RUN npm install -g pnpm
|
|
|
6
6
|
|
|
7
7
|
# Build stage:
|
|
8
8
|
FROM base AS builder
|
|
9
|
+
WORKDIR /repo
|
|
10
|
+
ENV CI=true
|
|
9
11
|
|
|
10
12
|
COPY . .
|
|
11
13
|
|
|
@@ -20,7 +22,7 @@ ENV BASE_PATH=${BASE_PATH}
|
|
|
20
22
|
|
|
21
23
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|
22
24
|
|
|
23
|
-
RUN NODE_ENV=production NODE_OPTIONS="--max-old-space-size=4096" pnpm build
|
|
25
|
+
RUN NODE_ENV=production NODE_OPTIONS="--max-old-space-size=4096" pnpm --filter ./packages/__APP_NAME__ build
|
|
24
26
|
|
|
25
27
|
# Remove .npmrc for security (contains auth token):
|
|
26
28
|
RUN rm -f .npmrc
|
|
@@ -43,21 +45,28 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|
|
43
45
|
ENV BASE_PATH=${BASE_PATH}
|
|
44
46
|
|
|
45
47
|
# Copy built app from builder stage
|
|
46
|
-
COPY --from=builder /
|
|
47
|
-
COPY --from=builder /
|
|
48
|
+
COPY --from=builder /repo/packages/__APP_NAME__/.next/standalone ./
|
|
49
|
+
COPY --from=builder /repo/packages/__APP_NAME__/.next/static ./packages/__APP_NAME__/.next/static
|
|
48
50
|
|
|
49
51
|
# Copy scripts and source files needed for start.sh and runtime
|
|
50
|
-
COPY --from=builder /
|
|
51
|
-
COPY --from=builder /
|
|
52
|
-
COPY --from=builder /
|
|
52
|
+
COPY --from=builder /repo/package.json ./package.json
|
|
53
|
+
COPY --from=builder /repo/pnpm-lock.yaml ./pnpm-lock.yaml
|
|
54
|
+
COPY --from=builder /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
55
|
+
COPY --from=builder /repo/node_modules ./node_modules
|
|
56
|
+
COPY --from=builder /repo/packages/__APP_NAME__/package.json ./packages/__APP_NAME__/package.json
|
|
57
|
+
COPY --from=builder /repo/packages/__APP_NAME__/tsconfig.json ./packages/__APP_NAME__/tsconfig.json
|
|
58
|
+
COPY --from=builder /repo/packages/__APP_NAME__/node_modules ./packages/__APP_NAME__/node_modules
|
|
59
|
+
COPY --from=builder /repo/packages/__APP_NAME__/scripts ./packages/__APP_NAME__/scripts
|
|
60
|
+
COPY --from=builder /repo/packages/__APP_NAME__/src ./packages/__APP_NAME__/src
|
|
53
61
|
|
|
54
62
|
|
|
55
63
|
# Expose the port:
|
|
56
64
|
EXPOSE 3000
|
|
57
65
|
|
|
58
66
|
# Set correct permissions and user:
|
|
59
|
-
RUN chown -R 1000:1000 /app && chmod +x /app/scripts/start.sh
|
|
67
|
+
RUN chown -R 1000:1000 /app && chmod +x /app/packages/__APP_NAME__/scripts/start.sh
|
|
60
68
|
USER 1000
|
|
69
|
+
WORKDIR /app/packages/__APP_NAME__
|
|
61
70
|
|
|
62
71
|
# Start the application:
|
|
63
72
|
CMD ["./scripts/start.sh"]
|