@aventara/cli 1.0.0-rc.0 → 1.0.0-rc.10
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/CHANGELOG.md +461 -0
- package/README.md +101 -44
- package/dist/apply/conflict.confirmer.js +5 -5
- package/dist/catalog/adapter.catalog.generated.js +1 -1
- package/dist/catalog/host-entry.interface.js +10 -0
- package/dist/catalog/host.catalog.generated.js +32 -0
- package/dist/cli.js +29 -16
- package/dist/command/command.parser.js +99 -13
- package/dist/plan/host-plan.interface.js +1 -0
- package/dist/plan/nest-host.planner.js +131 -0
- package/dist/plan/next-host.planner.js +133 -0
- package/dist/plan/project.planner.js +94 -167
- package/dist/project/project.inspector.js +56 -17
- package/dist/run/command.runner.js +37 -19
- package/dist/run/init.orchestrator.js +55 -38
- package/dist/run/new.orchestrator.js +105 -30
- package/dist/run/scaffold.committer.js +1 -1
- package/dist/run/skill.orchestrator.js +54 -0
- package/dist/skill/agent-skill.generated.js +7 -0
- package/dist/skill/agent-skill.planner.js +116 -0
- package/dist/templates/docs.links.js +2 -1
- package/dist/templates/next-server.entry.js +5 -0
- package/dist/templates/prisma7/prisma7-nest.templates.js +242 -0
- package/dist/templates/prisma7/prisma7-next.templates.js +189 -0
- package/dist/templates/prisma7/prisma7-orm.templates.js +180 -0
- package/dist/templates/prisma7/prisma7.templates.js +7 -365
- package/dist/templates/template.registry.js +2 -2
- package/dist/wizard/answer.resolver.js +5 -5
- package/dist/wizard/readline.prompter.js +3 -3
- package/dist/wizard/wizard.questions.js +11 -11
- package/package.json +3 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { spliceAppModule, wiringInstructions, } from "../apply/app-module.anchor.js";
|
|
2
|
+
import { E2E_SPEC, spliceContractTest } from "../apply/e2e-spec.anchor.js";
|
|
3
|
+
import { CORS_ORIGINS_VARIABLE, MAIN_FILE, mainCorsInstructions, SCAFFOLD_CORS_ORIGINS, spliceMainCors, } from "../apply/main-cors.anchor.js";
|
|
4
|
+
import { hostOf } from "../catalog/host-entry.interface.js";
|
|
5
|
+
import { APP_MODULE, } from "../project/project.inspector.js";
|
|
6
|
+
import { SCAFFOLD_API, } from "../templates/template.registry.js";
|
|
7
|
+
import { PREPARE_SCRIPT, } from "./host-plan.interface.js";
|
|
8
|
+
const PREPARED_SCRIPTS = [
|
|
9
|
+
"build",
|
|
10
|
+
"start",
|
|
11
|
+
"start:dev",
|
|
12
|
+
"start:debug",
|
|
13
|
+
];
|
|
14
|
+
const START_SCRIPTS = {
|
|
15
|
+
start: { from: "nest start", to: "nest start --env-file .env" },
|
|
16
|
+
"start:dev": {
|
|
17
|
+
from: "nest start --watch",
|
|
18
|
+
to: "nest start --watch --env-file .env",
|
|
19
|
+
},
|
|
20
|
+
"start:debug": {
|
|
21
|
+
from: "nest start --debug --watch",
|
|
22
|
+
to: "nest start --debug --watch --env-file .env",
|
|
23
|
+
},
|
|
24
|
+
"start:prod": {
|
|
25
|
+
from: "node dist/main",
|
|
26
|
+
to: "node --env-file-if-exists=.env dist/main",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const E2E_SCRIPTS = {
|
|
30
|
+
esm: {
|
|
31
|
+
from: "vitest run --config ./vitest.config.e2e.ts",
|
|
32
|
+
to: "node --env-file-if-exists=.env ./node_modules/vitest/vitest.mjs run --config ./vitest.config.e2e.ts",
|
|
33
|
+
},
|
|
34
|
+
cjs: {
|
|
35
|
+
from: "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./test/jest-e2e.json",
|
|
36
|
+
to: "node --env-file-if-exists=.env --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./test/jest-e2e.json",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const NEST = hostOf("nest");
|
|
40
|
+
export function frontendInitCommand(packageManager, cliVersion) {
|
|
41
|
+
const tag = /^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/.exec(cliVersion)?.[1];
|
|
42
|
+
const runner = packageManager === "pnpm" ? "pnpm dlx" : "npx";
|
|
43
|
+
return `${runner} @aventara/client${tag === undefined ? "" : `@${tag}`} init`;
|
|
44
|
+
}
|
|
45
|
+
export function planNestHost(builder, input) {
|
|
46
|
+
const { inspection, scaffold, packageManager } = input;
|
|
47
|
+
const wiring = {
|
|
48
|
+
imports: [
|
|
49
|
+
`import { AventaraModule } from '${NEST.package}';`,
|
|
50
|
+
...scaffold.appModule.imports,
|
|
51
|
+
],
|
|
52
|
+
entries: scaffold.appModule.entries,
|
|
53
|
+
};
|
|
54
|
+
const appModule = builder.read(APP_MODULE);
|
|
55
|
+
const edit = input.wiringByHand !== undefined
|
|
56
|
+
? { kind: "not-anchored", reason: input.wiringByHand }
|
|
57
|
+
: appModule === undefined
|
|
58
|
+
? {
|
|
59
|
+
kind: "not-anchored",
|
|
60
|
+
reason: `there is no ${APP_MODULE}`,
|
|
61
|
+
}
|
|
62
|
+
: spliceAppModule(appModule, wiring);
|
|
63
|
+
if (edit.kind === "edited") {
|
|
64
|
+
builder.write(APP_MODULE, appModule, edit.text);
|
|
65
|
+
const spec = builder.read(E2E_SPEC);
|
|
66
|
+
const tested = spec === undefined ? undefined : spliceContractTest(spec, SCAFFOLD_API);
|
|
67
|
+
if (tested !== undefined) {
|
|
68
|
+
builder.write(E2E_SPEC, spec, tested);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
builder.instruct(wiringInstructions(wiring));
|
|
73
|
+
builder.warn(`${APP_MODULE} was not edited: ${edit.reason}; add AventaraModule by hand, as printed below`);
|
|
74
|
+
}
|
|
75
|
+
const main = builder.read(MAIN_FILE);
|
|
76
|
+
const cors = main === undefined
|
|
77
|
+
? { kind: "not-anchored", reason: `there is no ${MAIN_FILE}` }
|
|
78
|
+
: spliceMainCors(main);
|
|
79
|
+
if (cors.kind === "edited") {
|
|
80
|
+
builder.write(MAIN_FILE, main, cors.text);
|
|
81
|
+
}
|
|
82
|
+
else if (cors.kind === "configured") {
|
|
83
|
+
builder.warn(`${MAIN_FILE} already configures CORS, so it was left as it is; a browser frontend on another origin is answered only if that configuration allows it`);
|
|
84
|
+
}
|
|
85
|
+
else if (cors.kind === "not-anchored") {
|
|
86
|
+
builder.instruct(mainCorsInstructions());
|
|
87
|
+
builder.warn(`${MAIN_FILE} was not edited: ${cors.reason}; add CORS by hand, as printed below`);
|
|
88
|
+
}
|
|
89
|
+
const corsWanted = cors.kind !== "configured";
|
|
90
|
+
const scaffoldWritesEnv = Object.keys(scaffold.env).length > 0;
|
|
91
|
+
const env = builder.read(".env");
|
|
92
|
+
const corsStep = corsWanted && !scaffoldWritesEnv && env === undefined
|
|
93
|
+
? [
|
|
94
|
+
`Let a browser frontend call the server: add ${CORS_ORIGINS_VARIABLE}="${SCAFFOLD_CORS_ORIGINS}" to .env (unset, CORS stays off)`,
|
|
95
|
+
]
|
|
96
|
+
: [];
|
|
97
|
+
const run = packageManager === "pnpm" ? "pnpm" : "npm";
|
|
98
|
+
return {
|
|
99
|
+
dependencies: { [NEST.package]: input.aventaraVersion },
|
|
100
|
+
build: "aventara-build",
|
|
101
|
+
scripts: Object.fromEntries(PREPARED_SCRIPTS.map((script) => [
|
|
102
|
+
`pre${script}`,
|
|
103
|
+
`${packageManager} run ${PREPARE_SCRIPT}`,
|
|
104
|
+
])),
|
|
105
|
+
rewrites: inspection.usesNestConfig
|
|
106
|
+
? {}
|
|
107
|
+
: {
|
|
108
|
+
...START_SCRIPTS,
|
|
109
|
+
"test:e2e": E2E_SCRIPTS[inspection.moduleKind],
|
|
110
|
+
},
|
|
111
|
+
...(corsWanted
|
|
112
|
+
? {
|
|
113
|
+
envEntry: {
|
|
114
|
+
key: CORS_ORIGINS_VARIABLE,
|
|
115
|
+
value: SCAFFOLD_CORS_ORIGINS,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
119
|
+
gitignore: [],
|
|
120
|
+
nextSteps: [
|
|
121
|
+
...(inspection.usesNestConfig
|
|
122
|
+
? [
|
|
123
|
+
"The start scripts were left as they are: @nestjs/config loads .env, so make sure it does before Prisma connects.",
|
|
124
|
+
]
|
|
125
|
+
: []),
|
|
126
|
+
...corsStep,
|
|
127
|
+
`Start the server: ${run} run start:dev`,
|
|
128
|
+
`In your frontend: ${frontendInitCommand(packageManager, input.aventaraVersion)}`,
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { hostOf } from "../catalog/host-entry.interface.js";
|
|
2
|
+
import { SERVER_IMPORT } from "../templates/next-server.entry.js";
|
|
3
|
+
import { SCAFFOLD_API } from "../templates/template.registry.js";
|
|
4
|
+
import { PREPARE_SCRIPT, } from "./host-plan.interface.js";
|
|
5
|
+
const NEXT = hostOf("next");
|
|
6
|
+
const ROUTE_FILE = `src/app${SCAFFOLD_API}/[...aventara]/route.ts`;
|
|
7
|
+
const INSTRUMENTATION_FILE = "src/instrumentation.ts";
|
|
8
|
+
const INSTRUMENTATION_FILES = [INSTRUMENTATION_FILE, "src/instrumentation.js"];
|
|
9
|
+
const SERVER_ONLY = { "server-only": "0.0.1" };
|
|
10
|
+
function routeFile() {
|
|
11
|
+
return [
|
|
12
|
+
`import { handlers } from "${SERVER_IMPORT}";`,
|
|
13
|
+
"",
|
|
14
|
+
"// Aventara's API under the configuration's `api` path. Every method is exported,",
|
|
15
|
+
"// so one an operation does not take is answered by Aventara (405 with Allow).",
|
|
16
|
+
"export const { GET, POST, PUT, PATCH, DELETE } = handlers;",
|
|
17
|
+
"",
|
|
18
|
+
].join("\n");
|
|
19
|
+
}
|
|
20
|
+
const REGISTER_BODY = [
|
|
21
|
+
' if (process.env.NEXT_RUNTIME === "nodejs") {',
|
|
22
|
+
` const { ready } = await import("${SERVER_IMPORT}");`,
|
|
23
|
+
" await ready();",
|
|
24
|
+
" }",
|
|
25
|
+
];
|
|
26
|
+
function instrumentationFile() {
|
|
27
|
+
return [
|
|
28
|
+
"/**",
|
|
29
|
+
" * Next.js calls register() once, when the server starts. On the Node.js runtime",
|
|
30
|
+
" * it builds Aventara's framework, so a bad configuration stops `next start` and",
|
|
31
|
+
" * the first request finds it ready.",
|
|
32
|
+
" */",
|
|
33
|
+
"export async function register() {",
|
|
34
|
+
...REGISTER_BODY,
|
|
35
|
+
"}",
|
|
36
|
+
"",
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
const NEXT_CONFIG_FILES = [
|
|
40
|
+
"next.config.ts",
|
|
41
|
+
"next.config.mts",
|
|
42
|
+
"next.config.mjs",
|
|
43
|
+
"next.config.js",
|
|
44
|
+
"next.config.cjs",
|
|
45
|
+
];
|
|
46
|
+
const CONFIG_OPENING = /^const nextConfig(?:: NextConfig)? = \{\n/m;
|
|
47
|
+
function externalLines(packages, indent) {
|
|
48
|
+
return [
|
|
49
|
+
`${indent}// Loaded by Node.js once per server process, not bundled into each layer:`,
|
|
50
|
+
`${indent}// one Aventara core and one Prisma client, shared by every route and page.`,
|
|
51
|
+
`${indent}serverExternalPackages: [${packages.map((name) => JSON.stringify(name)).join(", ")}],`,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function newNextConfig(packages) {
|
|
55
|
+
return [
|
|
56
|
+
'import type { NextConfig } from "next";',
|
|
57
|
+
"",
|
|
58
|
+
"const nextConfig: NextConfig = {",
|
|
59
|
+
...externalLines(packages, " "),
|
|
60
|
+
"};",
|
|
61
|
+
"",
|
|
62
|
+
"export default nextConfig;",
|
|
63
|
+
"",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
function withServerExternals(text, packages) {
|
|
67
|
+
if (text.includes("serverExternalPackages")) {
|
|
68
|
+
return packages.every((name) => text.includes(JSON.stringify(name)))
|
|
69
|
+
? text
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
const opening = CONFIG_OPENING.exec(text);
|
|
73
|
+
if (opening === null) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const at = opening.index + opening[0].length;
|
|
77
|
+
const indent = /^[ \t]+/.exec(text.slice(at))?.[0] ?? " ";
|
|
78
|
+
return `${text.slice(0, at)}${externalLines(packages, indent).join("\n")}\n${text.slice(at)}`;
|
|
79
|
+
}
|
|
80
|
+
export function planNextHost(builder, input) {
|
|
81
|
+
builder.file(ROUTE_FILE, routeFile());
|
|
82
|
+
const existing = INSTRUMENTATION_FILES.find((file) => builder.read(file) !== undefined);
|
|
83
|
+
const wanted = instrumentationFile();
|
|
84
|
+
if (existing === undefined) {
|
|
85
|
+
builder.file(INSTRUMENTATION_FILE, wanted);
|
|
86
|
+
}
|
|
87
|
+
else if (builder.read(existing) !== wanted) {
|
|
88
|
+
builder.instruct([
|
|
89
|
+
`Add Aventara's startup to register() in ${existing}:`,
|
|
90
|
+
"",
|
|
91
|
+
...REGISTER_BODY,
|
|
92
|
+
"",
|
|
93
|
+
].join("\n"));
|
|
94
|
+
builder.warn(`${existing} is yours, so it was left as it is; add the lines printed below to its register()`);
|
|
95
|
+
}
|
|
96
|
+
const configFile = NEXT_CONFIG_FILES.find((file) => builder.read(file) !== undefined);
|
|
97
|
+
if (configFile === undefined) {
|
|
98
|
+
builder.file(NEXT_CONFIG_FILES[0], newNextConfig(input.externalPackages));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const before = builder.read(configFile);
|
|
102
|
+
const after = withServerExternals(before, input.externalPackages);
|
|
103
|
+
if (after === undefined) {
|
|
104
|
+
builder.instruct([
|
|
105
|
+
`Add Aventara's server packages to the configuration object in ${configFile} (merge them into serverExternalPackages if it has one):`,
|
|
106
|
+
"",
|
|
107
|
+
...externalLines(input.externalPackages, " "),
|
|
108
|
+
"",
|
|
109
|
+
].join("\n"));
|
|
110
|
+
builder.warn(`${configFile} is yours, so it was left as it is; add the serverExternalPackages line printed below to it`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
builder.write(configFile, before, after);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const { layout } = NEXT;
|
|
117
|
+
const run = input.packageManager === "pnpm" ? "pnpm" : "npm";
|
|
118
|
+
return {
|
|
119
|
+
dependencies: {
|
|
120
|
+
[NEXT.package]: input.aventaraVersion,
|
|
121
|
+
...SERVER_ONLY,
|
|
122
|
+
},
|
|
123
|
+
build: "aventara-build --client",
|
|
124
|
+
scripts: { prebuild: `${input.packageManager} run ${PREPARE_SCRIPT}` },
|
|
125
|
+
rewrites: {},
|
|
126
|
+
gitignore: [
|
|
127
|
+
`/${layout.generated}/`,
|
|
128
|
+
...(layout.client === undefined ? [] : [`/${layout.client}`]),
|
|
129
|
+
...(layout.universal === undefined ? [] : [`/${layout.universal}`]),
|
|
130
|
+
],
|
|
131
|
+
nextSteps: [`Start the app: ${run} run dev`],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -1,43 +1,10 @@
|
|
|
1
|
-
import { spliceAppModule, wiringInstructions, } from "../apply/app-module.anchor.js";
|
|
2
|
-
import { E2E_SPEC, spliceContractTest } from "../apply/e2e-spec.anchor.js";
|
|
3
|
-
import { CORS_ORIGINS_VARIABLE, MAIN_FILE, mainCorsInstructions, SCAFFOLD_CORS_ORIGINS, spliceMainCors, } from "../apply/main-cors.anchor.js";
|
|
4
1
|
import { appendEnvEntryIfAbsent, mergeEnvFile, mergeGitignore, mergePackageManifest, mergePnpmWorkspace, } from "../apply/manifest.merger.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"build",
|
|
10
|
-
"start",
|
|
11
|
-
"start:dev",
|
|
12
|
-
"start:debug",
|
|
13
|
-
];
|
|
14
|
-
const START_SCRIPTS = {
|
|
15
|
-
start: { from: "nest start", to: "nest start --env-file .env" },
|
|
16
|
-
"start:dev": {
|
|
17
|
-
from: "nest start --watch",
|
|
18
|
-
to: "nest start --watch --env-file .env",
|
|
19
|
-
},
|
|
20
|
-
"start:debug": {
|
|
21
|
-
from: "nest start --debug --watch",
|
|
22
|
-
to: "nest start --debug --watch --env-file .env",
|
|
23
|
-
},
|
|
24
|
-
"start:prod": {
|
|
25
|
-
from: "node dist/main",
|
|
26
|
-
to: "node --env-file-if-exists=.env dist/main",
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
const E2E_SCRIPTS = {
|
|
30
|
-
esm: {
|
|
31
|
-
from: "vitest run --config ./vitest.config.e2e.ts",
|
|
32
|
-
to: "node --env-file-if-exists=.env ./node_modules/vitest/vitest.mjs run --config ./vitest.config.e2e.ts",
|
|
33
|
-
},
|
|
34
|
-
cjs: {
|
|
35
|
-
from: "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./test/jest-e2e.json",
|
|
36
|
-
to: "node --env-file-if-exists=.env --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./test/jest-e2e.json",
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
const NEST_HOST = "@aventara/nest";
|
|
2
|
+
import { planAgentSkill, SKILL_DIRECTORY, } from "../skill/agent-skill.planner.js";
|
|
3
|
+
import { PREPARE_SCRIPT, } from "./host-plan.interface.js";
|
|
4
|
+
import { planNestHost } from "./nest-host.planner.js";
|
|
5
|
+
import { planNextHost } from "./next-host.planner.js";
|
|
40
6
|
const CORE = "@aventara/core";
|
|
7
|
+
const BUILD = "@aventara/build";
|
|
41
8
|
function writeOf(path, before, after) {
|
|
42
9
|
return before === after
|
|
43
10
|
? []
|
|
@@ -49,172 +16,132 @@ function writeOf(path, before, after) {
|
|
|
49
16
|
},
|
|
50
17
|
];
|
|
51
18
|
}
|
|
52
|
-
export function frontendInitCommand(packageManager, cliVersion) {
|
|
53
|
-
const tag = /^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/.exec(cliVersion)?.[1];
|
|
54
|
-
const runner = packageManager === "pnpm" ? "pnpm dlx" : "npx";
|
|
55
|
-
return `${runner} @aventara/client${tag === undefined ? "" : `@${tag}`} init`;
|
|
56
|
-
}
|
|
57
19
|
export function planInit(input) {
|
|
58
|
-
const { inspection,
|
|
20
|
+
const { inspection, hosted, packageManager } = input;
|
|
21
|
+
const { scaffold } = hosted;
|
|
59
22
|
const conflicts = new Set();
|
|
60
23
|
const keeping = [];
|
|
61
24
|
const replacing = [];
|
|
62
25
|
const warnings = [...(input.warnings ?? [])];
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
const instructions = [];
|
|
27
|
+
const builder = {
|
|
28
|
+
read: input.read,
|
|
29
|
+
file: (path, content) => {
|
|
30
|
+
const before = input.read(path);
|
|
31
|
+
if (before === undefined) {
|
|
32
|
+
keeping.push({ path, content, change: "create" });
|
|
33
|
+
replacing.push({ path, content, change: "create" });
|
|
34
|
+
}
|
|
35
|
+
else if (before !== content) {
|
|
36
|
+
conflicts.add(path);
|
|
37
|
+
replacing.push({ path, content, change: "replace" });
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
write: (path, before, after) => {
|
|
41
|
+
keeping.push(...writeOf(path, before, after));
|
|
42
|
+
replacing.push(...writeOf(path, before, after));
|
|
43
|
+
},
|
|
44
|
+
both: (path, before, merge) => {
|
|
45
|
+
const kept = merge(false);
|
|
46
|
+
const replaced = merge(true);
|
|
47
|
+
for (const conflict of replaced.conflicts) {
|
|
48
|
+
conflicts.add(conflict);
|
|
49
|
+
}
|
|
50
|
+
keeping.push(...writeOf(path, before, kept.text));
|
|
51
|
+
replacing.push(...writeOf(path, before, replaced.text));
|
|
52
|
+
},
|
|
53
|
+
warn: (warning) => {
|
|
54
|
+
warnings.push(warning);
|
|
55
|
+
},
|
|
56
|
+
instruct: (text) => {
|
|
57
|
+
instructions.push(text);
|
|
58
|
+
},
|
|
71
59
|
};
|
|
72
60
|
for (const file of scaffold.files) {
|
|
73
|
-
|
|
74
|
-
if (before === undefined) {
|
|
75
|
-
keeping.push({
|
|
76
|
-
path: file.path,
|
|
77
|
-
content: file.content,
|
|
78
|
-
change: "create",
|
|
79
|
-
});
|
|
80
|
-
replacing.push({
|
|
81
|
-
path: file.path,
|
|
82
|
-
content: file.content,
|
|
83
|
-
change: "create",
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
else if (before !== file.content) {
|
|
87
|
-
conflicts.add(file.path);
|
|
88
|
-
replacing.push({
|
|
89
|
-
path: file.path,
|
|
90
|
-
content: file.content,
|
|
91
|
-
change: "replace",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const wiring = {
|
|
96
|
-
imports: [
|
|
97
|
-
`import { AventaraModule } from '${NEST_HOST}';`,
|
|
98
|
-
...scaffold.appModule.imports,
|
|
99
|
-
],
|
|
100
|
-
entries: scaffold.appModule.entries,
|
|
101
|
-
};
|
|
102
|
-
const appModule = input.read(APP_MODULE);
|
|
103
|
-
const edit = input.wiringByHand !== undefined
|
|
104
|
-
? { kind: "not-anchored", reason: input.wiringByHand }
|
|
105
|
-
: appModule === undefined
|
|
106
|
-
? {
|
|
107
|
-
kind: "not-anchored",
|
|
108
|
-
reason: `there is no ${APP_MODULE}`,
|
|
109
|
-
}
|
|
110
|
-
: spliceAppModule(appModule, wiring);
|
|
111
|
-
let instructions;
|
|
112
|
-
if (edit.kind === "edited") {
|
|
113
|
-
keeping.push(...writeOf(APP_MODULE, appModule, edit.text));
|
|
114
|
-
replacing.push(...writeOf(APP_MODULE, appModule, edit.text));
|
|
115
|
-
const spec = input.read(E2E_SPEC);
|
|
116
|
-
const tested = spec === undefined
|
|
117
|
-
? undefined
|
|
118
|
-
: spliceContractTest(spec, SCAFFOLD_ENTRYPOINT);
|
|
119
|
-
if (tested !== undefined) {
|
|
120
|
-
keeping.push(...writeOf(E2E_SPEC, spec, tested));
|
|
121
|
-
replacing.push(...writeOf(E2E_SPEC, spec, tested));
|
|
122
|
-
}
|
|
61
|
+
builder.file(file.path, file.content);
|
|
123
62
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
instructions = [instructions, mainCorsInstructions()]
|
|
141
|
-
.filter((text) => text !== undefined)
|
|
142
|
-
.join("\n");
|
|
143
|
-
warnings.push(`${MAIN_FILE} was not edited: ${cors.reason}; add CORS by hand, as printed below`);
|
|
144
|
-
}
|
|
145
|
-
const corsWanted = cors.kind !== "configured";
|
|
63
|
+
const host = hosted.host === "nest"
|
|
64
|
+
? planNestHost(builder, {
|
|
65
|
+
inspection,
|
|
66
|
+
scaffold: hosted.scaffold,
|
|
67
|
+
packageManager,
|
|
68
|
+
aventaraVersion: input.aventaraVersion,
|
|
69
|
+
...(hosted.wiringByHand === undefined
|
|
70
|
+
? {}
|
|
71
|
+
: { wiringByHand: hosted.wiringByHand }),
|
|
72
|
+
})
|
|
73
|
+
: planNextHost(builder, {
|
|
74
|
+
inspection,
|
|
75
|
+
packageManager,
|
|
76
|
+
aventaraVersion: input.aventaraVersion,
|
|
77
|
+
externalPackages: [CORE, input.entry.adapterPackage],
|
|
78
|
+
});
|
|
146
79
|
const scaffoldWritesEnv = Object.keys(scaffold.env).length > 0;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
both("package.json", inspection.manifestText, (replace) => mergePackageManifest(inspection.manifestText, {
|
|
159
|
-
dependencies: { ...versions, ...scaffold.dependencies },
|
|
160
|
-
devDependencies: scaffold.devDependencies,
|
|
80
|
+
builder.both("package.json", inspection.manifestText, (replace) => mergePackageManifest(inspection.manifestText, {
|
|
81
|
+
dependencies: {
|
|
82
|
+
[CORE]: input.aventaraVersion,
|
|
83
|
+
...host.dependencies,
|
|
84
|
+
[input.entry.adapterPackage]: input.aventaraVersion,
|
|
85
|
+
...scaffold.dependencies,
|
|
86
|
+
},
|
|
87
|
+
devDependencies: {
|
|
88
|
+
[BUILD]: input.aventaraVersion,
|
|
89
|
+
...scaffold.devDependencies,
|
|
90
|
+
},
|
|
161
91
|
scripts: {
|
|
162
|
-
[PREPARE_SCRIPT]: scaffold.prepare
|
|
92
|
+
[PREPARE_SCRIPT]: `${scaffold.prepare} && ${host.build}`,
|
|
163
93
|
postinstall: `${packageManager} run ${PREPARE_SCRIPT}`,
|
|
164
|
-
...
|
|
165
|
-
`pre${script}`,
|
|
166
|
-
`${packageManager} run ${PREPARE_SCRIPT}`,
|
|
167
|
-
])),
|
|
94
|
+
...host.scripts,
|
|
168
95
|
},
|
|
169
|
-
rewrites:
|
|
170
|
-
? {}
|
|
171
|
-
: {
|
|
172
|
-
...START_SCRIPTS,
|
|
173
|
-
"test:e2e": E2E_SCRIPTS[inspection.moduleKind],
|
|
174
|
-
},
|
|
96
|
+
rewrites: host.rewrites,
|
|
175
97
|
}, replace));
|
|
176
|
-
|
|
177
|
-
|
|
98
|
+
const env = input.read(".env");
|
|
99
|
+
const { envEntry } = host;
|
|
100
|
+
if (scaffoldWritesEnv || (envEntry !== undefined && env !== undefined)) {
|
|
101
|
+
builder.both(".env", env, (replace) => {
|
|
178
102
|
const merged = scaffoldWritesEnv
|
|
179
103
|
? mergeEnvFile(env, scaffold.env, replace)
|
|
180
104
|
: { text: env ?? "", conflicts: [] };
|
|
181
|
-
return
|
|
182
|
-
?
|
|
183
|
-
|
|
105
|
+
return envEntry === undefined
|
|
106
|
+
? merged
|
|
107
|
+
: {
|
|
108
|
+
text: appendEnvEntryIfAbsent(merged.text, envEntry.key, envEntry.value),
|
|
184
109
|
conflicts: merged.conflicts,
|
|
185
|
-
}
|
|
186
|
-
: merged;
|
|
110
|
+
};
|
|
187
111
|
});
|
|
188
112
|
}
|
|
189
113
|
const gitignore = input.read(".gitignore");
|
|
190
|
-
both(".gitignore", gitignore, () => mergeGitignore(gitignore, [
|
|
114
|
+
builder.both(".gitignore", gitignore, () => mergeGitignore(gitignore, [
|
|
191
115
|
...scaffold.gitignore,
|
|
116
|
+
...host.gitignore,
|
|
192
117
|
...(scaffoldWritesEnv ? [".env"] : []),
|
|
193
118
|
]));
|
|
194
119
|
if (packageManager === "pnpm" && scaffold.nativeBuilds.length > 0) {
|
|
195
120
|
const workspace = input.read("pnpm-workspace.yaml");
|
|
196
|
-
both("pnpm-workspace.yaml", workspace, () => mergePnpmWorkspace(workspace, scaffold.nativeBuilds));
|
|
121
|
+
builder.both("pnpm-workspace.yaml", workspace, () => mergePnpmWorkspace(workspace, scaffold.nativeBuilds));
|
|
197
122
|
}
|
|
123
|
+
const skill = planAgentSkill({
|
|
124
|
+
read: input.read,
|
|
125
|
+
coreVersion: input.aventaraVersion,
|
|
126
|
+
force: false,
|
|
127
|
+
});
|
|
128
|
+
keeping.push(...skill.writes);
|
|
129
|
+
replacing.push(...skill.writes);
|
|
130
|
+
warnings.push(...skill.warnings);
|
|
198
131
|
const run = packageManager === "pnpm" ? "pnpm" : "npm";
|
|
199
132
|
return {
|
|
200
133
|
keeping,
|
|
201
134
|
replacing,
|
|
202
135
|
conflicts: [...conflicts],
|
|
203
136
|
warnings,
|
|
204
|
-
instructions,
|
|
137
|
+
instructions: instructions.length === 0 ? undefined : instructions.join("\n"),
|
|
205
138
|
nextSteps: [
|
|
206
139
|
...(input.skipInstall
|
|
207
140
|
? [`Install, which also runs ${PREPARE_SCRIPT}: ${run} install`]
|
|
208
141
|
: []),
|
|
209
142
|
...scaffold.nextSteps,
|
|
210
|
-
...
|
|
211
|
-
|
|
212
|
-
"The start scripts were left as they are: @nestjs/config loads .env, so make sure it does before Prisma connects.",
|
|
213
|
-
]
|
|
214
|
-
: []),
|
|
215
|
-
...corsStep,
|
|
216
|
-
`Start the server: ${run} run start:dev`,
|
|
217
|
-
`In your frontend: ${frontendInitCommand(packageManager, input.aventaraVersion)}`,
|
|
143
|
+
...host.nextSteps,
|
|
144
|
+
`Call the API with curl: see "Calling the API over HTTP" in ${SKILL_DIRECTORY}/SKILL.md`,
|
|
218
145
|
],
|
|
219
146
|
};
|
|
220
147
|
}
|