package-management 0.0.16 → 0.1.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,88 @@
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 } 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, accessSync, constants, statSync, readFileSync } 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
8
  import { readFile, 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';
19
18
  import { createStorage, restoreSnapshot, snapshot } from 'unstorage';
20
19
  import defineMemoryDriver from 'unstorage/drivers/memory';
21
20
  import defineFsLiteDriver from 'unstorage/drivers/fs-lite';
22
21
 
22
+ const getCallSites = "getCallSites" in util ? util.getCallSites : void 0;
23
+ const MAX_FRAMES = 200;
24
+ function resolveCallerFile(options) {
25
+ const { from, boundaryFunctionName, internalScripts = [] } = options ?? {};
26
+ if (from) return toCallerPath(String(from));
27
+ if (getCallSites === void 0) return void 0;
28
+ const sites = getCallSites(MAX_FRAMES, { sourceMap: true });
29
+ const scriptName = boundaryFunctionName && frameAfterFunction(sites, boundaryFunctionName) || firstForeignFrame(sites, [OWN_SCRIPT, ...internalScripts]);
30
+ return scriptName ? toFilePath(scriptName) : void 0;
31
+ }
32
+ const OWN_SCRIPT = import.meta.url;
33
+ function frameAfterFunction(sites, functionName) {
34
+ const boundary = sites.findIndex((site) => site.functionName === functionName);
35
+ return boundary === -1 ? void 0 : sites[boundary + 1]?.scriptName;
36
+ }
37
+ function firstForeignFrame(sites, internalScripts) {
38
+ const internal = new Set(internalScripts.map(toFilePath));
39
+ return sites.find(
40
+ (site) => (
41
+ // Frames from `node:` internals and evaluated code name no file, so they
42
+ // are never the caller's location however far out they appear.
43
+ isFileScript(site.scriptName) && !internal.has(toFilePath(site.scriptName))
44
+ )
45
+ )?.scriptName;
46
+ }
47
+ const isFileScript = (script) => Boolean(script) && (script.startsWith("file:") || isAbsolute(script));
48
+ const toFilePath = (script) => script.startsWith("file:") ? fileURLToPath(script) : script;
49
+ function toCallerPath(from) {
50
+ if (from.startsWith("file:")) return fileURLToPath(from);
51
+ if (isAbsolute(from)) return from;
52
+ throw new Error(
53
+ `\`from\` must be a file: URL or an absolute path, received ${JSON.stringify(from)}. Pass \`import.meta.url\`.`
54
+ );
55
+ }
56
+
57
+ const _filename = (options) => resolveCallerFile(withOwnScript(options));
58
+ const _dirname = (options) => {
59
+ const filePath = resolveCallerFile(withOwnScript(options));
60
+ return filePath === void 0 ? void 0 : dirname(filePath);
61
+ };
62
+ const withOwnScript = (options) => ({
63
+ ...options,
64
+ internalScripts: [...options?.internalScripts ?? [], import.meta.url]
65
+ });
66
+
67
+ function createFile(filePath, data, options) {
68
+ const { encoding = "utf-8", ...rest } = typeof options === "string" ? { encoding: options } : options ?? {};
69
+ const dir = path.dirname(filePath);
70
+ const writeFileOptions = { encoding, ...rest };
71
+ if (!existsSync(dir)) {
72
+ mkdirSync(dir, { recursive: true });
73
+ }
74
+ writeFileSync(filePath, data, writeFileOptions);
75
+ }
76
+
77
+ function isWritable(filename) {
78
+ try {
79
+ accessSync(filename, constants.W_OK);
80
+ return true;
81
+ } catch (e) {
82
+ return false;
83
+ }
84
+ }
85
+
23
86
  const toArray = (data) => Array.isArray(data) ? data : [data];
24
87
  const entriesOf = (o) => Object.entries(o);
25
88
  const fromEntries = (entries) => Object.fromEntries(entries);
@@ -33,7 +96,7 @@ const select = (obj, selection, mode) => {
33
96
  };
34
97
  function normalizePath(path, option) {
35
98
  if (typeof option === "function") {
36
- return normalizePath(path);
99
+ return option(path);
37
100
  }
38
101
  if (option === false) {
39
102
  return path;
@@ -45,14 +108,6 @@ const invariant = (predicate, message) => {
45
108
  throw new Error(message);
46
109
  }
47
110
  };
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
111
  function checkResult(cb, options) {
57
112
  try {
58
113
  return buildSingleProp({
@@ -77,123 +132,16 @@ function buildSingleProp(entry) {
77
132
  return { [key]: value };
78
133
  }
79
134
 
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
135
  async function resolveModule(module) {
181
136
  const resolved = await module;
182
- return resolved.default || resolved;
137
+ const hasDefault = resolved !== null && typeof resolved === "object" && "default" in resolved;
138
+ return hasDefault ? resolved.default : resolved;
183
139
  }
184
140
  function isPackageModuleFound(name, options) {
185
141
  return Boolean(resolvePackageModulePath(name, options));
186
142
  }
187
143
  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;
144
+ return findResolvedModulePath([`${name}/package.json`, name], options);
197
145
  }
198
146
  function findResolvedModulePath(paths, options) {
199
147
  for (const path of paths) {
@@ -215,28 +163,68 @@ function resolveModulePath(modulePath, options) {
215
163
  }
216
164
  }
217
165
 
166
+ function getPackageFolder(options) {
167
+ return WST.findPackageRoot(options?.cwd ?? process.cwd());
168
+ }
169
+
170
+ function getPackageInfo(options) {
171
+ const packageDir = getPackageFolder(options);
172
+ if (!packageDir) {
173
+ throw new Error(
174
+ `No package.json found searching up from "${options?.cwd ?? process.cwd()}"`
175
+ );
176
+ }
177
+ return readPackageInfo({ packageDir });
178
+ }
179
+ function readPackageInfo({
180
+ packageDir
181
+ }) {
182
+ const packageJsonPath = path$1.join(packageDir, "package.json");
183
+ const packageJson = readPackageJson(packageJsonPath);
184
+ return {
185
+ name: packageJson.name,
186
+ path: packageJsonPath,
187
+ dirpath: packageDir,
188
+ packageJson
189
+ };
190
+ }
191
+ const packageJsonCache = /* @__PURE__ */ new Map();
192
+ function readPackageJson(packageJsonPath) {
193
+ const { mtimeMs } = statSync(packageJsonPath);
194
+ const cached = packageJsonCache.get(packageJsonPath);
195
+ if (cached?.mtimeMs === mtimeMs) return cached.packageJson;
196
+ const packageJson = JSON.parse(
197
+ readFileSync(packageJsonPath, "utf-8")
198
+ );
199
+ packageJsonCache.set(packageJsonPath, { mtimeMs, packageJson });
200
+ return packageJson;
201
+ }
202
+
218
203
  const tsconfig = (tsconfigDir) => ({
219
204
  get paths() {
205
+ if (!tsconfigDir) return [];
220
206
  return globbySync(["tsconfig.json", "tsconfig.*.json"], {
221
- cwd: tsconfigDir
222
- }) ?? [];
207
+ cwd: tsconfigDir,
208
+ absolute: true
209
+ });
223
210
  }
224
211
  });
225
212
 
226
- function parseGitignoreFile(gitignorePath, options) {
227
- return gitignoreParser(gitignorePath, options);
213
+ function parseGitignoreContent(gitignoreContent, options) {
214
+ return gitignoreParser(gitignoreContent, options);
228
215
  }
229
- function getGitignoreData(gitignorePath) {
230
- const gitignoreFileContent = readFileSync(gitignorePath, "utf-8");
231
- const gitignores = parseGitignoreFile(gitignoreFileContent);
232
- return gitignores;
216
+ function getGitignoreData(gitignorePath, options) {
217
+ if (!gitignorePath || !existsSync(gitignorePath)) {
218
+ return parseGitignoreContent("", options);
219
+ }
220
+ return parseGitignoreContent(readFileSync(gitignorePath, "utf-8"), options);
233
221
  }
234
- const gitignore = (gitignorePath) => ({
222
+ const gitignore = (gitignorePath, options) => ({
235
223
  get data() {
236
- return getGitignoreData(gitignorePath);
224
+ return getGitignoreData(gitignorePath, options);
237
225
  },
238
226
  get patterns() {
239
- return getGitignoreData(gitignorePath).patterns;
227
+ return getGitignoreData(gitignorePath, options).patterns;
240
228
  }
241
229
  });
242
230
 
@@ -299,36 +287,13 @@ function getWorkspaceFolder(options) {
299
287
  throwIfNotFound = true
300
288
  } = options ?? {};
301
289
  const getFolder = fallbackToGitRoot ? WST.findProjectRoot : WST.getWorkspaceManagerRoot;
302
- const folder = getFolder(cwd);
290
+ const folder = checkResult(() => getFolder(cwd)).data;
303
291
  if (folder === void 0 && throwIfNotFound) {
304
292
  throw new Error(`Could not find workspace folder`);
305
293
  }
306
294
  return folder;
307
295
  }
308
296
 
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
297
  function getWorkspaceProjectInfo(options) {
333
298
  try {
334
299
  const workspaceDir = getWorkspaceFolder(options);
@@ -344,13 +309,21 @@ function getWorkspacePackageInfoList(options) {
344
309
  const wstList = WST.getWorkspaceInfos(workspaceDir);
345
310
  return resolveWstList(wstList);
346
311
  }
312
+ function toPackageInfo(info) {
313
+ return {
314
+ name: info.name,
315
+ dirpath: info.path,
316
+ path: join(info.path, "package.json"),
317
+ packageJson: info.packageJson
318
+ };
319
+ }
347
320
  function common(options) {
348
321
  const { cwd, includeRoot: includeWorkspace } = options ?? {};
349
322
  const workspaceDir = getWorkspaceFolder({ cwd });
350
323
  function resolveWstList(list) {
351
324
  if (!workspaceDir || !list) return [];
352
- if (includeWorkspace) return [getWorkspaceProjectInfo(), ...list];
353
- return list;
325
+ const packages = list.map(toPackageInfo);
326
+ return includeWorkspace ? [getWorkspaceProjectInfo({ cwd }), ...packages] : packages;
354
327
  }
355
328
  return {
356
329
  workspaceDir,
@@ -367,7 +340,7 @@ function getWorkspacePackageInfoMap(options) {
367
340
 
368
341
  function getProjectInfoByName(name, options) {
369
342
  const { cwd } = options ?? {};
370
- return getWorkspacePackageInfoMap({ cwd })[name];
343
+ return getWorkspacePackageInfoMap({ cwd, includeRoot: true })[name];
371
344
  }
372
345
 
373
346
  function getGitRootFolder(options) {
@@ -382,7 +355,7 @@ function getGitRootFolder(options) {
382
355
  }
383
356
  return void 0;
384
357
  }
385
- return path$1.normalize(stdout);
358
+ return path.normalize(stdout);
386
359
  }
387
360
 
388
361
  function getProjectInfo(folder, options) {
@@ -413,9 +386,14 @@ function parseWorkspaceFolderTypeOptions(option, defaultOptions) {
413
386
 
414
387
  const project = (...args) => {
415
388
  const [source, projectOptions] = args;
416
- const { packageJson, packageJsonPath, packageName, projectDir } = info();
417
389
  const {
418
- findPackageManager: findPackageManager2,
390
+ packageJson,
391
+ name: packageName,
392
+ dirpath: projectDir,
393
+ path: packageJsonPath
394
+ } = getProjectInfo(source, projectOptions) ?? {};
395
+ const {
396
+ findPackageManager,
419
397
  detectPackageManagers,
420
398
  detectLockfilePackageManagers,
421
399
  detectGlobalPackageManagers,
@@ -423,41 +401,29 @@ const project = (...args) => {
423
401
  filterPackageManagers,
424
402
  mapPackageManagers
425
403
  } = definePackageManagerClient({ cwd: projectDir });
404
+ const getPackageJson = () => projectDir ? readPackageInfo({ packageDir: projectDir }).packageJson : packageJson;
426
405
  return {
427
406
  packageJson,
428
407
  packageJsonPath,
429
408
  packageName,
430
409
  projectDir,
431
- findPackageManager: findPackageManager2,
410
+ findPackageManager,
432
411
  detectPackageManagers,
433
412
  detectGlobalPackageManagers,
434
413
  detectLockfilePackageManagers,
435
414
  globalVersions,
436
415
  mapPackageManagers,
437
- tsconfig: tsconfig(projectDir ?? ""),
438
- gitignore: gitignore(path$1.join(projectDir ?? "", ".gitignore")),
416
+ // Passed through as-is: substituting "" for an unresolved project made
417
+ // both of these read from the calling process's directory instead.
418
+ tsconfig: tsconfig(projectDir),
419
+ gitignore: gitignore(
420
+ projectDir ? path.join(projectDir, ".gitignore") : void 0
421
+ ),
439
422
  filterPackageManagers,
440
- getPackageJson: () => get("packageJson"),
441
- findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, get("packageJson")),
442
- isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, get("packageJson"))
423
+ getPackageJson,
424
+ findDependencyInPackageJson: (options) => findDependencyInPackageJson(options, getPackageJson()),
425
+ isDependencyInPackageJson: (options) => isDependencyInPackageJson(options, getPackageJson())
443
426
  };
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
427
  };
462
428
 
463
429
  function getWorkspacePackageNames(options) {
@@ -472,18 +438,24 @@ const workspace = {
472
438
  getProject: project
473
439
  };
474
440
 
475
- function importer(imports, options) {
441
+ async function importer(imports, options) {
476
442
  const { install: defaultInstall = true, installer } = options ?? {};
443
+ const resolved = imports.map((option) => resolveImportOption(option));
444
+ const missing = resolved.filter(
445
+ (option) => Boolean(option.name) && (option.install ?? defaultInstall) && !((option.checkExists ?? true) && isSatisfied(option.name))
446
+ );
447
+ await installMissing(
448
+ missing.filter(({ dev }) => !dev).map(({ name }) => name),
449
+ { dev: false },
450
+ installer
451
+ );
452
+ await installMissing(
453
+ missing.filter(({ dev }) => dev).map(({ name }) => name),
454
+ { dev: true },
455
+ installer
456
+ );
477
457
  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
- })
458
+ resolved.map(async (option) => resolveModule(await option.import()))
487
459
  );
488
460
  }
489
461
  const definePackage = (option) => {
@@ -507,12 +479,11 @@ function resolveImportOption(option) {
507
479
  }
508
480
  return option;
509
481
  }
510
- async function installImport(packageName, options, installer) {
511
- if (!packageName) return;
512
- const { checkExists } = options ?? {};
513
- if (checkExists && isPackageDependency(packageName)) return;
482
+ const isSatisfied = (packageName) => isPackageDependency(packageName) && isPackageModuleFound(packageName);
483
+ async function installMissing(packageNames, options, installer) {
484
+ if (packageNames.length === 0) return;
514
485
  const installerFn = installer ?? (await workspace.getProject("<package_folder>").findPackageManager()).installPackage;
515
- await installerFn(packageName, options);
486
+ await installerFn(packageNames, options);
516
487
  }
517
488
 
518
489
  const importMap = async (importMap2, options) => {
@@ -534,20 +505,36 @@ function definePackageManager(config, options) {
534
505
  const lockfiles = toArray(config.meta.lockfile);
535
506
  return await findUp(lockfiles, { cwd });
536
507
  });
508
+ const globalVersion = asyncCacheFn(
509
+ async (options2) => {
510
+ try {
511
+ const { stdout } = await $$({
512
+ command,
513
+ args: [agentOptions.version],
514
+ cwd: defaultCwd,
515
+ ...options2
516
+ });
517
+ return `${stdout}`.trim();
518
+ } catch {
519
+ return void 0;
520
+ }
521
+ }
522
+ );
537
523
  const installPackage = async (packageName, options2) => {
538
524
  const install = agentArgs.install;
539
- const { dev, preferOffline } = select(
540
- install.options,
541
- {
542
- preferOffline: true,
543
- cwd: defaultCwd,
544
- ...options2
545
- });
525
+ const { dev = false, preferOffline = true } = options2 ?? {};
526
+ const flags = select(install.options, { dev, preferOffline });
546
527
  const packageNames = toArray(packageName);
528
+ assertInstallableNames(packageNames);
547
529
  try {
548
530
  await $$({
549
531
  command,
550
- args: [install.command, dev, preferOffline, ...packageNames],
532
+ args: [
533
+ install.command,
534
+ flags.dev,
535
+ flags.preferOffline,
536
+ ...packageNames
537
+ ],
551
538
  cwd: defaultCwd,
552
539
  ...options2
553
540
  });
@@ -591,19 +578,12 @@ function definePackageManager(config, options) {
591
578
  if (!lockfilePath) return void 0;
592
579
  return readFile(lockfilePath, "utf8");
593
580
  }),
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
- }
581
+ globalVersion,
582
+ matchesVersion: asyncCacheFn(async (...args) => {
583
+ const { matchesVersion } = config.meta;
584
+ if (!matchesVersion) return true;
585
+ const version = await globalVersion.noCache(...args);
586
+ return version ? matchesVersion(version) : false;
607
587
  }),
608
588
  definePackage,
609
589
  installPackage,
@@ -617,6 +597,16 @@ function definePackageManager(config, options) {
617
597
  }
618
598
  };
619
599
  }
600
+ function assertInstallableNames(packageNames) {
601
+ const rejected = packageNames.filter(
602
+ (name) => name.length === 0 || name.startsWith("-")
603
+ );
604
+ if (rejected.length > 0) {
605
+ throw new Error(
606
+ `Not a package name: ${rejected.map((name) => JSON.stringify(name)).join(", ")}. A package name cannot be empty or begin with "-".`
607
+ );
608
+ }
609
+ }
620
610
  async function $$(options) {
621
611
  const { command, args = [], silent = true, cwd, shellOptions } = options;
622
612
  return execa(command, args.filter(notFalsy), {
@@ -639,7 +629,9 @@ const bun = definePackageManagerConfig({
639
629
  command: "bun",
640
630
  runner: "bunx",
641
631
  meta: {
642
- lockfile: "bun.lockb"
632
+ // Bun wrote a binary lockfile originally and a text one from 1.2 onwards,
633
+ // so a project using either must still be detected.
634
+ lockfile: ["bun.lock", "bun.lockb"]
643
635
  },
644
636
  args: {
645
637
  install: {
@@ -650,7 +642,7 @@ const bun = definePackageManagerConfig({
650
642
  }
651
643
  },
652
644
  uninstall: {
653
- command: "uninstall"
645
+ command: "remove"
654
646
  }
655
647
  },
656
648
  options: {
@@ -714,7 +706,9 @@ const yarn = definePackageManagerConfig({
714
706
  command: "yarn",
715
707
  runner: "yarn dlx",
716
708
  meta: {
717
- lockfile: "yarn.lock"
709
+ lockfile: "yarn.lock",
710
+ // Berry shares this command and lockfile, so the version decides.
711
+ matchesVersion: (version) => version.startsWith("1.")
718
712
  },
719
713
  args: {
720
714
  install: {
@@ -733,6 +727,33 @@ const yarn = definePackageManagerConfig({
733
727
  }
734
728
  });
735
729
 
730
+ const yarnBerry = definePackageManagerConfig({
731
+ id: "yarn-berry",
732
+ name: "Yarn Berry",
733
+ command: "yarn",
734
+ runner: "yarn dlx",
735
+ meta: {
736
+ lockfile: "yarn.lock",
737
+ // Classic shares this command and lockfile; everything past 1.x is Berry.
738
+ matchesVersion: (version) => !version.startsWith("1.")
739
+ },
740
+ args: {
741
+ install: {
742
+ command: "add",
743
+ options: {
744
+ dev: "-D",
745
+ preferOffline: "--cached"
746
+ }
747
+ },
748
+ uninstall: {
749
+ command: "remove"
750
+ }
751
+ },
752
+ options: {
753
+ version: "--version"
754
+ }
755
+ });
756
+
736
757
  async function findPackageManager(packageManagers, options) {
737
758
  const packageManager = await findPackageManagerSafely(
738
759
  packageManagers,
@@ -762,7 +783,9 @@ async function detectPackageManagers(packageManagers, options) {
762
783
  async function detectLockfilePackageManagers(packageManagers, options) {
763
784
  return filterPackageManagers(
764
785
  packageManagers,
765
- (packageManager) => packageManager.hasLockfile(options),
786
+ // Yarn Classic and Berry share a lockfile name, so a lockfile match alone
787
+ // would report both. The version check settles which one it is.
788
+ async (packageManager) => await packageManager.hasLockfile(options) && await packageManager.matchesVersion(options),
766
789
  options
767
790
  );
768
791
  }
@@ -772,7 +795,7 @@ async function detectGlobalPackageManagers(packageManagers, options) {
772
795
  // Without the `await`, `Boolean` receives a pending promise and is always
773
796
  // `true`, so every package manager passes the filter regardless of whether
774
797
  // it is actually installed.
775
- async (packageManager) => Boolean(await packageManager.globalVersion(options)),
798
+ async (packageManager) => Boolean(await packageManager.globalVersion(options)) && await packageManager.matchesVersion(options),
776
799
  options
777
800
  );
778
801
  }
@@ -805,7 +828,7 @@ function selectAllowedPackageManagers(packageManagers, options) {
805
828
  );
806
829
  }
807
830
 
808
- const packageManagerConfigs = [pnpm, yarn, bun, npm];
831
+ const packageManagerConfigs = [pnpm, yarn, yarnBerry, bun, npm];
809
832
  function definePackageManagerClient(options) {
810
833
  const configs = packageManagerConfigs.map(
811
834
  (config) => definePackageManager(config, options)
@@ -835,8 +858,11 @@ function definePackageManagerClient(options) {
835
858
  };
836
859
  }
837
860
 
838
- function isPackageDependency(packageName) {
839
- return toArray(packageName).every((name) => isDependencyInPackageJson(name));
861
+ function isPackageDependency(packageName, options) {
862
+ const { packageJson } = getPackageInfo(options);
863
+ return toArray(packageName).every(
864
+ (name) => isDependencyInPackageJson(name, packageJson)
865
+ );
840
866
  }
841
867
 
842
868
  function definePathAliases(aliasDefinitions) {
@@ -854,42 +880,49 @@ function definePathAliases(aliasDefinitions) {
854
880
  };
855
881
  }
856
882
  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 };
883
+ const { to, startingFrom, cwd, from, checkExistence, glob } = typeof options === "string" || Array.isArray(options) ? { to: options } : options;
884
+ const resolveOptions = { cwd, from, checkExistence, glob, aliasMap };
859
885
  try {
860
886
  return startingFrom ? resolveRelativePathTo(to, startingFrom, resolveOptions) : resolvePathTo(to, resolveOptions);
861
887
  } catch (error) {
862
- if (checkExistence || glob) return void 0;
888
+ if (error instanceof PathNotFoundError) return void 0;
863
889
  throw error;
864
890
  }
865
891
  }
892
+ class PathNotFoundError extends Error {
893
+ }
866
894
  function resolveRelativePathTo(to, from, options) {
867
895
  const pathFrom = resolvePathTo(from, options);
868
896
  const pathTo = resolvePathTo(to, options);
869
- return path$1.relative(pathFrom, pathTo);
897
+ return path.relative(pathFrom, pathTo);
870
898
  }
871
- function resolvePathTo(pathTo, { cwd, checkExistence, glob, aliasMap }) {
872
- const normalized = normalizePathTo(pathTo, { cwd, aliasMap });
899
+ function resolvePathTo(pathTo, { cwd, from, checkExistence, glob, aliasMap }) {
900
+ const normalized = normalizePathTo(pathTo, { cwd, from, aliasMap });
873
901
  if (glob) {
874
902
  const globPaths = globbySync(normalized, { cwd });
875
903
  if (!globPaths[0])
876
- throw new Error(`No paths found for glob: ${normalized}`);
904
+ throw new PathNotFoundError(`No paths found for glob: ${normalized}`);
877
905
  return globPaths[0];
878
906
  }
879
907
  if (!glob && checkExistence && !existsSync(normalized))
880
- throw new Error(`Path does not exist: ${normalized}`);
908
+ throw new PathNotFoundError(`Path does not exist: ${normalized}`);
881
909
  return normalized;
882
910
  }
883
911
  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}`);
912
+ const { cwd, from, aliasMap } = options ?? {};
913
+ const baseDir = Array.isArray(pathTo) ? pathTo[0] : findAliasToken(pathTo, aliasMap);
914
+ if (baseDir === void 0) return pathTo;
915
+ const baseDirPath = assertResolved(
916
+ baseDir,
917
+ executeMapFn(aliasMap, baseDir, [{ cwd, from }])
918
+ );
919
+ return Array.isArray(pathTo) ? path.join(baseDirPath, ...pathTo.slice(1).filter(isNotNull)) : resolveAlias(pathTo, { [baseDir]: baseDirPath });
920
+ }
921
+ function assertResolved(alias, resolved) {
922
+ if (typeof resolved !== "string" || resolved.length === 0) {
923
+ throw new Error(`Path alias resolved to no location: ${alias}`);
891
924
  }
892
- return resolveAlias(aliasedPath, { [baseDir]: baseDirPath });
925
+ return resolved;
893
926
  }
894
927
  function findAliasToken(pathTo, aliasMap) {
895
928
  return Object.keys(aliasMap ?? {}).filter((alias) => pathTo === alias || pathTo.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length)[0];
@@ -907,10 +940,17 @@ function getAliasMap(aliasDefs) {
907
940
  return [
908
941
  [alias, resolve],
909
942
  ...subpaths.map(({ to }) => {
910
- const subpathAlias = path$1.join(alias, to);
943
+ const subpathAlias = path.join(alias, to);
911
944
  return [
912
945
  subpathAlias,
913
- (opts) => resolveAlias(subpathAlias, { [alias]: resolve(opts) })
946
+ (opts) => (
947
+ // The same assertion the bare alias gets. Without it an
948
+ // unresolvable parent produced `join(undefined, "/node_modules")`
949
+ // — a path at the filesystem root — instead of failing.
950
+ resolveAlias(subpathAlias, {
951
+ [alias]: assertResolved(alias, resolve(opts))
952
+ })
953
+ )
914
954
  ];
915
955
  })
916
956
  ];
@@ -985,13 +1025,13 @@ const predefinedPathAliases = {
985
1025
  subpaths: []
986
1026
  },
987
1027
  "<current_file>": {
988
- // resolve: () => fileURLToPath(import.meta.url),
989
- resolve: () => _filename({ rootFunctionName: "getFilePath" }) ?? "",
1028
+ // `from` is the caller naming itself, which is exact. Without it the stack
1029
+ // is read, which is a best effort and Node-only.
1030
+ resolve: (opts) => _filename({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
990
1031
  subpaths: []
991
1032
  },
992
1033
  "<current_folder>": {
993
- // resolve: () => fileURLToPath(import.meta.url),
994
- resolve: () => _dirname({ rootFunctionName: "getFilePath" }) ?? "",
1034
+ resolve: (opts) => _dirname({ from: opts?.from, boundaryFunctionName: "getFilePath" }),
995
1035
  subpaths: []
996
1036
  }
997
1037
  };
@@ -1014,59 +1054,65 @@ const jsonSourceResolvers = {
1014
1054
  text: (text) => {
1015
1055
  return {
1016
1056
  text: () => text,
1017
- data: () => JSON.parse(text)
1057
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1058
+ // module is editing files like tsconfig.json, which carry comments.
1059
+ data: () => parse(text)
1018
1060
  };
1019
1061
  },
1020
1062
  filepath: (filepath) => {
1021
1063
  const text = readFileSync(filepath, "utf-8");
1022
1064
  return {
1023
1065
  text: () => text,
1024
- data: () => JSON.parse(text)
1066
+ // `jsonc-parser`'s parse, not `JSON.parse`: the whole point of this
1067
+ // module is editing files like tsconfig.json, which carry comments.
1068
+ data: () => parse(text)
1025
1069
  };
1026
1070
  }
1027
1071
  };
1028
1072
  function resolveJsonSource(source, as) {
1029
1073
  const [sourceType, sourceData] = Object.entries(source).find(([_, selected]) => selected !== void 0) ?? [];
1030
- if (!sourceType || !(sourceType in jsonSourceResolvers) || !sourceData) {
1074
+ if (!sourceType || !(sourceType in jsonSourceResolvers) || sourceData === void 0) {
1031
1075
  throw new Error("Invalid source data");
1032
1076
  }
1033
1077
  const sourceTypeResolvers = jsonSourceResolvers[sourceType](
1034
1078
  sourceData
1035
1079
  );
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
- });
1080
+ const resolved = fromEntries(
1081
+ entriesOf(sourceTypeResolvers).flatMap(([key, v]) => {
1082
+ if (as && key !== as) return [];
1083
+ try {
1084
+ return [[key, v()]];
1085
+ } catch (error) {
1086
+ throw new Error(`Failed to resolve ${key} from ${sourceType}`, {
1087
+ cause: error
1088
+ });
1089
+ }
1090
+ })
1091
+ );
1044
1092
  if (as) {
1045
1093
  return resolved[as];
1046
1094
  }
1047
1095
  return resolved;
1048
1096
  }
1049
1097
 
1098
+ const toPathSegment = (segment) => /^\d+$/.test(segment) ? Number(segment) : segment;
1050
1099
  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();
1100
+ const { pathSeparator = "." } = options || {};
1101
+ if (Array.isArray(path)) return path;
1102
+ if (typeof path === "number" || pathSeparator === false) return [path];
1103
+ return path.split(pathSeparator).map(toPathSegment);
1059
1104
  }
1060
- function parseJSONCEdits(edits) {
1105
+ function parseJSONCEdits(edits, defaultEditOptions) {
1106
+ const resolve = (path, options) => resolveEditPath(path, { ...defaultEditOptions, ...options });
1061
1107
  if (Array.isArray(edits)) {
1062
1108
  return edits.map(({ path, ...edit }) => ({
1063
- path: resolveEditPath(path),
1109
+ path: resolve(path, edit.options),
1064
1110
  ...edit
1065
1111
  }));
1066
1112
  }
1067
1113
  return Object.entries(edits).map(
1068
1114
  ([path, value]) => ({
1069
- path: resolveEditPath(path),
1115
+ path: resolve(path, value.options),
1070
1116
  ...value
1071
1117
  })
1072
1118
  );
@@ -1078,14 +1124,17 @@ function modifyJSON({
1078
1124
  }) {
1079
1125
  return checkResult(() => {
1080
1126
  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
- })
1127
+ const jsoncEdits = parseJSONCEdits(edits, defaultEditOptions);
1128
+ const updated = jsoncEdits.reduce(
1129
+ (current, edit) => applyEdits(
1130
+ current,
1131
+ modify(current, edit.path, edit.value, {
1132
+ ...defaultEditOptions,
1133
+ ...edit.options
1134
+ })
1135
+ ),
1136
+ text
1087
1137
  );
1088
- const updated = applyEdits(text, editResult);
1089
1138
  return resolveJsonSource({ text: updated });
1090
1139
  });
1091
1140
  }
@@ -1120,110 +1169,82 @@ const storage = createStorage({ driver: defineMemoryDriver() });
1120
1169
  const tempFileSystem = defineFileSystemStorage({
1121
1170
  base: join(os.tmpdir(), ".package-manager")
1122
1171
  });
1172
+ const encodeEntry = (value) => typeof value === "string" ? value : JSON.stringify(value);
1123
1173
  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
- };
1174
+ const fileSystemEntries = Object.entries(definition).map(([key, value]) => {
1175
+ if (typeof value === "function") {
1176
+ const { file, options } = value();
1177
+ return { key, value: encodeEntry(file), options };
1145
1178
  }
1146
- if (isStorageValue(v)) {
1147
- return {
1148
- key: k,
1149
- value: JSON.stringify(v)
1150
- };
1179
+ if (!isStorageValue(value)) {
1180
+ throw new Error(
1181
+ `Cannot store a ${typeof value} at "${key}": file system entries must be a storage value or a function returning one.`
1182
+ );
1151
1183
  }
1152
- return void 0;
1153
- }).filter((v) => v !== void 0);
1184
+ return { key, value: encodeEntry(value) };
1185
+ });
1154
1186
  const resolved = {
1155
1187
  definition,
1156
1188
  fileSystemEntries
1157
1189
  };
1158
1190
  return resolved;
1159
1191
  }
1192
+ const keyToFilePath = (root, key) => join(root, ...key.split(/[:/\\]+/).filter(Boolean));
1160
1193
  function defineFileSystemStorage(options) {
1161
1194
  const { base: root, initial, ...storageOptions } = options;
1162
- let initialFileEntriesData = defineFileSystemEntries(initial ?? {});
1195
+ let entriesData = defineFileSystemEntries(initial ?? {});
1163
1196
  const storage2 = createStorage({
1164
1197
  driver: defineFsLiteDriver({ base: root, ...storageOptions })
1165
1198
  });
1199
+ const getFilePath = (key) => keyToFilePath(root, key);
1200
+ const getFile = async (key) => ({
1201
+ key,
1202
+ filepath: getFilePath(key),
1203
+ data: await storage2.getItem(key),
1204
+ // `getItemRaw` resolves to null for a missing file rather than throwing.
1205
+ read: async () => (await storage2.getItemRaw(key))?.toString()
1206
+ });
1207
+ const removeAllFiles = async () => storage2.clear();
1166
1208
  const fileStorage = {
1167
1209
  createFile: async (key, data) => {
1168
1210
  await storage2.setItem(key, data);
1169
1211
  return {
1170
1212
  key,
1171
- filepath: join(root, key),
1213
+ filepath: getFilePath(key),
1172
1214
  get: async () => storage2.getItem(key),
1173
1215
  update: async (data2) => storage2.setItem(key, data2)
1174
1216
  };
1175
1217
  },
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
- },
1218
+ getFilePath,
1219
+ getFile,
1220
+ readFile: async (key) => (await getFile(key)).read(),
1221
+ // `base` is a key prefix. Defaulting it to the filesystem root would match
1222
+ // no key at all and silently report an empty filesystem.
1223
+ snapshotFs: async (base = "") => snapshot(storage2, base),
1224
+ restoreFs: async (snapshot2, base) => restoreSnapshot(storage2, snapshot2, base),
1199
1225
  initializeFs: async (override) => {
1200
- if (override) {
1201
- initialFileEntriesData = defineFileSystemEntries(override);
1202
- }
1203
- await storage2.clear();
1204
- return storage2.setItems(initialFileEntriesData.fileSystemEntries);
1226
+ const previousKeys = entriesData.fileSystemEntries.map(({ key }) => key);
1227
+ if (override) entriesData = defineFileSystemEntries(override);
1228
+ await Promise.all(previousKeys.map((key) => storage2.removeItem(key)));
1229
+ await storage2.setItems(entriesData.fileSystemEntries);
1205
1230
  },
1206
1231
  defineFileSystemEntries,
1207
- removeAllFiles: storage2.clear,
1232
+ removeAllFiles,
1208
1233
  async deleteFileSystem() {
1209
- await this.removeAllFiles();
1210
- await storage2.unmount(root, true);
1234
+ await removeAllFiles();
1235
+ await storage2.dispose();
1211
1236
  await rm(root, { recursive: true, force: true });
1212
1237
  },
1213
1238
  storage: storage2,
1214
1239
  meta: {
1215
- fileEntriesData: initialFileEntriesData
1240
+ // A getter, so that `initializeFs(override)` is reflected here rather
1241
+ // than this reporting whatever the definition was at construction.
1242
+ get fileEntriesData() {
1243
+ return entriesData;
1244
+ }
1216
1245
  }
1217
1246
  };
1218
- if (initial) {
1219
- return {
1220
- initialize: async () => {
1221
- await fileStorage.initializeFs(initial);
1222
- return fileStorage;
1223
- }
1224
- };
1225
- }
1226
1247
  return fileStorage;
1227
1248
  }
1228
1249
 
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 };
1250
+ export { _dirname, _filename, createFile, defineFileSystemEntries, defineFileSystemStorage, definePackage, definePackageManager, definePackageManagerClient, definePathAliases, detectGlobalPackageManagers, detectLockfilePackageManagers, detectPackageManagers, filterPackageManagers, findDependencyInPackageJson, findPackageManager, findPackageManagerSafely, findResolvedModulePath, getAliasMap, getFolderByPackageName, getGitRootFolder, getGlobalVersions, getPackageFolder, getPath, getWorkspaceFolder, importMap, importer, isDependencyInPackageJson, isPackageDependency, isPackageModuleFound, isWritable, mapPackageManagers, modifyJSON, modifyJSONFile, packageManagerConfigs, predefinedPathAliases, project, resolveModule, resolveModulePath, resolvePackageModulePath, storage, tempFileSystem, workspace };