lucynui 0.1.2 → 0.1.4
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/cli/create.ts +25 -126
- package/cli/postinstall.ts +18 -56
- package/package.json +3 -2
package/cli/create.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import { closeSync, openSync } from "node:fs";
|
|
2
1
|
import { cp } from "node:fs/promises";
|
|
3
2
|
import { basename, join } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import * as proc from "./proc";
|
|
6
5
|
import * as ui from "./ui";
|
|
7
6
|
|
|
8
|
-
/** Matches the line the framework logs once the server is listening. */
|
|
9
7
|
const SERVER_READY = /dev server running on http:\/\/localhost:(\d+)/;
|
|
10
|
-
|
|
11
|
-
/** Files copied from the bundled template into a new project. */
|
|
12
|
-
const TEMPLATE_FILES = ["index.html", "style.css", "app.js", "app.ts", "lucyn.config.ts", "tsconfig.json", ".gitignore"];
|
|
8
|
+
const TEMPLATE_FILES = ["index.html", "style.css", "app.js", "app.ts", "lucyn.config.ts", "tsconfig.json", ".gitignore", "package.json"];
|
|
13
9
|
|
|
14
10
|
const bun = () => proc.findBun() ?? "bun";
|
|
15
11
|
|
|
16
|
-
/** Absolute path to the bundled template directory inside this package. */
|
|
17
12
|
function templateDir(): string {
|
|
18
13
|
return fileURLToPath(new URL("../template", import.meta.url));
|
|
19
14
|
}
|
|
@@ -28,71 +23,51 @@ export async function dev(): Promise<never> {
|
|
|
28
23
|
export async function build(): Promise<never> {
|
|
29
24
|
proc.requireProject();
|
|
30
25
|
ui.info("Building your app...");
|
|
31
|
-
|
|
32
26
|
const code = await proc.run(process.cwd(), [bun(), proc.frameworkPath("build.ts")]);
|
|
33
27
|
if (code !== 0) process.exit(code);
|
|
34
|
-
|
|
35
28
|
ui.ok("Build complete! Check ./dist");
|
|
36
29
|
process.exit(0);
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
/** `lucyn create-ui <appname>`
|
|
32
|
+
/** `lucyn create-ui <appname>` */
|
|
40
33
|
export async function create(appName: string): Promise<never> {
|
|
41
34
|
if (process.platform === "win32") ui.warn(ui.WINDOWS_NOTICE);
|
|
42
|
-
|
|
43
|
-
const bunPath = await proc.ensureBun();
|
|
35
|
+
await proc.ensureBun();
|
|
44
36
|
await proc.ensureSystemDeps();
|
|
45
37
|
|
|
46
|
-
ui.step(1, "
|
|
38
|
+
ui.step(1, "copying template files");
|
|
47
39
|
await scaffoldInto(join(process.cwd(), appName));
|
|
48
|
-
|
|
49
|
-
ui.done("scaffolding complete — fetching the deps and required builds");
|
|
50
|
-
|
|
51
|
-
ui.step(2, "installing modules including lucynui");
|
|
52
|
-
await proc.runStep(appName, [bunPath, "install"]);
|
|
53
|
-
ui.done("modules installed");
|
|
40
|
+
ui.done(`app created in ./${appName}`);
|
|
54
41
|
|
|
55
|
-
ui.
|
|
56
|
-
|
|
57
|
-
ui.
|
|
42
|
+
ui.line();
|
|
43
|
+
ui.info(` cd ${appName}`);
|
|
44
|
+
ui.info(" bun install");
|
|
45
|
+
ui.info(" bun run dev");
|
|
46
|
+
ui.line();
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
const code = await runDev(appName, (port) => {
|
|
61
|
-
ui.done(`dev is up and running on port ${port}`);
|
|
62
|
-
ui.ready(basename(appName));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
process.exit(code);
|
|
48
|
+
process.exit(0);
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
/** `lucyn` / `lucyn init`
|
|
51
|
+
/** `lucyn` / `lucyn init` */
|
|
69
52
|
export async function init(): Promise<never> {
|
|
70
|
-
|
|
53
|
+
await proc.ensureBun();
|
|
71
54
|
await proc.ensureSystemDeps();
|
|
72
55
|
|
|
73
56
|
if (await isProject(process.cwd())) {
|
|
74
|
-
ui.info("
|
|
75
|
-
|
|
76
|
-
ui.step(1, "scaffolding the template, hang on");
|
|
77
|
-
await scaffoldInto(process.cwd());
|
|
78
|
-
ui.done("scaffolding complete — fetching the deps and required builds");
|
|
79
|
-
|
|
80
|
-
ui.step(2, "installing modules including lucynui");
|
|
81
|
-
await proc.runStep(process.cwd(), [bunPath, "install"]);
|
|
82
|
-
ui.done("modules installed");
|
|
83
|
-
|
|
84
|
-
ui.step(3, "testing build");
|
|
85
|
-
await testBuild(process.cwd());
|
|
86
|
-
ui.done("build was successful");
|
|
57
|
+
ui.info("Already a LucyN UI app. Run: bun run dev");
|
|
58
|
+
process.exit(0);
|
|
87
59
|
}
|
|
88
60
|
|
|
89
|
-
ui.step(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
ui.ready(basename(process.cwd()));
|
|
93
|
-
});
|
|
61
|
+
ui.step(1, "copying template files");
|
|
62
|
+
await scaffoldInto(process.cwd());
|
|
63
|
+
ui.done("done");
|
|
94
64
|
|
|
95
|
-
|
|
65
|
+
ui.line();
|
|
66
|
+
ui.info(" bun install");
|
|
67
|
+
ui.info(" bun run dev");
|
|
68
|
+
ui.line();
|
|
69
|
+
|
|
70
|
+
process.exit(0);
|
|
96
71
|
}
|
|
97
72
|
|
|
98
73
|
/* ------------------------------------------------------------------- helpers */
|
|
@@ -101,20 +76,6 @@ export function isProject(dir: string) {
|
|
|
101
76
|
return Bun.file(join(dir, proc.CONFIG_FILE)).exists();
|
|
102
77
|
}
|
|
103
78
|
|
|
104
|
-
/** Runs a build and throws away the binary — the point is proving it compiles. */
|
|
105
|
-
export async function testBuild(dir: string) {
|
|
106
|
-
const { rm } = await import("node:fs/promises");
|
|
107
|
-
const code = await proc.run(dir, [bun(), proc.frameworkPath("build.ts")]);
|
|
108
|
-
await rm(join(dir, "dist"), { recursive: true, force: true });
|
|
109
|
-
|
|
110
|
-
if (code !== 0) throw new Error("the test build failed");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Copies the bundled template files into `dir`, never overwriting existing
|
|
115
|
-
* files — except `package.json`: `bun add` creates a bare manifest (no scripts)
|
|
116
|
-
* before postinstall runs, so we always replace it with the full template version.
|
|
117
|
-
*/
|
|
118
79
|
export async function scaffoldInto(dir: string) {
|
|
119
80
|
const { mkdir } = await import("node:fs/promises");
|
|
120
81
|
await mkdir(dir, { recursive: true });
|
|
@@ -123,35 +84,11 @@ export async function scaffoldInto(dir: string) {
|
|
|
123
84
|
|
|
124
85
|
for (const file of TEMPLATE_FILES) {
|
|
125
86
|
const target = join(dir, file);
|
|
126
|
-
if (await Bun.file(target).exists()) continue;
|
|
87
|
+
if (file !== "package.json" && await Bun.file(target).exists()) continue;
|
|
127
88
|
await cp(join(source, file), target);
|
|
128
89
|
}
|
|
129
|
-
|
|
130
|
-
// Always write the template package.json — the bare one bun creates before
|
|
131
|
-
// postinstall (no scripts, no name, no type) is useless and must be replaced.
|
|
132
|
-
await cp(join(source, "package.json"), join(dir, "package.json"));
|
|
133
90
|
}
|
|
134
91
|
|
|
135
|
-
/** Points the app at this CLI's own version, so template drift can never bite. */
|
|
136
|
-
async function pinFramework(dir: string) {
|
|
137
|
-
const manifest = Bun.file(join(dir, "package.json"));
|
|
138
|
-
if (!(await manifest.exists())) return;
|
|
139
|
-
|
|
140
|
-
// Running from a checkout rather than an install? Point the app at this copy
|
|
141
|
-
// so the whole flow can be tried out before the package is published.
|
|
142
|
-
const spec = import.meta.dir.includes("node_modules")
|
|
143
|
-
? `^${await proc.packageVersion()}`
|
|
144
|
-
: `file:${proc.packageRoot()}`;
|
|
145
|
-
|
|
146
|
-
const pkg = await manifest.json();
|
|
147
|
-
pkg.dependencies = { ...(pkg.dependencies ?? {}), lucynui: spec };
|
|
148
|
-
await Bun.write(manifest, JSON.stringify(pkg, null, 2) + "\n");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Starts dev in the foreground, forwarding output, and calls `onReady` with the
|
|
153
|
-
* port the server actually bound to.
|
|
154
|
-
*/
|
|
155
92
|
async function runDev(dir: string, onReady: (port: number) => void): Promise<number> {
|
|
156
93
|
const child = Bun.spawn([bun(), proc.frameworkPath("lucyn.ts"), "--dev"], {
|
|
157
94
|
cwd: dir,
|
|
@@ -167,7 +104,6 @@ async function runDev(dir: string, onReady: (port: number) => void): Promise<num
|
|
|
167
104
|
for await (const chunk of child.stdout) {
|
|
168
105
|
process.stdout.write(chunk);
|
|
169
106
|
if (announced) continue;
|
|
170
|
-
|
|
171
107
|
seen += decoder.decode(chunk, { stream: true });
|
|
172
108
|
const port = SERVER_READY.exec(seen)?.[1];
|
|
173
109
|
if (port) {
|
|
@@ -178,40 +114,3 @@ async function runDev(dir: string, onReady: (port: number) => void): Promise<num
|
|
|
178
114
|
|
|
179
115
|
return await child.exited;
|
|
180
116
|
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Starts dev detached so the caller can exit, writing output to `logPath`.
|
|
184
|
-
* Returns the port, or null when the window never came up.
|
|
185
|
-
*/
|
|
186
|
-
export async function devDetached(dir: string, logPath: string): Promise<number | null> {
|
|
187
|
-
const fd = openSync(logPath, "a");
|
|
188
|
-
|
|
189
|
-
const child = Bun.spawn([bun(), proc.frameworkPath("lucyn.ts"), "--dev"], {
|
|
190
|
-
cwd: dir,
|
|
191
|
-
stdin: "ignore",
|
|
192
|
-
stdout: fd,
|
|
193
|
-
stderr: fd,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
for (let attempt = 0; attempt < 80; attempt++) {
|
|
198
|
-
await Bun.sleep(250);
|
|
199
|
-
|
|
200
|
-
const log = await Bun.file(logPath).text();
|
|
201
|
-
const port = SERVER_READY.exec(log)?.[1];
|
|
202
|
-
|
|
203
|
-
if (port) {
|
|
204
|
-
// Give it a moment to prove it survives the window actually opening.
|
|
205
|
-
await Bun.sleep(500);
|
|
206
|
-
return child.exitCode === null ? Number(port) : null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (child.exitCode !== null) return null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return null;
|
|
213
|
-
} finally {
|
|
214
|
-
child.unref();
|
|
215
|
-
closeSync(fd);
|
|
216
|
-
}
|
|
217
|
-
}
|
package/cli/postinstall.ts
CHANGED
|
@@ -1,64 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cp } from "node:fs/promises";
|
|
2
2
|
import { join, sep } from "node:path";
|
|
3
|
-
import
|
|
4
|
-
import * as ui from "./ui";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* When the folder is empty it scaffolds the starter app, proves the build works
|
|
10
|
-
* and leaves a dev server running before the install command returns. Any folder
|
|
11
|
-
* that already looks like a project is left completely alone.
|
|
12
|
-
*/
|
|
13
|
-
export async function postinstall(): Promise<void> {
|
|
14
|
-
// Only when installed as a dependency — not while developing this package.
|
|
15
|
-
if (!import.meta.dir.includes("node_modules")) return;
|
|
16
|
-
if (process.env.LUCYN_SKIP_POSTINSTALL) return;
|
|
5
|
+
if (!import.meta.dir.includes("node_modules")) process.exit(0);
|
|
6
|
+
if (process.env.LUCYN_SKIP_POSTINSTALL) process.exit(0);
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
const projectDir = process.env.INIT_CWD
|
|
9
|
+
?? process.env.npm_config_local_prefix
|
|
10
|
+
?? (() => {
|
|
11
|
+
const i = import.meta.dir.lastIndexOf(`${sep}node_modules${sep}`);
|
|
12
|
+
return i === -1 ? null : import.meta.dir.slice(0, i);
|
|
13
|
+
})();
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
if (existsSync(join(dir, "index.html")) || existsSync(join(dir, "src"))) return;
|
|
15
|
+
if (!projectDir) process.exit(0);
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ui.line();
|
|
17
|
+
const template = fileURLToPath(new URL("../template", import.meta.url));
|
|
18
|
+
const FILES = ["index.html", "style.css", "app.js", "app.ts", "lucyn.config.ts", "tsconfig.json", ".gitignore", "package.json"];
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
ui.step(2, "installing modules including lucynui");
|
|
33
|
-
ui.done("modules installed");
|
|
34
|
-
|
|
35
|
-
ui.step(3, "testing build");
|
|
36
|
-
await create.testBuild(dir);
|
|
37
|
-
ui.done("build was successful");
|
|
38
|
-
|
|
39
|
-
ui.step(4, "starting dev with hmr");
|
|
40
|
-
const logPath = join(dir, ".lucyn-dev.log");
|
|
41
|
-
const port = await create.devDetached(dir, logPath);
|
|
42
|
-
|
|
43
|
-
if (port === null) {
|
|
44
|
-
ui.warn(`The window did not come up. Run lucyn dev yourself — log: ${logPath}`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
ui.done(`dev is up and running on port ${port}`);
|
|
49
|
-
ui.line();
|
|
50
|
-
ui.line(` ${ui.accent("Log:")} ${ui.text(logPath)}`);
|
|
51
|
-
ui.line();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** The folder the user ran `bun add` in — the one holding `node_modules`. */
|
|
55
|
-
function projectDir(): string | null {
|
|
56
|
-
const fromEnv = process.env.INIT_CWD ?? process.env.npm_config_local_prefix;
|
|
57
|
-
if (fromEnv) return fromEnv;
|
|
58
|
-
|
|
59
|
-
const marker = `${sep}node_modules${sep}`;
|
|
60
|
-
const index = import.meta.dir.lastIndexOf(marker);
|
|
61
|
-
return index === -1 ? null : import.meta.dir.slice(0, index);
|
|
20
|
+
for (const file of FILES) {
|
|
21
|
+
const target = join(projectDir, file);
|
|
22
|
+
if (file !== "package.json" && await Bun.file(target).exists()) continue;
|
|
23
|
+
await cp(join(template, file), target);
|
|
62
24
|
}
|
|
63
25
|
|
|
64
|
-
|
|
26
|
+
console.log("\n LucyN UI ready.\n\n bun install\n bun run dev\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lucynui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Desktop apps from plain HTML, CSS and JS. No React, no Rust, no Electron.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"javascript"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"elysia": "^1.4.30"
|
|
39
|
+
"elysia": "^1.4.30",
|
|
40
|
+
"@sinclair/typebox": "^0.34.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/bun": "latest"
|