ygc-dsh-plugin-autoupdate 1.0.0 → 1.1.1

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/lib/restart.js CHANGED
@@ -35,6 +35,7 @@ import os from 'node:os';
35
35
  import { spawn, execFileSync } from 'node:child_process';
36
36
  import { fileURLToPath } from 'node:url';
37
37
  import { rootSameExePid } from './pidtree.js';
38
+ import { resolveNodeInterpreter } from './runtime-locator.js';
38
39
 
39
40
  const HELPER = fileURLToPath(new URL('./restart-helper.js', import.meta.url));
40
41
  const STATE_DIR = path.join(os.homedir(), '.dsh', 'storages', 'plugin-autoupdate');
@@ -76,6 +77,12 @@ export const LAUNCH_ENV = [
76
77
  'PATH',
77
78
  'DSH_HOME',
78
79
  'DSH_PROFILE',
80
+ 'DSH_PROFILE_DIR',
81
+ 'DSH_INSTALL_DIR',
82
+ // 下面两个决定新宿主去哪个目录找运行时载荷。WMI 不继承调用方环境,
83
+ // 漏传的话新宿主还得自己再解析一遍 —— 布局再变就可能找不到。
84
+ 'DSH_RUNTIME_ROOT',
85
+ 'DSH_PRIMARY_RUNTIME',
79
86
  'DSH_DESKTOP_NODE_EXECUTABLE',
80
87
  'TEMP',
81
88
  'TMP',
@@ -113,28 +120,24 @@ const isFile = (p) => {
113
120
  *
114
121
  * @returns {{exe: string, kind: string, needsRunAsNode: boolean}}
115
122
  */
116
- export function helperExecutable() {
117
- const self = process.execPath;
118
- // 跑在真 node 里(CLI、测试、开发)—— 直接就用它
119
- if (!process.versions.electron) return { exe: self, kind: 'node-self', needsRunAsNode: false };
120
- if (typeof self !== 'string' || !self) return { exe: self, kind: 'unknown', needsRunAsNode: true };
121
-
122
- const installDir = path.dirname(self);
123
- const candidates = [
124
- path.join(installDir, 'resources', 'runtime', 'primary-runtime', 'dependencies', 'node', 'bin', 'node.exe'),
125
- path.join(installDir, 'resources', 'runtime', 'primary-runtime', 'dependencies', 'node', 'node.exe'),
126
- path.join(installDir, 'runtime', 'primary-runtime', 'dependencies', 'node', 'bin', 'node.exe'),
127
- ];
128
- for (const candidate of candidates) {
129
- if (isFile(candidate)) return { exe: candidate, kind: 'bundled-node', needsRunAsNode: false };
130
- }
131
-
132
- const hinted = process.env.DSH_DESKTOP_NODE_EXECUTABLE;
133
- if (typeof hinted === 'string' && /node(\.exe)?$/i.test(hinted) && isFile(hinted)) {
134
- return { exe: hinted, kind: 'env-node', needsRunAsNode: false };
135
- }
136
-
137
- return { exe: self, kind: 'electron-as-node', needsRunAsNode: true };
123
+ export function helperExecutable(options = {}) {
124
+ // 解释器选择交给 runtime-locator:认载荷标记(runtime.json),不认目录长相。
125
+ // 1.0.0 及以前把 resources/runtime/primary-runtime 写死在这里;桌面版把载荷搬到
126
+ // $DSH_HOME/dsh-runtimes/ 后三条候选全 miss,每次都退化成 electron-as-node:
127
+ // 助手顶着宿主 exe 的名字跑,任何按映像名的清场都能顺手打死它。
128
+ const picked = resolveNodeInterpreter({
129
+ desktop: options.desktop,
130
+ env: options.env,
131
+ execPath: options.execPath,
132
+ runtime: options.runtime,
133
+ });
134
+ return {
135
+ exe: picked.exe,
136
+ kind: picked.kind,
137
+ needsRunAsNode: picked.needsRunAsNode,
138
+ runtimeRoot: picked.runtime && picked.runtime.found ? picked.runtime.root : undefined,
139
+ runtimeSource: picked.runtime ? picked.runtime.source : 'none',
140
+ };
138
141
  }
139
142
 
140
143
  export function appendRestartLog(message) {
@@ -0,0 +1,326 @@
1
+ /**
2
+ * runtime-locator.js -- 定位 DSH 的运行时载荷(node / pnpm / python)。
3
+ *
4
+ * == 为什么要有它 ==
5
+ * 桌面版升级后,运行时载荷搬了家:
6
+ *
7
+ * 旧:<install>/resources/runtime/primary-runtime/
8
+ * 新:$DSH_HOME/dsh-runtimes/dsh-primary-runtime/
9
+ *
10
+ * 关键细节:resources/runtime/ 这个目录**还在,只是空的**。按「目录存在」判断
11
+ * 会一路走到 ENOENT,而不是干净地落到下一个候选。
12
+ *
13
+ * == 判据不是路径,是载荷自己的契约 ==
14
+ * 宿主把载荷定义成「带 runtime.json 与 dependencies/ 的目录」(见 app.asar 里
15
+ * dsh-tool-workspace-dependencies 的配置说明:source/root 两个字段都以它为准)。
16
+ * 所以这里只认两件事:
17
+ *
18
+ * 1. 目录里有 runtime.json -- 强证据,宿主自己也只认这个;
19
+ * 2. 目录里有 dependencies/node/... -- 弱证据,兜底。
20
+ *
21
+ * 目录叫 primary-runtime 还是 secondary-runtime、挂在 resources 下还是
22
+ * dsh-runtimes 下,都不重要。下次再搬家,只要契约还在,这个文件不用改。
23
+ *
24
+ * == 解析顺序(先问宿主,再猜路径)==
25
+ * env:DSH_RUNTIME_ROOT > env:DSH_PRIMARY_RUNTIME
26
+ * > $DSH_HOME/dsh-runtimes/<name>(含 glob 扫描,认标记不认名字)
27
+ * > $DSH_HOME/runtime/<name>
28
+ * > <install>/resources/runtime/<name> (旧布局,保留)
29
+ * > <install>/runtime/<name>
30
+ * > 进程参数里出现的载荷路径
31
+ * 全都不中 -> 明确降级,并把 tried 列表交回去,绝不静默。
32
+ *
33
+ * 零依赖,import 无副作用,可在 Electron 主进程、Host、纯 node、测试里跑。
34
+ */
35
+
36
+ import fs from 'node:fs';
37
+ import path from 'node:path';
38
+ import os from 'node:os';
39
+
40
+ export const LOCATOR_VERSION = '1.0.0';
41
+
42
+ /** node 可执行文件在载荷内的相对位置,按平台通用性排序。 */
43
+ const NODE_TAILS = [
44
+ ['dependencies', 'node', 'bin', 'node.exe'],
45
+ ['dependencies', 'node', 'bin', 'node'],
46
+ ['dependencies', 'node', 'node.exe'],
47
+ ['dependencies', 'node', 'node'],
48
+ ];
49
+
50
+ /** pnpm 入口在载荷内的相对位置。 */
51
+ const PNPM_TAILS = [
52
+ ['dependencies', 'pnpm', 'bin', 'pnpm.mjs'],
53
+ ['dependencies', 'pnpm', 'bin', 'pnpm.cjs'],
54
+ ['dependencies', 'pnpm', 'dist', 'pnpm.mjs'],
55
+ ];
56
+
57
+ /** python 可执行文件在载荷内的相对位置。 */
58
+ const PYTHON_TAILS = [
59
+ ['dependencies', 'python', 'python.exe'],
60
+ ['dependencies', 'python', 'bin', 'python3'],
61
+ ['dependencies', 'python', 'bin', 'python'],
62
+ ];
63
+
64
+ function statOf(target) {
65
+ try {
66
+ return fs.statSync(target);
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ }
71
+
72
+ function isFile(target) {
73
+ const info = statOf(target);
74
+ return Boolean(info && info.isFile() && info.size > 0);
75
+ }
76
+
77
+ function isDir(target) {
78
+ const info = statOf(target);
79
+ return Boolean(info && info.isDirectory());
80
+ }
81
+
82
+ function readJson(file) {
83
+ try {
84
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
85
+ } catch {
86
+ return undefined;
87
+ }
88
+ }
89
+
90
+ function listDirs(dir) {
91
+ try {
92
+ return fs
93
+ .readdirSync(dir, { withFileTypes: true })
94
+ .filter((entry) => entry.isDirectory())
95
+ .map((entry) => path.join(dir, entry.name))
96
+ .sort();
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+
102
+ function firstExisting(root, tails) {
103
+ for (const tail of tails) {
104
+ const candidate = path.join(root, ...tail);
105
+ if (isFile(candidate)) return candidate;
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ /** DSH 主目录:优先 DSH_HOME,其次 ~/.dsh。 */
111
+ export function dshHome(env = process.env) {
112
+ const fromEnv = typeof env.DSH_HOME === 'string' ? env.DSH_HOME.trim() : '';
113
+ return fromEnv ? path.resolve(fromEnv) : path.join(os.homedir(), '.dsh');
114
+ }
115
+
116
+ /**
117
+ * 从若干种子路径反推安装根。
118
+ *
119
+ * 判据:某一层祖先里存在 resources/app.asar(文件)、resources/app(目录)
120
+ * 或 resources/runtime(目录,旧布局就算空了也算)。
121
+ */
122
+ export function installRootsFrom(seeds) {
123
+ const roots = [];
124
+ for (const seed of seeds) {
125
+ if (typeof seed !== 'string' || !seed) continue;
126
+ let current = path.resolve(seed);
127
+ if (!isDir(current)) current = path.dirname(current);
128
+ for (let depth = 0; depth < 8; depth += 1) {
129
+ const hit =
130
+ isFile(path.join(current, 'resources', 'app.asar')) ||
131
+ isDir(path.join(current, 'resources', 'app')) ||
132
+ isDir(path.join(current, 'resources', 'runtime'));
133
+ if (hit) {
134
+ if (!roots.includes(current)) roots.push(current);
135
+ break;
136
+ }
137
+ const parent = path.dirname(current);
138
+ if (parent === current) break;
139
+ current = parent;
140
+ }
141
+ }
142
+ return roots;
143
+ }
144
+
145
+ /** 从一条进程参数里抽出可能的载荷根(.../dsh-runtimes/<name> 或 .../runtime/<name>)。 */
146
+ function rootFromArg(arg) {
147
+ if (typeof arg !== 'string') return undefined;
148
+ const flat = arg.replace(/\\/g, '/');
149
+ const match = /^(.*\/(?:dsh-runtimes|runtime)\/[^/]+)(\/|$)/.exec(flat);
150
+ return match ? match[1].replace(/\//g, path.sep) : undefined;
151
+ }
152
+
153
+ /**
154
+ * 候选载荷根,按可信度排序,附带来源标签。
155
+ *
156
+ * @param {{env?: object, home?: string, argv?: string[], execPath?: string}} options
157
+ * @returns {{root: string, source: string}[]}
158
+ */
159
+ export function candidateRoots(options = {}) {
160
+ const env = options.env ?? process.env;
161
+ const home = options.home ?? dshHome(env);
162
+ const argv = options.argv ?? process.argv;
163
+ const execPath = options.execPath ?? process.execPath;
164
+
165
+ const out = [];
166
+ const push = (root, source) => {
167
+ if (typeof root !== 'string' || !root) return;
168
+ const resolved = path.resolve(root);
169
+ if (out.some((item) => item.root === resolved)) return;
170
+ out.push({ root: resolved, source });
171
+ };
172
+
173
+ // 1) 显式覆盖:宿主自己就是这么告诉插件的
174
+ push(env.DSH_RUNTIME_ROOT, 'env:DSH_RUNTIME_ROOT');
175
+ push(env.DSH_PRIMARY_RUNTIME, 'env:DSH_PRIMARY_RUNTIME');
176
+
177
+ // 2) 当前官方布局:$DSH_HOME/dsh-runtimes/<name>
178
+ push(path.join(home, 'dsh-runtimes', 'dsh-primary-runtime'), 'home:dsh-runtimes/dsh-primary-runtime');
179
+ for (const dir of listDirs(path.join(home, 'dsh-runtimes'))) push(dir, 'home:dsh-runtimes/*');
180
+
181
+ // 3) 更早的家目录布局(保险)
182
+ push(path.join(home, 'runtime', 'primary-runtime'), 'home:runtime/primary-runtime');
183
+ for (const dir of listDirs(path.join(home, 'runtime'))) push(dir, 'home:runtime/*');
184
+
185
+ // 4) 安装目录内:resources/runtime(旧)与 runtime(更旧)
186
+ //
187
+ // 容器目录本身也要进候选。新版桌面把 resources/runtime 留成一个**空目录**,
188
+ // 只列子目录的话它根本不会出现在 tried 里,排障时看不到「这里是空的」,
189
+ // 只剩下一个找不到的结论。
190
+ const installSeeds = [execPath, env.DSH_INSTALL_DIR, env.DSH_APP_ROOT, env.DSH_INSTALL_ROOT].concat(argv);
191
+ for (const install of installRootsFrom(installSeeds)) {
192
+ for (const container of [
193
+ path.join(install, 'resources', 'runtime'),
194
+ path.join(install, 'runtime'),
195
+ ]) {
196
+ push(path.join(container, 'primary-runtime'), 'install:' + path.basename(path.dirname(container)) + '/' + path.basename(container) + '/primary-runtime');
197
+ push(container, 'install:' + path.basename(path.dirname(container)) + '/' + path.basename(container));
198
+ for (const dir of listDirs(container)) push(dir, 'install:' + path.basename(path.dirname(container)) + '/' + path.basename(container) + '/*');
199
+ }
200
+ }
201
+
202
+ // 5) 进程参数里现成的载荷路径(宿主有时直接把 root 传进来)
203
+ for (const arg of argv) push(rootFromArg(arg), 'argv');
204
+
205
+ return out;
206
+ }
207
+
208
+ /** 这个目录是不是一个运行时载荷。 */
209
+ export function looksLikeRuntimeRoot(root) {
210
+ if (!root || !isDir(root)) return false;
211
+ if (isFile(path.join(root, 'runtime.json'))) return true;
212
+ return Boolean(firstExisting(root, NODE_TAILS));
213
+ }
214
+
215
+ function inspectRuntimeRoot(root) {
216
+ if (!looksLikeRuntimeRoot(root)) return undefined;
217
+ const runtimeJsonPath = path.join(root, 'runtime.json');
218
+ return {
219
+ root,
220
+ runtimeJsonPath: isFile(runtimeJsonPath) ? runtimeJsonPath : undefined,
221
+ runtimeJson: isFile(runtimeJsonPath) ? readJson(runtimeJsonPath) : undefined,
222
+ node: firstExisting(root, NODE_TAILS),
223
+ pnpm: firstExisting(root, PNPM_TAILS),
224
+ python: firstExisting(root, PYTHON_TAILS),
225
+ };
226
+ }
227
+
228
+ function missReason(root) {
229
+ if (!isDir(root)) return 'missing';
230
+ if (isDir(root) && listDirs(root).length === 0 && !isFile(path.join(root, 'runtime.json'))) return 'empty-dir';
231
+ return 'no-runtime.json / no node';
232
+ }
233
+
234
+ /**
235
+ * 按顺序试候选,返回第一个像样的载荷。
236
+ *
237
+ * @returns {{found: boolean, root?: string, source: string, node?: string,
238
+ * pnpm?: string, python?: string, runtimeJson?: object,
239
+ * runtimeJsonPath?: string, tried: object[]}}
240
+ */
241
+ export function resolveRuntime(options = {}) {
242
+ const tried = [];
243
+ for (const item of candidateRoots(options)) {
244
+ const hit = inspectRuntimeRoot(item.root);
245
+ if (hit) {
246
+ tried.push({ root: item.root, source: item.source, ok: true, reason: 'runtime.json' });
247
+ return Object.assign({ found: true, source: item.source }, hit, { tried });
248
+ }
249
+ tried.push({ root: item.root, source: item.source, ok: false, reason: missReason(item.root) });
250
+ }
251
+ return { found: false, source: 'none', tried };
252
+ }
253
+
254
+ /**
255
+ * 挑跑子进程用的 node 解释器。
256
+ *
257
+ * 桌面版会注入 DSH_NODE_EXECUTABLE / DSH_DESKTOP_NODE_EXECUTABLE,本机实测
258
+ * 它们指向 **应用 exe**(DeepSeek Harness.exe),不是 node.exe。直接拿来当
259
+ * node 跑会启动第二个宿主。所以这里必须验明正身:basename 必须是 node(.exe)。
260
+ *
261
+ * @param {{desktop?: boolean, runtime?: object}} options
262
+ * desktop -- 是否按桌面宿主处理;默认看 process.versions.electron。
263
+ * 测试里显式传 true 才能在纯 node 下走真实分支。
264
+ */
265
+ export function resolveNodeInterpreter(options = {}) {
266
+ const env = options.env ?? process.env;
267
+ const self = options.execPath ?? process.execPath;
268
+ const desktop = options.desktop !== undefined ? Boolean(options.desktop) : Boolean(process.versions && process.versions.electron);
269
+ const runtime = options.runtime ?? resolveRuntime(options);
270
+
271
+ if (!desktop) return { exe: self, kind: 'node-self', needsRunAsNode: false, runtime };
272
+ if (runtime.node) return { exe: runtime.node, kind: 'runtime-node', needsRunAsNode: false, runtime };
273
+
274
+ for (const key of ['DSH_DESKTOP_NODE_EXECUTABLE', 'DSH_NODE_EXECUTABLE']) {
275
+ const value = env[key];
276
+ if (typeof value === 'string' && /^node(\.exe)?$/i.test(path.basename(value)) && isFile(value)) {
277
+ return { exe: value, kind: 'env:' + key, needsRunAsNode: false, runtime };
278
+ }
279
+ }
280
+
281
+ // 退路:用宿主 exe 顶着 ELECTRON_RUN_AS_NODE 跑。
282
+ // 能跑,但助手的映像名和宿主一样,任何按映像名的清场都会顺手打死它。
283
+ return { exe: self, kind: 'electron-as-node', needsRunAsNode: true, runtime };
284
+ }
285
+
286
+ /** 找桌面宿主自带的 pnpm 入口。先看进程参数(沿用旧逻辑),再看载荷。 */
287
+ export function resolvePnpm(options = {}) {
288
+ const argv = options.argv ?? process.argv;
289
+ for (const value of argv) {
290
+ if (typeof value !== 'string') continue;
291
+ if (!/pnpm[\\/](bin[\\/])?pnpm\.mjs$/i.test(value.replace(/\\/g, '/'))) continue;
292
+ if (!path.isAbsolute(value) || !isFile(value)) continue;
293
+ const binIndex = argv.findIndex((item) => typeof item === 'string' && /[\\/]runtime[\\/]bin$/i.test(item.replace(/\\/g, '/')));
294
+ return { pnpm: value, bin: binIndex >= 0 ? argv[binIndex] : undefined, source: 'argv' };
295
+ }
296
+
297
+ const runtime = options.runtime ?? resolveRuntime(options);
298
+ if (runtime.pnpm) {
299
+ return {
300
+ pnpm: runtime.pnpm,
301
+ bin: runtime.node ? path.dirname(runtime.node) : undefined,
302
+ source: 'runtime:' + runtime.source,
303
+ };
304
+ }
305
+ return undefined;
306
+ }
307
+
308
+ /** 一行摘要,写日志/状态工具用。 */
309
+ export function describeRuntime(runtime) {
310
+ if (!runtime || !runtime.found) {
311
+ const lines = ['runtime: NOT FOUND'];
312
+ for (const item of (runtime && runtime.tried) || []) {
313
+ lines.push(' - ' + item.source + ' -> ' + item.root + ' (' + item.reason + ')');
314
+ }
315
+ return lines.join('\n');
316
+ }
317
+ const json = runtime.runtimeJson || {};
318
+ return [
319
+ 'runtime: ' + runtime.root,
320
+ ' source=' + runtime.source + ' locator=' + LOCATOR_VERSION,
321
+ ' desktopVersion=' + (json.desktopVersion || '?') + ' node=' + (json.node || '?') + ' pnpm=' + (json.pnpm || '?'),
322
+ ' node=' + (runtime.node || '-'),
323
+ ' pnpm=' + (runtime.pnpm || '-'),
324
+ ' python=' + (runtime.python || '-'),
325
+ ].join('\n');
326
+ }
package/lib/updater.js CHANGED
@@ -13,6 +13,7 @@ import path from 'node:path';
13
13
  import os from 'node:os';
14
14
  import { spawn } from 'node:child_process';
15
15
  import { profileDir } from './core.js';
16
+ import { resolveNodeInterpreter, resolvePnpm } from './runtime-locator.js';
16
17
 
17
18
  const BACKUP_ROOT = path.join(os.homedir(), '.dsh', 'storages', 'plugin-autoupdate', 'backups');
18
19
  const BACKUP_KEEP = 20;
@@ -56,6 +57,7 @@ function pruneBackups() {
56
57
 
57
58
  /** 桌面宿主自带 pnpm 的定位:从 process.argv 里找 `.../pnpm/bin/pnpm.mjs`。 */
58
59
  export function locateDesktopPnpm(argv = process.argv) {
60
+ // 旧路优先:宿主把 pnpm 绝对路径塞进 argv 时最准(连 PATH 该加什么都带上了)。
59
61
  for (let i = 0; i < argv.length; i += 1) {
60
62
  const value = argv[i];
61
63
  if (typeof value !== 'string') continue;
@@ -66,7 +68,10 @@ export function locateDesktopPnpm(argv = process.argv) {
66
68
  );
67
69
  return { pnpm: value, bin: binIndex >= 0 ? argv[binIndex] : undefined };
68
70
  }
69
- return undefined;
71
+
72
+ // argv 里没有就按载荷找。新布局把 pnpm 放在 $DSH_HOME/dsh-runtimes 下,
73
+ // 既不进 PATH 也不一定出现在 argv 里;旧实现在这里直接返回 undefined。
74
+ return resolvePnpm({ argv });
70
75
  }
71
76
 
72
77
  /**
@@ -182,7 +187,12 @@ async function pnpmInstall(spec, log, registry) {
182
187
  if (!found) {
183
188
  return { ok: false, via: 'none', message: '当前宿主既没有 pluginManager 也定位不到 pnpm,无法自动更新。' };
184
189
  }
185
- const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1', NO_COLOR: '1' };
190
+ // 解释器同样换成载荷里的真 node.exe:用 process.execPath(= 应用 exe)顶着
191
+ // ELECTRON_RUN_AS_NODE 也能跑,但那等于再起一个宿主进程去干 pnpm 的活。
192
+ const runner = resolveNodeInterpreter({ desktop: true });
193
+ const env = { ...process.env, NO_COLOR: '1' };
194
+ if (runner.needsRunAsNode) env.ELECTRON_RUN_AS_NODE = '1';
195
+ else delete env.ELECTRON_RUN_AS_NODE;
186
196
  if (found.bin) {
187
197
  const key = Object.keys(env).find((k) => k.toUpperCase() === 'PATH');
188
198
  env[key || 'PATH'] = `${found.bin}${path.delimiter}${env[key || 'PATH'] || ''}`;
@@ -190,7 +200,7 @@ async function pnpmInstall(spec, log, registry) {
190
200
  const args = ['--expose-internals', found.pnpm, 'add', '--config.minimumReleaseAge=0', spec];
191
201
  // 装到哪个源就跟查询用哪个源:查 npmmirror、装 npmjs 会装不到刚发布的版本
192
202
  if (registry) args.push(`--registry=${registry}`);
193
- const result = await run(process.execPath, args, { cwd: profileDir(), env });
203
+ const result = await run(runner.exe, args, { cwd: profileDir(), env });
194
204
  if (result.code === 0) {
195
205
  log?.(`pnpm 安装完成:${spec}`);
196
206
  return { ok: true, via: 'pnpm', message: result.output };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ygc-dsh-plugin-autoupdate",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "DSH 插件自动更新器:定时检查已装插件的新版本并按策略升级、装完按需重启、可回滚。只管「已装插件的新版本」,插件清单共享已移交 ygc-dsh-sync。",
6
6
  "icon": "icon.svg",
@@ -34,7 +34,8 @@
34
34
  "lib/uninstall.js",
35
35
  "lib/updater.js",
36
36
  "locale/*.json",
37
- "lib/pidtree.js"
37
+ "lib/pidtree.js",
38
+ "lib/runtime-locator.js"
38
39
  ],
39
40
  "publishConfig": {
40
41
  "registry": "https://registry.npmjs.org/",
@@ -52,4 +53,4 @@
52
53
  ]
53
54
  }
54
55
  }
55
- }
56
+ }