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.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const ErrorStackParser = require('error-stack-parser');
4
3
  const path = require('pathe');
5
- const process = require('node:process');
6
- const path$1 = require('node:path');
4
+ const util = require('node:util');
7
5
  const node_url = require('node:url');
8
6
  const node_fs = require('node:fs');
9
7
  const asyncCacheFn = require('async-cache-fn');
@@ -11,17 +9,20 @@ const execa = require('execa');
11
9
  const findUp = require('find-up');
12
10
  const promises = require('node:fs/promises');
13
11
  const mlly = require('mlly');
12
+ const WST = require('workspace-tools');
13
+ const process = require('node:process');
14
+ const path$1 = require('node:path');
14
15
  const globby = require('globby');
15
16
  const gitignoreParser = require('parse-gitignore');
16
- const WST = require('workspace-tools');
17
17
  const utils = require('pathe/utils');
18
18
  const os = require('node:os');
19
19
  const jsoncParser = require('jsonc-parser');
20
- const util = require('@arktype/util');
20
+ const confbox = require('confbox');
21
21
  const unstorage = require('unstorage');
22
22
  const defineMemoryDriver = require('unstorage/drivers/memory');
23
23
  const defineFsLiteDriver = require('unstorage/drivers/fs-lite');
24
24
 
25
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
25
26
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
26
27
 
27
28
  function _interopNamespaceCompat(e) {
@@ -36,16 +37,87 @@ function _interopNamespaceCompat(e) {
36
37
  return n;
37
38
  }
38
39
 
39
- const ErrorStackParser__default = /*#__PURE__*/_interopDefaultCompat(ErrorStackParser);
40
- const path__default$1 = /*#__PURE__*/_interopDefaultCompat(path);
40
+ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
41
+ const util__default = /*#__PURE__*/_interopDefaultCompat(util);
42
+ const WST__namespace = /*#__PURE__*/_interopNamespaceCompat(WST);
41
43
  const process__default = /*#__PURE__*/_interopDefaultCompat(process);
42
- const path__default = /*#__PURE__*/_interopDefaultCompat(path$1);
44
+ const path__default$1 = /*#__PURE__*/_interopDefaultCompat(path$1);
43
45
  const gitignoreParser__default = /*#__PURE__*/_interopDefaultCompat(gitignoreParser);
44
- const WST__namespace = /*#__PURE__*/_interopNamespaceCompat(WST);
45
46
  const os__default = /*#__PURE__*/_interopDefaultCompat(os);
46
47
  const defineMemoryDriver__default = /*#__PURE__*/_interopDefaultCompat(defineMemoryDriver);
47
48
  const defineFsLiteDriver__default = /*#__PURE__*/_interopDefaultCompat(defineFsLiteDriver);
48
49
 
50
+ const getCallSites = "getCallSites" in util__default ? util__default.getCallSites : void 0;
51
+ const MAX_FRAMES = 200;
52
+ function resolveCallerFile(options) {
53
+ const { from, boundaryFunctionName, internalScripts = [] } = options ?? {};
54
+ if (from) return toCallerPath(String(from));
55
+ if (getCallSites === void 0) return void 0;
56
+ const sites = getCallSites(MAX_FRAMES, { sourceMap: true });
57
+ const scriptName = boundaryFunctionName && frameAfterFunction(sites, boundaryFunctionName) || firstForeignFrame(sites, [OWN_SCRIPT, ...internalScripts]);
58
+ return scriptName ? toFilePath(scriptName) : void 0;
59
+ }
60
+ const OWN_SCRIPT = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href));
61
+ function frameAfterFunction(sites, functionName) {
62
+ const boundary = sites.findIndex((site) => site.functionName === functionName);
63
+ return boundary === -1 ? void 0 : sites[boundary + 1]?.scriptName;
64
+ }
65
+ function firstForeignFrame(sites, internalScripts) {
66
+ const internal = new Set(internalScripts.map(toFilePath));
67
+ return sites.find(
68
+ (site) => (
69
+ // Frames from `node:` internals and evaluated code name no file, so they
70
+ // are never the caller's location however far out they appear.
71
+ isFileScript(site.scriptName) && !internal.has(toFilePath(site.scriptName))
72
+ )
73
+ )?.scriptName;
74
+ }
75
+ const isFileScript = (script) => Boolean(script) && (script.startsWith("file:") || path.isAbsolute(script));
76
+ const toFilePath = (script) => script.startsWith("file:") ? node_url.fileURLToPath(script) : script;
77
+ function toCallerPath(from) {
78
+ if (from.startsWith("file:")) return node_url.fileURLToPath(from);
79
+ if (path.isAbsolute(from)) return from;
80
+ throw new Error(
81
+ `\`from\` must be a file: URL or an absolute path, received ${JSON.stringify(from)}. Pass \`import.meta.url\`.`
82
+ );
83
+ }
84
+
85
+ const _filename = (options) => resolveCallerFile(withOwnScript(options));
86
+ const _dirname = (options) => {
87
+ const filePath = resolveCallerFile(withOwnScript(options));
88
+ return filePath === void 0 ? void 0 : path.dirname(filePath);
89
+ };
90
+ const withOwnScript = (options) => ({
91
+ ...options,
92
+ internalScripts: [...options?.internalScripts ?? [], (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))]
93
+ });
94
+
95
+ function createFile(filePath, data, options) {
96
+ const { encoding = "utf-8", ...rest } = typeof options === "string" ? { encoding: options } : options ?? {};
97
+ const dir = path__default.dirname(filePath);
98
+ const writeFileOptions = { encoding, ...rest };
99
+ if (!node_fs.existsSync(dir)) {
100
+ node_fs.mkdirSync(dir, { recursive: true });
101
+ }
102
+ node_fs.writeFileSync(filePath, data, writeFileOptions);
103
+ }
104
+
105
+ function readFile(filePath, options) {
106
+ return node_fs.readFileSync(filePath, options?.encoding ?? "utf-8");
107
+ }
108
+ function readFileSafely(filePath, options) {
109
+ return node_fs.existsSync(filePath) ? readFile(filePath, options) : void 0;
110
+ }
111
+
112
+ function isWritable(filename) {
113
+ try {
114
+ node_fs.accessSync(filename, node_fs.constants.W_OK);
115
+ return true;
116
+ } catch (e) {
117
+ return false;
118
+ }
119
+ }
120
+
49
121
  const toArray = (data) => Array.isArray(data) ? data : [data];
50
122
  const entriesOf = (o) => Object.entries(o);
51
123
  const fromEntries = (entries) => Object.fromEntries(entries);
@@ -59,7 +131,7 @@ const select = (obj, selection, mode) => {
59
131
  };
60
132
  function normalizePath(path$1, option) {
61
133
  if (typeof option === "function") {
62
- return normalizePath(path$1);
134
+ return option(path$1);
63
135
  }
64
136
  if (option === false) {
65
137
  return path$1;
@@ -71,14 +143,6 @@ const invariant = (predicate, message) => {
71
143
  throw new Error(message);
72
144
  }
73
145
  };
74
- function getArrayItemAtOffset(arr, index, offset = 0) {
75
- if (arr === void 0 || index === void 0) return void 0;
76
- return arr[index + offset];
77
- }
78
- function isMatching(a, b) {
79
- if (!a || !b) return false;
80
- return a === b;
81
- }
82
146
  function checkResult(cb, options) {
83
147
  try {
84
148
  return buildSingleProp({
@@ -103,123 +167,16 @@ function buildSingleProp(entry) {
103
167
  return { [key]: value };
104
168
  }
105
169
 
106
- function findCallerStackFrame(options) {
107
- return findErrorStackFrame(options, (frame) => frame.isParentOfRootFunction);
108
- }
109
- function findErrorStackFrame(options, find) {
110
- return getErrorStackFrames(options, find)[0];
111
- }
112
- function getErrorStackFrames(options, filter) {
113
- const { error, rootFunctionName, startFrom = "top" } = options;
114
- const frames = startFrom === "bottom" ? ErrorStackParser__default.parse(error).reverse() : ErrorStackParser__default.parse(error);
115
- const { parsedFrames } = frames.reduce(
116
- (acc, frame, index) => {
117
- const parsed = parseFrame({
118
- frame,
119
- index,
120
- rootFunctionName,
121
- frames
122
- });
123
- const isValid = filter ? filter(parsed) : true;
124
- if (isValid) {
125
- acc.parsedFrames.push(parsed);
126
- }
127
- return acc;
128
- },
129
- { parsedFrames: [] }
130
- );
131
- return parsedFrames ?? [];
132
- }
133
- function parseFrame(options) {
134
- const { frame, frames, index, rootFunctionName, cwd, debug } = options ?? {};
135
- const functionName = parseFunctionName(frame.functionName);
136
- const isRootFunction = isMatching(functionName, rootFunctionName);
137
- const beforeFrameFunctionName = parseFunctionName(
138
- getArrayItemAtOffset(frames, index, -1)?.functionName
139
- );
140
- const isParentOfRootFunction = isMatching(
141
- beforeFrameFunctionName,
142
- rootFunctionName
143
- );
144
- const fileData = frame.fileName ? getFilePathData({ filepath: frame.fileName, cwd }) : void 0;
145
- const { isFileInCwd: isFrameInScope, ...restFileData } = fileData ?? {};
146
- const data = {
147
- ...restFileData,
148
- functionName,
149
- source: frame.source,
150
- sourceFunctionName: frame.functionName,
151
- isFrameInScope,
152
- place: placeFormatter(index, frames?.length),
153
- isRootFunction,
154
- isParentOfRootFunction,
155
- rootFunctionName
156
- };
157
- debug && console.log(data);
158
- return data;
159
- }
160
- function getFilePathData({
161
- filepath,
162
- cwd
163
- }) {
164
- const workingDir = cwd ?? process__default.cwd();
165
- const filePath = path.normalize(toFilePath(filepath));
166
- const fileBasename = path.basename(filePath);
167
- const dirPath = path.dirname(filePath);
168
- const dirBasename = path.basename(dirPath);
169
- const relativeFilePath = path.relative(workingDir, filePath);
170
- const relativeDirPath = path.dirname(relativeFilePath);
171
- const isFileInCwd = filePath.startsWith(workingDir);
172
- return {
173
- filePath,
174
- dirPath,
175
- relativeFilePath,
176
- relativeDirPath,
177
- fileBasename,
178
- dirBasename,
179
- isFileInCwd
180
- };
181
- }
182
- function toFilePath(fileName) {
183
- return fileName.startsWith("file:") ? node_url.fileURLToPath(fileName) : fileName;
184
- }
185
- function parseFunctionName(functionName) {
186
- if (!functionName) return void 0;
187
- return removeStart(functionName, "Module.");
188
- }
189
- function removeStart(input, value) {
190
- return input.startsWith(value) ? input.slice(value.length) : input;
191
- }
192
- function placeFormatter(index, total) {
193
- if (index === void 0 || !total) return void 0;
194
- return [index, total].join("/");
195
- }
196
-
197
- const _filename = (options) => {
198
- const { filePath } = findCallerStackFrame({ error: new Error(), ...options }) ?? {};
199
- return filePath;
200
- };
201
- const _dirname = (options) => {
202
- const { dirPath } = findCallerStackFrame({ error: new Error(), ...options }) ?? {};
203
- return dirPath;
204
- };
205
-
206
170
  async function resolveModule(module) {
207
171
  const resolved = await module;
208
- return resolved.default || resolved;
172
+ const hasDefault = resolved !== null && typeof resolved === "object" && "default" in resolved;
173
+ return hasDefault ? resolved.default : resolved;
209
174
  }
210
175
  function isPackageModuleFound(name, options) {
211
176
  return Boolean(resolvePackageModulePath(name, options));
212
177
  }
213
178
  function resolvePackageModulePath(name, options) {
214
- const resolvedPath = findResolvedModulePath(
215
- [`${name}/package.json`, name],
216
- options
217
- );
218
- if (resolvedPath === void 0) {
219
- console.error(`Could not resolve package ${name}`);
220
- return void 0;
221
- }
222
- return resolvedPath;
179
+ return findResolvedModulePath([`${name}/package.json`, name], options);
223
180
  }
224
181
  function findResolvedModulePath(paths, options) {
225
182
  for (const path of paths) {
@@ -241,28 +198,68 @@ function resolveModulePath(modulePath, options) {
241
198
  }
242
199
  }
243
200
 
201
+ function getPackageFolder(options) {
202
+ return WST__namespace.findPackageRoot(options?.cwd ?? process__default.cwd());
203
+ }
204
+
205
+ function getPackageInfo(options) {
206
+ const packageDir = getPackageFolder(options);
207
+ if (!packageDir) {
208
+ throw new Error(
209
+ `No package.json found searching up from "${options?.cwd ?? process__default.cwd()}"`
210
+ );
211
+ }
212
+ return readPackageInfo({ packageDir });
213
+ }
214
+ function readPackageInfo({
215
+ packageDir
216
+ }) {
217
+ const packageJsonPath = path__default$1.join(packageDir, "package.json");
218
+ const packageJson = readPackageJson(packageJsonPath);
219
+ return {
220
+ name: packageJson.name,
221
+ path: packageJsonPath,
222
+ dirpath: packageDir,
223
+ packageJson
224
+ };
225
+ }
226
+ const packageJsonCache = /* @__PURE__ */ new Map();
227
+ function readPackageJson(packageJsonPath) {
228
+ const { mtimeMs } = node_fs.statSync(packageJsonPath);
229
+ const cached = packageJsonCache.get(packageJsonPath);
230
+ if (cached?.mtimeMs === mtimeMs) return cached.packageJson;
231
+ const packageJson = JSON.parse(
232
+ node_fs.readFileSync(packageJsonPath, "utf-8")
233
+ );
234
+ packageJsonCache.set(packageJsonPath, { mtimeMs, packageJson });
235
+ return packageJson;
236
+ }
237
+
244
238
  const tsconfig = (tsconfigDir) => ({
245
239
  get paths() {
240
+ if (!tsconfigDir) return [];
246
241
  return globby.globbySync(["tsconfig.json", "tsconfig.*.json"], {
247
- cwd: tsconfigDir
248
- }) ?? [];
242
+ cwd: tsconfigDir,
243
+ absolute: true
244
+ });
249
245
  }
250
246
  });
251
247
 
252
- function parseGitignoreFile(gitignorePath, options) {
253
- return gitignoreParser__default(gitignorePath, options);
248
+ function parseGitignoreContent(gitignoreContent, options) {
249
+ return gitignoreParser__default(gitignoreContent, options);
254
250
  }
255
- function getGitignoreData(gitignorePath) {
256
- const gitignoreFileContent = node_fs.readFileSync(gitignorePath, "utf-8");
257
- const gitignores = parseGitignoreFile(gitignoreFileContent);
258
- return gitignores;
251
+ function getGitignoreData(gitignorePath, options) {
252
+ if (!gitignorePath || !node_fs.existsSync(gitignorePath)) {
253
+ return parseGitignoreContent("", options);
254
+ }
255
+ return parseGitignoreContent(node_fs.readFileSync(gitignorePath, "utf-8"), options);
259
256
  }
260
- const gitignore = (gitignorePath) => ({
257
+ const gitignore = (gitignorePath, options) => ({
261
258
  get data() {
262
- return getGitignoreData(gitignorePath);
259
+ return getGitignoreData(gitignorePath, options);
263
260
  },
264
261
  get patterns() {
265
- return getGitignoreData(gitignorePath).patterns;
262
+ return getGitignoreData(gitignorePath, options).patterns;
266
263
  }
267
264
  });
268
265
 
@@ -325,36 +322,13 @@ function getWorkspaceFolder(options) {
325
322
  throwIfNotFound = true
326
323
  } = options ?? {};
327
324
  const getFolder = fallbackToGitRoot ? WST__namespace.findProjectRoot : WST__namespace.getWorkspaceManagerRoot;
328
- const folder = getFolder(cwd);
325
+ const folder = checkResult(() => getFolder(cwd)).data;
329
326
  if (folder === void 0 && throwIfNotFound) {
330
327
  throw new Error(`Could not find workspace folder`);
331
328
  }
332
329
  return folder;
333
330
  }
334
331
 
335
- function getPackageFolder(options) {
336
- return WST__namespace.findPackageRoot(options?.cwd ?? process__default.cwd());
337
- }
338
-
339
- function getPackageInfo(options) {
340
- const packageDir = getPackageFolder(options);
341
- return readPackageInfo({ packageDir });
342
- }
343
- function readPackageInfo({
344
- packageDir
345
- }) {
346
- const packageJsonPath = path__default.join(packageDir, "package.json");
347
- const packageJson = JSON.parse(
348
- node_fs.readFileSync(packageJsonPath, "utf-8")
349
- );
350
- return {
351
- name: packageJson.name,
352
- path: packageJsonPath,
353
- dirpath: packageDir,
354
- packageJson
355
- };
356
- }
357
-
358
332
  function getWorkspaceProjectInfo(options) {
359
333
  try {
360
334
  const workspaceDir = getWorkspaceFolder(options);
@@ -370,13 +344,21 @@ function getWorkspacePackageInfoList(options) {
370
344
  const wstList = WST__namespace.getWorkspaceInfos(workspaceDir);
371
345
  return resolveWstList(wstList);
372
346
  }
347
+ function toPackageInfo(info) {
348
+ return {
349
+ name: info.name,
350
+ dirpath: info.path,
351
+ path: path.join(info.path, "package.json"),
352
+ packageJson: info.packageJson
353
+ };
354
+ }
373
355
  function common(options) {
374
356
  const { cwd, includeRoot: includeWorkspace } = options ?? {};
375
357
  const workspaceDir = getWorkspaceFolder({ cwd });
376
358
  function resolveWstList(list) {
377
359
  if (!workspaceDir || !list) return [];
378
- if (includeWorkspace) return [getWorkspaceProjectInfo(), ...list];
379
- return list;
360
+ const packages = list.map(toPackageInfo);
361
+ return includeWorkspace ? [getWorkspaceProjectInfo({ cwd }), ...packages] : packages;
380
362
  }
381
363
  return {
382
364
  workspaceDir,
@@ -393,7 +375,7 @@ function getWorkspacePackageInfoMap(options) {
393
375
 
394
376
  function getProjectInfoByName(name, options) {
395
377
  const { cwd } = options ?? {};
396
- return getWorkspacePackageInfoMap({ cwd })[name];
378
+ return getWorkspacePackageInfoMap({ cwd, includeRoot: true })[name];
397
379
  }
398
380
 
399
381
  function getGitRootFolder(options) {
@@ -408,7 +390,7 @@ function getGitRootFolder(options) {
408
390
  }
409
391
  return void 0;
410
392
  }
411
- return path__default$1.normalize(stdout);
393
+ return path__default.normalize(stdout);
412
394
  }
413
395
 
414
396
  function getProjectInfo(folder, options) {
@@ -439,9 +421,14 @@ function parseWorkspaceFolderTypeOptions(option, defaultOptions) {
439
421
 
440
422
  const project = (...args) => {
441
423
  const [source, projectOptions] = args;
442
- const { packageJson, packageJsonPath, packageName, projectDir } = info();
443
424
  const {
444
- findPackageManager: findPackageManager2,
425
+ packageJson,
426
+ name: packageName,
427
+ dirpath: projectDir,
428
+ path: packageJsonPath
429
+ } = getProjectInfo(source, projectOptions) ?? {};
430
+ const {
431
+ findPackageManager,
445
432
  detectPackageManagers,
446
433
  detectLockfilePackageManagers,
447
434
  detectGlobalPackageManagers,
@@ -449,41 +436,29 @@ const project = (...args) => {
449
436
  filterPackageManagers,
450
437
  mapPackageManagers
451
438
  } = definePackageManagerClient({ cwd: projectDir });
439
+ const getPackageJson = () => projectDir ? readPackageInfo({ packageDir: projectDir }).packageJson : packageJson;
452
440
  return {
453
441
  packageJson,
454
442
  packageJsonPath,
455
443
  packageName,
456
444
  projectDir,
457
- findPackageManager: findPackageManager2,
445
+ findPackageManager,
458
446
  detectPackageManagers,
459
447
  detectGlobalPackageManagers,
460
448
  detectLockfilePackageManagers,
461
449
  globalVersions,
462
450
  mapPackageManagers,
463
- tsconfig: tsconfig(projectDir ?? ""),
464
- gitignore: gitignore(path__default$1.join(projectDir ?? "", ".gitignore")),
451
+ // Passed through as-is: substituting "" for an unresolved project made
452
+ // both of these read from the calling process's directory instead.
453
+ tsconfig: tsconfig(projectDir),
454
+ gitignore: gitignore(
455
+ projectDir ? path__default.join(projectDir, ".gitignore") : void 0
456
+ ),
465
457
  filterPackageManagers,
466
- getPackageJson: () => get("packageJson"),
467
- findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, get("packageJson")),
468
- isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, get("packageJson"))
458
+ getPackageJson,
459
+ findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, getPackageJson()),
460
+ isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, getPackageJson())
469
461
  };
470
- function info() {
471
- const {
472
- packageJson: packageJson2,
473
- name: packageName2,
474
- dirpath: projectDir2,
475
- path: packageJsonPath2
476
- } = getProjectInfo(source, projectOptions) ?? {};
477
- return {
478
- packageJson: packageJson2,
479
- packageJsonPath: packageJsonPath2,
480
- packageName: packageName2,
481
- projectDir: projectDir2
482
- };
483
- }
484
- function get(key) {
485
- return info()[key];
486
- }
487
462
  };
488
463
 
489
464
  function getWorkspacePackageNames(options) {
@@ -498,18 +473,24 @@ const workspace = {
498
473
  getProject: project
499
474
  };
500
475
 
501
- function importer(imports, options) {
476
+ async function importer(imports, options) {
502
477
  const { install: defaultInstall = true, installer } = options ?? {};
478
+ const resolved = imports.map((option) => resolveImportOption(option));
479
+ const missing = resolved.filter(
480
+ (option) => Boolean(option.name) && (option.install ?? defaultInstall) && !((option.checkExists ?? true) && isSatisfied(option.name))
481
+ );
482
+ await installMissing(
483
+ missing.filter(({ dev }) => !dev).map(({ name }) => name),
484
+ { dev: false },
485
+ installer
486
+ );
487
+ await installMissing(
488
+ missing.filter(({ dev }) => dev).map(({ name }) => name),
489
+ { dev: true },
490
+ installer
491
+ );
503
492
  return Promise.all(
504
- imports.map(async (e) => {
505
- const importOpt = resolveImportOption(e);
506
- const shouldInstall = importOpt.install ?? defaultInstall;
507
- if (shouldInstall && importOpt.name) {
508
- await installImport(importOpt.name, importOpt, installer);
509
- }
510
- const m = await importOpt.import();
511
- return resolveModule(m);
512
- })
493
+ resolved.map(async (option) => resolveModule(await option.import()))
513
494
  );
514
495
  }
515
496
  const definePackage = (option) => {
@@ -533,12 +514,11 @@ function resolveImportOption(option) {
533
514
  }
534
515
  return option;
535
516
  }
536
- async function installImport(packageName, options, installer) {
537
- if (!packageName) return;
538
- const { checkExists } = options ?? {};
539
- if (checkExists && isPackageDependency(packageName)) return;
517
+ const isSatisfied = (packageName) => isPackageDependency(packageName) && isPackageModuleFound(packageName);
518
+ async function installMissing(packageNames, options, installer) {
519
+ if (packageNames.length === 0) return;
540
520
  const installerFn = installer ?? (await workspace.getProject("<package_folder>").findPackageManager()).installPackage;
541
- await installerFn(packageName, options);
521
+ await installerFn(packageNames, options);
542
522
  }
543
523
 
544
524
  const importMap = async (importMap2, options) => {
@@ -560,20 +540,36 @@ function definePackageManager(config, options) {
560
540
  const lockfiles = toArray(config.meta.lockfile);
561
541
  return await findUp.findUp(lockfiles, { cwd });
562
542
  });
543
+ const globalVersion = asyncCacheFn.asyncCacheFn(
544
+ async (options2) => {
545
+ try {
546
+ const { stdout } = await $$({
547
+ command,
548
+ args: [agentOptions.version],
549
+ cwd: defaultCwd,
550
+ ...options2
551
+ });
552
+ return `${stdout}`.trim();
553
+ } catch {
554
+ return void 0;
555
+ }
556
+ }
557
+ );
563
558
  const installPackage = async (packageName, options2) => {
564
559
  const install = agentArgs.install;
565
- const { dev, preferOffline } = select(
566
- install.options,
567
- {
568
- preferOffline: true,
569
- cwd: defaultCwd,
570
- ...options2
571
- });
560
+ const { dev = false, preferOffline = true } = options2 ?? {};
561
+ const flags = select(install.options, { dev, preferOffline });
572
562
  const packageNames = toArray(packageName);
563
+ assertInstallableNames(packageNames);
573
564
  try {
574
565
  await $$({
575
566
  command,
576
- args: [install.command, dev, preferOffline, ...packageNames],
567
+ args: [
568
+ install.command,
569
+ flags.dev,
570
+ flags.preferOffline,
571
+ ...packageNames
572
+ ],
577
573
  cwd: defaultCwd,
578
574
  ...options2
579
575
  });
@@ -617,19 +613,12 @@ function definePackageManager(config, options) {
617
613
  if (!lockfilePath) return void 0;
618
614
  return promises.readFile(lockfilePath, "utf8");
619
615
  }),
620
- globalVersion: asyncCacheFn.asyncCacheFn(async (...args) => {
621
- const [options2] = args;
622
- try {
623
- const { stdout } = await $$({
624
- command,
625
- args: [agentOptions.version],
626
- cwd: defaultCwd,
627
- ...options2
628
- });
629
- return `${stdout}`;
630
- } catch (e) {
631
- return void 0;
632
- }
616
+ globalVersion,
617
+ matchesVersion: asyncCacheFn.asyncCacheFn(async (...args) => {
618
+ const { matchesVersion } = config.meta;
619
+ if (!matchesVersion) return true;
620
+ const version = await globalVersion.noCache(...args);
621
+ return version ? matchesVersion(version) : false;
633
622
  }),
634
623
  definePackage,
635
624
  installPackage,
@@ -643,6 +632,16 @@ function definePackageManager(config, options) {
643
632
  }
644
633
  };
645
634
  }
635
+ function assertInstallableNames(packageNames) {
636
+ const rejected = packageNames.filter(
637
+ (name) => name.length === 0 || name.startsWith("-")
638
+ );
639
+ if (rejected.length > 0) {
640
+ throw new Error(
641
+ `Not a package name: ${rejected.map((name) => JSON.stringify(name)).join(", ")}. A package name cannot be empty or begin with "-".`
642
+ );
643
+ }
644
+ }
646
645
  async function $$(options) {
647
646
  const { command, args = [], silent = true, cwd, shellOptions } = options;
648
647
  return execa.execa(command, args.filter(notFalsy), {
@@ -665,7 +664,9 @@ const bun = definePackageManagerConfig({
665
664
  command: "bun",
666
665
  runner: "bunx",
667
666
  meta: {
668
- lockfile: "bun.lockb"
667
+ // Bun wrote a binary lockfile originally and a text one from 1.2 onwards,
668
+ // so a project using either must still be detected.
669
+ lockfile: ["bun.lock", "bun.lockb"]
669
670
  },
670
671
  args: {
671
672
  install: {
@@ -676,7 +677,7 @@ const bun = definePackageManagerConfig({
676
677
  }
677
678
  },
678
679
  uninstall: {
679
- command: "uninstall"
680
+ command: "remove"
680
681
  }
681
682
  },
682
683
  options: {
@@ -740,7 +741,9 @@ const yarn = definePackageManagerConfig({
740
741
  command: "yarn",
741
742
  runner: "yarn dlx",
742
743
  meta: {
743
- lockfile: "yarn.lock"
744
+ lockfile: "yarn.lock",
745
+ // Berry shares this command and lockfile, so the version decides.
746
+ matchesVersion: (version) => version.startsWith("1.")
744
747
  },
745
748
  args: {
746
749
  install: {
@@ -759,6 +762,33 @@ const yarn = definePackageManagerConfig({
759
762
  }
760
763
  });
761
764
 
765
+ const yarnBerry = definePackageManagerConfig({
766
+ id: "yarn-berry",
767
+ name: "Yarn Berry",
768
+ command: "yarn",
769
+ runner: "yarn dlx",
770
+ meta: {
771
+ lockfile: "yarn.lock",
772
+ // Classic shares this command and lockfile; everything past 1.x is Berry.
773
+ matchesVersion: (version) => !version.startsWith("1.")
774
+ },
775
+ args: {
776
+ install: {
777
+ command: "add",
778
+ options: {
779
+ dev: "-D",
780
+ preferOffline: "--cached"
781
+ }
782
+ },
783
+ uninstall: {
784
+ command: "remove"
785
+ }
786
+ },
787
+ options: {
788
+ version: "--version"
789
+ }
790
+ });
791
+
762
792
  async function findPackageManager(packageManagers, options) {
763
793
  const packageManager = await findPackageManagerSafely(
764
794
  packageManagers,
@@ -788,7 +818,9 @@ async function detectPackageManagers(packageManagers, options) {
788
818
  async function detectLockfilePackageManagers(packageManagers, options) {
789
819
  return filterPackageManagers(
790
820
  packageManagers,
791
- (packageManager) => packageManager.hasLockfile(options),
821
+ // Yarn Classic and Berry share a lockfile name, so a lockfile match alone
822
+ // would report both. The version check settles which one it is.
823
+ async (packageManager) => await packageManager.hasLockfile(options) && await packageManager.matchesVersion(options),
792
824
  options
793
825
  );
794
826
  }
@@ -798,7 +830,7 @@ async function detectGlobalPackageManagers(packageManagers, options) {
798
830
  // Without the `await`, `Boolean` receives a pending promise and is always
799
831
  // `true`, so every package manager passes the filter regardless of whether
800
832
  // it is actually installed.
801
- async (packageManager) => Boolean(await packageManager.globalVersion(options)),
833
+ async (packageManager) => Boolean(await packageManager.globalVersion(options)) && await packageManager.matchesVersion(options),
802
834
  options
803
835
  );
804
836
  }
@@ -831,7 +863,7 @@ function selectAllowedPackageManagers(packageManagers, options) {
831
863
  );
832
864
  }
833
865
 
834
- const packageManagerConfigs = [pnpm, yarn, bun, npm];
866
+ const packageManagerConfigs = [pnpm, yarn, yarnBerry, bun, npm];
835
867
  function definePackageManagerClient(options) {
836
868
  const configs = packageManagerConfigs.map(
837
869
  (config) => definePackageManager(config, options)
@@ -861,8 +893,11 @@ function definePackageManagerClient(options) {
861
893
  };
862
894
  }
863
895
 
864
- function isPackageDependency(packageName) {
865
- return toArray(packageName).every((name) => isDependencyInPackageJson(name));
896
+ function isPackageDependency(packageName, options) {
897
+ const { packageJson } = getPackageInfo(options);
898
+ return toArray(packageName).every(
899
+ (name) => isDependencyInPackageJson(name, packageJson)
900
+ );
866
901
  }
867
902
 
868
903
  function definePathAliases(aliasDefinitions) {
@@ -880,42 +915,49 @@ function definePathAliases(aliasDefinitions) {
880
915
  };
881
916
  }
882
917
  function getAliasedFilePath(aliasMap, options) {
883
- const { to, startingFrom, cwd, checkExistence, glob } = typeof options === "string" || Array.isArray(options) ? { to: options } : options;
884
- const resolveOptions = { cwd, checkExistence, glob, aliasMap };
918
+ const { to, startingFrom, cwd, from, checkExistence, glob } = typeof options === "string" || Array.isArray(options) ? { to: options } : options;
919
+ const resolveOptions = { cwd, from, checkExistence, glob, aliasMap };
885
920
  try {
886
921
  return startingFrom ? resolveRelativePathTo(to, startingFrom, resolveOptions) : resolvePathTo(to, resolveOptions);
887
922
  } catch (error) {
888
- if (checkExistence || glob) return void 0;
923
+ if (error instanceof PathNotFoundError) return void 0;
889
924
  throw error;
890
925
  }
891
926
  }
927
+ class PathNotFoundError extends Error {
928
+ }
892
929
  function resolveRelativePathTo(to, from, options) {
893
930
  const pathFrom = resolvePathTo(from, options);
894
931
  const pathTo = resolvePathTo(to, options);
895
- return path__default$1.relative(pathFrom, pathTo);
932
+ return path__default.relative(pathFrom, pathTo);
896
933
  }
897
- function resolvePathTo(pathTo, { cwd, checkExistence, glob, aliasMap }) {
898
- const normalized = normalizePathTo(pathTo, { cwd, aliasMap });
934
+ function resolvePathTo(pathTo, { cwd, from, checkExistence, glob, aliasMap }) {
935
+ const normalized = normalizePathTo(pathTo, { cwd, from, aliasMap });
899
936
  if (glob) {
900
937
  const globPaths = globby.globbySync(normalized, { cwd });
901
938
  if (!globPaths[0])
902
- throw new Error(`No paths found for glob: ${normalized}`);
939
+ throw new PathNotFoundError(`No paths found for glob: ${normalized}`);
903
940
  return globPaths[0];
904
941
  }
905
942
  if (!glob && checkExistence && !node_fs.existsSync(normalized))
906
- throw new Error(`Path does not exist: ${normalized}`);
943
+ throw new PathNotFoundError(`Path does not exist: ${normalized}`);
907
944
  return normalized;
908
945
  }
909
946
  function normalizePathTo(pathTo, options) {
910
- const { cwd, aliasMap } = options ?? {};
911
- const aliasedPath = Array.isArray(pathTo) ? path__default$1.join(...pathTo.filter(isNotNull)) : pathTo;
912
- const baseDir = Array.isArray(pathTo) ? pathTo[0] : findAliasToken(aliasedPath, aliasMap);
913
- if (baseDir === void 0) return aliasedPath;
914
- const baseDirPath = executeMapFn(aliasMap, baseDir, [{ cwd }]);
915
- if (typeof baseDirPath !== "string" || baseDirPath.length === 0) {
916
- throw new Error(`Path alias resolved to no location: ${baseDir}`);
947
+ const { cwd, from, aliasMap } = options ?? {};
948
+ const baseDir = Array.isArray(pathTo) ? pathTo[0] : findAliasToken(pathTo, aliasMap);
949
+ if (baseDir === void 0) return pathTo;
950
+ const baseDirPath = assertResolved(
951
+ baseDir,
952
+ executeMapFn(aliasMap, baseDir, [{ cwd, from }])
953
+ );
954
+ return Array.isArray(pathTo) ? path__default.join(baseDirPath, ...pathTo.slice(1).filter(isNotNull)) : utils.resolveAlias(pathTo, { [baseDir]: baseDirPath });
955
+ }
956
+ function assertResolved(alias, resolved) {
957
+ if (typeof resolved !== "string" || resolved.length === 0) {
958
+ throw new Error(`Path alias resolved to no location: ${alias}`);
917
959
  }
918
- return utils.resolveAlias(aliasedPath, { [baseDir]: baseDirPath });
960
+ return resolved;
919
961
  }
920
962
  function findAliasToken(pathTo, aliasMap) {
921
963
  return Object.keys(aliasMap ?? {}).filter((alias) => pathTo === alias || pathTo.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length)[0];
@@ -933,10 +975,17 @@ function getAliasMap(aliasDefs) {
933
975
  return [
934
976
  [alias, resolve],
935
977
  ...subpaths.map(({ to }) => {
936
- const subpathAlias = path__default$1.join(alias, to);
978
+ const subpathAlias = path__default.join(alias, to);
937
979
  return [
938
980
  subpathAlias,
939
- (opts) => utils.resolveAlias(subpathAlias, { [alias]: resolve(opts) })
981
+ (opts) => (
982
+ // The same assertion the bare alias gets. Without it an
983
+ // unresolvable parent produced `join(undefined, "/node_modules")`
984
+ // — a path at the filesystem root — instead of failing.
985
+ utils.resolveAlias(subpathAlias, {
986
+ [alias]: assertResolved(alias, resolve(opts))
987
+ })
988
+ )
940
989
  ];
941
990
  })
942
991
  ];
@@ -1011,13 +1060,13 @@ const predefinedPathAliases = {
1011
1060
  subpaths: []
1012
1061
  },
1013
1062
  "<current_file>": {
1014
- // resolve: () => fileURLToPath(import.meta.url),
1015
- resolve: () => _filename({ rootFunctionName: "getFilePath" }) ?? "",
1063
+ // `from` is the caller naming itself, which is exact. Without it the stack
1064
+ // is read, which is a best effort and Node-only.
1065
+ resolve: (opts) => _filename({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
1016
1066
  subpaths: []
1017
1067
  },
1018
1068
  "<current_folder>": {
1019
- // resolve: () => fileURLToPath(import.meta.url),
1020
- resolve: () => _dirname({ rootFunctionName: "getFilePath" }) ?? "",
1069
+ resolve: (opts) => _dirname({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
1021
1070
  subpaths: []
1022
1071
  }
1023
1072
  };
@@ -1040,59 +1089,65 @@ const jsonSourceResolvers = {
1040
1089
  text: (text) => {
1041
1090
  return {
1042
1091
  text: () => text,
1043
- data: () => JSON.parse(text)
1092
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1093
+ // module is editing files like tsconfig.json, which carry comments.
1094
+ data: () => jsoncParser.parse(text)
1044
1095
  };
1045
1096
  },
1046
1097
  filepath: (filepath) => {
1047
1098
  const text = node_fs.readFileSync(filepath, "utf-8");
1048
1099
  return {
1049
1100
  text: () => text,
1050
- data: () => JSON.parse(text)
1101
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1102
+ // module is editing files like tsconfig.json, which carry comments.
1103
+ data: () => jsoncParser.parse(text)
1051
1104
  };
1052
1105
  }
1053
1106
  };
1054
1107
  function resolveJsonSource(source, as) {
1055
1108
  const [sourceType, sourceData] = Object.entries(source).find(([_, selected]) => selected !== void 0) ?? [];
1056
- if (!sourceType || !(sourceType in jsonSourceResolvers) || !sourceData) {
1109
+ if (!sourceType || !(sourceType in jsonSourceResolvers) || sourceData === void 0) {
1057
1110
  throw new Error("Invalid source data");
1058
1111
  }
1059
1112
  const sourceTypeResolvers = jsonSourceResolvers[sourceType](
1060
1113
  sourceData
1061
1114
  );
1062
- const resolved = util.morph(sourceTypeResolvers, (key, v) => {
1063
- try {
1064
- const value = v();
1065
- return !as || key === as ? [key, value] : [];
1066
- } catch (e) {
1067
- throw new Error(`Failed to resolve ${key} from ${sourceType}`);
1068
- }
1069
- });
1115
+ const resolved = fromEntries(
1116
+ entriesOf(sourceTypeResolvers).flatMap(([key, v]) => {
1117
+ if (as && key !== as) return [];
1118
+ try {
1119
+ return [[key, v()]];
1120
+ } catch (error) {
1121
+ throw new Error(`Failed to resolve ${key} from ${sourceType}`, {
1122
+ cause: error
1123
+ });
1124
+ }
1125
+ })
1126
+ );
1070
1127
  if (as) {
1071
1128
  return resolved[as];
1072
1129
  }
1073
1130
  return resolved;
1074
1131
  }
1075
1132
 
1076
- function resolveEditPath(path, options) {
1077
- const { pathSeparator = "." } = {};
1078
- const arr = toArray(path);
1079
- return arr.map((path2) => {
1080
- if (typeof path2 === "string") {
1081
- return pathSeparator === false ? path2 : path2.split(pathSeparator);
1082
- }
1083
- return path2;
1084
- }).flat();
1085
- }
1086
- function parseJSONCEdits(edits) {
1133
+ const toPathSegment$1 = (segment) => /^\d+$/.test(segment) ? Number(segment) : segment;
1134
+ function resolveEditPath$1(path, options) {
1135
+ const { pathSeparator = "." } = options || {};
1136
+ if (Array.isArray(path)) return path;
1137
+ if (typeof path === "number" || pathSeparator === false) return [path];
1138
+ return path.split(pathSeparator).map(toPathSegment$1);
1139
+ }
1140
+ function parseJSONCEdits(edits, defaultEditOptions) {
1141
+ const resolve = (path, options) => resolveEditPath$1(path, { ...defaultEditOptions, ...options });
1087
1142
  if (Array.isArray(edits)) {
1088
1143
  return edits.map(({ path, ...edit }) => ({
1089
- path: resolveEditPath(path),
1144
+ path: resolve(path, edit.options),
1090
1145
  ...edit
1091
1146
  }));
1092
1147
  }
1093
1148
  return Object.entries(edits).map(
1094
1149
  ([path, value]) => ({
1095
- path: resolveEditPath(path),
1150
+ path: resolve(path, value.options),
1096
1151
  ...value
1097
1152
  })
1098
1153
  );
@@ -1104,14 +1159,17 @@ function modifyJSON({
1104
1159
  }) {
1105
1160
  return checkResult(() => {
1106
1161
  const text = resolveJsonSource(json, "text");
1107
- const jsoncEdits = parseJSONCEdits(edits);
1108
- const editResult = jsoncEdits.flatMap(
1109
- (edit) => jsoncParser.modify(text, edit.path, edit.value, {
1110
- ...defaultEditOptions,
1111
- ...edit.options
1112
- })
1162
+ const jsoncEdits = parseJSONCEdits(edits, defaultEditOptions);
1163
+ const updated = jsoncEdits.reduce(
1164
+ (current, edit) => jsoncParser.applyEdits(
1165
+ current,
1166
+ jsoncParser.modify(current, edit.path, edit.value, {
1167
+ ...defaultEditOptions,
1168
+ ...edit.options
1169
+ })
1170
+ ),
1171
+ text
1113
1172
  );
1114
- const updated = jsoncParser.applyEdits(text, editResult);
1115
1173
  return resolveJsonSource({ text: updated });
1116
1174
  });
1117
1175
  }
@@ -1138,6 +1196,154 @@ function modifyJSONFile(filepath, edits, options) {
1138
1196
  });
1139
1197
  }
1140
1198
 
1199
+ const configLanguages = {
1200
+ json: { parse: confbox.parseJSONC, stringify: confbox.stringifyJSONC, surgical: true },
1201
+ jsonc: { parse: confbox.parseJSONC, stringify: confbox.stringifyJSONC, surgical: true },
1202
+ json5: { parse: confbox.parseJSON5, stringify: confbox.stringifyJSON5, surgical: false },
1203
+ yaml: { parse: confbox.parseYAML, stringify: confbox.stringifyYAML, surgical: false },
1204
+ toml: { parse: confbox.parseTOML, stringify: confbox.stringifyTOML, surgical: false }
1205
+ };
1206
+ const extensionFormats = {
1207
+ ".json": "json",
1208
+ ".jsonc": "jsonc",
1209
+ ".json5": "json5",
1210
+ ".yaml": "yaml",
1211
+ ".yml": "yaml",
1212
+ ".toml": "toml"
1213
+ };
1214
+ function isConfigFormat(value) {
1215
+ return value in configLanguages;
1216
+ }
1217
+ function getConfigFormat(filepath) {
1218
+ const extension = path.extname(filepath).toLowerCase();
1219
+ return extension in extensionFormats ? extensionFormats[extension] : void 0;
1220
+ }
1221
+
1222
+ function resolveFormat(source) {
1223
+ if (source.format) return source.format;
1224
+ const inferred = source.filepath ? getConfigFormat(source.filepath) : void 0;
1225
+ if (!inferred) {
1226
+ throw new Error(
1227
+ source.filepath ? `Unsupported config format: ${source.filepath}` : "A `format` is required for a text or data source"
1228
+ );
1229
+ }
1230
+ return inferred;
1231
+ }
1232
+ function resolveConfigSource(source, as) {
1233
+ const format = resolveFormat(source);
1234
+ const language = configLanguages[format];
1235
+ const { text: sourceText, data: sourceData, filepath } = source;
1236
+ const resolvers = {
1237
+ data: () => ({
1238
+ text: () => language.stringify(sourceData),
1239
+ data: () => sourceData
1240
+ }),
1241
+ text: () => ({
1242
+ text: () => sourceText,
1243
+ data: () => language.parse(sourceText)
1244
+ }),
1245
+ filepath: () => {
1246
+ const text = node_fs.readFileSync(filepath, "utf-8");
1247
+ return {
1248
+ text: () => text,
1249
+ data: () => language.parse(text)
1250
+ };
1251
+ }
1252
+ };
1253
+ const selected = ["data", "text", "filepath"].find(
1254
+ (key) => source[key] !== void 0
1255
+ );
1256
+ if (!selected) {
1257
+ throw new Error("Invalid source data");
1258
+ }
1259
+ const sourceResolvers = resolvers[selected]();
1260
+ const resolved = fromEntries(
1261
+ entriesOf(sourceResolvers).flatMap(([key, value]) => {
1262
+ if (as && key !== as) return [];
1263
+ try {
1264
+ return [[key, value()]];
1265
+ } catch (error) {
1266
+ throw new Error(`Failed to resolve ${key} from ${selected}`, {
1267
+ cause: error
1268
+ });
1269
+ }
1270
+ })
1271
+ );
1272
+ if (as) {
1273
+ return resolved[as];
1274
+ }
1275
+ return resolved;
1276
+ }
1277
+
1278
+ const toPathSegment = (segment) => /^\d+$/.test(segment) ? Number(segment) : segment;
1279
+ function resolveEditPath(path, options) {
1280
+ const { pathSeparator = "." } = options || {};
1281
+ if (Array.isArray(path)) return path;
1282
+ if (typeof path === "number" || pathSeparator === false) return [path];
1283
+ return path.split(pathSeparator).map(toPathSegment);
1284
+ }
1285
+ function toEditList(edits, defaultEditOptions) {
1286
+ const entries = Array.isArray(edits) ? edits : Object.entries(edits).map(([path, edit]) => ({ path, ...edit }));
1287
+ return entries.map(({ path, value, options }) => ({
1288
+ path: resolveEditPath(path, { ...defaultEditOptions, ...options }),
1289
+ value
1290
+ }));
1291
+ }
1292
+ function setPath(target, path, value) {
1293
+ const [head, ...rest] = path;
1294
+ if (head === void 0) return value;
1295
+ const container = Array.isArray(target) ? [...target] : { ...target ?? {} };
1296
+ container[head] = setPath(container[head], rest, value);
1297
+ return container;
1298
+ }
1299
+ function modifyConfig({
1300
+ config,
1301
+ edits,
1302
+ defaultEditOptions
1303
+ }) {
1304
+ return checkResult(() => {
1305
+ const format = config.format ?? (config.filepath ? getConfigFormat(config.filepath) : void 0) ?? "json";
1306
+ const language = configLanguages[format];
1307
+ if (language.surgical) {
1308
+ const { data, error } = modifyJSON({
1309
+ json: config.filepath ? { filepath: config.filepath } : config.text !== void 0 ? { text: config.text } : { data: config.data },
1310
+ edits,
1311
+ defaultEditOptions
1312
+ });
1313
+ if (error) throw error;
1314
+ return data;
1315
+ }
1316
+ const current = resolveConfigSource(config, "data");
1317
+ const updated = toEditList(edits, defaultEditOptions).reduce(
1318
+ (result, edit) => setPath(result, edit.path, edit.value),
1319
+ current
1320
+ );
1321
+ return resolveConfigSource({ text: language.stringify(updated), format });
1322
+ });
1323
+ }
1324
+ function modifyConfigFile(filepath, edits, options) {
1325
+ return checkResult(() => {
1326
+ const { autoCommit = true, defaultEditOptions, format } = options || {};
1327
+ const { data: config, error } = modifyConfig({
1328
+ config: format ? { filepath, format } : { filepath },
1329
+ edits,
1330
+ defaultEditOptions
1331
+ });
1332
+ if (error) {
1333
+ throw error;
1334
+ }
1335
+ const commit = () => node_fs.writeFileSync(filepath, config.text);
1336
+ if (autoCommit) {
1337
+ commit();
1338
+ return config;
1339
+ }
1340
+ return {
1341
+ config,
1342
+ commit
1343
+ };
1344
+ });
1345
+ }
1346
+
1141
1347
  function isStorageValue(input) {
1142
1348
  return input === null || typeof input === "string" || typeof input === "number" || typeof input === "boolean" || typeof input === "object";
1143
1349
  }
@@ -1146,117 +1352,92 @@ const storage = unstorage.createStorage({ driver: defineMemoryDriver__default()
1146
1352
  const tempFileSystem = defineFileSystemStorage({
1147
1353
  base: path.join(os__default.tmpdir(), ".package-manager")
1148
1354
  });
1355
+ const encodeEntry = (value) => typeof value === "string" ? value : JSON.stringify(value);
1149
1356
  function defineFileSystemEntries(definition) {
1150
- const fileSystemEntries = Object.entries(definition).map(([k, v]) => {
1151
- if (v instanceof Function) {
1152
- const {
1153
- file,
1154
- options,
1155
- serialize = (input) => JSON.stringify(input),
1156
- deserialize = (input) => {
1157
- try {
1158
- return JSON.parse(input);
1159
- } catch {
1160
- return input;
1161
- }
1162
- }
1163
- } = v();
1164
- return {
1165
- key: k,
1166
- value: serialize(file),
1167
- options,
1168
- serialize,
1169
- deserialize
1170
- };
1357
+ const fileSystemEntries = Object.entries(definition).map(([key, value]) => {
1358
+ if (typeof value === "function") {
1359
+ const { file, options } = value();
1360
+ return { key, value: encodeEntry(file), options };
1171
1361
  }
1172
- if (isStorageValue(v)) {
1173
- return {
1174
- key: k,
1175
- value: JSON.stringify(v)
1176
- };
1362
+ if (!isStorageValue(value)) {
1363
+ throw new Error(
1364
+ `Cannot store a ${typeof value} at "${key}": file system entries must be a storage value or a function returning one.`
1365
+ );
1177
1366
  }
1178
- return void 0;
1179
- }).filter((v) => v !== void 0);
1367
+ return { key, value: encodeEntry(value) };
1368
+ });
1180
1369
  const resolved = {
1181
1370
  definition,
1182
1371
  fileSystemEntries
1183
1372
  };
1184
1373
  return resolved;
1185
1374
  }
1375
+ const keyToFilePath = (root, key) => path.join(root, ...key.split(/[:/\\]+/).filter(Boolean));
1186
1376
  function defineFileSystemStorage(options) {
1187
1377
  const { base: root, initial, ...storageOptions } = options;
1188
- let initialFileEntriesData = defineFileSystemEntries(initial ?? {});
1378
+ let entriesData = defineFileSystemEntries(initial ?? {});
1189
1379
  const storage2 = unstorage.createStorage({
1190
1380
  driver: defineFsLiteDriver__default({ base: root, ...storageOptions })
1191
1381
  });
1382
+ const getFilePath = (key) => keyToFilePath(root, key);
1383
+ const getFile = async (key) => ({
1384
+ key,
1385
+ filepath: getFilePath(key),
1386
+ data: await storage2.getItem(key),
1387
+ // `getItemRaw` resolves to null for a missing file rather than throwing.
1388
+ read: async () => (await storage2.getItemRaw(key))?.toString()
1389
+ });
1390
+ const removeAllFiles = async () => storage2.clear();
1192
1391
  const fileStorage = {
1193
1392
  createFile: async (key, data) => {
1194
1393
  await storage2.setItem(key, data);
1195
1394
  return {
1196
1395
  key,
1197
- filepath: path.join(root, key),
1396
+ filepath: getFilePath(key),
1198
1397
  get: async () => storage2.getItem(key),
1199
1398
  update: async (data2) => storage2.setItem(key, data2)
1200
1399
  };
1201
1400
  },
1202
- async getFilePath(key) {
1203
- return (await this.getFile(key)).filepath;
1204
- },
1205
- getFile: async (key) => {
1206
- const storageData = await storage2.getItem(key);
1207
- return {
1208
- key,
1209
- filepath: path.join(root, key),
1210
- data: storageData,
1211
- read: async () => (await storage2.getItemRaw(key))?.toString()
1212
- };
1213
- },
1214
- async readFile(key) {
1215
- const { read } = await this.getFile(key);
1216
- const content = await read();
1217
- return content;
1218
- },
1219
- snapshotFs: async (base = root) => {
1220
- return unstorage.snapshot(storage2, base);
1221
- },
1222
- restoreFs: async (snapshot2) => {
1223
- return unstorage.restoreSnapshot(storage2, snapshot2);
1224
- },
1401
+ getFilePath,
1402
+ getFile,
1403
+ readFile: async (key) => (await getFile(key)).read(),
1404
+ // `base` is a key prefix. Defaulting it to the filesystem root would match
1405
+ // no key at all and silently report an empty filesystem.
1406
+ snapshotFs: async (base = "") => unstorage.snapshot(storage2, base),
1407
+ restoreFs: async (snapshot2, base) => unstorage.restoreSnapshot(storage2, snapshot2, base),
1225
1408
  initializeFs: async (override) => {
1226
- if (override) {
1227
- initialFileEntriesData = defineFileSystemEntries(override);
1228
- }
1229
- await storage2.clear();
1230
- return storage2.setItems(initialFileEntriesData.fileSystemEntries);
1409
+ const previousKeys = entriesData.fileSystemEntries.map(({ key }) => key);
1410
+ if (override) entriesData = defineFileSystemEntries(override);
1411
+ await Promise.all(previousKeys.map((key) => storage2.removeItem(key)));
1412
+ await storage2.setItems(entriesData.fileSystemEntries);
1231
1413
  },
1232
1414
  defineFileSystemEntries,
1233
- removeAllFiles: storage2.clear,
1415
+ removeAllFiles,
1234
1416
  async deleteFileSystem() {
1235
- await this.removeAllFiles();
1236
- await storage2.unmount(root, true);
1417
+ await removeAllFiles();
1418
+ await storage2.dispose();
1237
1419
  await promises.rm(root, { recursive: true, force: true });
1238
1420
  },
1239
1421
  storage: storage2,
1240
1422
  meta: {
1241
- fileEntriesData: initialFileEntriesData
1423
+ // A getter, so that `initializeFs(override)` is reflected here rather
1424
+ // than this reporting whatever the definition was at construction.
1425
+ get fileEntriesData() {
1426
+ return entriesData;
1427
+ }
1242
1428
  }
1243
1429
  };
1244
- if (initial) {
1245
- return {
1246
- initialize: async () => {
1247
- await fileStorage.initializeFs(initial);
1248
- return fileStorage;
1249
- }
1250
- };
1251
- }
1252
1430
  return fileStorage;
1253
1431
  }
1254
1432
 
1255
1433
  exports._dirname = _dirname;
1256
1434
  exports._filename = _filename;
1435
+ exports.configLanguages = configLanguages;
1436
+ exports.createFile = createFile;
1257
1437
  exports.defineFileSystemEntries = defineFileSystemEntries;
1258
1438
  exports.defineFileSystemStorage = defineFileSystemStorage;
1259
1439
  exports.definePackage = definePackage;
1440
+ exports.definePackageManager = definePackageManager;
1260
1441
  exports.definePackageManagerClient = definePackageManagerClient;
1261
1442
  exports.definePathAliases = definePathAliases;
1262
1443
  exports.detectGlobalPackageManagers = detectGlobalPackageManagers;
@@ -1268,6 +1449,7 @@ exports.findPackageManager = findPackageManager;
1268
1449
  exports.findPackageManagerSafely = findPackageManagerSafely;
1269
1450
  exports.findResolvedModulePath = findResolvedModulePath;
1270
1451
  exports.getAliasMap = getAliasMap;
1452
+ exports.getConfigFormat = getConfigFormat;
1271
1453
  exports.getFolderByPackageName = getFolderByPackageName;
1272
1454
  exports.getGitRootFolder = getGitRootFolder;
1273
1455
  exports.getGlobalVersions = getGlobalVersions;
@@ -1276,15 +1458,22 @@ exports.getPath = getPath;
1276
1458
  exports.getWorkspaceFolder = getWorkspaceFolder;
1277
1459
  exports.importMap = importMap;
1278
1460
  exports.importer = importer;
1461
+ exports.isConfigFormat = isConfigFormat;
1279
1462
  exports.isDependencyInPackageJson = isDependencyInPackageJson;
1280
1463
  exports.isPackageDependency = isPackageDependency;
1281
1464
  exports.isPackageModuleFound = isPackageModuleFound;
1465
+ exports.isWritable = isWritable;
1282
1466
  exports.mapPackageManagers = mapPackageManagers;
1467
+ exports.modifyConfig = modifyConfig;
1468
+ exports.modifyConfigFile = modifyConfigFile;
1283
1469
  exports.modifyJSON = modifyJSON;
1284
1470
  exports.modifyJSONFile = modifyJSONFile;
1285
1471
  exports.packageManagerConfigs = packageManagerConfigs;
1286
1472
  exports.predefinedPathAliases = predefinedPathAliases;
1287
1473
  exports.project = project;
1474
+ exports.readFile = readFile;
1475
+ exports.readFileSafely = readFileSafely;
1476
+ exports.resolveConfigSource = resolveConfigSource;
1288
1477
  exports.resolveModule = resolveModule;
1289
1478
  exports.resolveModulePath = resolveModulePath;
1290
1479
  exports.resolvePackageModulePath = resolvePackageModulePath;