@wwkit/shared 1.0.12 → 1.0.14

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/shell.js +115 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwkit/shared",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "author": "bluesliu <langcai163@163.com>",
5
5
  "description": "Shared utilities for @wwkit packages — config loader, platform detection",
6
6
  "type": "module",
package/src/shell.js CHANGED
@@ -110,6 +110,45 @@ export class Shell {
110
110
  return path.join(os.homedir(), '.profile')
111
111
  }
112
112
 
113
+ /**
114
+ * 检测当前用户所有需要写入的 shell profile 文件
115
+ *
116
+ * 返回数组以覆盖多种场景(login / non-login / 不同 shell):
117
+ * - zsh: ~/.zshrc(交互式 zsh 总是读取)
118
+ * - bash: ~/.bashrc(非登录交互式)+ ~/.bash_profile(登录式,仅当已存在)
119
+ * - fish: ~/.config/fish/config.fish(fish 唯一配置文件)
120
+ * - 未知: ~/.profile(POSIX 通用回退)
121
+ *
122
+ * 每项含 `path`(绝对路径)和 `syntax`('export' | 'fish'),
123
+ * 调用方据此生成正确语法的变量设置行。
124
+ *
125
+ * @returns {{ path: string, syntax: 'export' | 'fish' }[]}
126
+ */
127
+ getProfiles() {
128
+ const home = os.homedir()
129
+ const shell = this.env.SHELL || ''
130
+ const profiles = []
131
+
132
+ if (shell.includes('fish')) {
133
+ profiles.push({
134
+ path: path.join(home, '.config', 'fish', 'config.fish'),
135
+ syntax: 'fish',
136
+ })
137
+ } else if (shell.includes('zsh')) {
138
+ profiles.push({ path: path.join(home, '.zshrc'), syntax: 'export' })
139
+ } else if (shell.includes('bash')) {
140
+ profiles.push({ path: path.join(home, '.bashrc'), syntax: 'export' })
141
+ const bp = path.join(home, '.bash_profile')
142
+ if (fs.existsSync(bp)) {
143
+ profiles.push({ path: bp, syntax: 'export' })
144
+ }
145
+ } else {
146
+ profiles.push({ path: path.join(home, '.profile'), syntax: 'export' })
147
+ }
148
+
149
+ return profiles
150
+ }
151
+
113
152
  /**
114
153
  * 将 shell 初始化行写入 profile(按 marker 去重)
115
154
  *
@@ -132,6 +171,82 @@ export class Shell {
132
171
  return true
133
172
  }
134
173
 
174
+ /**
175
+ * 持久化设置系统环境变量(跨平台)
176
+ *
177
+ * Unix: 将变量设置行写入所有相关 shell profile(getProfiles()),使用
178
+ * begin/end 标记块管理区块,支持幂等(值相同则跳过)和值更新
179
+ * (路径变化时自动替换)。覆盖 bash/zsh/fish 及 login/non-login 场景。
180
+ * Windows: 使用 `setx` 设置用户级环境变量(写入注册表,始终覆盖)。
181
+ *
182
+ * @param {string} name - 环境变量名
183
+ * @param {string} value - 环境变量值
184
+ * @returns {boolean} 是否写入/更新了至少一个目标(全部已存在且值相同则 false)
185
+ */
186
+ setEnvVar(name, value) {
187
+ if (this.isWindows) {
188
+ try {
189
+ const result = spawnSync('setx', [name, String(value)], {
190
+ encoding: 'utf8',
191
+ stdio: ['pipe', 'pipe', 'pipe'],
192
+ })
193
+ return result.status === 0
194
+ } catch {
195
+ return false
196
+ }
197
+ }
198
+
199
+ const escaped = String(value).replace(/[$`\\"]/g, '\\$&')
200
+ let wrote = false
201
+
202
+ for (const { path: profilePath, syntax } of this.getProfiles()) {
203
+ const line = syntax === 'fish'
204
+ ? `set -gx ${name} "${escaped}"`
205
+ : `export ${name}="${escaped}"`
206
+ if (this._writeEnvBlock(profilePath, name, line)) {
207
+ wrote = true
208
+ }
209
+ }
210
+
211
+ return wrote
212
+ }
213
+
214
+ /**
215
+ * 向指定 profile 文件写入/更新一个环境变量标记块
216
+ *
217
+ * @param {string} profilePath - profile 文件绝对路径
218
+ * @param {string} name - 环境变量名(用于生成标记)
219
+ * @param {string} line - 变量设置行(含正确语法)
220
+ * @returns {boolean} 是否写入/更新了该文件
221
+ */
222
+ _writeEnvBlock(profilePath, name, line) {
223
+ let existing = ''
224
+ try {
225
+ existing = fs.readFileSync(profilePath, 'utf8')
226
+ } catch {}
227
+
228
+ const beginMarker = `# >>> wwkit ${name} >>>`
229
+ const endMarker = `# <<< wwkit ${name} <<<`
230
+ const block = `${beginMarker}\n${line}\n${endMarker}`
231
+
232
+ const beginIdx = existing.indexOf(beginMarker)
233
+ if (beginIdx !== -1) {
234
+ const endIdx = existing.indexOf(endMarker)
235
+ if (endIdx !== -1 && existing.includes(line)) {
236
+ return false
237
+ }
238
+ const afterEnd = endIdx !== -1 ? endIdx + endMarker.length : beginIdx + beginMarker.length
239
+ existing = existing.slice(0, beginIdx) + block + existing.slice(afterEnd)
240
+ fs.writeFileSync(profilePath, existing, 'utf8')
241
+ return true
242
+ }
243
+
244
+ const dir = path.dirname(profilePath)
245
+ fs.mkdirSync(dir, { recursive: true })
246
+ fs.appendFileSync(profilePath, `\n${block}\n`, 'utf8')
247
+ return true
248
+ }
249
+
135
250
  /**
136
251
  * 检测系统命令是否可用
137
252
  * Windows: where <cmd>;Unix: which <cmd>