ylyx-cli 1.0.1 → 1.0.2
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 +8 -2
- package/bin/ylyx.js +4 -1
- package/lib/config.js +107 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,8 +116,14 @@ ylyx init config --force
|
|
|
116
116
|
|
|
117
117
|
`init config` 还会尝试在当前目录的 `package.json` 中写入 npm 前置脚本(可在 `.ylyxrc.json` 的 `preScripts` 配置脚本名):
|
|
118
118
|
|
|
119
|
-
- 默认会写入 `predev` 和 `
|
|
120
|
-
- 执行 `npm run dev`
|
|
119
|
+
- 默认会写入 `predev` 和 `postbuild:prod`
|
|
120
|
+
- 执行 `npm run dev` 前,会自动先执行 `ylyx config dev`(dev 使用 symlink 实时同步)
|
|
121
|
+
- 执行 `npm run build:prod` 后,会自动执行 `ylyx config prod`(将 `default-prod.js` 复制到打包输出目录)
|
|
122
|
+
|
|
123
|
+
prod 输出目录规则:
|
|
124
|
+
|
|
125
|
+
- 优先使用 `.ylyxrc.json` 的 `buildDir`
|
|
126
|
+
- 否则读取当前项目 `.env.production` 的 `VUE_APP_PUBLIC_URL`,默认写入 `<VUE_APP_PUBLIC_URL>/default.js`(会去掉首尾 `/`)
|
|
121
127
|
|
|
122
128
|
## 云端模板
|
|
123
129
|
|
package/bin/ylyx.js
CHANGED
|
@@ -20,6 +20,8 @@ program
|
|
|
20
20
|
.command('config [mode]')
|
|
21
21
|
.description('写入/更新 .ylyxrc.json 配置(目前支持:mode=dev|prod)')
|
|
22
22
|
.option('-m, --mode <mode>', '运行模式:dev | prod')
|
|
23
|
+
.option('--symlink', '切换 default.js 时强制使用 symlink(dev 默认)')
|
|
24
|
+
.option('--copy', '切换 default.js 时强制使用 copy')
|
|
23
25
|
.action((modeArg, options) => {
|
|
24
26
|
try {
|
|
25
27
|
const mode = options.mode || modeArg;
|
|
@@ -28,7 +30,8 @@ program
|
|
|
28
30
|
console.error('示例:ylyx config --mode dev 或 ylyx config dev');
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
const method = options.copy ? 'copy' : options.symlink ? 'symlink' : undefined;
|
|
34
|
+
setMode(mode, { method });
|
|
32
35
|
} catch (error) {
|
|
33
36
|
console.error('❌ 配置失败:', error.message);
|
|
34
37
|
process.exit(1);
|
package/lib/config.js
CHANGED
|
@@ -13,6 +13,57 @@ function normalizeMode(mode) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
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
|
+
|
|
16
67
|
function readConfigSafe(configPath) {
|
|
17
68
|
if (!fs.existsSync(configPath)) return {};
|
|
18
69
|
try {
|
|
@@ -56,10 +107,41 @@ function deriveDefaultVariantContent(baseContent, mode) {
|
|
|
56
107
|
return content;
|
|
57
108
|
}
|
|
58
109
|
|
|
110
|
+
function ensureSymlinkOrCopy({ src, dest, preferSymlink }) {
|
|
111
|
+
fs.ensureDirSync(path.dirname(dest));
|
|
112
|
+
if (!fs.existsSync(src)) {
|
|
113
|
+
log.warn(`未找到源文件:${path.relative(process.cwd(), src)}`);
|
|
114
|
+
return { ok: false, method: 'missing' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 如果目标已存在,先删除(不管是文件还是链接)
|
|
118
|
+
if (fs.existsSync(dest)) {
|
|
119
|
+
fs.removeSync(dest);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (preferSymlink) {
|
|
123
|
+
try {
|
|
124
|
+
// Windows/macOS/Linux 都支持;Windows 可能因为权限失败,失败后回退 copy
|
|
125
|
+
fs.ensureSymlinkSync(src, dest, 'file');
|
|
126
|
+
return { ok: true, method: 'symlink' };
|
|
127
|
+
} catch (e) {
|
|
128
|
+
log.warn(`创建 symlink 失败,将降级为 copy:${e.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
fs.copyFileSync(src, dest);
|
|
134
|
+
return { ok: true, method: 'copy' };
|
|
135
|
+
} catch (e) {
|
|
136
|
+
log.error(`写入失败:${e.message}`);
|
|
137
|
+
return { ok: false, method: 'copy_failed' };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
59
141
|
/**
|
|
60
142
|
* 写入/更新 .ylyxrc.json 的 mode 字段(dev|prod),并与已有配置合并
|
|
61
143
|
*/
|
|
62
|
-
function setMode(mode) {
|
|
144
|
+
function setMode(mode, options = {}) {
|
|
63
145
|
const normalized = normalizeMode(mode);
|
|
64
146
|
if (!normalized) {
|
|
65
147
|
throw new Error(`mode 取值只能是 dev 或 prod(当前: ${mode})`);
|
|
@@ -74,20 +156,32 @@ function setMode(mode) {
|
|
|
74
156
|
const configDir = path.resolve(process.cwd(), config.configDir || 'config');
|
|
75
157
|
const publicDir = path.resolve(process.cwd(), config.publicDir || 'public');
|
|
76
158
|
const defaultVariant = path.join(configDir, `default-${normalized}.js`);
|
|
77
|
-
const defaultFile =
|
|
159
|
+
const defaultFile =
|
|
160
|
+
normalized === 'prod'
|
|
161
|
+
? path.join(resolveProdBuildDir(config), 'default.js')
|
|
162
|
+
: path.join(publicDir, 'default.js');
|
|
78
163
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
164
|
+
// 默认策略:dev 使用 symlink(实时同步),prod 使用 copy(稳定)
|
|
165
|
+
const method = options.method || (normalized === 'dev' ? 'symlink' : 'copy');
|
|
166
|
+
const result = ensureSymlinkOrCopy({
|
|
167
|
+
src: defaultVariant,
|
|
168
|
+
dest: defaultFile,
|
|
169
|
+
preferSymlink: method === 'symlink',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (result.ok) {
|
|
173
|
+
const label = result.method === 'symlink' ? 'symlink' : 'copy';
|
|
84
174
|
log.success(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`未找到 ${path.relative(process.cwd(), defaultVariant)},仅写入 mode 到 .ylyxrc.json,请手动切换 public/default.js`
|
|
175
|
+
`已(${label})切换 ${path.relative(process.cwd(), defaultFile)} <- ${path.relative(
|
|
176
|
+
process.cwd(),
|
|
177
|
+
defaultVariant
|
|
178
|
+
)}`
|
|
90
179
|
);
|
|
180
|
+
if (normalized === 'dev' && result.method === 'symlink') {
|
|
181
|
+
log.info('dev 模式实时同步已启用:修改 default-dev.js 会立即反映到 default.js');
|
|
182
|
+
}
|
|
183
|
+
} else if (result.method === 'missing') {
|
|
184
|
+
log.warn(`未找到 ${path.relative(process.cwd(), defaultVariant)},请先运行 ylyx init config`);
|
|
91
185
|
}
|
|
92
186
|
|
|
93
187
|
config.mode = normalized;
|
|
@@ -183,7 +277,7 @@ function initConfig(options = {}) {
|
|
|
183
277
|
|
|
184
278
|
const desired = [
|
|
185
279
|
{ name: `pre${devTarget}`, value: 'ylyx config dev' },
|
|
186
|
-
{ name: `
|
|
280
|
+
{ name: `post${prodTarget}`, value: 'ylyx config prod' },
|
|
187
281
|
];
|
|
188
282
|
|
|
189
283
|
for (const item of desired) {
|