@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
package/commands/test.js
CHANGED
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import { COLORS } from "@vistta/console";
|
|
2
2
|
import fs from "@vistta/fs";
|
|
3
|
+
import { TestRunner } from "../classes/test-runner.js";
|
|
3
4
|
import { parseArgs } from "../utils.js";
|
|
4
5
|
import DefaultCommand from "./default.js";
|
|
5
6
|
|
|
6
7
|
const cwd = process.cwd();
|
|
7
8
|
|
|
8
9
|
export default class extends DefaultCommand {
|
|
9
|
-
#
|
|
10
|
-
#results = [];
|
|
11
|
-
#failed;
|
|
12
|
-
#suite;
|
|
13
|
-
#runningCounter = 0;
|
|
10
|
+
#testRunner;
|
|
14
11
|
|
|
15
12
|
constructor() {
|
|
16
13
|
super();
|
|
17
|
-
const suite = this.suite.bind(this, false);
|
|
18
|
-
suite.only = this.suite.bind(this, true);
|
|
19
14
|
this.define("env", "NODE_ENV", "testing");
|
|
20
|
-
this.define("global", "suite", suite);
|
|
21
|
-
const test = this.test.bind(this, false);
|
|
22
|
-
test.only = this.test.bind(this, true);
|
|
23
|
-
this.define("global", "test", test);
|
|
24
|
-
this.define("global", "expect", this.expect.bind(this));
|
|
25
15
|
}
|
|
26
16
|
|
|
27
|
-
help() {
|
|
17
|
+
async help() {
|
|
28
18
|
console.print("Runs all the tests files that match the pattern/s in the current project");
|
|
29
19
|
console.print("\nUsage:");
|
|
30
20
|
console.print("vistta test [...patterns]\tRuns all the tests files that match the pattern/s in the current project");
|
|
@@ -36,179 +26,57 @@ export default class extends DefaultCommand {
|
|
|
36
26
|
|
|
37
27
|
async main(_, ...argv) {
|
|
38
28
|
let [args, options] = parseArgs(argv);
|
|
39
|
-
this.#
|
|
40
|
-
|
|
29
|
+
this.#testRunner = new TestRunner(options);
|
|
30
|
+
this.define("global", "suite", this.#testRunner.suite);
|
|
31
|
+
this.define("global", "test", this.#testRunner.test);
|
|
32
|
+
this.define("global", "expect", this.#testRunner.expect);
|
|
33
|
+
if (args.length === 0) args = this.resolvers.map((ext) => `**/*.test.${ext}`);
|
|
41
34
|
for (let i = 0, len = args.length; i < len; i++) args[i] = fs.resolve(cwd, args[i]);
|
|
42
35
|
const entries = fs.glob(args);
|
|
43
36
|
let entry = (await entries.next())?.value;
|
|
44
37
|
if (!process.env.NODE_DEBUG) console.disable();
|
|
38
|
+
let failed = false;
|
|
45
39
|
while (entry) {
|
|
46
40
|
try {
|
|
47
41
|
process.chdir(fs.dirname(entry));
|
|
48
42
|
if (!entry.includes("\\node_modules\\")) await import(fs.resolve(entry));
|
|
49
|
-
await this.#await();
|
|
50
43
|
} catch {
|
|
51
|
-
|
|
44
|
+
failed = true;
|
|
52
45
|
}
|
|
53
46
|
entry = (await entries.next())?.value;
|
|
54
47
|
}
|
|
55
|
-
if (!process.env.NODE_DEBUG) console.enable();
|
|
56
48
|
let output = "";
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
const results = await this.#testRunner.run();
|
|
50
|
+
if (!process.env.NODE_DEBUG) console.enable();
|
|
51
|
+
for (let i = 0, len = results.length; i < len; i++) {
|
|
52
|
+
const { name, tests } = results[i];
|
|
53
|
+
let passing = 0;
|
|
54
|
+
let total = 0;
|
|
55
|
+
let first, last;
|
|
59
56
|
if (name) output += `\n${name}\n`;
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
for (let n = 0, nlen = tests?.length || 0; n < nlen; n++) {
|
|
58
|
+
const { name, status, start, end, result, error } = tests[n];
|
|
59
|
+
if (!first || start < first) first = start;
|
|
60
|
+
if (!last || end > last) last = end;
|
|
61
|
+
switch (status) {
|
|
62
|
+
case "pass":
|
|
63
|
+
output += ` ${COLORS.GREEN}PASSED ${name} ${COLORS.RESET + COLORS.DIM}(${Math.round(end - start)}ms)${COLORS.RESET}\n${typeof result === "string" ? "\t" + COLORS.GREEN + result + COLORS.RESET + "\n" : ""}`;
|
|
64
|
+
passing++;
|
|
65
|
+
total++;
|
|
66
|
+
break;
|
|
67
|
+
case "fail":
|
|
68
|
+
output += ` ${COLORS.RED}FAILED ${name} ${COLORS.RESET + COLORS.DIM}(${Math.round(end - start)}ms)${COLORS.RESET}\n\t${COLORS.RED + error + COLORS.RESET}\n`;
|
|
69
|
+
total++;
|
|
70
|
+
break;
|
|
71
|
+
case "skip":
|
|
72
|
+
output += ` ${COLORS.YELLOW}SKIPPED ${name} ${COLORS.RESET + COLORS.DIM}(${Math.round(end - start)}ms)${COLORS.RESET}\n`;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (total !== passing) failed = true;
|
|
77
|
+
output += `${passing === total ? COLORS.GREEN : COLORS.RED}${passing}/${total} passing ${COLORS.RESET + COLORS.DIM}(${Math.round(last - first)}ms)${COLORS.RESET}\n`;
|
|
62
78
|
}
|
|
63
79
|
console.print(output);
|
|
64
|
-
process.exit(
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async suite(only, name, callback) {
|
|
68
|
-
if (this.#suite) throw new Error("Suites/Describes cannot be stacked");
|
|
69
|
-
this.#runningCounter++;
|
|
70
|
-
this.#suite = { name, only, tests: [] };
|
|
71
|
-
try {
|
|
72
|
-
const value = callback();
|
|
73
|
-
if (value instanceof Promise) await value;
|
|
74
|
-
} catch {
|
|
75
|
-
/* Do Nothing */
|
|
76
|
-
}
|
|
77
|
-
this.#results.push(this.#suite);
|
|
78
|
-
this.#suite = null;
|
|
79
|
-
this.#runningCounter--;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async test(only, name, callback) {
|
|
83
|
-
if (
|
|
84
|
-
(this.#options.filter && !name.match(new RegExp(this.#options.filter, "i"))) ||
|
|
85
|
-
(this.#options.only && !(only || this.#suite?.only))
|
|
86
|
-
)
|
|
87
|
-
return;
|
|
88
|
-
this.#runningCounter++;
|
|
89
|
-
const test = { name };
|
|
90
|
-
test.start = performance();
|
|
91
|
-
if (this.#suite) this.#suite.tests.push(test);
|
|
92
|
-
else this.#results.push({ tests: [test], time: test.time });
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
test.result = callback();
|
|
96
|
-
if (test.result instanceof Promise) test.result = await test.result;
|
|
97
|
-
test.status = "pass";
|
|
98
|
-
test.end = performance();
|
|
99
|
-
} catch (error) {
|
|
100
|
-
test.status = "fail";
|
|
101
|
-
test.error = error;
|
|
102
|
-
test.end = performance();
|
|
103
|
-
this.#failed = true;
|
|
104
|
-
}
|
|
105
|
-
this.#runningCounter--;
|
|
80
|
+
process.exit(failed ? -1 : 0);
|
|
106
81
|
}
|
|
107
|
-
|
|
108
|
-
expect(target, label) {
|
|
109
|
-
return {
|
|
110
|
-
not: {
|
|
111
|
-
toEqual: (value, unit = "") => {
|
|
112
|
-
if (target !== value) return;
|
|
113
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not equal ${value}${unit}`);
|
|
114
|
-
},
|
|
115
|
-
toBeLessThan: (value, unit = "") => {
|
|
116
|
-
if (target >= value) return;
|
|
117
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not be less than ${value}${unit}`);
|
|
118
|
-
},
|
|
119
|
-
toBeGreaterThan: (value, unit = "") => {
|
|
120
|
-
if (target <= value) return;
|
|
121
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not be greater than ${value}${unit}`);
|
|
122
|
-
},
|
|
123
|
-
toInclude: (value) => {
|
|
124
|
-
if (target.indexOf(value) === -1) return;
|
|
125
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not include ${value}`);
|
|
126
|
-
},
|
|
127
|
-
toMatch: (regex) => {
|
|
128
|
-
if (!target.match(new RegExp(regex))) return;
|
|
129
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to not match ${regex}`);
|
|
130
|
-
},
|
|
131
|
-
toThrow: (message) => {
|
|
132
|
-
try {
|
|
133
|
-
target();
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if (!message)
|
|
136
|
-
throw new Error(`expected ${label ? label + "()" : "function()"} to not throw, but got ${error.message}`);
|
|
137
|
-
if (error.message === message)
|
|
138
|
-
throw new Error(
|
|
139
|
-
`expected ${label ? label + "()" : "function()"} to not throw ${message}, but got ${error.message}`,
|
|
140
|
-
);
|
|
141
|
-
if (error.message.match(new RegExp(message)))
|
|
142
|
-
throw new Error(
|
|
143
|
-
`expected ${label ? label + "()" : "function()"} to not throw ${message}, but got ${error.message}`,
|
|
144
|
-
);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
toEqual: (value, unit = "") => {
|
|
150
|
-
if (target === value) return;
|
|
151
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to equal ${value}${unit}`);
|
|
152
|
-
},
|
|
153
|
-
toBeLessThan: (value, unit = "") => {
|
|
154
|
-
if (target < value) return;
|
|
155
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to be less than ${value}${unit}`);
|
|
156
|
-
},
|
|
157
|
-
toBeGreaterThan: (value, unit = "") => {
|
|
158
|
-
if (target > value) return;
|
|
159
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to be greater than ${value}${unit}`);
|
|
160
|
-
},
|
|
161
|
-
toInclude: (value) => {
|
|
162
|
-
if (target.indexOf(value) > -1) return;
|
|
163
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to include ${value}`);
|
|
164
|
-
},
|
|
165
|
-
toMatch: (regex) => {
|
|
166
|
-
if (target.match(new RegExp(regex))) return;
|
|
167
|
-
throw new Error(`expected ${label ? label + "(" + target + ")" : target} to match ${regex}`);
|
|
168
|
-
},
|
|
169
|
-
toThrow: (message) => {
|
|
170
|
-
try {
|
|
171
|
-
target();
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (!message) return;
|
|
174
|
-
if (error.message === message) return;
|
|
175
|
-
if (error.message.match(new RegExp(message))) return;
|
|
176
|
-
throw new Error(`expected ${label ? label + "()" : "function()"} to throw ${message}, but got ${error.message}`);
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
#await() {
|
|
183
|
-
return new Promise((resolve) => {
|
|
184
|
-
const timer = () => {
|
|
185
|
-
if (this.#runningCounter === 0) resolve();
|
|
186
|
-
else setTimeout(timer, 500);
|
|
187
|
-
};
|
|
188
|
-
timer();
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function processTests(tests) {
|
|
194
|
-
let passing = 0;
|
|
195
|
-
let total = 0;
|
|
196
|
-
let acc = "";
|
|
197
|
-
let first, last;
|
|
198
|
-
for (let i = 0, len = tests?.length || 0; i < len; i++) {
|
|
199
|
-
const { name, status, start, end, result, error } = tests[i];
|
|
200
|
-
if (!first || start < first) first = start;
|
|
201
|
-
if (!last || end > last) last = end;
|
|
202
|
-
if (status === "pass") {
|
|
203
|
-
acc += ` ${COLORS.GREEN}✔ ${name} ${COLORS.RESET + COLORS.DIM}(${Math.round(end - start)}ms)${COLORS.RESET}\n${typeof result === "string" ? "\t" + COLORS.GREEN + result + COLORS.RESET + "\n" : ""}`;
|
|
204
|
-
passing++;
|
|
205
|
-
} else
|
|
206
|
-
acc += ` ${COLORS.RED}✖ ${name} ${COLORS.RESET + COLORS.DIM}(${Math.round(end - start)}ms)${COLORS.RESET}\n\t${COLORS.RED + error + COLORS.RESET}\n`;
|
|
207
|
-
total++;
|
|
208
|
-
}
|
|
209
|
-
return [acc, passing, total, Math.round(last - first)];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function performance(time = process.hrtime()) {
|
|
213
|
-
return time[0] * 1000 + time[1] / 1e6;
|
|
214
82
|
}
|
package/config.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
export function mergeConfig(target, source) {
|
|
8
|
+
const result = { ...target };
|
|
9
|
+
for (const [key, value] of Object.entries(source)) {
|
|
10
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) continue;
|
|
11
|
+
result[key] = value && typeof value === "object" && !Array.isArray(value) ? mergeConfig(result[key], value) : value;
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadProjectConfig(directory = process.cwd()) {
|
|
17
|
+
const file = ["tsconfig.json", "jsconfig.json"].map((name) => resolve(directory, name)).find(existsSync);
|
|
18
|
+
if (!file) return { file: undefined, config: {}, sources: {} };
|
|
19
|
+
const active = new Set();
|
|
20
|
+
const sources = {};
|
|
21
|
+
function load(filepath) {
|
|
22
|
+
if (active.has(filepath)) throw new Error(`Circular configuration inheritance: ${filepath}`);
|
|
23
|
+
active.add(filepath);
|
|
24
|
+
const errors = [];
|
|
25
|
+
const own = parse(readFileSync(filepath, "utf8"), errors, { allowTrailingComma: true });
|
|
26
|
+
if (errors.length) throw new Error(`Invalid configuration ${filepath}: ${printParseErrorCode(errors[0].error)}`);
|
|
27
|
+
if (!own || typeof own !== "object" || Array.isArray(own))
|
|
28
|
+
throw new Error(`Expected a configuration object in ${filepath}`);
|
|
29
|
+
for (const name of ["cli", "cliOptions"]) {
|
|
30
|
+
if (own[name] !== undefined && (!own[name] || typeof own[name] !== "object" || Array.isArray(own[name])))
|
|
31
|
+
throw new Error(`${name} must be an object in ${filepath}`);
|
|
32
|
+
}
|
|
33
|
+
let inherited = {};
|
|
34
|
+
for (const parent of own.extends ? (Array.isArray(own.extends) ? own.extends : [own.extends]) : []) {
|
|
35
|
+
if (typeof parent !== "string") throw new Error(`Expected a string in extends in ${filepath}`);
|
|
36
|
+
const base = dirname(filepath);
|
|
37
|
+
let parentFile;
|
|
38
|
+
if (parent.startsWith(".") || isAbsolute(parent)) {
|
|
39
|
+
parentFile = resolve(base, parent);
|
|
40
|
+
if (!existsSync(parentFile) && existsSync(parentFile + ".json")) parentFile += ".json";
|
|
41
|
+
} else parentFile = createRequire(filepath).resolve(parent);
|
|
42
|
+
inherited = mergeConfig(inherited, load(parentFile));
|
|
43
|
+
}
|
|
44
|
+
const cli = mergeConfig(own.cliOptions, own.cli || {});
|
|
45
|
+
if (cli.commands !== undefined && (!cli.commands || typeof cli.commands !== "object" || Array.isArray(cli.commands)))
|
|
46
|
+
throw new Error(`cli.commands must be an object in ${filepath}`);
|
|
47
|
+
for (const [name, value] of Object.entries(cli.commands || {})) {
|
|
48
|
+
if (typeof value !== "string") throw new Error(`Expected a module path for cli.commands.${name} in ${filepath}`);
|
|
49
|
+
cli.commands[name] =
|
|
50
|
+
value.startsWith(".") || isAbsolute(value)
|
|
51
|
+
? pathToFileURL(resolve(dirname(filepath), value)).href
|
|
52
|
+
: pathToFileURL(createRequire(filepath).resolve(value)).href;
|
|
53
|
+
}
|
|
54
|
+
for (const section of ["lint", "format"]) {
|
|
55
|
+
if (cli[section]?.config !== undefined && typeof cli[section].config !== "string")
|
|
56
|
+
throw new Error(`cli.${section}.config must be a module path in ${filepath}`);
|
|
57
|
+
if (typeof cli[section]?.config === "string") {
|
|
58
|
+
const value = cli[section].config;
|
|
59
|
+
cli[section].config =
|
|
60
|
+
value.startsWith(".") || isAbsolute(value)
|
|
61
|
+
? pathToFileURL(resolve(dirname(filepath), value)).href
|
|
62
|
+
: pathToFileURL(createRequire(filepath).resolve(value)).href;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const normalized = { ...own, cli };
|
|
66
|
+
delete normalized.extends;
|
|
67
|
+
delete normalized.cliOptions;
|
|
68
|
+
function record(value, prefix = "") {
|
|
69
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
70
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
71
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) record(entry, path);
|
|
72
|
+
else sources[path] = filepath;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
record(normalized);
|
|
76
|
+
active.delete(filepath);
|
|
77
|
+
return mergeConfig(inherited, normalized);
|
|
78
|
+
}
|
|
79
|
+
const config = load(file);
|
|
80
|
+
function stringList(value, path) {
|
|
81
|
+
if (value !== undefined && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")))
|
|
82
|
+
throw new Error(`${path} must be an array of strings in ${file}`);
|
|
83
|
+
}
|
|
84
|
+
for (const name of ["include", "exclude", "files"]) stringList(config[name], name);
|
|
85
|
+
for (const section of ["check", "lint", "format", "tasks"]) {
|
|
86
|
+
const value = config.cli?.[section];
|
|
87
|
+
if (value !== undefined && (!value || typeof value !== "object" || Array.isArray(value)))
|
|
88
|
+
throw new Error(`cli.${section} must be an object in ${file}`);
|
|
89
|
+
}
|
|
90
|
+
for (const [name, value] of Object.entries(config.cli?.check || {})) {
|
|
91
|
+
if (!["format", "lint", "types"].includes(name) || typeof value !== "boolean")
|
|
92
|
+
throw new Error(`cli.check.${name} must be a boolean check named format, lint, or types`);
|
|
93
|
+
}
|
|
94
|
+
for (const section of ["lint", "format"]) {
|
|
95
|
+
const options = config.cli?.[section] || {};
|
|
96
|
+
const engine = options.engine;
|
|
97
|
+
const expected = section === "lint" ? "eslint" : "prettier";
|
|
98
|
+
if (engine !== undefined && engine !== expected) throw new Error(`cli.${section}.engine must be "${expected}"`);
|
|
99
|
+
for (const name of ["include", "exclude"]) stringList(options[name], `cli.${section}.${name}`);
|
|
100
|
+
for (const name of section === "lint" ? ["rules", "languageOptions", "options"] : ["options"]) {
|
|
101
|
+
const value = options[name];
|
|
102
|
+
if (value !== undefined && (!value || typeof value !== "object" || Array.isArray(value)))
|
|
103
|
+
throw new Error(`cli.${section}.${name} must be an object in ${file}`);
|
|
104
|
+
}
|
|
105
|
+
for (const name of Object.keys(options)) {
|
|
106
|
+
if (
|
|
107
|
+
![
|
|
108
|
+
"engine",
|
|
109
|
+
"include",
|
|
110
|
+
"exclude",
|
|
111
|
+
"options",
|
|
112
|
+
"config",
|
|
113
|
+
...(section === "lint" ? ["rules", "languageOptions"] : []),
|
|
114
|
+
].includes(name)
|
|
115
|
+
)
|
|
116
|
+
throw new Error(`Unknown setting cli.${section}.${name} in ${file}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const [name, task] of Object.entries(config.cli?.tasks || {})) {
|
|
120
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) throw new Error(`cli.tasks.${name} must be an object`);
|
|
121
|
+
stringList(task.dependsOn, `cli.tasks.${name}.dependsOn`);
|
|
122
|
+
if (task.script !== undefined && typeof task.script !== "string")
|
|
123
|
+
throw new Error(`cli.tasks.${name}.script must be a package script name`);
|
|
124
|
+
for (const key of Object.keys(task))
|
|
125
|
+
if (!["dependsOn", "script"].includes(key)) throw new Error(`Unknown setting cli.tasks.${name}.${key}`);
|
|
126
|
+
}
|
|
127
|
+
return { file, config, sources };
|
|
128
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "VISTTA project configuration",
|
|
4
|
+
"description": "VISTTA settings in tsconfig.json or jsconfig.json. Other TypeScript and framework properties are preserved.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"extends": {
|
|
8
|
+
"description": "Relative or package presets, applied from left to right before local settings.",
|
|
9
|
+
"oneOf": [
|
|
10
|
+
{
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"$ref": "#/definitions/stringList"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"compilerOptions": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"description": "Compiler options used unchanged by TypeScript, with noEmit enabled for checks."
|
|
21
|
+
},
|
|
22
|
+
"include": {
|
|
23
|
+
"$ref": "#/definitions/stringList"
|
|
24
|
+
},
|
|
25
|
+
"exclude": {
|
|
26
|
+
"$ref": "#/definitions/stringList"
|
|
27
|
+
},
|
|
28
|
+
"files": {
|
|
29
|
+
"$ref": "#/definitions/stringList"
|
|
30
|
+
},
|
|
31
|
+
"cli": {
|
|
32
|
+
"$ref": "#/definitions/cli"
|
|
33
|
+
},
|
|
34
|
+
"cliOptions": {
|
|
35
|
+
"$ref": "#/definitions/cli",
|
|
36
|
+
"description": "Legacy alias. The cli block takes precedence."
|
|
37
|
+
},
|
|
38
|
+
"bundlerOptions": {
|
|
39
|
+
"type": "object"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"definitions": {
|
|
43
|
+
"stringList": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"cli": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"commands": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"check": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"additionalProperties": false,
|
|
61
|
+
"properties": {
|
|
62
|
+
"format": {
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"default": true
|
|
65
|
+
},
|
|
66
|
+
"lint": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"default": true
|
|
69
|
+
},
|
|
70
|
+
"types": {
|
|
71
|
+
"type": "boolean",
|
|
72
|
+
"default": true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"format": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"properties": {
|
|
80
|
+
"engine": {
|
|
81
|
+
"const": "prettier",
|
|
82
|
+
"default": "prettier"
|
|
83
|
+
},
|
|
84
|
+
"options": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"description": "Prettier options overriding any discovered or referenced configuration."
|
|
87
|
+
},
|
|
88
|
+
"config": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Optional module exporting Prettier options as its default export."
|
|
91
|
+
},
|
|
92
|
+
"include": {
|
|
93
|
+
"$ref": "#/definitions/stringList"
|
|
94
|
+
},
|
|
95
|
+
"exclude": {
|
|
96
|
+
"$ref": "#/definitions/stringList"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"lint": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"properties": {
|
|
104
|
+
"engine": {
|
|
105
|
+
"const": "eslint",
|
|
106
|
+
"default": "eslint"
|
|
107
|
+
},
|
|
108
|
+
"rules": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"description": "ESLint rules applied after the existing or referenced flat config."
|
|
111
|
+
},
|
|
112
|
+
"languageOptions": {
|
|
113
|
+
"type": "object"
|
|
114
|
+
},
|
|
115
|
+
"options": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"description": "Additional JSON-compatible ESLint flat-config properties, such as settings or files."
|
|
118
|
+
},
|
|
119
|
+
"config": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Optional module exporting an ESLint flat config or array. Replaces config-file discovery."
|
|
122
|
+
},
|
|
123
|
+
"include": {
|
|
124
|
+
"$ref": "#/definitions/stringList"
|
|
125
|
+
},
|
|
126
|
+
"exclude": {
|
|
127
|
+
"$ref": "#/definitions/stringList"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"tasks": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"additionalProperties": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"properties": {
|
|
137
|
+
"dependsOn": {
|
|
138
|
+
"$ref": "#/definitions/stringList"
|
|
139
|
+
},
|
|
140
|
+
"script": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "A script name from the current package.json. Dependencies run first."
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/global.d.ts
CHANGED
|
@@ -1 +1,69 @@
|
|
|
1
1
|
/// <reference path="@vistta/console" />
|
|
2
|
+
|
|
3
|
+
interface CliRuntimeData {
|
|
4
|
+
loaders: any[];
|
|
5
|
+
resolvers: string[];
|
|
6
|
+
options: any;
|
|
7
|
+
port?: any;
|
|
8
|
+
main?: (...args: any[]) => any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare namespace NodeJS {
|
|
12
|
+
interface Process {
|
|
13
|
+
argv: string[];
|
|
14
|
+
env: Record<string, string | undefined>;
|
|
15
|
+
chdir(directory: string): void;
|
|
16
|
+
cwd(): string;
|
|
17
|
+
exit(code?: number): never;
|
|
18
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
19
|
+
send?(message: any): void;
|
|
20
|
+
restart?: () => void;
|
|
21
|
+
vistta: CliRuntimeData;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare var vistta: CliRuntimeData;
|
|
26
|
+
declare var loaders: {
|
|
27
|
+
send: (event: string, ...args: any[]) => void;
|
|
28
|
+
on: (event: string, callback: (...args: any[]) => void) => void;
|
|
29
|
+
shared: Record<string, any>;
|
|
30
|
+
};
|
|
31
|
+
declare var main: {
|
|
32
|
+
send: (event: string, ...args: any[]) => void;
|
|
33
|
+
on: (event: string, callback: (...args: any[]) => void) => void;
|
|
34
|
+
shared: Record<string, any>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
declare var suite: {
|
|
38
|
+
(name: string, callback: () => void, options?: { only?: boolean; skip?: boolean }): void;
|
|
39
|
+
only(name: string, callback: () => void): void;
|
|
40
|
+
skip(name: string, callback: () => void): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
declare var test: {
|
|
44
|
+
(name: string, callback: () => void, options?: { only?: boolean; skip?: boolean }): void;
|
|
45
|
+
only(name: string, callback: () => void): void;
|
|
46
|
+
skip(name: string, callback: () => void): void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
interface ExpectNotMatchers {
|
|
50
|
+
toEqual(expected: any, unit?: string): void;
|
|
51
|
+
toBeLessThan(expected: any, unit?: string): void;
|
|
52
|
+
toBeGreaterThan(expected: any, unit?: string): void;
|
|
53
|
+
toInclude(expected: any): void;
|
|
54
|
+
toMatch(expected: string | RegExp): void;
|
|
55
|
+
toThrow(expected?: any): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ExpectMatchers {
|
|
59
|
+
toEqual(expected: any): void;
|
|
60
|
+
toBeLessThan(expected: any, unit?: string): void;
|
|
61
|
+
toBeGreaterThan(expected: any, unit?: string): void;
|
|
62
|
+
toInclude(expected: any): void;
|
|
63
|
+
toMatch(expected: string | RegExp): void;
|
|
64
|
+
toThrow(expected?: any): void;
|
|
65
|
+
not: ExpectNotMatchers;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
declare function expect(value: any, label?: string): ExpectMatchers;
|
|
69
|
+
declare function setImmediate(callback: (...args: any[]) => void): any;
|
package/index.js
CHANGED
|
@@ -2,8 +2,12 @@ export { Bundler } from "./classes/bundler.js";
|
|
|
2
2
|
export { Command } from "./classes/command.js";
|
|
3
3
|
export { Loader } from "./classes/loader.js";
|
|
4
4
|
export { Runtime } from "./classes/runtime.js";
|
|
5
|
+
export { TestRunner } from "./classes/test-runner.js";
|
|
5
6
|
export { default as BundleCommand } from "./commands/bundle.js";
|
|
6
7
|
export { default as DefaultCommand } from "./commands/default.js";
|
|
7
8
|
export { default as ProjectCommand } from "./commands/project.js";
|
|
9
|
+
export { default as QualityCommand } from "./commands/quality.js";
|
|
10
|
+
export { default as TaskCommand } from "./commands/task.js";
|
|
8
11
|
export { default as TestCommand } from "./commands/test.js";
|
|
12
|
+
export { loadProjectConfig } from "./config.js";
|
|
9
13
|
export { parseArgs } from "./utils.js";
|
package/loaders/bundler.js
CHANGED
|
@@ -6,7 +6,9 @@ export default class BundlerLoader extends Loader {
|
|
|
6
6
|
async load(_, { path, ...options }) {
|
|
7
7
|
options.write = false;
|
|
8
8
|
remove(options, "type", "extension");
|
|
9
|
-
|
|
9
|
+
if (options.importAttributes?.type === "bundler") delete options.importAttributes.type;
|
|
10
|
+
const runtime = /** @type {import("../classes/runtime").Runtime} */ (/** @type {unknown} */ (this));
|
|
11
|
+
const { code, files, resources, errors, warnings } = await new Bundler(runtime).run(path, options);
|
|
10
12
|
return {
|
|
11
13
|
code:
|
|
12
14
|
`export const code = ${JSON.stringify(code)}; ` +
|
package/loaders/index.js
CHANGED
|
@@ -8,12 +8,12 @@ export async function initialize({ loaders, resolvers, options, port }) {
|
|
|
8
8
|
const shared = {};
|
|
9
9
|
const events = {};
|
|
10
10
|
global.main = {
|
|
11
|
-
send: (event, ...args) =>
|
|
11
|
+
send: (event, ...args) => port.postMessage(JSON.stringify({ event, args })),
|
|
12
12
|
on: (event, callback) => (events[event] ? events[event].push(callback) : (events[event] = [callback])),
|
|
13
13
|
shared: new Proxy(shared, {
|
|
14
14
|
set(_, prop, value) {
|
|
15
15
|
port.postMessage(JSON.stringify({ sync: true, prop, value }));
|
|
16
|
-
return Reflect.set(
|
|
16
|
+
return Reflect.set(shared, prop, value);
|
|
17
17
|
},
|
|
18
18
|
}),
|
|
19
19
|
};
|
package/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
process.vistta.main();
|
|
1
|
+
process.vistta.main?.();
|