@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.
@@ -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 { APP_MODULE, } from "../project/project.inspector.js";
6
- import { SCAFFOLD_ENTRYPOINT, } from "../templates/template.registry.js";
7
- export const PREPARE_SCRIPT = "aventara:prepare";
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_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, scaffold, packageManager } = input;
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 both = (path, before, merge) => {
64
- const kept = merge(false);
65
- const replaced = merge(true);
66
- for (const conflict of replaced.conflicts) {
67
- conflicts.add(conflict);
68
- }
69
- keeping.push(...writeOf(path, before, kept.text));
70
- replacing.push(...writeOf(path, before, replaced.text));
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
- const before = input.read(file.path);
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
- else {
125
- instructions = wiringInstructions(wiring);
126
- warnings.push(`${APP_MODULE} was not edited: ${edit.reason}; add AventaraModule by hand, as printed below`);
127
- }
128
- const main = input.read(MAIN_FILE);
129
- const cors = main === undefined
130
- ? { kind: "not-anchored", reason: `there is no ${MAIN_FILE}` }
131
- : spliceMainCors(main);
132
- if (cors.kind === "edited") {
133
- keeping.push(...writeOf(MAIN_FILE, main, cors.text));
134
- replacing.push(...writeOf(MAIN_FILE, main, cors.text));
135
- }
136
- else if (cors.kind === "configured") {
137
- warnings.push(`${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`);
138
- }
139
- else if (cors.kind === "not-anchored") {
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
- const env = input.read(".env");
148
- const corsStep = corsWanted && !scaffoldWritesEnv && env === undefined
149
- ? [
150
- `Let a browser frontend call the server: add ${CORS_ORIGINS_VARIABLE}="${SCAFFOLD_CORS_ORIGINS}" to .env (unset, CORS stays off)`,
151
- ]
152
- : [];
153
- const versions = {
154
- [CORE]: input.aventaraVersion,
155
- [NEST_HOST]: input.aventaraVersion,
156
- [input.entry.adapterPackage]: input.aventaraVersion,
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
- ...Object.fromEntries(PREPARED_SCRIPTS.map((script) => [
165
- `pre${script}`,
166
- `${packageManager} run ${PREPARE_SCRIPT}`,
167
- ])),
94
+ ...host.scripts,
168
95
  },
169
- rewrites: inspection.usesNestConfig
170
- ? {}
171
- : {
172
- ...START_SCRIPTS,
173
- "test:e2e": E2E_SCRIPTS[inspection.moduleKind],
174
- },
96
+ rewrites: host.rewrites,
175
97
  }, replace));
176
- if (scaffoldWritesEnv || (corsWanted && env !== undefined)) {
177
- both(".env", env, (replace) => {
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 corsWanted
182
- ? {
183
- text: appendEnvEntryIfAbsent(merged.text, CORS_ORIGINS_VARIABLE, SCAFFOLD_CORS_ORIGINS),
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
- ...(inspection.usesNestConfig
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
  }