package-management 0.0.16 → 0.2.0

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/dist/index.mjs CHANGED
@@ -1,25 +1,96 @@
1
- import ErrorStackParser from 'error-stack-parser';
2
- import path$1, { normalize, basename, dirname, relative, join } from 'pathe';
3
- import process from 'node:process';
4
- import path from 'node:path';
1
+ import path, { isAbsolute, dirname, normalize, join, extname } from 'pathe';
2
+ import util from 'node:util';
5
3
  import { fileURLToPath } from 'node:url';
6
- import { readFileSync, existsSync, writeFileSync } from 'node:fs';
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, accessSync, constants, statSync } from 'node:fs';
7
5
  import { asyncCacheFn } from 'async-cache-fn';
8
6
  import { execaSync, execa } from 'execa';
9
7
  import { findUp } from 'find-up';
10
- import { readFile, rm } from 'node:fs/promises';
8
+ import { readFile as readFile$1, rm } from 'node:fs/promises';
11
9
  import { resolvePathSync } from 'mlly';
10
+ import * as WST from 'workspace-tools';
11
+ import process from 'node:process';
12
+ import path$1 from 'node:path';
12
13
  import { globbySync } from 'globby';
13
14
  import gitignoreParser from 'parse-gitignore';
14
- import * as WST from 'workspace-tools';
15
15
  import { resolveAlias } from 'pathe/utils';
16
16
  import os from 'node:os';
17
- import { modify, applyEdits } from 'jsonc-parser';
18
- import { morph } from '@arktype/util';
17
+ import { parse, applyEdits, modify } from 'jsonc-parser';
18
+ import { stringifyTOML, parseTOML, stringifyYAML, parseYAML, stringifyJSON5, parseJSON5, stringifyJSONC, parseJSONC } from 'confbox';
19
19
  import { createStorage, restoreSnapshot, snapshot } from 'unstorage';
20
20
  import defineMemoryDriver from 'unstorage/drivers/memory';
21
21
  import defineFsLiteDriver from 'unstorage/drivers/fs-lite';
22
22
 
23
+ const getCallSites = "getCallSites" in util ? util.getCallSites : void 0;
24
+ const MAX_FRAMES = 200;
25
+ function resolveCallerFile(options) {
26
+ const { from, boundaryFunctionName, internalScripts = [] } = options ?? {};
27
+ if (from) return toCallerPath(String(from));
28
+ if (getCallSites === void 0) return void 0;
29
+ const sites = getCallSites(MAX_FRAMES, { sourceMap: true });
30
+ const scriptName = boundaryFunctionName && frameAfterFunction(sites, boundaryFunctionName) || firstForeignFrame(sites, [OWN_SCRIPT, ...internalScripts]);
31
+ return scriptName ? toFilePath(scriptName) : void 0;
32
+ }
33
+ const OWN_SCRIPT = import.meta.url;
34
+ function frameAfterFunction(sites, functionName) {
35
+ const boundary = sites.findIndex((site) => site.functionName === functionName);
36
+ return boundary === -1 ? void 0 : sites[boundary + 1]?.scriptName;
37
+ }
38
+ function firstForeignFrame(sites, internalScripts) {
39
+ const internal = new Set(internalScripts.map(toFilePath));
40
+ return sites.find(
41
+ (site) => (
42
+ // Frames from `node:` internals and evaluated code name no file, so they
43
+ // are never the caller's location however far out they appear.
44
+ isFileScript(site.scriptName) && !internal.has(toFilePath(site.scriptName))
45
+ )
46
+ )?.scriptName;
47
+ }
48
+ const isFileScript = (script) => Boolean(script) && (script.startsWith("file:") || isAbsolute(script));
49
+ const toFilePath = (script) => script.startsWith("file:") ? fileURLToPath(script) : script;
50
+ function toCallerPath(from) {
51
+ if (from.startsWith("file:")) return fileURLToPath(from);
52
+ if (isAbsolute(from)) return from;
53
+ throw new Error(
54
+ `\`from\` must be a file: URL or an absolute path, received ${JSON.stringify(from)}. Pass \`import.meta.url\`.`
55
+ );
56
+ }
57
+
58
+ const _filename = (options) => resolveCallerFile(withOwnScript(options));
59
+ const _dirname = (options) => {
60
+ const filePath = resolveCallerFile(withOwnScript(options));
61
+ return filePath === void 0 ? void 0 : dirname(filePath);
62
+ };
63
+ const withOwnScript = (options) => ({
64
+ ...options,
65
+ internalScripts: [...options?.internalScripts ?? [], import.meta.url]
66
+ });
67
+
68
+ function createFile(filePath, data, options) {
69
+ const { encoding = "utf-8", ...rest } = typeof options === "string" ? { encoding: options } : options ?? {};
70
+ const dir = path.dirname(filePath);
71
+ const writeFileOptions = { encoding, ...rest };
72
+ if (!existsSync(dir)) {
73
+ mkdirSync(dir, { recursive: true });
74
+ }
75
+ writeFileSync(filePath, data, writeFileOptions);
76
+ }
77
+
78
+ function readFile(filePath, options) {
79
+ return readFileSync(filePath, options?.encoding ?? "utf-8");
80
+ }
81
+ function readFileSafely(filePath, options) {
82
+ return existsSync(filePath) ? readFile(filePath, options) : void 0;
83
+ }
84
+
85
+ function isWritable(filename) {
86
+ try {
87
+ accessSync(filename, constants.W_OK);
88
+ return true;
89
+ } catch (e) {
90
+ return false;
91
+ }
92
+ }
93
+
23
94
  const toArray = (data) => Array.isArray(data) ? data : [data];
24
95
  const entriesOf = (o) => Object.entries(o);
25
96
  const fromEntries = (entries) => Object.fromEntries(entries);
@@ -33,7 +104,7 @@ const select = (obj, selection, mode) => {
33
104
  };
34
105
  function normalizePath(path, option) {
35
106
  if (typeof option === "function") {
36
- return normalizePath(path);
107
+ return option(path);
37
108
  }
38
109
  if (option === false) {
39
110
  return path;
@@ -45,14 +116,6 @@ const invariant = (predicate, message) => {
45
116
  throw new Error(message);
46
117
  }
47
118
  };
48
- function getArrayItemAtOffset(arr, index, offset = 0) {
49
- if (arr === void 0 || index === void 0) return void 0;
50
- return arr[index + offset];
51
- }
52
- function isMatching(a, b) {
53
- if (!a || !b) return false;
54
- return a === b;
55
- }
56
119
  function checkResult(cb, options) {
57
120
  try {
58
121
  return buildSingleProp({
@@ -77,123 +140,16 @@ function buildSingleProp(entry) {
77
140
  return { [key]: value };
78
141
  }
79
142
 
80
- function findCallerStackFrame(options) {
81
- return findErrorStackFrame(options, (frame) => frame.isParentOfRootFunction);
82
- }
83
- function findErrorStackFrame(options, find) {
84
- return getErrorStackFrames(options, find)[0];
85
- }
86
- function getErrorStackFrames(options, filter) {
87
- const { error, rootFunctionName, startFrom = "top" } = options;
88
- const frames = startFrom === "bottom" ? ErrorStackParser.parse(error).reverse() : ErrorStackParser.parse(error);
89
- const { parsedFrames } = frames.reduce(
90
- (acc, frame, index) => {
91
- const parsed = parseFrame({
92
- frame,
93
- index,
94
- rootFunctionName,
95
- frames
96
- });
97
- const isValid = filter ? filter(parsed) : true;
98
- if (isValid) {
99
- acc.parsedFrames.push(parsed);
100
- }
101
- return acc;
102
- },
103
- { parsedFrames: [] }
104
- );
105
- return parsedFrames ?? [];
106
- }
107
- function parseFrame(options) {
108
- const { frame, frames, index, rootFunctionName, cwd, debug } = options ?? {};
109
- const functionName = parseFunctionName(frame.functionName);
110
- const isRootFunction = isMatching(functionName, rootFunctionName);
111
- const beforeFrameFunctionName = parseFunctionName(
112
- getArrayItemAtOffset(frames, index, -1)?.functionName
113
- );
114
- const isParentOfRootFunction = isMatching(
115
- beforeFrameFunctionName,
116
- rootFunctionName
117
- );
118
- const fileData = frame.fileName ? getFilePathData({ filepath: frame.fileName, cwd }) : void 0;
119
- const { isFileInCwd: isFrameInScope, ...restFileData } = fileData ?? {};
120
- const data = {
121
- ...restFileData,
122
- functionName,
123
- source: frame.source,
124
- sourceFunctionName: frame.functionName,
125
- isFrameInScope,
126
- place: placeFormatter(index, frames?.length),
127
- isRootFunction,
128
- isParentOfRootFunction,
129
- rootFunctionName
130
- };
131
- debug && console.log(data);
132
- return data;
133
- }
134
- function getFilePathData({
135
- filepath,
136
- cwd
137
- }) {
138
- const workingDir = cwd ?? process.cwd();
139
- const filePath = normalize(toFilePath(filepath));
140
- const fileBasename = basename(filePath);
141
- const dirPath = dirname(filePath);
142
- const dirBasename = basename(dirPath);
143
- const relativeFilePath = relative(workingDir, filePath);
144
- const relativeDirPath = dirname(relativeFilePath);
145
- const isFileInCwd = filePath.startsWith(workingDir);
146
- return {
147
- filePath,
148
- dirPath,
149
- relativeFilePath,
150
- relativeDirPath,
151
- fileBasename,
152
- dirBasename,
153
- isFileInCwd
154
- };
155
- }
156
- function toFilePath(fileName) {
157
- return fileName.startsWith("file:") ? fileURLToPath(fileName) : fileName;
158
- }
159
- function parseFunctionName(functionName) {
160
- if (!functionName) return void 0;
161
- return removeStart(functionName, "Module.");
162
- }
163
- function removeStart(input, value) {
164
- return input.startsWith(value) ? input.slice(value.length) : input;
165
- }
166
- function placeFormatter(index, total) {
167
- if (index === void 0 || !total) return void 0;
168
- return [index, total].join("/");
169
- }
170
-
171
- const _filename = (options) => {
172
- const { filePath } = findCallerStackFrame({ error: new Error(), ...options }) ?? {};
173
- return filePath;
174
- };
175
- const _dirname = (options) => {
176
- const { dirPath } = findCallerStackFrame({ error: new Error(), ...options }) ?? {};
177
- return dirPath;
178
- };
179
-
180
143
  async function resolveModule(module) {
181
144
  const resolved = await module;
182
- return resolved.default || resolved;
145
+ const hasDefault = resolved !== null && typeof resolved === "object" && "default" in resolved;
146
+ return hasDefault ? resolved.default : resolved;
183
147
  }
184
148
  function isPackageModuleFound(name, options) {
185
149
  return Boolean(resolvePackageModulePath(name, options));
186
150
  }
187
151
  function resolvePackageModulePath(name, options) {
188
- const resolvedPath = findResolvedModulePath(
189
- [`${name}/package.json`, name],
190
- options
191
- );
192
- if (resolvedPath === void 0) {
193
- console.error(`Could not resolve package ${name}`);
194
- return void 0;
195
- }
196
- return resolvedPath;
152
+ return findResolvedModulePath([`${name}/package.json`, name], options);
197
153
  }
198
154
  function findResolvedModulePath(paths, options) {
199
155
  for (const path of paths) {
@@ -215,28 +171,68 @@ function resolveModulePath(modulePath, options) {
215
171
  }
216
172
  }
217
173
 
174
+ function getPackageFolder(options) {
175
+ return WST.findPackageRoot(options?.cwd ?? process.cwd());
176
+ }
177
+
178
+ function getPackageInfo(options) {
179
+ const packageDir = getPackageFolder(options);
180
+ if (!packageDir) {
181
+ throw new Error(
182
+ `No package.json found searching up from "${options?.cwd ?? process.cwd()}"`
183
+ );
184
+ }
185
+ return readPackageInfo({ packageDir });
186
+ }
187
+ function readPackageInfo({
188
+ packageDir
189
+ }) {
190
+ const packageJsonPath = path$1.join(packageDir, "package.json");
191
+ const packageJson = readPackageJson(packageJsonPath);
192
+ return {
193
+ name: packageJson.name,
194
+ path: packageJsonPath,
195
+ dirpath: packageDir,
196
+ packageJson
197
+ };
198
+ }
199
+ const packageJsonCache = /* @__PURE__ */ new Map();
200
+ function readPackageJson(packageJsonPath) {
201
+ const { mtimeMs } = statSync(packageJsonPath);
202
+ const cached = packageJsonCache.get(packageJsonPath);
203
+ if (cached?.mtimeMs === mtimeMs) return cached.packageJson;
204
+ const packageJson = JSON.parse(
205
+ readFileSync(packageJsonPath, "utf-8")
206
+ );
207
+ packageJsonCache.set(packageJsonPath, { mtimeMs, packageJson });
208
+ return packageJson;
209
+ }
210
+
218
211
  const tsconfig = (tsconfigDir) => ({
219
212
  get paths() {
213
+ if (!tsconfigDir) return [];
220
214
  return globbySync(["tsconfig.json", "tsconfig.*.json"], {
221
- cwd: tsconfigDir
222
- }) ?? [];
215
+ cwd: tsconfigDir,
216
+ absolute: true
217
+ });
223
218
  }
224
219
  });
225
220
 
226
- function parseGitignoreFile(gitignorePath, options) {
227
- return gitignoreParser(gitignorePath, options);
221
+ function parseGitignoreContent(gitignoreContent, options) {
222
+ return gitignoreParser(gitignoreContent, options);
228
223
  }
229
- function getGitignoreData(gitignorePath) {
230
- const gitignoreFileContent = readFileSync(gitignorePath, "utf-8");
231
- const gitignores = parseGitignoreFile(gitignoreFileContent);
232
- return gitignores;
224
+ function getGitignoreData(gitignorePath, options) {
225
+ if (!gitignorePath || !existsSync(gitignorePath)) {
226
+ return parseGitignoreContent("", options);
227
+ }
228
+ return parseGitignoreContent(readFileSync(gitignorePath, "utf-8"), options);
233
229
  }
234
- const gitignore = (gitignorePath) => ({
230
+ const gitignore = (gitignorePath, options) => ({
235
231
  get data() {
236
- return getGitignoreData(gitignorePath);
232
+ return getGitignoreData(gitignorePath, options);
237
233
  },
238
234
  get patterns() {
239
- return getGitignoreData(gitignorePath).patterns;
235
+ return getGitignoreData(gitignorePath, options).patterns;
240
236
  }
241
237
  });
242
238
 
@@ -299,36 +295,13 @@ function getWorkspaceFolder(options) {
299
295
  throwIfNotFound = true
300
296
  } = options ?? {};
301
297
  const getFolder = fallbackToGitRoot ? WST.findProjectRoot : WST.getWorkspaceManagerRoot;
302
- const folder = getFolder(cwd);
298
+ const folder = checkResult(() => getFolder(cwd)).data;
303
299
  if (folder === void 0 && throwIfNotFound) {
304
300
  throw new Error(`Could not find workspace folder`);
305
301
  }
306
302
  return folder;
307
303
  }
308
304
 
309
- function getPackageFolder(options) {
310
- return WST.findPackageRoot(options?.cwd ?? process.cwd());
311
- }
312
-
313
- function getPackageInfo(options) {
314
- const packageDir = getPackageFolder(options);
315
- return readPackageInfo({ packageDir });
316
- }
317
- function readPackageInfo({
318
- packageDir
319
- }) {
320
- const packageJsonPath = path.join(packageDir, "package.json");
321
- const packageJson = JSON.parse(
322
- readFileSync(packageJsonPath, "utf-8")
323
- );
324
- return {
325
- name: packageJson.name,
326
- path: packageJsonPath,
327
- dirpath: packageDir,
328
- packageJson
329
- };
330
- }
331
-
332
305
  function getWorkspaceProjectInfo(options) {
333
306
  try {
334
307
  const workspaceDir = getWorkspaceFolder(options);
@@ -344,13 +317,21 @@ function getWorkspacePackageInfoList(options) {
344
317
  const wstList = WST.getWorkspaceInfos(workspaceDir);
345
318
  return resolveWstList(wstList);
346
319
  }
320
+ function toPackageInfo(info) {
321
+ return {
322
+ name: info.name,
323
+ dirpath: info.path,
324
+ path: join(info.path, "package.json"),
325
+ packageJson: info.packageJson
326
+ };
327
+ }
347
328
  function common(options) {
348
329
  const { cwd, includeRoot: includeWorkspace } = options ?? {};
349
330
  const workspaceDir = getWorkspaceFolder({ cwd });
350
331
  function resolveWstList(list) {
351
332
  if (!workspaceDir || !list) return [];
352
- if (includeWorkspace) return [getWorkspaceProjectInfo(), ...list];
353
- return list;
333
+ const packages = list.map(toPackageInfo);
334
+ return includeWorkspace ? [getWorkspaceProjectInfo({ cwd }), ...packages] : packages;
354
335
  }
355
336
  return {
356
337
  workspaceDir,
@@ -367,7 +348,7 @@ function getWorkspacePackageInfoMap(options) {
367
348
 
368
349
  function getProjectInfoByName(name, options) {
369
350
  const { cwd } = options ?? {};
370
- return getWorkspacePackageInfoMap({ cwd })[name];
351
+ return getWorkspacePackageInfoMap({ cwd, includeRoot: true })[name];
371
352
  }
372
353
 
373
354
  function getGitRootFolder(options) {
@@ -382,7 +363,7 @@ function getGitRootFolder(options) {
382
363
  }
383
364
  return void 0;
384
365
  }
385
- return path$1.normalize(stdout);
366
+ return path.normalize(stdout);
386
367
  }
387
368
 
388
369
  function getProjectInfo(folder, options) {
@@ -413,9 +394,14 @@ function parseWorkspaceFolderTypeOptions(option, defaultOptions) {
413
394
 
414
395
  const project = (...args) => {
415
396
  const [source, projectOptions] = args;
416
- const { packageJson, packageJsonPath, packageName, projectDir } = info();
417
397
  const {
418
- findPackageManager: findPackageManager2,
398
+ packageJson,
399
+ name: packageName,
400
+ dirpath: projectDir,
401
+ path: packageJsonPath
402
+ } = getProjectInfo(source, projectOptions) ?? {};
403
+ const {
404
+ findPackageManager,
419
405
  detectPackageManagers,
420
406
  detectLockfilePackageManagers,
421
407
  detectGlobalPackageManagers,
@@ -423,41 +409,29 @@ const project = (...args) => {
423
409
  filterPackageManagers,
424
410
  mapPackageManagers
425
411
  } = definePackageManagerClient({ cwd: projectDir });
412
+ const getPackageJson = () => projectDir ? readPackageInfo({ packageDir: projectDir }).packageJson : packageJson;
426
413
  return {
427
414
  packageJson,
428
415
  packageJsonPath,
429
416
  packageName,
430
417
  projectDir,
431
- findPackageManager: findPackageManager2,
418
+ findPackageManager,
432
419
  detectPackageManagers,
433
420
  detectGlobalPackageManagers,
434
421
  detectLockfilePackageManagers,
435
422
  globalVersions,
436
423
  mapPackageManagers,
437
- tsconfig: tsconfig(projectDir ?? ""),
438
- gitignore: gitignore(path$1.join(projectDir ?? "", ".gitignore")),
424
+ // Passed through as-is: substituting "" for an unresolved project made
425
+ // both of these read from the calling process's directory instead.
426
+ tsconfig: tsconfig(projectDir),
427
+ gitignore: gitignore(
428
+ projectDir ? path.join(projectDir, ".gitignore") : void 0
429
+ ),
439
430
  filterPackageManagers,
440
- getPackageJson: () => get("packageJson"),
441
- findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, get("packageJson")),
442
- isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, get("packageJson"))
431
+ getPackageJson,
432
+ findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, getPackageJson()),
433
+ isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, getPackageJson())
443
434
  };
444
- function info() {
445
- const {
446
- packageJson: packageJson2,
447
- name: packageName2,
448
- dirpath: projectDir2,
449
- path: packageJsonPath2
450
- } = getProjectInfo(source, projectOptions) ?? {};
451
- return {
452
- packageJson: packageJson2,
453
- packageJsonPath: packageJsonPath2,
454
- packageName: packageName2,
455
- projectDir: projectDir2
456
- };
457
- }
458
- function get(key) {
459
- return info()[key];
460
- }
461
435
  };
462
436
 
463
437
  function getWorkspacePackageNames(options) {
@@ -472,18 +446,24 @@ const workspace = {
472
446
  getProject: project
473
447
  };
474
448
 
475
- function importer(imports, options) {
449
+ async function importer(imports, options) {
476
450
  const { install: defaultInstall = true, installer } = options ?? {};
451
+ const resolved = imports.map((option) => resolveImportOption(option));
452
+ const missing = resolved.filter(
453
+ (option) => Boolean(option.name) && (option.install ?? defaultInstall) && !((option.checkExists ?? true) && isSatisfied(option.name))
454
+ );
455
+ await installMissing(
456
+ missing.filter(({ dev }) => !dev).map(({ name }) => name),
457
+ { dev: false },
458
+ installer
459
+ );
460
+ await installMissing(
461
+ missing.filter(({ dev }) => dev).map(({ name }) => name),
462
+ { dev: true },
463
+ installer
464
+ );
477
465
  return Promise.all(
478
- imports.map(async (e) => {
479
- const importOpt = resolveImportOption(e);
480
- const shouldInstall = importOpt.install ?? defaultInstall;
481
- if (shouldInstall && importOpt.name) {
482
- await installImport(importOpt.name, importOpt, installer);
483
- }
484
- const m = await importOpt.import();
485
- return resolveModule(m);
486
- })
466
+ resolved.map(async (option) => resolveModule(await option.import()))
487
467
  );
488
468
  }
489
469
  const definePackage = (option) => {
@@ -507,12 +487,11 @@ function resolveImportOption(option) {
507
487
  }
508
488
  return option;
509
489
  }
510
- async function installImport(packageName, options, installer) {
511
- if (!packageName) return;
512
- const { checkExists } = options ?? {};
513
- if (checkExists && isPackageDependency(packageName)) return;
490
+ const isSatisfied = (packageName) => isPackageDependency(packageName) && isPackageModuleFound(packageName);
491
+ async function installMissing(packageNames, options, installer) {
492
+ if (packageNames.length === 0) return;
514
493
  const installerFn = installer ?? (await workspace.getProject("<package_folder>").findPackageManager()).installPackage;
515
- await installerFn(packageName, options);
494
+ await installerFn(packageNames, options);
516
495
  }
517
496
 
518
497
  const importMap = async (importMap2, options) => {
@@ -534,20 +513,36 @@ function definePackageManager(config, options) {
534
513
  const lockfiles = toArray(config.meta.lockfile);
535
514
  return await findUp(lockfiles, { cwd });
536
515
  });
516
+ const globalVersion = asyncCacheFn(
517
+ async (options2) => {
518
+ try {
519
+ const { stdout } = await $$({
520
+ command,
521
+ args: [agentOptions.version],
522
+ cwd: defaultCwd,
523
+ ...options2
524
+ });
525
+ return `${stdout}`.trim();
526
+ } catch {
527
+ return void 0;
528
+ }
529
+ }
530
+ );
537
531
  const installPackage = async (packageName, options2) => {
538
532
  const install = agentArgs.install;
539
- const { dev, preferOffline } = select(
540
- install.options,
541
- {
542
- preferOffline: true,
543
- cwd: defaultCwd,
544
- ...options2
545
- });
533
+ const { dev = false, preferOffline = true } = options2 ?? {};
534
+ const flags = select(install.options, { dev, preferOffline });
546
535
  const packageNames = toArray(packageName);
536
+ assertInstallableNames(packageNames);
547
537
  try {
548
538
  await $$({
549
539
  command,
550
- args: [install.command, dev, preferOffline, ...packageNames],
540
+ args: [
541
+ install.command,
542
+ flags.dev,
543
+ flags.preferOffline,
544
+ ...packageNames
545
+ ],
551
546
  cwd: defaultCwd,
552
547
  ...options2
553
548
  });
@@ -589,21 +584,14 @@ function definePackageManager(config, options) {
589
584
  readLockfile: asyncCacheFn(async (...args) => {
590
585
  const lockfilePath = await findLockfilePath.noCache(...args);
591
586
  if (!lockfilePath) return void 0;
592
- return readFile(lockfilePath, "utf8");
587
+ return readFile$1(lockfilePath, "utf8");
593
588
  }),
594
- globalVersion: asyncCacheFn(async (...args) => {
595
- const [options2] = args;
596
- try {
597
- const { stdout } = await $$({
598
- command,
599
- args: [agentOptions.version],
600
- cwd: defaultCwd,
601
- ...options2
602
- });
603
- return `${stdout}`;
604
- } catch (e) {
605
- return void 0;
606
- }
589
+ globalVersion,
590
+ matchesVersion: asyncCacheFn(async (...args) => {
591
+ const { matchesVersion } = config.meta;
592
+ if (!matchesVersion) return true;
593
+ const version = await globalVersion.noCache(...args);
594
+ return version ? matchesVersion(version) : false;
607
595
  }),
608
596
  definePackage,
609
597
  installPackage,
@@ -617,6 +605,16 @@ function definePackageManager(config, options) {
617
605
  }
618
606
  };
619
607
  }
608
+ function assertInstallableNames(packageNames) {
609
+ const rejected = packageNames.filter(
610
+ (name) => name.length === 0 || name.startsWith("-")
611
+ );
612
+ if (rejected.length > 0) {
613
+ throw new Error(
614
+ `Not a package name: ${rejected.map((name) => JSON.stringify(name)).join(", ")}. A package name cannot be empty or begin with "-".`
615
+ );
616
+ }
617
+ }
620
618
  async function $$(options) {
621
619
  const { command, args = [], silent = true, cwd, shellOptions } = options;
622
620
  return execa(command, args.filter(notFalsy), {
@@ -639,7 +637,9 @@ const bun = definePackageManagerConfig({
639
637
  command: "bun",
640
638
  runner: "bunx",
641
639
  meta: {
642
- lockfile: "bun.lockb"
640
+ // Bun wrote a binary lockfile originally and a text one from 1.2 onwards,
641
+ // so a project using either must still be detected.
642
+ lockfile: ["bun.lock", "bun.lockb"]
643
643
  },
644
644
  args: {
645
645
  install: {
@@ -650,7 +650,7 @@ const bun = definePackageManagerConfig({
650
650
  }
651
651
  },
652
652
  uninstall: {
653
- command: "uninstall"
653
+ command: "remove"
654
654
  }
655
655
  },
656
656
  options: {
@@ -714,7 +714,9 @@ const yarn = definePackageManagerConfig({
714
714
  command: "yarn",
715
715
  runner: "yarn dlx",
716
716
  meta: {
717
- lockfile: "yarn.lock"
717
+ lockfile: "yarn.lock",
718
+ // Berry shares this command and lockfile, so the version decides.
719
+ matchesVersion: (version) => version.startsWith("1.")
718
720
  },
719
721
  args: {
720
722
  install: {
@@ -733,6 +735,33 @@ const yarn = definePackageManagerConfig({
733
735
  }
734
736
  });
735
737
 
738
+ const yarnBerry = definePackageManagerConfig({
739
+ id: "yarn-berry",
740
+ name: "Yarn Berry",
741
+ command: "yarn",
742
+ runner: "yarn dlx",
743
+ meta: {
744
+ lockfile: "yarn.lock",
745
+ // Classic shares this command and lockfile; everything past 1.x is Berry.
746
+ matchesVersion: (version) => !version.startsWith("1.")
747
+ },
748
+ args: {
749
+ install: {
750
+ command: "add",
751
+ options: {
752
+ dev: "-D",
753
+ preferOffline: "--cached"
754
+ }
755
+ },
756
+ uninstall: {
757
+ command: "remove"
758
+ }
759
+ },
760
+ options: {
761
+ version: "--version"
762
+ }
763
+ });
764
+
736
765
  async function findPackageManager(packageManagers, options) {
737
766
  const packageManager = await findPackageManagerSafely(
738
767
  packageManagers,
@@ -762,7 +791,9 @@ async function detectPackageManagers(packageManagers, options) {
762
791
  async function detectLockfilePackageManagers(packageManagers, options) {
763
792
  return filterPackageManagers(
764
793
  packageManagers,
765
- (packageManager) => packageManager.hasLockfile(options),
794
+ // Yarn Classic and Berry share a lockfile name, so a lockfile match alone
795
+ // would report both. The version check settles which one it is.
796
+ async (packageManager) => await packageManager.hasLockfile(options) && await packageManager.matchesVersion(options),
766
797
  options
767
798
  );
768
799
  }
@@ -772,7 +803,7 @@ async function detectGlobalPackageManagers(packageManagers, options) {
772
803
  // Without the `await`, `Boolean` receives a pending promise and is always
773
804
  // `true`, so every package manager passes the filter regardless of whether
774
805
  // it is actually installed.
775
- async (packageManager) => Boolean(await packageManager.globalVersion(options)),
806
+ async (packageManager) => Boolean(await packageManager.globalVersion(options)) && await packageManager.matchesVersion(options),
776
807
  options
777
808
  );
778
809
  }
@@ -805,7 +836,7 @@ function selectAllowedPackageManagers(packageManagers, options) {
805
836
  );
806
837
  }
807
838
 
808
- const packageManagerConfigs = [pnpm, yarn, bun, npm];
839
+ const packageManagerConfigs = [pnpm, yarn, yarnBerry, bun, npm];
809
840
  function definePackageManagerClient(options) {
810
841
  const configs = packageManagerConfigs.map(
811
842
  (config) => definePackageManager(config, options)
@@ -835,8 +866,11 @@ function definePackageManagerClient(options) {
835
866
  };
836
867
  }
837
868
 
838
- function isPackageDependency(packageName) {
839
- return toArray(packageName).every((name) => isDependencyInPackageJson(name));
869
+ function isPackageDependency(packageName, options) {
870
+ const { packageJson } = getPackageInfo(options);
871
+ return toArray(packageName).every(
872
+ (name) => isDependencyInPackageJson(name, packageJson)
873
+ );
840
874
  }
841
875
 
842
876
  function definePathAliases(aliasDefinitions) {
@@ -854,42 +888,49 @@ function definePathAliases(aliasDefinitions) {
854
888
  };
855
889
  }
856
890
  function getAliasedFilePath(aliasMap, options) {
857
- const { to, startingFrom, cwd, checkExistence, glob } = typeof options === "string" || Array.isArray(options) ? { to: options } : options;
858
- const resolveOptions = { cwd, checkExistence, glob, aliasMap };
891
+ const { to, startingFrom, cwd, from, checkExistence, glob } = typeof options === "string" || Array.isArray(options) ? { to: options } : options;
892
+ const resolveOptions = { cwd, from, checkExistence, glob, aliasMap };
859
893
  try {
860
894
  return startingFrom ? resolveRelativePathTo(to, startingFrom, resolveOptions) : resolvePathTo(to, resolveOptions);
861
895
  } catch (error) {
862
- if (checkExistence || glob) return void 0;
896
+ if (error instanceof PathNotFoundError) return void 0;
863
897
  throw error;
864
898
  }
865
899
  }
900
+ class PathNotFoundError extends Error {
901
+ }
866
902
  function resolveRelativePathTo(to, from, options) {
867
903
  const pathFrom = resolvePathTo(from, options);
868
904
  const pathTo = resolvePathTo(to, options);
869
- return path$1.relative(pathFrom, pathTo);
905
+ return path.relative(pathFrom, pathTo);
870
906
  }
871
- function resolvePathTo(pathTo, { cwd, checkExistence, glob, aliasMap }) {
872
- const normalized = normalizePathTo(pathTo, { cwd, aliasMap });
907
+ function resolvePathTo(pathTo, { cwd, from, checkExistence, glob, aliasMap }) {
908
+ const normalized = normalizePathTo(pathTo, { cwd, from, aliasMap });
873
909
  if (glob) {
874
910
  const globPaths = globbySync(normalized, { cwd });
875
911
  if (!globPaths[0])
876
- throw new Error(`No paths found for glob: ${normalized}`);
912
+ throw new PathNotFoundError(`No paths found for glob: ${normalized}`);
877
913
  return globPaths[0];
878
914
  }
879
915
  if (!glob && checkExistence && !existsSync(normalized))
880
- throw new Error(`Path does not exist: ${normalized}`);
916
+ throw new PathNotFoundError(`Path does not exist: ${normalized}`);
881
917
  return normalized;
882
918
  }
883
919
  function normalizePathTo(pathTo, options) {
884
- const { cwd, aliasMap } = options ?? {};
885
- const aliasedPath = Array.isArray(pathTo) ? path$1.join(...pathTo.filter(isNotNull)) : pathTo;
886
- const baseDir = Array.isArray(pathTo) ? pathTo[0] : findAliasToken(aliasedPath, aliasMap);
887
- if (baseDir === void 0) return aliasedPath;
888
- const baseDirPath = executeMapFn(aliasMap, baseDir, [{ cwd }]);
889
- if (typeof baseDirPath !== "string" || baseDirPath.length === 0) {
890
- throw new Error(`Path alias resolved to no location: ${baseDir}`);
920
+ const { cwd, from, aliasMap } = options ?? {};
921
+ const baseDir = Array.isArray(pathTo) ? pathTo[0] : findAliasToken(pathTo, aliasMap);
922
+ if (baseDir === void 0) return pathTo;
923
+ const baseDirPath = assertResolved(
924
+ baseDir,
925
+ executeMapFn(aliasMap, baseDir, [{ cwd, from }])
926
+ );
927
+ return Array.isArray(pathTo) ? path.join(baseDirPath, ...pathTo.slice(1).filter(isNotNull)) : resolveAlias(pathTo, { [baseDir]: baseDirPath });
928
+ }
929
+ function assertResolved(alias, resolved) {
930
+ if (typeof resolved !== "string" || resolved.length === 0) {
931
+ throw new Error(`Path alias resolved to no location: ${alias}`);
891
932
  }
892
- return resolveAlias(aliasedPath, { [baseDir]: baseDirPath });
933
+ return resolved;
893
934
  }
894
935
  function findAliasToken(pathTo, aliasMap) {
895
936
  return Object.keys(aliasMap ?? {}).filter((alias) => pathTo === alias || pathTo.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length)[0];
@@ -907,10 +948,17 @@ function getAliasMap(aliasDefs) {
907
948
  return [
908
949
  [alias, resolve],
909
950
  ...subpaths.map(({ to }) => {
910
- const subpathAlias = path$1.join(alias, to);
951
+ const subpathAlias = path.join(alias, to);
911
952
  return [
912
953
  subpathAlias,
913
- (opts) => resolveAlias(subpathAlias, { [alias]: resolve(opts) })
954
+ (opts) => (
955
+ // The same assertion the bare alias gets. Without it an
956
+ // unresolvable parent produced `join(undefined, "/node_modules")`
957
+ // — a path at the filesystem root — instead of failing.
958
+ resolveAlias(subpathAlias, {
959
+ [alias]: assertResolved(alias, resolve(opts))
960
+ })
961
+ )
914
962
  ];
915
963
  })
916
964
  ];
@@ -985,13 +1033,13 @@ const predefinedPathAliases = {
985
1033
  subpaths: []
986
1034
  },
987
1035
  "<current_file>": {
988
- // resolve: () => fileURLToPath(import.meta.url),
989
- resolve: () => _filename({ rootFunctionName: "getFilePath" }) ?? "",
1036
+ // `from` is the caller naming itself, which is exact. Without it the stack
1037
+ // is read, which is a best effort and Node-only.
1038
+ resolve: (opts) => _filename({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
990
1039
  subpaths: []
991
1040
  },
992
1041
  "<current_folder>": {
993
- // resolve: () => fileURLToPath(import.meta.url),
994
- resolve: () => _dirname({ rootFunctionName: "getFilePath" }) ?? "",
1042
+ resolve: (opts) => _dirname({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
995
1043
  subpaths: []
996
1044
  }
997
1045
  };
@@ -1014,59 +1062,65 @@ const jsonSourceResolvers = {
1014
1062
  text: (text) => {
1015
1063
  return {
1016
1064
  text: () => text,
1017
- data: () => JSON.parse(text)
1065
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1066
+ // module is editing files like tsconfig.json, which carry comments.
1067
+ data: () => parse(text)
1018
1068
  };
1019
1069
  },
1020
1070
  filepath: (filepath) => {
1021
1071
  const text = readFileSync(filepath, "utf-8");
1022
1072
  return {
1023
1073
  text: () => text,
1024
- data: () => JSON.parse(text)
1074
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1075
+ // module is editing files like tsconfig.json, which carry comments.
1076
+ data: () => parse(text)
1025
1077
  };
1026
1078
  }
1027
1079
  };
1028
1080
  function resolveJsonSource(source, as) {
1029
1081
  const [sourceType, sourceData] = Object.entries(source).find(([_, selected]) => selected !== void 0) ?? [];
1030
- if (!sourceType || !(sourceType in jsonSourceResolvers) || !sourceData) {
1082
+ if (!sourceType || !(sourceType in jsonSourceResolvers) || sourceData === void 0) {
1031
1083
  throw new Error("Invalid source data");
1032
1084
  }
1033
1085
  const sourceTypeResolvers = jsonSourceResolvers[sourceType](
1034
1086
  sourceData
1035
1087
  );
1036
- const resolved = morph(sourceTypeResolvers, (key, v) => {
1037
- try {
1038
- const value = v();
1039
- return !as || key === as ? [key, value] : [];
1040
- } catch (e) {
1041
- throw new Error(`Failed to resolve ${key} from ${sourceType}`);
1042
- }
1043
- });
1088
+ const resolved = fromEntries(
1089
+ entriesOf(sourceTypeResolvers).flatMap(([key, v]) => {
1090
+ if (as && key !== as) return [];
1091
+ try {
1092
+ return [[key, v()]];
1093
+ } catch (error) {
1094
+ throw new Error(`Failed to resolve ${key} from ${sourceType}`, {
1095
+ cause: error
1096
+ });
1097
+ }
1098
+ })
1099
+ );
1044
1100
  if (as) {
1045
1101
  return resolved[as];
1046
1102
  }
1047
1103
  return resolved;
1048
1104
  }
1049
1105
 
1050
- function resolveEditPath(path, options) {
1051
- const { pathSeparator = "." } = {};
1052
- const arr = toArray(path);
1053
- return arr.map((path2) => {
1054
- if (typeof path2 === "string") {
1055
- return pathSeparator === false ? path2 : path2.split(pathSeparator);
1056
- }
1057
- return path2;
1058
- }).flat();
1059
- }
1060
- function parseJSONCEdits(edits) {
1106
+ const toPathSegment$1 = (segment) => /^\d+$/.test(segment) ? Number(segment) : segment;
1107
+ function resolveEditPath$1(path, options) {
1108
+ const { pathSeparator = "." } = options || {};
1109
+ if (Array.isArray(path)) return path;
1110
+ if (typeof path === "number" || pathSeparator === false) return [path];
1111
+ return path.split(pathSeparator).map(toPathSegment$1);
1112
+ }
1113
+ function parseJSONCEdits(edits, defaultEditOptions) {
1114
+ const resolve = (path, options) => resolveEditPath$1(path, { ...defaultEditOptions, ...options });
1061
1115
  if (Array.isArray(edits)) {
1062
1116
  return edits.map(({ path, ...edit }) => ({
1063
- path: resolveEditPath(path),
1117
+ path: resolve(path, edit.options),
1064
1118
  ...edit
1065
1119
  }));
1066
1120
  }
1067
1121
  return Object.entries(edits).map(
1068
1122
  ([path, value]) => ({
1069
- path: resolveEditPath(path),
1123
+ path: resolve(path, value.options),
1070
1124
  ...value
1071
1125
  })
1072
1126
  );
@@ -1078,14 +1132,17 @@ function modifyJSON({
1078
1132
  }) {
1079
1133
  return checkResult(() => {
1080
1134
  const text = resolveJsonSource(json, "text");
1081
- const jsoncEdits = parseJSONCEdits(edits);
1082
- const editResult = jsoncEdits.flatMap(
1083
- (edit) => modify(text, edit.path, edit.value, {
1084
- ...defaultEditOptions,
1085
- ...edit.options
1086
- })
1135
+ const jsoncEdits = parseJSONCEdits(edits, defaultEditOptions);
1136
+ const updated = jsoncEdits.reduce(
1137
+ (current, edit) => applyEdits(
1138
+ current,
1139
+ modify(current, edit.path, edit.value, {
1140
+ ...defaultEditOptions,
1141
+ ...edit.options
1142
+ })
1143
+ ),
1144
+ text
1087
1145
  );
1088
- const updated = applyEdits(text, editResult);
1089
1146
  return resolveJsonSource({ text: updated });
1090
1147
  });
1091
1148
  }
@@ -1112,6 +1169,154 @@ function modifyJSONFile(filepath, edits, options) {
1112
1169
  });
1113
1170
  }
1114
1171
 
1172
+ const configLanguages = {
1173
+ json: { parse: parseJSONC, stringify: stringifyJSONC, surgical: true },
1174
+ jsonc: { parse: parseJSONC, stringify: stringifyJSONC, surgical: true },
1175
+ json5: { parse: parseJSON5, stringify: stringifyJSON5, surgical: false },
1176
+ yaml: { parse: parseYAML, stringify: stringifyYAML, surgical: false },
1177
+ toml: { parse: parseTOML, stringify: stringifyTOML, surgical: false }
1178
+ };
1179
+ const extensionFormats = {
1180
+ ".json": "json",
1181
+ ".jsonc": "jsonc",
1182
+ ".json5": "json5",
1183
+ ".yaml": "yaml",
1184
+ ".yml": "yaml",
1185
+ ".toml": "toml"
1186
+ };
1187
+ function isConfigFormat(value) {
1188
+ return value in configLanguages;
1189
+ }
1190
+ function getConfigFormat(filepath) {
1191
+ const extension = extname(filepath).toLowerCase();
1192
+ return extension in extensionFormats ? extensionFormats[extension] : void 0;
1193
+ }
1194
+
1195
+ function resolveFormat(source) {
1196
+ if (source.format) return source.format;
1197
+ const inferred = source.filepath ? getConfigFormat(source.filepath) : void 0;
1198
+ if (!inferred) {
1199
+ throw new Error(
1200
+ source.filepath ? `Unsupported config format: ${source.filepath}` : "A `format` is required for a text or data source"
1201
+ );
1202
+ }
1203
+ return inferred;
1204
+ }
1205
+ function resolveConfigSource(source, as) {
1206
+ const format = resolveFormat(source);
1207
+ const language = configLanguages[format];
1208
+ const { text: sourceText, data: sourceData, filepath } = source;
1209
+ const resolvers = {
1210
+ data: () => ({
1211
+ text: () => language.stringify(sourceData),
1212
+ data: () => sourceData
1213
+ }),
1214
+ text: () => ({
1215
+ text: () => sourceText,
1216
+ data: () => language.parse(sourceText)
1217
+ }),
1218
+ filepath: () => {
1219
+ const text = readFileSync(filepath, "utf-8");
1220
+ return {
1221
+ text: () => text,
1222
+ data: () => language.parse(text)
1223
+ };
1224
+ }
1225
+ };
1226
+ const selected = ["data", "text", "filepath"].find(
1227
+ (key) => source[key] !== void 0
1228
+ );
1229
+ if (!selected) {
1230
+ throw new Error("Invalid source data");
1231
+ }
1232
+ const sourceResolvers = resolvers[selected]();
1233
+ const resolved = fromEntries(
1234
+ entriesOf(sourceResolvers).flatMap(([key, value]) => {
1235
+ if (as && key !== as) return [];
1236
+ try {
1237
+ return [[key, value()]];
1238
+ } catch (error) {
1239
+ throw new Error(`Failed to resolve ${key} from ${selected}`, {
1240
+ cause: error
1241
+ });
1242
+ }
1243
+ })
1244
+ );
1245
+ if (as) {
1246
+ return resolved[as];
1247
+ }
1248
+ return resolved;
1249
+ }
1250
+
1251
+ const toPathSegment = (segment) => /^\d+$/.test(segment) ? Number(segment) : segment;
1252
+ function resolveEditPath(path, options) {
1253
+ const { pathSeparator = "." } = options || {};
1254
+ if (Array.isArray(path)) return path;
1255
+ if (typeof path === "number" || pathSeparator === false) return [path];
1256
+ return path.split(pathSeparator).map(toPathSegment);
1257
+ }
1258
+ function toEditList(edits, defaultEditOptions) {
1259
+ const entries = Array.isArray(edits) ? edits : Object.entries(edits).map(([path, edit]) => ({ path, ...edit }));
1260
+ return entries.map(({ path, value, options }) => ({
1261
+ path: resolveEditPath(path, { ...defaultEditOptions, ...options }),
1262
+ value
1263
+ }));
1264
+ }
1265
+ function setPath(target, path, value) {
1266
+ const [head, ...rest] = path;
1267
+ if (head === void 0) return value;
1268
+ const container = Array.isArray(target) ? [...target] : { ...target ?? {} };
1269
+ container[head] = setPath(container[head], rest, value);
1270
+ return container;
1271
+ }
1272
+ function modifyConfig({
1273
+ config,
1274
+ edits,
1275
+ defaultEditOptions
1276
+ }) {
1277
+ return checkResult(() => {
1278
+ const format = config.format ?? (config.filepath ? getConfigFormat(config.filepath) : void 0) ?? "json";
1279
+ const language = configLanguages[format];
1280
+ if (language.surgical) {
1281
+ const { data, error } = modifyJSON({
1282
+ json: config.filepath ? { filepath: config.filepath } : config.text !== void 0 ? { text: config.text } : { data: config.data },
1283
+ edits,
1284
+ defaultEditOptions
1285
+ });
1286
+ if (error) throw error;
1287
+ return data;
1288
+ }
1289
+ const current = resolveConfigSource(config, "data");
1290
+ const updated = toEditList(edits, defaultEditOptions).reduce(
1291
+ (result, edit) => setPath(result, edit.path, edit.value),
1292
+ current
1293
+ );
1294
+ return resolveConfigSource({ text: language.stringify(updated), format });
1295
+ });
1296
+ }
1297
+ function modifyConfigFile(filepath, edits, options) {
1298
+ return checkResult(() => {
1299
+ const { autoCommit = true, defaultEditOptions, format } = options || {};
1300
+ const { data: config, error } = modifyConfig({
1301
+ config: format ? { filepath, format } : { filepath },
1302
+ edits,
1303
+ defaultEditOptions
1304
+ });
1305
+ if (error) {
1306
+ throw error;
1307
+ }
1308
+ const commit = () => writeFileSync(filepath, config.text);
1309
+ if (autoCommit) {
1310
+ commit();
1311
+ return config;
1312
+ }
1313
+ return {
1314
+ config,
1315
+ commit
1316
+ };
1317
+ });
1318
+ }
1319
+
1115
1320
  function isStorageValue(input) {
1116
1321
  return input === null || typeof input === "string" || typeof input === "number" || typeof input === "boolean" || typeof input === "object";
1117
1322
  }
@@ -1120,110 +1325,82 @@ const storage = createStorage({ driver: defineMemoryDriver() });
1120
1325
  const tempFileSystem = defineFileSystemStorage({
1121
1326
  base: join(os.tmpdir(), ".package-manager")
1122
1327
  });
1328
+ const encodeEntry = (value) => typeof value === "string" ? value : JSON.stringify(value);
1123
1329
  function defineFileSystemEntries(definition) {
1124
- const fileSystemEntries = Object.entries(definition).map(([k, v]) => {
1125
- if (v instanceof Function) {
1126
- const {
1127
- file,
1128
- options,
1129
- serialize = (input) => JSON.stringify(input),
1130
- deserialize = (input) => {
1131
- try {
1132
- return JSON.parse(input);
1133
- } catch {
1134
- return input;
1135
- }
1136
- }
1137
- } = v();
1138
- return {
1139
- key: k,
1140
- value: serialize(file),
1141
- options,
1142
- serialize,
1143
- deserialize
1144
- };
1330
+ const fileSystemEntries = Object.entries(definition).map(([key, value]) => {
1331
+ if (typeof value === "function") {
1332
+ const { file, options } = value();
1333
+ return { key, value: encodeEntry(file), options };
1145
1334
  }
1146
- if (isStorageValue(v)) {
1147
- return {
1148
- key: k,
1149
- value: JSON.stringify(v)
1150
- };
1335
+ if (!isStorageValue(value)) {
1336
+ throw new Error(
1337
+ `Cannot store a ${typeof value} at "${key}": file system entries must be a storage value or a function returning one.`
1338
+ );
1151
1339
  }
1152
- return void 0;
1153
- }).filter((v) => v !== void 0);
1340
+ return { key, value: encodeEntry(value) };
1341
+ });
1154
1342
  const resolved = {
1155
1343
  definition,
1156
1344
  fileSystemEntries
1157
1345
  };
1158
1346
  return resolved;
1159
1347
  }
1348
+ const keyToFilePath = (root, key) => join(root, ...key.split(/[:/\\]+/).filter(Boolean));
1160
1349
  function defineFileSystemStorage(options) {
1161
1350
  const { base: root, initial, ...storageOptions } = options;
1162
- let initialFileEntriesData = defineFileSystemEntries(initial ?? {});
1351
+ let entriesData = defineFileSystemEntries(initial ?? {});
1163
1352
  const storage2 = createStorage({
1164
1353
  driver: defineFsLiteDriver({ base: root, ...storageOptions })
1165
1354
  });
1355
+ const getFilePath = (key) => keyToFilePath(root, key);
1356
+ const getFile = async (key) => ({
1357
+ key,
1358
+ filepath: getFilePath(key),
1359
+ data: await storage2.getItem(key),
1360
+ // `getItemRaw` resolves to null for a missing file rather than throwing.
1361
+ read: async () => (await storage2.getItemRaw(key))?.toString()
1362
+ });
1363
+ const removeAllFiles = async () => storage2.clear();
1166
1364
  const fileStorage = {
1167
1365
  createFile: async (key, data) => {
1168
1366
  await storage2.setItem(key, data);
1169
1367
  return {
1170
1368
  key,
1171
- filepath: join(root, key),
1369
+ filepath: getFilePath(key),
1172
1370
  get: async () => storage2.getItem(key),
1173
1371
  update: async (data2) => storage2.setItem(key, data2)
1174
1372
  };
1175
1373
  },
1176
- async getFilePath(key) {
1177
- return (await this.getFile(key)).filepath;
1178
- },
1179
- getFile: async (key) => {
1180
- const storageData = await storage2.getItem(key);
1181
- return {
1182
- key,
1183
- filepath: join(root, key),
1184
- data: storageData,
1185
- read: async () => (await storage2.getItemRaw(key))?.toString()
1186
- };
1187
- },
1188
- async readFile(key) {
1189
- const { read } = await this.getFile(key);
1190
- const content = await read();
1191
- return content;
1192
- },
1193
- snapshotFs: async (base = root) => {
1194
- return snapshot(storage2, base);
1195
- },
1196
- restoreFs: async (snapshot2) => {
1197
- return restoreSnapshot(storage2, snapshot2);
1198
- },
1374
+ getFilePath,
1375
+ getFile,
1376
+ readFile: async (key) => (await getFile(key)).read(),
1377
+ // `base` is a key prefix. Defaulting it to the filesystem root would match
1378
+ // no key at all and silently report an empty filesystem.
1379
+ snapshotFs: async (base = "") => snapshot(storage2, base),
1380
+ restoreFs: async (snapshot2, base) => restoreSnapshot(storage2, snapshot2, base),
1199
1381
  initializeFs: async (override) => {
1200
- if (override) {
1201
- initialFileEntriesData = defineFileSystemEntries(override);
1202
- }
1203
- await storage2.clear();
1204
- return storage2.setItems(initialFileEntriesData.fileSystemEntries);
1382
+ const previousKeys = entriesData.fileSystemEntries.map(({ key }) => key);
1383
+ if (override) entriesData = defineFileSystemEntries(override);
1384
+ await Promise.all(previousKeys.map((key) => storage2.removeItem(key)));
1385
+ await storage2.setItems(entriesData.fileSystemEntries);
1205
1386
  },
1206
1387
  defineFileSystemEntries,
1207
- removeAllFiles: storage2.clear,
1388
+ removeAllFiles,
1208
1389
  async deleteFileSystem() {
1209
- await this.removeAllFiles();
1210
- await storage2.unmount(root, true);
1390
+ await removeAllFiles();
1391
+ await storage2.dispose();
1211
1392
  await rm(root, { recursive: true, force: true });
1212
1393
  },
1213
1394
  storage: storage2,
1214
1395
  meta: {
1215
- fileEntriesData: initialFileEntriesData
1396
+ // A getter, so that `initializeFs(override)` is reflected here rather
1397
+ // than this reporting whatever the definition was at construction.
1398
+ get fileEntriesData() {
1399
+ return entriesData;
1400
+ }
1216
1401
  }
1217
1402
  };
1218
- if (initial) {
1219
- return {
1220
- initialize: async () => {
1221
- await fileStorage.initializeFs(initial);
1222
- return fileStorage;
1223
- }
1224
- };
1225
- }
1226
1403
  return fileStorage;
1227
1404
  }
1228
1405
 
1229
- export { _dirname, _filename, defineFileSystemEntries, defineFileSystemStorage, definePackage, definePackageManagerClient, definePathAliases, detectGlobalPackageManagers, detectLockfilePackageManagers, detectPackageManagers, filterPackageManagers, findDependencyInPackageJson, findPackageManager, findPackageManagerSafely, findResolvedModulePath, getAliasMap, getFolderByPackageName, getGitRootFolder, getGlobalVersions, getPackageFolder, getPath, getWorkspaceFolder, importMap, importer, isDependencyInPackageJson, isPackageDependency, isPackageModuleFound, mapPackageManagers, modifyJSON, modifyJSONFile, packageManagerConfigs, predefinedPathAliases, project, resolveModule, resolveModulePath, resolvePackageModulePath, storage, tempFileSystem, workspace };
1406
+ export { _dirname, _filename, configLanguages, createFile, defineFileSystemEntries, defineFileSystemStorage, definePackage, definePackageManager, definePackageManagerClient, definePathAliases, detectGlobalPackageManagers, detectLockfilePackageManagers, detectPackageManagers, filterPackageManagers, findDependencyInPackageJson, findPackageManager, findPackageManagerSafely, findResolvedModulePath, getAliasMap, getConfigFormat, getFolderByPackageName, getGitRootFolder, getGlobalVersions, getPackageFolder, getPath, getWorkspaceFolder, importMap, importer, isConfigFormat, isDependencyInPackageJson, isPackageDependency, isPackageModuleFound, isWritable, mapPackageManagers, modifyConfig, modifyConfigFile, modifyJSON, modifyJSONFile, packageManagerConfigs, predefinedPathAliases, project, readFile, readFileSafely, resolveConfigSource, resolveModule, resolveModulePath, resolvePackageModulePath, storage, tempFileSystem, workspace };