@aventara/cli 0.1.0-pilot.2 → 0.1.0-pilot.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 +14 -7
- package/dist/apply/main-cors.anchor.d.ts +50 -0
- package/dist/apply/main-cors.anchor.js +60 -0
- package/dist/apply/manifest.merger.d.ts +6 -0
- package/dist/apply/manifest.merger.js +8 -0
- package/dist/catalog/adapter.catalog.generated.d.ts +1 -0
- package/dist/catalog/adapter.catalog.generated.js +1 -0
- package/dist/catalog/catalog-entry.interface.d.ts +5 -0
- package/dist/cli.js +5 -2
- package/dist/command/command.parser.d.ts +4 -2
- package/dist/command/command.parser.js +22 -11
- package/dist/plan/project.planner.js +51 -6
- package/dist/run/init.orchestrator.js +2 -4
- package/dist/run/new.orchestrator.d.ts +3 -1
- package/dist/run/new.orchestrator.js +10 -2
- package/dist/run/scaffold.committer.d.ts +28 -0
- package/dist/run/scaffold.committer.js +53 -0
- package/dist/templates/docs.links.d.ts +8 -0
- package/dist/templates/docs.links.js +2 -0
- package/dist/wizard/wizard.questions.d.ts +14 -1
- package/dist/wizard/wizard.questions.js +21 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,6 +17,11 @@ Runs the pinned `nest new` (`npx -y @nestjs/cli@12.0.8 new …`, or `pnpm dlx
|
|
|
17
17
|
`src/app.module.ts` is what that version writes, then runs exactly `aventara init`'s pipeline over the new project and
|
|
18
18
|
installs it. A directory that exists and is not empty is refused before anything runs.
|
|
19
19
|
|
|
20
|
+
Once installed, the project gets its first git commit, `Initial Aventara Scaffold` — the lockfile in it, `.env` not
|
|
21
|
+
(it is in `.gitignore`). No commit is made, and one line says why, when git is not installed, when git has no
|
|
22
|
+
`user.name`/`user.email`, or when the directory is already inside a git repository (a monorepo: commit it there).
|
|
23
|
+
`--skip-git` makes no repository and no commit.
|
|
24
|
+
|
|
20
25
|
## `aventara init`
|
|
21
26
|
|
|
22
27
|
Runs inside an existing **NestJS 12** project (refused: no `package.json`, another Nest major, TypeScript 7, a yarn-only
|
|
@@ -25,8 +30,9 @@ lockfile, a project already initialized).
|
|
|
25
30
|
- **No ORM yet:** the ORM and the database are asked (or taken from the flags), and everything below is written.
|
|
26
31
|
- **Prisma already there:** the installed Prisma must be one an Aventara adapter supports — today Prisma 7, `^7.10.0`,
|
|
27
32
|
SQLite or PostgreSQL, a driver adapter, the `prisma-client` generator — or the run is refused in one sentence, with
|
|
28
|
-
nothing written. Your schema, Prisma config
|
|
29
|
-
extending `PrismaClient` and the module
|
|
33
|
+
nothing written. Your schema, Prisma config and service are **reused and never written**, and your `.env` only gains
|
|
34
|
+
a `CORS_ORIGINS` line at its end when it has none; init finds the class extending `PrismaClient` and the module
|
|
35
|
+
exporting it, or asks.
|
|
30
36
|
|
|
31
37
|
### Questions and flags
|
|
32
38
|
|
|
@@ -37,7 +43,7 @@ possible answer is shown, not asked.
|
|
|
37
43
|
| Flag | Question | Default |
|
|
38
44
|
|---|---|---|
|
|
39
45
|
| `<name>` (`new` only) | The project directory. | — (required) |
|
|
40
|
-
| `--orm <
|
|
46
|
+
| `--orm <alias>` | The ORM, as an adapter's alias: `prisma7` (Prisma 7, `@aventara/prisma7-adapter`). `--help` lists the aliases that exist. | the only one there is |
|
|
41
47
|
| `--db <provider>` | The database: the adapter's providers (`sqlite`, `postgresql`). | `sqlite` |
|
|
42
48
|
| `--package-manager <npm\|pnpm>` | — | the lockfile's, else the one that launched the CLI, else npm |
|
|
43
49
|
| `--prisma-service <path#Export>` (`init`) | Your Prisma service, when more than one, or none, is found. | the one found; none → write `src/prisma.service.ts` |
|
|
@@ -59,10 +65,11 @@ when you confirm, or with `--yes`; where nobody can be asked, the run stops and
|
|
|
59
65
|
| `src/aventara.config.ts` — `aventaraConfig(prisma)`: the entrypoint (`/api`), and the restrictions and pipelines you add | you |
|
|
60
66
|
| `src/app.module.ts` — one edit: the imports, `PrismaModule` and `AventaraModule.forRootAsync({ … })`; printed instead when the file is not the shape expected | you |
|
|
61
67
|
| `test/app.e2e-spec.ts` — one edit: a `GET /api/_contract` → 200 test after Nest's `GET /` test; left alone when the file is not Nest's | you |
|
|
62
|
-
|
|
|
63
|
-
| `
|
|
68
|
+
| `src/main.ts` — one edit after `NestFactory.create`: `app.enableCors(…)` for the comma-separated origins in `CORS_ORIGINS`, and CORS off when it is unset or empty, then a commented-out platform-level rate limiter (`express-rate-limit`; Nest's `@nestjs/throttler` is a guard, which does not run on Aventara's routes); left alone when the file already configures CORS, printed when it has no such line | you |
|
|
69
|
+
| `.env` (`DATABASE_URL`, and `CORS_ORIGINS="http://localhost:5173,http://localhost:3001"` appended unless a line sets it), `.gitignore` (`/src/generated/`, `/dev.db*`, `.env`) | you |
|
|
70
|
+
| `package.json` — `@aventara/*` at this CLI's version, `prisma`, `@prisma/client` and the driver at `7.10.0`; `aventara:prepare`, run by `postinstall` and by `prebuild`, `prestart`, `prestart:dev` and `prestart:debug`, and the `.env` on the start scripts and `test:e2e` (`--env-file`, `--env-file-if-exists`) | you |
|
|
64
71
|
| `pnpm-workspace.yaml` (pnpm only) — `allowBuilds` for Prisma's and SQLite's install scripts | you |
|
|
65
|
-
| `src/generated/prisma/**`, `src/generated/aventara/discovery.artifact.ts` | **generated** by `aventara:prepare` (run on every install); never edit |
|
|
72
|
+
| `src/generated/prisma/**`, `src/generated/aventara/discovery.artifact.ts` | **generated** by `aventara:prepare` (run on every install, build and start, so a schema change needs only a restart); never edit |
|
|
66
73
|
|
|
67
74
|
### Next steps it prints
|
|
68
75
|
|
|
@@ -107,7 +114,7 @@ whenever the schema or the configuration does: fetch it again, and regenerate th
|
|
|
107
114
|
|
|
108
115
|
## Supported
|
|
109
116
|
|
|
110
|
-
NestJS 12; Node `^22.18.0 || >=24.2.0` (
|
|
117
|
+
NestJS 12; Node `^22.18.0 || >=24.2.0` (on any other Node the bin refuses in one sentence naming that range;
|
|
111
118
|
on Node 22 use npm ≥ 11 — Node 22's bundled npm 10 cannot install Nest 12's own scaffold; a CommonJS project's jest
|
|
112
119
|
e2e needs Node ≥ 24.9, Nest's and Jest's limit); npm and pnpm (yarn is not supported); ESM and
|
|
113
120
|
CommonJS projects; Prisma 7 (`^7.10.0`) on SQLite and PostgreSQL. The ORMs, majors, databases and drivers on offer are
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pilot.3 — the scaffold turns CORS on in `src/main.ts` for the origins listed in
|
|
3
|
+
* `CORS_ORIGINS`, so a browser frontend on another port (Vite's 5173, a Next.js
|
|
4
|
+
* dev server on 3001) can call the protocol:
|
|
5
|
+
*
|
|
6
|
+
* const corsOrigins = (process.env.CORS_ORIGINS ?? '')…filter(Boolean);
|
|
7
|
+
* app.enableCors({ origin: corsOrigins.length > 0 ? corsOrigins : false });
|
|
8
|
+
*
|
|
9
|
+
* followed by a commented-out platform-level rate limiter.
|
|
10
|
+
*
|
|
11
|
+
* **Unset or empty is OFF, never open.** The `cors` package Nest's Express
|
|
12
|
+
* platform uses — and `@fastify/cors` — answer `Access-Control-Allow-Origin: *`
|
|
13
|
+
* when given no origin, so the list is never passed through as it is: no origin is
|
|
14
|
+
* `false`, CORS disabled.
|
|
15
|
+
*
|
|
16
|
+
* Like the `app.module.ts` edit, this one is anchored and never reformats: the
|
|
17
|
+
* lines are inserted after the one statement that creates the application — `const
|
|
18
|
+
* app = await NestFactory.create(…);`, found in code exactly once — through its
|
|
19
|
+
* own variable. A `main.ts` that already configures CORS (`enableCors`, or a
|
|
20
|
+
* `cors` option given to `NestFactory.create`) is the developer's decision and is
|
|
21
|
+
* left as it is; one with no such statement is not edited, and the lines are
|
|
22
|
+
* printed instead.
|
|
23
|
+
*/
|
|
24
|
+
/** The variable the inserted lines read. */
|
|
25
|
+
export declare const CORS_ORIGINS_VARIABLE = "CORS_ORIGINS";
|
|
26
|
+
/** What the scaffold's `.env` lists: Vite's dev server, and a Next.js one beside Nest's 3000. */
|
|
27
|
+
export declare const SCAFFOLD_CORS_ORIGINS = "http://localhost:5173,http://localhost:3001";
|
|
28
|
+
/** The main file `nest new` writes. */
|
|
29
|
+
export declare const MAIN_FILE = "src/main.ts";
|
|
30
|
+
export type MainCorsEdit =
|
|
31
|
+
/** The lines were inserted. */
|
|
32
|
+
{
|
|
33
|
+
readonly kind: "edited";
|
|
34
|
+
readonly text: string;
|
|
35
|
+
}
|
|
36
|
+
/** The lines are already there: a previous run's edit. */
|
|
37
|
+
| {
|
|
38
|
+
readonly kind: "present";
|
|
39
|
+
}
|
|
40
|
+
/** The developer configures CORS: nothing is changed. */
|
|
41
|
+
| {
|
|
42
|
+
readonly kind: "configured";
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "not-anchored";
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
};
|
|
47
|
+
/** Splices the CORS lines into `text`, or says why they are not. */
|
|
48
|
+
export declare function spliceMainCors(text: string): MainCorsEdit;
|
|
49
|
+
/** What to add by hand when there is no anchor. */
|
|
50
|
+
export declare function mainCorsInstructions(): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { codeIndexes, codeMask } from "../project/source.scanner.js";
|
|
2
|
+
import { RATE_LIMITING_DOCS } from "../templates/docs.links.js";
|
|
3
|
+
export const CORS_ORIGINS_VARIABLE = "CORS_ORIGINS";
|
|
4
|
+
export const SCAFFOLD_CORS_ORIGINS = "http://localhost:5173,http://localhost:3001";
|
|
5
|
+
export const MAIN_FILE = "src/main.ts";
|
|
6
|
+
function corsLines(app, indent) {
|
|
7
|
+
return [
|
|
8
|
+
`// The browser origins allowed to call this server: ${CORS_ORIGINS_VARIABLE} in .env,`,
|
|
9
|
+
"// comma-separated. Unset or empty, CORS stays off.",
|
|
10
|
+
`const corsOrigins = (process.env.${CORS_ORIGINS_VARIABLE} ?? '')`,
|
|
11
|
+
" .split(',')",
|
|
12
|
+
" .map((origin) => origin.trim())",
|
|
13
|
+
" .filter(Boolean);",
|
|
14
|
+
`${app}.enableCors({ origin: corsOrigins.length > 0 ? corsOrigins : false });`,
|
|
15
|
+
...RATE_LIMIT_LINES(app),
|
|
16
|
+
]
|
|
17
|
+
.map((line) => `${indent}${line}`)
|
|
18
|
+
.join("\n");
|
|
19
|
+
}
|
|
20
|
+
function RATE_LIMIT_LINES(app) {
|
|
21
|
+
return [
|
|
22
|
+
"// Rate limiting: @nestjs/throttler is a Nest guard and does not run on Aventara's",
|
|
23
|
+
"// routes; a platform-level limiter does. `npm i express-rate-limit`, import",
|
|
24
|
+
"// { rateLimit } from 'express-rate-limit', pick your limits and uncomment below.",
|
|
25
|
+
`// See: ${RATE_LIMITING_DOCS}`,
|
|
26
|
+
`// ${app}.use(rateLimit({ windowMs: 60_000, limit: 100 }));`,
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
const CREATE = /^([ \t]*)(?:const|let)\s+([A-Za-z_$][\w$]*)\s*=\s*await\s+NestFactory\.create\b[^;]*;[ \t]*$/gm;
|
|
30
|
+
export function spliceMainCors(text) {
|
|
31
|
+
const code = codeMask(text);
|
|
32
|
+
const creates = [...text.matchAll(CREATE)].filter((match) => code[match.index + match[1].length]);
|
|
33
|
+
const create = creates.length === 1 ? creates[0] : undefined;
|
|
34
|
+
if (create !== undefined) {
|
|
35
|
+
const lines = corsLines(create[2], create[1]);
|
|
36
|
+
if (text.includes(lines)) {
|
|
37
|
+
return { kind: "present" };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (codeIndexes(text, code, /\benableCors\b/g).length > 0 ||
|
|
41
|
+
creates.some((match) => /\bcors\s*:/.test(match[0]))) {
|
|
42
|
+
return { kind: "configured" };
|
|
43
|
+
}
|
|
44
|
+
if (create === undefined) {
|
|
45
|
+
return {
|
|
46
|
+
kind: "not-anchored",
|
|
47
|
+
reason: creates.length === 0
|
|
48
|
+
? "it has no `const app = await NestFactory.create(…);` line"
|
|
49
|
+
: `it has ${creates.length} \`NestFactory.create\` lines, not one`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const end = create.index + create[0].length;
|
|
53
|
+
return {
|
|
54
|
+
kind: "edited",
|
|
55
|
+
text: `${text.slice(0, end)}\n${corsLines(create[2], create[1])}${text.slice(end)}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function mainCorsInstructions() {
|
|
59
|
+
return `Add to ${MAIN_FILE}, after the application is created:\n${corsLines("app", " ")}`;
|
|
60
|
+
}
|
|
@@ -36,6 +36,12 @@ export type ManifestChanges = {
|
|
|
36
36
|
export declare function mergePackageManifest(text: string, changes: ManifestChanges, replaceConflicts: boolean): Merge;
|
|
37
37
|
/** `.env`: a missing key is appended as `KEY="value"`, after a final newline. */
|
|
38
38
|
export declare function mergeEnvFile(text: string | undefined, entries: Readonly<Record<string, string>>, replaceConflicts: boolean): Merge;
|
|
39
|
+
/**
|
|
40
|
+
* pilot.3 — `.env`: `KEY="value"` appended after a final newline when no line sets
|
|
41
|
+
* `key`; a line that does is the developer's, never a conflict, and nothing else
|
|
42
|
+
* is reordered or rewritten.
|
|
43
|
+
*/
|
|
44
|
+
export declare function appendEnvEntryIfAbsent(text: string | undefined, key: string, value: string): string;
|
|
39
45
|
/** `.gitignore`: missing lines appended, nothing else touched. */
|
|
40
46
|
export declare function mergeGitignore(text: string | undefined, additions: readonly string[]): Merge;
|
|
41
47
|
/**
|
|
@@ -79,6 +79,14 @@ export function mergeEnvFile(text, entries, replaceConflicts) {
|
|
|
79
79
|
}
|
|
80
80
|
return { text: `${lines.join("\n")}\n`, conflicts };
|
|
81
81
|
}
|
|
82
|
+
export function appendEnvEntryIfAbsent(text, key, value) {
|
|
83
|
+
const current = text ?? "";
|
|
84
|
+
if (current.split("\n").some((line) => ENV_LINE.exec(line)?.[1] === key)) {
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
87
|
+
const separator = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
88
|
+
return `${current}${separator}${key}="${value}"\n`;
|
|
89
|
+
}
|
|
82
90
|
export function mergeGitignore(text, additions) {
|
|
83
91
|
const lines = text === undefined || text === ""
|
|
84
92
|
? []
|
|
@@ -14,6 +14,11 @@ export type CatalogEntry = {
|
|
|
14
14
|
* templates' key.
|
|
15
15
|
*/
|
|
16
16
|
readonly id: string;
|
|
17
|
+
/**
|
|
18
|
+
* `aventara.adapter.alias`, e.g. `prisma7`: what `--orm` takes. Always
|
|
19
|
+
* `<orm><major>`, the generator refuses any other.
|
|
20
|
+
*/
|
|
21
|
+
readonly alias: string;
|
|
17
22
|
/** The adapter package a project installs, e.g. `@aventara/prisma7-adapter`. */
|
|
18
23
|
readonly adapterPackage: string;
|
|
19
24
|
readonly orm: {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { ConflictsNotConfirmedError } from "./apply/conflict.confirmer.js";
|
|
3
3
|
import { ADAPTER_CATALOG } from "./catalog/adapter.catalog.generated.js";
|
|
4
|
-
import { CliCommandError, commandUsage, parseCliCommand,
|
|
4
|
+
import { CliCommandError, commandUsage, parseCliCommand, usage, } from "./command/command.parser.js";
|
|
5
5
|
import { ProjectRefusedError } from "./project/project.inspector.js";
|
|
6
6
|
import { runCommand } from "./run/command.runner.js";
|
|
7
7
|
import { InstallFailedError, runInit } from "./run/init.orchestrator.js";
|
|
@@ -27,7 +27,10 @@ export async function runCli(argv, io) {
|
|
|
27
27
|
try {
|
|
28
28
|
const command = parseCliCommand(argv);
|
|
29
29
|
if (command.command === "help") {
|
|
30
|
-
|
|
30
|
+
const catalog = io.catalog ?? ADAPTER_CATALOG;
|
|
31
|
+
io.stdout(command.topic === undefined
|
|
32
|
+
? usage(catalog)
|
|
33
|
+
: commandUsage(command.topic, catalog));
|
|
31
34
|
return 0;
|
|
32
35
|
}
|
|
33
36
|
if (command.command === "version") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdapterCatalog } from "../catalog/catalog-entry.interface.js";
|
|
1
2
|
import { type QuestionId } from "../wizard/wizard.questions.js";
|
|
2
3
|
/**
|
|
3
4
|
* The `aventara` command line: `new <name>` and `init`, each question's flag from
|
|
@@ -30,7 +31,8 @@ export declare class CliCommandError extends Error {
|
|
|
30
31
|
readonly name = "CliCommandError";
|
|
31
32
|
}
|
|
32
33
|
/** `aventara <command> --help` (pilot.1): that command's usage alone. */
|
|
33
|
-
export declare function commandUsage(command: ScaffoldCommandName): string;
|
|
34
|
-
|
|
34
|
+
export declare function commandUsage(command: ScaffoldCommandName, catalog: AdapterCatalog): string;
|
|
35
|
+
/** `aventara --help`: both commands, each flag with the values `catalog` offers. */
|
|
36
|
+
export declare function usage(catalog: AdapterCatalog): string;
|
|
35
37
|
/** @throws CliCommandError when `argv` is not a command this CLI has. */
|
|
36
38
|
export declare function parseCliCommand(argv: readonly string[]): CliCommand;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INIT_FLAG_QUESTIONS, NEW_QUESTIONS, } from "../wizard/wizard.questions.js";
|
|
1
|
+
import { INIT_FLAG_QUESTIONS, NEW_QUESTIONS, ormAliasesSentence, } from "../wizard/wizard.questions.js";
|
|
2
2
|
export class CliCommandError extends Error {
|
|
3
3
|
name = "CliCommandError";
|
|
4
4
|
}
|
|
@@ -28,12 +28,18 @@ const QUESTIONS = {
|
|
|
28
28
|
init: INIT_FLAG_QUESTIONS,
|
|
29
29
|
};
|
|
30
30
|
const HELP = "run `aventara --help` for usage";
|
|
31
|
-
function
|
|
31
|
+
function flagUsage(question, catalog) {
|
|
32
|
+
const accepted = question.accepts?.(catalog);
|
|
33
|
+
return `${question.flag} ${accepted === undefined
|
|
34
|
+
? (question.placeholder ?? "<value>")
|
|
35
|
+
: `<${accepted.join("|")}>`}`;
|
|
36
|
+
}
|
|
37
|
+
function usageLines(command, catalog) {
|
|
32
38
|
const rows = [
|
|
33
39
|
...QUESTIONS[command]
|
|
34
40
|
.filter((question) => question.flag.startsWith("--"))
|
|
35
41
|
.map((question) => [
|
|
36
|
-
|
|
42
|
+
flagUsage(question, catalog),
|
|
37
43
|
`${question.label}; asked when omitted`,
|
|
38
44
|
]),
|
|
39
45
|
...OPTIONS.filter((option) => option.commands.includes(command)).map((option) => [
|
|
@@ -54,33 +60,38 @@ const SYNOPSIS = {
|
|
|
54
60
|
new: "aventara new <name> [options]",
|
|
55
61
|
init: "aventara init [options]",
|
|
56
62
|
};
|
|
57
|
-
|
|
63
|
+
function asked(catalog) {
|
|
64
|
+
return `${ormAliasesSentence(catalog)}.
|
|
65
|
+
On a terminal every unanswered question is asked; anywhere else, pass its flag
|
|
58
66
|
or --yes, or the run stops before writing anything.
|
|
59
67
|
`;
|
|
60
|
-
|
|
68
|
+
}
|
|
69
|
+
export function commandUsage(command, catalog) {
|
|
61
70
|
return `Usage: ${SYNOPSIS[command]}
|
|
62
71
|
|
|
63
72
|
${SUMMARY[command]}
|
|
64
73
|
|
|
65
74
|
Options:
|
|
66
|
-
${usageLines(command)}
|
|
75
|
+
${usageLines(command, catalog)}
|
|
67
76
|
|
|
68
|
-
${
|
|
77
|
+
${asked(catalog)}`;
|
|
69
78
|
}
|
|
70
|
-
export
|
|
79
|
+
export function usage(catalog) {
|
|
80
|
+
return `aventara — scaffold an Aventara server on NestJS.
|
|
71
81
|
|
|
72
82
|
Usage:
|
|
73
83
|
${SYNOPSIS.new} ${SUMMARY.new}
|
|
74
|
-
${usageLines("new")}
|
|
84
|
+
${usageLines("new", catalog)}
|
|
75
85
|
|
|
76
86
|
${SYNOPSIS.init} ${SUMMARY.init}
|
|
77
|
-
${usageLines("init")}
|
|
87
|
+
${usageLines("init", catalog)}
|
|
78
88
|
|
|
79
89
|
aventara --help Print this and exit 0.
|
|
80
90
|
aventara <command> --help Print that command's usage and exit 0.
|
|
81
91
|
aventara --version, -v Print this CLI's version and exit 0.
|
|
82
92
|
|
|
83
|
-
${
|
|
93
|
+
${asked(catalog)}`;
|
|
94
|
+
}
|
|
84
95
|
export function parseCliCommand(argv) {
|
|
85
96
|
const [command, ...rest] = argv;
|
|
86
97
|
if (command === undefined) {
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { spliceAppModule, wiringInstructions, } from "../apply/app-module.anchor.js";
|
|
2
2
|
import { E2E_SPEC, spliceContractTest } from "../apply/e2e-spec.anchor.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CORS_ORIGINS_VARIABLE, MAIN_FILE, mainCorsInstructions, SCAFFOLD_CORS_ORIGINS, spliceMainCors, } from "../apply/main-cors.anchor.js";
|
|
4
|
+
import { appendEnvEntryIfAbsent, mergeEnvFile, mergeGitignore, mergePackageManifest, mergePnpmWorkspace, } from "../apply/manifest.merger.js";
|
|
4
5
|
import { APP_MODULE, } from "../project/project.inspector.js";
|
|
5
6
|
import { SCAFFOLD_ENTRYPOINT, } from "../templates/template.registry.js";
|
|
6
7
|
export const PREPARE_SCRIPT = "aventara:prepare";
|
|
8
|
+
const PREPARED_SCRIPTS = [
|
|
9
|
+
"build",
|
|
10
|
+
"start",
|
|
11
|
+
"start:dev",
|
|
12
|
+
"start:debug",
|
|
13
|
+
];
|
|
7
14
|
const START_SCRIPTS = {
|
|
8
15
|
start: { from: "nest start", to: "nest start --env-file .env" },
|
|
9
16
|
"start:dev": {
|
|
@@ -118,6 +125,31 @@ export function planInit(input) {
|
|
|
118
125
|
instructions = wiringInstructions(wiring);
|
|
119
126
|
warnings.push(`${APP_MODULE} was not edited: ${edit.reason}; add AventaraModule by hand, as printed below`);
|
|
120
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";
|
|
146
|
+
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
|
+
: [];
|
|
121
153
|
const versions = {
|
|
122
154
|
[CORE]: input.aventaraVersion,
|
|
123
155
|
[NEST_HOST]: input.aventaraVersion,
|
|
@@ -129,6 +161,10 @@ export function planInit(input) {
|
|
|
129
161
|
scripts: {
|
|
130
162
|
[PREPARE_SCRIPT]: scaffold.prepare,
|
|
131
163
|
postinstall: `${packageManager} run ${PREPARE_SCRIPT}`,
|
|
164
|
+
...Object.fromEntries(PREPARED_SCRIPTS.map((script) => [
|
|
165
|
+
`pre${script}`,
|
|
166
|
+
`${packageManager} run ${PREPARE_SCRIPT}`,
|
|
167
|
+
])),
|
|
132
168
|
},
|
|
133
169
|
rewrites: inspection.usesNestConfig
|
|
134
170
|
? {}
|
|
@@ -137,15 +173,23 @@ export function planInit(input) {
|
|
|
137
173
|
"test:e2e": E2E_SCRIPTS[inspection.moduleKind],
|
|
138
174
|
},
|
|
139
175
|
}, replace));
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
176
|
+
if (scaffoldWritesEnv || (corsWanted && env !== undefined)) {
|
|
177
|
+
both(".env", env, (replace) => {
|
|
178
|
+
const merged = scaffoldWritesEnv
|
|
179
|
+
? mergeEnvFile(env, scaffold.env, replace)
|
|
180
|
+
: { text: env ?? "", conflicts: [] };
|
|
181
|
+
return corsWanted
|
|
182
|
+
? {
|
|
183
|
+
text: appendEnvEntryIfAbsent(merged.text, CORS_ORIGINS_VARIABLE, SCAFFOLD_CORS_ORIGINS),
|
|
184
|
+
conflicts: merged.conflicts,
|
|
185
|
+
}
|
|
186
|
+
: merged;
|
|
187
|
+
});
|
|
144
188
|
}
|
|
145
189
|
const gitignore = input.read(".gitignore");
|
|
146
190
|
both(".gitignore", gitignore, () => mergeGitignore(gitignore, [
|
|
147
191
|
...scaffold.gitignore,
|
|
148
|
-
...(
|
|
192
|
+
...(scaffoldWritesEnv ? [".env"] : []),
|
|
149
193
|
]));
|
|
150
194
|
if (packageManager === "pnpm" && scaffold.nativeBuilds.length > 0) {
|
|
151
195
|
const workspace = input.read("pnpm-workspace.yaml");
|
|
@@ -168,6 +212,7 @@ export function planInit(input) {
|
|
|
168
212
|
"The start scripts were left as they are: @nestjs/config loads .env, so make sure it does before Prisma connects.",
|
|
169
213
|
]
|
|
170
214
|
: []),
|
|
215
|
+
...corsStep,
|
|
171
216
|
`Start the server: ${run} run start:dev`,
|
|
172
217
|
`In your frontend: ${frontendInitCommand(packageManager, input.aventaraVersion)}`,
|
|
173
218
|
],
|
|
@@ -138,12 +138,10 @@ async function prepareReuse(command, io, inspection, detectedPackageManager) {
|
|
|
138
138
|
throw new ProjectRefusedError(`this project's ${facts.generator} generator block in ${facts.schema} has no output, and the generate step reads the client from it; give it one, such as ../src/generated/${entry.orm.family}`);
|
|
139
139
|
}
|
|
140
140
|
for (const [flag, given, found] of [
|
|
141
|
-
["--orm", command.given.orm,
|
|
141
|
+
["--orm", command.given.orm, entry.alias],
|
|
142
142
|
["--db", command.given.db, provider],
|
|
143
143
|
]) {
|
|
144
|
-
if (given !== undefined &&
|
|
145
|
-
given.trim().toLowerCase() !== found &&
|
|
146
|
-
!(flag === "--orm" && given.trim().toLowerCase() === entry.orm.family)) {
|
|
144
|
+
if (given !== undefined && given.trim().toLowerCase() !== found) {
|
|
147
145
|
throw new ProjectRefusedError(`${flag} ${given} does not match this project, which already uses ${found}`);
|
|
148
146
|
}
|
|
149
147
|
}
|
|
@@ -3,7 +3,9 @@ import { type InitIo } from "./init.orchestrator.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* `aventara new <name>`: the pinned `nest new`, then **exactly** `aventara init`'s
|
|
5
5
|
* pipeline over the fresh project, with the wizard's answers carried in — so every
|
|
6
|
-
* `new` run exercises the anchored edit, the merges and the templates `init` uses
|
|
6
|
+
* `new` run exercises the anchored edit, the merges and the templates `init` uses
|
|
7
|
+
* — and, after the install, the project's first commit (`scaffold.committer.ts`,
|
|
8
|
+
* pilot.3).
|
|
7
9
|
*
|
|
8
10
|
* Nest authors its own scaffold: the CLI never writes Nest's files. Its output is
|
|
9
11
|
* pinned by digest, so a `@nestjs/cli` that writes something else stops the run
|
|
@@ -6,6 +6,7 @@ import { ProjectRefusedError } from "../project/project.inspector.js";
|
|
|
6
6
|
import { resolveAnswers } from "../wizard/answer.resolver.js";
|
|
7
7
|
import { NEW_QUESTIONS } from "../wizard/wizard.questions.js";
|
|
8
8
|
import { InstallFailedError, runInit, } from "./init.orchestrator.js";
|
|
9
|
+
import { commitScaffold, gitSurroundings } from "./scaffold.committer.js";
|
|
9
10
|
export const NEST_CLI = "@nestjs/cli@12.0.8";
|
|
10
11
|
export const NEST_APP_MODULE_SHA256 = "fe98c4cd2552342272494652287c02e4ef58db3072fcb18513ea831c60f85778";
|
|
11
12
|
function oneOff(packageManager) {
|
|
@@ -34,6 +35,9 @@ export async function runNew(command, io) {
|
|
|
34
35
|
if (existsSync(directory) && readdirSync(directory).length > 0) {
|
|
35
36
|
throw new ProjectRefusedError(`aventara new creates ${directory}, which already exists and is not empty; choose another name, or run aventara init inside an existing project`);
|
|
36
37
|
}
|
|
38
|
+
const surroundings = command.skipGit
|
|
39
|
+
? undefined
|
|
40
|
+
: await gitSurroundings(io.runner, io.cwd);
|
|
37
41
|
const [runner, prefix] = oneOff(packageManager);
|
|
38
42
|
const args = [
|
|
39
43
|
...prefix,
|
|
@@ -58,10 +62,10 @@ export async function runNew(command, io) {
|
|
|
58
62
|
if (digest !== NEST_APP_MODULE_SHA256) {
|
|
59
63
|
throw new InstallFailedError(`nest new's output changed: ${appModule} is not what ${NEST_CLI} writes (sha256 ${digest}), so nothing of Aventara's was written over it; run aventara init inside ${directory} to add Aventara to it`);
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
const initialized = await runInit({
|
|
62
66
|
command: "init",
|
|
63
67
|
given: {
|
|
64
|
-
orm:
|
|
68
|
+
orm: orm.alias,
|
|
65
69
|
db,
|
|
66
70
|
packageManager,
|
|
67
71
|
},
|
|
@@ -69,4 +73,8 @@ export async function runNew(command, io) {
|
|
|
69
73
|
skipGit: false,
|
|
70
74
|
yes: true,
|
|
71
75
|
}, { ...io, directory, firstSteps: [`Enter the project: cd ${name}`] });
|
|
76
|
+
if (initialized === 0 && surroundings !== undefined) {
|
|
77
|
+
io.stdout(`aventara: ${await commitScaffold(io.runner, directory, surroundings, io.cwd)}.\n`);
|
|
78
|
+
}
|
|
79
|
+
return initialized;
|
|
72
80
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CommandRunner } from "./command.runner.js";
|
|
2
|
+
/**
|
|
3
|
+
* pilot.3 — `aventara new` ends with one commit of the whole scaffold, the
|
|
4
|
+
* install's lockfile included, under one fixed message. `.env` is not in it: the
|
|
5
|
+
* scaffold's `.gitignore` lists it (Nest's own does too). `init` never commits:
|
|
6
|
+
* the project is the developer's.
|
|
7
|
+
*
|
|
8
|
+
* Never a failure: where a commit cannot or should not be made, one line says why
|
|
9
|
+
* and the run still succeeds —
|
|
10
|
+
* - git is not installed;
|
|
11
|
+
* - the directory `new` ran in is inside a git work tree already (a monorepo): the
|
|
12
|
+
* project belongs to that repository, and gets no commit of its own;
|
|
13
|
+
* - git has no `user.name` and `user.email` to commit as. `--skip-git` (passed to
|
|
14
|
+
* `nest new`, which then initializes no repository) makes no commit and says
|
|
15
|
+
* nothing.
|
|
16
|
+
*
|
|
17
|
+
* git runs through the one process seam, `CommandRunner`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const INITIAL_COMMIT_MESSAGE = "Initial Aventara Scaffold";
|
|
20
|
+
/** Where `new` runs, as git sees it — read BEFORE `nest new` initializes a repository. */
|
|
21
|
+
export type GitSurroundings = "no-git" | "inside-work-tree" | "outside";
|
|
22
|
+
/** Whether git is installed, and whether `directory` lies in a work tree. */
|
|
23
|
+
export declare function gitSurroundings(runner: CommandRunner, directory: string): Promise<GitSurroundings>;
|
|
24
|
+
/**
|
|
25
|
+
* Commits the project in `directory` as its first commit, or says why not: the
|
|
26
|
+
* line to print, without the `aventara: ` prefix or the final period.
|
|
27
|
+
*/
|
|
28
|
+
export declare function commitScaffold(runner: CommandRunner, directory: string, surroundings: GitSurroundings, enclosing: string): Promise<string>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
export const INITIAL_COMMIT_MESSAGE = "Initial Aventara Scaffold";
|
|
3
|
+
function isMissingCommand(error) {
|
|
4
|
+
return (error instanceof Error &&
|
|
5
|
+
error.code === "ENOENT");
|
|
6
|
+
}
|
|
7
|
+
export async function gitSurroundings(runner, directory) {
|
|
8
|
+
try {
|
|
9
|
+
const answered = await runner("git", ["rev-parse", "--is-inside-work-tree"], { cwd: directory });
|
|
10
|
+
return answered.code === 0 && answered.stdout.trim() === "true"
|
|
11
|
+
? "inside-work-tree"
|
|
12
|
+
: "outside";
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (isMissingCommand(error)) {
|
|
16
|
+
return "no-git";
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function commitScaffold(runner, directory, surroundings, enclosing) {
|
|
22
|
+
if (surroundings === "no-git") {
|
|
23
|
+
return "no initial commit: git is not installed";
|
|
24
|
+
}
|
|
25
|
+
if (surroundings === "inside-work-tree") {
|
|
26
|
+
return `no initial commit: ${enclosing} is inside a git repository already; commit the project there`;
|
|
27
|
+
}
|
|
28
|
+
const git = (args) => runner("git", args, { cwd: directory });
|
|
29
|
+
for (const key of ["user.name", "user.email"]) {
|
|
30
|
+
const set = await git(["config", key]);
|
|
31
|
+
if (set.code !== 0 || set.stdout.trim() === "") {
|
|
32
|
+
return "no initial commit: git has no user.name and user.email set; set them, then commit the project yourself";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const top = await git(["rev-parse", "--show-toplevel"]);
|
|
36
|
+
if (top.code !== 0 ||
|
|
37
|
+
realpathSync(top.stdout.trim()) !== realpathSync(directory)) {
|
|
38
|
+
const initialized = await git(["init", "-q"]);
|
|
39
|
+
if (initialized.code !== 0) {
|
|
40
|
+
return `no initial commit: \`git init\` exited with code ${initialized.code}: ${initialized.stderr.trim()}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const args of [
|
|
44
|
+
["add", "-A"],
|
|
45
|
+
["commit", "-q", "-m", INITIAL_COMMIT_MESSAGE],
|
|
46
|
+
]) {
|
|
47
|
+
const ran = await git(args);
|
|
48
|
+
if (ran.code !== 0) {
|
|
49
|
+
return `no initial commit: \`git ${args.join(" ")}\` exited with code ${ran.code}: ${ran.stderr.trim()}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return `committed the scaffold: "${INITIAL_COMMIT_MESSAGE}"`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the public documentation lives: the one base every link the scaffold
|
|
3
|
+
* writes into a project starts from. A placeholder until the docs domain is
|
|
4
|
+
* settled; changing it changes only comments in the files the CLI writes.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DOCS_BASE_URL = "https://<docs-domain>";
|
|
7
|
+
/** The documentation's rate-limiting section, linked from the scaffold's `main.ts`. */
|
|
8
|
+
export declare const RATE_LIMITING_DOCS = "https://<docs-domain>/docs/limits-and-safety#rate-limiting";
|
|
@@ -56,7 +56,7 @@ export type WizardContext = {
|
|
|
56
56
|
readonly services?: ServiceContext;
|
|
57
57
|
};
|
|
58
58
|
export type Choice = {
|
|
59
|
-
/** What the flag takes and the prompt accepts, e.g. `
|
|
59
|
+
/** What the flag takes and the prompt accepts, e.g. `prisma7`. */
|
|
60
60
|
readonly value: string;
|
|
61
61
|
/** How it is shown, e.g. `Prisma 7`. */
|
|
62
62
|
readonly label: string;
|
|
@@ -77,6 +77,11 @@ export type WizardQuestion<K extends QuestionId> = {
|
|
|
77
77
|
readonly after?: QuestionId;
|
|
78
78
|
/** The answers on offer, or `undefined` for free text. */
|
|
79
79
|
readonly choices: (context: WizardContext) => readonly Choice[] | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Every value its flag takes before anything is answered — what `--help`
|
|
82
|
+
* lists (pilot.3); absent for free text. From the catalog, never a literal.
|
|
83
|
+
*/
|
|
84
|
+
readonly accepts?: (catalog: AdapterCatalog) => readonly string[];
|
|
80
85
|
/** The answer `--yes` (or Enter at the prompt) takes; `undefined` when there is none. */
|
|
81
86
|
readonly defaultFor: (context: WizardContext) => string | undefined;
|
|
82
87
|
/** @throws WizardAnswerError when `raw` is not an answer to this question. */
|
|
@@ -87,7 +92,15 @@ export type WizardQuestion<K extends QuestionId> = {
|
|
|
87
92
|
export type AnyWizardQuestion = {
|
|
88
93
|
readonly [K in QuestionId]: WizardQuestion<K>;
|
|
89
94
|
}[QuestionId];
|
|
95
|
+
/**
|
|
96
|
+
* What `--orm` takes: each adapter's alias, with what it is — the refusal's words,
|
|
97
|
+
* and the usage's.
|
|
98
|
+
*/
|
|
99
|
+
export declare function ormAliasesSentence(catalog: AdapterCatalog): string;
|
|
90
100
|
export declare const NAME_QUESTION: WizardQuestion<"name">;
|
|
101
|
+
/**
|
|
102
|
+
* The prompt keeps the family-then-major order; every answer is an alias.
|
|
103
|
+
*/
|
|
91
104
|
export declare const ORM_QUESTION: WizardQuestion<"orm">;
|
|
92
105
|
export declare const DB_QUESTION: WizardQuestion<"db">;
|
|
93
106
|
export declare const PACKAGE_MANAGER_QUESTION: WizardQuestion<"packageManager">;
|
|
@@ -6,14 +6,19 @@ export class WizardAnswerError extends Error {
|
|
|
6
6
|
function ormLabel(entry) {
|
|
7
7
|
return `${entry.orm.label} ${entry.orm.major}`;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
return
|
|
11
|
-
.
|
|
12
|
-
.join(", ")
|
|
9
|
+
function listed(items) {
|
|
10
|
+
return items.length <= 1
|
|
11
|
+
? items.join("")
|
|
12
|
+
: `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
|
|
13
|
+
}
|
|
14
|
+
function families(catalog) {
|
|
15
|
+
return [...new Set(catalog.map((entry) => entry.orm.family))];
|
|
16
|
+
}
|
|
17
|
+
export function ormAliasesSentence(catalog) {
|
|
18
|
+
return `--orm takes ${listed(ormChoices(catalog).map((entry) => `${entry.alias} (${ormLabel(entry)}, ${entry.adapterPackage})`))}`;
|
|
13
19
|
}
|
|
14
20
|
function ormChoices(catalog) {
|
|
15
|
-
|
|
16
|
-
return families.flatMap((family) => catalog
|
|
21
|
+
return families(catalog).flatMap((family) => catalog
|
|
17
22
|
.filter((entry) => entry.orm.family === family)
|
|
18
23
|
.sort((left, right) => right.orm.major - left.orm.major));
|
|
19
24
|
}
|
|
@@ -38,24 +43,16 @@ export const ORM_QUESTION = {
|
|
|
38
43
|
flag: "--orm",
|
|
39
44
|
label: "ORM",
|
|
40
45
|
choices: (context) => ormChoices(context.catalog).map((entry) => ({
|
|
41
|
-
value:
|
|
46
|
+
value: entry.alias,
|
|
42
47
|
label: ormLabel(entry),
|
|
43
48
|
})),
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
: undefined,
|
|
49
|
+
accepts: (catalog) => ormChoices(catalog).map((entry) => entry.alias),
|
|
50
|
+
defaultFor: (context) => context.catalog.length === 1 ? context.catalog[0]?.alias : undefined,
|
|
47
51
|
parse: (raw, context) => {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
if (rest.length > 0 || offered.length === 0) {
|
|
51
|
-
throw new WizardAnswerError(`there is no Aventara adapter for the ORM ${JSON.stringify(raw)}; available: ${available(context.catalog)}`);
|
|
52
|
-
}
|
|
53
|
-
if (major === undefined) {
|
|
54
|
-
return offered[0];
|
|
55
|
-
}
|
|
56
|
-
const entry = offered.find((candidate) => String(candidate.orm.major) === major);
|
|
52
|
+
const alias = raw.trim().toLowerCase();
|
|
53
|
+
const entry = context.catalog.find((candidate) => candidate.alias === alias);
|
|
57
54
|
if (entry === undefined) {
|
|
58
|
-
throw new WizardAnswerError(`there is no Aventara adapter for ${
|
|
55
|
+
throw new WizardAnswerError(`there is no Aventara adapter for the ORM ${JSON.stringify(raw)}; ${ormAliasesSentence(context.catalog)}`);
|
|
59
56
|
}
|
|
60
57
|
return entry;
|
|
61
58
|
},
|
|
@@ -77,6 +74,9 @@ export const DB_QUESTION = {
|
|
|
77
74
|
value: provider,
|
|
78
75
|
label: provider,
|
|
79
76
|
})),
|
|
77
|
+
accepts: (catalog) => [
|
|
78
|
+
...new Set(catalog.flatMap((entry) => entry.providers)),
|
|
79
|
+
],
|
|
80
80
|
defaultFor: (context) => requireOrm(context).providers[0],
|
|
81
81
|
parse: (raw, context) => {
|
|
82
82
|
const orm = requireOrm(context);
|
|
@@ -93,6 +93,7 @@ export const PACKAGE_MANAGER_QUESTION = {
|
|
|
93
93
|
flag: "--package-manager",
|
|
94
94
|
label: "Package manager",
|
|
95
95
|
choices: () => PACKAGE_MANAGERS.map((manager) => ({ value: manager, label: manager })),
|
|
96
|
+
accepts: () => PACKAGE_MANAGERS,
|
|
96
97
|
defaultFor: (context) => context.detectedPackageManager,
|
|
97
98
|
parse: (raw) => {
|
|
98
99
|
const manager = PACKAGE_MANAGERS.find((candidate) => candidate === raw.trim().toLowerCase());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/cli",
|
|
3
|
-
"version": "0.1.0-pilot.
|
|
3
|
+
"version": "0.1.0-pilot.4",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Scaffolds an Aventara server: `aventara new` for a new NestJS project, `aventara init` for an existing one.",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"prebuild": "node scripts/adapter-catalog.generator.ts",
|
|
26
|
-
"build": "tsc -p tsconfig.json --removeComments --declaration false
|
|
26
|
+
"build": "tsc -p tsconfig.json --removeComments --declaration false",
|
|
27
27
|
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
|
|
28
28
|
"test": "vitest run --typecheck --config ../../vitest.config.mts --root .",
|
|
29
29
|
"lint": "biome lint src scripts"
|