@aventara/cli 1.0.0-rc.1 → 1.0.0-rc.11
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 +261 -1
- package/README.md +55 -6
- package/dist/apply/conflict.confirmer.js +1 -1
- 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 +15 -2
- package/dist/command/command.parser.js +91 -5
- 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 -165
- package/dist/project/project.inspector.js +53 -14
- package/dist/run/command.runner.js +37 -19
- package/dist/run/init.orchestrator.js +47 -30
- package/dist/run/new.orchestrator.js +102 -27
- package/dist/run/scaffold.committer.js +1 -1
- package/dist/run/skill.orchestrator.js +54 -0
- package/dist/skill/agent-skill.generated.js +8 -0
- package/dist/skill/agent-skill.planner.js +116 -0
- package/dist/templates/docs.links.js +1 -1
- package/dist/templates/next-server.entry.js +5 -0
- package/dist/templates/prisma7/prisma7-nest.templates.js +241 -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 -386
- package/dist/templates/template.registry.js +1 -1
- package/dist/wizard/wizard.questions.js +2 -2
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_HOST, HOSTS } from "../catalog/host-entry.interface.js";
|
|
1
2
|
import { INIT_FLAG_QUESTIONS, NEW_QUESTIONS, ormAliasesSentence, } from "../wizard/wizard.questions.js";
|
|
2
3
|
export class AvCliCommandError extends Error {
|
|
3
4
|
name = "AvCliCommandError";
|
|
@@ -34,6 +35,23 @@ const QUESTIONS = {
|
|
|
34
35
|
init: INIT_FLAG_QUESTIONS,
|
|
35
36
|
};
|
|
36
37
|
const HELP = "run `aventara --help` for usage";
|
|
38
|
+
const HOST_FLAG = "--host";
|
|
39
|
+
function hostAliasesSentence() {
|
|
40
|
+
const hosts = HOSTS.map((host) => `${host.alias} (${host.label}, ${host.package})`);
|
|
41
|
+
return `--host takes ${hosts.length <= 1
|
|
42
|
+
? hosts.join("")
|
|
43
|
+
: `${hosts.slice(0, -1).join(", ")} or ${hosts[hosts.length - 1]}`}`;
|
|
44
|
+
}
|
|
45
|
+
const NEW_ONLY_ROWS = [
|
|
46
|
+
[
|
|
47
|
+
`${HOST_FLAG} <${HOSTS.map((host) => host.alias).join("|")}>`,
|
|
48
|
+
`the framework to build on; ${DEFAULT_HOST} when omitted`,
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"-- <args>",
|
|
52
|
+
"passed as they are to the project generator (create-next-app, with --host next)",
|
|
53
|
+
],
|
|
54
|
+
];
|
|
37
55
|
function flagUsage(question, catalog) {
|
|
38
56
|
const accepted = question.accepts?.(catalog);
|
|
39
57
|
return `${question.flag} ${accepted === undefined
|
|
@@ -52,6 +70,7 @@ function usageLines(command, catalog) {
|
|
|
52
70
|
`${option.alias === undefined ? "" : `${option.alias}, `}${option.flag}`,
|
|
53
71
|
option.summary,
|
|
54
72
|
]),
|
|
73
|
+
...(command === "new" ? NEW_ONLY_ROWS : []),
|
|
55
74
|
];
|
|
56
75
|
const width = Math.max(...rows.map(([left]) => left.length));
|
|
57
76
|
return rows
|
|
@@ -59,8 +78,8 @@ function usageLines(command, catalog) {
|
|
|
59
78
|
.join("\n");
|
|
60
79
|
}
|
|
61
80
|
const SUMMARY = {
|
|
62
|
-
new: "Create a
|
|
63
|
-
init:
|
|
81
|
+
new: "Create a project with Aventara (nest new or create-next-app, then init).",
|
|
82
|
+
init: `Add Aventara to the ${HOSTS.map((host) => host.label).join(" or ")} project in this directory.`,
|
|
64
83
|
};
|
|
65
84
|
const SYNOPSIS = {
|
|
66
85
|
new: "aventara new <name> [options]",
|
|
@@ -72,7 +91,18 @@ On a terminal every unanswered question is asked; anywhere else, pass its flag
|
|
|
72
91
|
or --yes, or the run stops before writing anything.
|
|
73
92
|
`;
|
|
74
93
|
}
|
|
94
|
+
const SKILL_SUMMARY = "Write or refresh the agent skill (.claude/skills/aventara/) and the Aventara section of AGENTS.md, for the installed @aventara/core.";
|
|
95
|
+
const SKILL_USAGE = `Usage: aventara skill [options]
|
|
96
|
+
|
|
97
|
+
${SKILL_SUMMARY}
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--force also replace skill files you changed (AGENTS.md outside the Aventara section is never touched)
|
|
101
|
+
`;
|
|
75
102
|
export function commandUsage(command, catalog) {
|
|
103
|
+
if (command === "skill") {
|
|
104
|
+
return SKILL_USAGE;
|
|
105
|
+
}
|
|
76
106
|
return `Usage: ${SYNOPSIS[command]}
|
|
77
107
|
|
|
78
108
|
${SUMMARY[command]}
|
|
@@ -83,7 +113,7 @@ ${usageLines(command, catalog)}
|
|
|
83
113
|
${asked(catalog)}`;
|
|
84
114
|
}
|
|
85
115
|
export function usage(catalog) {
|
|
86
|
-
return `aventara — scaffold an Aventara server on
|
|
116
|
+
return `aventara — scaffold an Aventara server on ${HOSTS.map((host) => host.label).join(" or ")}.
|
|
87
117
|
|
|
88
118
|
Usage:
|
|
89
119
|
${SYNOPSIS.new} ${SUMMARY.new}
|
|
@@ -92,6 +122,8 @@ ${usageLines("new", catalog)}
|
|
|
92
122
|
${SYNOPSIS.init} ${SUMMARY.init}
|
|
93
123
|
${usageLines("init", catalog)}
|
|
94
124
|
|
|
125
|
+
aventara skill [--force] ${SKILL_SUMMARY}
|
|
126
|
+
|
|
95
127
|
aventara --help Print this and exit 0.
|
|
96
128
|
aventara <command> --help Print that command's usage and exit 0.
|
|
97
129
|
aventara --version, -v Print this CLI's version and exit 0.
|
|
@@ -101,7 +133,7 @@ ${asked(catalog)}`;
|
|
|
101
133
|
export function parseCliCommand(argv) {
|
|
102
134
|
const [command, ...rest] = argv;
|
|
103
135
|
if (command === undefined) {
|
|
104
|
-
throw new AvCliCommandError(`a command is required: new or
|
|
136
|
+
throw new AvCliCommandError(`a command is required: new, init or skill; ${HELP}`);
|
|
105
137
|
}
|
|
106
138
|
if (command === "--help" || command === "-h") {
|
|
107
139
|
return { command: "help" };
|
|
@@ -109,8 +141,11 @@ export function parseCliCommand(argv) {
|
|
|
109
141
|
if (command === "--version" || command === "-v") {
|
|
110
142
|
return { command: "version" };
|
|
111
143
|
}
|
|
144
|
+
if (command === "skill") {
|
|
145
|
+
return parseSkillCommand(rest);
|
|
146
|
+
}
|
|
112
147
|
if (command !== "new" && command !== "init") {
|
|
113
|
-
throw new AvCliCommandError(`unknown command ${JSON.stringify(command)}: the commands are new and
|
|
148
|
+
throw new AvCliCommandError(`unknown command ${JSON.stringify(command)}: the commands are new, init and skill; ${HELP}`);
|
|
114
149
|
}
|
|
115
150
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
116
151
|
return { command: "help", topic: command };
|
|
@@ -118,10 +153,19 @@ export function parseCliCommand(argv) {
|
|
|
118
153
|
const questions = QUESTIONS[command];
|
|
119
154
|
const given = {};
|
|
120
155
|
const options = {};
|
|
156
|
+
let host;
|
|
157
|
+
let passthrough;
|
|
121
158
|
const seen = new Set();
|
|
122
159
|
const positional = questions.find((question) => !question.flag.startsWith("--"));
|
|
123
160
|
for (let index = 0; index < rest.length; index += 1) {
|
|
124
161
|
const argument = rest[index];
|
|
162
|
+
if (argument === "--") {
|
|
163
|
+
if (command !== "new") {
|
|
164
|
+
throw new AvCliCommandError(`aventara ${command} passes nothing on: arguments after -- go to aventara new's project generator; ${HELP}`);
|
|
165
|
+
}
|
|
166
|
+
passthrough = rest.slice(index + 1);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
125
169
|
if (!argument.startsWith("-")) {
|
|
126
170
|
if (positional === undefined || given[positional.id] !== undefined) {
|
|
127
171
|
throw new AvCliCommandError(`unexpected argument ${JSON.stringify(argument)}: aventara ${command} takes ${positional === undefined ? "no" : "one"} positional argument; ${HELP}`);
|
|
@@ -132,6 +176,28 @@ export function parseCliCommand(argv) {
|
|
|
132
176
|
const equals = argument.indexOf("=");
|
|
133
177
|
const flag = equals === -1 ? argument : argument.slice(0, equals);
|
|
134
178
|
const inline = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
179
|
+
if (flag === HOST_FLAG) {
|
|
180
|
+
if (command !== "new") {
|
|
181
|
+
throw new AvCliCommandError(`${HOST_FLAG} is a flag of aventara new, not aventara ${command}: init reads the host from package.json; ${HELP}`);
|
|
182
|
+
}
|
|
183
|
+
if (host !== undefined) {
|
|
184
|
+
throw new AvCliCommandError(`${HOST_FLAG} was given twice; ${HELP}`);
|
|
185
|
+
}
|
|
186
|
+
const value = inline ?? rest[index + 1];
|
|
187
|
+
if (value === undefined ||
|
|
188
|
+
(inline === undefined && value.startsWith("-"))) {
|
|
189
|
+
throw new AvCliCommandError(`${HOST_FLAG} takes a value; ${HELP}`);
|
|
190
|
+
}
|
|
191
|
+
if (inline === undefined) {
|
|
192
|
+
index += 1;
|
|
193
|
+
}
|
|
194
|
+
const alias = value.trim().toLowerCase();
|
|
195
|
+
if (!HOSTS.some((candidate) => candidate.alias === alias)) {
|
|
196
|
+
throw new AvCliCommandError(`there is no Aventara host for ${JSON.stringify(value)}; ${hostAliasesSentence()}; ${HELP}`);
|
|
197
|
+
}
|
|
198
|
+
host = alias;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
135
201
|
const question = questions.find((candidate) => candidate.flag === flag);
|
|
136
202
|
const option = OPTIONS.find((candidate) => candidate.flag === flag || candidate.alias === flag);
|
|
137
203
|
const canonical = question?.flag ?? option?.flag ?? flag;
|
|
@@ -172,5 +238,25 @@ export function parseCliCommand(argv) {
|
|
|
172
238
|
skipGit: options.skipGit === true,
|
|
173
239
|
yes: options.yes === true,
|
|
174
240
|
...(options.verbose === true ? { verbose: true } : {}),
|
|
241
|
+
...(host === undefined ? {} : { host }),
|
|
242
|
+
...(passthrough === undefined ? {} : { passthrough }),
|
|
175
243
|
};
|
|
176
244
|
}
|
|
245
|
+
function parseSkillCommand(rest) {
|
|
246
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
247
|
+
return { command: "help", topic: "skill" };
|
|
248
|
+
}
|
|
249
|
+
let force = false;
|
|
250
|
+
for (const argument of rest) {
|
|
251
|
+
if (argument !== "--force") {
|
|
252
|
+
throw new AvCliCommandError(argument.startsWith("-")
|
|
253
|
+
? `unknown flag ${argument.split("=")[0]}; ${HELP}`
|
|
254
|
+
: `unexpected argument ${JSON.stringify(argument)}: aventara skill takes no positional argument; ${HELP}`);
|
|
255
|
+
}
|
|
256
|
+
if (force) {
|
|
257
|
+
throw new AvCliCommandError(`--force was given twice; ${HELP}`);
|
|
258
|
+
}
|
|
259
|
+
force = true;
|
|
260
|
+
}
|
|
261
|
+
return { command: "skill", force };
|
|
262
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PREPARE_SCRIPT = "aventara:prepare";
|
|
@@ -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
|
+
}
|