@aventara/cli 0.1.0-pilot.4 → 1.0.0-rc.0

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +9 -4
  3. package/dist/cli.js +4 -0
  4. package/dist/command/command.parser.js +7 -0
  5. package/dist/run/command.runner.js +2 -0
  6. package/dist/run/init.orchestrator.js +33 -13
  7. package/dist/run/new.orchestrator.js +29 -11
  8. package/dist/run/step.reporter.js +87 -0
  9. package/package.json +4 -3
  10. package/dist/apply/app-module.anchor.d.ts +0 -24
  11. package/dist/apply/conflict.confirmer.d.ts +0 -15
  12. package/dist/apply/e2e-spec.anchor.d.ts +0 -18
  13. package/dist/apply/main-cors.anchor.d.ts +0 -50
  14. package/dist/apply/manifest.merger.d.ts +0 -51
  15. package/dist/aventara.bin.d.ts +0 -2
  16. package/dist/catalog/adapter.catalog.generated.d.ts +0 -20
  17. package/dist/catalog/catalog-entry.interface.d.ts +0 -54
  18. package/dist/catalog/catalog.matcher.d.ts +0 -40
  19. package/dist/catalog/range.reader.d.ts +0 -11
  20. package/dist/cli.d.ts +0 -29
  21. package/dist/command/command.parser.d.ts +0 -38
  22. package/dist/node-version.guard.d.ts +0 -8
  23. package/dist/plan/project.planner.d.ts +0 -53
  24. package/dist/project/package-manager.detector.d.ts +0 -5
  25. package/dist/project/project.inspector.d.ts +0 -32
  26. package/dist/project/service.detector.d.ts +0 -40
  27. package/dist/project/source.scanner.d.ts +0 -16
  28. package/dist/run/command.runner.d.ts +0 -15
  29. package/dist/run/init.orchestrator.d.ts +0 -27
  30. package/dist/run/new.orchestrator.d.ts +0 -20
  31. package/dist/run/scaffold.committer.d.ts +0 -28
  32. package/dist/templates/docs.links.d.ts +0 -8
  33. package/dist/templates/prisma7/prisma7.templates.d.ts +0 -34
  34. package/dist/templates/template.registry.d.ts +0 -152
  35. package/dist/wizard/answer.resolver.d.ts +0 -47
  36. package/dist/wizard/readline.prompter.d.ts +0 -22
  37. package/dist/wizard/wizard.questions.d.ts +0 -120
package/CHANGELOG.md ADDED
@@ -0,0 +1,114 @@
1
+ # @aventara/cli
2
+
3
+ ## 1.0.0-rc.0
4
+
5
+ ### Major Changes
6
+
7
+ - First release candidate of Aventara 1.0. The public API is now considered stable; changes before 1.0.0 are possible
8
+ based on feedback.
9
+
10
+ ### Minor Changes
11
+
12
+ - 24ffbab: `aventara new`, `aventara init`, `avclient init` and `avclient generate` show their steps while they work, so a long
13
+ install never looks hung: on a terminal, `[3/4] Installing dependencies (npm install)` with a spinner and the seconds so
14
+ far, then `✓` or `✗` and its time; in a pipe, in CI or with `NO_COLOR`, one plain line per step (`… done (12.4s)`),
15
+ with no control sequence. A closing line gives the total time. The steps go to stderr; stdout keeps the results and the
16
+ next steps. The output of `nest new` and of the install is shown only when it fails, as before, or as it runs with the
17
+ new `--verbose`.
18
+
19
+ ### Patch Changes
20
+
21
+ - 24ffbab: Each package ships its `CHANGELOG.md`, so the release notes are in the installed package beside the README.
22
+ - 24ffbab: `aventara new` prints its next steps last — after the initial commit and the closing line — so they are what is on
23
+ screen when it ends, in a terminal and in a pipe alike.
24
+
25
+ ## 0.1.0-pilot.4
26
+
27
+ ### Patch Changes
28
+
29
+ - f37dcd6: `@aventara/cli` ships no type declarations: it is a command, run with `npx` or installed globally, and has no API to
30
+ import.
31
+ - f37dcd6: `aventara new` and `aventara init` add `prebuild`, `prestart`, `prestart:dev` and `prestart:debug` scripts that run
32
+ `aventara:prepare`, so after a change to `schema.prisma` the next `npm run start:dev` (or `build`, `start`,
33
+ `start:debug`) regenerates the Prisma client and the discovery artifact before the server compiles — no reinstall
34
+ needed. npm and pnpm both run these hooks. On an existing project, `aventara init` treats a `prebuild` or `prestart*`
35
+ script you already have as a conflict, like any other script: kept unless you confirm, replaced with `--yes`.
36
+
37
+ The scaffold's rate-limiting note in `src/main.ts` links to the docs:
38
+ `https://aventara-docs.vercel.app/docs/limits-and-safety#rate-limiting`.
39
+
40
+ ## 0.1.0-pilot.3
41
+
42
+ ### Patch Changes
43
+
44
+ - d8c128b: `aventara new` and `aventara init` turn CORS on in `src/main.ts`, so a browser frontend on another port can call the
45
+ API — 0.1.0-pilot.2's scaffold had none, and a browser's preflight got the protocol's answer instead. Right after
46
+ `const app = await NestFactory.create(AppModule);` they add `app.enableCors({ origin: … })` with the comma-separated
47
+ origins in `CORS_ORIGINS`, and write `CORS_ORIGINS="http://localhost:5173,http://localhost:3001"` into the `.env` they
48
+ write. Unset or empty, CORS is **off** (`origin: false`), never open to every origin. A `main.ts` that already
49
+ configures CORS is left as it is, with a warning; one without that line is not edited, and the lines are printed. Over an
50
+ existing Prisma setup, whose `.env` init never writes, adding `CORS_ORIGINS` is printed as a next step. Nest's
51
+ `enableCors` reaches Aventara's routes on Express and Fastify alike: preflights naming the `Aventara-*` headers are
52
+ allowed, and the calls answered with the origin.
53
+ - 05d39e8: `aventara new` ends with the project's first git commit, `Initial Aventara Scaffold`: every scaffolded file and the
54
+ install's lockfile, never `.env` (it is in `.gitignore`). 0.1.0-pilot.2 left a repository with nothing committed. Where
55
+ a commit cannot or should not be made the run still succeeds, saying why in one line: git is not installed, git has no
56
+ `user.name`/`user.email`, or the directory `new` ran in is already inside a git repository (a monorepo), where no
57
+ commit is made in either repository. `--skip-git` creates no repository and makes no commit; `aventara init` never
58
+ commits.
59
+ - 6755a1f: `aventara --help`, `aventara new --help` and `aventara init --help` list the values `--orm`, `--db` and
60
+ `--package-manager` take, read from the adapter catalog: `--orm <prisma7>`, `--db <sqlite|postgresql>`,
61
+ `--package-manager <npm|pnpm>`. A refused `--orm` now names the values it takes — "`--orm takes prisma7 (Prisma 7,
62
+ @aventara/prisma7-adapter)`" — where 0.1.0-pilot.2 said "available: Prisma 7", which is not a value the flag accepts.
63
+ - 2afc651: The `main.ts` lines `aventara new` and `aventara init` add now end with a commented-out, platform-level rate limiter
64
+ and a short note on why: `@nestjs/throttler` is a Nest guard and does not run on Aventara's routes, while Express
65
+ middleware does. Uncommented (after `npm i express-rate-limit` and its import), it limits the protocol's routes and
66
+ answers `429` past the limit. Nothing is installed or enabled by default.
67
+ - `CORS_ORIGINS="http://localhost:5173,http://localhost:3001"` is appended to the project's existing `.env` too — over an existing Prisma setup as well — every other line kept, and never when a line already sets `CORS_ORIGINS`; only a reused setup with no `.env` gets it as a printed next step.
68
+ - `--orm` takes an adapter's alias, `prisma7` (Prisma 7, `@aventara/prisma7-adapter`), declared in the adapter's manifest and listed by `--help`; `prisma@7` and `prisma` are no longer accepted.
69
+
70
+ ## 0.1.0-pilot.2
71
+
72
+ ### Patch Changes
73
+
74
+ - aa65a0c: `aventara new` and `aventara init` add `.env` to the project's `.gitignore` whenever they write the `DATABASE_URL`
75
+ entry into `.env`. The file is only appended to: existing lines are kept, and nothing is added when `.env` is already
76
+ listed.
77
+ - e69e88d: The published packages are written for the people who use them. The JavaScript ships without comments; the type
78
+ declarations keep their documentation, and it, the error messages, the protocol fixtures' labels and the comments in a
79
+ generated client no longer carry the project's internal development notes.
80
+ - 0ec37d4: `avclient --version` (and `-v`) prints the generator's version and exits 0, and `aventara` now accepts `-v` beside
81
+ `--version`, so both bins answer the same two flags.
82
+
83
+ ## 0.1.0-pilot.1
84
+
85
+ ### Patch Changes
86
+
87
+ - 7714ef3: The scaffold's e2e test now proves Aventara is mounted. `aventara new` and `aventara init` add a `GET /api/_contract`
88
+ → 200 test to Nest's `test/app.e2e-spec.ts`, right after its `GET /` test (ESM and CommonJS), when AventaraModule was
89
+ added and the spec is Nest's own; a spec of the developer's own is left untouched.
90
+ - 7f2b5d4: The frontend next step is now a command that runs as printed: `pnpm dlx` under pnpm (not `npx`), and, while the CLI is a
91
+ prerelease, the client's dist-tag read from the CLI's own version — `npx @aventara/client@pilot init` for
92
+ 0.1.0-pilot.N — so a frontend gets the client from the same release instead of nothing (`@aventara/client` has no
93
+ `latest`).
94
+ - 799346e: The package READMEs (each package's npm page) are rewritten for the people who use the packages. The Prisma 7
95
+ adapter's README now states the actual behaviour of a to-many relation or `$count` projected on
96
+ `update.many`/`create.many`: a validation refusal, `A2004` with `V1008`, before any statement runs (it said `A3001`).
97
+ Every install or run command names the `@pilot` dist-tag.
98
+ - e58aebf: The READMEs show how to call the API by hand: a copy-pasteable `curl` that fetches `/api/_contract` and sends a
99
+ `find.many` with both identity headers, and runs as printed against a scaffolded server. They also say that
100
+ Resource keys are the model names as written (`/_resources/User/…`, `avClient.User.find.many`), and that list results are
101
+ `readonly` arrays (`readonly User[]`).
102
+ - 88408be: `--help` (and `-h`) works after a command: `aventara new --help`, `aventara init --help`, `avclient init --help` and
103
+ `avclient generate --help` print that command's usage and exit 0, instead of refusing "unknown flag --help". Nothing
104
+ else on the line is read or run.
105
+
106
+ ## 0.1.0-pilot.0
107
+
108
+ ### Minor Changes
109
+
110
+ - b65a4f8: New package: `@aventara/cli`, bin `aventara`. `aventara new <name>` runs the pinned `nest new` and adds Aventara;
111
+ `aventara init` adds Aventara to an existing NestJS 12 project — the Prisma 7 schema, config and service when there is
112
+ no ORM yet, or the developer's own Prisma setup reused and never written. Every question has a flag; `--yes` accepts the
113
+ defaults; a non-interactive run without the answers refuses before writing anything. The ORMs and databases on offer
114
+ come from the adapter packages' own manifests.
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  The `aventara` command: it scaffolds an Aventara server on NestJS 12.
4
4
 
5
5
  ```bash
6
- npm i -g @aventara/cli@pilot # or run it once: npx @aventara/cli@pilot <command>
6
+ npm i -g @aventara/cli@rc # or run it once: npx @aventara/cli@rc <command>
7
7
  aventara new my-api # a new NestJS project with Aventara
8
8
  aventara init # Aventara added to the NestJS 12 project in this directory
9
9
  ```
10
10
 
11
11
  It is run once, globally or through `npx`; it is not a dependency of the projects it writes. The frontend's side —
12
- `framework.client.ts` and the generated client — is `@aventara/client`'s (`npx @aventara/client@pilot init`).
12
+ `framework.client.ts` and the generated client — is `@aventara/client`'s (`npx @aventara/client@rc init`).
13
13
 
14
14
  ## `aventara new <name>`
15
15
 
@@ -51,6 +51,11 @@ possible answer is shown, not asked.
51
51
  | `--skip-install` | Write the files; print the install instead of running it. | |
52
52
  | `--skip-git` (`new`) | Passed to `nest new`. | |
53
53
  | `-y`, `--yes` | Accept every unanswered default **and** replace existing content that differs. | |
54
+ | `--verbose` | Show the output of `nest new` and the install as they run. | |
55
+
56
+ While it works, each step is shown on stderr — `[3/4] Installing dependencies (npm install)` — with a spinner and the
57
+ seconds so far on a terminal, or as one plain line per step (`… done (12.4s)`) in a pipe, in CI (`CI`) or with
58
+ `NO_COLOR`; the output of `nest new` and the install is shown only if it fails, or with `--verbose`.
54
59
 
55
60
  Existing content that differs from what would be written (a `.env` key, a script, a file) is listed, and replaced only
56
61
  when you confirm, or with `--yes`; where nobody can be asked, the run stops and touches nothing.
@@ -76,11 +81,11 @@ when you confirm, or with `--yes`; where nobody can be asked, the run stops and
76
81
  ```bash
77
82
  npx prisma db push # create the tables (init never touches a database); pnpm: pnpm exec prisma db push
78
83
  npm run start:dev # GET http://localhost:3000/api/_contract
79
- npx @aventara/client@pilot init # in your frontend; pnpm: pnpm dlx @aventara/client@pilot init
84
+ npx @aventara/client@rc init # in your frontend; pnpm: pnpm dlx @aventara/client@rc init
80
85
  ```
81
86
 
82
87
  The frontend command is the package manager's own runner (`npx`, or `pnpm dlx` under pnpm) and, while the CLI is a
83
- prerelease, carries its dist-tag (`@pilot`), so the client comes from the same release.
88
+ prerelease, carries its dist-tag (`@rc`), so the client comes from the same release.
84
89
 
85
90
  With PostgreSQL, set `DATABASE_URL` in `.env` first (init writes Prisma's placeholder, and never asks for a secret).
86
91
 
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ 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";
8
8
  import { runNew } from "./run/new.orchestrator.js";
9
+ import { progressModeOf } from "./run/step.reporter.js";
9
10
  import { UnansweredQuestionsError, } from "./wizard/answer.resolver.js";
10
11
  import { createReadlinePrompter, WizardCancelledError, } from "./wizard/readline.prompter.js";
11
12
  import { WizardAnswerError } from "./wizard/wizard.questions.js";
@@ -47,6 +48,7 @@ export async function runCli(argv, io) {
47
48
  runner: io.runner ?? runCommand,
48
49
  catalog: io.catalog ?? ADAPTER_CATALOG,
49
50
  aventaraVersion: ownVersion(),
51
+ progressMode: io.progress ?? "off",
50
52
  });
51
53
  }
52
54
  return await runNew(command, {
@@ -58,6 +60,7 @@ export async function runCli(argv, io) {
58
60
  runner: io.runner ?? runCommand,
59
61
  catalog: io.catalog ?? ADAPTER_CATALOG,
60
62
  aventaraVersion: ownVersion(),
63
+ progressMode: io.progress ?? "off",
61
64
  });
62
65
  }
63
66
  catch (error) {
@@ -85,6 +88,7 @@ export async function runFromProcess() {
85
88
  process.stderr.write(text);
86
89
  },
87
90
  prompter,
91
+ progress: progressModeOf(process.stderr.isTTY, process.env),
88
92
  });
89
93
  }
90
94
  finally {
@@ -22,6 +22,12 @@ const OPTIONS = [
22
22
  commands: ["new", "init"],
23
23
  summary: "accept every unanswered question's default, and confirm overwriting existing content",
24
24
  },
25
+ {
26
+ flag: "--verbose",
27
+ key: "verbose",
28
+ commands: ["new", "init"],
29
+ summary: "show the output of every command it runs as it runs (nest new, the install)",
30
+ },
25
31
  ];
26
32
  const QUESTIONS = {
27
33
  new: NEW_QUESTIONS,
@@ -165,5 +171,6 @@ export function parseCliCommand(argv) {
165
171
  skipInstall: options.skipInstall === true,
166
172
  skipGit: options.skipGit === true,
167
173
  yes: options.yes === true,
174
+ ...(options.verbose === true ? { verbose: true } : {}),
168
175
  };
169
176
  }
@@ -9,9 +9,11 @@ export const runCommand = (command, args, options) => new Promise((resolve, reje
9
9
  let stderr = "";
10
10
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
11
11
  stdout += chunk;
12
+ options.onOutput?.(chunk);
12
13
  });
13
14
  child.stderr.setEncoding("utf8").on("data", (chunk) => {
14
15
  stderr += chunk;
16
+ options.onOutput?.(chunk);
15
17
  });
16
18
  child.once("error", reject);
17
19
  child.once("close", (code) => {
@@ -9,10 +9,20 @@ import { scanServices } from "../project/service.detector.js";
9
9
  import { templatesFor, } from "../templates/template.registry.js";
10
10
  import { resolveAnswers } from "../wizard/answer.resolver.js";
11
11
  import { INIT_QUESTIONS, REUSE_QUESTIONS, } from "../wizard/wizard.questions.js";
12
+ import { createStepReporter, } from "./step.reporter.js";
13
+ export function printNextSteps(stdout, steps) {
14
+ stdout(`Next:\n${steps.map((step, index) => ` ${index + 1}. ${step}`).join("\n")}\n`);
15
+ }
12
16
  export class InstallFailedError extends Error {
13
17
  name = "InstallFailedError";
14
18
  }
15
19
  export async function runInit(command, io) {
20
+ const { progress, nextSteps } = await initProject(command, io);
21
+ progress.finish("aventara", "done");
22
+ printNextSteps(io.stdout, nextSteps);
23
+ return 0;
24
+ }
25
+ export async function initProject(command, io) {
16
26
  const inspection = inspectProject(io.directory, io.catalog);
17
27
  const read = (file) => {
18
28
  try {
@@ -48,11 +58,20 @@ export async function runInit(command, io) {
48
58
  prompter: io.prompter,
49
59
  });
50
60
  const writes = plan.conflicts.length === 0 ? plan.keeping : plan.replacing;
51
- for (const write of writes) {
52
- const target = path.join(io.directory, write.path);
53
- mkdirSync(path.dirname(target), { recursive: true });
54
- writeFileSync(target, write.content);
55
- }
61
+ const progress = io.progress ??
62
+ createStepReporter({
63
+ write: io.stderr,
64
+ mode: io.progressMode,
65
+ total: command.skipInstall ? 1 : 2,
66
+ verbose: command.verbose === true,
67
+ });
68
+ await progress.step("Writing Aventara's files", () => {
69
+ for (const write of writes) {
70
+ const target = path.join(io.directory, write.path);
71
+ mkdirSync(path.dirname(target), { recursive: true });
72
+ writeFileSync(target, write.content);
73
+ }
74
+ });
56
75
  for (const warning of plan.warnings) {
57
76
  io.stderr(`aventara: warning: ${warning}.\n`);
58
77
  }
@@ -63,16 +82,17 @@ export async function runInit(command, io) {
63
82
  io.stdout(`${plan.instructions}\n`);
64
83
  }
65
84
  if (!command.skipInstall) {
66
- io.stdout(`aventara: running ${packageManager} install…\n`);
67
- const installed = await io.runner(packageManager, ["install"], {
68
- cwd: io.directory,
85
+ await progress.step(`Installing dependencies (${packageManager} install)`, async () => {
86
+ const installed = await io.runner(packageManager, ["install"], {
87
+ cwd: io.directory,
88
+ onOutput: progress.output,
89
+ });
90
+ if (installed.code !== 0) {
91
+ throw new InstallFailedError(`\`${packageManager} install\` exited with code ${installed.code} after the files were written; fix what it reports and run \`${packageManager} install\` again:\n${installed.stderr.trimEnd()}`);
92
+ }
69
93
  });
70
- if (installed.code !== 0) {
71
- throw new InstallFailedError(`\`${packageManager} install\` exited with code ${installed.code} after the files were written; fix what it reports and run \`${packageManager} install\` again:\n${installed.stderr.trimEnd()}`);
72
- }
73
94
  }
74
- io.stdout(`Next:\n${[...(io.firstSteps ?? []), ...plan.nextSteps].map((step, index) => ` ${index + 1}. ${step}`).join("\n")}\n`);
75
- return 0;
95
+ return { progress, nextSteps: plan.nextSteps };
76
96
  }
77
97
  async function prepareFresh(command, io, inspection, detectedPackageManager) {
78
98
  const answers = await resolveAnswers(INIT_QUESTIONS, {
@@ -5,8 +5,9 @@ import { detectPackageManager } from "../project/package-manager.detector.js";
5
5
  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
- import { InstallFailedError, runInit, } from "./init.orchestrator.js";
8
+ import { InstallFailedError, initProject, printNextSteps, } from "./init.orchestrator.js";
9
9
  import { commitScaffold, gitSurroundings } from "./scaffold.committer.js";
10
+ import { createStepReporter } from "./step.reporter.js";
10
11
  export const NEST_CLI = "@nestjs/cli@12.0.8";
11
12
  export const NEST_APP_MODULE_SHA256 = "fe98c4cd2552342272494652287c02e4ef58db3072fcb18513ea831c60f85778";
12
13
  function oneOff(packageManager) {
@@ -50,11 +51,21 @@ export async function runNew(command, io) {
50
51
  "--no-observe",
51
52
  ...(command.skipGit ? ["--skip-git"] : []),
52
53
  ];
53
- io.stdout(`aventara: running ${runner} ${args.join(" ")}…\n`);
54
- const created = await io.runner(runner, args, { cwd: io.cwd });
55
- if (created.code !== 0) {
56
- throw new InstallFailedError(`\`${runner} ${args.join(" ")}\` exited with code ${created.code}, and nothing of Aventara's was written; fix what it reports and run aventara new again:\n${created.stderr.trimEnd()}`);
57
- }
54
+ const progress = createStepReporter({
55
+ write: io.stderr,
56
+ mode: io.progressMode,
57
+ total: 2 + (command.skipInstall ? 0 : 1) + (command.skipGit ? 0 : 1),
58
+ verbose: command.verbose === true,
59
+ });
60
+ await progress.step(`Creating the NestJS project (${runner} ${[...prefix, NEST_CLI, "new", name].join(" ")})`, async () => {
61
+ const created = await io.runner(runner, args, {
62
+ cwd: io.cwd,
63
+ onOutput: progress.output,
64
+ });
65
+ if (created.code !== 0) {
66
+ throw new InstallFailedError(`\`${runner} ${args.join(" ")}\` exited with code ${created.code}, and nothing of Aventara's was written; fix what it reports and run aventara new again:\n${created.stderr.trimEnd()}`);
67
+ }
68
+ });
58
69
  const appModule = path.join(directory, "src", "app.module.ts");
59
70
  const digest = existsSync(appModule)
60
71
  ? createHash("sha256").update(readFileSync(appModule)).digest("hex")
@@ -62,7 +73,7 @@ export async function runNew(command, io) {
62
73
  if (digest !== NEST_APP_MODULE_SHA256) {
63
74
  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`);
64
75
  }
65
- const initialized = await runInit({
76
+ const { nextSteps } = await initProject({
66
77
  command: "init",
67
78
  given: {
68
79
  orm: orm.alias,
@@ -72,9 +83,16 @@ export async function runNew(command, io) {
72
83
  skipInstall: command.skipInstall,
73
84
  skipGit: false,
74
85
  yes: true,
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`);
86
+ }, {
87
+ ...io,
88
+ directory,
89
+ progress,
90
+ });
91
+ if (surroundings !== undefined) {
92
+ const committed = await progress.step("Committing the scaffold", () => commitScaffold(io.runner, directory, surroundings, io.cwd));
93
+ io.stdout(`aventara: ${committed}.\n`);
78
94
  }
79
- return initialized;
95
+ progress.finish("aventara", `created ${name}`);
96
+ printNextSteps(io.stdout, [`Enter the project: cd ${name}`, ...nextSteps]);
97
+ return 0;
80
98
  }
@@ -0,0 +1,87 @@
1
+ export function progressModeOf(isTTY, env) {
2
+ const ci = env.CI;
3
+ if (isTTY !== true ||
4
+ (ci !== undefined && ci !== "" && ci !== "0" && ci !== "false") ||
5
+ (env.NO_COLOR !== undefined && env.NO_COLOR !== "") ||
6
+ env.TERM === "dumb") {
7
+ return "plain";
8
+ }
9
+ return "live";
10
+ }
11
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
12
+ const CLEAR_LINE = "\r\u001b[2K";
13
+ const GREEN = (text) => `\u001b[32m${text}\u001b[39m`;
14
+ const RED = (text) => `\u001b[31m${text}\u001b[39m`;
15
+ const DIM = (text) => `\u001b[2m${text}\u001b[22m`;
16
+ export function formatElapsed(milliseconds) {
17
+ const seconds = milliseconds / 1000;
18
+ if (seconds < 60) {
19
+ return `${seconds.toFixed(1)}s`;
20
+ }
21
+ const whole = Math.round(seconds);
22
+ return `${Math.floor(whole / 60)}m ${String(whole % 60).padStart(2, "0")}s`;
23
+ }
24
+ export function createStepReporter(options) {
25
+ const now = options.now ?? Date.now;
26
+ const started = now();
27
+ const verbose = options.verbose === true && options.mode !== "off";
28
+ const mode = verbose ? "plain" : options.mode;
29
+ let index = 0;
30
+ const step = async (title, body) => {
31
+ index += 1;
32
+ const label = `[${index}/${options.total}] ${title}`;
33
+ const at = now();
34
+ const elapsed = () => formatElapsed(now() - at);
35
+ if (mode === "off") {
36
+ return await body();
37
+ }
38
+ if (mode === "plain") {
39
+ options.write(verbose ? `${label}…\n` : `${label}… `);
40
+ try {
41
+ const value = await body();
42
+ options.write(verbose ? `${label}… done (${elapsed()})\n` : `done (${elapsed()})\n`);
43
+ return value;
44
+ }
45
+ catch (error) {
46
+ options.write(verbose
47
+ ? `${label}… failed (${elapsed()})\n`
48
+ : `failed (${elapsed()})\n`);
49
+ throw error;
50
+ }
51
+ }
52
+ let frame = 0;
53
+ const draw = () => {
54
+ const seconds = Math.floor((now() - at) / 1000);
55
+ options.write(`${CLEAR_LINE}${SPINNER[frame % SPINNER.length]} ${label}…${seconds > 0 ? DIM(` ${seconds}s`) : ""}`);
56
+ frame += 1;
57
+ };
58
+ draw();
59
+ const timer = setInterval(draw, options.interval ?? 80);
60
+ timer.unref?.();
61
+ try {
62
+ const value = await body();
63
+ clearInterval(timer);
64
+ options.write(`${CLEAR_LINE}${GREEN("✓")} ${label} ${DIM(elapsed())}\n`);
65
+ return value;
66
+ }
67
+ catch (error) {
68
+ clearInterval(timer);
69
+ options.write(`${CLEAR_LINE}${RED("✗")} ${label} ${DIM(elapsed())}\n`);
70
+ throw error;
71
+ }
72
+ };
73
+ return {
74
+ step,
75
+ output: (chunk) => {
76
+ if (verbose) {
77
+ options.write(chunk);
78
+ }
79
+ },
80
+ finish: (prefix, what) => {
81
+ if (mode !== "off") {
82
+ options.write(`${prefix}: ${what} in ${formatElapsed(now() - started)}.\n`);
83
+ }
84
+ },
85
+ verbose,
86
+ };
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/cli",
3
- "version": "0.1.0-pilot.4",
3
+ "version": "1.0.0-rc.0",
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",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "files": [
14
14
  "dist",
15
- "LICENSE-ADDITIONAL-PERMISSION.md"
15
+ "LICENSE-ADDITIONAL-PERMISSION.md",
16
+ "CHANGELOG.md"
16
17
  ],
17
18
  "publishConfig": {
18
19
  "access": "public"
@@ -23,7 +24,7 @@
23
24
  },
24
25
  "scripts": {
25
26
  "prebuild": "node scripts/adapter-catalog.generator.ts",
26
- "build": "tsc -p tsconfig.json --removeComments --declaration false",
27
+ "build": "node ../../scripts/build/build-output.cleaner.ts dist && tsc -p tsconfig.json --removeComments",
27
28
  "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
28
29
  "test": "vitest run --typecheck --config ../../vitest.config.mts --root .",
29
30
  "lint": "biome lint src scripts"
@@ -1,24 +0,0 @@
1
- /**
2
- * The scan is string- and comment-aware (a `@Module(` or an `imports: [` inside a
3
- * comment, a string or a template literal is not code), and it never reformats:
4
- * everything outside the two insertion points keeps its bytes. When the anchor is
5
- * not found exactly once — two `@Module`s, a computed `imports`, no `imports` at
6
- * all — nothing is edited and the caller prints the lines to add instead.
7
- */
8
- export type AppModuleWiring = {
9
- /** Complete import declarations, one per line. */
10
- readonly imports: readonly string[];
11
- /** `imports:` entries, each possibly multi-line, each ending with a comma. */
12
- readonly entries: readonly string[];
13
- };
14
- export type AppModuleEdit = {
15
- readonly kind: "edited";
16
- readonly text: string;
17
- } | {
18
- readonly kind: "not-anchored";
19
- readonly reason: string;
20
- };
21
- /** Splices `wiring` into `text` at the anchor, or says why there is no anchor. */
22
- export declare function spliceAppModule(text: string, wiring: AppModuleWiring): AppModuleEdit;
23
- /** What to add by hand when there is no anchor. */
24
- export declare function wiringInstructions(wiring: AppModuleWiring): string;
@@ -1,15 +0,0 @@
1
- import type { Prompter } from "../wizard/answer.resolver.js";
2
- /** Nobody confirmed replacing existing content. A refusal: nothing was touched. */
3
- export declare class ConflictsNotConfirmedError extends Error {
4
- readonly name = "ConflictsNotConfirmedError";
5
- }
6
- export declare function conflictWarning(conflicts: readonly string[]): string;
7
- /**
8
- * Resolves when `conflicts` may be replaced; throws when they may not.
9
- *
10
- * @throws ConflictsNotConfirmedError
11
- */
12
- export declare function confirmConflicts(conflicts: readonly string[], consent: {
13
- readonly yes: boolean;
14
- readonly prompter: Prompter;
15
- }): Promise<void>;
@@ -1,18 +0,0 @@
1
- /**
2
- * pilot.1 — the scaffold's own e2e spec also proves the protocol is mounted.
3
- *
4
- * `nest new`'s `test/app.e2e-spec.ts` (`@nestjs/schematics` 12's template, ESM
5
- * and CommonJS alike) tests `GET /` and nothing else. Like the `app.module.ts`
6
- * edit, this one is anchored on Nest's exact text: the `/ (GET)` test is found
7
- * exactly once, and a `GET <entrypoint>/_contract` test is added right after
8
- * it, through the spec's own `app` and `request`. A spec that is not Nest's —
9
- * the developer's own — is left as it is, and nothing is said: the test is a
10
- * convenience, and the developer's tests are theirs.
11
- */
12
- /** The e2e spec `nest new` writes. */
13
- export declare const E2E_SPEC = "test/app.e2e-spec.ts";
14
- /**
15
- * `spec` with the contract test after Nest's `GET /` test, or `undefined` when
16
- * the anchor is not there exactly once, or the contract test already is.
17
- */
18
- export declare function spliceContractTest(spec: string, entrypoint: string): string | undefined;
@@ -1,50 +0,0 @@
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;
@@ -1,51 +0,0 @@
1
- /**
2
- * The merges `aventara init` makes into files a project already has —
3
- * `package.json`, `.env`, `.gitignore`, `pnpm-workspace.yaml` — each answering the
4
- * merged text and the **conflicts**: values already there that differ from what
5
- * would be written (the `generateAt` rule's "content the tool did not produce"). A
6
- * value already equal to what would be written is no change; a conflict is
7
- * replaced only once confirmed (`conflict.confirmer.ts`), so each merge is
8
- * computed with conflicts both kept and replaced.
9
- */
10
- export type Merge = {
11
- readonly text: string;
12
- /** What differs, named for the person asked: `package.json's scripts["postinstall"]`. */
13
- readonly conflicts: readonly string[];
14
- };
15
- export type PackageManifest = {
16
- readonly [key: string]: unknown;
17
- readonly dependencies?: Readonly<Record<string, string>>;
18
- readonly devDependencies?: Readonly<Record<string, string>>;
19
- readonly scripts?: Readonly<Record<string, string>>;
20
- };
21
- export type ManifestChanges = {
22
- readonly dependencies: Readonly<Record<string, string>>;
23
- readonly devDependencies: Readonly<Record<string, string>>;
24
- /** Scripts to set; a different existing value is a conflict. */
25
- readonly scripts: Readonly<Record<string, string>>;
26
- /**
27
- * Scripts to rewrite only from a known value: `from` → `to`. Any other existing
28
- * value is a conflict; an absent script is left absent.
29
- */
30
- readonly rewrites: Readonly<Record<string, {
31
- readonly from: string;
32
- readonly to: string;
33
- }>>;
34
- };
35
- /** `package.json`, in npm's own layout: two-space JSON, dependencies sorted. */
36
- export declare function mergePackageManifest(text: string, changes: ManifestChanges, replaceConflicts: boolean): Merge;
37
- /** `.env`: a missing key is appended as `KEY="value"`, after a final newline. */
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;
45
- /** `.gitignore`: missing lines appended, nothing else touched. */
46
- export declare function mergeGitignore(text: string | undefined, additions: readonly string[]): Merge;
47
- /**
48
- * Keys missing under an existing `allowBuilds:` are added there; otherwise the
49
- * block is appended.
50
- */
51
- export declare function mergePnpmWorkspace(text: string | undefined, packages: readonly string[]): Merge;
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};