@emeryld/manager 0.4.2 → 0.4.3
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/dist/create-package/index.js +23 -4
- package/dist/create-package/shared.js +63 -3
- package/dist/create-package/variants/client.js +8 -4
- package/dist/create-package/variants/contract.js +5 -4
- package/dist/create-package/variants/docker.js +10 -9
- package/dist/create-package/variants/empty.js +5 -4
- package/dist/create-package/variants/server.js +5 -4
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, stat } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { stdin as input } from 'node:process';
|
|
@@ -105,6 +105,10 @@ function parseCreateCliArgs(argv) {
|
|
|
105
105
|
options.skipBuild = true;
|
|
106
106
|
continue;
|
|
107
107
|
}
|
|
108
|
+
if (arg === '--reset') {
|
|
109
|
+
options.reset = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
108
112
|
if (arg === '--help' || arg === '-h') {
|
|
109
113
|
options.help = true;
|
|
110
114
|
continue;
|
|
@@ -123,6 +127,7 @@ function printCreateHelp() {
|
|
|
123
127
|
console.log(' pnpm manager-cli create --describe rrr-server');
|
|
124
128
|
console.log(' pnpm manager-cli create --variant rrr-client --dir packages/rrr-client --name @scope/client');
|
|
125
129
|
console.log(' pnpm manager-cli create --variant rrr-server --contract @scope/contract --skip-install');
|
|
130
|
+
console.log(' pnpm manager-cli create --variant rrr-server --reset # blow away an existing target before scaffolding');
|
|
126
131
|
console.log('');
|
|
127
132
|
console.log('Flags:');
|
|
128
133
|
console.log(' --list, -l Show available templates');
|
|
@@ -131,16 +136,28 @@ function printCreateHelp() {
|
|
|
131
136
|
console.log(' --dir, --path, -p Target directory (skips path prompt)');
|
|
132
137
|
console.log(' --name, -n Package name (skips name prompt)');
|
|
133
138
|
console.log(' --contract Contract import to inject (server/client variants)');
|
|
139
|
+
console.log(' --reset Remove the target directory if it already exists');
|
|
134
140
|
console.log(' --skip-install Do not run pnpm install after scaffolding');
|
|
135
141
|
console.log(' --skip-build Skip build after scaffolding');
|
|
136
142
|
console.log(' --help, -h Show this help');
|
|
137
143
|
}
|
|
138
|
-
async function ensureTargetDir(targetDir) {
|
|
144
|
+
async function ensureTargetDir(targetDir, options) {
|
|
145
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
146
|
+
const shouldReset = options?.reset ?? false;
|
|
147
|
+
if (shouldReset && resolvedTarget === workspaceRoot) {
|
|
148
|
+
throw new Error('Refusing to reset the workspace root directory.');
|
|
149
|
+
}
|
|
139
150
|
try {
|
|
140
151
|
const stats = await stat(targetDir);
|
|
141
152
|
if (!stats.isDirectory()) {
|
|
142
153
|
throw new Error(`Target "${targetDir}" exists and is not a directory.`);
|
|
143
154
|
}
|
|
155
|
+
if (shouldReset) {
|
|
156
|
+
logGlobal(`Resetting existing target ${path.relative(workspaceRoot, resolvedTarget) || '.'}…`, colors.yellow);
|
|
157
|
+
await rm(resolvedTarget, { recursive: true, force: true });
|
|
158
|
+
await mkdir(resolvedTarget, { recursive: true });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
144
161
|
const entries = await readdir(targetDir);
|
|
145
162
|
if (entries.length > 0) {
|
|
146
163
|
logGlobal(`Target ${path.relative(workspaceRoot, targetDir)} is not empty; existing files will be preserved.`, colors.yellow);
|
|
@@ -150,7 +167,7 @@ async function ensureTargetDir(targetDir) {
|
|
|
150
167
|
if (error &&
|
|
151
168
|
typeof error === 'object' &&
|
|
152
169
|
error.code === 'ENOENT') {
|
|
153
|
-
await mkdir(
|
|
170
|
+
await mkdir(resolvedTarget, { recursive: true });
|
|
154
171
|
return;
|
|
155
172
|
}
|
|
156
173
|
throw error;
|
|
@@ -329,12 +346,13 @@ async function gatherTarget(initial = {}) {
|
|
|
329
346
|
? await askLine(`Package name? (${fallbackName}): `)
|
|
330
347
|
: initial.pkgName;
|
|
331
348
|
const pkgName = (nameAnswer || fallbackName).trim() || fallbackName;
|
|
332
|
-
await ensureTargetDir(targetDir);
|
|
349
|
+
await ensureTargetDir(targetDir, { reset: initial.reset });
|
|
333
350
|
return {
|
|
334
351
|
variant,
|
|
335
352
|
targetDir,
|
|
336
353
|
pkgName,
|
|
337
354
|
contractName: initial.contractName,
|
|
355
|
+
reset: initial.reset,
|
|
338
356
|
};
|
|
339
357
|
}
|
|
340
358
|
export async function createRrrPackage(options = {}) {
|
|
@@ -381,6 +399,7 @@ export async function runCreatePackageCli(argv) {
|
|
|
381
399
|
targetDir,
|
|
382
400
|
pkgName: parsed.pkgName,
|
|
383
401
|
contractName: parsed.contractName,
|
|
402
|
+
reset: parsed.reset,
|
|
384
403
|
skipInstall: parsed.skipInstall,
|
|
385
404
|
skipBuild: parsed.skipBuild ?? parsed.skipInstall,
|
|
386
405
|
});
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
export const workspaceRoot = process.cwd();
|
|
5
|
+
function pathExists(target) {
|
|
6
|
+
return access(target)
|
|
7
|
+
.then(() => true)
|
|
8
|
+
.catch(() => false);
|
|
9
|
+
}
|
|
10
|
+
function toPosixPath(value) {
|
|
11
|
+
return value.split(path.sep).join('/');
|
|
12
|
+
}
|
|
4
13
|
export async function writeFileIfMissing(baseDir, relative, contents) {
|
|
5
14
|
const fullPath = path.join(baseDir, relative);
|
|
6
15
|
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
@@ -23,7 +32,8 @@ export async function writeFileIfMissing(baseDir, relative, contents) {
|
|
|
23
32
|
return 'created';
|
|
24
33
|
}
|
|
25
34
|
export function baseTsConfig(options) {
|
|
26
|
-
|
|
35
|
+
const config = {
|
|
36
|
+
...(options?.extends ? { extends: options.extends } : {}),
|
|
27
37
|
compilerOptions: {
|
|
28
38
|
target: 'ES2020',
|
|
29
39
|
module: 'NodeNext',
|
|
@@ -41,11 +51,15 @@ export function baseTsConfig(options) {
|
|
|
41
51
|
},
|
|
42
52
|
include: options?.include ?? ['src/**/*'],
|
|
43
53
|
exclude: options?.exclude ?? ['dist', 'node_modules'],
|
|
44
|
-
}
|
|
54
|
+
};
|
|
55
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
45
56
|
}
|
|
46
57
|
export function baseEslintConfig(tsconfigPath = './tsconfig.json') {
|
|
47
58
|
return `import tseslint from 'typescript-eslint'
|
|
48
59
|
import prettierPlugin from 'eslint-plugin-prettier'
|
|
60
|
+
import { fileURLToPath } from 'node:url'
|
|
61
|
+
|
|
62
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
49
63
|
|
|
50
64
|
export default tseslint.config(
|
|
51
65
|
{ ignores: ['dist', 'node_modules'] },
|
|
@@ -55,7 +69,7 @@ export default tseslint.config(
|
|
|
55
69
|
languageOptions: {
|
|
56
70
|
parserOptions: {
|
|
57
71
|
project: ${JSON.stringify(tsconfigPath)},
|
|
58
|
-
tsconfigRootDir:
|
|
72
|
+
tsconfigRootDir: __dirname,
|
|
59
73
|
},
|
|
60
74
|
},
|
|
61
75
|
plugins: {
|
|
@@ -125,6 +139,8 @@ const DEFAULT_GITIGNORE_ENTRIES = [
|
|
|
125
139
|
'.env',
|
|
126
140
|
'coverage',
|
|
127
141
|
'*.log',
|
|
142
|
+
'.vscode',
|
|
143
|
+
'.husky',
|
|
128
144
|
];
|
|
129
145
|
export function gitignoreFrom(entries = DEFAULT_GITIGNORE_ENTRIES) {
|
|
130
146
|
return entries.join('\n');
|
|
@@ -199,6 +215,10 @@ function stripUndefined(obj) {
|
|
|
199
215
|
}
|
|
200
216
|
export function basePackageJson(options) {
|
|
201
217
|
const applyDefaults = options.useDefaults ?? true;
|
|
218
|
+
const inheritPackageManager = options.inheritPackageManager ?? true;
|
|
219
|
+
const packageManager = inheritPackageManager && !options.extraFields?.packageManager
|
|
220
|
+
? readRootPackageManager()
|
|
221
|
+
: undefined;
|
|
202
222
|
const pkg = stripUndefined({
|
|
203
223
|
name: options.name,
|
|
204
224
|
version: options.version ?? '0.1.0',
|
|
@@ -221,7 +241,47 @@ export function basePackageJson(options) {
|
|
|
221
241
|
dependencies: options.dependencies,
|
|
222
242
|
devDependencies: options.devDependencies,
|
|
223
243
|
'lint-staged': LINT_STAGED_CONFIG,
|
|
244
|
+
packageManager,
|
|
224
245
|
...options.extraFields,
|
|
225
246
|
});
|
|
226
247
|
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
227
248
|
}
|
|
249
|
+
function readRootPackageManager() {
|
|
250
|
+
try {
|
|
251
|
+
const raw = readFileSync(path.join(workspaceRoot, 'package.json'), 'utf8');
|
|
252
|
+
const pkg = JSON.parse(raw);
|
|
253
|
+
return pkg.packageManager;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (error &&
|
|
257
|
+
typeof error === 'object' &&
|
|
258
|
+
error.code !== 'ENOENT') {
|
|
259
|
+
console.warn(`Could not read root package.json for packageManager: ${error instanceof Error ? error.message : String(error)}`);
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function resolveRootTsconfig() {
|
|
265
|
+
const candidates = ['tsconfig.base.json', 'tsconfig.json'];
|
|
266
|
+
for (const candidate of candidates) {
|
|
267
|
+
const fullPath = path.join(workspaceRoot, candidate);
|
|
268
|
+
if (await pathExists(fullPath)) {
|
|
269
|
+
return fullPath;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
export async function packageTsConfig(targetDir, options) {
|
|
275
|
+
const extendsFromRoot = options?.extendsFromRoot ?? true;
|
|
276
|
+
let extendsPath;
|
|
277
|
+
if (extendsFromRoot) {
|
|
278
|
+
const rootConfig = await resolveRootTsconfig();
|
|
279
|
+
if (rootConfig) {
|
|
280
|
+
const relative = path.relative(targetDir, rootConfig);
|
|
281
|
+
if (relative !== '') {
|
|
282
|
+
extendsPath = toPosixPath(relative || './tsconfig.json');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return baseTsConfig({ ...options, extends: extendsPath });
|
|
287
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts,
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const CLIENT_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -45,10 +45,14 @@ export function clientPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
|
|
|
45
45
|
},
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
function clientFiles(pkgName, contractImport) {
|
|
48
|
+
async function clientFiles(pkgName, contractImport, targetDir) {
|
|
49
|
+
const tsconfig = await packageTsConfig(targetDir, {
|
|
50
|
+
lib: ['ES2020', 'DOM'],
|
|
51
|
+
types: ['node'],
|
|
52
|
+
});
|
|
49
53
|
return {
|
|
50
54
|
'package.json': clientPackageJson(pkgName, contractImport),
|
|
51
|
-
'tsconfig.json':
|
|
55
|
+
'tsconfig.json': tsconfig,
|
|
52
56
|
...basePackageFiles(),
|
|
53
57
|
'src/index.ts': clientIndexTs(contractImport),
|
|
54
58
|
'README.md': buildReadme({
|
|
@@ -92,7 +96,7 @@ export const clientVariant = {
|
|
|
92
96
|
],
|
|
93
97
|
async scaffold(ctx) {
|
|
94
98
|
const contractImport = ctx.contractName ?? CONTRACT_IMPORT_PLACEHOLDER;
|
|
95
|
-
const files = clientFiles(ctx.pkgName, contractImport);
|
|
99
|
+
const files = await clientFiles(ctx.pkgName, contractImport, ctx.targetDir);
|
|
96
100
|
for (const [relative, contents] of Object.entries(files)) {
|
|
97
101
|
// eslint-disable-next-line no-await-in-loop
|
|
98
102
|
await writeFileIfMissing(ctx.targetDir, relative, contents);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts,
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const CONTRACT_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -100,10 +100,11 @@ function contractPackageJson(name) {
|
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
function contractFiles(pkgName) {
|
|
103
|
+
async function contractFiles(pkgName, targetDir) {
|
|
104
|
+
const tsconfig = await packageTsConfig(targetDir);
|
|
104
105
|
return {
|
|
105
106
|
'package.json': contractPackageJson(pkgName),
|
|
106
|
-
'tsconfig.json':
|
|
107
|
+
'tsconfig.json': tsconfig,
|
|
107
108
|
...basePackageFiles(),
|
|
108
109
|
'src/index.ts': CONTRACT_TS,
|
|
109
110
|
'README.md': buildReadme({
|
|
@@ -141,7 +142,7 @@ export const contractVariant = {
|
|
|
141
142
|
'Edit src/index.ts to define routes and socket events; exports registry/socket config.',
|
|
142
143
|
],
|
|
143
144
|
async scaffold(ctx) {
|
|
144
|
-
const files = contractFiles(ctx.pkgName);
|
|
145
|
+
const files = await contractFiles(ctx.pkgName, ctx.targetDir);
|
|
145
146
|
for (const [relative, contents] of Object.entries(files)) {
|
|
146
147
|
// eslint-disable-next-line no-await-in-loop
|
|
147
148
|
await writeFileIfMissing(ctx.targetDir, relative, contents);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts,
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const DOCKER_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -41,7 +41,7 @@ function dockerPackageJson(name) {
|
|
|
41
41
|
'@types/cors': '^2.8.5',
|
|
42
42
|
'@types/express': '^5.0.6',
|
|
43
43
|
'@types/node': '^24.10.2',
|
|
44
|
-
'docker-cli-js': '^
|
|
44
|
+
'docker-cli-js': '^2.10.0',
|
|
45
45
|
},
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -95,10 +95,11 @@ EXPOSE 3000
|
|
|
95
95
|
CMD ["node", "dist/index.js"]
|
|
96
96
|
`;
|
|
97
97
|
}
|
|
98
|
-
function dockerFiles(pkgName) {
|
|
98
|
+
async function dockerFiles(pkgName, targetDir) {
|
|
99
|
+
const tsconfig = await packageTsConfig(targetDir, { types: ['node'] });
|
|
99
100
|
return {
|
|
100
101
|
'package.json': dockerPackageJson(pkgName),
|
|
101
|
-
'tsconfig.json':
|
|
102
|
+
'tsconfig.json': tsconfig,
|
|
102
103
|
'src/index.ts': dockerIndexTs(),
|
|
103
104
|
'scripts/docker.ts': dockerCliScript(pkgName),
|
|
104
105
|
'.dockerignore': DOCKER_DOCKERIGNORE,
|
|
@@ -190,8 +191,8 @@ async function main() {
|
|
|
190
191
|
async function safe(run: () => Promise<unknown>) {
|
|
191
192
|
try {
|
|
192
193
|
await run()
|
|
193
|
-
} catch (error) {
|
|
194
|
-
console.warn(String(error))
|
|
194
|
+
} catch (error: unknown) {
|
|
195
|
+
console.warn(error instanceof Error ? error.message : String(error))
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -211,8 +212,8 @@ function printHelp() {
|
|
|
211
212
|
)
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
main().catch((
|
|
215
|
-
console.error(
|
|
215
|
+
main().catch((error: unknown) => {
|
|
216
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
216
217
|
process.exit(1)
|
|
217
218
|
})
|
|
218
219
|
`;
|
|
@@ -234,7 +235,7 @@ export const dockerVariant = {
|
|
|
234
235
|
'scripts/docker.ts wraps common docker commands with consistent naming.',
|
|
235
236
|
],
|
|
236
237
|
async scaffold(ctx) {
|
|
237
|
-
const files = dockerFiles(ctx.pkgName);
|
|
238
|
+
const files = await dockerFiles(ctx.pkgName, ctx.targetDir);
|
|
238
239
|
for (const [relative, contents] of Object.entries(files)) {
|
|
239
240
|
// eslint-disable-next-line no-await-in-loop
|
|
240
241
|
await writeFileIfMissing(ctx.targetDir, relative, contents);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts,
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const EMPTY_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -19,10 +19,11 @@ function emptyPackageJson(name) {
|
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
function emptyFiles(pkgName) {
|
|
22
|
+
async function emptyFiles(pkgName, targetDir) {
|
|
23
|
+
const tsconfig = await packageTsConfig(targetDir, { types: ['node'] });
|
|
23
24
|
return {
|
|
24
25
|
'package.json': emptyPackageJson(pkgName),
|
|
25
|
-
'tsconfig.json':
|
|
26
|
+
'tsconfig.json': tsconfig,
|
|
26
27
|
...basePackageFiles(),
|
|
27
28
|
'src/index.ts': "export const hello = 'world'\n",
|
|
28
29
|
'README.md': buildReadme({
|
|
@@ -55,7 +56,7 @@ export const emptyVariant = {
|
|
|
55
56
|
scripts: EMPTY_SCRIPTS,
|
|
56
57
|
notes: ['Start coding in src/index.ts; everything else is wired up.'],
|
|
57
58
|
async scaffold(ctx) {
|
|
58
|
-
const files = emptyFiles(ctx.pkgName);
|
|
59
|
+
const files = await emptyFiles(ctx.pkgName, ctx.targetDir);
|
|
59
60
|
for (const [relative, contents] of Object.entries(files)) {
|
|
60
61
|
// eslint-disable-next-line no-await-in-loop
|
|
61
62
|
await writeFileIfMissing(ctx.targetDir, relative, contents);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts,
|
|
1
|
+
import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, packageTsConfig, writeFileIfMissing, } from '../shared.js';
|
|
2
2
|
const SERVER_SCRIPTS = [
|
|
3
3
|
'dev',
|
|
4
4
|
'build',
|
|
@@ -81,10 +81,11 @@ export function serverPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
|
|
|
81
81
|
},
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
function serverFiles(pkgName, contractImport) {
|
|
84
|
+
async function serverFiles(pkgName, contractImport, targetDir) {
|
|
85
|
+
const tsconfig = await packageTsConfig(targetDir, { types: ['node'] });
|
|
85
86
|
return {
|
|
86
87
|
'package.json': serverPackageJson(pkgName, contractImport),
|
|
87
|
-
'tsconfig.json':
|
|
88
|
+
'tsconfig.json': tsconfig,
|
|
88
89
|
...basePackageFiles(),
|
|
89
90
|
'src/index.ts': serverIndexTs(contractImport),
|
|
90
91
|
'.env.example': 'PORT=4000\n',
|
|
@@ -130,7 +131,7 @@ export const serverVariant = {
|
|
|
130
131
|
],
|
|
131
132
|
async scaffold(ctx) {
|
|
132
133
|
const contractImport = ctx.contractName ?? CONTRACT_IMPORT_PLACEHOLDER;
|
|
133
|
-
const files = serverFiles(ctx.pkgName, contractImport);
|
|
134
|
+
const files = await serverFiles(ctx.pkgName, contractImport, ctx.targetDir);
|
|
134
135
|
for (const [relative, contents] of Object.entries(files)) {
|
|
135
136
|
// eslint-disable-next-line no-await-in-loop
|
|
136
137
|
await writeFileIfMissing(ctx.targetDir, relative, contents);
|