@quickgui/cli 0.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.
- package/README.md +72 -0
- package/package.json +50 -0
- package/src/args.ts +182 -0
- package/src/build.ts +773 -0
- package/src/cli.ts +126 -0
- package/src/config.ts +256 -0
- package/src/dev.ts +279 -0
- package/src/error.ts +17 -0
- package/src/index.ts +9 -0
- package/src/init.ts +107 -0
- package/src/targets.ts +85 -0
- package/templates/solid/README.md +8 -0
- package/templates/solid/gitignore +3 -0
- package/templates/solid/package.json +19 -0
- package/templates/solid/quickgui.config.ts +7 -0
- package/templates/solid/src/app.tsx +49 -0
- package/templates/solid/tsconfig.json +16 -0
package/src/init.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
import { resolveConfig } from "./config.ts";
|
|
13
|
+
import { CliError } from "./error.ts";
|
|
14
|
+
|
|
15
|
+
export interface InitProjectOptions {
|
|
16
|
+
directory: string;
|
|
17
|
+
install: boolean;
|
|
18
|
+
name?: string;
|
|
19
|
+
identifier?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const templateRoot = fileURLToPath(new URL("../templates/solid/", import.meta.url));
|
|
23
|
+
const templateFiles = [
|
|
24
|
+
["package.json", "package.json"],
|
|
25
|
+
["quickgui.config.ts", "quickgui.config.ts"],
|
|
26
|
+
["tsconfig.json", "tsconfig.json"],
|
|
27
|
+
["gitignore", ".gitignore"],
|
|
28
|
+
["README.md", "README.md"],
|
|
29
|
+
["src/app.tsx", "src/app.tsx"],
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export async function initProject(options: InitProjectOptions): Promise<string> {
|
|
33
|
+
const destination = resolve(options.directory);
|
|
34
|
+
if (existsSync(destination) && !statSync(destination).isDirectory()) {
|
|
35
|
+
throw new CliError(`Project destination is not a directory: ${destination}`);
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(destination) && readdirSync(destination).length > 0) {
|
|
38
|
+
throw new CliError(`Project destination is not empty: ${destination}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const defaultName = displayName(basename(destination));
|
|
42
|
+
const name = options.name?.trim() || defaultName;
|
|
43
|
+
const identifier = options.identifier?.trim() || `com.example.${identifierSegment(name)}`;
|
|
44
|
+
resolveConfig({ name, identifier }, destination);
|
|
45
|
+
|
|
46
|
+
const replacements: Record<string, string> = {
|
|
47
|
+
"{{APP_NAME}}": JSON.stringify(name),
|
|
48
|
+
"{{IDENTIFIER}}": JSON.stringify(identifier),
|
|
49
|
+
"{{PACKAGE_NAME}}": JSON.stringify(packageName(name)),
|
|
50
|
+
"{{README_TITLE}}": name.replaceAll("\n", " ").replaceAll("\r", " "),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
mkdirSync(destination, { recursive: true });
|
|
54
|
+
for (const [sourceName, targetName] of templateFiles) {
|
|
55
|
+
const source = join(templateRoot, sourceName);
|
|
56
|
+
if (!existsSync(source)) throw new CliError(`CLI template is missing: ${source}`);
|
|
57
|
+
const target = join(destination, targetName);
|
|
58
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
59
|
+
let contents = readFileSync(source, "utf8");
|
|
60
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
61
|
+
contents = contents.replaceAll(token, value);
|
|
62
|
+
}
|
|
63
|
+
writeFileSync(target, contents);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.install) {
|
|
67
|
+
const child = Bun.spawn(["bun", "install"], {
|
|
68
|
+
cwd: destination,
|
|
69
|
+
stdin: "inherit",
|
|
70
|
+
stdout: "inherit",
|
|
71
|
+
stderr: "inherit",
|
|
72
|
+
});
|
|
73
|
+
const status = await child.exited;
|
|
74
|
+
if (status !== 0) {
|
|
75
|
+
throw new CliError(
|
|
76
|
+
`Project created at ${destination}, but \`bun install\` failed with status ${status}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return destination;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function displayName(value: string): string {
|
|
85
|
+
const words = value
|
|
86
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
87
|
+
.split(/[^A-Za-z0-9]+/)
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
return words.length > 0
|
|
90
|
+
? words.map((word) => word[0]!.toUpperCase() + word.slice(1)).join(" ")
|
|
91
|
+
: "QuickGUI App";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function packageName(value: string): string {
|
|
95
|
+
const name = value
|
|
96
|
+
.normalize("NFKD")
|
|
97
|
+
.toLowerCase()
|
|
98
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
99
|
+
.replace(/^[._-]+|[._-]+$/g, "")
|
|
100
|
+
.slice(0, 214);
|
|
101
|
+
if (!name) throw new CliError(`Could not derive a package name from ${JSON.stringify(value)}`);
|
|
102
|
+
return name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function identifierSegment(value: string): string {
|
|
106
|
+
return packageName(value).replace(/[._]+/g, "-");
|
|
107
|
+
}
|
package/src/targets.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CliError } from "./error.ts";
|
|
2
|
+
|
|
3
|
+
export const supportedTargets = [
|
|
4
|
+
"darwin-arm64",
|
|
5
|
+
"darwin-x64",
|
|
6
|
+
"linux-arm64",
|
|
7
|
+
"linux-x64",
|
|
8
|
+
"windows-arm64",
|
|
9
|
+
"windows-x64",
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type QuickGuiTarget = (typeof supportedTargets)[number];
|
|
13
|
+
|
|
14
|
+
export interface TargetInfo {
|
|
15
|
+
target: QuickGuiTarget;
|
|
16
|
+
platform: "darwin" | "linux" | "windows";
|
|
17
|
+
architecture: "arm64" | "x64";
|
|
18
|
+
bunTarget: Bun.Build.CompileTarget;
|
|
19
|
+
nativeAddon: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const targets: Record<QuickGuiTarget, TargetInfo> = {
|
|
23
|
+
"darwin-arm64": {
|
|
24
|
+
target: "darwin-arm64",
|
|
25
|
+
platform: "darwin",
|
|
26
|
+
architecture: "arm64",
|
|
27
|
+
bunTarget: "bun-darwin-arm64",
|
|
28
|
+
nativeAddon: "quickgui-native.darwin-arm64.node",
|
|
29
|
+
},
|
|
30
|
+
"darwin-x64": {
|
|
31
|
+
target: "darwin-x64",
|
|
32
|
+
platform: "darwin",
|
|
33
|
+
architecture: "x64",
|
|
34
|
+
bunTarget: "bun-darwin-x64",
|
|
35
|
+
nativeAddon: "quickgui-native.darwin-x64.node",
|
|
36
|
+
},
|
|
37
|
+
"linux-arm64": {
|
|
38
|
+
target: "linux-arm64",
|
|
39
|
+
platform: "linux",
|
|
40
|
+
architecture: "arm64",
|
|
41
|
+
bunTarget: "bun-linux-arm64",
|
|
42
|
+
nativeAddon: "quickgui-native.linux-arm64-gnu.node",
|
|
43
|
+
},
|
|
44
|
+
"linux-x64": {
|
|
45
|
+
target: "linux-x64",
|
|
46
|
+
platform: "linux",
|
|
47
|
+
architecture: "x64",
|
|
48
|
+
bunTarget: "bun-linux-x64",
|
|
49
|
+
nativeAddon: "quickgui-native.linux-x64-gnu.node",
|
|
50
|
+
},
|
|
51
|
+
"windows-arm64": {
|
|
52
|
+
target: "windows-arm64",
|
|
53
|
+
platform: "windows",
|
|
54
|
+
architecture: "arm64",
|
|
55
|
+
bunTarget: "bun-windows-arm64",
|
|
56
|
+
nativeAddon: "quickgui-native.win32-arm64-msvc.node",
|
|
57
|
+
},
|
|
58
|
+
"windows-x64": {
|
|
59
|
+
target: "windows-x64",
|
|
60
|
+
platform: "windows",
|
|
61
|
+
architecture: "x64",
|
|
62
|
+
bunTarget: "bun-windows-x64",
|
|
63
|
+
nativeAddon: "quickgui-native.win32-x64-msvc.node",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function parseTarget(value: string): QuickGuiTarget {
|
|
68
|
+
if ((supportedTargets as readonly string[]).includes(value)) return value as QuickGuiTarget;
|
|
69
|
+
throw new CliError(
|
|
70
|
+
`Unsupported target \`${value}\`. Expected one of: ${supportedTargets.join(", ")}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function targetInfo(target: QuickGuiTarget): TargetInfo {
|
|
75
|
+
return targets[target];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function hostTarget(): QuickGuiTarget {
|
|
79
|
+
const architecture = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : null;
|
|
80
|
+
if (!architecture) throw new CliError(`Unsupported host architecture: ${process.arch}`);
|
|
81
|
+
if (process.platform === "darwin") return `darwin-${architecture}`;
|
|
82
|
+
if (process.platform === "linux") return `linux-${architecture}`;
|
|
83
|
+
if (process.platform === "win32") return `windows-${architecture}`;
|
|
84
|
+
throw new CliError(`Unsupported host platform: ${process.platform}`);
|
|
85
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": {{PACKAGE_NAME}},
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "quickgui dev",
|
|
7
|
+
"build": "quickgui build"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@quickgui/native": "^0.0.1",
|
|
11
|
+
"@quickgui/solid": "^0.0.1",
|
|
12
|
+
"solid-js": "2.0.0-rc.3"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@quickgui/cli": "^0.0.1",
|
|
16
|
+
"@types/bun": "latest",
|
|
17
|
+
"typescript": "latest"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { app, Window } from "@quickgui/native";
|
|
2
|
+
import { Button, Text, View, createRenderer } from "@quickgui/solid";
|
|
3
|
+
import { createSignal } from "solid-js";
|
|
4
|
+
|
|
5
|
+
await app.whenReady();
|
|
6
|
+
new Window({
|
|
7
|
+
title: {{APP_NAME}},
|
|
8
|
+
width: 720,
|
|
9
|
+
height: 480,
|
|
10
|
+
background: "#0b1020",
|
|
11
|
+
renderer: createRenderer(() => <Counter />),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function Counter() {
|
|
15
|
+
const [count, setCount] = createSignal(0);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View
|
|
19
|
+
style={{
|
|
20
|
+
display: "flex",
|
|
21
|
+
width: "100%",
|
|
22
|
+
height: "100%",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
background: "#0b1020",
|
|
26
|
+
color: "#f8fafc",
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<View style={{ display: "flex", flexDirection: "column", width: 360, gap: 16 }}>
|
|
30
|
+
<Text style={{ fontSize: 28, fontWeight: 700 }}>QuickGUI + Solid</Text>
|
|
31
|
+
<Text style={{ color: "#94a3b8" }}>Count: {count()}</Text>
|
|
32
|
+
<Button
|
|
33
|
+
onClick={() => setCount((value) => value + 1)}
|
|
34
|
+
style={{
|
|
35
|
+
height: 44,
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
justifyContent: "center",
|
|
38
|
+
background: "#2563eb",
|
|
39
|
+
borderRadius: 9,
|
|
40
|
+
cursor: "default",
|
|
41
|
+
appRegion: "no-drag",
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
Increment
|
|
45
|
+
</Button>
|
|
46
|
+
</View>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowImportingTsExtensions": true,
|
|
4
|
+
"exactOptionalPropertyTypes": true,
|
|
5
|
+
"jsx": "preserve",
|
|
6
|
+
"jsxImportSource": "@quickgui/solid",
|
|
7
|
+
"lib": ["ESNext"],
|
|
8
|
+
"module": "Preserve",
|
|
9
|
+
"moduleResolution": "Bundler",
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"target": "ES2023",
|
|
13
|
+
"types": ["bun"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|