@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,34 @@
1
+ import { Runtime } from "../classes/runtime.js";
2
+
3
+ let runtime;
4
+
5
+ export async function initialize({ loaders, resolvers, options, port }) {
6
+ runtime = new Runtime({ loaders, resolvers, options });
7
+ if (port) {
8
+ const shared = {};
9
+ const events = {};
10
+ global.main = {
11
+ send: (event, ...args) => args.port.postMessage(JSON.stringify({ event, args })),
12
+ on: (event, callback) => (events[event] ? events[event].push(callback) : (events[event] = [callback])),
13
+ shared: new Proxy(shared, {
14
+ set(_, prop, value) {
15
+ port.postMessage(JSON.stringify({ sync: true, prop, value }));
16
+ return Reflect.set(...arguments);
17
+ },
18
+ }),
19
+ };
20
+ port.on("message", (msg) => {
21
+ const { event, sync, args, prop, value } = JSON.parse(msg);
22
+ if (event) events[event]?.forEach((callback) => callback(...args));
23
+ else if (sync) shared[prop] = value;
24
+ });
25
+ }
26
+ }
27
+
28
+ export async function resolve(specifier, context, nextResolve, options) {
29
+ return runtime.resolve(specifier, context, nextResolve, options);
30
+ }
31
+
32
+ export async function load(url, context, nextLoad) {
33
+ return runtime.load(url, context, nextLoad);
34
+ }
@@ -0,0 +1,11 @@
1
+ import { Loader } from "../index.js";
2
+
3
+ export default class JSONLoader extends Loader {
4
+ async load(source, { path }) {
5
+ try {
6
+ return { code: `export default (${JSON.stringify(JSON.parse(source))});` };
7
+ } catch (e) {
8
+ return { errors: [`${path}(0,0): json error: ${e.message}.`] };
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,29 @@
1
+ import { transform } from "esbuild";
2
+ import { Loader } from "../index.js";
3
+
4
+ export default class TypescriptLoader extends Loader {
5
+ compilerOptions;
6
+
7
+ constructor(options) {
8
+ super(options);
9
+ this.compilerOptions = options.compiler || {};
10
+ }
11
+
12
+ async load(source) {
13
+ try {
14
+ return await transform(source, {
15
+ format: "esm",
16
+ treeShaking: true,
17
+ loader: "ts",
18
+ tsconfigRaw: {
19
+ compilerOptions: this.compilerOptions,
20
+ },
21
+ jsx: "preserve",
22
+ sourcemap: false,
23
+ target: "esnext",
24
+ });
25
+ } catch (e) {
26
+ return { errors: [e] };
27
+ }
28
+ }
29
+ }
package/main.js ADDED
@@ -0,0 +1 @@
1
+ process.vistta.main();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@vistta/cli",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "exports": {
7
+ ".": "./index.js",
8
+ "./config": "./tsconfig.json",
9
+ "./commands/default": "./commands/default.js",
10
+ "./commands/bundle": "./commands/bundle.js",
11
+ "./commands/project": "./commands/project.js",
12
+ "./commands/test": "./commands/test.js",
13
+ "./loaders/bundler": "./loaders/bundler.js",
14
+ "./loaders/file": "./loaders/file.js",
15
+ "./loaders/json": "./loaders/json.js",
16
+ "./loaders/typescript": "./loaders/typescript.js"
17
+ },
18
+ "bin": {
19
+ "vistta": "bin.js"
20
+ },
21
+ "scripts": {
22
+ "postinstall": ""
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/vistta-org/cli.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=22.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@vistta/console": "latest",
33
+ "@vistta/fs": "latest",
34
+ "@vistta/utils": "latest",
35
+ "esbuild": "^0.25.5",
36
+ "semver": "^7.7.2"
37
+ },
38
+ "author": "Tiago Terenas Almeida",
39
+ "license": "Apache 2.0 with Commons Clause",
40
+ "description": "Vistta CLI is a framework for extending the Node.js loader API and building reusable, customizable commands tools"
41
+ }
package/register.js ADDED
@@ -0,0 +1,51 @@
1
+ import "@vistta/console/global";
2
+ import { register } from "node:module";
3
+ import { MessageChannel } from "node:worker_threads";
4
+ import { fs, importConfig, saveCrashReport } from "./utils.js";
5
+
6
+ console.clear();
7
+ console.print(`Vistta CLI v${process.env.CLI_VERSION}\n`);
8
+
9
+ const { port1, port2 } = new MessageChannel();
10
+ const shared = {};
11
+ const events = {};
12
+ global.loaders = {
13
+ send: (event, ...args) => port1.postMessage(JSON.stringify({ event, args })),
14
+ on: (event, callback) => (events[event] ? events[event].push(callback) : (events[event] = [callback])),
15
+ shared: new Proxy(shared, {
16
+ set(_, prop, value) {
17
+ port1.postMessage(JSON.stringify({ sync: true, prop, value }));
18
+ return Reflect.set(...arguments);
19
+ },
20
+ }),
21
+ };
22
+ port1.on("message", (msg) => {
23
+ const { event, sync, args, prop, value } = JSON.parse(msg);
24
+ if (event) events[event]?.forEach((callback) => callback(...args));
25
+ else if (sync) shared[prop] = value;
26
+ });
27
+ port1.unref();
28
+
29
+ const config = await importConfig({
30
+ cli: {
31
+ commands: {
32
+ default: fs.resolve(import.meta.dirname, "./commands/default.js"),
33
+ bundle: fs.resolve(import.meta.dirname, "./commands/bundle.js"),
34
+ project: fs.resolve(import.meta.dirname, "./commands/project.js"),
35
+ test: fs.resolve(import.meta.dirname, "./commands/test.js"),
36
+ },
37
+ },
38
+ });
39
+ const command = new (await import(config.cli.commands[process.argv[2]] || config.cli.commands["default"])).default();
40
+ process.vistta = {
41
+ loaders: command.loaders,
42
+ resolvers: command.resolvers,
43
+ options: config,
44
+ port: port2,
45
+ };
46
+ register("./loaders/index.js", import.meta.url, { data: process.vistta, transferList: [port2] });
47
+ process.vistta.main = command[process.env.NODE_HELP ? "help" : "main"].bind(command, ...process.argv.slice(2));
48
+ process.on("uncaughtException", (error) => (console.error(error.stack), saveCrashReport(), process.exit(1)));
49
+ process.on("warning", () => {
50
+ /* Do nothing */
51
+ });
@@ -0,0 +1,38 @@
1
+ import { Command } from "../classes/command.js";
2
+
3
+ suite("CLI", () => {
4
+ let cmd;
5
+ test("register adds a loader", () => {
6
+ cmd = new Command();
7
+ cmd.register("/path/to/loader.js", { type: "custom", filter: ".*\\.custom" });
8
+ expect(cmd.loaders.length).toEqual(1);
9
+ expect(cmd.loaders[0].main).toEqual("/path/to/loader.js");
10
+ expect(cmd.loaders[0].type).toEqual("custom");
11
+ expect(cmd.loaders[0].filter).toEqual(".*\\.custom");
12
+ });
13
+
14
+ test("resolve adds new extensions", () => {
15
+ cmd = new Command();
16
+ cmd.resolve("ts", "json");
17
+ expect(cmd.resolvers).toInclude("ts");
18
+ expect(cmd.resolvers).toInclude("json");
19
+ });
20
+
21
+ test("main throws error if not implemented", () => {
22
+ cmd = new Command();
23
+ expect(() => cmd.main()).toThrow("Method 'main' must be implemented.");
24
+ });
25
+
26
+ test("help does not throw by default", () => {
27
+ cmd = new Command();
28
+ expect(() => cmd.help()).not.toThrow();
29
+ });
30
+
31
+ test("define sets global and env variables", () => {
32
+ cmd = new Command();
33
+ cmd.define("global", "FOO", 123);
34
+ cmd.define("env", "BAR", "baz");
35
+ expect(global["FOO"]).toEqual(123);
36
+ expect(process.env["BAR"]).toEqual("baz");
37
+ });
38
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": false,
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "resolveJsonModule": true,
7
+ "module": "esnext",
8
+ "target": "ESNext",
9
+ "esModuleInterop": true,
10
+ "moduleResolution": "bundler",
11
+ "skipLibCheck": true,
12
+ "jsx": "react",
13
+ "typeRoots": ["./global.d.ts"],
14
+ "paths": {
15
+ "@/*": ["./*"]
16
+ }
17
+ }
18
+ }
package/utils.js ADDED
@@ -0,0 +1,208 @@
1
+ import fs from "@vistta/fs";
2
+ import { assign, async, extract, remove } from "@vistta/utils";
3
+ import { spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { inc, satisfies, valid } from "semver";
6
+
7
+ export const ENABLED_NODE_OPTIONS = {
8
+ "-w": "NODE_WATCH",
9
+ "--watch": "NODE_WATCH",
10
+ "--node-memory-debug": true,
11
+ "--tls-cipher-list": true,
12
+ "--prof": true,
13
+ "--prof-process": true,
14
+ "--enable-source-maps": true,
15
+ "--trace-deprecation": true,
16
+ "--max-old-space-size": true,
17
+ "--inspect": true,
18
+ "--inspect-brk": true,
19
+ };
20
+
21
+ export async function importJSON(filepath) {
22
+ try {
23
+ return JSON.parse(await fs.readFile(filepath));
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ export async function importEnv(filepath) {
30
+ try {
31
+ const env = {};
32
+ const entries = (await fs.readFile(filepath, "utf8")).split("\n");
33
+ for (let i = 0, len = entries.length; i < len; i++) {
34
+ const [key, value] = entries[i].split("=");
35
+ env[key] = value;
36
+ }
37
+ return env;
38
+ } catch {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ export async function importConfig(options = {}) {
44
+ try {
45
+ const helper = async (file) => {
46
+ file = fileURLToPath(import.meta.resolve(file));
47
+ const {
48
+ extends: parent,
49
+ cliOptions: cli = {},
50
+ compilerOptions: compiler = {},
51
+ bundlerOptions: bundler = {},
52
+ } = await importJSON(file);
53
+ if (cli.commands) {
54
+ const commands = Object.keys(cli.commands);
55
+ const dirname = fs.dirname(file);
56
+ for (let i = 0, len = commands.length; i < len; i++)
57
+ cli.commands[commands[i]] = fs.resolve(dirname, cli.commands[commands[i]]);
58
+ }
59
+ if (parent) return assign(await helper(parent), { cli, compiler, bundler });
60
+ return { cli, compiler, bundler };
61
+ };
62
+ let file = fs.resolve(process.cwd(), "tsconfig.json");
63
+ if (!fs.existsSync(file)) file = fs.resolve(process.cwd(), "jsconfig.json");
64
+ if (!fs.existsSync(file)) return options;
65
+ return assign(options, await helper(file));
66
+ } catch (e) {
67
+ console.error(e.trace || e.message);
68
+ return options;
69
+ }
70
+ }
71
+
72
+ export async function getOutdatedPackages(dirname) {
73
+ const packages = (await getProjectLock(dirname))?.packages || {};
74
+ const data = {};
75
+ const promises = [];
76
+ async function processPackage(filepath, workspace) {
77
+ if (!fs.existsSync(filepath)) {
78
+ if (workspace) return;
79
+ else throw new Error("No Package found");
80
+ }
81
+ const { dependencies, devDependencies, workspaces } = await importJSON(filepath);
82
+ await fetchPackageUpdates(filepath, dependencies);
83
+ await fetchPackageUpdates(filepath, devDependencies, true);
84
+ if (!(workspaces?.length > 0)) return;
85
+ const workspaceDirnames = await resolveWorkspacesDirnames(workspaces);
86
+ for (let i = 0, len = workspaceDirnames.length; i < len; i++)
87
+ await processPackage(fs.resolve(workspaceDirnames[i], "package.json"), true);
88
+ }
89
+ async function fetchPackageUpdates(path, deps = {}, dev) {
90
+ const keys = Object.keys(deps);
91
+ for (let i = 0, len = keys.length; i < len; i++) {
92
+ const packageName = keys[i];
93
+ const { version, resolved, link } = packages["node_modules/" + packageName] || {};
94
+ if (link) continue;
95
+ if (!version) throw new Error(`Module ${packageName} not found `);
96
+ promises.push(
97
+ new Promise((resolve, reject) =>
98
+ fetch(resolved ? resolved.split("/-/")[0] + "/latest" : `https://registry.npmjs.org/${packageName}/latest`)
99
+ .then((response) => response.json())
100
+ .then(resolve)
101
+ .catch(reject),
102
+ ),
103
+ );
104
+ data[packageName] = {
105
+ package: path,
106
+ current: version,
107
+ wanted: deps[packageName],
108
+ dev,
109
+ };
110
+ }
111
+ }
112
+ const result = [];
113
+ await processPackage(fs.resolve(dirname, "package.json"));
114
+ const values = await Promise.all(promises);
115
+ for (let i = 0, len = values.length; i < len; i++) {
116
+ const module = data[values[i]?.name];
117
+ module.name = values[i]?.name;
118
+ module.latest = values[i]?.version;
119
+ if (!module || !module?.latest || !module?.current || !module?.wanted) throw new Error(`Internal Error`);
120
+ if (module.current === module.latest) continue;
121
+ module.wanted = satisfies(module.latest, module.wanted) ? module.latest : module.current;
122
+ result.push(module);
123
+ }
124
+ return result;
125
+ }
126
+
127
+ export async function incrementPackageVersion(filepath, value, id) {
128
+ const packageJSON = await importJSON(filepath);
129
+ if (["major", "minor", "patch", "premajor", "preminor", "prepatch", "prerelease"].includes(value))
130
+ packageJSON.version = inc(packageJSON.version, value, id);
131
+ else if (valid(value)) packageJSON.version = value;
132
+ else throw new TypeError("Invalid version or increment type.");
133
+ await fs.writeFile(filepath, JSON.stringify(packageJSON, null, 2));
134
+ return packageJSON.version;
135
+ }
136
+
137
+ export function saveCrashReport() {
138
+ if (!process.env.NODE_CRASH_REPORT) return;
139
+ const crashFolder = fs.resolve(process.cwd(), ".logs");
140
+ if (!fs.existsSync(crashFolder)) fs.mkdir(crashFolder);
141
+ const logs = console.logs;
142
+ let output = "";
143
+ for (let i = 0, len = logs.length; i < len; i++) {
144
+ output += logs[i].time.toISOString() + " - " + logs[i].toString().replace(/\n/gm, " ") + "\n";
145
+ }
146
+ fs.writeFileSync(fs.resolve(crashFolder, Date.now() + ".log"), output);
147
+ }
148
+
149
+ export function run(script, ...args) {
150
+ return new Promise((resolve, reject) => {
151
+ const child = spawn("node", ["--no-warnings", "--run", script, "--", ...args], {
152
+ stdio: "inherit",
153
+ shell: true,
154
+ windowsHide: true,
155
+ });
156
+ child.on("error", reject);
157
+ child.on("close", (code) => resolve(code));
158
+ process.on("SIGINT", () => !child.killed && child.kill("SIGINT"));
159
+ process.on("SIGTERM", () => !child.killed && child.kill("SIGTERM"));
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Function to parse command line arguments.
165
+ * @param {string[]} args
166
+ * @returns
167
+ */
168
+ export function parseArgs(args) {
169
+ const result = [[], {}];
170
+ for (let i = 0, len = args.length; i < len; i++) {
171
+ const [option, value] = args[i].toLowerCase().split("=");
172
+ if (option.startsWith("--")) result[1][option.slice(2)] = evaluate(value);
173
+ else result[0].push(args[i]);
174
+ }
175
+ return result;
176
+ }
177
+
178
+ export { assign, async, extract, fs, remove };
179
+
180
+ async function getProjectLock(path) {
181
+ if (fs.existsSync(path + "/package-lock.json")) return await importJSON(fs.resolve(path, "package-lock.json"));
182
+ const newPath = fs.resolve(path, "..");
183
+ if (newPath === path) throw new Error("No Package Lock found.");
184
+ return await getProjectLock(newPath);
185
+ }
186
+
187
+ async function resolveWorkspacesDirnames(workspaces) {
188
+ const paths = [];
189
+ for (let i = 0, len = workspaces.length; i < len; i++) {
190
+ if (!workspaces[i].includes("*")) {
191
+ paths.push(workspaces[i]);
192
+ continue;
193
+ }
194
+ const entries = fs.glob(workspaces[i]);
195
+ let entry = (await entries.next())?.value;
196
+ while (entry) {
197
+ paths.push(entry);
198
+ entry = (await entries.next())?.value;
199
+ }
200
+ }
201
+ return paths;
202
+ }
203
+
204
+ function evaluate(value) {
205
+ if (value == null || value === "true") return true;
206
+ if (value === "false") return false;
207
+ return value;
208
+ }