ylyx-cli 1.0.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +313 -276
- package/bin/ylyx.js +32 -24
- package/lib/config.js +188 -162
- package/lib/deploy.js +100 -86
- package/lib/environments.js +111 -0
- package/package.json +61 -59
- package/templates/react-app/files/.gitignore +22 -0
- package/templates/vue-app/files/.gitignore +24 -0
package/lib/deploy.js
CHANGED
|
@@ -4,6 +4,7 @@ const ora = require('ora');
|
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
const archiver = require('archiver');
|
|
6
6
|
const { readProjectConfig } = require('./utils');
|
|
7
|
+
const { assertConfigMigrated, findEnvironment, getConfigPath } = require('./environments');
|
|
7
8
|
|
|
8
9
|
let spinner = null; // 加载实例
|
|
9
10
|
|
|
@@ -123,6 +124,40 @@ function getBuildDirBaseName({ buildDir, localDir }) {
|
|
|
123
124
|
return sanitizeBuildDirName(path.basename(path.resolve(localDir || process.cwd())));
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
function normalizeAndValidateRemoteDir(remoteDir) {
|
|
128
|
+
if (!remoteDir || typeof remoteDir !== 'string') return null;
|
|
129
|
+
// 统一成 Linux 风格
|
|
130
|
+
const raw = remoteDir.trim().replace(/\\/g, '/');
|
|
131
|
+
// 必须是绝对路径
|
|
132
|
+
if (!raw.startsWith('/')) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`deploy.remoteDir 必须是 Linux 绝对路径,并且只能在 /usr/local/nginx/html/* 下(当前: ${remoteDir})`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 归一化,消除 .. 等
|
|
139
|
+
const resolved = path.posix.resolve(raw);
|
|
140
|
+
const base = '/usr/local/nginx/html';
|
|
141
|
+
const baseWithSlash = base + '/';
|
|
142
|
+
|
|
143
|
+
// 只允许 /usr/local/nginx/html/*(必须是子目录,不允许刚好等于 base)
|
|
144
|
+
if (!resolved.startsWith(baseWithSlash)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`deploy.remoteDir 只能设置为 ${baseWithSlash}* 下的目录(当前: ${resolved})`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (resolved === base) {
|
|
150
|
+
throw new Error(`deploy.remoteDir 不能是 ${base} 根目录,请设置为其子目录(例如 ${baseWithSlash}my-app)`);
|
|
151
|
+
}
|
|
152
|
+
// 再防一次:禁止路径段包含 ..(即便 resolve 已处理,这里保证输入约束更明确)
|
|
153
|
+
const rel = resolved.slice(baseWithSlash.length);
|
|
154
|
+
if (!rel || rel.split('/').some((seg) => seg === '..')) {
|
|
155
|
+
throw new Error(`deploy.remoteDir 非法:不允许包含 ..(当前: ${resolved})`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return resolved;
|
|
159
|
+
}
|
|
160
|
+
|
|
126
161
|
async function zipDirAsTopFolder({ srcDir, zipPath, topFolderName }) {
|
|
127
162
|
fs.ensureDirSync(path.dirname(zipPath));
|
|
128
163
|
await new Promise((resolve, reject) => {
|
|
@@ -172,18 +207,34 @@ function computeDirHashLetters(localDir) {
|
|
|
172
207
|
return hashLettersFromSha256(digest, 12);
|
|
173
208
|
}
|
|
174
209
|
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
// 注意:这里"环境不存在或不可部署"的判断逻辑与 resolveDeployConfig 内部的
|
|
211
|
+
// findEnvironment + deployMethod==='none' 检查在语义上等价,只是分别在
|
|
212
|
+
// "交互选择阶段"和"最终解析阶段"各自独立校验一次(后者作为独立导出函数,
|
|
213
|
+
// 必须能脱离 chooseDeployEnv 单独工作,因此不合并成一个函数)。
|
|
214
|
+
async function chooseDeployEnv(projectConfig, envArg) {
|
|
215
|
+
const environments = (projectConfig.environments || []).filter((e) => e.deployMethod !== 'none');
|
|
216
|
+
|
|
217
|
+
if (envArg) {
|
|
218
|
+
if (!environments.some((e) => e.name === envArg)) {
|
|
219
|
+
const available = environments.map((e) => e.name).join(', ') || '(无)';
|
|
220
|
+
throw new Error(`环境 "${envArg}" 不可部署或不存在\n可部署环境: ${available}`);
|
|
221
|
+
}
|
|
222
|
+
return envArg;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (environments.length === 0) {
|
|
226
|
+
throw new Error('没有可部署的环境(所有环境的 deployMethod 均为 none,或未定义 environments)');
|
|
227
|
+
}
|
|
228
|
+
|
|
177
229
|
const ans = await inquirer.prompt([
|
|
178
230
|
{
|
|
179
231
|
type: 'list',
|
|
180
232
|
name: 'env',
|
|
181
233
|
message: '请选择部署环境',
|
|
182
|
-
choices:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
default: 'test',
|
|
234
|
+
choices: environments.map((e) => ({
|
|
235
|
+
name: `${e.name}(${e.deployMethod === 'upload' ? '上传到服务器' : '仅压缩'})`,
|
|
236
|
+
value: e.name,
|
|
237
|
+
})),
|
|
187
238
|
},
|
|
188
239
|
]);
|
|
189
240
|
return ans.env;
|
|
@@ -255,96 +306,60 @@ async function uploadDirWithProgress({ sftp, localDir, remoteDirPosix, onProgres
|
|
|
255
306
|
return { totalBytes, uploadedBytes, fileCount: files.length };
|
|
256
307
|
}
|
|
257
308
|
|
|
258
|
-
function resolveDeployConfig(overrides = {}) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
function parseDotEnvFile(envPath) {
|
|
263
|
-
if (!fs.existsSync(envPath)) return {};
|
|
264
|
-
const content = fs.readFileSync(envPath, 'utf-8');
|
|
265
|
-
const lines = content.split(/\r?\n/);
|
|
266
|
-
const out = {};
|
|
267
|
-
for (const raw of lines) {
|
|
268
|
-
const line = raw.trim();
|
|
269
|
-
if (!line || line.startsWith('#')) continue;
|
|
270
|
-
const idx = line.indexOf('=');
|
|
271
|
-
if (idx < 0) continue;
|
|
272
|
-
const key = line.slice(0, idx).trim();
|
|
273
|
-
let value = line.slice(idx + 1).trim();
|
|
274
|
-
if (
|
|
275
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
276
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
277
|
-
) {
|
|
278
|
-
value = value.slice(1, -1);
|
|
279
|
-
}
|
|
280
|
-
out[key] = value;
|
|
281
|
-
}
|
|
282
|
-
return out;
|
|
309
|
+
function resolveDeployConfig(envName, overrides = {}, projectConfig) {
|
|
310
|
+
if (!projectConfig) {
|
|
311
|
+
projectConfig = readProjectConfig();
|
|
312
|
+
assertConfigMigrated(projectConfig, fs.existsSync(getConfigPath()));
|
|
283
313
|
}
|
|
314
|
+
const env = findEnvironment(projectConfig, envName);
|
|
284
315
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (!publicUrl || typeof publicUrl !== 'string') return '';
|
|
288
|
-
let v = publicUrl.trim();
|
|
289
|
-
v = v.replace(/\\/g, '/');
|
|
290
|
-
if (/^https?:\/\//i.test(v)) return '';
|
|
291
|
-
v = v.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
292
|
-
return v;
|
|
316
|
+
if (env.deployMethod === 'none') {
|
|
317
|
+
throw new Error(`环境 "${env.name}" 不支持部署(deployMethod=none)`);
|
|
293
318
|
}
|
|
294
319
|
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
const publicUrlDir = normalizePublicUrlToDir(envProd.VUE_APP_PUBLIC_URL);
|
|
299
|
-
const inferredFromEnvProd = publicUrlDir ? path.resolve(process.cwd(), publicUrlDir) : null;
|
|
320
|
+
const buildDir = overrides.buildDir ?? env.buildDir;
|
|
321
|
+
const localDirRaw = overrides.localDir ?? env.localDir ?? buildDir;
|
|
322
|
+
const localDir = localDirRaw ? path.resolve(process.cwd(), localDirRaw) : null;
|
|
300
323
|
|
|
301
|
-
|
|
302
|
-
// 规则:优先 deploy.localDir;否则使用 .env.production 的 VUE_APP_PUBLIC_URL;否则使用 .ylyxrc.json 的 buildDir;最后才回退 ./EXTERNAL_DIGIC
|
|
303
|
-
const inferredLocalDir =
|
|
304
|
-
deploy.localDir ||
|
|
305
|
-
inferredFromEnvProd ||
|
|
306
|
-
(projectConfig && projectConfig.buildDir ? path.resolve(process.cwd(), projectConfig.buildDir) : null);
|
|
307
|
-
|
|
308
|
-
if (!inferredLocalDir) {
|
|
324
|
+
if (!localDir) {
|
|
309
325
|
throw new Error(
|
|
310
|
-
|
|
311
|
-
'- 在 .ylyxrc.json 配置 deploy.localDir\n' +
|
|
312
|
-
'- 在项目 .env.production 配置 VUE_APP_PUBLIC_URL(相对路径,如 /dist/app/)\n' +
|
|
313
|
-
'- 在 .ylyxrc.json 配置 buildDir(可相对/绝对路径)'
|
|
326
|
+
`无法确定环境 "${env.name}" 的本地目录(用于上传/压缩)。请在 .ylyxrc.json 该环境条目配置 localDir 或 buildDir`
|
|
314
327
|
);
|
|
315
328
|
}
|
|
316
329
|
|
|
317
330
|
const cfg = {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
331
|
+
name: env.name,
|
|
332
|
+
deployMethod: env.deployMethod,
|
|
333
|
+
buildDir,
|
|
334
|
+
host: overrides.host ?? env.host,
|
|
335
|
+
port: String(overrides.port ?? env.port ?? '22'),
|
|
336
|
+
username: overrides.username ?? env.username,
|
|
337
|
+
password: overrides.password ?? env.password,
|
|
338
|
+
localDir,
|
|
339
|
+
remoteDir: normalizeAndValidateRemoteDir(overrides.remoteDir ?? env.remoteDir),
|
|
340
|
+
zipAfter: overrides.zipAfter ?? env.zipAfter ?? false,
|
|
341
|
+
zipOutDir: path.resolve(process.cwd(), overrides.zipOutDir ?? env.zipOutDir ?? './.ylyx-deploy'),
|
|
328
342
|
};
|
|
329
343
|
|
|
330
344
|
return cfg;
|
|
331
345
|
}
|
|
332
346
|
|
|
333
347
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* 正式服:不上传,仅压缩
|
|
337
|
-
* @param {{env?:'test'|'prod',host?:string,port?:string|number,username?:string,password?:string,localDir?:string,remoteDir?:string,zipAfter?:boolean,zipOutDir?:string}} overrides
|
|
348
|
+
* 执行部署(按环境的 deployMethod 分支:upload=SFTP 上传,zip=仅压缩)
|
|
349
|
+
* @param {{env?:string,host?:string,port?:string|number,username?:string,password?:string,localDir?:string,remoteDir?:string,zipAfter?:boolean,zipOutDir?:string,buildDir?:string}} overrides
|
|
338
350
|
*/
|
|
339
351
|
async function deploy(overrides = {}) {
|
|
340
|
-
const
|
|
352
|
+
const projectConfig = readProjectConfig();
|
|
353
|
+
assertConfigMigrated(projectConfig, fs.existsSync(getConfigPath()));
|
|
354
|
+
|
|
355
|
+
const envName = await chooseDeployEnv(projectConfig, overrides.env);
|
|
356
|
+
const cfg = resolveDeployConfig(envName, overrides, projectConfig);
|
|
341
357
|
const localFolderPath = cfg.localDir;
|
|
342
358
|
const remoteFolderPath = cfg.remoteDir;
|
|
343
359
|
|
|
344
|
-
const env = await chooseDeployEnv(cfg.env);
|
|
345
360
|
const buildDirNameForLog = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
|
|
346
|
-
const logger = createDeployLogger({ env, buildDirName: buildDirNameForLog, outDir: cfg.zipOutDir });
|
|
347
|
-
logger.logOp('DEPLOY_START', '', `env=${
|
|
361
|
+
const logger = createDeployLogger({ env: cfg.name, buildDirName: buildDirNameForLog, outDir: cfg.zipOutDir });
|
|
362
|
+
logger.logOp('DEPLOY_START', '', `env=${cfg.name}`);
|
|
348
363
|
logger.logOp('LOCAL_DIR', localFolderPath);
|
|
349
364
|
if (remoteFolderPath) logger.logOp('REMOTE_DIR', remoteFolderPath);
|
|
350
365
|
|
|
@@ -354,12 +369,11 @@ async function deploy(overrides = {}) {
|
|
|
354
369
|
throw new Error(`本地目录不存在:${path.resolve(localFolderPath)}`);
|
|
355
370
|
}
|
|
356
371
|
|
|
357
|
-
//
|
|
358
|
-
if (
|
|
372
|
+
// deployMethod=zip:仅压缩,不上传
|
|
373
|
+
if (cfg.deployMethod === 'zip') {
|
|
359
374
|
const buildDirName = buildDirNameForLog;
|
|
360
375
|
const ts = formatTimestampCompact(new Date());
|
|
361
376
|
const hashLetters = computeDirHashLetters(localFolderPath); // 纯字母
|
|
362
|
-
// 优化命名:更可读、更易分割(buildDir-时间-哈希)
|
|
363
377
|
const zipBaseName = `${buildDirName}-${ts}-${hashLetters}`;
|
|
364
378
|
fs.ensureDirSync(cfg.zipOutDir);
|
|
365
379
|
const zipName = `${zipBaseName}.zip`;
|
|
@@ -377,19 +391,19 @@ async function deploy(overrides = {}) {
|
|
|
377
391
|
await logger.close();
|
|
378
392
|
throw e;
|
|
379
393
|
}
|
|
380
|
-
logger.logOp('DEPLOY_END', '',
|
|
394
|
+
logger.logOp('DEPLOY_END', '', cfg.name);
|
|
381
395
|
await logger.close();
|
|
382
396
|
return;
|
|
383
397
|
}
|
|
384
398
|
|
|
385
|
-
//
|
|
386
|
-
if (!cfg.host) throw new Error(
|
|
387
|
-
if (!cfg.username) throw new Error(
|
|
388
|
-
if (!cfg.password) throw new Error(
|
|
399
|
+
// deployMethod=upload:上传到服务器
|
|
400
|
+
if (!cfg.host) throw new Error(`缺少环境 "${cfg.name}" 的 host(上传需要)`);
|
|
401
|
+
if (!cfg.username) throw new Error(`缺少环境 "${cfg.name}" 的 username(上传需要)`);
|
|
402
|
+
if (!cfg.password) throw new Error(`缺少环境 "${cfg.name}" 的 password(上传需要)`);
|
|
389
403
|
if (!remoteFolderPath) {
|
|
390
404
|
logger.logOp('ERROR', '', 'missing_remoteDir');
|
|
391
405
|
await logger.close();
|
|
392
|
-
throw new Error(
|
|
406
|
+
throw new Error(`缺少环境 "${cfg.name}" 的 remoteDir(上传需要):请在 .ylyxrc.json 该环境条目配置 remoteDir`);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
let Client;
|
|
@@ -453,7 +467,7 @@ async function deploy(overrides = {}) {
|
|
|
453
467
|
if (spinner) spinner.info('部署结束');
|
|
454
468
|
}
|
|
455
469
|
|
|
456
|
-
//
|
|
470
|
+
// 上传后可选:再压缩一份(延续之前的可选开关)
|
|
457
471
|
if (cfg.zipAfter) {
|
|
458
472
|
const buildDirName = buildDirNameForLog;
|
|
459
473
|
const ts = formatTimestampCompact(new Date());
|
|
@@ -477,7 +491,7 @@ async function deploy(overrides = {}) {
|
|
|
477
491
|
}
|
|
478
492
|
}
|
|
479
493
|
|
|
480
|
-
logger.logOp('DEPLOY_END', '',
|
|
494
|
+
logger.logOp('DEPLOY_END', '', cfg.name);
|
|
481
495
|
await logger.close();
|
|
482
496
|
}
|
|
483
497
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const CONFIG_VERSION = 2;
|
|
5
|
+
|
|
6
|
+
function getConfigPath() {
|
|
7
|
+
return path.join(process.cwd(), '.ylyxrc.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readConfigSafe(configPath) {
|
|
11
|
+
if (!fs.existsSync(configPath)) return {};
|
|
12
|
+
try {
|
|
13
|
+
return fs.readJsonSync(configPath);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
throw new Error(`读取 .ylyxrc.json 失败,请确认它是有效 JSON:${e.message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isConfigMigrated(config) {
|
|
20
|
+
return !!config && typeof config.configVersion === 'number' && config.configVersion >= CONFIG_VERSION;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertConfigMigrated(config, configExists) {
|
|
24
|
+
if (!isConfigMigrated(config)) {
|
|
25
|
+
if (configExists === false) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'未找到 .ylyxrc.json 配置文件\n' +
|
|
28
|
+
' 请先运行以下命令初始化配置:\n' +
|
|
29
|
+
' ylyx init config'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
throw new Error(
|
|
33
|
+
'检测到旧版配置(.ylyxrc.json 缺少 configVersion)\n' +
|
|
34
|
+
' 请先运行以下命令完成迁移:\n' +
|
|
35
|
+
' ylyx migrate config'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findEnvironment(config, name) {
|
|
41
|
+
const environments = (config && config.environments) || [];
|
|
42
|
+
const env = environments.find((e) => e.name === name);
|
|
43
|
+
if (!env) {
|
|
44
|
+
const available = environments.map((e) => e.name).join(', ') || '(无)';
|
|
45
|
+
throw new Error(`环境不存在: ${name}\n可用环境: ${available}`);
|
|
46
|
+
}
|
|
47
|
+
return env;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 将旧版 .ylyxrc.json(mode/deploy/顶层 buildDir/preScripts)转换为新版 environments 结构
|
|
52
|
+
* 纯函数:不读写文件,方便单独验证
|
|
53
|
+
*/
|
|
54
|
+
function buildMigratedConfig(oldConfig) {
|
|
55
|
+
oldConfig = oldConfig || {};
|
|
56
|
+
const oldMode = oldConfig.mode === 'prod' ? 'prod' : 'dev';
|
|
57
|
+
const oldDeploy = oldConfig.deploy || {};
|
|
58
|
+
const oldPreScripts = oldConfig.preScripts || {};
|
|
59
|
+
const oldBuildDir = oldConfig.buildDir;
|
|
60
|
+
|
|
61
|
+
const devEnv = {
|
|
62
|
+
name: 'dev',
|
|
63
|
+
isLocal: true,
|
|
64
|
+
npmScript: oldPreScripts.dev || 'dev',
|
|
65
|
+
deployMethod: 'none',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const testEnv = {
|
|
69
|
+
name: 'test',
|
|
70
|
+
npmScript: 'build:test',
|
|
71
|
+
deployMethod: 'upload',
|
|
72
|
+
buildDir: oldDeploy.buildDir || oldBuildDir || '',
|
|
73
|
+
host: oldDeploy.host || '',
|
|
74
|
+
port: oldDeploy.port || '22',
|
|
75
|
+
username: oldDeploy.username || '',
|
|
76
|
+
password: oldDeploy.password || '',
|
|
77
|
+
localDir: oldDeploy.localDir || '',
|
|
78
|
+
remoteDir: oldDeploy.remoteDir || '',
|
|
79
|
+
zipAfter: oldDeploy.zipAfter || false,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const prodEnv = {
|
|
83
|
+
name: 'prod',
|
|
84
|
+
npmScript: oldPreScripts.prod || 'build:prod',
|
|
85
|
+
deployMethod: 'zip',
|
|
86
|
+
buildDir: oldBuildDir || '',
|
|
87
|
+
zipOutDir: oldDeploy.zipOutDir || './.ylyx-deploy',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const next = { ...oldConfig };
|
|
91
|
+
delete next.mode;
|
|
92
|
+
delete next.deploy;
|
|
93
|
+
delete next.buildDir;
|
|
94
|
+
delete next.preScripts;
|
|
95
|
+
|
|
96
|
+
next.configVersion = CONFIG_VERSION;
|
|
97
|
+
next.currentEnv = oldMode;
|
|
98
|
+
next.environments = [devEnv, testEnv, prodEnv];
|
|
99
|
+
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
CONFIG_VERSION,
|
|
105
|
+
getConfigPath,
|
|
106
|
+
readConfigSafe,
|
|
107
|
+
isConfigMigrated,
|
|
108
|
+
assertConfigMigrated,
|
|
109
|
+
findEnvironment,
|
|
110
|
+
buildMigratedConfig,
|
|
111
|
+
};
|
package/package.json
CHANGED
|
@@ -1,59 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ylyx-cli",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "公司内部代码生成模板脚手架工具,支持快速生成项目初始结构和代码模板",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"ylyx": "./bin/ylyx.js",
|
|
8
|
-
"ylyx-cli": "./bin/ylyx.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin",
|
|
12
|
-
"lib",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "ylyx-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "公司内部代码生成模板脚手架工具,支持快速生成项目初始结构和代码模板",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ylyx": "./bin/ylyx.js",
|
|
8
|
+
"ylyx-cli": "./bin/ylyx.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"lib/**/*.js",
|
|
13
|
+
"!lib/**/*.test.js",
|
|
14
|
+
"templates",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node bin/ylyx.js",
|
|
19
|
+
"test": "jest"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"cli",
|
|
23
|
+
"scaffold",
|
|
24
|
+
"code-generator",
|
|
25
|
+
"template",
|
|
26
|
+
"generator",
|
|
27
|
+
"scaffolding",
|
|
28
|
+
"boilerplate",
|
|
29
|
+
"project-template",
|
|
30
|
+
"vue",
|
|
31
|
+
"react"
|
|
32
|
+
],
|
|
33
|
+
"author": "YLYX",
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/your-org/ylyx-cli.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/your-org/ylyx-cli/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/your-org/ylyx-cli#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=12.0.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"archiver": "^5.3.2",
|
|
48
|
+
"commander": "^11.1.0",
|
|
49
|
+
"inquirer": "^8.2.6",
|
|
50
|
+
"chalk": "^4.1.2",
|
|
51
|
+
"fs-extra": "^11.2.0",
|
|
52
|
+
"handlebars": "^4.7.8",
|
|
53
|
+
"glob": "^10.3.10",
|
|
54
|
+
"ora": "^5.4.1",
|
|
55
|
+
"ssh2-sftp-client": "^9.0.4"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.10.0",
|
|
59
|
+
"jest": "^29.7.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
/node_modules
|
|
3
|
+
/.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
|
|
6
|
+
# testing
|
|
7
|
+
/coverage
|
|
8
|
+
|
|
9
|
+
# production
|
|
10
|
+
/build
|
|
11
|
+
|
|
12
|
+
# misc
|
|
13
|
+
.DS_Store
|
|
14
|
+
.env.local
|
|
15
|
+
.env.development.local
|
|
16
|
+
.env.test.local
|
|
17
|
+
.env.production.local
|
|
18
|
+
|
|
19
|
+
npm-debug.log*
|
|
20
|
+
yarn-debug.log*
|
|
21
|
+
yarn-error.log*
|
|
22
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
node_modules/
|
|
3
|
+
dist/
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
**/*.log
|
|
8
|
+
hospManage/
|
|
9
|
+
EXTERNAL_HOSPITAL/
|
|
10
|
+
tests/**/coverage/
|
|
11
|
+
tests/e2e/reports
|
|
12
|
+
selenium-debug.log
|
|
13
|
+
|
|
14
|
+
# Editor directories and files
|
|
15
|
+
.idea
|
|
16
|
+
# .vscode
|
|
17
|
+
*.suo
|
|
18
|
+
*.ntvs*
|
|
19
|
+
*.njsproj
|
|
20
|
+
*.sln
|
|
21
|
+
*.local
|
|
22
|
+
|
|
23
|
+
package-lock.json
|
|
24
|
+
yarn.lock
|