x4js 2.2.74 → 2.3.2
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/bin/x4js.mjs +3 -0
- package/cli/build-options.mjs +68 -0
- package/cli/build.mjs +33 -0
- package/cli/config.mjs +165 -0
- package/cli/copy-plugin.mjs +115 -0
- package/cli/create.mjs +159 -0
- package/cli/dev-client.js +41 -0
- package/cli/dev.mjs +190 -0
- package/cli/less-plugin.mjs +47 -0
- package/cli/log.mjs +41 -0
- package/cli/main.mjs +74 -0
- package/cli/templates-source.mjs +61 -0
- package/cli/templates.mjs +13 -0
- package/package.json +10 -1
- package/readme.npm.md +248 -0
- package/src/components/themes.scss +44 -44
- package/src/x4.scss +4 -4
package/cli/dev.mjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import esbuild from "esbuild";
|
|
6
|
+
import { loadConfig, resolveTls } from "./config.mjs";
|
|
7
|
+
import { createBuildOptions } from "./build-options.mjs";
|
|
8
|
+
import { info, success, failure } from "./log.mjs";
|
|
9
|
+
|
|
10
|
+
function openBrowser(url) {
|
|
11
|
+
let command;
|
|
12
|
+
let args;
|
|
13
|
+
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
command = "cmd";
|
|
16
|
+
args = ["/c", "start", "", url];
|
|
17
|
+
}
|
|
18
|
+
else if (process.platform === "darwin") {
|
|
19
|
+
command = "open";
|
|
20
|
+
args = [url];
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
command = "xdg-open";
|
|
24
|
+
args = [url];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const child = spawn(command, args, {
|
|
28
|
+
detached: true,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
});
|
|
31
|
+
child.unref();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function protocolConfig(config, values) {
|
|
35
|
+
if (values.http && values.https)
|
|
36
|
+
throw new Error("--http and --https are mutually exclusive");
|
|
37
|
+
|
|
38
|
+
let https = config.dev.https;
|
|
39
|
+
if (values.http)
|
|
40
|
+
https = false;
|
|
41
|
+
if (values.https)
|
|
42
|
+
https = true;
|
|
43
|
+
|
|
44
|
+
return https;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function serverUrl(host, port, https) {
|
|
48
|
+
let displayHost = host;
|
|
49
|
+
if (host === "0.0.0.0")
|
|
50
|
+
displayHost = "127.0.0.1";
|
|
51
|
+
else if (host === "::")
|
|
52
|
+
displayHost = "[::1]";
|
|
53
|
+
else if (host.includes(":") && !host.startsWith("["))
|
|
54
|
+
displayHost = `[${host}]`;
|
|
55
|
+
return `${https ? "https" : "http"}://${displayHost}:${port}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function dev(argv = [], root = process.cwd()) {
|
|
59
|
+
const { values, positionals } = parseArgs({
|
|
60
|
+
args: argv,
|
|
61
|
+
options: {
|
|
62
|
+
host: { type: "string" },
|
|
63
|
+
port: { type: "string" },
|
|
64
|
+
http: { type: "boolean", default: false },
|
|
65
|
+
https: { type: "boolean", default: false },
|
|
66
|
+
open: { type: "boolean", default: false },
|
|
67
|
+
},
|
|
68
|
+
allowPositionals: true,
|
|
69
|
+
strict: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (positionals.length)
|
|
73
|
+
throw new Error(`Unexpected argument: ${positionals[0]}`);
|
|
74
|
+
|
|
75
|
+
if (values.port !== undefined && !/^\d+$/.test(values.port))
|
|
76
|
+
throw new Error("--port must be an integer");
|
|
77
|
+
|
|
78
|
+
let context;
|
|
79
|
+
let packageWatcher;
|
|
80
|
+
let stopping = false;
|
|
81
|
+
let restartTimer;
|
|
82
|
+
let restartChain = Promise.resolve();
|
|
83
|
+
let browserOpened = false;
|
|
84
|
+
|
|
85
|
+
async function start(config) {
|
|
86
|
+
const host = values.host ?? config.dev.host;
|
|
87
|
+
const port = values.port === undefined ? config.dev.port : Number(values.port);
|
|
88
|
+
if (port !== undefined && (port < 0 || port > 65535))
|
|
89
|
+
throw new Error("--port must be between 0 and 65535");
|
|
90
|
+
|
|
91
|
+
const useHttps = protocolConfig(config, values);
|
|
92
|
+
const serveOptions = {
|
|
93
|
+
host,
|
|
94
|
+
servedir: config.outdir,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (port !== undefined)
|
|
98
|
+
serveOptions.port = port;
|
|
99
|
+
|
|
100
|
+
if (useHttps)
|
|
101
|
+
Object.assign(serveOptions, resolveTls(config));
|
|
102
|
+
|
|
103
|
+
const next = await esbuild.context(createBuildOptions(config, "dev"));
|
|
104
|
+
try {
|
|
105
|
+
await next.watch();
|
|
106
|
+
const result = await next.serve(serveOptions);
|
|
107
|
+
context = next;
|
|
108
|
+
|
|
109
|
+
const actualHost = result.hosts.includes(host) ? host : (result.hosts[0] ?? host);
|
|
110
|
+
const url = serverUrl(actualHost, result.port, useHttps);
|
|
111
|
+
info("mode", "dev");
|
|
112
|
+
info("outdir", config.outdir);
|
|
113
|
+
success("server", url);
|
|
114
|
+
|
|
115
|
+
if (values.open && !browserOpened) {
|
|
116
|
+
browserOpened = true;
|
|
117
|
+
openBrowser(url);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
await next.dispose();
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function reload() {
|
|
127
|
+
let config;
|
|
128
|
+
try {
|
|
129
|
+
config = loadConfig(root);
|
|
130
|
+
protocolConfig(config, values);
|
|
131
|
+
if ((values.https || (!values.http && config.dev.https)))
|
|
132
|
+
resolveTls(config);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
failure(`config: ${error.message}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const previous = context;
|
|
140
|
+
context = undefined;
|
|
141
|
+
if (previous)
|
|
142
|
+
await previous.dispose();
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await start(config);
|
|
146
|
+
success("config", "reloaded");
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
failure(`restart: ${error.message}`);
|
|
150
|
+
// The old context cannot be restored after dispose. Keep watching
|
|
151
|
+
// package.json so fixing the configuration starts dev again.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function scheduleReload() {
|
|
156
|
+
clearTimeout(restartTimer);
|
|
157
|
+
restartTimer = setTimeout(() => {
|
|
158
|
+
restartChain = restartChain.then(reload, reload);
|
|
159
|
+
}, 150);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const initialConfig = loadConfig(root);
|
|
163
|
+
await start(initialConfig);
|
|
164
|
+
|
|
165
|
+
packageWatcher = fs.watch(path.resolve(root), { persistent: true }, (_event, filename) => {
|
|
166
|
+
if (filename === null || filename.toString() === "package.json")
|
|
167
|
+
scheduleReload();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
async function stop() {
|
|
171
|
+
if (stopping)
|
|
172
|
+
return;
|
|
173
|
+
stopping = true;
|
|
174
|
+
clearTimeout(restartTimer);
|
|
175
|
+
packageWatcher?.close();
|
|
176
|
+
await restartChain.catch(() => {});
|
|
177
|
+
await context?.dispose();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
process.once("SIGINT", async () => {
|
|
181
|
+
await stop();
|
|
182
|
+
process.exit(0);
|
|
183
|
+
});
|
|
184
|
+
process.once("SIGTERM", async () => {
|
|
185
|
+
await stop();
|
|
186
|
+
process.exit(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return { stop };
|
|
190
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
async function loadLess(projectRoot) {
|
|
7
|
+
const require = createRequire(path.join(projectRoot, "package.json"));
|
|
8
|
+
let resolved;
|
|
9
|
+
try {
|
|
10
|
+
resolved = require.resolve("less");
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'LESS support requires the "less" package.\n' +
|
|
15
|
+
'Install it with: npm install -D less',
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const module = await import(pathToFileURL(resolved).href);
|
|
20
|
+
return module.default ?? module;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function lessPlugin(projectRoot) {
|
|
24
|
+
let lessPromise;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
name: "x4-less",
|
|
28
|
+
setup(build) {
|
|
29
|
+
build.onLoad({ filter: /\.less$/ }, async (args) => {
|
|
30
|
+
const source = await fs.readFile(args.path, "utf8");
|
|
31
|
+
lessPromise ??= loadLess(projectRoot);
|
|
32
|
+
const less = await lessPromise;
|
|
33
|
+
|
|
34
|
+
const result = await less.render(source, {
|
|
35
|
+
filename: args.path,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
contents: result.css,
|
|
40
|
+
loader: "css",
|
|
41
|
+
resolveDir: path.dirname(args.path),
|
|
42
|
+
watchFiles: [args.path, ...(result.imports ?? [])],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
package/cli/log.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
2
|
+
|
|
3
|
+
function useColor(stream = process.stdout) {
|
|
4
|
+
if (process.env.NO_COLOR !== undefined)
|
|
5
|
+
return false;
|
|
6
|
+
if (process.env.FORCE_COLOR !== undefined)
|
|
7
|
+
return process.env.FORCE_COLOR !== "0";
|
|
8
|
+
return !!stream.isTTY;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function paint(style, value, stream = process.stdout) {
|
|
12
|
+
const text = String(value);
|
|
13
|
+
return useColor(stream) ? styleText(style, text) : text;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const color = {
|
|
17
|
+
cyan: (value) => paint("cyan", value),
|
|
18
|
+
green: (value) => paint("green", value),
|
|
19
|
+
yellow: (value) => paint("yellow", value),
|
|
20
|
+
red: (value) => paint("red", value, process.stderr),
|
|
21
|
+
gray: (value) => paint("gray", value),
|
|
22
|
+
white: (value) => paint("white", value),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function info(label, value = "") {
|
|
26
|
+
const left = color.cyan(String(label).padEnd(11));
|
|
27
|
+
console.log(value === "" ? left.trimEnd() : `${left}${value}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function success(label, value = "") {
|
|
31
|
+
const left = color.green(String(label).padEnd(11));
|
|
32
|
+
console.log(value === "" ? left.trimEnd() : `${left}${value}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function warning(message) {
|
|
36
|
+
console.warn(color.yellow(message));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function failure(message) {
|
|
40
|
+
console.error(color.red(message));
|
|
41
|
+
}
|
package/cli/main.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { failure } from "./log.mjs";
|
|
5
|
+
|
|
6
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageFile = path.join(cliDir, "..", "package.json");
|
|
8
|
+
|
|
9
|
+
function getVersion() {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(fs.readFileSync(packageFile, "utf8")).version ?? "unknown";
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return "unknown";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function printHelp(version) {
|
|
19
|
+
console.log(`x4js ${version}
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
x4js create <project> [--template <name>] [--no-install]
|
|
23
|
+
x4js templates
|
|
24
|
+
x4js dev [--host <host>] [--port <port>] [--http|--https] [--open]
|
|
25
|
+
x4js build [--debug]
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
-h, --help Show help
|
|
29
|
+
-v, --version Show version`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const version = getVersion();
|
|
33
|
+
const [command, ...argv] = process.argv.slice(2);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
switch (command) {
|
|
37
|
+
case "create": {
|
|
38
|
+
const { create } = await import("./create.mjs");
|
|
39
|
+
await create(argv, { version });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "templates": {
|
|
43
|
+
const { templates } = await import("./templates.mjs");
|
|
44
|
+
await templates(argv, { version });
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "dev": {
|
|
48
|
+
const { dev } = await import("./dev.mjs");
|
|
49
|
+
await dev(argv);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "build": {
|
|
53
|
+
const { build } = await import("./build.mjs");
|
|
54
|
+
await build(argv);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case undefined:
|
|
58
|
+
case "help":
|
|
59
|
+
case "--help":
|
|
60
|
+
case "-h":
|
|
61
|
+
printHelp(version);
|
|
62
|
+
break;
|
|
63
|
+
case "--version":
|
|
64
|
+
case "-v":
|
|
65
|
+
console.log(version);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
throw new Error(`Unknown command '${command}'. Run 'x4js --help'.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
failure(error instanceof Error ? error.message : String(error));
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const OWNER = "rlibre";
|
|
2
|
+
const REPO = "x4-templates";
|
|
3
|
+
const REF = "main";
|
|
4
|
+
|
|
5
|
+
function headers(version = "unknown") {
|
|
6
|
+
return {
|
|
7
|
+
"Accept": "application/vnd.github+json",
|
|
8
|
+
"User-Agent": `x4js/${version}`,
|
|
9
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function checkedFetch(url, options) {
|
|
14
|
+
const response = await fetch(url, options);
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
throw new Error(`Template repository request failed (${response.status} ${response.statusText})`);
|
|
17
|
+
return response;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchTemplates(version = "unknown") {
|
|
21
|
+
const url = `https://raw.githubusercontent.com/${OWNER}/${REPO}/${REF}/templates.json`;
|
|
22
|
+
const response = await checkedFetch(url, { headers: headers(version) });
|
|
23
|
+
const manifest = await response.json();
|
|
24
|
+
|
|
25
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
26
|
+
throw new Error("Invalid templates.json");
|
|
27
|
+
|
|
28
|
+
for (const [name, description] of Object.entries(manifest)) {
|
|
29
|
+
if (!name || typeof description !== "string")
|
|
30
|
+
throw new Error("Invalid templates.json entry");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return manifest;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function fetchTemplateFiles(name, version = "unknown") {
|
|
37
|
+
const treeUrl = `https://api.github.com/repos/${OWNER}/${REPO}/git/trees/${REF}?recursive=1`;
|
|
38
|
+
const response = await checkedFetch(treeUrl, { headers: headers(version) });
|
|
39
|
+
const tree = await response.json();
|
|
40
|
+
|
|
41
|
+
if (!Array.isArray(tree.tree))
|
|
42
|
+
throw new Error("Invalid template repository tree");
|
|
43
|
+
|
|
44
|
+
const prefix = `${name}/`;
|
|
45
|
+
const files = tree.tree
|
|
46
|
+
.filter((item) => item.type === "blob" && item.path.startsWith(prefix))
|
|
47
|
+
.map((item) => ({
|
|
48
|
+
path: item.path.slice(prefix.length),
|
|
49
|
+
url: `https://raw.githubusercontent.com/${OWNER}/${REPO}/${REF}/${item.path.split("/").map(encodeURIComponent).join("/")}`,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
if (!files.length)
|
|
53
|
+
throw new Error(`Template '${name}' contains no files`);
|
|
54
|
+
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function downloadTemplateFile(url, version = "unknown") {
|
|
59
|
+
const response = await checkedFetch(url, { headers: headers(version) });
|
|
60
|
+
return Buffer.from(await response.arrayBuffer());
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fetchTemplates } from "./templates-source.mjs";
|
|
2
|
+
import { color } from "./log.mjs";
|
|
3
|
+
|
|
4
|
+
export async function templates(_argv = [], { version = "unknown" } = {}) {
|
|
5
|
+
const manifest = await fetchTemplates(version);
|
|
6
|
+
const names = Object.keys(manifest).sort();
|
|
7
|
+
const width = names.reduce((max, name) => Math.max(max, name.length), 0) + 3;
|
|
8
|
+
|
|
9
|
+
console.log(color.cyan("Available templates"));
|
|
10
|
+
console.log();
|
|
11
|
+
for (const name of names)
|
|
12
|
+
console.log(` ${color.green(name.padEnd(width))}${manifest[name]}`);
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x4js",
|
|
3
|
-
"version": "2.2
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/x4.ts",
|
|
6
6
|
"module": "src/x4.ts",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
".": "./src/x4.ts",
|
|
10
10
|
"./monaco": "./src/components/monaco/monaco.ts"
|
|
11
11
|
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"x4js": "./bin/x4js.mjs"
|
|
14
|
+
},
|
|
12
15
|
"scripts": {
|
|
13
16
|
"build-publish": "deno run --allow-all ./publish.ts",
|
|
14
17
|
"build-doc": "typedoc",
|
|
@@ -16,6 +19,8 @@
|
|
|
16
19
|
},
|
|
17
20
|
"files": [
|
|
18
21
|
"src",
|
|
22
|
+
"bin",
|
|
23
|
+
"cli",
|
|
19
24
|
"README.md",
|
|
20
25
|
"LICENSE"
|
|
21
26
|
],
|
|
@@ -32,6 +37,10 @@
|
|
|
32
37
|
"desktop",
|
|
33
38
|
"application"
|
|
34
39
|
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"esbuild": "0.28",
|
|
42
|
+
"esbuild-sass-plugin": "^3.7"
|
|
43
|
+
},
|
|
35
44
|
"devDependencies": {
|
|
36
45
|
"typescript": "^5.8.3",
|
|
37
46
|
"typedoc": "^0.28.19",
|