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/bin/ylyx.js
CHANGED
|
@@ -4,7 +4,7 @@ const { program } = require('commander');
|
|
|
4
4
|
const { generate } = require('../lib/index');
|
|
5
5
|
const { listTemplates, getTemplateInfo, addTemplate } = require('../lib/template');
|
|
6
6
|
const { interactiveCreate } = require('../lib/interactive');
|
|
7
|
-
const {
|
|
7
|
+
const { setEnv, initConfig, migrateConfig, chooseEnvInteractive } = require('../lib/config');
|
|
8
8
|
const { deploy } = require('../lib/deploy');
|
|
9
9
|
const pkg = require('../package.json');
|
|
10
10
|
|
|
@@ -18,21 +18,15 @@ program
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
program
|
|
21
|
-
.command('config [
|
|
22
|
-
.description('写入/更新 .ylyxrc.json
|
|
23
|
-
.
|
|
24
|
-
.option('--symlink', '切换 default.js 时强制使用 symlink(dev 默认)')
|
|
25
|
-
.option('--copy', '切换 default.js 时强制使用 copy')
|
|
26
|
-
.action((modeArg, options) => {
|
|
21
|
+
.command('config [name]')
|
|
22
|
+
.description('写入/更新 .ylyxrc.json 配置(切换到指定环境;不传参数时交互式选择)')
|
|
23
|
+
.action(async (name) => {
|
|
27
24
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
process.exit(1);
|
|
25
|
+
if (name) {
|
|
26
|
+
setEnv(name);
|
|
27
|
+
} else {
|
|
28
|
+
await chooseEnvInteractive();
|
|
33
29
|
}
|
|
34
|
-
const method = options.copy ? 'copy' : options.symlink ? 'symlink' : undefined;
|
|
35
|
-
setMode(mode, { method });
|
|
36
30
|
} catch (error) {
|
|
37
31
|
console.error('❌ 配置失败:', error.message);
|
|
38
32
|
process.exit(1);
|
|
@@ -44,7 +38,7 @@ program
|
|
|
44
38
|
.description('初始化相关资源')
|
|
45
39
|
.command('config')
|
|
46
40
|
.description('初始化配置(生成 .ylyxrc.json / config/default-*.js 等)')
|
|
47
|
-
.option('-
|
|
41
|
+
.option('-e, --env <name>', '初始 currentEnv(默认 dev)')
|
|
48
42
|
.option('-f, --force', '强制覆盖已存在文件')
|
|
49
43
|
.action((options) => {
|
|
50
44
|
try {
|
|
@@ -55,6 +49,20 @@ program
|
|
|
55
49
|
}
|
|
56
50
|
});
|
|
57
51
|
|
|
52
|
+
program
|
|
53
|
+
.command('migrate')
|
|
54
|
+
.description('迁移相关资源')
|
|
55
|
+
.command('config')
|
|
56
|
+
.description('迁移旧版 .ylyxrc.json(mode/deploy)到 environments 结构')
|
|
57
|
+
.action(() => {
|
|
58
|
+
try {
|
|
59
|
+
migrateConfig();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('❌ 迁移失败:', error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
58
66
|
program
|
|
59
67
|
.command('generate <template-name>')
|
|
60
68
|
.alias('g')
|
|
@@ -113,15 +121,15 @@ program
|
|
|
113
121
|
|
|
114
122
|
program
|
|
115
123
|
.command('deploy')
|
|
116
|
-
.description('部署:递归上传本地目录到远端目录(读取当前项目 .ylyxrc.json 的
|
|
117
|
-
.option('--env <
|
|
118
|
-
.option('--build-dir <name>', '压缩文件名使用的 buildDir
|
|
119
|
-
.option('--host <host>', '
|
|
120
|
-
.option('--port <port>', '
|
|
121
|
-
.option('-u, --username <username>', '
|
|
122
|
-
.option('-p, --password <password>', '
|
|
123
|
-
.option('--local <path>', '
|
|
124
|
-
.option('--remote <path>', '
|
|
124
|
+
.description('部署:递归上传本地目录到远端目录(读取当前项目 .ylyxrc.json 的 environments 数组)')
|
|
125
|
+
.option('--env <name>', '部署环境名称(不传会交互选择)')
|
|
126
|
+
.option('--build-dir <name>', '压缩文件名使用的 buildDir(默认取该环境的 buildDir)')
|
|
127
|
+
.option('--host <host>', '覆盖该环境的 host')
|
|
128
|
+
.option('--port <port>', '覆盖该环境的 port')
|
|
129
|
+
.option('-u, --username <username>', '覆盖该环境的 username')
|
|
130
|
+
.option('-p, --password <password>', '覆盖该环境的 password')
|
|
131
|
+
.option('--local <path>', '覆盖该环境的 localDir(默认取该环境的 buildDir)')
|
|
132
|
+
.option('--remote <path>', '覆盖该环境的 remoteDir')
|
|
125
133
|
.option('--zip', '部署后把 localDir 压缩成 zip(参考 compressing.zip.compressDir)')
|
|
126
134
|
.option('--zip-out <path>', 'zip 输出目录(默认:./.ylyx-deploy)')
|
|
127
135
|
.action(async (options) => {
|
package/lib/config.js
CHANGED
|
@@ -1,77 +1,15 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
3
4
|
const { log } = require('./utils');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (m === 'dev' || m === 'prod') return m;
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function parseDotEnvFile(envPath) {
|
|
17
|
-
if (!fs.existsSync(envPath)) return {};
|
|
18
|
-
const content = fs.readFileSync(envPath, 'utf-8');
|
|
19
|
-
const lines = content.split(/\r?\n/);
|
|
20
|
-
const out = {};
|
|
21
|
-
for (const raw of lines) {
|
|
22
|
-
const line = raw.trim();
|
|
23
|
-
if (!line || line.startsWith('#')) continue;
|
|
24
|
-
const idx = line.indexOf('=');
|
|
25
|
-
if (idx < 0) continue;
|
|
26
|
-
const key = line.slice(0, idx).trim();
|
|
27
|
-
let value = line.slice(idx + 1).trim();
|
|
28
|
-
if (
|
|
29
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
30
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
31
|
-
) {
|
|
32
|
-
value = value.slice(1, -1);
|
|
33
|
-
}
|
|
34
|
-
out[key] = value;
|
|
35
|
-
}
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function normalizePublicUrlToDir(publicUrl) {
|
|
40
|
-
if (!publicUrl || typeof publicUrl !== 'string') return '';
|
|
41
|
-
let v = publicUrl.trim();
|
|
42
|
-
v = v.replace(/\\/g, '/');
|
|
43
|
-
// ignore full urls
|
|
44
|
-
if (/^https?:\/\//i.test(v)) return '';
|
|
45
|
-
v = v.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
46
|
-
return v;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resolveProdBuildDir(config) {
|
|
50
|
-
// 1) 优先使用 .ylyxrc.json 的 buildDir(可为相对/绝对路径)
|
|
51
|
-
if (config.buildDir) {
|
|
52
|
-
return path.resolve(process.cwd(), config.buildDir);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 2) 默认:读取 .env.production 的 VUE_APP_PUBLIC_URL,拼成 <publicUrlDir>
|
|
56
|
-
const envProdPath = path.join(process.cwd(), '.env.production');
|
|
57
|
-
const env = parseDotEnvFile(envProdPath);
|
|
58
|
-
const publicUrlDir = normalizePublicUrlToDir(env.VUE_APP_PUBLIC_URL);
|
|
59
|
-
if (publicUrlDir) {
|
|
60
|
-
return path.resolve(process.cwd(), publicUrlDir);
|
|
61
|
-
}
|
|
62
|
-
throw new Error(
|
|
63
|
-
'无法确定 prod 打包输出目录:请在 .ylyxrc.json 中配置 buildDir,或在 .env.production 中配置 VUE_APP_PUBLIC_URL'
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function readConfigSafe(configPath) {
|
|
68
|
-
if (!fs.existsSync(configPath)) return {};
|
|
69
|
-
try {
|
|
70
|
-
return fs.readJsonSync(configPath);
|
|
71
|
-
} catch (e) {
|
|
72
|
-
throw new Error(`读取 .ylyxrc.json 失败,请确认它是有效 JSON:${e.message}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
5
|
+
const {
|
|
6
|
+
getConfigPath,
|
|
7
|
+
readConfigSafe,
|
|
8
|
+
assertConfigMigrated,
|
|
9
|
+
findEnvironment,
|
|
10
|
+
buildMigratedConfig,
|
|
11
|
+
CONFIG_VERSION,
|
|
12
|
+
} = require('./environments');
|
|
75
13
|
|
|
76
14
|
function writeIfAllowed(filePath, content, force) {
|
|
77
15
|
if (fs.existsSync(filePath) && !force) {
|
|
@@ -84,12 +22,23 @@ function writeIfAllowed(filePath, content, force) {
|
|
|
84
22
|
return true;
|
|
85
23
|
}
|
|
86
24
|
|
|
87
|
-
|
|
88
|
-
|
|
25
|
+
/**
|
|
26
|
+
* 生成 config/default-<envName>.js 的内容:
|
|
27
|
+
* 注入 `let env = '<envName>';` 与 `let isDev = (envName === 'dev');`(向后兼容旧代码)
|
|
28
|
+
*/
|
|
29
|
+
function deriveDefaultVariantContent(baseContent, envName) {
|
|
30
|
+
const isDev = envName === 'dev';
|
|
89
31
|
|
|
90
32
|
let content = baseContent || '';
|
|
33
|
+
const envRe = /let\s+env\s*=\s*[^;]*;/;
|
|
91
34
|
const devRe = /let\s+isDev\s*=\s*[^;]*;/;
|
|
92
35
|
|
|
36
|
+
if (envRe.test(content)) {
|
|
37
|
+
content = content.replace(envRe, `let env = '${envName}';`);
|
|
38
|
+
} else {
|
|
39
|
+
content = `let env = '${envName}';\n` + content;
|
|
40
|
+
}
|
|
41
|
+
|
|
93
42
|
if (devRe.test(content)) {
|
|
94
43
|
content = content.replace(devRe, `let isDev = ${isDev};`);
|
|
95
44
|
} else {
|
|
@@ -100,127 +49,201 @@ function deriveDefaultVariantContent(baseContent, mode) {
|
|
|
100
49
|
// 删除历史遗留的 isProd 变量与输出字段(若存在)
|
|
101
50
|
content = content.replace(/let\s+isProd\s*=\s*[^;]*;\s*\r?\n?/g, '');
|
|
102
51
|
content = content.replace(/window\.config\.IS_PROD\s*=\s*[^;]*;\s*\r?\n?/g, '');
|
|
103
|
-
// 删除对象形式的 IS_PROD 字段(例如 IS_PROD: isProd,)
|
|
104
52
|
content = content.replace(/^\s*IS_PROD\s*:\s*[^,}]*,?\s*\r?\n?/gm, '');
|
|
105
53
|
|
|
106
54
|
return content;
|
|
107
55
|
}
|
|
108
56
|
|
|
109
|
-
function
|
|
57
|
+
function copyFile({ src, dest }) {
|
|
110
58
|
fs.ensureDirSync(path.dirname(dest));
|
|
111
59
|
if (!fs.existsSync(src)) {
|
|
112
60
|
log.warn(`未找到源文件:${path.relative(process.cwd(), src)}`);
|
|
113
61
|
return { ok: false, method: 'missing' };
|
|
114
62
|
}
|
|
115
63
|
|
|
116
|
-
// 如果目标已存在,先删除(不管是文件还是链接)
|
|
117
64
|
if (fs.existsSync(dest)) {
|
|
118
65
|
fs.removeSync(dest);
|
|
119
66
|
}
|
|
120
67
|
|
|
121
|
-
if (preferSymlink) {
|
|
122
|
-
try {
|
|
123
|
-
// Windows/macOS/Linux 都支持;Windows 可能因为权限失败,失败后回退 copy
|
|
124
|
-
fs.ensureSymlinkSync(src, dest, 'file');
|
|
125
|
-
return { ok: true, method: 'symlink' };
|
|
126
|
-
} catch (e) {
|
|
127
|
-
log.warn(`创建 symlink 失败,将降级为 copy:${e.message}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
68
|
try {
|
|
132
69
|
fs.copyFileSync(src, dest);
|
|
133
|
-
return { ok: true
|
|
70
|
+
return { ok: true };
|
|
134
71
|
} catch (e) {
|
|
135
72
|
log.error(`写入失败:${e.message}`);
|
|
136
|
-
return { ok: false
|
|
73
|
+
return { ok: false };
|
|
137
74
|
}
|
|
138
75
|
}
|
|
139
76
|
|
|
140
77
|
/**
|
|
141
|
-
*
|
|
78
|
+
* 切换到指定环境:把 config/default-<name>.js 复制到该环境的目标位置
|
|
79
|
+
* - isLocal 环境 -> publicDir/default.js
|
|
80
|
+
* - 其他环境 -> <环境 buildDir>/default.js
|
|
142
81
|
*/
|
|
143
|
-
function
|
|
144
|
-
const normalized = normalizeMode(mode);
|
|
145
|
-
if (!normalized) {
|
|
146
|
-
throw new Error(`mode 取值只能是 dev 或 prod(当前: ${mode})`);
|
|
147
|
-
}
|
|
148
|
-
|
|
82
|
+
function setEnv(name) {
|
|
149
83
|
const configPath = getConfigPath();
|
|
84
|
+
const configExists = fs.existsSync(configPath);
|
|
150
85
|
const config = readConfigSafe(configPath);
|
|
86
|
+
assertConfigMigrated(config, configExists);
|
|
87
|
+
|
|
88
|
+
const env = findEnvironment(config, name);
|
|
151
89
|
|
|
152
|
-
// 可选:如果当前项目目录存在 public/default-{mode}.js,则复制到 public/default.js
|
|
153
|
-
// 注意:切换应发生在“项目目录”(process.cwd()),而不是 CLI 自身的 lib 目录(__dirname)
|
|
154
|
-
// 优先从 .ylyxrc.json 中读取 publicDir,其次使用默认 ./public
|
|
155
90
|
const configDir = path.resolve(process.cwd(), config.configDir || 'config');
|
|
156
91
|
const publicDir = path.resolve(process.cwd(), config.publicDir || 'public');
|
|
157
|
-
const defaultVariant = path.join(configDir, `default-${
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
92
|
+
const defaultVariant = path.join(configDir, `default-${env.name}.js`);
|
|
93
|
+
|
|
94
|
+
// ponytail: 信任本地 .ylyxrc.json 的路径配置,不做路径穿越校验(本地 CLI 工具,配置由用户自行维护)
|
|
95
|
+
let targetDir;
|
|
96
|
+
if (env.isLocal) {
|
|
97
|
+
targetDir = publicDir;
|
|
98
|
+
} else {
|
|
99
|
+
if (!env.buildDir) {
|
|
100
|
+
throw new Error(`环境 "${env.name}" 未配置 buildDir,无法确定切换目标目录`);
|
|
101
|
+
}
|
|
102
|
+
targetDir = path.resolve(process.cwd(), env.buildDir);
|
|
103
|
+
}
|
|
104
|
+
const defaultFile = path.join(targetDir, 'default.js');
|
|
105
|
+
|
|
106
|
+
const result = copyFile({ src: defaultVariant, dest: defaultFile });
|
|
170
107
|
|
|
171
108
|
if (result.ok) {
|
|
172
|
-
const label = result.method === 'symlink' ? 'symlink' : 'copy';
|
|
173
109
|
log.success(
|
|
174
|
-
|
|
110
|
+
`已切换 ${path.relative(process.cwd(), defaultFile)} <- ${path.relative(
|
|
175
111
|
process.cwd(),
|
|
176
112
|
defaultVariant
|
|
177
113
|
)}`
|
|
178
114
|
);
|
|
179
|
-
|
|
180
|
-
log.info('dev 模式实时同步已启用:修改 default-dev.js 会立即反映到 default.js');
|
|
181
|
-
}
|
|
182
|
-
} else if (result.method === 'missing') {
|
|
115
|
+
} else if (!result.ok && !fs.existsSync(defaultVariant)) {
|
|
183
116
|
log.warn(`未找到 ${path.relative(process.cwd(), defaultVariant)},请先运行 ylyx init config`);
|
|
184
117
|
}
|
|
185
118
|
|
|
186
|
-
config.
|
|
119
|
+
config.currentEnv = env.name;
|
|
187
120
|
fs.writeJsonSync(configPath, config, { spaces: 2 });
|
|
188
|
-
log.success(`已写入配置:
|
|
121
|
+
log.success(`已写入配置:currentEnv=${env.name}`);
|
|
189
122
|
log.info(`配置文件:${configPath}`);
|
|
190
123
|
}
|
|
191
124
|
|
|
125
|
+
/**
|
|
126
|
+
* 交互式选择环境并切换:按 isLocal 分组展示,当前环境标注"(当前)",
|
|
127
|
+
* 默认光标停在当前环境,选定后复用 setEnv 完成实际切换
|
|
128
|
+
*/
|
|
129
|
+
async function chooseEnvInteractive() {
|
|
130
|
+
const configPath = getConfigPath();
|
|
131
|
+
const configExists = fs.existsSync(configPath);
|
|
132
|
+
const config = readConfigSafe(configPath);
|
|
133
|
+
assertConfigMigrated(config, configExists);
|
|
134
|
+
|
|
135
|
+
const environments = config.environments || [];
|
|
136
|
+
if (environments.length === 0) {
|
|
137
|
+
throw new Error('环境列表为空,请先在 .ylyxrc.json 配置 environments 或运行 ylyx init config');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const currentEnv = config.currentEnv;
|
|
141
|
+
const toChoice = (e) => ({
|
|
142
|
+
name: e.name === currentEnv ? `${e.name}(当前)` : e.name,
|
|
143
|
+
value: e.name,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const localEnvs = environments.filter((e) => e.isLocal);
|
|
147
|
+
const remoteEnvs = environments.filter((e) => !e.isLocal);
|
|
148
|
+
|
|
149
|
+
const choices = [];
|
|
150
|
+
if (localEnvs.length > 0) {
|
|
151
|
+
choices.push(new inquirer.Separator('-- 本地环境 --'));
|
|
152
|
+
choices.push(...localEnvs.map(toChoice));
|
|
153
|
+
}
|
|
154
|
+
if (remoteEnvs.length > 0) {
|
|
155
|
+
choices.push(new inquirer.Separator('-- 部署环境 --'));
|
|
156
|
+
choices.push(...remoteEnvs.map(toChoice));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const ans = await inquirer.prompt([
|
|
160
|
+
{
|
|
161
|
+
type: 'list',
|
|
162
|
+
name: 'env',
|
|
163
|
+
message: '请选择要切换到的环境',
|
|
164
|
+
choices,
|
|
165
|
+
default: currentEnv,
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
setEnv(ans.env);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 迁移旧版 .ylyxrc.json(mode/deploy/顶层 buildDir/preScripts)到 environments 结构
|
|
174
|
+
*/
|
|
175
|
+
function migrateConfig() {
|
|
176
|
+
const configPath = getConfigPath();
|
|
177
|
+
const existing = readConfigSafe(configPath);
|
|
178
|
+
|
|
179
|
+
if (typeof existing.configVersion === 'number' && existing.configVersion >= CONFIG_VERSION) {
|
|
180
|
+
log.info(`当前配置已是最新版本(configVersion=${existing.configVersion}),无需迁移`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const migrated = buildMigratedConfig(existing);
|
|
185
|
+
fs.writeJsonSync(configPath, migrated, { spaces: 2 });
|
|
186
|
+
|
|
187
|
+
const names = migrated.environments.map((e) => e.name).join(', ');
|
|
188
|
+
log.success(`迁移完成,已生成环境:${names}`);
|
|
189
|
+
log.warn('请检查 test/prod 环境的 host/username/password 等敏感信息是否正确');
|
|
190
|
+
log.info('请运行 `ylyx init config --force` 以补写 package.json 的 pre/post 钩子(迁移命令不会修改 package.json)');
|
|
191
|
+
}
|
|
192
|
+
|
|
192
193
|
/**
|
|
193
194
|
* init config:初始化配置目录与示例文件
|
|
194
|
-
* - 写入/补齐 .ylyxrc.json(
|
|
195
|
-
* -
|
|
196
|
-
* - 若 public/default.js
|
|
195
|
+
* - 写入/补齐 .ylyxrc.json(configVersion/environments/currentEnv/publicDir/configDir)
|
|
196
|
+
* - 为每个环境生成 config/default-<name>.js(基于现有 public/default.js 派生)
|
|
197
|
+
* - 若 public/default.js 不存在,则按 currentEnv 生成一份
|
|
198
|
+
* - 按每个环境的 npmScript 写 package.json 的 pre/post 钩子
|
|
197
199
|
*/
|
|
198
200
|
function initConfig(options = {}) {
|
|
199
201
|
const force = !!options.force;
|
|
200
202
|
const configPath = getConfigPath();
|
|
201
203
|
const existing = readConfigSafe(configPath);
|
|
202
204
|
|
|
205
|
+
const hasEnvironments = Array.isArray(existing.environments) && existing.environments.length > 0;
|
|
206
|
+
if (Array.isArray(existing.environments) && existing.environments.length === 0) {
|
|
207
|
+
log.info('检测到 .ylyxrc.json 中 environments 为空数组,已使用默认环境(dev/prod)填充');
|
|
208
|
+
}
|
|
209
|
+
const environments = hasEnvironments
|
|
210
|
+
? existing.environments
|
|
211
|
+
: [
|
|
212
|
+
{ name: 'dev', isLocal: true, npmScript: 'dev', deployMethod: 'none' },
|
|
213
|
+
{
|
|
214
|
+
name: 'prod',
|
|
215
|
+
npmScript: 'build:prod',
|
|
216
|
+
deployMethod: 'zip',
|
|
217
|
+
buildDir: 'dist/app',
|
|
218
|
+
zipOutDir: './.ylyx-deploy',
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
let currentEnv = existing.currentEnv || 'dev';
|
|
223
|
+
if (options.env) {
|
|
224
|
+
const exists = environments.some((e) => e.name === options.env);
|
|
225
|
+
if (!exists) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`环境不存在: ${options.env}\n可用环境: ${environments.map((e) => e.name).join(', ')}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
currentEnv = options.env;
|
|
231
|
+
}
|
|
232
|
+
|
|
203
233
|
const next = {
|
|
204
234
|
...existing,
|
|
205
|
-
|
|
235
|
+
configVersion: CONFIG_VERSION,
|
|
206
236
|
publicDir: existing.publicDir || './public',
|
|
207
237
|
configDir: existing.configDir || './config',
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
preScripts: existing.preScripts || { dev: 'dev', prod: 'build:prod' },
|
|
238
|
+
currentEnv,
|
|
239
|
+
environments,
|
|
211
240
|
};
|
|
212
241
|
|
|
213
|
-
if (options.mode) {
|
|
214
|
-
const normalized = normalizeMode(options.mode);
|
|
215
|
-
if (!normalized) throw new Error(`mode 取值只能是 dev 或 prod(当前: ${options.mode})`);
|
|
216
|
-
next.mode = normalized;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
242
|
fs.writeJsonSync(configPath, next, { spaces: 2 });
|
|
220
243
|
log.success(`已初始化配置文件:${path.relative(process.cwd(), configPath)}`);
|
|
221
244
|
|
|
222
|
-
const publicDirAbs = path.resolve(process.cwd(), next.publicDir
|
|
223
|
-
const configDirAbs = path.resolve(process.cwd(), next.configDir
|
|
245
|
+
const publicDirAbs = path.resolve(process.cwd(), next.publicDir);
|
|
246
|
+
const configDirAbs = path.resolve(process.cwd(), next.configDir);
|
|
224
247
|
fs.ensureDirSync(publicDirAbs);
|
|
225
248
|
fs.ensureDirSync(configDirAbs);
|
|
226
249
|
|
|
@@ -229,23 +252,16 @@ function initConfig(options = {}) {
|
|
|
229
252
|
if (fs.existsSync(publicDefault)) {
|
|
230
253
|
baseContent = fs.readFileSync(publicDefault, 'utf-8');
|
|
231
254
|
} else {
|
|
232
|
-
baseContent =
|
|
233
|
-
"// 自动生成的默认配置(请按项目需要补全)\n" +
|
|
234
|
-
"let isDev = true;\n"
|
|
255
|
+
baseContent = '// 自动生成的默认配置(请按项目需要补全)\n';
|
|
235
256
|
}
|
|
236
257
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
writeIfAllowed(prodVariantPath, deriveDefaultVariantContent(baseContent, 'prod'), force);
|
|
258
|
+
for (const env of next.environments) {
|
|
259
|
+
const variantPath = path.join(configDirAbs, `default-${env.name}.js`);
|
|
260
|
+
writeIfAllowed(variantPath, deriveDefaultVariantContent(baseContent, env.name), force);
|
|
261
|
+
}
|
|
242
262
|
|
|
243
|
-
// 如果 public/default.js 不存在,则按当前 mode 生成一份
|
|
244
263
|
if (!fs.existsSync(publicDefault)) {
|
|
245
|
-
const src =
|
|
246
|
-
next.mode === 'prod'
|
|
247
|
-
? path.join(configDirAbs, 'default-prod.js')
|
|
248
|
-
: path.join(configDirAbs, 'default-dev.js');
|
|
264
|
+
const src = path.join(configDirAbs, `default-${next.currentEnv}.js`);
|
|
249
265
|
if (fs.existsSync(src)) {
|
|
250
266
|
fs.copyFileSync(src, publicDefault);
|
|
251
267
|
log.success(
|
|
@@ -256,10 +272,9 @@ function initConfig(options = {}) {
|
|
|
256
272
|
log.info(`已存在:${path.relative(process.cwd(), publicDefault)}(未覆盖)`);
|
|
257
273
|
}
|
|
258
274
|
|
|
259
|
-
// 初始化 package.json 的 npm 前置脚本:pre<devScript> / pre<prodScript>
|
|
260
275
|
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
261
276
|
if (!fs.existsSync(packageJsonPath)) {
|
|
262
|
-
log.warn('未找到 package.json,已跳过 npm
|
|
277
|
+
log.warn('未找到 package.json,已跳过 npm 钩子初始化');
|
|
263
278
|
return;
|
|
264
279
|
}
|
|
265
280
|
|
|
@@ -271,21 +286,30 @@ function initConfig(options = {}) {
|
|
|
271
286
|
}
|
|
272
287
|
|
|
273
288
|
pkg.scripts = pkg.scripts || {};
|
|
274
|
-
const devTarget = (next.preScripts && next.preScripts.dev) || 'dev';
|
|
275
|
-
const prodTarget = (next.preScripts && next.preScripts.prod) || 'build:prod';
|
|
276
289
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
290
|
+
for (const env of next.environments) {
|
|
291
|
+
const npmScript = env.npmScript;
|
|
292
|
+
if (!npmScript) continue;
|
|
293
|
+
|
|
294
|
+
let hookName;
|
|
295
|
+
let hookValue;
|
|
296
|
+
if (env.isLocal) {
|
|
297
|
+
hookName = `pre${npmScript}`;
|
|
298
|
+
hookValue = `ylyx config ${env.name}`;
|
|
299
|
+
} else {
|
|
300
|
+
hookName = `post${npmScript}`;
|
|
301
|
+
hookValue =
|
|
302
|
+
env.deployMethod && env.deployMethod !== 'none'
|
|
303
|
+
? `ylyx config ${env.name} && ylyx deploy --env ${env.name}`
|
|
304
|
+
: `ylyx config ${env.name}`;
|
|
305
|
+
}
|
|
281
306
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
log.warn(`package.json scripts 已存在,跳过:${item.name}`);
|
|
307
|
+
if (pkg.scripts[hookName] && !force) {
|
|
308
|
+
log.warn(`package.json scripts 已存在,跳过:${hookName}`);
|
|
285
309
|
continue;
|
|
286
310
|
}
|
|
287
|
-
pkg.scripts[
|
|
288
|
-
log.success(`已写入 package.json scripts:${
|
|
311
|
+
pkg.scripts[hookName] = hookValue;
|
|
312
|
+
log.success(`已写入 package.json scripts:${hookName}`);
|
|
289
313
|
}
|
|
290
314
|
|
|
291
315
|
fs.writeJsonSync(packageJsonPath, pkg, { spaces: 2 });
|
|
@@ -293,7 +317,9 @@ function initConfig(options = {}) {
|
|
|
293
317
|
}
|
|
294
318
|
|
|
295
319
|
module.exports = {
|
|
296
|
-
|
|
297
|
-
normalizeMode,
|
|
320
|
+
setEnv,
|
|
298
321
|
initConfig,
|
|
322
|
+
migrateConfig,
|
|
323
|
+
chooseEnvInteractive,
|
|
324
|
+
deriveDefaultVariantContent,
|
|
299
325
|
};
|