@waishnav/devspace 1.0.2 → 1.0.3

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 (94) hide show
  1. package/README.md +43 -7
  2. package/dist/apply-patch.js +399 -0
  3. package/dist/cli.js +13 -0
  4. package/dist/config.js +11 -13
  5. package/dist/process-platform.js +49 -0
  6. package/dist/process-sessions.js +329 -0
  7. package/dist/roots.js +5 -2
  8. package/dist/server.js +439 -199
  9. package/dist/skills.js +23 -3
  10. package/dist/ui/.vite/manifest.json +280 -280
  11. package/dist/ui/assets/{angular-html-Dj4LysSE.js → angular-html-D6nc7PYw.js} +1 -1
  12. package/dist/ui/assets/{angular-ts-CxuC_FFh.js → angular-ts-BAIStLeI.js} +1 -1
  13. package/dist/ui/assets/{apl-RwuvNFFo.js → apl-CtEv5pop.js} +1 -1
  14. package/dist/ui/assets/{astro-ncoaUbsO.js → astro-CVFIMXc6.js} +1 -1
  15. package/dist/ui/assets/{blade-Dw8-Lzux.js → blade-BgSlZ6jY.js} +1 -1
  16. package/dist/ui/assets/{c-Dpbbo_f2.js → c-bxLlCV26.js} +1 -1
  17. package/dist/ui/assets/{cobol-4ddX3THy.js → cobol-BuZ-r51r.js} +1 -1
  18. package/dist/ui/assets/{coffee-Bn-a5KqK.js → coffee-_JP0Joa7.js} +1 -1
  19. package/dist/ui/assets/{cpp-DoRrlM_T.js → cpp-CP_eaz-H.js} +1 -1
  20. package/dist/ui/assets/{crystal-DiZfCxn-.js → crystal-DZ-sEZd0.js} +1 -1
  21. package/dist/ui/assets/{css-DiQoSjDH.js → css-CZnyti9s.js} +1 -1
  22. package/dist/ui/assets/{edge-wEhCUoEt.js → edge-DjsoDMTR.js} +1 -1
  23. package/dist/ui/assets/{elixir-C3obhm0H.js → elixir-C5b04AkY.js} +1 -1
  24. package/dist/ui/assets/{elm-DyTBfh1L.js → elm-Ddx-rgVY.js} +1 -1
  25. package/dist/ui/assets/{erb-f4TnXRuY.js → erb-cjF7oPcT.js} +1 -1
  26. package/dist/ui/assets/{git-rebase-vhZbWNfp.js → git-rebase-DN5exBTl.js} +1 -1
  27. package/dist/ui/assets/{glimmer-js-BG7puL6Y.js → glimmer-js-CEq0hNwa.js} +1 -1
  28. package/dist/ui/assets/{glimmer-ts-CNCfLLL1.js → glimmer-ts-4uYGFkon.js} +1 -1
  29. package/dist/ui/assets/{glsl-DhT76AL5.js → glsl-DEJ9tTWm.js} +1 -1
  30. package/dist/ui/assets/{graphql-CJX08w8y.js → graphql-Bim5WuQh.js} +1 -1
  31. package/dist/ui/assets/{hack-DY4NGCCL.js → hack-BcyVIXhE.js} +1 -1
  32. package/dist/ui/assets/{haml-DwSkmVcG.js → haml-D9YaukDg.js} +1 -1
  33. package/dist/ui/assets/{handlebars-CQ_1dEmi.js → handlebars-CFaMw9zH.js} +1 -1
  34. package/dist/ui/assets/{heavy-payload-2rGBiitA.js → heavy-payload-B9aTqgfs.js} +1 -1
  35. package/dist/ui/assets/{html-B_e3Njif.js → html-CgO_zx4D.js} +1 -1
  36. package/dist/ui/assets/{html-derivative-_nHZ_Bk-.js → html-derivative-BxqkysJT.js} +1 -1
  37. package/dist/ui/assets/{http-CfcvPIru.js → http-CO5dv0RA.js} +1 -1
  38. package/dist/ui/assets/{hurl-9vWDQIMO.js → hurl-DRHtrK78.js} +1 -1
  39. package/dist/ui/assets/{java-C4S5r6L5.js → java-BvHC_DgR.js} +1 -1
  40. package/dist/ui/assets/{javascript-BRwqbMMC.js → javascript-C-3payQx.js} +1 -1
  41. package/dist/ui/assets/{jinja-DHxzMMc5.js → jinja-CAOPvEOH.js} +1 -1
  42. package/dist/ui/assets/{jison-B1DqeWXb.js → jison-totCB1L-.js} +1 -1
  43. package/dist/ui/assets/{json-DcFTkEdd.js → json-D0J9rEe1.js} +1 -1
  44. package/dist/ui/assets/{jsx-DgfmYa9f.js → jsx-na-XQD_B.js} +1 -1
  45. package/dist/ui/assets/{julia-3M8SJ8iF.js → julia-CqrTtZ4-.js} +1 -1
  46. package/dist/ui/assets/{just-UyH7_DNz.js → just-pmrVSkMc.js} +1 -1
  47. package/dist/ui/assets/{latex-wUbha6EX.js → latex-Cd_geX3E.js} +1 -1
  48. package/dist/ui/assets/{liquid-DqNTB9kN.js → liquid-C6ky5sJ-.js} +1 -1
  49. package/dist/ui/assets/{lua-DrEQ1QTW.js → lua-dgyBbeof.js} +1 -1
  50. package/dist/ui/assets/{marko-B3GJw9Md.js → marko-Dy5mfXY9.js} +1 -1
  51. package/dist/ui/assets/{mdc-BnThk82m.js → mdc-7NUvxgI1.js} +1 -1
  52. package/dist/ui/assets/{nginx-B5IeSsOX.js → nginx-C-KiEXbI.js} +1 -1
  53. package/dist/ui/assets/{nim-BJraatnS.js → nim-BBw8kfl9.js} +1 -1
  54. package/dist/ui/assets/{perl-BAhrR4gt.js → perl-CyxhJgbO.js} +1 -1
  55. package/dist/ui/assets/{php-BKRCk_wX.js → php-m4LAOpam.js} +1 -1
  56. package/dist/ui/assets/{pug-B8gmSV7z.js → pug-Cpl0baYI.js} +1 -1
  57. package/dist/ui/assets/{qml-DKrxIjHR.js → qml-BoSGNgIb.js} +1 -1
  58. package/dist/ui/assets/{r-YASBZvTH.js → r-C0JhqQd5.js} +1 -1
  59. package/dist/ui/assets/{razor-P-XFZqh6.js → razor-CntsBNoh.js} +1 -1
  60. package/dist/ui/assets/{regexp-CCpiH6nO.js → regexp-BNZmOCRl.js} +1 -1
  61. package/dist/ui/assets/{review-payload-BLYwg6t8.js → review-payload-BgdWHWiv.js} +1 -1
  62. package/dist/ui/assets/{rst-CFAzckbn.js → rst-BZ2XHIxM.js} +1 -1
  63. package/dist/ui/assets/{ruby-Dyp_Rx-S.js → ruby-BIthfCTP.js} +1 -1
  64. package/dist/ui/assets/{sas-YkWZIXxT.js → sas-5aT3fOla.js} +1 -1
  65. package/dist/ui/assets/{scss-Cf89idGl.js → scss-DZPfdkDy.js} +1 -1
  66. package/dist/ui/assets/{shellscript-BFMb52q8.js → shellscript-DbhKc5Xj.js} +1 -1
  67. package/dist/ui/assets/{shellsession-DuthW-5v.js → shellsession-X9-bcyHh.js} +1 -1
  68. package/dist/ui/assets/{soy-BohVVlJU.js → soy-Bd4tecrW.js} +1 -1
  69. package/dist/ui/assets/{sql-CnK9R2Gt.js → sql-Chf5JrDn.js} +1 -1
  70. package/dist/ui/assets/{stata-D-sV4Vbr.js → stata-DvMMIc_T.js} +1 -1
  71. package/dist/ui/assets/{surrealql-ChAy0gHv.js → surrealql-D5FMzVF5.js} +1 -1
  72. package/dist/ui/assets/{svelte-3HBVqG-H.js → svelte-CTiVXZrv.js} +1 -1
  73. package/dist/ui/assets/{templ-BixlV4Cp.js → templ-ldnfq-7w.js} +1 -1
  74. package/dist/ui/assets/{tex-DWoJKHvD.js → tex-QW0_7T2r.js} +1 -1
  75. package/dist/ui/assets/{ts-tags-DLA8eH13.js → ts-tags-CAKiWIBx.js} +1 -1
  76. package/dist/ui/assets/{tsx-BYWa5JUz.js → tsx-Bu9xJJmq.js} +1 -1
  77. package/dist/ui/assets/{twig-CnY65vAo.js → twig-Dp1752_V.js} +1 -1
  78. package/dist/ui/assets/{typescript-ByInUsro.js → typescript-RfO03Kmm.js} +1 -1
  79. package/dist/ui/assets/{useFileDiffInstance-Dg0pWcJV.js → useFileDiffInstance-DjqU4y1a.js} +3 -3
  80. package/dist/ui/assets/{vue-Qngt2rkz.js → vue-Dlasv57G.js} +1 -1
  81. package/dist/ui/assets/{vue-html-DDue9upr.js → vue-html-BHuJ7anu.js} +1 -1
  82. package/dist/ui/assets/{vue-vine-DBcAKsA6.js → vue-vine-CavE0PSk.js} +1 -1
  83. package/dist/ui/assets/{workspace-app-DuzpU4Hf.js → workspace-app-DDV_qdjx.js} +4 -4
  84. package/dist/ui/assets/{xml-CRLR5jiL.js → xml-Bbv64l4W.js} +1 -1
  85. package/dist/ui/assets/{xsl-CmcwyXbm.js → xsl-Sro4gyQY.js} +1 -1
  86. package/dist/ui/assets/{yaml-Dpe6Eg-k.js → yaml-Du68c8sA.js} +1 -1
  87. package/dist/ui/workspace-app.html +1 -1
  88. package/docs/chatgpt-coding-workflow.md +24 -4
  89. package/docs/codex-tool-mode-qa.md +73 -0
  90. package/docs/configuration.md +28 -5
  91. package/docs/gotchas.md +11 -5
  92. package/docs/setup.md +1 -1
  93. package/package.json +10 -5
  94. package/scripts/fix-node-pty-permissions.mjs +22 -0
@@ -0,0 +1,329 @@
1
+ import { spawn } from "node:child_process";
2
+ import { resolveShellCommand, terminateProcessTree } from "./process-platform.js";
3
+ const DEFAULT_EXEC_YIELD_MS = 10_000;
4
+ const DEFAULT_INTERACTIVE_YIELD_MS = 250;
5
+ const DEFAULT_POLL_YIELD_MS = 5_000;
6
+ const MAX_COMMAND_YIELD_MS = 30_000;
7
+ const MAX_POLL_YIELD_MS = 110_000;
8
+ const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;
9
+ const DEFAULT_BUFFER_CHARACTERS = 1_000_000;
10
+ const COMPLETED_SESSION_TTL_MS = 5 * 60 * 1_000;
11
+ const DEFAULT_COLUMNS = 80;
12
+ const DEFAULT_ROWS = 24;
13
+ function boundedInteger(value, fallback, maximum) {
14
+ if (value === undefined)
15
+ return fallback;
16
+ if (!Number.isFinite(value) || value < 0) {
17
+ throw new Error("Duration and output limits must be non-negative.");
18
+ }
19
+ return Math.min(Math.floor(value), maximum);
20
+ }
21
+ function terminalSize(value, fallback) {
22
+ if (value === undefined)
23
+ return fallback;
24
+ if (!Number.isInteger(value) || value < 1 || value > 1_000) {
25
+ throw new Error("Terminal dimensions must be integers between 1 and 1000.");
26
+ }
27
+ return value;
28
+ }
29
+ function processEnvironment() {
30
+ return {
31
+ ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)),
32
+ NO_COLOR: "1",
33
+ TERM: "dumb",
34
+ PAGER: "cat",
35
+ GIT_PAGER: "cat",
36
+ GH_PAGER: "cat",
37
+ CODEX_CI: "1",
38
+ LANG: process.env.LANG ?? "C.UTF-8",
39
+ LC_ALL: process.env.LC_ALL ?? "C.UTF-8",
40
+ };
41
+ }
42
+ function codePointLength(value) {
43
+ return Array.from(value).length;
44
+ }
45
+ function sliceCodePoints(value, start, end) {
46
+ return Array.from(value).slice(start, end).join("");
47
+ }
48
+ function takeHead(value, count) {
49
+ if (count <= 0)
50
+ return "";
51
+ return sliceCodePoints(value, 0, count);
52
+ }
53
+ function takeTail(value, count) {
54
+ if (count <= 0)
55
+ return "";
56
+ const characters = Array.from(value);
57
+ return characters.slice(Math.max(0, characters.length - count)).join("");
58
+ }
59
+ function splitBudget(maxCharacters) {
60
+ return {
61
+ head: Math.ceil(maxCharacters / 2),
62
+ tail: Math.floor(maxCharacters / 2),
63
+ };
64
+ }
65
+ function formatHeadTail(head, tail, omittedCharacters) {
66
+ if (omittedCharacters <= 0)
67
+ return head + tail;
68
+ return `${head}\n... output truncated (${omittedCharacters} characters omitted) ...\n${tail}`;
69
+ }
70
+ export class HeadTailBuffer {
71
+ maxCharacters;
72
+ head = "";
73
+ tail = "";
74
+ totalCharacters = 0;
75
+ constructor(maxCharacters) {
76
+ this.maxCharacters = maxCharacters;
77
+ if (!Number.isInteger(maxCharacters) || maxCharacters < 1) {
78
+ throw new Error("Head/tail buffer limit must be a positive integer.");
79
+ }
80
+ }
81
+ append(output) {
82
+ if (!output)
83
+ return;
84
+ const previousTotal = this.totalCharacters;
85
+ this.totalCharacters += codePointLength(output);
86
+ if (this.totalCharacters <= this.maxCharacters) {
87
+ this.head += output;
88
+ return;
89
+ }
90
+ const budget = splitBudget(this.maxCharacters);
91
+ if (previousTotal <= this.maxCharacters) {
92
+ const fullOutput = this.head + output;
93
+ this.head = takeHead(fullOutput, budget.head);
94
+ this.tail = takeTail(fullOutput, budget.tail);
95
+ return;
96
+ }
97
+ this.tail = takeTail(this.tail + output, budget.tail);
98
+ }
99
+ hasOutput() {
100
+ return this.totalCharacters > 0;
101
+ }
102
+ drain(maxCharacters) {
103
+ if (!Number.isInteger(maxCharacters) || maxCharacters < 1) {
104
+ throw new Error("Output limit must be a positive integer.");
105
+ }
106
+ const omittedByBuffer = Math.max(0, this.totalCharacters - codePointLength(this.head) - codePointLength(this.tail));
107
+ const retained = formatHeadTail(this.head, this.tail, omittedByBuffer);
108
+ const output = truncateOutput(retained, maxCharacters);
109
+ const truncated = omittedByBuffer > 0 || output.truncated;
110
+ this.head = "";
111
+ this.tail = "";
112
+ this.totalCharacters = 0;
113
+ return { output: output.output, truncated };
114
+ }
115
+ }
116
+ function truncateOutput(output, maxCharacters) {
117
+ const outputCharacters = codePointLength(output);
118
+ if (outputCharacters <= maxCharacters)
119
+ return { output, truncated: false };
120
+ const marker = "\n... output truncated ...\n";
121
+ const markerCharacters = codePointLength(marker);
122
+ const available = Math.max(0, maxCharacters - markerCharacters);
123
+ const budget = splitBudget(available);
124
+ return {
125
+ output: takeHead(output, budget.head) + marker + takeTail(output, budget.tail),
126
+ truncated: true,
127
+ };
128
+ }
129
+ export class ProcessSessionManager {
130
+ sessions = new Map();
131
+ maxBufferCharacters;
132
+ completedSessionTtlMs;
133
+ nextSessionId = 1;
134
+ constructor(options = {}) {
135
+ this.maxBufferCharacters = options.maxBufferCharacters ?? DEFAULT_BUFFER_CHARACTERS;
136
+ this.completedSessionTtlMs = options.completedSessionTtlMs ?? COMPLETED_SESSION_TTL_MS;
137
+ }
138
+ async start(input) {
139
+ const session = this.createSession(input);
140
+ this.sessions.set(session.id, session);
141
+ try {
142
+ if (input.tty && process.platform !== "win32")
143
+ await this.startPty(session, input);
144
+ else
145
+ this.startPipe(session, input);
146
+ }
147
+ catch (error) {
148
+ this.sessions.delete(session.id);
149
+ throw error;
150
+ }
151
+ const yieldTimeMs = boundedInteger(input.yieldTimeMs, DEFAULT_EXEC_YIELD_MS, MAX_COMMAND_YIELD_MS);
152
+ await this.waitForExit(session, yieldTimeMs);
153
+ const snapshot = this.consume(session, input.maxOutputTokens);
154
+ if (!session.running)
155
+ this.removeSession(session.id);
156
+ return snapshot;
157
+ }
158
+ async write(input) {
159
+ const session = this.getOwnedSession(input.workspaceId, input.sessionId);
160
+ const chars = input.chars ?? "";
161
+ const interactionRequested = chars.length > 0 || input.columns !== undefined || input.rows !== undefined;
162
+ if (input.columns !== undefined || input.rows !== undefined) {
163
+ session.columns = terminalSize(input.columns, session.columns);
164
+ session.rows = terminalSize(input.rows, session.rows);
165
+ if (!session.process?.resize) {
166
+ throw new Error(`Process session ${session.id} is not a PTY and cannot be resized.`);
167
+ }
168
+ session.process.resize(session.columns, session.rows);
169
+ }
170
+ const interruptRequested = chars.includes("\u0003") && session.running;
171
+ if (interruptRequested) {
172
+ session.process?.kill("SIGINT");
173
+ }
174
+ const writableChars = chars.replaceAll("\u0003", "");
175
+ if (writableChars && session.running)
176
+ session.process?.write(writableChars);
177
+ if ((interactionRequested || !session.buffer.hasOutput()) && session.running) {
178
+ const fallback = interactionRequested ? DEFAULT_INTERACTIVE_YIELD_MS : DEFAULT_POLL_YIELD_MS;
179
+ const maximum = interactionRequested ? MAX_COMMAND_YIELD_MS : MAX_POLL_YIELD_MS;
180
+ const yieldTimeMs = boundedInteger(input.yieldTimeMs, fallback, maximum);
181
+ await this.waitForExit(session, yieldTimeMs);
182
+ }
183
+ const snapshot = this.consume(session, input.maxOutputTokens);
184
+ if (!session.running)
185
+ this.removeSession(session.id);
186
+ return snapshot;
187
+ }
188
+ terminate(workspaceId, sessionId) {
189
+ const session = this.getOwnedSession(workspaceId, sessionId);
190
+ if (session.running)
191
+ session.process?.kill("SIGTERM");
192
+ }
193
+ shutdown() {
194
+ for (const session of this.sessions.values()) {
195
+ if (session.cleanupTimer)
196
+ clearTimeout(session.cleanupTimer);
197
+ if (session.running)
198
+ session.process?.kill("SIGTERM");
199
+ }
200
+ this.sessions.clear();
201
+ }
202
+ async waitForExit(session, yieldTimeMs) {
203
+ let timer;
204
+ try {
205
+ await Promise.race([
206
+ session.exitPromise,
207
+ new Promise((resolve) => {
208
+ timer = setTimeout(resolve, yieldTimeMs);
209
+ }),
210
+ ]);
211
+ }
212
+ finally {
213
+ if (timer)
214
+ clearTimeout(timer);
215
+ }
216
+ }
217
+ createSession(input) {
218
+ let resolveExit = () => undefined;
219
+ const exitPromise = new Promise((resolve) => {
220
+ resolveExit = resolve;
221
+ });
222
+ return {
223
+ id: this.nextSessionId++,
224
+ workspaceId: input.workspaceId,
225
+ startedAt: Date.now(),
226
+ columns: terminalSize(input.columns, DEFAULT_COLUMNS),
227
+ rows: terminalSize(input.rows, DEFAULT_ROWS),
228
+ buffer: new HeadTailBuffer(this.maxBufferCharacters),
229
+ running: true,
230
+ exitPromise,
231
+ resolveExit,
232
+ };
233
+ }
234
+ startPipe(session, input) {
235
+ const shell = resolveShellCommand(input.command);
236
+ const detached = process.platform !== "win32";
237
+ const child = spawn(input.command, {
238
+ cwd: input.cwd,
239
+ env: processEnvironment(),
240
+ stdio: "pipe",
241
+ windowsHide: true,
242
+ detached,
243
+ shell: shell.executable,
244
+ });
245
+ session.process = {
246
+ write: (data) => child.stdin.write(data),
247
+ kill: (signal = "SIGTERM") => terminateProcessTree(child, signal, detached),
248
+ resize: input.tty ? () => undefined : undefined,
249
+ };
250
+ child.stdout.on("data", (data) => this.append(session, data.toString("utf8")));
251
+ child.stderr.on("data", (data) => this.append(session, data.toString("utf8")));
252
+ child.on("error", (error) => this.append(session, `${error.message}\n`));
253
+ child.on("close", (code, signal) => this.finish(session, code ?? undefined, signal ?? undefined));
254
+ }
255
+ async startPty(session, input) {
256
+ let nodePty;
257
+ try {
258
+ nodePty = await import("node-pty");
259
+ }
260
+ catch {
261
+ throw new Error("PTY support requires the optional node-pty dependency.");
262
+ }
263
+ const shell = resolveShellCommand(input.command);
264
+ let pty;
265
+ try {
266
+ pty = nodePty.spawn(shell.executable, shell.args, {
267
+ cwd: input.cwd,
268
+ env: processEnvironment(),
269
+ name: "xterm-256color",
270
+ cols: session.columns,
271
+ rows: session.rows,
272
+ });
273
+ }
274
+ catch (error) {
275
+ throw error;
276
+ }
277
+ session.process = {
278
+ write: (data) => pty.write(data),
279
+ kill: (signal) => pty.kill(signal),
280
+ resize: (columns, rows) => pty.resize(columns, rows),
281
+ };
282
+ pty.onData((data) => this.append(session, data));
283
+ pty.onExit(({ exitCode, signal }) => {
284
+ this.finish(session, exitCode, signal === 0 ? undefined : String(signal));
285
+ });
286
+ }
287
+ finish(session, exitCode, signal) {
288
+ if (!session.running)
289
+ return;
290
+ session.running = false;
291
+ session.exitCode = exitCode;
292
+ session.signal = signal;
293
+ session.resolveExit();
294
+ session.cleanupTimer = setTimeout(() => this.sessions.delete(session.id), this.completedSessionTtlMs);
295
+ session.cleanupTimer.unref();
296
+ }
297
+ append(session, output) {
298
+ session.buffer.append(output);
299
+ }
300
+ consume(session, maxOutputTokens) {
301
+ const limit = boundedInteger(maxOutputTokens, DEFAULT_MAX_OUTPUT_TOKENS, 100_000);
302
+ const maxCharacters = Math.max(256, limit * 4);
303
+ const buffered = session.buffer.drain(maxCharacters);
304
+ return {
305
+ sessionId: session.running ? session.id : undefined,
306
+ output: buffered.output,
307
+ outputTruncated: buffered.truncated,
308
+ running: session.running,
309
+ exitCode: session.exitCode,
310
+ signal: session.signal,
311
+ wallTimeMs: Date.now() - session.startedAt,
312
+ };
313
+ }
314
+ getOwnedSession(workspaceId, sessionId) {
315
+ const session = this.sessions.get(sessionId);
316
+ if (!session)
317
+ throw new Error(`Unknown process session: ${sessionId}`);
318
+ if (session.workspaceId !== workspaceId) {
319
+ throw new Error(`Process session ${sessionId} does not belong to workspace ${workspaceId}.`);
320
+ }
321
+ return session;
322
+ }
323
+ removeSession(sessionId) {
324
+ const session = this.sessions.get(sessionId);
325
+ if (session?.cleanupTimer)
326
+ clearTimeout(session.cleanupTimer);
327
+ this.sessions.delete(sessionId);
328
+ }
329
+ }
package/dist/roots.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { homedir } from "node:os";
2
- import { relative, resolve, sep } from "node:path";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
3
  export class AccessDeniedError extends Error {
4
4
  constructor(message) {
5
5
  super(message);
@@ -19,7 +19,10 @@ export function isPathInsideRoot(path, root) {
19
19
  const resolvedRoot = resolve(expandHomePath(root));
20
20
  const relationship = relative(resolvedRoot, resolvedPath);
21
21
  return (relationship === "" ||
22
- (!relationship.startsWith("..") && relationship !== ".." && !relationship.includes(`..${sep}`)));
22
+ (!isAbsolute(relationship) &&
23
+ !relationship.startsWith("..") &&
24
+ relationship !== ".." &&
25
+ !relationship.includes(`..${sep}`)));
23
26
  }
24
27
  export function assertAllowedPath(path, allowedRoots) {
25
28
  const resolvedPath = resolve(expandHomePath(path));