@vistta/cli 1.0.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.
@@ -0,0 +1,70 @@
1
+ /* eslint-disable no-unused-vars */
2
+ /**
3
+ * @define {Object} LoaderConfig
4
+ * @property {string} main The path to the module exporting the loader class
5
+ * @property {string} [type] The import attribute type that the loader supports
6
+ * @property {string} [filter] A filter to match against the specifier
7
+ */
8
+
9
+ export class Command {
10
+ #loaders = [];
11
+ #resolvers = ["js"];
12
+
13
+ get loaders() {
14
+ return this.#loaders;
15
+ }
16
+
17
+ get resolvers() {
18
+ return this.#resolvers;
19
+ }
20
+
21
+ /**
22
+ * Optional method to display help about the plugin.
23
+ * @param {...string} args
24
+ */
25
+ help(...args) {
26
+ // Optional: subclasses may implement if needed
27
+ }
28
+
29
+ /**
30
+ * Main method to execute the plugin.
31
+ * @abstract
32
+ * @param {...string} args
33
+ */
34
+ main(...args) {
35
+ throw new Error("Method 'main' must be implemented.");
36
+ }
37
+
38
+ /**
39
+ * Registers a new loader.
40
+ * @param {string} path - The path to the module exporting the loader class
41
+ * @param {Object} [options] - The loader options
42
+ * @param {string} [options.type] - The import attribute type that the loader supports
43
+ * @param {string} [options.filter] - A filter to match against the specifier
44
+ */
45
+ register(path, options = {}) {
46
+ this.#loaders.push({ main: path, ...options });
47
+ }
48
+
49
+ /**
50
+ * Adds new file extensions to resolve without needing to specify them.
51
+ * @param {...string} extensions - The file extensions to add (without the dot)
52
+ */
53
+ resolve(...extensions) {
54
+ this.#resolvers.push(...extensions);
55
+ }
56
+
57
+ /**
58
+ * Defines either an environment variable or a global variable.
59
+ * @param {"global"|"env"} type - Either environment or global.
60
+ * @param {string} key - The key.
61
+ * @param {any} value - The value.
62
+ */
63
+ define(type, key, value) {
64
+ if (type === "env") process.env[key] = value?.toString() ?? "";
65
+ else if (type === "global") global[key] = value;
66
+ else throw new Error(`Unknown define type "${type}"`);
67
+ }
68
+ }
69
+
70
+ export default Command;
@@ -0,0 +1,15 @@
1
+ /* eslint-disable no-unused-vars */
2
+ export class Loader {
3
+ constructor() {}
4
+
5
+ /**
6
+ * Main method to execute the loader.
7
+ * @abstract
8
+ * @param {string} source The source code to load/process
9
+ * @param {Object} properties Properties for the loader
10
+ */
11
+ load(source, properties) {
12
+ throw new Error("Method 'load' must be implemented.");
13
+ }
14
+ }
15
+ export default Loader;
@@ -0,0 +1,154 @@
1
+ import fs from "@vistta/fs";
2
+ import { isBuiltin } from "node:module";
3
+ import { fileURLToPath, pathToFileURL, URLSearchParams } from "node:url";
4
+ import { Loader } from "./loader.js";
5
+
6
+ const CWD = process.cwd();
7
+ const FALLBACK = "*";
8
+
9
+ export class Runtime {
10
+ static Error = class extends Error {
11
+ constructor(name, message) {
12
+ super(message);
13
+ this.name = name + " Loader Error";
14
+ }
15
+ };
16
+
17
+ #init;
18
+ #loaders;
19
+ #resolvers;
20
+ #options;
21
+
22
+ get loaders() {
23
+ return this.#loaders;
24
+ }
25
+
26
+ get options() {
27
+ return this.#options;
28
+ }
29
+
30
+ constructor({ loaders, resolvers, options }) {
31
+ this.#init = async () => {
32
+ this.#loaders = {};
33
+ for (let i = 0, len = loaders?.length || 0; i < len; i++) {
34
+ let { main, type, filter } = loaders[i];
35
+ const { default: LoaderClass } = await import(main);
36
+ if (!(LoaderClass?.prototype instanceof Loader)) throw new Runtime.Error(`Invalid loader "${loaders[i].main}"`);
37
+ if (!type) type = FALLBACK;
38
+ if (!this.#loaders[type]) this.#loaders[type] = {};
39
+ this.#loaders[type][filter || FALLBACK] = new LoaderClass(options);
40
+ }
41
+ };
42
+ this.#resolvers = resolvers;
43
+ this.#options = options;
44
+ }
45
+
46
+ async resolve(specifier, context, nextResolve, options) {
47
+ if (!this.#loaders) await this.#init();
48
+ [specifier, options] = specifier.split(/\?(.*)/);
49
+ options = new URLSearchParams(options);
50
+ const { parentURL = CWD, conditions: [type] = [] } = context;
51
+ if (isBuiltin(specifier)) {
52
+ if (type === "bundler") context.builtin = true;
53
+ return nextResolve(specifier, context);
54
+ }
55
+
56
+ const paths = this.#options?.compiler?.paths || {};
57
+ const pathKeys = Object.keys(paths);
58
+ for (let i = 0, len = pathKeys.length; i < len; i++) {
59
+ const cur = pathKeys[i];
60
+ const resolved = paths[cur][0];
61
+ if (cur.endsWith("*")) {
62
+ const regex = cur.slice(0, -1);
63
+ if (new RegExp(`^${regex}.*`).test(specifier) && resolved.endsWith("*")) {
64
+ specifier = fs.resolve(CWD + "/", resolved.slice(0, -1), specifier.replace(new RegExp(`^${regex}`), ""));
65
+ break;
66
+ }
67
+ } else if (cur === specifier) {
68
+ specifier = fs.resolve(CWD + "/", resolved);
69
+ break;
70
+ }
71
+ }
72
+
73
+ if (!/^(\w:|\.?\.?\/)/.test(specifier)) return nextResolve(specifier, context);
74
+ if (specifier?.startsWith("file://")) specifier = fileURLToPath(specifier);
75
+
76
+ if (!fs.isAbsolute(specifier))
77
+ specifier = fs.resolve(parentURL?.startsWith("file://") ? fs.dirname(parentURL) : parentURL, specifier);
78
+ const len = this.#resolvers.length;
79
+ if (fs.existsSync(specifier)) {
80
+ if (fs.isDirectory(specifier)) {
81
+ for (let i = 0; i < len; i++) {
82
+ const cur = fs.resolve(specifier, `index.${this.#resolvers[i]}`);
83
+ if (fs.existsSync(cur)) {
84
+ specifier = cur;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ } else
90
+ for (let i = 0; i < len; i++) {
91
+ const cur = specifier + `.${this.#resolvers[i]}`;
92
+ if (fs.existsSync(cur)) {
93
+ specifier = cur;
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (type === "bundler") {
99
+ if (fs.isAbsolute(specifier)) context.file = true;
100
+ return nextResolve(specifier, context);
101
+ }
102
+
103
+ const params = options.toString();
104
+ specifier = pathToFileURL(specifier).href;
105
+ if (params.length > 0) return nextResolve(`${specifier}?${params}`, context);
106
+ return nextResolve(specifier, context);
107
+ }
108
+
109
+ async load(url, context, nextLoad) {
110
+ if (isBuiltin(url)) return nextLoad(url, context);
111
+ const { ...options } = context?.importAttributes || {};
112
+ url = options.bundler ? pathToFileURL(url.split("?")[0]).href : url.split("?")[0];
113
+ const path = fileURLToPath(url);
114
+ const loader = match(this.#loaders, path, options.type);
115
+ if (!loader) return nextLoad(url, context);
116
+ options.path = path;
117
+ options.extension = fs.extname(url).slice(1);
118
+ const {
119
+ code,
120
+ warnings,
121
+ errors,
122
+ resources,
123
+ files = [],
124
+ } = await loader.load.call(this, await fs.readFile(path, "utf-8"), options);
125
+ if (!code)
126
+ await loader.load.call(this, await fs.readFile(path, "utf-8"), {
127
+ ...options,
128
+ debug: true,
129
+ });
130
+ if (errors?.length) throw new Runtime.Error(loader.name || loader.constructor.name, `${errors.join("\n")}`);
131
+ warnings?.forEach((warning) => console.warn(`${loader.name || loader.constructor.name} Loader Warning: ${warning}\n`));
132
+ return {
133
+ format: "module",
134
+ shortCircuit: true,
135
+ source: code,
136
+ resources: resources,
137
+ files: [options.path, ...files.map((url) => fileURLToPath(url))],
138
+ };
139
+ }
140
+ }
141
+
142
+ function match(loaders, path, type = FALLBACK) {
143
+ if (type === FALLBACK && new RegExp(`^.*\\.(js|mjs|cjs)$`).test(path)) return null;
144
+ const patterns = Object.keys(loaders[type] || {});
145
+ let fallback = null;
146
+ for (let i = 0, len = patterns.length; i < len; i++) {
147
+ const pattern = patterns[i];
148
+ if (pattern === FALLBACK) fallback = pattern;
149
+ else if (new RegExp(`^${pattern}$`).test(path)) return loaders[type][pattern];
150
+ }
151
+ if (fallback) return loaders[type][fallback];
152
+ if (path === FALLBACK) return null;
153
+ return match(loaders, FALLBACK, type);
154
+ }
@@ -0,0 +1,19 @@
1
+ import { resolve } from "node:path";
2
+ import { Bundler } from "../classes/bundler.js";
3
+ import { Runtime } from "../classes/runtime.js";
4
+ import DefaultCommand from "./default.js";
5
+
6
+ export default class BundlerCommand extends DefaultCommand {
7
+ help() {
8
+ console.print("Bundles the specified entry file");
9
+ console.print("\nUsage:");
10
+ console.print("vistta bundle [filename] [outdir]\tBundles the specified entry file");
11
+ }
12
+
13
+ async main(_, ...argv) {
14
+ let [args, options] = this.parse(argv);
15
+ if (args.length === 0) return this.help();
16
+ options = Object.assign({}, this.options, options, { outdir: args[1] });
17
+ new Bundler(new Runtime(global.vistta)).run(resolve(process.cwd(), args[0]), options);
18
+ }
19
+ }
@@ -0,0 +1,49 @@
1
+ import { COLORS } from "@vistta/console";
2
+ import fs from "@vistta/fs";
3
+ import { Command } from "../classes/command.js";
4
+
5
+ const dirname = import.meta.dirname;
6
+
7
+ export default class DefaultCommand extends Command {
8
+ constructor() {
9
+ super();
10
+ this.register(fs.resolve(dirname, "../loaders/file.js"));
11
+ this.register(fs.resolve(dirname, "../loaders/typescript.js"), { filter: ".*\\.(ts|mts|cts)" });
12
+ this.register(fs.resolve(dirname, "../loaders/json.js"), { filter: ".*\\.json" });
13
+ this.register(fs.resolve(dirname, "../loaders/bundler.js"), { type: "bundler" });
14
+ this.resolve("ts");
15
+ }
16
+
17
+ async help() {
18
+ console.print("vistta <command/script>\n\nAvailable commands:");
19
+ const commands = Object.keys(process.vistta.options.cli.commands);
20
+ let output = "\n\t";
21
+ for (let i = 0, len = commands.length; i < len; i++) {
22
+ if (commands[i] !== "default") output += commands[i] + (i < len - 1 ? ", " : "");
23
+ }
24
+ console.print(output);
25
+ }
26
+
27
+ async main(file) {
28
+ if (process.env.NODE_ENV === "development") await this.outdated();
29
+ const filepath = fs.resolve(process.cwd(), file);
30
+ if (!fs.existsSync(filepath)) {
31
+ console.print(`Unknown command/script "${file}"\n`);
32
+ return this.help();
33
+ }
34
+ import(filepath);
35
+ }
36
+
37
+ async outdated() {
38
+ try {
39
+ const modules = await (await import("../utils.js"))?.getOutdatedPackages(process.cwd());
40
+ for (let i = 0, len = modules.length; i < len; i++) {
41
+ const { name, current, wanted, latest } = modules[i];
42
+ if (current === wanted) console.print(`${COLORS.CYAN}Module "${name}" has a new version (${latest})${COLORS.RESET}`);
43
+ else console.print(`${COLORS.YELLOW}Module "${name}" is outdated (${latest})${COLORS.RESET}`);
44
+ }
45
+ } catch {
46
+ console.print(`${COLORS.YELLOW}Failed to verify modules${COLORS.RESET}`);
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,101 @@
1
+ import { COLORS } from "@vistta/console";
2
+ import fs from "@vistta/fs";
3
+ import { capitalize } from "@vistta/utils";
4
+ import { Command } from "../classes/command.js";
5
+ import { getOutdatedPackages, importJSON, incrementPackageVersion } from "../utils.js";
6
+
7
+ const HELP = {
8
+ name: "outputs current project name",
9
+ version: "outputs the project version",
10
+ "version [increment]": "updates the project version",
11
+ outdated: "lists all outdated modules in the project",
12
+ patch: "updates the version of outdated modules to the latest patch version in the project",
13
+ "patch [module]": "updates the version of outdated module to the latest patch version in the project",
14
+ update: "updates the version of outdated modules to the latest version in the project",
15
+ "update [module]": "updates the version of outdated module to the latest version in the project",
16
+ };
17
+
18
+ export default class extends Command {
19
+ constructor(options) {
20
+ super(options);
21
+ }
22
+
23
+ help(_, command) {
24
+ switch (command) {
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;
58
+ }
59
+ }
60
+
61
+ async main(_, command, ...args) {
62
+ console.clear();
63
+ switch (command) {
64
+ case "name":
65
+ return console.print(process.env.PROJECT_NAME);
66
+ case "version":
67
+ if (!args[0]) return console.print(process.env.PROJECT_VERSION);
68
+ return console.print(
69
+ await incrementPackageVersion(fs.resolve(process.env.PROJECT_PATH, "package.json"), args[0], args[1]),
70
+ );
71
+ case "outdated":
72
+ case "patch":
73
+ case "update":
74
+ return modules(command, args[0]);
75
+ }
76
+ return this.help(_, command);
77
+ }
78
+ }
79
+
80
+ async function modules(command, arg1) {
81
+ const modules = await getOutdatedPackages(process.cwd());
82
+ let valid = true;
83
+ let update = 0;
84
+ for (let i = 0, len = modules.length; i < len; i++) {
85
+ const { name, package: packagePath, current, wanted, latest, dev } = modules[i];
86
+ if (command === "patch" || command === "update") {
87
+ if (command === "patch" && current === wanted) continue;
88
+ if (arg1 && name !== arg1) continue;
89
+ const packageObj = await importJSON(packagePath);
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));
96
+ }
97
+
98
+ if (modules.length == 0) console.print("Everything is up to date");
99
+ else if (update > 0) console.print(`Updated ${update} module${update > 1 ? "s" : ""} versions in project package.`);
100
+ process.exit(valid ? 0 : -1);
101
+ }
@@ -0,0 +1,214 @@
1
+ import { COLORS } from "@vistta/console";
2
+ import fs from "@vistta/fs";
3
+ import { parseArgs } from "../utils.js";
4
+ import DefaultCommand from "./default.js";
5
+
6
+ const cwd = process.cwd();
7
+
8
+ export default class extends DefaultCommand {
9
+ #options = {};
10
+ #results = [];
11
+ #failed;
12
+ #suite;
13
+ #runningCounter = 0;
14
+
15
+ constructor() {
16
+ super();
17
+ const suite = this.suite.bind(this, false);
18
+ suite.only = this.suite.bind(this, true);
19
+ 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
+ }
26
+
27
+ help() {
28
+ console.print("Runs all the tests files that match the pattern/s in the current project");
29
+ console.print("\nUsage:");
30
+ console.print("vistta test [...patterns]\tRuns all the tests files that match the pattern/s in the current project");
31
+ console.print(
32
+ 'vistta test --filter="pattern"\tRuns all the tests that match the filter pattern/s in the current project',
33
+ );
34
+ console.print("vistta test --only\tRuns all the tests that have the only");
35
+ }
36
+
37
+ async main(_, ...argv) {
38
+ let [args, options] = parseArgs(argv);
39
+ this.#options = options;
40
+ if (args.length === 0) args = ["**/*.test.js", "**/*.test.ts"];
41
+ for (let i = 0, len = args.length; i < len; i++) args[i] = fs.resolve(cwd, args[i]);
42
+ const entries = fs.glob(args);
43
+ let entry = (await entries.next())?.value;
44
+ if (!process.env.NODE_DEBUG) console.disable();
45
+ while (entry) {
46
+ try {
47
+ process.chdir(fs.dirname(entry));
48
+ if (!entry.includes("\\node_modules\\")) await import(fs.resolve(entry));
49
+ await this.#await();
50
+ } catch {
51
+ this.#failed = true;
52
+ }
53
+ entry = (await entries.next())?.value;
54
+ }
55
+ if (!process.env.NODE_DEBUG) console.enable();
56
+ let output = "";
57
+ for (let i = 0, len = this.#results.length; i < len; i++) {
58
+ const { name, tests } = this.#results[i];
59
+ if (name) output += `\n${name}\n`;
60
+ const [results, passing, total, time] = processTests(tests);
61
+ output += `${results}${passing === total ? COLORS.GREEN : COLORS.RED}${passing}/${total} passing ${COLORS.RESET + COLORS.DIM}(${time}ms)${COLORS.RESET}\n`;
62
+ }
63
+ console.print(output);
64
+ process.exit(this.#failed ? -1 : 0);
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--;
106
+ }
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
+ }
package/global.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference path="@vistta/console" />
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { Bundler } from "./classes/bundler.js";
2
+ export { Command } from "./classes/command.js";
3
+ export { Loader } from "./classes/loader.js";
4
+ export { Runtime } from "./classes/runtime.js";
5
+ export { default as BundleCommand } from "./commands/bundle.js";
6
+ export { default as DefaultCommand } from "./commands/default.js";
7
+ export { default as ProjectCommand } from "./commands/project.js";
8
+ export { default as TestCommand } from "./commands/test.js";
9
+ export { parseArgs } from "./utils.js";
@@ -0,0 +1,20 @@
1
+ import { Bundler } from "../classes/bundler.js";
2
+ import { Loader } from "../index.js";
3
+ import { remove } from "../utils.js";
4
+
5
+ export default class BundlerLoader extends Loader {
6
+ async load(_, { path, ...options }) {
7
+ options.write = false;
8
+ remove(options, "type", "extension");
9
+ const { code, files, resources, errors, warnings } = await new Bundler(this).run(path, options);
10
+ return {
11
+ code:
12
+ `export const code = ${JSON.stringify(code)}; ` +
13
+ `export const files = ${JSON.stringify(files)}; ` +
14
+ `export const resources = ${JSON.stringify(resources)}; ` +
15
+ `export const errors = ${JSON.stringify(errors)}; ` +
16
+ `export const warnings = ${JSON.stringify(warnings)};`,
17
+ resources: resources,
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ import fs from "@vistta/fs";
2
+ import { Loader } from "../index.js";
3
+
4
+ export default class FileLoader extends Loader {
5
+ async load(_, { path, encoding }) {
6
+ const urlHash = fs.fileId(path) + fs.extname(path);
7
+ return {
8
+ code: `export default ${JSON.stringify(await fs.readFile(path, encoding || "utf8"))}`,
9
+ resources: [{ path, hash: urlHash }],
10
+ };
11
+ }
12
+ }