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 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` 和 `prebuild:prod`
120
- - 执行 `npm run dev` / `npm run build:prod` 前,会自动先执行 `ylyx config dev/prod`
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
- setMode(mode);
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 = path.join(publicDir, 'default.js');
159
+ const defaultFile =
160
+ normalized === 'prod'
161
+ ? path.join(resolveProdBuildDir(config), 'default.js')
162
+ : path.join(publicDir, 'default.js');
78
163
 
79
- if (fs.existsSync(defaultVariant)) {
80
- if (!fs.existsSync(publicDir)) {
81
- fs.ensureDirSync(publicDir);
82
- }
83
- fs.copyFileSync(defaultVariant, defaultFile);
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
- `已切换 ${path.relative(process.cwd(), defaultFile)} <- ${path.relative(process.cwd(), defaultVariant)}`
86
- );
87
- } else {
88
- log.warn(
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: `pre${prodTarget}`, value: 'ylyx config prod' },
280
+ { name: `post${prodTarget}`, value: 'ylyx config prod' },
187
281
  ];
188
282
 
189
283
  for (const item of desired) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ylyx-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "公司内部代码生成模板脚手架工具,支持快速生成项目初始结构和代码模板",
5
5
  "main": "lib/index.js",
6
6
  "bin": {