@indigoai-us/hq-cli 5.345.147 → 5.345.148
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/CHANGELOG.md +30 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +297 -3
- package/dist/command-catalog.generated.js +368 -3
- package/dist/commands/agent-inbox.js +2 -1
- package/dist/commands/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +17 -0
- package/dist/commands/core-refresh-vault-access-native.js +214 -19
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +6 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +9 -2
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/install-global.d.ts +7 -0
- package/dist/commands/install-global.js +129 -35
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- package/dist/commands/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +36 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +6 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +3 -0
- package/dist/lib/doctor/checks/anywhere.js +23 -8
- package/dist/lib/doctor/checks/cli-path.d.ts +11 -0
- package/dist/lib/doctor/checks/cli-path.js +45 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +16 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +10 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- package/dist/lib/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -0
- package/dist/lib/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- package/dist/lib/windows/user-path.d.ts +48 -0
- package/dist/lib/windows/user-path.js +114 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- package/dist/utils/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- package/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* copy-tree: portable replacement for the `rsync -a` copies on the pack
|
|
3
|
+
* install path.
|
|
4
|
+
*
|
|
5
|
+
* Pack installs used to shell out to `rsync`, which Windows does not ship, so
|
|
6
|
+
* every git-subpath or local-directory install failed there with
|
|
7
|
+
* `spawnSync rsync ENOENT`. This module does the same copy in Node on every
|
|
8
|
+
* platform:
|
|
9
|
+
*
|
|
10
|
+
* - `copyTree(src, dest)` copies the CONTENTS of `src` into `dest` (the
|
|
11
|
+
* `rsync -a src/ dest/` trailing-slash form). Directories recurse, regular
|
|
12
|
+
* files keep their permission bits and mtimes, and symlinks are recreated
|
|
13
|
+
* as symlinks with the identical target string. Links are never followed.
|
|
14
|
+
* Optional `exclude` names match an entry's basename at any depth, like
|
|
15
|
+
* `rsync --exclude=<name>`. Sockets, FIFOs and devices are skipped with a
|
|
16
|
+
* warning, as a non-root `rsync -a` does.
|
|
17
|
+
* - `removeTreeNoFollow(target)` deletes a tree without ever following a
|
|
18
|
+
* symlink or junction inside it: each entry is lstat'd, links are removed
|
|
19
|
+
* as links (unlink, or a non-recursive rmdir for a Windows junction), and
|
|
20
|
+
* directories are emptied then removed with a non-recursive rmdir. A
|
|
21
|
+
* Windows directory that is a reparse point but not a link (a volume mount
|
|
22
|
+
* point, a cloud-synced placeholder folder) stops the removal with
|
|
23
|
+
* `ReparsePointError`; its contents are never deleted.
|
|
24
|
+
*
|
|
25
|
+
* On Windows, creating a symlink needs Developer Mode or an elevated shell.
|
|
26
|
+
* When that fails with EPERM/EACCES and the link points at a directory inside
|
|
27
|
+
* the tree being copied, the copy falls back to a junction (no privilege
|
|
28
|
+
* needed). A junction needs an absolute target, so it points at the matching
|
|
29
|
+
* entry under `finalRoot`: the path the copy will live at once the caller has
|
|
30
|
+
* renamed it into place. Any other link target throws `CopyTreeLinkError` with
|
|
31
|
+
* a Developer Mode hint instead of silently dropping the link.
|
|
32
|
+
*
|
|
33
|
+
* TODO(windows-links): src/utils/safe-link.ts (PR 1967, branch
|
|
34
|
+
* fix/windows-install-path-daemon) adds its own junction fallback and
|
|
35
|
+
* link-only removal. Whichever of the two PRs merges second should reconcile
|
|
36
|
+
* these helpers into one module.
|
|
37
|
+
*/
|
|
38
|
+
import * as fs from 'fs';
|
|
39
|
+
import * as path from 'path';
|
|
40
|
+
export class CopyTreeLinkError extends Error {
|
|
41
|
+
constructor(linkPath, target, cause) {
|
|
42
|
+
super(`Could not create the symlink ${linkPath} -> ${target} (${cause.code ?? cause.message}). ` +
|
|
43
|
+
'Windows only lets elevated shells create symlinks unless Developer Mode is on. ' +
|
|
44
|
+
'Turn on Developer Mode (Settings > System > For developers) or run the install from an elevated terminal, then retry.');
|
|
45
|
+
this.name = 'CopyTreeLinkError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class ReparsePointError extends Error {
|
|
49
|
+
constructor(dir) {
|
|
50
|
+
super(`Refusing to delete ${dir}: it is a Windows reparse point (such as a volume mount point or a cloud-synced ` +
|
|
51
|
+
'placeholder folder), not a plain folder or link. Deleting through it could remove files stored elsewhere. ' +
|
|
52
|
+
'Unmount or remove it by hand, then retry.');
|
|
53
|
+
this.name = 'ReparsePointError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function copyTree(src, dest, opts = {}) {
|
|
57
|
+
const exclude = new Set(opts.exclude ?? []);
|
|
58
|
+
const srcRoot = path.resolve(src);
|
|
59
|
+
const destRoot = path.resolve(dest);
|
|
60
|
+
const finalRoot = path.resolve(opts.finalRoot ?? dest);
|
|
61
|
+
const rootStat = fs.statSync(srcRoot);
|
|
62
|
+
if (!rootStat.isDirectory()) {
|
|
63
|
+
throw new Error(`copyTree source is not a directory: ${srcRoot}`);
|
|
64
|
+
}
|
|
65
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
66
|
+
copyDirContents(srcRoot, destRoot, { srcRoot, finalRoot, exclude });
|
|
67
|
+
applyMetadata(destRoot, rootStat);
|
|
68
|
+
}
|
|
69
|
+
function copyDirContents(srcDir, destDir, ctx) {
|
|
70
|
+
for (const name of fs.readdirSync(srcDir)) {
|
|
71
|
+
if (ctx.exclude.has(name))
|
|
72
|
+
continue;
|
|
73
|
+
const srcPath = path.join(srcDir, name);
|
|
74
|
+
const destPath = path.join(destDir, name);
|
|
75
|
+
const st = fs.lstatSync(srcPath);
|
|
76
|
+
if (st.isSymbolicLink()) {
|
|
77
|
+
copyLink(srcPath, destPath, ctx);
|
|
78
|
+
}
|
|
79
|
+
else if (st.isDirectory()) {
|
|
80
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
81
|
+
copyDirContents(srcPath, destPath, ctx);
|
|
82
|
+
// Children are written first, so the directory's own mtime and mode are
|
|
83
|
+
// set last and stay equal to the source.
|
|
84
|
+
applyMetadata(destPath, st);
|
|
85
|
+
}
|
|
86
|
+
else if (st.isFile()) {
|
|
87
|
+
fs.copyFileSync(srcPath, destPath);
|
|
88
|
+
applyMetadata(destPath, st);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
process.stderr.write(`hq: skipping special file ${srcPath} (not a file, directory or symlink)\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function applyMetadata(target, st) {
|
|
96
|
+
fs.chmodSync(target, st.mode & 0o7777);
|
|
97
|
+
fs.utimesSync(target, st.atime, st.mtime);
|
|
98
|
+
}
|
|
99
|
+
function copyLink(srcPath, destPath, ctx) {
|
|
100
|
+
const target = fs.readlinkSync(srcPath);
|
|
101
|
+
const resolved = path.resolve(path.dirname(srcPath), target);
|
|
102
|
+
const targetIsDir = isDirectory(resolved);
|
|
103
|
+
try {
|
|
104
|
+
fs.symlinkSync(target, destPath, targetIsDir ? 'dir' : 'file');
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const e = err;
|
|
108
|
+
if (process.platform !== 'win32' || (e.code !== 'EPERM' && e.code !== 'EACCES'))
|
|
109
|
+
throw e;
|
|
110
|
+
if (!targetIsDir || !isInside(ctx.srcRoot, resolved)) {
|
|
111
|
+
throw new CopyTreeLinkError(destPath, target, e);
|
|
112
|
+
}
|
|
113
|
+
// A junction needs an absolute target. Point it at the same directory in
|
|
114
|
+
// the copy as it will be after the caller's final rename, so the junction
|
|
115
|
+
// stays inside the installed tree and does not dangle once a staging dir
|
|
116
|
+
// is renamed. Windows creates a junction to a path that does not exist
|
|
117
|
+
// yet, so nothing is created here: the target appears when the copy
|
|
118
|
+
// reaches it, and a target under an excluded name stays absent, as the
|
|
119
|
+
// link would dangle after `rsync --exclude`.
|
|
120
|
+
const junctionTarget = path.join(ctx.finalRoot, path.relative(ctx.srcRoot, resolved));
|
|
121
|
+
fs.symlinkSync(junctionTarget, destPath, 'junction');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function isDirectory(p) {
|
|
125
|
+
try {
|
|
126
|
+
return fs.statSync(p).isDirectory();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function isInside(root, candidate) {
|
|
133
|
+
const rel = path.relative(root, candidate);
|
|
134
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Remove `target` and everything under it without following links. A missing
|
|
138
|
+
* target is not an error. If `target` itself is a link, only the link goes.
|
|
139
|
+
*/
|
|
140
|
+
export function removeTreeNoFollow(target) {
|
|
141
|
+
removeEntry(target, () => listedAsLink(target));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `listedAsLink` reports whether the directory listing marks the entry as a
|
|
145
|
+
* link. On Windows Node marks every entry with FILE_ATTRIBUTE_REPARSE_POINT
|
|
146
|
+
* that way, while lstat reports only symlinks and junctions as links. A
|
|
147
|
+
* directory that lstat calls a directory but the listing calls a link is a
|
|
148
|
+
* reparse point of another kind, such as a volume mount point or a cloud
|
|
149
|
+
* placeholder. Elsewhere the two always agree.
|
|
150
|
+
*/
|
|
151
|
+
function removeEntry(target, listedAsLink) {
|
|
152
|
+
let st;
|
|
153
|
+
try {
|
|
154
|
+
st = fs.lstatSync(target);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (err.code === 'ENOENT')
|
|
158
|
+
return;
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
if (st.isSymbolicLink()) {
|
|
162
|
+
removeLinkOnly(target);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (st.isDirectory()) {
|
|
166
|
+
if (listedAsLink())
|
|
167
|
+
throw new ReparsePointError(target);
|
|
168
|
+
for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
|
|
169
|
+
removeEntry(path.join(target, entry.name), () => entry.isSymbolicLink());
|
|
170
|
+
}
|
|
171
|
+
fs.rmdirSync(target);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
fs.unlinkSync(target);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const code = err.code;
|
|
179
|
+
if (code !== 'EPERM' && code !== 'EACCES')
|
|
180
|
+
throw err;
|
|
181
|
+
if (process.platform === 'win32') {
|
|
182
|
+
// Windows refuses to delete a file with the read-only attribute, which a
|
|
183
|
+
// copied 0o444 file carries. Clear it on the file itself (not a link,
|
|
184
|
+
// checked above) and retry once.
|
|
185
|
+
retryWithMode(target, 0o666, () => fs.unlinkSync(target));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// On POSIX the right to unlink comes from the parent folder, which a
|
|
189
|
+
// copied 0o555 folder lacks. Give its owner write access and retry once.
|
|
190
|
+
const parent = path.dirname(target);
|
|
191
|
+
retryWithMode(parent, (fs.statSync(parent).mode & 0o7777) | 0o700, () => fs.unlinkSync(target), true);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Set `p` to `mode`, run `op` once, and put the original mode back if `op`
|
|
197
|
+
* throws, or always when `alwaysRestore` is set.
|
|
198
|
+
*/
|
|
199
|
+
function retryWithMode(p, mode, op, alwaysRestore = false) {
|
|
200
|
+
const original = fs.statSync(p).mode & 0o7777;
|
|
201
|
+
fs.chmodSync(p, mode);
|
|
202
|
+
let ok = false;
|
|
203
|
+
try {
|
|
204
|
+
op();
|
|
205
|
+
ok = true;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
if (!ok || alwaysRestore) {
|
|
209
|
+
try {
|
|
210
|
+
fs.chmodSync(p, original);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// The retry's own error, or its success, is what the caller needs.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function listedAsLink(target) {
|
|
219
|
+
const parent = path.dirname(target);
|
|
220
|
+
if (parent === target)
|
|
221
|
+
return false;
|
|
222
|
+
const name = path.basename(target);
|
|
223
|
+
let entries;
|
|
224
|
+
try {
|
|
225
|
+
entries = fs.readdirSync(parent, { withFileTypes: true });
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
// A parent that cannot be listed gives no listing to compare against.
|
|
229
|
+
// Each child is still checked against its own listing during the walk.
|
|
230
|
+
if (err.code === 'EACCES' || err.code === 'EPERM')
|
|
231
|
+
return false;
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
return entries.some((e) => e.name === name && e.isSymbolicLink());
|
|
235
|
+
}
|
|
236
|
+
function removeLinkOnly(linkPath) {
|
|
237
|
+
try {
|
|
238
|
+
fs.unlinkSync(linkPath);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const code = err.code;
|
|
242
|
+
// Windows directory symlinks and junctions can refuse unlink. A
|
|
243
|
+
// non-recursive rmdir removes the link entry and never touches the target.
|
|
244
|
+
if (code !== 'EPERM' && code !== 'EISDIR' && code !== 'EACCES')
|
|
245
|
+
throw err;
|
|
246
|
+
fs.rmdirSync(linkPath);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=copy-tree.js.map
|
|
@@ -262,9 +262,13 @@ export function filesystemErrorMessage(err) {
|
|
|
262
262
|
const safePath = redactedPath(details.rawPath);
|
|
263
263
|
const synced = syncedFolder(resolvedPath);
|
|
264
264
|
const permissionDenied = details.code === "EACCES" || details.code === "EPERM";
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
// Windows refuses symlinks without SeCreateSymbolicLinkPrivilege; that is a
|
|
266
|
+
// privilege problem, not a locked or cloud-synced file.
|
|
267
|
+
const remedy = permissionDenied && process.platform === "win32" && details.syscall === "symlink"
|
|
268
|
+
? "Windows needs Developer Mode (Settings > System > For developers) or an elevated terminal to create links; turn one on, then retry."
|
|
269
|
+
: permissionDenied && process.platform !== "win32" && synced === "none"
|
|
270
|
+
? "Check file and folder permissions, close the file, then retry."
|
|
271
|
+
: "Close the file or exclude its folder from cloud sync; if it persists, check permissions, then retry.";
|
|
268
272
|
return `Filesystem operation ${details.syscall} failed (${details.code}) at ${safePath}. ${remedy}`;
|
|
269
273
|
}
|
|
270
274
|
//# sourceMappingURL=environmental-error.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm-invocation: run npm without a shell on every platform.
|
|
3
|
+
*
|
|
4
|
+
* On Windows `npm` is a `npm.cmd` batch shim. `execFileSync('npm', ...)`
|
|
5
|
+
* cannot find it (ENOENT), and since Node's CVE-2024-27980 fix a `.cmd` file
|
|
6
|
+
* only runs through a shell, which would re-parse caller-supplied arguments
|
|
7
|
+
* such as a pack source. Instead, on Windows this resolves npm's JavaScript
|
|
8
|
+
* entry (`node_modules/npm/bin/npm-cli.js`, the file the shim itself runs)
|
|
9
|
+
* and launches it with the current Node binary as a plain argv array.
|
|
10
|
+
*
|
|
11
|
+
* Elsewhere `npm` is an executable on PATH and is used as is.
|
|
12
|
+
*/
|
|
13
|
+
export interface NpmInvocation {
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface NpmInvocationEnv {
|
|
18
|
+
platform?: NodeJS.Platform;
|
|
19
|
+
execPath?: string;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
exists?: (file: string) => boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class NpmNotFoundError extends Error {
|
|
24
|
+
constructor(searched: string[]);
|
|
25
|
+
}
|
|
26
|
+
export declare function npmInvocation(args: readonly string[], ctx?: NpmInvocationEnv): NpmInvocation;
|
|
27
|
+
//# sourceMappingURL=npm-invocation.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm-invocation: run npm without a shell on every platform.
|
|
3
|
+
*
|
|
4
|
+
* On Windows `npm` is a `npm.cmd` batch shim. `execFileSync('npm', ...)`
|
|
5
|
+
* cannot find it (ENOENT), and since Node's CVE-2024-27980 fix a `.cmd` file
|
|
6
|
+
* only runs through a shell, which would re-parse caller-supplied arguments
|
|
7
|
+
* such as a pack source. Instead, on Windows this resolves npm's JavaScript
|
|
8
|
+
* entry (`node_modules/npm/bin/npm-cli.js`, the file the shim itself runs)
|
|
9
|
+
* and launches it with the current Node binary as a plain argv array.
|
|
10
|
+
*
|
|
11
|
+
* Elsewhere `npm` is an executable on PATH and is used as is.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
export class NpmNotFoundError extends Error {
|
|
16
|
+
constructor(searched) {
|
|
17
|
+
super('Could not find npm. Looked for npm-cli.js at: ' +
|
|
18
|
+
(searched.length > 0 ? searched.join(', ') : '(no candidate locations)') +
|
|
19
|
+
'. Install Node.js with npm, or make sure npm.cmd is on PATH, then retry.');
|
|
20
|
+
this.name = 'NpmNotFoundError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function npmInvocation(args, ctx = {}) {
|
|
24
|
+
const platform = ctx.platform ?? process.platform;
|
|
25
|
+
if (platform !== 'win32')
|
|
26
|
+
return { command: 'npm', args: [...args] };
|
|
27
|
+
const execPath = ctx.execPath ?? process.execPath;
|
|
28
|
+
const env = ctx.env ?? process.env;
|
|
29
|
+
const exists = ctx.exists ?? ((file) => fs.existsSync(file));
|
|
30
|
+
const win = path.win32;
|
|
31
|
+
const cliUnder = (dir) => win.join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
32
|
+
// Prefer the npm the user's shell would run, so an npm installed or
|
|
33
|
+
// upgraded on its own (`npm i -g npm`, a version manager) wins over the
|
|
34
|
+
// copy bundled with node.exe.
|
|
35
|
+
const candidates = [];
|
|
36
|
+
// Set when hq itself runs under an npm script.
|
|
37
|
+
const npmExecPath = env.npm_execpath;
|
|
38
|
+
if (npmExecPath && win.basename(npmExecPath).toLowerCase() === 'npm-cli.js')
|
|
39
|
+
candidates.push(npmExecPath);
|
|
40
|
+
// The first npm.cmd shim on PATH, followed to the npm-cli.js beside it.
|
|
41
|
+
const pathValue = env.Path ?? env.PATH ?? '';
|
|
42
|
+
for (const dir of pathValue.split(';').filter(Boolean)) {
|
|
43
|
+
if (exists(win.join(dir, 'npm.cmd')))
|
|
44
|
+
candidates.push(cliUnder(dir));
|
|
45
|
+
}
|
|
46
|
+
// Fallback: the Node.js installer, nvm-windows and fnm put npm next to
|
|
47
|
+
// node.exe.
|
|
48
|
+
candidates.push(cliUnder(win.dirname(execPath)));
|
|
49
|
+
const cli = candidates.find((candidate) => exists(candidate));
|
|
50
|
+
if (!cli)
|
|
51
|
+
throw new NpmNotFoundError(candidates);
|
|
52
|
+
return { command: execPath, args: [cli, ...args] };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=npm-invocation.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type LinkKind = 'symlink' | 'junction' | 'copy';
|
|
2
|
+
export interface LinkDeps {
|
|
3
|
+
platform?: NodeJS.Platform;
|
|
4
|
+
/** Copy the folder when Windows refuses both link types. Default true. */
|
|
5
|
+
allowCopy?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Marker file written into a skill folder that HQ copied instead of linked. */
|
|
8
|
+
export declare const SKILL_COPY_MARKER = ".hq-skill-copy";
|
|
9
|
+
/** Thrown when Windows refuses a directory symlink, a junction and a copy. */
|
|
10
|
+
export declare class LinkUnavailableError extends Error {
|
|
11
|
+
readonly linkPath: string;
|
|
12
|
+
readonly expected: true;
|
|
13
|
+
constructor(linkPath: string, cause: unknown);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Link `linkPath` to the directory `target`. On Windows a privilege refusal
|
|
17
|
+
* falls back to a junction (absolute target, no privilege needed), and a
|
|
18
|
+
* refused junction falls back to a marked copy unless `allowCopy` is false.
|
|
19
|
+
* Other platforms keep the plain symlink behaviour.
|
|
20
|
+
*/
|
|
21
|
+
export declare function linkDirectory(target: string, linkPath: string, deps?: LinkDeps): LinkKind;
|
|
22
|
+
/**
|
|
23
|
+
* The source folder recorded in a skill copy's marker, or null when `p` is
|
|
24
|
+
* not a real folder carrying a valid marker.
|
|
25
|
+
*/
|
|
26
|
+
export declare function skillCopySource(p: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Remove an HQ skill entry: a link (never recursing into it) or a folder HQ
|
|
29
|
+
* copied and marked. Refuses an unmarked real folder. A marked copy is kept,
|
|
30
|
+
* with a notice through `onKept`, when its marker names a different entry
|
|
31
|
+
* (the user renamed or duplicated it) or its files changed since HQ copied
|
|
32
|
+
* them. Returns false when nothing exists at `p` or the copy was kept.
|
|
33
|
+
*/
|
|
34
|
+
export declare function removeSkillEntry(p: string, onKept?: (line: string) => void): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Remove whatever is at `p` without following any link: a link at `p` goes
|
|
37
|
+
* through `removeLinkOnly`, and a real folder is removed entry by entry so a
|
|
38
|
+
* link nested inside it is removed as a link. With `force`, a missing path is
|
|
39
|
+
* not an error.
|
|
40
|
+
*/
|
|
41
|
+
export declare function removeEntryNoFollow(p: string, opts?: {
|
|
42
|
+
force?: boolean;
|
|
43
|
+
}): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Remove a symlink or junction without recursing into it. Refuses anything
|
|
46
|
+
* that is not a link, so a real folder is never deleted through this path.
|
|
47
|
+
* Returns false when nothing exists at `linkPath`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function removeLinkOnly(linkPath: string): boolean;
|
|
50
|
+
//# sourceMappingURL=safe-link.d.ts.map
|