@vistta/cli 1.1.0 → 1.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.
@@ -0,0 +1,116 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ function project(report = {}) {
9
+ const cwd = mkdtempSync(join(tmpdir(), "vistta-project-"));
10
+ const files = {
11
+ "package.json": { name: "fixture", version: "1.0.0", dependencies: { latest: "^1.0.0" } },
12
+ "package-lock.json": { packages: {} },
13
+ "packages/first/package.json": { name: "first", dependencies: { shared: "^1.0.0", untouched: "^1.0.0" } },
14
+ "packages/second/package.json": { name: "second", devDependencies: { shared: "^2.0.0" } },
15
+ };
16
+ for (const [path, content] of Object.entries(files)) {
17
+ mkdirSync(dirname(join(cwd, path)), { recursive: true });
18
+ writeFileSync(join(cwd, path), JSON.stringify(content));
19
+ }
20
+ const npm = join(cwd, "npm.cjs");
21
+ writeFileSync(
22
+ npm,
23
+ `process.stdout.write(${JSON.stringify(JSON.stringify(report))}); process.exit(${Object.keys(report).length ? 1 : 0});`,
24
+ );
25
+ return {
26
+ cwd,
27
+ read: (path) => JSON.parse(readFileSync(join(cwd, path), "utf8")),
28
+ invoke: (...args) =>
29
+ spawnSync(process.execPath, [fileURLToPath(new URL("../bin.js", import.meta.url)), "project", ...args], {
30
+ cwd,
31
+ encoding: "utf8",
32
+ env: { ...process.env, npm_execpath: npm },
33
+ }),
34
+ clean: () => rmSync(cwd, { recursive: true, force: true }),
35
+ };
36
+ }
37
+
38
+ const report = {
39
+ shared: [
40
+ { current: "1.0.0", wanted: "1.5.0", latest: "3.0.0", type: "dependencies", dependedByLocation: "packages/first" },
41
+ { current: "2.0.0", wanted: "2.8.0", latest: "3.0.0", type: "devDependencies", dependedByLocation: "packages/second" },
42
+ ],
43
+ latest: { current: "1.0.0", wanted: "1.0.0", latest: "2.0.0", type: "dependencies" },
44
+ };
45
+
46
+ suite("Project command", () => {
47
+ test("shows consistent help and metadata and rejects unknown commands and arguments", () => {
48
+ const fixture = project();
49
+ try {
50
+ assert.match(fixture.invoke().stdout, /Project metadata and dependency maintenance/);
51
+ assert.match(fixture.invoke("update", "--help").stdout, /may include major upgrades/);
52
+ assert.match(fixture.invoke("help", "patch").stdout, /not patch-only/);
53
+ assert.match(fixture.invoke("name").stdout, /fixture/);
54
+ assert.match(fixture.invoke("version").stdout, /1\.0\.0/);
55
+ assert.equal(fixture.invoke("version", "patch").status, 0);
56
+ assert.equal(fixture.read("package.json").version, "1.0.1");
57
+ assert.notEqual(fixture.invoke("typo").status, 0);
58
+ assert.notEqual(fixture.invoke("update", "--typo").status, 0);
59
+ assert.notEqual(fixture.invoke("name", "extra").status, 0);
60
+ } finally {
61
+ fixture.clean();
62
+ }
63
+ });
64
+
65
+ test("reports versions and manifests and preserves informational-only exit status", () => {
66
+ const fixture = project(report);
67
+ try {
68
+ const result = fixture.invoke("outdated");
69
+ assert.equal(result.status, 1);
70
+ assert.match(result.stdout, /Current\s+Wanted\s+Latest/);
71
+ assert.match(result.stdout, /1\.5\.0/);
72
+ assert.match(result.stdout, /packages[\\/]second[\\/]package\.json/);
73
+ assert.match(result.stdout, /2 compatible update/);
74
+ const informational = fixture.invoke("outdated", "latest");
75
+ assert.equal(informational.status, 0);
76
+ assert.doesNotMatch(informational.stdout, /shared/);
77
+ assert.match(fixture.invoke("outdated", "absent").stdout, /No outdated dependency/);
78
+ } finally {
79
+ fixture.clean();
80
+ }
81
+ });
82
+
83
+ test("previews compatible changes and writes each workspace's own target version", () => {
84
+ const fixture = project(report);
85
+ try {
86
+ const preview = fixture.invoke("update", "shared", "--compatible", "--dry-run");
87
+ assert.equal(preview.status, 0, preview.stdout + preview.stderr);
88
+ assert.match(preview.stdout, /Dry run: no files changed/);
89
+ assert.equal(fixture.read("packages/first/package.json").dependencies.shared, "^1.0.0");
90
+ assert.equal(fixture.read("packages/second/package.json").devDependencies.shared, "^2.0.0");
91
+ const updated = fixture.invoke("patch", "shared");
92
+ assert.equal(updated.status, 0, updated.stdout + updated.stderr);
93
+ assert.equal(fixture.read("packages/first/package.json").dependencies.shared, "^1.5.0");
94
+ assert.equal(fixture.read("packages/first/package.json").dependencies.untouched, "^1.0.0");
95
+ assert.equal(fixture.read("packages/second/package.json").devDependencies.shared, "^2.8.0");
96
+ assert.equal(fixture.read("package.json").dependencies.latest, "^1.0.0");
97
+ assert.match(updated.stdout, /Run npm install/);
98
+ assert.match(fixture.invoke("patch", "shared").stdout, /No compatible dependency range changes needed/);
99
+ } finally {
100
+ fixture.clean();
101
+ }
102
+ });
103
+
104
+ test("keeps latest update behavior explicit and reports missing installed dependencies", () => {
105
+ const fixture = project({ ...report, missing: { wanted: "1.0.0", latest: "1.0.0", type: "dependencies" } });
106
+ try {
107
+ assert.equal(fixture.invoke("outdated", "missing").status, 1);
108
+ assert.match(fixture.invoke("outdated", "missing").stdout, /MISSING/);
109
+ assert.equal(fixture.invoke("update", "shared").status, 0);
110
+ assert.equal(fixture.read("packages/first/package.json").dependencies.shared, "^3.0.0");
111
+ assert.equal(fixture.read("packages/second/package.json").devDependencies.shared, "^3.0.0");
112
+ } finally {
113
+ fixture.clean();
114
+ }
115
+ });
116
+ });
package/tools.js ADDED
@@ -0,0 +1,210 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, globSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, matchesGlob, resolve } from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+
7
+ export async function projectTool(name, cwd) {
8
+ const require = createRequire(resolve(cwd, "package.json"));
9
+ let entry;
10
+ try {
11
+ entry = require.resolve(name);
12
+ } catch {
13
+ throw new Error(`Missing project tool "${name}". Install it locally: npm install --save-dev ${name}`);
14
+ }
15
+ const module = await import(pathToFileURL(entry).href);
16
+ return module.default || module;
17
+ }
18
+
19
+ export function toolFiles(project, name, patterns = [], cwd = process.cwd()) {
20
+ const section = project.config.cli?.[name] || {};
21
+ const explicit = patterns.length > 0;
22
+ const source = project.sources[`cli.${name}.include`] || project.sources.include || project.file;
23
+ const base = explicit ? cwd : source ? dirname(source) : cwd;
24
+ const defaults = name === "lint" ? ["**/*.{js,jsx,mjs,cjs,ts,tsx}"] : ["**/*"];
25
+ const include = explicit ? patterns : section.include || project.config.include || (project.config.files ? [] : defaults);
26
+ const excluded = ["**/node_modules", "**/node_modules/**", "**/.git", "**/.git/**"];
27
+ for (const pattern of section.exclude || project.config.exclude || []) {
28
+ const source = project.sources[`cli.${name}.exclude`] || project.sources.exclude || project.file;
29
+ const path = resolve(source ? dirname(source) : cwd, pattern).replaceAll("\\", "/");
30
+ excluded.push(path, path + "/**");
31
+ }
32
+ const output = project.config.compilerOptions?.outDir;
33
+ if (output) {
34
+ const source = project.sources["compilerOptions.outDir"] || project.file;
35
+ excluded.push(resolve(source ? dirname(source) : cwd, output).replaceAll("\\", "/") + "/**");
36
+ }
37
+ const isExcluded = (entry) => {
38
+ const path = resolve(base, entry).replaceAll("\\", "/");
39
+ return (
40
+ path.split("/").some((part) => part === "node_modules" || part === ".git") ||
41
+ excluded.some((pattern) => matchesGlob(path, pattern))
42
+ );
43
+ };
44
+ const files = globSync(
45
+ include.map((pattern) => resolve(base, pattern).replaceAll("\\", "/")),
46
+ {
47
+ cwd: base,
48
+ exclude: isExcluded,
49
+ },
50
+ );
51
+ if (!explicit && !section.include) {
52
+ for (const file of project.config.files || []) files.push(resolve(dirname(project.sources.files || project.file), file));
53
+ }
54
+ return [...new Set(files)].filter((file) => !isExcluded(file) && statSync(file).isFile()).sort();
55
+ }
56
+
57
+ async function lint(project, patterns, fix, cwd) {
58
+ const section = project.config.cli?.lint || {};
59
+ const { ESLint } = await projectTool("eslint", cwd);
60
+ const external = section.config ? (await import(section.config)).default : undefined;
61
+ const inline = section.rules || section.languageOptions || section.options;
62
+ const override = {
63
+ ...section.options,
64
+ ...(section.rules ? { rules: section.rules } : {}),
65
+ ...(section.languageOptions ? { languageOptions: section.languageOptions } : {}),
66
+ };
67
+ const probe = new ESLint({ cwd });
68
+ const hasConfig = await probe.findConfigFile();
69
+ const eslint = new ESLint({
70
+ cwd,
71
+ fix,
72
+ ...(external || inline
73
+ ? { overrideConfig: [...(external ? (Array.isArray(external) ? external : [external]) : []), override] }
74
+ : {}),
75
+ ...(external || (!hasConfig && inline) ? { overrideConfigFile: true } : {}),
76
+ });
77
+ const files = [];
78
+ for (const file of toolFiles(project, "lint", patterns, cwd)) if (!(await eslint.isPathIgnored(file))) files.push(file);
79
+ if (!files.length) return 0;
80
+ const results = await eslint.lintFiles(files);
81
+ if (fix) await ESLint.outputFixes(results);
82
+ const formatted = await (await eslint.loadFormatter("stylish")).format(results);
83
+ if (formatted) console.log(formatted);
84
+ return results.some((result) => result.errorCount || result.warningCount) ? 1 : 0;
85
+ }
86
+
87
+ async function format(project, patterns, write, cwd) {
88
+ const prettier = await projectTool("prettier", cwd);
89
+ const section = project.config.cli?.format || {};
90
+ const external = section.config ? (await import(section.config)).default : undefined;
91
+ let failed = false;
92
+ const ignorePath = resolve(cwd, ".prettierignore");
93
+ for (const file of toolFiles(project, "format", patterns, cwd)) {
94
+ const info = await prettier.getFileInfo(file, {
95
+ ...(existsSync(ignorePath) ? { ignorePath } : {}),
96
+ withNodeModules: false,
97
+ });
98
+ if (info.ignored || !info.inferredParser) continue;
99
+ const options = { ...(external || (await prettier.resolveConfig(file))), ...section.options, filepath: file };
100
+ const original = readFileSync(file, "utf8");
101
+ if (await prettier.check(original, options)) continue;
102
+ if (write) writeFileSync(file, await prettier.format(original, options));
103
+ else failed = true;
104
+ console.log(`${write ? "Formatted" : "Needs formatting"}: ${file}`);
105
+ }
106
+ return failed ? 1 : 0;
107
+ }
108
+
109
+ async function types(project, cwd) {
110
+ if (!project.file) throw new Error("Type checking requires tsconfig.json or jsconfig.json");
111
+ const ts = await projectTool("typescript", cwd);
112
+ const diagnostics = [];
113
+ const host = { ...ts.sys, onUnRecoverableConfigFileDiagnostic: (error) => diagnostics.push(error) };
114
+ const parsed = ts.getParsedCommandLineOfConfigFile(project.file, { noEmit: true }, host);
115
+ if (parsed) {
116
+ diagnostics.push(...parsed.errors);
117
+ if (!parsed.errors.length) {
118
+ const program = ts.createProgram({
119
+ rootNames: parsed.fileNames,
120
+ options: parsed.options,
121
+ projectReferences: parsed.projectReferences,
122
+ });
123
+ diagnostics.push(...ts.getPreEmitDiagnostics(program));
124
+ }
125
+ }
126
+ if (diagnostics.length)
127
+ console.log(
128
+ ts.formatDiagnosticsWithColorAndContext(diagnostics, {
129
+ getCanonicalFileName: (file) => file,
130
+ getCurrentDirectory: () => cwd,
131
+ getNewLine: () => "\n",
132
+ }),
133
+ );
134
+ return diagnostics.some((error) => error.category === ts.DiagnosticCategory.Error) ? 1 : 0;
135
+ }
136
+
137
+ export async function runChecks(names, project, { patterns = [], fix = false, cwd = process.cwd() } = {}) {
138
+ const results = [];
139
+ const runners = { format, lint, types };
140
+ for (const name of names) {
141
+ try {
142
+ const code = name === "types" ? await types(project, cwd) : await runners[name](project, patterns, fix, cwd);
143
+ results.push({ name, code });
144
+ console.log(`${name}: ${code ? "failed" : "passed"}`);
145
+ } catch (error) {
146
+ results.push({ name, code: 1 });
147
+ console.error(`${name}: ${error.message}`);
148
+ }
149
+ }
150
+ console.log(`Checks: ${results.filter((result) => !result.code).length}/${results.length} passed`);
151
+ return results.some((result) => result.code) ? 1 : 0;
152
+ }
153
+
154
+ export function planTasks(name, tasks = {}, scripts = {}, commands = ["check", "lint", "fmt", "types", "test", "bundle"]) {
155
+ const result = [];
156
+ const visited = new Set();
157
+ const active = new Set();
158
+ function visit(name) {
159
+ if (active.has(name)) throw new Error(`Circular task dependency: ${[...active, name].join(" -> ")}`);
160
+ if (visited.has(name)) return;
161
+ const task = Object.hasOwn(tasks, name) ? tasks[name] : undefined;
162
+ if (!task && !Object.hasOwn(scripts, name) && !commands.includes(name))
163
+ throw new Error(`Unknown task or package script "${name}"`);
164
+ active.add(name);
165
+ for (const dependency of task?.dependsOn || []) visit(dependency);
166
+ active.delete(name);
167
+ visited.add(name);
168
+ if (task?.script && !Object.hasOwn(scripts, task.script))
169
+ throw new Error(`Unknown package script "${task.script}" for task "${name}"`);
170
+ result.push({
171
+ name,
172
+ ...(task ? { script: task.script } : Object.hasOwn(scripts, name) ? { script: name } : { command: name }),
173
+ });
174
+ }
175
+ visit(name);
176
+ return result;
177
+ }
178
+
179
+ export function executeNode(args, cwd) {
180
+ return new Promise((resolve, reject) => {
181
+ const child = spawn(process.execPath, args, { cwd, stdio: "inherit", windowsHide: true });
182
+ const stop = () => child.kill("SIGINT");
183
+ process.on("SIGINT", stop);
184
+ child.on("error", (error) => {
185
+ process.removeListener("SIGINT", stop);
186
+ reject(error);
187
+ });
188
+ child.on("close", (code) => {
189
+ process.removeListener("SIGINT", stop);
190
+ resolve(code ?? 1);
191
+ });
192
+ });
193
+ }
194
+
195
+ export async function runTasks(plan, cwd, args = [], execute = executeNode) {
196
+ const bin = fileURLToPath(new URL("./bin.js", import.meta.url));
197
+ for (const task of plan) {
198
+ if (!task.script && !task.command) continue;
199
+ console.log(`Task: ${task.name}`);
200
+ const forwarded = task === plan.at(-1) ? args : [];
201
+ const code = await execute(
202
+ task.script
203
+ ? ["--run", task.script, ...(forwarded.length ? ["--", ...forwarded] : [])]
204
+ : [bin, task.command, ...forwarded],
205
+ cwd,
206
+ );
207
+ if (code) return code;
208
+ }
209
+ return 0;
210
+ }
package/utils.js CHANGED
@@ -2,9 +2,10 @@ import fs from "@vistta/fs";
2
2
  import { assign, async, extract, remove } from "@vistta/utils";
3
3
  import { spawn } from "node:child_process";
4
4
  import { platform } from "node:os";
5
- import { isAbsolute } from "node:path";
6
- import { fileURLToPath, pathToFileURL } from "node:url";
7
- import { inc, satisfies, valid } from "semver";
5
+ import { relative } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { inc, valid } from "semver";
8
+ import { loadProjectConfig, mergeConfig } from "./config.js";
8
9
 
9
10
  export const CWD = process.cwd();
10
11
  export const FALLBACK = "*";
@@ -48,95 +49,70 @@ export async function importEnv(filepath) {
48
49
  }
49
50
 
50
51
  export async function importConfig(options = {}) {
51
- try {
52
- const helper = async (file) => {
53
- const {
54
- extends: parent,
55
- cliOptions: cli = {},
56
- compilerOptions: compiler = {},
57
- bundlerOptions: bundler = {},
58
- } = await importJSON(file);
59
- if (cli.commands) {
60
- const commands = Object.keys(cli.commands);
61
- const dirname = fs.dirname(file);
62
- for (let i = 0, len = commands.length; i < len; i++)
63
- cli.commands[commands[i]] = resolve(dirname, cli.commands[commands[i]]);
64
- }
65
- if (parent) return assign(await helper(ensurePath(parent)), { cli, compiler, bundler });
66
- return { cli, compiler, bundler };
67
- };
68
- let file = fs.resolve(process.cwd(), "tsconfig.json");
69
- if (!fs.existsSync(file)) file = fs.resolve(process.cwd(), "jsconfig.json");
70
- if (!fs.existsSync(file)) return options;
71
- return assign(options, await helper(ensurePath(file)));
72
- } catch (e) {
73
- console.error(e.trace || e.message);
74
- return options;
75
- }
52
+ const { config } = loadProjectConfig();
53
+ return mergeConfig(options, {
54
+ cli: config.cli || {},
55
+ compiler: config.compilerOptions || {},
56
+ bundler: config.bundlerOptions || {},
57
+ });
76
58
  }
77
59
 
78
60
  export async function getOutdatedPackages(dirname) {
79
- const packages = (await getProjectLock(dirname))?.packages || {};
80
- const data = {};
81
- const promises = [];
82
- async function processPackage(filepath, workspace) {
83
- if (!fs.existsSync(filepath)) {
84
- if (workspace) return;
85
- else throw new Error("No Package found");
86
- }
87
- const { dependencies, devDependencies, workspaces } = await importJSON(filepath);
88
- await fetchPackageUpdates(filepath, dependencies);
89
- await fetchPackageUpdates(filepath, devDependencies, true);
90
- if (!(workspaces?.length > 0)) return;
91
- const workspaceDirnames = await resolveWorkspacesDirnames(workspaces);
92
- for (let i = 0, len = workspaceDirnames.length; i < len; i++)
93
- await processPackage(fs.resolve(workspaceDirnames[i], "package.json"), true);
94
- }
95
- async function fetchPackageUpdates(path, deps = {}, dev) {
96
- const keys = Object.keys(deps);
97
- for (let i = 0, len = keys.length; i < len; i++) {
98
- const packageName = keys[i];
99
- const { version, resolved, link } = packages["node_modules/" + packageName] || {};
100
- if (link) continue;
101
- if (!version) {
102
- data[packageName] = {
103
- package: path,
104
- dev,
105
- };
106
- promises.push({ name: packageName });
107
- continue;
61
+ const lock = await getProjectLock(dirname);
62
+ const packages = lock.packages || {};
63
+ if (!fs.existsSync(fs.resolve(dirname, "package.json"))) throw new Error("No Package found");
64
+ const output = await new Promise((resolve, reject) => {
65
+ const npmPath = process.env.npm_execpath;
66
+ const args = ["outdated", "--json", "--long", "--include=dev"];
67
+ const windowsShell = !npmPath && platform() === "win32";
68
+ const command = npmPath ? process.execPath : windowsShell ? process.env.ComSpec || "cmd.exe" : "npm";
69
+ const child = spawn(
70
+ command,
71
+ npmPath ? [npmPath, ...args] : windowsShell ? ["/d", "/s", "/c", "npm " + args.join(" ")] : args,
72
+ {
73
+ cwd: dirname,
74
+ windowsHide: true,
75
+ },
76
+ );
77
+ let stdout = "";
78
+ let stderr = "";
79
+ child.stdout.on("data", (value) => {
80
+ stdout += value;
81
+ });
82
+ child.stderr.on("data", (value) => {
83
+ stderr += value;
84
+ });
85
+ child.on("error", reject);
86
+ child.on("close", (code) => {
87
+ try {
88
+ const data = JSON.parse(stdout || "{}");
89
+ if ((code !== 0 && code !== 1) || data.error)
90
+ reject(new Error(data.error?.summary || stderr || `npm outdated exited with code ${code}`));
91
+ else resolve(data);
92
+ } catch (error) {
93
+ reject(new Error(stderr || error.message));
108
94
  }
109
- promises.push(
110
- new Promise((resolve, reject) =>
111
- fetch(resolved ? resolved.split("/-/")[0] + "/latest" : `https://registry.npmjs.org/${packageName}/latest`)
112
- .then((response) => response.json())
113
- .then(resolve)
114
- .catch(reject),
115
- ),
116
- );
117
- data[packageName] = {
118
- package: path,
119
- current: version,
120
- wanted: deps[packageName],
121
- dev,
122
- };
123
- }
124
- }
95
+ });
96
+ });
125
97
  const result = [];
126
- await processPackage(fs.resolve(dirname, "package.json"));
127
- const values = await Promise.all(promises);
128
- for (let i = 0, len = values.length; i < len; i++) {
129
- const module = data[values[i]?.name];
130
- module.name = values[i]?.name;
131
- module.latest = values[i]?.version;
132
- if (!module.current) {
133
- result.push(module);
134
- continue;
98
+ for (const [name, entries] of Object.entries(output)) {
99
+ for (const entry of Array.isArray(entries) ? entries : [entries]) {
100
+ const location = entry.location
101
+ ? relative(lock.dirname, entry.location).replaceAll("\\", "/")
102
+ : "node_modules/" + name;
103
+ if (packages[location]?.link || packages["node_modules/" + name]?.link) continue;
104
+ if (entry.type !== "dependencies" && entry.type !== "devDependencies") continue;
105
+ const packagePath = fs.resolve(lock.dirname, entry.dependedByLocation || "", "package.json");
106
+ if (relative(lock.dirname, dirname) && relative(dirname, packagePath) !== "package.json") continue;
107
+ result.push({
108
+ name,
109
+ package: packagePath,
110
+ current: entry.current,
111
+ wanted: entry.wanted,
112
+ latest: entry.latest,
113
+ dev: entry.type === "devDependencies",
114
+ });
135
115
  }
136
- if (!module || !module?.latest || !module?.wanted) throw new Error(`Internal Error`);
137
- if (module.current === module.latest) continue;
138
- module.wanted = satisfies(module.latest, module.wanted) ? module.latest : module.current;
139
- result.push(module);
140
116
  }
141
117
  return result;
142
118
  }
@@ -232,42 +208,15 @@ export function markAttributes(attrs) {
232
208
  export { assign, async, extract, remove };
233
209
 
234
210
  async function getProjectLock(path) {
235
- if (fs.existsSync(path + "/package-lock.json")) return await importJSON(fs.resolve(path, "package-lock.json"));
211
+ if (fs.existsSync(path + "/package-lock.json"))
212
+ return { ...(await importJSON(fs.resolve(path, "package-lock.json"))), dirname: path };
236
213
  const newPath = fs.resolve(path, "..");
237
214
  if (newPath === path) throw new Error("No Package Lock found.");
238
215
  return await getProjectLock(newPath);
239
216
  }
240
217
 
241
- async function resolveWorkspacesDirnames(workspaces) {
242
- const paths = [];
243
- for (let i = 0, len = workspaces.length; i < len; i++) {
244
- if (!workspaces[i].includes("*")) {
245
- paths.push(workspaces[i]);
246
- continue;
247
- }
248
- const entries = fs.glob(workspaces[i]);
249
- let entry = (await entries.next())?.value;
250
- while (entry) {
251
- paths.push(entry);
252
- entry = (await entries.next())?.value;
253
- }
254
- }
255
- return paths;
256
- }
257
-
258
218
  function evaluate(value) {
259
219
  if (value == null || value === "true") return true;
260
220
  if (value === "false") return false;
261
221
  return value;
262
222
  }
263
-
264
- function ensurePath(value) {
265
- try {
266
- if (isAbsolute(value)) return value;
267
- value = import.meta.resolve(value);
268
- if (isAbsolute(value)) return value;
269
- return fileURLToPath(value);
270
- } catch {
271
- return value;
272
- }
273
- }