create-mirta 0.3.4 → 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/classic.mjs +120 -0
- package/dist/index.mjs +800 -690
- package/dist/mono.mjs +208 -0
- package/dist/resolver.mjs +843 -0
- package/locales/en-US.json +109 -0
- package/locales/ru-RU.json +109 -0
- package/package.json +25 -22
- package/templates/classic/starter/content/package.json +11 -0
- package/{dist/templates/config/typescript → templates/classic/starter/content}/tsconfig.json +0 -5
- package/{dist/templates/eslint/eslint.config.mjs.ejs → templates/classic/starter/features/eslint/eslint.config.mjs.tt} +11 -6
- package/{dist/templates/base/_env → templates/classic/starter/features/examples/_.env} +1 -1
- package/templates/classic/starter/features/examples/src/wb-rules/01-counter.ts +12 -0
- package/templates/classic/starter/features/examples/src/wb-rules-modules/counter.ts +42 -0
- package/templates/classic/starter/features/examples-vitest/tests/wb-rules-modules/counter.test.ts +56 -0
- package/templates/classic/starter/features/vitest/package.json +10 -0
- package/templates/classic/starter/features/vitest-eslint/package.json +5 -0
- package/templates/classic/starter/template.json +10 -0
- package/{dist/templates/config/store → templates/classic/store/content}/package.json +1 -1
- package/templates/classic/store/features/examples/src/wb-rules/01-counter.ts +13 -0
- package/templates/classic/store/features/examples/src/wb-rules/02-counter.ts +19 -0
- package/templates/classic/store/features/examples/src/wb-rules-modules/counter-store.ts +33 -0
- package/templates/classic/store/features/examples/src/wb-rules-modules/counter.ts +44 -0
- package/templates/classic/store/features/examples-vitest/tests/wb-rules-modules/counter.test.ts +49 -0
- package/templates/classic/store/template.json +14 -0
- package/templates/mono/core/content/package.json +6 -0
- package/templates/mono/core/content/pnpm-workspace.yaml +3 -0
- package/templates/mono/core/content/sites/.gitkeep +0 -0
- package/templates/mono/core/content/tsconfig.json +31 -0
- package/templates/mono/core/features/eslint/eslint.config.mjs.tt +126 -0
- package/templates/mono/core/features/vitest/package.json +10 -0
- package/templates/mono/core/template.json +5 -0
- package/templates/mono/package/features/package/packages/{{package}}/package.json.tt +25 -0
- package/templates/mono/package/features/package/packages/{{package}}/src/.gitkeep +0 -0
- package/templates/mono/package/features/package/packages/{{package}}/tsconfig.build.json +10 -0
- package/templates/mono/package/features/package/sites/{{package}}-demo/package.json.tt +19 -0
- package/templates/mono/package/features/package/sites/{{package}}-demo/src/wb-rules/.gitkeep +0 -0
- package/templates/mono/package/features/package/sites/{{package}}-demo/src/wb-rules-modules/.gitkeep +0 -0
- package/templates/mono/package/features/package/sites/{{package}}-demo/tsconfig.build.json +10 -0
- package/templates/mono/package/features/package-examples/packages/{{package}}/src/index.ts +2 -0
- package/templates/mono/package/features/package-examples/packages/{{package}}/src/thermostat.ts +99 -0
- package/templates/mono/package/features/package-examples/packages/{{package}}/src/types.ts +25 -0
- package/templates/mono/package/features/package-examples/sites/{{package}}-demo/src/wb-rules/01-thermo.ts.tt +11 -0
- package/templates/mono/package/features/package-examples-vitest/packages/{{package}}/tests/mirta-thermostat.test.ts +72 -0
- package/templates/mono/package/features/package-github/packages/{{package}}/package.json.tt +11 -0
- package/templates/mono/package/template.json +16 -0
- package/templates/mono/sites/content/mirta.config.json +37 -0
- package/templates/mono/sites/content/sites/home/package.json +17 -0
- package/templates/mono/sites/content/sites/home/src/wb-rules/.gitkeep +0 -0
- package/templates/mono/sites/content/sites/home/src/wb-rules-modules/.gitkeep +0 -0
- package/templates/mono/sites/content/sites/home-staging/package.json +17 -0
- package/templates/mono/sites/content/sites/home-staging/src/wb-rules/.gitkeep +0 -0
- package/templates/mono/sites/content/sites/home-staging/src/wb-rules-modules/.gitkeep +0 -0
- package/templates/mono/sites/template.json +9 -0
- package/{dist/templates/base/_editorconfig → templates/shared/base/content/_.editorconfig} +1 -1
- package/{dist/templates/base/_gitignore → templates/shared/base/content/_.gitignore} +1 -3
- package/templates/shared/base/content/_.node-version +1 -0
- package/{dist/templates/base/.vscode → templates/shared/base/content/_.vscode}/settings.json +12 -1
- package/templates/shared/base/content/package.json +22 -0
- package/templates/shared/base/features/connection/_.env.local.tt +1 -0
- package/templates/shared/base/features/connection/mirta.config.json +6 -0
- package/templates/shared/base/features/eslint/package.json +9 -0
- package/templates/shared/base/features/examples/mirta.config.json.tt +51 -0
- package/templates/shared/base/features/github/_.github/workflows/build.yml.tt +56 -0
- package/templates/shared/base/features/github-vitest/_.github/workflows/test.yml +30 -0
- package/templates/shared/base/template.json +16 -0
- package/dist/locales/en-US.json +0 -85
- package/dist/locales/ru-RU.json +0 -86
- package/dist/templates/base/package.json +0 -27
- package/dist/templates/base/rollup.config.mjs +0 -15
- package/dist/templates/config/typescript/package.json +0 -8
- package/dist/templates/config/vitest/package.json +0 -10
- package/dist/templates/config/vitest/tests/setup/dotenv.ts +0 -5
- package/dist/templates/config/vitest/tests/setup/mirta.ts +0 -41
- package/dist/templates/config/vitest/tests/tsconfig.json +0 -9
- package/dist/templates/config/vitest/vitest.config.ts +0 -37
- package/dist/templates/example/base/src/wb-rules/00-dotenv.ts +0 -52
- package/dist/templates/example/base/src/wb-rules/01-count.ts +0 -6
- package/dist/templates/example/base/src/wb-rules-modules/counter.ts +0 -14
- package/dist/templates/example/store/src/wb-rules/01-count.ts +0 -10
- package/dist/templates/example/store/src/wb-rules-modules/counter-store.ts +0 -7
- package/dist/templates/example/store/src/wb-rules-modules/counter.ts +0 -20
- package/dist/templates/example/vitest/base/tests/wb-rules-modules/counter.test.ts +0 -10
- package/dist/templates/example/vitest/store/tests/wb-rules-modules/counter-store.test.ts +0 -13
- /package/{dist/assets → assets}/logo.art +0 -0
- /package/{dist/templates/base → templates/classic/starter/content}/src/wb-rules/.gitkeep +0 -0
- /package/{dist/templates/base → templates/classic/starter/content}/src/wb-rules-modules/.gitkeep +0 -0
- /package/{dist/templates/base → templates/classic/starter/content}/types/env.d.ts +0 -0
- /package/{dist/templates/config → templates/classic/starter/features}/vitest/tests/wb-rules-modules/.gitkeep +0 -0
- /package/{dist/templates/config → templates/classic/starter/features}/vitest/tsconfig.json +0 -0
- /package/{dist/templates/base/_gitattributes → templates/shared/base/content/_.gitattributes} +0 -0
- /package/{dist/templates/base/_npmrc → templates/shared/base/content/_.npmrc} +0 -0
- /package/{dist/templates/base/.vscode → templates/shared/base/content/_.vscode}/extensions.json +0 -0
- /package/{dist/templates/base/.vscode → templates/shared/base/content/_.vscode}/tasks.json +0 -0
- /package/{dist/templates/config/eslint/.vscode → templates/shared/base/features/eslint/_.vscode}/extensions.json +0 -0
- /package/{dist/templates/config/eslint/.vscode → templates/shared/base/features/eslint/_.vscode}/settings.json +0 -0
- /package/{dist/templates/config/vitest/.vscode → templates/shared/base/features/vitest/_.vscode}/extensions.json +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,191 +1,569 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import * as p from '@clack/prompts';
|
|
6
|
-
import { intro, text, confirm, multiselect, select, outro } from '@clack/prompts';
|
|
1
|
+
import cliPackage from '../package.json' with { type: 'json' };
|
|
2
|
+
import { createStagedArgs } from '@mirta/staged-args';
|
|
3
|
+
import { initLocalizationAsync } from '@mirta/i18n';
|
|
4
|
+
import { resolve, basename, dirname, sep } from 'node:path';
|
|
7
5
|
import chalk from 'chalk';
|
|
8
|
-
import
|
|
9
|
-
import merge from 'lodash.merge';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
10
7
|
import { fruit } from 'gradient-string';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
8
|
+
import p from 'prompts';
|
|
9
|
+
import { isString } from '@mirta/basics';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
12
|
+
const { t, getLocale, setLocaleAsync } = await initLocalizationAsync({
|
|
13
|
+
cwd: resolve(import.meta.dirname, '../'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function assertNoParseErrors(result) {
|
|
17
|
+
if (!result.hasErrors)
|
|
18
|
+
return;
|
|
19
|
+
const lines = [
|
|
20
|
+
t('args.errorHeader', { count: result.errors.length }),
|
|
21
|
+
];
|
|
22
|
+
for (const error of result.errors) {
|
|
23
|
+
switch (error.type) {
|
|
24
|
+
case 'unknown-option':
|
|
25
|
+
if (error.suggestion) {
|
|
26
|
+
lines.push(t('args.unknownOptionSuggest', {
|
|
27
|
+
option: chalk.bold(error.option),
|
|
28
|
+
suggestion: chalk.bold(`--${error.suggestion}`),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
lines.push(t('args.unknownOption', {
|
|
33
|
+
option: chalk.bold(error.option),
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case 'missing-value':
|
|
38
|
+
lines.push(t('args.missingValue', {
|
|
39
|
+
option: chalk.bold(error.option),
|
|
40
|
+
}));
|
|
41
|
+
break;
|
|
35
42
|
}
|
|
36
|
-
fileCallback(fullpath);
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
function emptyDir(targetDir) {
|
|
40
|
-
if (!existsSync(targetDir))
|
|
41
|
-
return;
|
|
42
|
-
postOrderDirectoryTraverse(targetDir, (path) => {
|
|
43
|
-
rmdirSync(path);
|
|
44
|
-
}, (path) => {
|
|
45
|
-
unlinkSync(path);
|
|
46
|
-
});
|
|
44
|
+
throw new Error(lines.join('\n'));
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
const fallbackLocale = 'en-US';
|
|
50
|
-
const localesPath = './locales';
|
|
51
|
-
let currentLocale = '';
|
|
52
47
|
/**
|
|
48
|
+
* Символ-разделитель, используемый в префиксе логов.
|
|
53
49
|
*
|
|
54
|
-
*
|
|
50
|
+
* @since 0.3.0
|
|
55
51
|
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
**/
|
|
53
|
+
const dot = '•';
|
|
54
|
+
/**
|
|
55
|
+
* Баннер, отображаемый в начале логов по умолчанию.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.3.0
|
|
58
|
+
*
|
|
59
|
+
**/
|
|
60
|
+
const banner$1 = `Mirta ${dot}`;
|
|
61
|
+
/**
|
|
62
|
+
* Цвета текста для каждого уровня логирования.
|
|
63
|
+
*
|
|
64
|
+
* @since 0.4.0
|
|
65
|
+
*
|
|
66
|
+
**/
|
|
67
|
+
const colors = {
|
|
68
|
+
debug: chalk.magenta,
|
|
69
|
+
info: chalk.cyan,
|
|
70
|
+
warn: chalk.yellow,
|
|
71
|
+
error: chalk.red,
|
|
72
|
+
success: chalk.green,
|
|
73
|
+
cancel: chalk.red,
|
|
74
|
+
step: chalk.dim,
|
|
75
|
+
note: chalk.yellowBright,
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Цвета фона для "pill" (подсветки метки уровня).
|
|
79
|
+
*
|
|
80
|
+
* @since 0.4.0
|
|
81
|
+
*
|
|
82
|
+
**/
|
|
83
|
+
const bgColors = {
|
|
84
|
+
debug: chalk.bgMagenta.black,
|
|
85
|
+
info: chalk.bgCyan.black,
|
|
86
|
+
warn: chalk.bgYellow.black,
|
|
87
|
+
error: chalk.bgRed.white,
|
|
88
|
+
success: chalk.bgGreen.black,
|
|
89
|
+
cancel: chalk.bgRed,
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Приоритет уровней логирования. Определяет, какие сообщения будут отображаться
|
|
93
|
+
* при установленном уровне детализации.
|
|
94
|
+
*
|
|
95
|
+
* @since 0.4.0
|
|
96
|
+
*
|
|
97
|
+
**/
|
|
98
|
+
const levelPriority = [
|
|
99
|
+
'debug',
|
|
100
|
+
'info',
|
|
101
|
+
'warn',
|
|
102
|
+
'error',
|
|
103
|
+
'success',
|
|
104
|
+
'cancel',
|
|
105
|
+
'step',
|
|
106
|
+
'note',
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* Целевой уровень логирования. Сообщения с уровнем ниже указанного — игнорируются.
|
|
110
|
+
*
|
|
111
|
+
* @since 0.4.0
|
|
112
|
+
*
|
|
113
|
+
**/
|
|
114
|
+
let targetLevel = 0;
|
|
115
|
+
/**
|
|
116
|
+
* Проверяет, должно ли сообщение быть залогировано, исходя из текущего уровня.
|
|
117
|
+
*
|
|
118
|
+
* @param level - Уровень логирования сообщения.
|
|
119
|
+
* @returns {boolean} `true`, если сообщение удовлетворяет текущему уровню детализации.
|
|
120
|
+
*
|
|
121
|
+
* @since 0.4.0
|
|
122
|
+
*
|
|
123
|
+
**/
|
|
124
|
+
function shouldLog(level) {
|
|
125
|
+
const currentLevel = levelPriority.indexOf(level);
|
|
126
|
+
return currentLevel === -1 || currentLevel >= targetLevel;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Создаёт функцию для формирования "pill" — цветной метки с названием уровня.
|
|
130
|
+
*
|
|
131
|
+
* @param level - Уровень логирования.
|
|
132
|
+
* @returns Функция, возвращающая отформатированную метку.
|
|
133
|
+
*
|
|
134
|
+
* @since 0.4.0
|
|
135
|
+
*
|
|
136
|
+
**/
|
|
137
|
+
function createPill(level) {
|
|
138
|
+
const bgColor = bgColors[level] ?? ((text) => text);
|
|
139
|
+
return (...text) => {
|
|
140
|
+
const filteredText = text
|
|
141
|
+
.filter(x => x !== undefined)
|
|
142
|
+
.join(' ');
|
|
143
|
+
if (filteredText.length === 0)
|
|
144
|
+
return '';
|
|
145
|
+
return bgColor(` ${filteredText} `) + ` ${dot} `;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Определяет, нужно ли применять цвет к строке сообщения.
|
|
150
|
+
*
|
|
151
|
+
* @param colorScope - Режим применения цвета.
|
|
152
|
+
* @param lineIndex - Индекс строки (для многострочных сообщений).
|
|
153
|
+
* @returns `true`, если цвет следует применить.
|
|
154
|
+
*
|
|
155
|
+
* @since 0.4.0
|
|
156
|
+
*
|
|
157
|
+
**/
|
|
158
|
+
function shouldColorLine(colorScope, lineIndex) {
|
|
159
|
+
if (colorScope === 'all')
|
|
160
|
+
return true;
|
|
161
|
+
if (colorScope === 'first-line')
|
|
162
|
+
return lineIndex === 0;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Форматирует сообщение с учётом уровня, опций и цветов.
|
|
167
|
+
*
|
|
168
|
+
* @param level - Уровень логирования.
|
|
169
|
+
* @param message - Сообщение для логирования. Может быть любого типа.
|
|
170
|
+
* @param labelOrOptions - Метка (строка) или опции форматирования.
|
|
171
|
+
* @param options - Опции форматирования (если первый параметр — метка).
|
|
172
|
+
* @returns Отформатированная строка для вывода в консоль.
|
|
173
|
+
*
|
|
174
|
+
* @since 0.4.0
|
|
175
|
+
*
|
|
176
|
+
**/
|
|
177
|
+
function formatMessage(level, message, labelOrOptions, options) {
|
|
178
|
+
let label;
|
|
179
|
+
let finalOptions;
|
|
180
|
+
if (typeof labelOrOptions === 'string') {
|
|
181
|
+
label = labelOrOptions;
|
|
182
|
+
finalOptions = {};
|
|
65
183
|
}
|
|
66
|
-
|
|
67
|
-
|
|
184
|
+
else {
|
|
185
|
+
finalOptions = labelOrOptions ?? {};
|
|
68
186
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
187
|
+
const { indent = 0, includePrefix = true, colorScope = 'first-line', colorOverride, } = finalOptions;
|
|
188
|
+
const actualLevel = colorOverride ?? level;
|
|
189
|
+
const color = colors[actualLevel];
|
|
190
|
+
const pill = createPill(actualLevel);
|
|
191
|
+
let text = '';
|
|
192
|
+
if (Array.isArray(message)) {
|
|
193
|
+
text = message.map(x => String(x)).join(' ');
|
|
72
194
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
195
|
+
else {
|
|
196
|
+
text = String(message);
|
|
197
|
+
}
|
|
198
|
+
const lineIndent = ' '.repeat(indent);
|
|
199
|
+
const lines = text.split('\n');
|
|
200
|
+
let prefix = includePrefix ? `${banner$1} ${pill(label)}` : '';
|
|
201
|
+
if (prefix && colorScope !== 'none')
|
|
202
|
+
prefix = color(prefix);
|
|
203
|
+
return lines
|
|
204
|
+
.map((line, lineIndex) => {
|
|
205
|
+
line = line.trim();
|
|
206
|
+
if (shouldColorLine(colorScope, lineIndex))
|
|
207
|
+
line = color(line);
|
|
208
|
+
return lineIndex === 0
|
|
209
|
+
? lineIndent + prefix + line
|
|
210
|
+
: lineIndent + `${' '.repeat(2)}${line}`;
|
|
211
|
+
})
|
|
212
|
+
.join('\n');
|
|
89
213
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Основная функция логирования. Проверяет уровень и выводит сообщение.
|
|
216
|
+
*
|
|
217
|
+
* @param level - Уровень логирования.
|
|
218
|
+
* @param value - Сообщение.
|
|
219
|
+
* @param labelOrOptions - Метка или опции.
|
|
220
|
+
* @param options - Опции (если метка передана отдельно).
|
|
221
|
+
*
|
|
222
|
+
* @since 0.4.0
|
|
223
|
+
*
|
|
224
|
+
**/
|
|
225
|
+
function log(level, value, labelOrOptions, options) {
|
|
226
|
+
if (!shouldLog(level))
|
|
227
|
+
return;
|
|
228
|
+
console.log(formatMessage(level, value, labelOrOptions));
|
|
100
229
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Публичный интерфейс логгера. Предоставляет методы для логирования на разных уровнях.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* logger.info('Команда запущена')
|
|
236
|
+
* logger.warn('Устаревший режим', 'DEPRECATED')
|
|
237
|
+
* logger.step('Сборка...', { indent: 2 })
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @since 0.4.0
|
|
241
|
+
*
|
|
242
|
+
**/
|
|
243
|
+
const logger = {
|
|
244
|
+
/**
|
|
245
|
+
* Устанавливает минимальный уровень логирования.
|
|
246
|
+
*
|
|
247
|
+
* @param level - Уровень, начиная с которого выводятся сообщения.
|
|
248
|
+
*
|
|
249
|
+
**/
|
|
250
|
+
setLevel: (level) => {
|
|
251
|
+
targetLevel = levelPriority.indexOf(level);
|
|
252
|
+
},
|
|
253
|
+
/**
|
|
254
|
+
* Логирует нейтральное сообщение с визуальным оформлением успеха.
|
|
255
|
+
* Использует уровень `info`, но цвет `success` (только в префиксе).
|
|
256
|
+
*
|
|
257
|
+
**/
|
|
258
|
+
log: (value) => {
|
|
259
|
+
log('info', value, {
|
|
260
|
+
colorScope: 'prefix',
|
|
261
|
+
colorOverride: 'success',
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
/**
|
|
265
|
+
* Логирует отладочное сообщение.
|
|
266
|
+
*
|
|
267
|
+
* @param value - Сообщение.
|
|
268
|
+
* @param label - Настраиваемая метка.
|
|
269
|
+
*
|
|
270
|
+
**/
|
|
271
|
+
debug: (value, label = t('label.debug')) => {
|
|
272
|
+
log('debug', value, label);
|
|
273
|
+
},
|
|
274
|
+
/**
|
|
275
|
+
* Логирует информационное сообщение.
|
|
276
|
+
*
|
|
277
|
+
* @param value - Сообщение.
|
|
278
|
+
* @param label - Настраиваемая метка.
|
|
279
|
+
*
|
|
280
|
+
**/
|
|
281
|
+
info: (value, label = t('label.info')) => {
|
|
282
|
+
log('info', value, label);
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* Логирует предупреждение.
|
|
286
|
+
*
|
|
287
|
+
* @param value - Сообщение.
|
|
288
|
+
* @param label - Настраиваемая метка.
|
|
289
|
+
*
|
|
290
|
+
**/
|
|
291
|
+
warn: (value, label = t('label.warning')) => {
|
|
292
|
+
log('warn', value, label);
|
|
293
|
+
},
|
|
294
|
+
/**
|
|
295
|
+
* Логирует ошибку.
|
|
296
|
+
*
|
|
297
|
+
* @param value - Сообщение.
|
|
298
|
+
* @param label - Настраиваемая метка.
|
|
299
|
+
*
|
|
300
|
+
**/
|
|
301
|
+
error: (value, label = t('label.error')) => {
|
|
302
|
+
log('error', value, label);
|
|
303
|
+
},
|
|
304
|
+
/**
|
|
305
|
+
* Логирует сообщение об успешном завершении.
|
|
306
|
+
*
|
|
307
|
+
* @param value - Сообщение.
|
|
308
|
+
* @param label - Настраиваемая метка.
|
|
309
|
+
*
|
|
310
|
+
**/
|
|
311
|
+
success: (value, label = t('label.success')) => {
|
|
312
|
+
log('success', value, label);
|
|
313
|
+
},
|
|
314
|
+
/**
|
|
315
|
+
* Логирует сообщение об отмене действия.
|
|
316
|
+
*
|
|
317
|
+
* @param value - Сообщение.
|
|
318
|
+
* @param label - Настраиваемая метка.
|
|
319
|
+
*
|
|
320
|
+
**/
|
|
321
|
+
cancel: (value, label = t('label.canceled')) => {
|
|
322
|
+
log('cancel', value, label);
|
|
323
|
+
},
|
|
324
|
+
/**
|
|
325
|
+
* Логирует шаг процесса. Без префикса, цветной текст, с отступом.
|
|
326
|
+
*
|
|
327
|
+
* @param value - Сообщение.
|
|
328
|
+
* @param options - Настройка отступа.
|
|
329
|
+
*
|
|
330
|
+
**/
|
|
331
|
+
step: (value, options = { indent: 0 }) => {
|
|
332
|
+
log('step', value, {
|
|
333
|
+
includePrefix: false,
|
|
334
|
+
colorScope: 'all',
|
|
335
|
+
indent: options.indent,
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
/**
|
|
339
|
+
* Логирует вспомогательную заметку. Цвет применяется только к префиксу.
|
|
340
|
+
*
|
|
341
|
+
* @param value - Сообщение.
|
|
342
|
+
* @param options - Опции форматирования.
|
|
343
|
+
*
|
|
344
|
+
**/
|
|
345
|
+
note: (value, options = { indent: 0, includePrefix: true }) => {
|
|
346
|
+
log('note', value, {
|
|
347
|
+
includePrefix: options.includePrefix,
|
|
348
|
+
colorScope: 'prefix',
|
|
349
|
+
indent: options.indent,
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Имя текущего пакета в формате, используемом в npm-реестре.
|
|
356
|
+
*
|
|
357
|
+
* @since 0.4.0
|
|
358
|
+
*
|
|
359
|
+
* @internal
|
|
360
|
+
*
|
|
361
|
+
**/
|
|
362
|
+
const THIS_PACKAGE_NAME = 'create-mirta';
|
|
363
|
+
/**
|
|
364
|
+
* Источник фичи - CLI.
|
|
365
|
+
*
|
|
366
|
+
* @since 0.4.0
|
|
367
|
+
*
|
|
368
|
+
**/
|
|
369
|
+
const FEATURE_ORIGIN_CLI = 'cli';
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Специализированный класс для обработки ошибок, связанных с работой локализации.
|
|
373
|
+
*
|
|
374
|
+
* Предоставляет структурированные и типизированные ошибки с использованием кодов, что упрощает
|
|
375
|
+
* программную обработку исключений в инструментах, работающих с пакетами.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* throw CreationError.get('template.notFound', 'fullstack')
|
|
380
|
+
* ```
|
|
381
|
+
* @since 0.4.0
|
|
382
|
+
*
|
|
383
|
+
**/
|
|
384
|
+
class CreationError extends Error {
|
|
385
|
+
/**
|
|
386
|
+
* Код ошибки для программной идентификации.
|
|
387
|
+
*
|
|
388
|
+
* Позволяет точно определить причину ошибки в обработчиках `try/catch`.
|
|
389
|
+
*
|
|
390
|
+
**/
|
|
391
|
+
code;
|
|
392
|
+
/**
|
|
393
|
+
* Приватный конструктор, используемый только внутри
|
|
394
|
+
* класса для создания экземпляров ошибки.
|
|
395
|
+
*
|
|
396
|
+
* @param message - Полное сообщение об ошибке.
|
|
397
|
+
* @param code - Код ошибки для идентификации.
|
|
398
|
+
* @param scope - Пространство имён или модуль, в котором возникла ошибка.
|
|
399
|
+
* По умолчанию — {@link THIS_PACKAGE_NAME}.
|
|
400
|
+
*
|
|
401
|
+
**/
|
|
402
|
+
constructor(message, code) {
|
|
403
|
+
super(`[${THIS_PACKAGE_NAME}] ${message}`);
|
|
404
|
+
this.name = 'CreationError';
|
|
405
|
+
this.code = code;
|
|
406
|
+
// Захватываем стек вызовов, исключая фабричный метод `get`,
|
|
407
|
+
// чтобы улучшить читаемость трассировки.
|
|
408
|
+
//
|
|
409
|
+
if ('captureStackTrace' in Error)
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
411
|
+
Error.captureStackTrace(this, CreationError.get);
|
|
412
|
+
}
|
|
413
|
+
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
414
|
+
static codeMappings = {
|
|
415
|
+
'load.noTemplates': (templateType) => t('load.noTemplates', { type: templateType }),
|
|
416
|
+
'template.notFound': (templateName) => t('template.notFound', { name: templateName }),
|
|
417
|
+
'template.invalidConfig': (templateName) => `Invalid template config of ${templateName}`,
|
|
418
|
+
'template.duplicateName': (templateName) => `Duplicate template name ${templateName}`,
|
|
419
|
+
'project.outsideRoot': () => t('project.outsideRoot'),
|
|
420
|
+
'project.denyOverwrite': () => t('project.denyOverwrite'),
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Фабричный метод для создания экземпляра ошибки по её коду.
|
|
424
|
+
*
|
|
425
|
+
* Автоматически подставляет сообщение из `codeMappings` и формирует ошибку с заданными параметрами.
|
|
426
|
+
*
|
|
427
|
+
* @template T - Ограниченный ключами `codeMappings` тип, гарантирующий корректность кода.
|
|
428
|
+
* @param code - Код ошибки (например, `'alreadyDefined'`).
|
|
429
|
+
* @param args - Аргументы, соответствующие параметрам функции сообщения из `codeMappings`.
|
|
430
|
+
* @returns Новый экземпляр {@link CreationError} с шаблонным сообщением.
|
|
431
|
+
*
|
|
432
|
+
**/
|
|
433
|
+
static get(code, ...args) {
|
|
434
|
+
const messageFn = this.codeMappings[code];
|
|
435
|
+
const message = messageFn(...args);
|
|
436
|
+
return new CreationError(message, code);
|
|
437
|
+
}
|
|
105
438
|
}
|
|
106
439
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const unicode = isUnicodeSupported();
|
|
122
|
-
const unicodeOr = (c, fallback) => (unicode ? c : fallback);
|
|
123
|
-
const S_BAR = unicodeOr('│', '|');
|
|
124
|
-
function usePrompts(messages) {
|
|
125
|
-
function cancel(message = messages.errors.operationCanceled) {
|
|
126
|
-
p.cancel(formatError(message, messages.status.canceled));
|
|
440
|
+
/**
|
|
441
|
+
* Ошибка, указывающая на отмену операции (например, через Ctrl+C).
|
|
442
|
+
*
|
|
443
|
+
* Соответствует коду выхода 130 (SIGINT).
|
|
444
|
+
*
|
|
445
|
+
* @since 0.4.0
|
|
446
|
+
*
|
|
447
|
+
**/
|
|
448
|
+
class OperationCanceledError extends Error {
|
|
449
|
+
constructor() {
|
|
450
|
+
super();
|
|
451
|
+
this.name = 'OperationCanceledError';
|
|
452
|
+
Error.captureStackTrace(this, OperationCanceledError);
|
|
127
453
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class PromptCanceledError extends Error {
|
|
457
|
+
constructor() {
|
|
458
|
+
super();
|
|
459
|
+
this.name = 'PromptCanceledError';
|
|
460
|
+
Error.captureStackTrace(this, PromptCanceledError);
|
|
135
461
|
}
|
|
136
|
-
|
|
137
|
-
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function loadAsync(type) {
|
|
465
|
+
switch (type) {
|
|
466
|
+
case 'classic':
|
|
467
|
+
return await import('./classic.mjs');
|
|
468
|
+
case 'mono':
|
|
469
|
+
return await import('./mono.mjs');
|
|
470
|
+
default:
|
|
471
|
+
throw new Error(`Unknown project type: ${type}`);
|
|
138
472
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
step: p.log.step,
|
|
143
|
-
message: p.log.message,
|
|
144
|
-
inlineSub,
|
|
145
|
-
};
|
|
473
|
+
}
|
|
474
|
+
async function resolveRunnerAsync(projectType) {
|
|
475
|
+
return await loadAsync(projectType);
|
|
146
476
|
}
|
|
147
477
|
|
|
148
478
|
const canUseColors = process.stdout.isTTY
|
|
149
479
|
&& process.stdout.getColorDepth() > 8;
|
|
150
|
-
const data = readFileSync(new URL('
|
|
480
|
+
const data = readFileSync(new URL('../assets/logo.art', import.meta.url), 'utf-8');
|
|
151
481
|
const banner = canUseColors
|
|
152
482
|
? fruit(data)
|
|
153
483
|
: data;
|
|
154
484
|
|
|
155
|
-
const
|
|
156
|
-
|
|
485
|
+
const finalMessageEn = `\
|
|
486
|
+
${chalk.green('Welcome to your new wb-rules project!')} 🎉
|
|
487
|
+
Open it in VSCode or your favourite editor and start building.
|
|
488
|
+
|
|
489
|
+
📚 Documentation:
|
|
490
|
+
https://dzen.ru/wihome
|
|
491
|
+
|
|
492
|
+
💡 Mirta is powered entirely by community support.
|
|
493
|
+
To keep the project alive and growing, your help is essential.
|
|
494
|
+
|
|
495
|
+
💖 A recurring subscription on Boosty is the best way to support:
|
|
496
|
+
https://boosty.to/wihome
|
|
497
|
+
|
|
498
|
+
☕ One-time tips are also appreciated:
|
|
499
|
+
https://pay.cloudtips.ru/p/58512cca
|
|
500
|
+
|
|
501
|
+
⭐ Your star on GitHub helps others discover Mirta:
|
|
502
|
+
https://github.com/wb-mirta/core
|
|
503
|
+
|
|
504
|
+
Thank you for using Mirta!
|
|
505
|
+
`;
|
|
506
|
+
const finalMessageRu = `\
|
|
507
|
+
${chalk.green('Добро пожаловать в ваш новый проект wb-rules!')} 🎉
|
|
508
|
+
Откройте его в VSCode или другом редакторе и начинайте разработку.
|
|
509
|
+
|
|
510
|
+
📚 Документация:
|
|
511
|
+
https://dzen.ru/wihome
|
|
512
|
+
|
|
513
|
+
Мирта развивается только за счёт добровольных взносов.
|
|
514
|
+
Чтобы проект жил и рос — нужна ваша поддержка.
|
|
515
|
+
|
|
516
|
+
💖 Регулярная подписка на Boosty — лучший способ помочь:
|
|
517
|
+
https://boosty.to/wihome
|
|
518
|
+
|
|
519
|
+
☕ Также можно поддержать разовым платежом:
|
|
520
|
+
https://pay.cloudtips.ru/p/58512cca
|
|
521
|
+
|
|
522
|
+
⭐ Нравится фреймворк? Поставьте ему звёздочку на GitHub:
|
|
523
|
+
https://github.com/wb-mirta/core
|
|
524
|
+
|
|
525
|
+
Спасибо, что выбрали Мирту!
|
|
526
|
+
`;
|
|
527
|
+
function getFinalMessage() {
|
|
528
|
+
const locale = getLocale();
|
|
529
|
+
return locale === 'ru-RU'
|
|
530
|
+
? finalMessageRu
|
|
531
|
+
: finalMessageEn;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const { dim, yellow } = chalk;
|
|
157
535
|
const helpMessageEn = `\
|
|
158
536
|
Creates a new wb-rules project with the Mirta Framework
|
|
159
537
|
|
|
160
538
|
Starts the CLI in interactive mode when no feature flags is provided,
|
|
161
539
|
or if the directory argument is not a valid project name.
|
|
162
540
|
|
|
163
|
-
${yellow
|
|
541
|
+
${yellow('Usage:')}
|
|
164
542
|
create-mirta [feature_flags...] [options...] [directory]
|
|
165
543
|
|
|
166
|
-
${yellow
|
|
544
|
+
${yellow('Feature flags:')}
|
|
167
545
|
--default
|
|
168
|
-
${dim
|
|
546
|
+
${dim('Create a project with the default configuration without any additional features')}
|
|
169
547
|
--eslint
|
|
170
|
-
${dim
|
|
548
|
+
${dim('Add ESLint for code quality')}
|
|
171
549
|
--vitest
|
|
172
|
-
${dim
|
|
550
|
+
${dim('Add Vitest for unit testing')}
|
|
173
551
|
--store
|
|
174
|
-
${dim
|
|
552
|
+
${dim('Add store for state management')}
|
|
175
553
|
|
|
176
|
-
${yellow
|
|
554
|
+
${yellow('Options:')}
|
|
177
555
|
--ssh
|
|
178
|
-
${dim
|
|
556
|
+
${dim('Set SSH destination of deployment process as [username@][hostname][:port]')}
|
|
179
557
|
--rutoken
|
|
180
|
-
${dim
|
|
558
|
+
${dim('Use Rutoken ECP as encrypted store of SSH private key')}
|
|
181
559
|
-v, --version
|
|
182
|
-
${dim
|
|
560
|
+
${dim('Display the version number of this CLI')}
|
|
183
561
|
-f, --force
|
|
184
|
-
${dim
|
|
562
|
+
${dim('Create the project even if the directory is not empty')}
|
|
185
563
|
-b, --bare
|
|
186
|
-
${dim
|
|
564
|
+
${dim('Create a barebone project without any code')}
|
|
187
565
|
-h, --help
|
|
188
|
-
${dim
|
|
566
|
+
${dim('Display this help message')}
|
|
189
567
|
`;
|
|
190
568
|
const helpMessageRu = `\
|
|
191
569
|
Создаёт новый проект wb-rules с использованием Mirta Framework
|
|
@@ -193,612 +571,344 @@ const helpMessageRu = `\
|
|
|
193
571
|
Запускает CLI в интерактивном режиме, если не указано флагов функционала
|
|
194
572
|
или название каталога не подходит в качестве названия проекта.
|
|
195
573
|
|
|
196
|
-
${yellow
|
|
574
|
+
${yellow('Использование:')}
|
|
197
575
|
create-mirta [feature_flags...] [options...] [directory]
|
|
198
576
|
|
|
199
|
-
${yellow
|
|
577
|
+
${yellow('Флаги функционала:')}
|
|
200
578
|
--default
|
|
201
|
-
${dim
|
|
579
|
+
${dim('Создаёт проект с конфигурацией по умолчанию, без дополнений')}
|
|
202
580
|
--eslint
|
|
203
|
-
${dim
|
|
581
|
+
${dim('Добавить ESLint для контроля качества кода')}
|
|
204
582
|
--vitest
|
|
205
|
-
${dim
|
|
583
|
+
${dim('Добавить Mirta Testing для юнит-тестирования')}
|
|
206
584
|
--store
|
|
207
|
-
${dim
|
|
585
|
+
${dim('Добавить хранилище состояний')}
|
|
208
586
|
|
|
209
|
-
${yellow
|
|
587
|
+
${yellow('Опции:')}
|
|
210
588
|
--ssh
|
|
211
|
-
${dim
|
|
589
|
+
${dim('Настроить подключение SSH для деплоя в формате [username@][hostname][:port]')}
|
|
212
590
|
--rutoken
|
|
213
|
-
${dim
|
|
591
|
+
${dim('Использовать Рутокен ЭЦП в качестве хранилища для закрытого ключа SSH')}
|
|
214
592
|
-v, --version
|
|
215
|
-
${dim
|
|
593
|
+
${dim('Отобразить номер версии данного CLI')}
|
|
216
594
|
-f, --force
|
|
217
|
-
${dim
|
|
595
|
+
${dim('Создать проект, даже если целевая директория содержит файлы')}
|
|
218
596
|
-b, --bare
|
|
219
|
-
${dim
|
|
597
|
+
${dim('Создать проект-заготовку, без кода примеров')}
|
|
220
598
|
-h, --help
|
|
221
|
-
${dim
|
|
599
|
+
${dim('Отобразить данное сообщение')}
|
|
222
600
|
`;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
default: {
|
|
229
|
-
type: 'boolean',
|
|
230
|
-
},
|
|
231
|
-
eslint: {
|
|
232
|
-
type: 'boolean',
|
|
233
|
-
},
|
|
234
|
-
vitest: {
|
|
235
|
-
type: 'boolean',
|
|
236
|
-
},
|
|
237
|
-
store: {
|
|
238
|
-
type: 'boolean',
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
const allOptions = ({
|
|
242
|
-
...featureFlags,
|
|
243
|
-
ssh: {
|
|
244
|
-
type: 'string',
|
|
245
|
-
},
|
|
246
|
-
rutoken: {
|
|
247
|
-
type: 'boolean',
|
|
248
|
-
},
|
|
249
|
-
version: {
|
|
250
|
-
type: 'boolean',
|
|
251
|
-
short: 'v',
|
|
252
|
-
},
|
|
253
|
-
force: {
|
|
254
|
-
type: 'boolean',
|
|
255
|
-
short: 'f',
|
|
256
|
-
},
|
|
257
|
-
bare: {
|
|
258
|
-
type: 'boolean',
|
|
259
|
-
short: 'b',
|
|
260
|
-
},
|
|
261
|
-
help: {
|
|
262
|
-
type: 'boolean',
|
|
263
|
-
short: 'h',
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const urlRegex = /(?:(?<username>.+?)@)?(?:(?<hostname>[^:@\s]+))?(?::(?<port>\d+))?/;
|
|
268
|
-
function parseUrl(value) {
|
|
269
|
-
if (!value)
|
|
270
|
-
return {};
|
|
271
|
-
return (urlRegex.exec(value))?.groups ?? {};
|
|
601
|
+
function getHelpMessage() {
|
|
602
|
+
const locale = getLocale();
|
|
603
|
+
return locale === 'ru-RU'
|
|
604
|
+
? helpMessageRu
|
|
605
|
+
: helpMessageEn;
|
|
272
606
|
}
|
|
273
607
|
|
|
274
|
-
|
|
608
|
+
async function prompts(questions, options = {}) {
|
|
609
|
+
const po = {
|
|
610
|
+
onCancel: () => {
|
|
611
|
+
throw new PromptCanceledError();
|
|
612
|
+
},
|
|
613
|
+
...options,
|
|
614
|
+
};
|
|
615
|
+
return await p(questions, po);
|
|
616
|
+
}
|
|
275
617
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (isObject(packageJson[depType])) {
|
|
286
|
-
const sourceDeps = packageJson[depType];
|
|
287
|
-
const sortedDeps = sorted[depType] = {};
|
|
288
|
-
Object.keys(packageJson[depType])
|
|
289
|
-
.sort()
|
|
290
|
-
.forEach((name) => {
|
|
291
|
-
sortedDeps[name] = sourceDeps[name];
|
|
292
|
-
});
|
|
618
|
+
// Префиксы для определения типа
|
|
619
|
+
const MONO_PREFIX = /^(mono)-/;
|
|
620
|
+
async function pickProjectAsync(templateInput) {
|
|
621
|
+
if (isString(templateInput)) {
|
|
622
|
+
if (MONO_PREFIX.test(templateInput)) {
|
|
623
|
+
return {
|
|
624
|
+
type: 'mono',
|
|
625
|
+
templateName: templateInput.replace(MONO_PREFIX, ''),
|
|
626
|
+
};
|
|
293
627
|
}
|
|
628
|
+
// Всё остальное — классический тип проекта,
|
|
629
|
+
// полное название шаблона.
|
|
630
|
+
//
|
|
631
|
+
return {
|
|
632
|
+
type: 'classic',
|
|
633
|
+
templateName: templateInput.replace(/^classic-/, ''),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
const { projectType } = await prompts({
|
|
637
|
+
type: 'select',
|
|
638
|
+
name: 'projectType',
|
|
639
|
+
message: t('projectType.prompt'),
|
|
640
|
+
hint: t('hint.select'),
|
|
641
|
+
choices: [
|
|
642
|
+
{
|
|
643
|
+
title: t('projectType.classic'),
|
|
644
|
+
value: 'classic',
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
title: t('projectType.mono'),
|
|
648
|
+
value: 'mono',
|
|
649
|
+
},
|
|
650
|
+
],
|
|
294
651
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
...sorted,
|
|
298
|
-
};
|
|
652
|
+
// Шаблон будет выбран позже.
|
|
653
|
+
return { type: projectType };
|
|
299
654
|
}
|
|
300
655
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
656
|
+
const templatesDir = resolve(import.meta.dirname, '../templates');
|
|
657
|
+
function assertConfigIsValid(value) {
|
|
658
|
+
if (typeof value !== 'object' || value === null)
|
|
659
|
+
throw new Error('Template config must be an object');
|
|
660
|
+
}
|
|
661
|
+
async function discoverTemplatesAsync(type) {
|
|
662
|
+
const pathPattern = resolve(templatesDir, `{shared,${type}}/*/template.json`);
|
|
663
|
+
const templates = new Map();
|
|
664
|
+
for await (const filePath of fs.glob(pathPattern)) {
|
|
665
|
+
let rawConfig;
|
|
666
|
+
try {
|
|
667
|
+
rawConfig = JSON.parse(await fs.readFile(filePath, 'utf-8'));
|
|
313
668
|
}
|
|
314
|
-
|
|
315
|
-
|
|
669
|
+
catch {
|
|
670
|
+
throw CreationError.get('template.invalidConfig', basename(dirname(filePath)));
|
|
316
671
|
}
|
|
317
|
-
|
|
318
|
-
|
|
672
|
+
assertConfigIsValid(rawConfig);
|
|
673
|
+
const rootDir = dirname(filePath);
|
|
674
|
+
const name = (rawConfig.name || basename(rootDir));
|
|
675
|
+
if (templates.has(name))
|
|
676
|
+
throw CreationError.get('template.duplicateName', name);
|
|
677
|
+
templates.set(name, {
|
|
678
|
+
...rawConfig,
|
|
679
|
+
type: type,
|
|
680
|
+
name: name,
|
|
681
|
+
rootDir: rootDir,
|
|
682
|
+
displayName: rawConfig.displayName ?? name,
|
|
683
|
+
description: rawConfig.description ?? '',
|
|
684
|
+
order: rawConfig.order ?? Number.POSITIVE_INFINITY,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return templates;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function buildSequence(target, templates) {
|
|
691
|
+
const stack = [target];
|
|
692
|
+
// Иерархическая последовательность шаблонов.
|
|
693
|
+
const sequence = [];
|
|
694
|
+
// Для предотвращения зацикливания.
|
|
695
|
+
const seen = new Set();
|
|
696
|
+
let template;
|
|
697
|
+
// Извлекаем последний шаблон из стека.
|
|
698
|
+
while ((template = stack.pop())) {
|
|
699
|
+
if (seen.has(template.name))
|
|
700
|
+
throw new Error('Cyclic template inheritance');
|
|
701
|
+
seen.add(template.name);
|
|
702
|
+
if (template.extends) {
|
|
703
|
+
const parent = templates.get(template.extends);
|
|
704
|
+
if (!parent)
|
|
705
|
+
throw new Error(`Unknown parent template ${template.extends} in ${template.name}`);
|
|
706
|
+
stack.push(parent);
|
|
319
707
|
}
|
|
708
|
+
sequence.push(template);
|
|
320
709
|
}
|
|
321
|
-
|
|
710
|
+
// Инвертированная последовательность начинается с корня.
|
|
711
|
+
return sequence.reverse();
|
|
322
712
|
}
|
|
323
713
|
|
|
324
|
-
function
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
714
|
+
async function pickTargetAsync(templates, templateName) {
|
|
715
|
+
if (templateName) {
|
|
716
|
+
const template = templates.get(templateName);
|
|
717
|
+
if (!template)
|
|
718
|
+
throw CreationError.get('template.notFound', templateName);
|
|
719
|
+
return template;
|
|
720
|
+
}
|
|
721
|
+
if (templates.size === 1)
|
|
722
|
+
return templates.values().next().value;
|
|
723
|
+
const response = await prompts({
|
|
724
|
+
type: 'select',
|
|
725
|
+
name: 'templateName',
|
|
726
|
+
message: t('template.select'),
|
|
727
|
+
hint: t('hint.select'),
|
|
728
|
+
choices: [...templates.values()]
|
|
729
|
+
.filter(x => !x.hidden)
|
|
730
|
+
.sort((a, b) => (a.order - b.order))
|
|
731
|
+
.map(x => ({
|
|
732
|
+
title: t.plain(`templates.${x.name}.name`, x.displayName),
|
|
733
|
+
description: t.plain(`templates.${x.name}.description`, x.description),
|
|
734
|
+
value: x.name,
|
|
735
|
+
})),
|
|
736
|
+
});
|
|
737
|
+
const template = templates.get(response.templateName);
|
|
738
|
+
if (!template)
|
|
739
|
+
throw CreationError.get('template.notFound', response.templateName);
|
|
740
|
+
return template;
|
|
333
741
|
}
|
|
334
742
|
|
|
335
|
-
function
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (file === '.gitkeep')
|
|
343
|
-
continue;
|
|
344
|
-
renderTemplate(path.resolve(sourcePath, file), path.resolve(targetPath, file));
|
|
345
|
-
}
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const filename = path.basename(sourcePath);
|
|
349
|
-
if (filename === 'package.json' && fs.existsSync(targetPath)) {
|
|
350
|
-
renderJson(sourcePath, targetPath, result => sortDependencies(result));
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (['extensions.json', 'settings.json', 'tasks.json', 'tsconfig.json'].includes(filename) && fs.existsSync(targetPath)) {
|
|
354
|
-
renderJson(sourcePath, targetPath);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (filename.startsWith('_')) {
|
|
358
|
-
// rename `_file` to `.file`
|
|
359
|
-
targetPath = path.resolve(path.dirname(targetPath), filename.replace(/^_/, '.'));
|
|
360
|
-
}
|
|
361
|
-
if (filename === '_gitignore' && fs.existsSync(targetPath)) {
|
|
362
|
-
const existingContent = fs.readFileSync(targetPath, 'utf-8');
|
|
363
|
-
const newContent = fs.readFileSync(sourcePath, 'utf-8');
|
|
364
|
-
fs.writeFileSync(targetPath, existingContent + '\n' + newContent);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
fs.copyFileSync(sourcePath, targetPath);
|
|
743
|
+
async function resolveTemplateSequenceAsync(selection) {
|
|
744
|
+
const { type, templateName } = selection;
|
|
745
|
+
const templates = await discoverTemplatesAsync(type);
|
|
746
|
+
if (templates.size === 0)
|
|
747
|
+
throw CreationError.get('load.noTemplates', type);
|
|
748
|
+
const target = await pickTargetAsync(templates, templateName);
|
|
749
|
+
return buildSequence(target, templates);
|
|
368
750
|
}
|
|
369
751
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
const
|
|
374
|
-
|
|
752
|
+
async function confirmOverwriteAsync(projectRoot) {
|
|
753
|
+
const locationText = t('overwrite.notEmpty', { path: chalk.yellow(projectRoot) });
|
|
754
|
+
const promptText = chalk.red(t('overwrite.prompt'));
|
|
755
|
+
const { canOverwrite } = await prompts({
|
|
756
|
+
type: 'confirm',
|
|
757
|
+
name: 'canOverwrite',
|
|
758
|
+
message: `${locationText}\n ${promptText}`,
|
|
759
|
+
initial: false,
|
|
760
|
+
});
|
|
761
|
+
return canOverwrite;
|
|
375
762
|
}
|
|
376
763
|
|
|
377
|
-
function
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
764
|
+
async function promptProjectFolderAsync(defaultValue) {
|
|
765
|
+
const { projectFolder } = await prompts({
|
|
766
|
+
type: 'text',
|
|
767
|
+
name: 'projectFolder',
|
|
768
|
+
message: t('projectFolder.prompt'),
|
|
769
|
+
initial: defaultValue,
|
|
770
|
+
validate: (value) => {
|
|
771
|
+
return value.trim().length === 0
|
|
772
|
+
? t('validation.required')
|
|
773
|
+
: true;
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
return projectFolder.trim();
|
|
383
777
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
additionalConfigs.unshift({
|
|
401
|
-
devDependencies: pickDependencies(['@vitest/eslint-plugin']),
|
|
402
|
-
});
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Асинхронно проверяет, существует ли файл или директория по указанному пути.
|
|
781
|
+
*
|
|
782
|
+
* Использует `fs.access`, чтобы обойти ограничения `fs.existsSync` в асинхронной среде.
|
|
783
|
+
*
|
|
784
|
+
* @param path - Путь к файлу или директории.
|
|
785
|
+
* @returns `true`, если путь существует и доступен, иначе `false`.
|
|
786
|
+
*
|
|
787
|
+
* @since 0.4.0
|
|
788
|
+
*
|
|
789
|
+
**/
|
|
790
|
+
async function isExistsAsync(path) {
|
|
791
|
+
try {
|
|
792
|
+
await fs.access(path);
|
|
793
|
+
return true;
|
|
403
794
|
}
|
|
404
|
-
|
|
405
|
-
|
|
795
|
+
catch (e) {
|
|
796
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')
|
|
797
|
+
return false;
|
|
798
|
+
throw e;
|
|
406
799
|
}
|
|
407
|
-
const templateData = {
|
|
408
|
-
styleGuide,
|
|
409
|
-
addVitest,
|
|
410
|
-
fileExtensions,
|
|
411
|
-
};
|
|
412
|
-
function renderEjsEntry(fileName) {
|
|
413
|
-
const filePath = resolve(templateDir, fileName);
|
|
414
|
-
return [
|
|
415
|
-
fileName.replace(/\.ejs$/, ''),
|
|
416
|
-
renderEjs(filePath, templateData),
|
|
417
|
-
];
|
|
418
|
-
}
|
|
419
|
-
const entries = [
|
|
420
|
-
renderEjsEntry('eslint.config.mjs.ejs'),
|
|
421
|
-
];
|
|
422
|
-
return {
|
|
423
|
-
pkg,
|
|
424
|
-
files: Object.fromEntries(entries),
|
|
425
|
-
};
|
|
426
800
|
}
|
|
427
|
-
function
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
const { pkg, files } = createConfig(templateDir, {
|
|
431
|
-
styleGuide: 'default',
|
|
432
|
-
addVitest,
|
|
433
|
-
});
|
|
434
|
-
const targetPkgPath = resolve(rootDir, 'package.json');
|
|
435
|
-
renderJson(pkg, targetPkgPath, result => sortDependencies(result));
|
|
436
|
-
for (const [fileName, content] of Object.entries(files)) {
|
|
437
|
-
const fullPath = resolve(rootDir, fileName);
|
|
438
|
-
writeFileSync(fullPath, content, 'utf-8');
|
|
439
|
-
}
|
|
801
|
+
async function isDirEmptyAsync(targetDir) {
|
|
802
|
+
const files = await fs.readdir(targetDir);
|
|
803
|
+
return !files.length || (files.length === 1 && files[0] === '.git');
|
|
440
804
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
...options,
|
|
449
|
-
});
|
|
450
|
-
runner.on('exit', (code) => {
|
|
451
|
-
console.log();
|
|
452
|
-
if (code) {
|
|
453
|
-
console.log(` ${command} FAILED...`);
|
|
454
|
-
console.log();
|
|
455
|
-
reject(new Error());
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
resolve();
|
|
459
|
-
}
|
|
805
|
+
async function clearDirAsync(targetDir) {
|
|
806
|
+
for (const filename of await fs.readdir(targetDir)) {
|
|
807
|
+
if (filename === '.git')
|
|
808
|
+
continue;
|
|
809
|
+
await fs.rm(resolve(targetDir, filename), {
|
|
810
|
+
recursive: true,
|
|
811
|
+
force: true,
|
|
460
812
|
});
|
|
461
|
-
}
|
|
813
|
+
}
|
|
462
814
|
}
|
|
463
815
|
|
|
464
|
-
function
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
816
|
+
async function resolveProjectContextAsync(selection, options = {}) {
|
|
817
|
+
const { cwd = process.cwd(), barebone, } = options;
|
|
818
|
+
const projectName = options.projectFolder || await promptProjectFolderAsync(`wb-mirta-${selection.type}`);
|
|
819
|
+
if (options.projectFolder)
|
|
820
|
+
logger.step(`${t('projectFolder.prompt')}: ${options.projectFolder}`);
|
|
821
|
+
const projectRoot = resolve(cwd, projectName);
|
|
822
|
+
if (!projectRoot.startsWith(cwd.endsWith('/') ? cwd : cwd + sep) && projectRoot !== cwd)
|
|
823
|
+
throw CreationError.get('project.outsideRoot');
|
|
824
|
+
const isExists = await isExistsAsync(projectRoot);
|
|
825
|
+
const isEmpty = !isExists || await isDirEmptyAsync(projectRoot);
|
|
826
|
+
let shouldOverwrite = false;
|
|
827
|
+
if (!isEmpty) {
|
|
828
|
+
shouldOverwrite
|
|
829
|
+
= options.forceOverwrite === true || await confirmOverwriteAsync(projectRoot);
|
|
830
|
+
if (!shouldOverwrite)
|
|
831
|
+
throw new OperationCanceledError();
|
|
832
|
+
}
|
|
833
|
+
const templates = await resolveTemplateSequenceAsync(selection);
|
|
469
834
|
return {
|
|
470
|
-
|
|
471
|
-
|
|
835
|
+
rootDir: projectRoot,
|
|
836
|
+
name: projectName,
|
|
837
|
+
shouldOverwrite: shouldOverwrite,
|
|
838
|
+
shouldCreate: !isExists,
|
|
839
|
+
templates: templates,
|
|
840
|
+
barebone: barebone,
|
|
472
841
|
};
|
|
473
842
|
}
|
|
474
|
-
const runningPackageManager = getRunningPackageManager();
|
|
475
|
-
async function installDependencies(scope) {
|
|
476
|
-
if (!scope.packageManager)
|
|
477
|
-
return;
|
|
478
|
-
const args = ['install'];
|
|
479
|
-
await runCommand(scope.packageManager, args, { root: scope.projectRoot });
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const locale = getLocale();
|
|
483
|
-
const finalMessageEn = `\
|
|
484
|
-
To get started, open your project in VSCode or other code editor.
|
|
485
|
-
Documentation can be found at: https://dzen.ru/wihome
|
|
486
|
-
|
|
487
|
-
Please, give a star on GitHub if you appreciate this work:
|
|
488
|
-
https://github.com/wb-mirta/core
|
|
489
|
-
`;
|
|
490
|
-
const finalMessageRu = `\
|
|
491
|
-
Откройте проект в VSCode или другом подходящем редакторе.
|
|
492
|
-
Документация публикуется на Дзене: https://dzen.ru/wihome
|
|
493
|
-
|
|
494
|
-
Если вам нравится фреймворк, поставьте ему звёздочку на Гитхабе:
|
|
495
|
-
https://github.com/wb-mirta/core
|
|
496
|
-
|
|
497
|
-
Поблагодарить разработчика можно через Boosty:
|
|
498
|
-
https://boosty.to/wihome/donate
|
|
499
843
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
: finalMessageEn;
|
|
506
|
-
|
|
507
|
-
const { dim, red, yellow } = chalk;
|
|
508
|
-
const templatesPath = './templates';
|
|
509
|
-
const messages = await getLocalized();
|
|
510
|
-
const featureOptions = [
|
|
511
|
-
{
|
|
512
|
-
value: 'eslint',
|
|
513
|
-
label: messages.addEslint.message,
|
|
514
|
-
hint: messages.addEslint.hint,
|
|
844
|
+
const initialSchema = ({
|
|
845
|
+
// === Common options ===
|
|
846
|
+
version: {
|
|
847
|
+
type: 'boolean',
|
|
848
|
+
short: 'v',
|
|
515
849
|
},
|
|
516
|
-
{
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
hint: messages.addStore.hint,
|
|
850
|
+
help: {
|
|
851
|
+
type: 'boolean',
|
|
852
|
+
short: 'h',
|
|
520
853
|
},
|
|
521
|
-
{
|
|
522
|
-
|
|
523
|
-
label: messages.addVitest.message,
|
|
524
|
-
hint: messages.addVitest.hint,
|
|
854
|
+
locale: {
|
|
855
|
+
type: 'string',
|
|
525
856
|
},
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
.replace(/\s+/g, '-')
|
|
537
|
-
.replace(/^[._]+/, '')
|
|
538
|
-
.replace(/[^a-z0-9-~]+/g, '-');
|
|
539
|
-
}
|
|
857
|
+
template: {
|
|
858
|
+
type: 'string',
|
|
859
|
+
},
|
|
860
|
+
force: {
|
|
861
|
+
type: 'boolean',
|
|
862
|
+
},
|
|
863
|
+
bare: {
|
|
864
|
+
type: 'boolean',
|
|
865
|
+
},
|
|
866
|
+
});
|
|
540
867
|
async function run() {
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
});
|
|
548
|
-
if (argv.help) {
|
|
549
|
-
console.log(helpMessage);
|
|
550
|
-
process.exit(0);
|
|
551
|
-
}
|
|
868
|
+
const args = createStagedArgs(process.argv.slice(2));
|
|
869
|
+
const parseResult = args.parse(initialSchema);
|
|
870
|
+
assertNoParseErrors(parseResult);
|
|
871
|
+
const { values: argv, positionals, stagedArgs: runnerArgs } = parseResult.data;
|
|
872
|
+
if (argv.locale)
|
|
873
|
+
await setLocaleAsync(argv.locale);
|
|
552
874
|
if (argv.version) {
|
|
553
875
|
console.log(`${cliPackage.name} v${cliPackage.version}`);
|
|
554
|
-
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (argv.help) {
|
|
879
|
+
console.log(getHelpMessage());
|
|
880
|
+
return;
|
|
555
881
|
}
|
|
556
|
-
// Если какой-либо функционал указан заранее, пропустить этот вопрос.
|
|
557
|
-
const isFeatureFlagsUsed = Object.keys(featureFlags)
|
|
558
|
-
.some(flag => typeof argv[flag] === 'boolean');
|
|
559
|
-
let targetDir = positionals[0];
|
|
560
|
-
const hasPositionalDir = targetDir && targetDir !== '.';
|
|
561
|
-
const defaultProjectName = hasPositionalDir ? targetDir : 'wb-rules-mirta';
|
|
562
|
-
const shouldOverwrite = argv.force;
|
|
563
|
-
const sshAddress = parseUrl(argv.ssh);
|
|
564
|
-
const sshDefaultUser = 'root';
|
|
565
|
-
const sshDefaultHost = '10.200.200.1';
|
|
566
|
-
const sshDefaultPort = '22';
|
|
567
|
-
const sshFullyDefined = !!(sshAddress.username && sshAddress.hostname);
|
|
568
|
-
const scope = {
|
|
569
|
-
projectName: defaultProjectName,
|
|
570
|
-
projectRoot: '',
|
|
571
|
-
packageName: defaultProjectName,
|
|
572
|
-
shouldOverwrite,
|
|
573
|
-
features: [],
|
|
574
|
-
sshUsername: sshAddress.username,
|
|
575
|
-
sshHostname: sshAddress.hostname,
|
|
576
|
-
sshPort: sshAddress.port,
|
|
577
|
-
rutoken: argv.rutoken,
|
|
578
|
-
};
|
|
579
882
|
console.log(banner);
|
|
580
|
-
console.log(
|
|
883
|
+
console.log(t('title'));
|
|
581
884
|
console.log();
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (hasPositionalDir) {
|
|
598
|
-
step(`${messages.projectName.message}\n${dim(baseName)}`);
|
|
599
|
-
}
|
|
600
|
-
// Защита от выхода за пределы рабочей директории.
|
|
601
|
-
if (!root.startsWith(cwd)) {
|
|
602
|
-
cancel(messages.errors.rootIsNotRelative);
|
|
603
|
-
process.exit(1);
|
|
604
|
-
}
|
|
605
|
-
if (!isValidPackageName(targetDir)) {
|
|
606
|
-
scope.packageName = await prompt(text({
|
|
607
|
-
message: messages.packageName.message,
|
|
608
|
-
initialValue: toValidPackageName(baseName),
|
|
609
|
-
validate: value => isValidPackageName(value)
|
|
610
|
-
? undefined
|
|
611
|
-
: messages.packageName.errorMessage,
|
|
612
|
-
}));
|
|
613
|
-
}
|
|
614
|
-
if (!canSkipDir(targetDir) && !shouldOverwrite) {
|
|
615
|
-
scope.shouldOverwrite = await prompt(confirm({
|
|
616
|
-
message: `${targetDir === '.'
|
|
617
|
-
? messages.shouldOverwrite.directory.current
|
|
618
|
-
: messages.shouldOverwrite.directory.target} "${baseName}" ${messages.shouldOverwrite.message} ${red(messages.shouldOverwrite.confirmDelete)}`,
|
|
619
|
-
initialValue: false,
|
|
620
|
-
}));
|
|
621
|
-
if (!scope.shouldOverwrite) {
|
|
622
|
-
cancel();
|
|
623
|
-
process.exit(0);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
message(chalk.bgBlackBright.black(` ${messages.captions.deploy} `));
|
|
627
|
-
if (!scope.sshUsername) {
|
|
628
|
-
scope.sshUsername = await prompt(text({
|
|
629
|
-
message: messages.ssh.username,
|
|
630
|
-
placeholder: sshDefaultUser,
|
|
631
|
-
defaultValue: sshDefaultUser,
|
|
632
|
-
validate: (value) => {
|
|
633
|
-
if (value.length !== 0 && value.trim().length === 0)
|
|
634
|
-
return messages.validation.required;
|
|
635
|
-
},
|
|
636
|
-
}));
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
step(`${messages.ssh.username}\n${dim(scope.sshUsername)}`);
|
|
640
|
-
}
|
|
641
|
-
if (!scope.sshHostname) {
|
|
642
|
-
scope.sshHostname = await prompt(text({
|
|
643
|
-
message: messages.ssh.host,
|
|
644
|
-
placeholder: sshDefaultHost,
|
|
645
|
-
defaultValue: sshDefaultHost,
|
|
646
|
-
validate: (value) => {
|
|
647
|
-
if (value.length !== 0 && value.trim().length === 0)
|
|
648
|
-
return messages.validation.required;
|
|
649
|
-
},
|
|
650
|
-
}));
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
step(`${messages.ssh.host}\n${dim(scope.sshHostname)}`);
|
|
654
|
-
}
|
|
655
|
-
if (!sshFullyDefined && !scope.sshPort) {
|
|
656
|
-
scope.sshPort = await prompt(text({
|
|
657
|
-
message: messages.ssh.port,
|
|
658
|
-
placeholder: sshDefaultPort,
|
|
659
|
-
defaultValue: sshDefaultPort,
|
|
660
|
-
validate: (value) => {
|
|
661
|
-
if (value.length !== 0 && value.trim().length === 0)
|
|
662
|
-
return messages.validation.required;
|
|
663
|
-
},
|
|
664
|
-
}));
|
|
665
|
-
}
|
|
666
|
-
else if (scope.sshPort) {
|
|
667
|
-
step(`${messages.ssh.port}\n${dim(scope.sshPort)}`);
|
|
668
|
-
}
|
|
669
|
-
if (!sshFullyDefined && !scope.rutoken) {
|
|
670
|
-
scope.rutoken = await prompt(confirm({
|
|
671
|
-
message: `${messages.ssh.useRutoken} ${dim(messages.accent.ifConfigured)}`,
|
|
672
|
-
initialValue: false,
|
|
673
|
-
}));
|
|
674
|
-
}
|
|
675
|
-
else if (scope.rutoken) {
|
|
676
|
-
step(`${messages.ssh.useRutoken} ${dim(messages.accent.ifConfigured)}\n${dim('Yes')}`);
|
|
677
|
-
}
|
|
678
|
-
if (!isFeatureFlagsUsed) {
|
|
679
|
-
scope.features = await prompt(multiselect({
|
|
680
|
-
message: `${messages.featureSelection.message}${inlineSub(dim(messages.featureSelection.hint))}`,
|
|
681
|
-
options: featureOptions,
|
|
682
|
-
required: false,
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
const { features } = scope;
|
|
686
|
-
const addEslint = argv.eslint === true || features.includes('eslint');
|
|
687
|
-
const addStore = argv.store === true || features.includes('store');
|
|
688
|
-
const addVitest = argv.vitest === true || features.includes('vitest');
|
|
689
|
-
// Очистка целевой директории
|
|
690
|
-
if (fs.existsSync(root)) {
|
|
691
|
-
if (scope.shouldOverwrite)
|
|
692
|
-
emptyDir(root);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
fs.mkdirSync(root);
|
|
696
|
-
}
|
|
697
|
-
step(`${messages.status.scaffolding} ${yellow(root)}`);
|
|
698
|
-
const pkg = {
|
|
699
|
-
name: scope.packageName,
|
|
700
|
-
version: '0.0.0',
|
|
701
|
-
scripts: {
|
|
702
|
-
'wb:deploy': '',
|
|
703
|
-
},
|
|
704
|
-
};
|
|
705
|
-
const deployScript = ['rsync'];
|
|
706
|
-
if (process.platform === 'win32') {
|
|
707
|
-
// Run rsync trough WSL on Windows systems.
|
|
708
|
-
deployScript.unshift('wsl ');
|
|
709
|
-
}
|
|
710
|
-
if (scope.rutoken || scope.sshPort) {
|
|
711
|
-
deployScript.push(' -e \'ssh');
|
|
712
|
-
if (scope.rutoken)
|
|
713
|
-
deployScript.push(' -I /usr/lib/librtpkcs11ecp.so');
|
|
714
|
-
if (scope.sshPort)
|
|
715
|
-
deployScript.push(` -p ${scope.sshPort}`);
|
|
716
|
-
deployScript.push('\'');
|
|
717
|
-
}
|
|
718
|
-
deployScript.push(' -rltzvgO --progress --delete --exclude=\'alarms.conf\'');
|
|
719
|
-
deployScript.push(' --groupmap=\'*:developers\' dist/es5/*');
|
|
720
|
-
deployScript.push(` '${scope.sshUsername}@${scope.sshHostname}:/mnt/data/etc/'`);
|
|
721
|
-
pkg.scripts['wb:deploy'] = deployScript.join('');
|
|
722
|
-
fs.writeFileSync(resolve(root, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
723
|
-
const templateRoot = fileURLToPath(new URL(templatesPath, import.meta.url));
|
|
724
|
-
const render = function (templateName) {
|
|
725
|
-
const templateDir = resolve(templateRoot, templateName);
|
|
726
|
-
renderTemplate(templateDir, root);
|
|
727
|
-
};
|
|
728
|
-
// Create basic project structure
|
|
729
|
-
render('base');
|
|
730
|
-
// Add TypeScript support
|
|
731
|
-
render('config/typescript');
|
|
732
|
-
if (addEslint) {
|
|
733
|
-
renderEslintConfig(templateRoot, root, {
|
|
734
|
-
addVitest,
|
|
735
|
-
});
|
|
736
|
-
render('config/eslint');
|
|
737
|
-
}
|
|
738
|
-
if (!argv.bare) {
|
|
739
|
-
render('example/base');
|
|
885
|
+
// Определяем тип шаблона - по аргументам или через вопрос пользователю.
|
|
886
|
+
const selection = await pickProjectAsync(argv.template?.toLowerCase());
|
|
887
|
+
const runner = await resolveRunnerAsync(selection.type);
|
|
888
|
+
const context = await resolveProjectContextAsync(selection, {
|
|
889
|
+
projectFolder: positionals[0],
|
|
890
|
+
forceOverwrite: argv.force,
|
|
891
|
+
barebone: argv.bare,
|
|
892
|
+
});
|
|
893
|
+
await runner.runAsync(runnerArgs, context);
|
|
894
|
+
console.log();
|
|
895
|
+
console.log(getFinalMessage());
|
|
896
|
+
}
|
|
897
|
+
run().catch((e) => {
|
|
898
|
+
if (e instanceof PromptCanceledError || e instanceof OperationCanceledError) {
|
|
899
|
+
logger.cancel(t('step.canceled'));
|
|
740
900
|
}
|
|
741
|
-
if (
|
|
742
|
-
|
|
743
|
-
if (!argv.bare) {
|
|
744
|
-
render('example/store');
|
|
745
|
-
}
|
|
901
|
+
else if (e instanceof CreationError) {
|
|
902
|
+
logger.error(e.message);
|
|
746
903
|
}
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
render('example/vitest/base');
|
|
751
|
-
if (addStore) {
|
|
752
|
-
render('example/vitest/store');
|
|
753
|
-
}
|
|
754
|
-
}
|
|
904
|
+
else if (e instanceof Error) {
|
|
905
|
+
// Unexpected internal error - rethrow to preserve stack trace
|
|
906
|
+
throw e;
|
|
755
907
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
message: `${messages.dependencies.question} ${dim(messages.accent.recommended)}`,
|
|
759
|
-
options: runningPackageManager
|
|
760
|
-
? [
|
|
761
|
-
{
|
|
762
|
-
value: runningPackageManager.name,
|
|
763
|
-
label: `${messages.dependencies.current.message} ${runningPackageManager.name}`,
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
value: '',
|
|
767
|
-
label: messages.dependencies.no.message,
|
|
768
|
-
},
|
|
769
|
-
]
|
|
770
|
-
: [
|
|
771
|
-
{
|
|
772
|
-
value: 'pnpm',
|
|
773
|
-
label: messages.dependencies.pnpm.message,
|
|
774
|
-
hint: messages.dependencies.pnpm.hint,
|
|
775
|
-
},
|
|
776
|
-
{
|
|
777
|
-
value: 'yarn',
|
|
778
|
-
label: messages.dependencies.yarn.message,
|
|
779
|
-
},
|
|
780
|
-
{
|
|
781
|
-
value: 'npm',
|
|
782
|
-
label: messages.dependencies.npm.message,
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
value: 'bun',
|
|
786
|
-
label: messages.dependencies.bun.message,
|
|
787
|
-
},
|
|
788
|
-
{
|
|
789
|
-
value: '',
|
|
790
|
-
label: messages.dependencies.no.message,
|
|
791
|
-
},
|
|
792
|
-
],
|
|
793
|
-
}));
|
|
794
|
-
if (scope.packageManager) {
|
|
795
|
-
outro(messages.status.installingDependencies);
|
|
796
|
-
await installDependencies(scope);
|
|
908
|
+
else if (typeof e === 'string') {
|
|
909
|
+
logger.error(e);
|
|
797
910
|
}
|
|
798
|
-
console.log();
|
|
799
|
-
console.log(finalMessage);
|
|
800
|
-
}
|
|
801
|
-
run().catch((e) => {
|
|
802
|
-
console.error(e);
|
|
803
911
|
process.exit(1);
|
|
804
912
|
});
|
|
913
|
+
|
|
914
|
+
export { FEATURE_ORIGIN_CLI as F, OperationCanceledError as O, assertNoParseErrors as a, clearDirAsync as c, isExistsAsync as i, logger as l, prompts as p, t };
|