@adonisjs/assembler 6.1.3-2 → 6.1.3-20

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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The MIT License
2
2
 
3
- Copyright (c) 2023 AdonisJS Framework
3
+ Copyright (c) 2023 Harminder Virk
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <br />
4
4
 
5
- [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![synk-image]][synk-url]
5
+ [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]
6
6
 
7
7
  ## Introduction
8
8
  Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only.
@@ -21,8 +21,8 @@ In order to ensure that the AdonisJS community is welcoming to all, please revie
21
21
  ## License
22
22
  AdonisJS Assembler is open-sourced software licensed under the [MIT license](LICENSE.md).
23
23
 
24
- [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/assembler/test.yml?style=for-the-badge
25
- [gh-workflow-url]: https://github.com/adonisjs/assembler/actions/workflows/test.yml "Github action"
24
+ [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/assembler/checks.yml?style=for-the-badge
25
+ [gh-workflow-url]: https://github.com/adonisjs/assembler/actions/workflows/checks.yml "Github action"
26
26
 
27
27
  [npm-image]: https://img.shields.io/npm/v/@adonisjs/assembler/latest.svg?style=for-the-badge&logo=npm
28
28
  [npm-url]: https://npmjs.org/package/@adonisjs/assembler/v/latest "npm"
@@ -31,6 +31,3 @@ AdonisJS Assembler is open-sourced software licensed under the [MIT license](LIC
31
31
 
32
32
  [license-url]: LICENSE.md
33
33
  [license-image]: https://img.shields.io/github/license/adonisjs/ace?style=for-the-badge
34
-
35
- [synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/assembler?label=Synk%20Vulnerabilities&style=for-the-badge
36
- [synk-url]: https://snyk.io/test/github/adonisjs/assembler?targetFile=package.json "synk"
package/build/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Bundler } from './src/bundler.js';
2
2
  export { DevServer } from './src/dev_server.js';
3
+ export { TestRunner } from './src/test_runner.js';
package/build/index.js CHANGED
@@ -1,2 +1,11 @@
1
+ /*
2
+ * @adonisjs/assembler
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
1
9
  export { Bundler } from './src/bundler.js';
2
10
  export { DevServer } from './src/dev_server.js';
11
+ export { TestRunner } from './src/test_runner.js';
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { type Logger } from '@poppinss/cliui';
3
+ import type { AssetsBundlerOptions } from './types.js';
4
+ /**
5
+ * Exposes the API to start the development server for processing assets during
6
+ * development.
7
+ *
8
+ * - Here we are running the assets dev server in a child process.
9
+ * - Piping the output from the child process and reformatting it before writing it to
10
+ * process streams.
11
+ *
12
+ * AssetsDevServer is agnostic and can run any assets dev server. Be it Vite or Encore or
13
+ * even Webpack directly.
14
+ */
15
+ export declare class AssetsDevServer {
16
+ #private;
17
+ constructor(cwd: URL, options?: AssetsBundlerOptions);
18
+ /**
19
+ * Set a custom CLI UI logger
20
+ */
21
+ setLogger(logger: Logger): this;
22
+ /**
23
+ * Starts the assets bundler server. The assets bundler server process is
24
+ * considered as the secondary process and therefore we do not perform
25
+ * any cleanup if it dies.
26
+ */
27
+ start(): void;
28
+ /**
29
+ * Stop the dev server
30
+ */
31
+ stop(): void;
32
+ }
@@ -0,0 +1,158 @@
1
+ /*
2
+ * @adonisjs/core
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { cliui } from '@poppinss/cliui';
10
+ import { run } from './helpers.js';
11
+ /**
12
+ * Instance of CLIUI
13
+ */
14
+ const ui = cliui();
15
+ /**
16
+ * Exposes the API to start the development server for processing assets during
17
+ * development.
18
+ *
19
+ * - Here we are running the assets dev server in a child process.
20
+ * - Piping the output from the child process and reformatting it before writing it to
21
+ * process streams.
22
+ *
23
+ * AssetsDevServer is agnostic and can run any assets dev server. Be it Vite or Encore or
24
+ * even Webpack directly.
25
+ */
26
+ export class AssetsDevServer {
27
+ #cwd;
28
+ #logger = ui.logger;
29
+ #options;
30
+ #devServer;
31
+ /**
32
+ * Getting reference to colors library from logger
33
+ */
34
+ get #colors() {
35
+ return this.#logger.getColors();
36
+ }
37
+ constructor(cwd, options) {
38
+ this.#cwd = cwd;
39
+ this.#options = options;
40
+ }
41
+ /**
42
+ * Logs messages from vite dev server stdout and stderr
43
+ */
44
+ #logViteDevServerMessage(data) {
45
+ const dataString = data.toString();
46
+ const lines = dataString.split('\n');
47
+ /**
48
+ * Logging VITE ready in message with proper
49
+ * spaces and newlines
50
+ */
51
+ if (dataString.includes('ready in')) {
52
+ console.log('');
53
+ console.log(dataString.trim());
54
+ return;
55
+ }
56
+ /**
57
+ * Put a wrapper around vite network address log
58
+ */
59
+ if (dataString.includes('Local') && dataString.includes('Network')) {
60
+ const sticker = ui.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer());
61
+ lines.forEach((line) => {
62
+ if (line.trim()) {
63
+ sticker.add(line);
64
+ }
65
+ });
66
+ sticker.render();
67
+ return;
68
+ }
69
+ /**
70
+ * Log rest of the lines
71
+ */
72
+ lines.forEach((line) => {
73
+ if (line.trim()) {
74
+ console.log(line);
75
+ }
76
+ });
77
+ }
78
+ /**
79
+ * Logs messages from assets dev server stdout and stderr
80
+ */
81
+ #logAssetsDevServerMessage(data) {
82
+ const dataString = data.toString();
83
+ const lines = dataString.split('\n');
84
+ lines.forEach((line) => {
85
+ if (line.trim()) {
86
+ console.log(line);
87
+ }
88
+ });
89
+ }
90
+ /**
91
+ * Set a custom CLI UI logger
92
+ */
93
+ setLogger(logger) {
94
+ this.#logger = logger;
95
+ return this;
96
+ }
97
+ /**
98
+ * Starts the assets bundler server. The assets bundler server process is
99
+ * considered as the secondary process and therefore we do not perform
100
+ * any cleanup if it dies.
101
+ */
102
+ start() {
103
+ if (!this.#options?.serve) {
104
+ return;
105
+ }
106
+ this.#logger.info(`starting "${this.#options.driver}" dev server...`);
107
+ /**
108
+ * Create child process
109
+ */
110
+ this.#devServer = run(this.#cwd, {
111
+ script: this.#options.cmd,
112
+ /**
113
+ * We do not inherit the stdio for vite and encore, because in
114
+ * inherit mode they own the stdin and interrupts the
115
+ * `Ctrl + C` command.
116
+ */
117
+ stdio: 'pipe',
118
+ scriptArgs: this.#options.args,
119
+ });
120
+ /**
121
+ * Log child process messages
122
+ */
123
+ this.#devServer.stdout?.on('data', (data) => {
124
+ if (this.#options.driver === 'vite') {
125
+ this.#logViteDevServerMessage(data);
126
+ }
127
+ else {
128
+ this.#logAssetsDevServerMessage(data);
129
+ }
130
+ });
131
+ this.#devServer.stderr?.on('data', (data) => {
132
+ if (this.#options.driver === 'vite') {
133
+ this.#logViteDevServerMessage(data);
134
+ }
135
+ else {
136
+ this.#logAssetsDevServerMessage(data);
137
+ }
138
+ });
139
+ this.#devServer
140
+ .then((result) => {
141
+ this.#logger.warning(`"${this.#options.driver}" dev server closed with status code "${result.exitCode}"`);
142
+ })
143
+ .catch((error) => {
144
+ this.#logger.warning(`unable to connect to "${this.#options.driver}" dev server`);
145
+ this.#logger.fatal(error);
146
+ });
147
+ }
148
+ /**
149
+ * Stop the dev server
150
+ */
151
+ stop() {
152
+ if (this.#devServer) {
153
+ this.#devServer.removeAllListeners();
154
+ this.#devServer.kill('SIGKILL');
155
+ this.#devServer = undefined;
156
+ }
157
+ }
158
+ }
@@ -2,9 +2,18 @@
2
2
  import type tsStatic from 'typescript';
3
3
  import { type Logger } from '@poppinss/cliui';
4
4
  import type { BundlerOptions } from './types.js';
5
+ /**
6
+ * The bundler class exposes the API to build an AdonisJS project.
7
+ */
5
8
  export declare class Bundler {
6
9
  #private;
7
10
  constructor(cwd: URL, ts: typeof tsStatic, options: BundlerOptions);
11
+ /**
12
+ * Set a custom CLI UI logger
13
+ */
8
14
  setLogger(logger: Logger): this;
15
+ /**
16
+ * Bundles the application to be run in production
17
+ */
9
18
  bundle(stopOnError?: boolean, client?: 'npm' | 'yarn' | 'pnpm'): Promise<boolean>;
10
19
  }
@@ -1,18 +1,33 @@
1
+ /*
2
+ * @adonisjs/assembler
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
1
9
  import slash from 'slash';
2
- import copyfiles from 'cpy';
3
10
  import fs from 'node:fs/promises';
4
11
  import { fileURLToPath } from 'node:url';
5
12
  import { join, relative } from 'node:path';
6
13
  import { cliui } from '@poppinss/cliui';
7
- import { run } from './run.js';
8
- import { parseConfig } from './parse_config.js';
14
+ import { run, parseConfig, copyFiles } from './helpers.js';
15
+ /**
16
+ * Instance of CLIUI
17
+ */
9
18
  const ui = cliui();
19
+ /**
20
+ * The bundler class exposes the API to build an AdonisJS project.
21
+ */
10
22
  export class Bundler {
11
23
  #cwd;
12
24
  #cwdPath;
13
25
  #ts;
14
26
  #logger = ui.logger;
15
27
  #options;
28
+ /**
29
+ * Getting reference to colors library from logger
30
+ */
16
31
  get #colors() {
17
32
  return this.#logger.getColors();
18
33
  }
@@ -25,9 +40,15 @@ export class Bundler {
25
40
  #getRelativeName(filePath) {
26
41
  return slash(relative(this.#cwdPath, filePath));
27
42
  }
43
+ /**
44
+ * Cleans up the build directory
45
+ */
28
46
  async #cleanupBuildDirectory(outDir) {
29
47
  await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 });
30
48
  }
49
+ /**
50
+ * Runs assets bundler command to build assets.
51
+ */
31
52
  async #buildAssets() {
32
53
  const assetsBundler = this.#options.assets;
33
54
  if (!assetsBundler?.serve) {
@@ -38,7 +59,7 @@ export class Bundler {
38
59
  await run(this.#cwd, {
39
60
  stdio: 'inherit',
40
61
  script: assetsBundler.cmd,
41
- scriptArgs: [],
62
+ scriptArgs: assetsBundler.args,
42
63
  });
43
64
  return true;
44
65
  }
@@ -46,6 +67,9 @@ export class Bundler {
46
67
  return false;
47
68
  }
48
69
  }
70
+ /**
71
+ * Runs tsc command to build the source.
72
+ */
49
73
  async #runTsc(outDir) {
50
74
  try {
51
75
  await run(this.#cwd, {
@@ -59,9 +83,12 @@ export class Bundler {
59
83
  return false;
60
84
  }
61
85
  }
86
+ /**
87
+ * Copy files to destination directory
88
+ */
62
89
  async #copyFiles(files, outDir) {
63
90
  try {
64
- await copyfiles(files, outDir, { cwd: this.#cwdPath });
91
+ await copyFiles(files, this.#cwdPath, outDir);
65
92
  }
66
93
  catch (error) {
67
94
  if (!error.message.includes("the file doesn't exist")) {
@@ -69,12 +96,18 @@ export class Bundler {
69
96
  }
70
97
  }
71
98
  }
99
+ /**
100
+ * Copy meta files to the output directory
101
+ */
72
102
  async #copyMetaFiles(outDir, additionalFilesToCopy) {
73
103
  const metaFiles = (this.#options.metaFiles || [])
74
104
  .map((file) => file.pattern)
75
105
  .concat(additionalFilesToCopy);
76
106
  await this.#copyFiles(metaFiles, outDir);
77
107
  }
108
+ /**
109
+ * Copies .adonisrc.json file to the destination
110
+ */
78
111
  async #copyAdonisRcFile(outDir) {
79
112
  const existingContents = JSON.parse(await fs.readFile(join(this.#cwdPath, '.adonisrc.json'), 'utf-8'));
80
113
  const compiledContents = Object.assign({}, existingContents, {
@@ -84,6 +117,9 @@ export class Bundler {
84
117
  await fs.mkdir(outDir, { recursive: true });
85
118
  await fs.writeFile(join(outDir, '.adonisrc.json'), JSON.stringify(compiledContents, null, 2) + '\n');
86
119
  }
120
+ /**
121
+ * Returns the lock file name for a given packages client
122
+ */
87
123
  #getClientLockFile(client) {
88
124
  switch (client) {
89
125
  case 'npm':
@@ -94,6 +130,9 @@ export class Bundler {
94
130
  return 'pnpm-lock.yaml';
95
131
  }
96
132
  }
133
+ /**
134
+ * Returns the installation command for a given packages client
135
+ */
97
136
  #getClientInstallCommand(client) {
98
137
  switch (client) {
99
138
  case 'npm':
@@ -104,24 +143,46 @@ export class Bundler {
104
143
  return 'pnpm i --prod';
105
144
  }
106
145
  }
146
+ /**
147
+ * Set a custom CLI UI logger
148
+ */
107
149
  setLogger(logger) {
108
150
  this.#logger = logger;
109
151
  return this;
110
152
  }
153
+ /**
154
+ * Bundles the application to be run in production
155
+ */
111
156
  async bundle(stopOnError = true, client = 'npm') {
157
+ /**
158
+ * Step 1: Parse config file to get the build output directory
159
+ */
112
160
  const config = parseConfig(this.#cwd, this.#ts);
113
161
  if (!config) {
114
162
  return false;
115
163
  }
164
+ /**
165
+ * Step 2: Cleanup existing build directory (if any)
166
+ */
116
167
  const outDir = config.options.outDir || fileURLToPath(new URL('build/', this.#cwd));
117
168
  this.#logger.info('cleaning up output directory', { suffix: this.#getRelativeName(outDir) });
118
169
  await this.#cleanupBuildDirectory(outDir);
170
+ /**
171
+ * Step 3: Build frontend assets
172
+ */
119
173
  if (!(await this.#buildAssets())) {
120
174
  return false;
121
175
  }
176
+ /**
177
+ * Step 4: Build typescript source code
178
+ */
122
179
  this.#logger.info('compiling typescript source', { suffix: 'tsc' });
123
180
  const buildCompleted = await this.#runTsc(outDir);
124
181
  await this.#copyFiles(['ace.js'], outDir);
182
+ /**
183
+ * Remove incomplete build directory when tsc build
184
+ * failed and stopOnError is set to true.
185
+ */
125
186
  if (!buildCompleted && stopOnError) {
126
187
  await this.#cleanupBuildDirectory(outDir);
127
188
  const instructions = ui
@@ -133,13 +194,22 @@ export class Bundler {
133
194
  this.#logger.logError(instructions.prepare());
134
195
  return false;
135
196
  }
197
+ /**
198
+ * Step 5: Copy meta files to the build directory
199
+ */
136
200
  const pkgFiles = ['package.json', this.#getClientLockFile(client)];
137
201
  this.#logger.info('copying meta files to the output directory');
138
202
  await this.#copyMetaFiles(outDir, pkgFiles);
203
+ /**
204
+ * Step 6: Copy .adonisrc.json file to the build directory
205
+ */
139
206
  this.#logger.info('copying .adonisrc.json file to the output directory');
140
207
  await this.#copyAdonisRcFile(outDir);
141
208
  this.#logger.success('build completed');
142
209
  this.#logger.log('');
210
+ /**
211
+ * Next steps
212
+ */
143
213
  ui.instructions()
144
214
  .useRenderer(this.#logger.getRenderer())
145
215
  .heading('Run the following commands to start the server in production')
@@ -0,0 +1,28 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { RcFileTransformer } from './rc_file_transformer.js';
3
+ import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js';
4
+ /**
5
+ * This class is responsible for updating
6
+ */
7
+ export declare class CodeTransformer {
8
+ #private;
9
+ constructor(cwd: URL);
10
+ /**
11
+ * Update the `adonisrc.ts` file
12
+ */
13
+ updateRcFile(callback: (transformer: RcFileTransformer) => void): Promise<void>;
14
+ /**
15
+ * Define new middlewares inside the `start/kernel.ts`
16
+ * file
17
+ *
18
+ * This function is highly based on some assumptions
19
+ * and will not work if you significantly tweaked
20
+ * your `start/kernel.ts` file.
21
+ */
22
+ addMiddlewareToStack(stack: 'server' | 'router' | 'named', middleware: AddMiddlewareEntry[]): Promise<void>;
23
+ /**
24
+ * Add new env variable validation in the
25
+ * `env.ts` file
26
+ */
27
+ defineEnvValidations(definition: EnvValidationDefinition): Promise<void>;
28
+ }
@@ -0,0 +1,201 @@
1
+ /*
2
+ * @adonisjs/assembler
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { join } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { Node, Project, SyntaxKind } from 'ts-morph';
12
+ import { RcFileTransformer } from './rc_file_transformer.js';
13
+ /**
14
+ * This class is responsible for updating
15
+ */
16
+ export class CodeTransformer {
17
+ /**
18
+ * Directory of the adonisjs project
19
+ */
20
+ #cwd;
21
+ /**
22
+ * The TsMorph project
23
+ */
24
+ #project;
25
+ /**
26
+ * Settings to use when persisting files
27
+ */
28
+ #editorSettings = {
29
+ indentSize: 2,
30
+ convertTabsToSpaces: true,
31
+ trimTrailingWhitespace: true,
32
+ };
33
+ constructor(cwd) {
34
+ this.#cwd = cwd;
35
+ this.#project = new Project({
36
+ tsConfigFilePath: join(fileURLToPath(this.#cwd), 'tsconfig.json'),
37
+ });
38
+ }
39
+ /**
40
+ * Update the `adonisrc.ts` file
41
+ */
42
+ async updateRcFile(callback) {
43
+ const rcFileTransformer = new RcFileTransformer(this.#cwd, this.#project);
44
+ callback(rcFileTransformer);
45
+ await rcFileTransformer.save();
46
+ }
47
+ /**
48
+ * Add a new middleware to the middleware array of the
49
+ * given file
50
+ */
51
+ #addToMiddlewareArray(file, target, middlewareEntry) {
52
+ const callExpressions = file
53
+ .getDescendantsOfKind(SyntaxKind.CallExpression)
54
+ .filter((statement) => statement.getExpression().getText() === target);
55
+ if (!callExpressions.length) {
56
+ throw new Error(`Cannot find ${target} statement in the file.`);
57
+ }
58
+ const arrayLiteralExpression = callExpressions[0].getArguments()[0];
59
+ if (!arrayLiteralExpression || !Node.isArrayLiteralExpression(arrayLiteralExpression)) {
60
+ throw new Error(`Cannot find middleware array in ${target} statement.`);
61
+ }
62
+ const middleware = `() => import('${middlewareEntry.path}')`;
63
+ /**
64
+ * Delete the existing middleware if it exists
65
+ */
66
+ const existingMiddleware = arrayLiteralExpression
67
+ .getElements()
68
+ .findIndex((element) => element.getText() === middleware);
69
+ if (existingMiddleware !== -1) {
70
+ arrayLiteralExpression.removeElement(existingMiddleware);
71
+ }
72
+ /**
73
+ * Add the middleware to the top or bottom of the array
74
+ */
75
+ if (middlewareEntry.position === 'before') {
76
+ arrayLiteralExpression.insertElement(0, middleware);
77
+ }
78
+ else {
79
+ arrayLiteralExpression.addElement(middleware);
80
+ }
81
+ }
82
+ /**
83
+ * Add a new middleware to the named middleware of the given file
84
+ */
85
+ #addToNamedMiddleware(file, middlewareEntry) {
86
+ if (!middlewareEntry.name)
87
+ throw new Error('Named middleware requires a name.');
88
+ const callArguments = file
89
+ .getVariableDeclarationOrThrow('middleware')
90
+ .getInitializerIfKindOrThrow(SyntaxKind.CallExpression)
91
+ .getArguments();
92
+ if (callArguments.length === 0) {
93
+ throw new Error('Named middleware call has no arguments.');
94
+ }
95
+ const namedMiddlewareObject = callArguments[0];
96
+ if (!Node.isObjectLiteralExpression(namedMiddlewareObject)) {
97
+ throw new Error('The argument of the named middleware call is not an object literal.');
98
+ }
99
+ /**
100
+ * Check if property is already defined. If so, remove it
101
+ */
102
+ const existingProperty = namedMiddlewareObject.getProperty(middlewareEntry.name);
103
+ if (existingProperty)
104
+ existingProperty.remove();
105
+ /**
106
+ * Add the named middleware
107
+ */
108
+ const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')`;
109
+ namedMiddlewareObject.insertProperty(0, middleware);
110
+ }
111
+ /**
112
+ * Write a leading comment
113
+ */
114
+ #addLeadingComment(writer, comment) {
115
+ if (!comment)
116
+ return writer.blankLine();
117
+ return writer
118
+ .blankLine()
119
+ .writeLine('/*')
120
+ .writeLine(`|----------------------------------------------------------`)
121
+ .writeLine(`| ${comment}`)
122
+ .writeLine(`|----------------------------------------------------------`)
123
+ .writeLine(`*/`);
124
+ }
125
+ /**
126
+ * Define new middlewares inside the `start/kernel.ts`
127
+ * file
128
+ *
129
+ * This function is highly based on some assumptions
130
+ * and will not work if you significantly tweaked
131
+ * your `start/kernel.ts` file.
132
+ */
133
+ async addMiddlewareToStack(stack, middleware) {
134
+ /**
135
+ * Get the `start/kernel.ts` source file
136
+ */
137
+ const kernelUrl = fileURLToPath(new URL('./start/kernel.ts', this.#cwd));
138
+ const file = this.#project.getSourceFileOrThrow(kernelUrl);
139
+ /**
140
+ * Process each middleware entry
141
+ */
142
+ for (const middlewareEntry of middleware) {
143
+ if (stack === 'named') {
144
+ this.#addToNamedMiddleware(file, middlewareEntry);
145
+ }
146
+ else {
147
+ this.#addToMiddlewareArray(file, `${stack}.use`, middlewareEntry);
148
+ }
149
+ }
150
+ file.formatText(this.#editorSettings);
151
+ await file.save();
152
+ }
153
+ /**
154
+ * Add new env variable validation in the
155
+ * `env.ts` file
156
+ */
157
+ async defineEnvValidations(definition) {
158
+ /**
159
+ * Get the `start/env.ts` source file
160
+ */
161
+ const kernelUrl = fileURLToPath(new URL('./start/env.ts', this.#cwd));
162
+ const file = this.#project.getSourceFileOrThrow(kernelUrl);
163
+ /**
164
+ * Get the `Env.create` call expression
165
+ */
166
+ const callExpressions = file
167
+ .getDescendantsOfKind(SyntaxKind.CallExpression)
168
+ .filter((statement) => statement.getExpression().getText() === 'Env.create');
169
+ if (!callExpressions.length) {
170
+ throw new Error(`Cannot find Env.create statement in the file.`);
171
+ }
172
+ const objectLiteralExpression = callExpressions[0].getArguments()[1];
173
+ if (!Node.isObjectLiteralExpression(objectLiteralExpression)) {
174
+ throw new Error(`The second argument of Env.create is not an object literal.`);
175
+ }
176
+ let firstAdded = false;
177
+ /**
178
+ * Add each variable validation
179
+ */
180
+ for (const [variable, validation] of Object.entries(definition.variables)) {
181
+ /**
182
+ * Check if the variable is already defined. If so, remove it
183
+ */
184
+ const existingProperty = objectLiteralExpression.getProperty(variable);
185
+ if (existingProperty)
186
+ existingProperty.remove();
187
+ objectLiteralExpression.addPropertyAssignment({
188
+ name: variable,
189
+ initializer: validation,
190
+ leadingTrivia: (writer) => {
191
+ if (firstAdded)
192
+ return;
193
+ firstAdded = true;
194
+ return this.#addLeadingComment(writer, definition.leadingComment);
195
+ },
196
+ });
197
+ }
198
+ file.formatText(this.#editorSettings);
199
+ await file.save();
200
+ }
201
+ }