@vistta/cli 1.1.0 → 1.2.1
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/README.md +184 -11
- package/bin.js +4 -0
- package/commands/project.js +109 -69
- package/commands/quality.js +52 -0
- package/commands/task.js +23 -0
- package/commands/test.js +1 -1
- package/config.js +128 -0
- package/config.schema.json +150 -0
- package/index.js +3 -0
- package/package.json +8 -4
- package/register.js +7 -0
- package/tests/config.test.js +217 -0
- package/tests/index.test.js +140 -0
- package/tests/project.test.js +116 -0
- package/tools.js +210 -0
- package/utils.js +64 -115
|
@@ -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 {
|
|
6
|
-
import {
|
|
7
|
-
import { inc,
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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"))
|
|
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
|
-
}
|