@vistta/cli 1.0.1 → 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.
- package/LICENSE +674 -188
- package/README.md +150 -11
- package/bin.js +14 -4
- package/classes/bundler.js +172 -36
- package/classes/loader.js +1 -1
- package/classes/runtime.js +25 -15
- package/classes/test-runner.js +210 -0
- package/commands/bundle.js +4 -3
- package/commands/default.js +3 -1
- package/commands/project.js +110 -74
- package/commands/quality.js +52 -0
- package/commands/task.js +23 -0
- package/commands/test.js +39 -171
- package/config.js +128 -0
- package/config.schema.json +150 -0
- package/global.d.ts +68 -0
- package/index.js +4 -0
- package/loaders/bundler.js +3 -1
- package/loaders/index.js +2 -2
- package/main.js +1 -1
- package/package.json +17 -14
- package/register.js +15 -7
- package/tests/bundler.test.js +118 -0
- package/tests/config.test.js +217 -0
- package/tests/index.test.js +140 -0
- package/tests/loader.test.js +23 -0
- package/tests/project.test.js +116 -0
- package/tests/runtime.test.js +76 -0
- package/tests/test-runner.test.js +83 -0
- package/tools.js +210 -0
- package/tsconfig.json +4 -5
- package/utils.js +122 -108
- package/.gitea/workflows/prerelease.yml +0 -62
- package/.gitea/workflows/release.yml +0 -124
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{ only?: boolean; filter?: string }} TestRunnerOptions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* name: string;
|
|
8
|
+
* start?: number;
|
|
9
|
+
* end?: number;
|
|
10
|
+
* status?: "pass" | "fail";
|
|
11
|
+
* result?: any;
|
|
12
|
+
* error?: any;
|
|
13
|
+
* }} TestResult
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{ only: boolean; skip: boolean; name: string; callback: () => any }} SuiteDefinition
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{ only?: boolean; skip?: boolean; name?: string; tests: TestResult[]; time?: number }} SuiteResult
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* toEqual: (value: any, unit?: string) => void;
|
|
27
|
+
* toBeLessThan: (value: any, unit?: string) => void;
|
|
28
|
+
* toBeGreaterThan: (value: any, unit?: string) => void;
|
|
29
|
+
* toInclude: (value: any) => void;
|
|
30
|
+
* toMatch: (regex: string | RegExp) => void;
|
|
31
|
+
* toThrow: (message?: string) => void;
|
|
32
|
+
* }} ExpectMatcherSet
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {ExpectMatcherSet & { not: ExpectMatcherSet }} ExpectMatchers
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export class TestRunner {
|
|
40
|
+
/** @type {TestRunnerOptions} */
|
|
41
|
+
#options = {};
|
|
42
|
+
/** @type {SuiteDefinition[]} */
|
|
43
|
+
#suites = [];
|
|
44
|
+
/** @type {SuiteResult[]} */
|
|
45
|
+
#results;
|
|
46
|
+
/** @type {SuiteResult | null} */
|
|
47
|
+
#active;
|
|
48
|
+
/** @type {number} */
|
|
49
|
+
#running;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {TestRunnerOptions} [options]
|
|
53
|
+
*/
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.#options = options;
|
|
56
|
+
this.suite = this.#suite.bind(this);
|
|
57
|
+
this.suite.only = (name, callback) => this.#suite(name, callback, { only: true });
|
|
58
|
+
this.suite.skip = (name, callback) => this.#suite(name, callback, { skip: true });
|
|
59
|
+
this.test = this.#test.bind(this);
|
|
60
|
+
this.test.only = (name, callback) => this.#test(name, callback, { only: true });
|
|
61
|
+
this.test.skip = (name, callback) => this.#test(name, callback, { skip: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {Promise<SuiteResult[]>}
|
|
66
|
+
*/
|
|
67
|
+
async run() {
|
|
68
|
+
this.#running = 0;
|
|
69
|
+
this.#results = [];
|
|
70
|
+
for (const suite of this.#suites) {
|
|
71
|
+
this.#active = { only: suite.only, name: suite.name, skip: suite.skip, tests: [] };
|
|
72
|
+
try {
|
|
73
|
+
await suite.callback();
|
|
74
|
+
} catch {
|
|
75
|
+
/* Do Nothing */
|
|
76
|
+
}
|
|
77
|
+
this.#results.push(this.#active);
|
|
78
|
+
this.#active = null;
|
|
79
|
+
}
|
|
80
|
+
while (this.#running > 0) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
81
|
+
return this.#results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} name
|
|
86
|
+
* @param {() => any} callback
|
|
87
|
+
* @param {{ only?: boolean, skip?: boolean }} options
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async #suite(name, callback, { only, skip } = {}) {
|
|
91
|
+
if (this.#active) throw new Error("Suites cannot be stacked");
|
|
92
|
+
this.#suites.push({ only, skip, name, callback });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} name
|
|
97
|
+
* @param {() => any | Promise<any>} callback
|
|
98
|
+
* @param {{ only?: boolean, skip?: boolean }} options
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
*/
|
|
101
|
+
async #test(name, callback, { only, skip } = {}) {
|
|
102
|
+
if (
|
|
103
|
+
(this.#options.filter && !name.match(new RegExp(this.#options.filter, "i"))) ||
|
|
104
|
+
(this.#options.only && !(only || this.#active?.only))
|
|
105
|
+
)
|
|
106
|
+
return;
|
|
107
|
+
const test = { name };
|
|
108
|
+
if (skip || this.#active?.skip) {
|
|
109
|
+
test.status = "skip";
|
|
110
|
+
this.#active?.tests.push(test);
|
|
111
|
+
return;
|
|
112
|
+
} else this.#running++;
|
|
113
|
+
test.start = performance();
|
|
114
|
+
if (this.#active) this.#active.tests.push(test);
|
|
115
|
+
else this.#results.push({ tests: [test], time: test.time });
|
|
116
|
+
try {
|
|
117
|
+
test.result = await callback();
|
|
118
|
+
test.status = "pass";
|
|
119
|
+
test.end = performance();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
test.status = "fail";
|
|
122
|
+
test.error = error;
|
|
123
|
+
test.end = performance();
|
|
124
|
+
}
|
|
125
|
+
this.#running--;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {any} target
|
|
130
|
+
* @param {string} [label]
|
|
131
|
+
* @returns {ExpectMatchers}
|
|
132
|
+
*/
|
|
133
|
+
expect(target, label) {
|
|
134
|
+
return {
|
|
135
|
+
not: {
|
|
136
|
+
toEqual: (value, unit = "") => {
|
|
137
|
+
if (target !== value) return;
|
|
138
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not equal ${value}${unit}`);
|
|
139
|
+
},
|
|
140
|
+
toBeLessThan: (value, unit = "") => {
|
|
141
|
+
if (target >= value) return;
|
|
142
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not be less than ${value}${unit}`);
|
|
143
|
+
},
|
|
144
|
+
toBeGreaterThan: (value, unit = "") => {
|
|
145
|
+
if (target <= value) return;
|
|
146
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not be greater than ${value}${unit}`);
|
|
147
|
+
},
|
|
148
|
+
toInclude: (value) => {
|
|
149
|
+
if (target.indexOf(value) === -1) return;
|
|
150
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not include ${value}`);
|
|
151
|
+
},
|
|
152
|
+
toMatch: (regex) => {
|
|
153
|
+
if (!target.match(new RegExp(regex))) return;
|
|
154
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not match ${regex}`);
|
|
155
|
+
},
|
|
156
|
+
toThrow: (message) => {
|
|
157
|
+
try {
|
|
158
|
+
target();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (!message)
|
|
161
|
+
throw new Error(`expected ${label ? label + "()" : "function()"} to not throw, but got ${error.message}`);
|
|
162
|
+
if (error.message === message)
|
|
163
|
+
throw new Error(
|
|
164
|
+
`expected ${label ? label + "()" : "function()"} to not throw ${message}, but got ${error.message}`,
|
|
165
|
+
);
|
|
166
|
+
if (error.message.match(new RegExp(message)))
|
|
167
|
+
throw new Error(
|
|
168
|
+
`expected ${label ? label + "()" : "function()"} to not throw ${message}, but got ${error.message}`,
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
toEqual: (value, unit = "") => {
|
|
175
|
+
if (target === value) return;
|
|
176
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to equal ${value}${unit}`);
|
|
177
|
+
},
|
|
178
|
+
toBeLessThan: (value, unit = "") => {
|
|
179
|
+
if (target < value) return;
|
|
180
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to be less than ${value}${unit}`);
|
|
181
|
+
},
|
|
182
|
+
toBeGreaterThan: (value, unit = "") => {
|
|
183
|
+
if (target > value) return;
|
|
184
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to be greater than ${value}${unit}`);
|
|
185
|
+
},
|
|
186
|
+
toInclude: (value) => {
|
|
187
|
+
if (target.indexOf(value) > -1) return;
|
|
188
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to include ${value}`);
|
|
189
|
+
},
|
|
190
|
+
toMatch: (regex) => {
|
|
191
|
+
if (target.match(new RegExp(regex))) return;
|
|
192
|
+
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to match ${regex}`);
|
|
193
|
+
},
|
|
194
|
+
toThrow: (message) => {
|
|
195
|
+
try {
|
|
196
|
+
target();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (!message) return;
|
|
199
|
+
if (error.message === message) return;
|
|
200
|
+
if (error.message.match(new RegExp(message))) return;
|
|
201
|
+
throw new Error(`expected ${label ? label + "()" : "function()"} to throw ${message}, but got ${error.message}`);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function performance() {
|
|
209
|
+
return globalThis.performance.now();
|
|
210
|
+
}
|
package/commands/bundle.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { Bundler } from "../classes/bundler.js";
|
|
3
3
|
import { Runtime } from "../classes/runtime.js";
|
|
4
|
+
import { parseArgs } from "../utils.js";
|
|
4
5
|
import DefaultCommand from "./default.js";
|
|
5
6
|
|
|
6
7
|
export default class BundlerCommand extends DefaultCommand {
|
|
7
|
-
help() {
|
|
8
|
+
async help() {
|
|
8
9
|
console.print("Bundles the specified entry file");
|
|
9
10
|
console.print("\nUsage:");
|
|
10
11
|
console.print("vistta bundle [filename] [outdir]\tBundles the specified entry file");
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
async main(_, ...argv) {
|
|
14
|
-
let [args, options] =
|
|
15
|
+
let [args, options] = parseArgs(argv);
|
|
15
16
|
if (args.length === 0) return this.help();
|
|
16
|
-
options = Object.assign({},
|
|
17
|
+
options = Object.assign({}, process.vistta?.options?.bundler || {}, options, { outdir: args[1] });
|
|
17
18
|
new Bundler(new Runtime(global.vistta)).run(resolve(process.cwd(), args[0]), options);
|
|
18
19
|
}
|
|
19
20
|
}
|
package/commands/default.js
CHANGED
|
@@ -39,7 +39,9 @@ export default class DefaultCommand extends Command {
|
|
|
39
39
|
const modules = await (await import("../utils.js"))?.getOutdatedPackages(process.cwd());
|
|
40
40
|
for (let i = 0, len = modules.length; i < len; i++) {
|
|
41
41
|
const { name, current, wanted, latest } = modules[i];
|
|
42
|
-
if (current
|
|
42
|
+
if (!current) console.print(`${COLORS.YELLOW}Module "${name}" is missing${COLORS.RESET}`);
|
|
43
|
+
else if (current === wanted)
|
|
44
|
+
console.print(`${COLORS.CYAN}Module "${name}" has a new version (${latest})${COLORS.RESET}`);
|
|
43
45
|
else console.print(`${COLORS.YELLOW}Module "${name}" is outdated (${latest})${COLORS.RESET}`);
|
|
44
46
|
}
|
|
45
47
|
} catch {
|
package/commands/project.js
CHANGED
|
@@ -1,101 +1,137 @@
|
|
|
1
|
-
import { COLORS } from "@vistta/console";
|
|
2
1
|
import fs from "@vistta/fs";
|
|
3
|
-
import {
|
|
2
|
+
import { relative } from "node:path";
|
|
4
3
|
import { Command } from "../classes/command.js";
|
|
5
|
-
import { getOutdatedPackages, importJSON, incrementPackageVersion } from "../utils.js";
|
|
4
|
+
import { getOutdatedPackages, importJSON, incrementPackageVersion, parseArgs } from "../utils.js";
|
|
6
5
|
|
|
7
6
|
const HELP = {
|
|
8
|
-
name: "
|
|
9
|
-
version:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
name: ["name", "Print the project name."],
|
|
8
|
+
version: [
|
|
9
|
+
"version [increment|version] [prerelease-id]",
|
|
10
|
+
"Print or change the project version. Increments: major, minor, patch, premajor, preminor, prepatch, prerelease.",
|
|
11
|
+
],
|
|
12
|
+
outdated: [
|
|
13
|
+
"outdated [dependency]",
|
|
14
|
+
"Compare installed, wanted (within the declared range), and latest dependency versions.",
|
|
15
|
+
],
|
|
16
|
+
update: [
|
|
17
|
+
"update [dependency] [--compatible] [--dry-run]",
|
|
18
|
+
"Update dependency ranges to latest; this may include major upgrades. --compatible selects wanted versions instead.",
|
|
19
|
+
],
|
|
20
|
+
patch: ["patch [dependency] [--dry-run]", "Alias for update --compatible. This is range-compatible, not patch-only."],
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
export default class extends Command {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
case "name":
|
|
26
|
-
console.print(capitalize(HELP["name"]));
|
|
27
|
-
console.print("\nUsage:");
|
|
28
|
-
console.print("vistta project name");
|
|
29
|
-
break;
|
|
30
|
-
case "version":
|
|
31
|
-
console.print(capitalize(HELP["version"]) + " or " + HELP["version [increment]"]);
|
|
32
|
-
console.print("\nUsage:");
|
|
33
|
-
console.print("vistta project version");
|
|
34
|
-
console.print("vistta project version [increment]");
|
|
35
|
-
break;
|
|
36
|
-
case "outdated":
|
|
37
|
-
console.print(capitalize(HELP["outdated"]));
|
|
38
|
-
console.print("\nUsage:");
|
|
39
|
-
console.print("vistta project outdated");
|
|
40
|
-
break;
|
|
41
|
-
case "patch":
|
|
42
|
-
console.print(capitalize(HELP["patch"]) + " or " + HELP["patch [module]"]);
|
|
43
|
-
console.print("\nUsage:");
|
|
44
|
-
console.print("vistta project patch");
|
|
45
|
-
console.print("vistta project patch [module]");
|
|
46
|
-
break;
|
|
47
|
-
case "update":
|
|
48
|
-
console.print(capitalize(HELP["update"]) + " or " + HELP["update [module]"]);
|
|
49
|
-
console.print("\nUsage:");
|
|
50
|
-
console.print("vistta project update");
|
|
51
|
-
console.print("vistta project update [module]");
|
|
52
|
-
break;
|
|
53
|
-
default:
|
|
54
|
-
console.print("Outputs the current project name or version, or updates the version");
|
|
55
|
-
console.print("\nUsage:");
|
|
56
|
-
Object.keys(HELP).forEach((key) => console.print(`vistta project ${key}\t\t\t${HELP[key]}`));
|
|
57
|
-
break;
|
|
24
|
+
async help(_, command) {
|
|
25
|
+
if (command && !Object.hasOwn(HELP, command))
|
|
26
|
+
throw new Error(`Unknown project command "${command}". Run vistta project --help.`);
|
|
27
|
+
console.print("Project metadata and dependency maintenance\n");
|
|
28
|
+
for (const [usage, description] of command ? [HELP[command]] : Object.values(HELP)) {
|
|
29
|
+
console.print(` vistta project ${usage}\n ${description}\n`);
|
|
58
30
|
}
|
|
31
|
+
if (!command || ["outdated", "update", "patch"].includes(command))
|
|
32
|
+
console.print(
|
|
33
|
+
"Wanted respects npm policies and dependency ranges. Updates edit package.json only; run npm install afterward.",
|
|
34
|
+
);
|
|
59
35
|
}
|
|
60
36
|
|
|
61
37
|
async main(_, command, ...args) {
|
|
62
|
-
|
|
38
|
+
if (!command || command === "help") return this.help(_, args[0]);
|
|
39
|
+
if (!Object.hasOwn(HELP, command)) return this.help(_, command);
|
|
40
|
+
const [values, options] = parseArgs(args);
|
|
41
|
+
const allowed = command === "update" ? ["compatible", "dry-run"] : command === "patch" ? ["dry-run"] : [];
|
|
42
|
+
for (const [name, value] of Object.entries(options)) {
|
|
43
|
+
if (!allowed.includes(name)) throw new Error(`Unknown option --${name} for project ${command}`);
|
|
44
|
+
if (typeof value !== "boolean") throw new Error(`--${name} must be a boolean flag`);
|
|
45
|
+
}
|
|
46
|
+
const limit = command === "name" ? 0 : command === "version" ? 2 : 1;
|
|
47
|
+
if (values.length > limit) throw new Error(`Too many arguments. Run vistta project ${command} --help.`);
|
|
63
48
|
switch (command) {
|
|
64
49
|
case "name":
|
|
65
50
|
return console.print(process.env.PROJECT_NAME);
|
|
66
51
|
case "version":
|
|
67
|
-
if (!
|
|
52
|
+
if (!values[0]) return console.print(process.env.PROJECT_VERSION);
|
|
68
53
|
return console.print(
|
|
69
|
-
await incrementPackageVersion(fs.resolve(process.env.PROJECT_PATH, "package.json"),
|
|
54
|
+
await incrementPackageVersion(fs.resolve(process.env.PROJECT_PATH, "package.json"), values[0], values[1]),
|
|
70
55
|
);
|
|
71
56
|
case "outdated":
|
|
72
57
|
case "patch":
|
|
73
58
|
case "update":
|
|
74
|
-
|
|
59
|
+
process.exit(await dependencies(command, values[0], options));
|
|
75
60
|
}
|
|
76
|
-
return this.help(_, command);
|
|
77
61
|
}
|
|
78
62
|
}
|
|
79
63
|
|
|
80
|
-
async function
|
|
64
|
+
async function dependencies(command, dependency, options) {
|
|
81
65
|
const modules = await getOutdatedPackages(process.cwd());
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
packageObj[dev ? "devDependencies" : "dependencies"][name] = "^" + (command === "patch" ? wanted : latest);
|
|
91
|
-
await fs.writeFile(packagePath, JSON.stringify(packageObj, null, 2));
|
|
92
|
-
update++;
|
|
93
|
-
} else if (current === wanted)
|
|
94
|
-
console.print(`${COLORS.CYAN}Module "${name}" has a new version (${latest})${COLORS.RESET}`);
|
|
95
|
-
else (console.print(`${COLORS.YELLOW}Module "${name}" is outdated (${latest})${COLORS.RESET}`), (valid = false));
|
|
66
|
+
const selected = modules.filter((entry) => !dependency || entry.name === dependency);
|
|
67
|
+
if (!selected.length) {
|
|
68
|
+
console.print(
|
|
69
|
+
dependency
|
|
70
|
+
? `No outdated dependency "${dependency}" found in this scope.`
|
|
71
|
+
: "All checked dependencies are up to date.",
|
|
72
|
+
);
|
|
73
|
+
return 0;
|
|
96
74
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
75
|
+
if (command === "outdated") {
|
|
76
|
+
const rows = [
|
|
77
|
+
["Dependency", "Current", "Wanted", "Latest", "Manifest", "Type"],
|
|
78
|
+
...selected.map((entry) => [
|
|
79
|
+
entry.name,
|
|
80
|
+
entry.current || "MISSING",
|
|
81
|
+
entry.wanted || "-",
|
|
82
|
+
entry.latest || "-",
|
|
83
|
+
relative(process.cwd(), entry.package) || "package.json",
|
|
84
|
+
entry.dev ? "dev" : "prod",
|
|
85
|
+
]),
|
|
86
|
+
];
|
|
87
|
+
const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
|
|
88
|
+
for (const row of rows)
|
|
89
|
+
console.print(
|
|
90
|
+
row
|
|
91
|
+
.map((value, column) => value.padEnd(widths[column]))
|
|
92
|
+
.join(" ")
|
|
93
|
+
.trimEnd(),
|
|
94
|
+
);
|
|
95
|
+
const compatible = selected.filter((entry) => !entry.current || entry.current !== entry.wanted).length;
|
|
96
|
+
console.print(
|
|
97
|
+
`\n${compatible} compatible update(s) or missing dependency(s); ${selected.length - compatible} latest-only update(s).`,
|
|
98
|
+
);
|
|
99
|
+
console.print("Compatible updates cause a nonzero exit status; latest-only updates are informational.");
|
|
100
|
+
return compatible ? 1 : 0;
|
|
101
|
+
}
|
|
102
|
+
const compatible = command === "patch" || options.compatible === true;
|
|
103
|
+
const changes = new Map();
|
|
104
|
+
let count = 0;
|
|
105
|
+
for (const entry of selected) {
|
|
106
|
+
const target = compatible ? entry.wanted : entry.latest;
|
|
107
|
+
if (!target || entry.current === target) continue;
|
|
108
|
+
const manifest = changes.get(entry.package) || (await importJSON(entry.package));
|
|
109
|
+
const section = entry.dev ? "devDependencies" : "dependencies";
|
|
110
|
+
const previous = manifest[section]?.[entry.name];
|
|
111
|
+
if (typeof previous !== "string") throw new Error(`Dependency "${entry.name}" is missing from ${entry.package}`);
|
|
112
|
+
const next = "^" + target;
|
|
113
|
+
if (previous === next) continue;
|
|
114
|
+
manifest[section][entry.name] = next;
|
|
115
|
+
changes.set(entry.package, manifest);
|
|
116
|
+
count++;
|
|
117
|
+
console.print(
|
|
118
|
+
`${options["dry-run"] ? "Would update" : "Update"}: ${entry.name} ${previous} -> ${next} (${relative(process.cwd(), entry.package)}, ${section})`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (!count) {
|
|
122
|
+
console.print(
|
|
123
|
+
`No ${compatible ? "compatible " : ""}dependency range changes needed. Run npm install if installed versions are behind.`,
|
|
124
|
+
);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
if (!options["dry-run"]) {
|
|
128
|
+
for (const [path, manifest] of changes) await fs.writeFile(path, JSON.stringify(manifest, null, 2) + "\n");
|
|
129
|
+
}
|
|
130
|
+
console.print(
|
|
131
|
+
`\n${options["dry-run"] ? "Would change" : "Changed"} ${count} dependency range(s) in ${changes.size} manifest(s).`,
|
|
132
|
+
);
|
|
133
|
+
console.print(
|
|
134
|
+
options["dry-run"] ? "Dry run: no files changed." : "Run npm install to update installed packages and the lockfile.",
|
|
135
|
+
);
|
|
136
|
+
return 0;
|
|
101
137
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from "../classes/command.js";
|
|
2
|
+
import { loadProjectConfig, mergeConfig } from "../config.js";
|
|
3
|
+
import { runChecks } from "../tools.js";
|
|
4
|
+
import { parseArgs } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
export default class extends Command {
|
|
7
|
+
help() {
|
|
8
|
+
console.log(
|
|
9
|
+
"vistta check [patterns] [--fix]\nvistta lint [patterns] [--fix]\nvistta fmt [patterns] [--write]\nvistta types\nvistta config [--sources]",
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async main(command, ...argv) {
|
|
14
|
+
const [patterns, options] = parseArgs(argv);
|
|
15
|
+
const allowed = command === "config" ? ["sources"] : command === "fmt" ? ["write", "check", "fix"] : ["fix"];
|
|
16
|
+
for (const [option, value] of Object.entries(options)) {
|
|
17
|
+
if (!allowed.includes(option)) throw new Error(`Unknown option --${option}`);
|
|
18
|
+
if (typeof value !== "boolean") throw new Error(`--${option} must be a boolean flag`);
|
|
19
|
+
}
|
|
20
|
+
if (["types", "config"].includes(command) && patterns.length)
|
|
21
|
+
throw new Error(`${command} does not accept file patterns`);
|
|
22
|
+
if (command === "fmt" && options.check && (options.write || options.fix))
|
|
23
|
+
throw new Error("Use either --check or --write, not both");
|
|
24
|
+
const project = loadProjectConfig();
|
|
25
|
+
if (command === "config") {
|
|
26
|
+
const defaults = {
|
|
27
|
+
cli: {
|
|
28
|
+
check: { format: true, lint: true, types: true },
|
|
29
|
+
lint: { engine: "eslint" },
|
|
30
|
+
format: { engine: "prettier" },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const config = mergeConfig(defaults, project.config);
|
|
34
|
+
const sources = {
|
|
35
|
+
"cli.check.format": "<default>",
|
|
36
|
+
"cli.check.lint": "<default>",
|
|
37
|
+
"cli.check.types": "<default>",
|
|
38
|
+
"cli.lint.engine": "<default>",
|
|
39
|
+
"cli.format.engine": "<default>",
|
|
40
|
+
...project.sources,
|
|
41
|
+
};
|
|
42
|
+
console.print(JSON.stringify(options.sources ? { file: project.file, config, sources } : config, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const names =
|
|
46
|
+
command === "check"
|
|
47
|
+
? ["format", "lint", "types"].filter((name) => project.config.cli?.check?.[name] !== false)
|
|
48
|
+
: [command === "fmt" ? "format" : command];
|
|
49
|
+
const code = await runChecks(names, project, { patterns, fix: options.fix === true || options.write === true });
|
|
50
|
+
process.exit(code);
|
|
51
|
+
}
|
|
52
|
+
}
|
package/commands/task.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { Command } from "../classes/command.js";
|
|
4
|
+
import { loadProjectConfig } from "../config.js";
|
|
5
|
+
import { planTasks, runTasks } from "../tools.js";
|
|
6
|
+
|
|
7
|
+
export default class extends Command {
|
|
8
|
+
help() {
|
|
9
|
+
console.log("vistta task <name> [-- args]\nvistta run <name> [-- args] (unless overridden by a framework)");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async main(_, name, ...args) {
|
|
13
|
+
if (!name) return this.help();
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const { config } = loadProjectConfig(cwd);
|
|
16
|
+
const { scripts = {} } = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
|
|
17
|
+
const commands = Object.keys(process.vistta.options.cli.commands).filter(
|
|
18
|
+
(name) => !["default", "run", "task"].includes(name),
|
|
19
|
+
);
|
|
20
|
+
const plan = planTasks(name, config.cli?.tasks, scripts, commands);
|
|
21
|
+
process.exit(await runTasks(plan, cwd, args[0] === "--" ? args.slice(1) : args));
|
|
22
|
+
}
|
|
23
|
+
}
|