webnek 0.1.1 → 0.1.3
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 +5 -13
- package/bin/cli.mjs +54 -97
- package/package.json +5 -2
- package/template/.cargo/config.toml +2 -0
- package/template/Cargo.toml +2 -1
- package/template/README.md +3 -3
- package/template/build.rs +42 -65
package/README.md
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
# webnek
|
|
2
2
|
|
|
3
3
|
```bash
|
|
4
|
-
|
|
4
|
+
bun create webnek@latest my-app
|
|
5
5
|
cd my-app
|
|
6
|
-
|
|
6
|
+
cargo dev
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Create compiles the app (first time on a machine can take a minute). After that `cargo dev` starts the server.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Same create command: `npm create webnek@latest my-app` or `pnpm create webnek@latest my-app`.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Commands
|
|
16
|
-
|
|
17
|
-
| Command | What it does |
|
|
18
|
-
|---|---|
|
|
19
|
-
| `bunx webnek [dir]` | Scaffold a project |
|
|
20
|
-
| `bunx webnek dev` | Start the app |
|
|
21
|
-
| `bunx webnek add <pkg>` | Add a crate or npm package |
|
|
13
|
+
Generated apps have no `package.json` and no `node_modules`. `cargo build` and `cargo test` work as usual.
|
|
22
14
|
|
|
23
15
|
Requires [Rust](https://rustup.rs/).
|
package/bin/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
const pkgRoot = resolve(here, "..");
|
|
17
17
|
const templateRoot = join(pkgRoot, "template");
|
|
18
|
-
const
|
|
18
|
+
const VERSION = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")).version;
|
|
19
19
|
|
|
20
20
|
const args = process.argv.slice(2);
|
|
21
21
|
const cmd = args[0];
|
|
@@ -40,16 +40,15 @@ const IGNORE_WHEN_EMPTY = new Set([
|
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
function help() {
|
|
43
|
-
const run = invokeCmd();
|
|
44
43
|
console.log(`
|
|
45
|
-
webnek
|
|
44
|
+
webnek ${VERSION}
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
bun create webnek@latest [dir]
|
|
47
|
+
npm create webnek@latest [dir]
|
|
48
|
+
pnpm create webnek@latest [dir]
|
|
49
|
+
|
|
50
|
+
Then:
|
|
51
|
+
cargo dev
|
|
53
52
|
`);
|
|
54
53
|
}
|
|
55
54
|
|
|
@@ -85,11 +84,10 @@ function frameworkDep(appDir) {
|
|
|
85
84
|
const fw = resolve(root, "crates/framework");
|
|
86
85
|
let rel = relative(appDir, fw);
|
|
87
86
|
if (!rel.startsWith(".") && !rel.startsWith("/")) rel = `./${rel}`;
|
|
88
|
-
// Cargo.toml paths work with forward slashes on all platforms.
|
|
89
87
|
rel = rel.replaceAll("\\", "/");
|
|
90
88
|
return `webnek = { path = "${rel}", features = ["ssr"] }`;
|
|
91
89
|
}
|
|
92
|
-
return `webnek = {
|
|
90
|
+
return `webnek = { version = "0.1.0", features = ["ssr"] }`;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
function isEffectivelyEmpty(dir) {
|
|
@@ -137,10 +135,35 @@ function stripNodeManifests(appDir) {
|
|
|
137
135
|
if (existsSync(nm)) rmSync(nm, { recursive: true, force: true });
|
|
138
136
|
}
|
|
139
137
|
|
|
138
|
+
function which(bin) {
|
|
139
|
+
const r = spawnSync("sh", ["-c", `command -v ${bin}`], { encoding: "utf8" });
|
|
140
|
+
return r.status === 0 ? r.stdout.trim() : "";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function cargoBuild(appDir) {
|
|
144
|
+
if (!which("cargo")) {
|
|
145
|
+
console.error(`
|
|
146
|
+
✖ cargo is not installed. Install Rust first:
|
|
147
|
+
|
|
148
|
+
https://rustup.rs/
|
|
149
|
+
`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
console.log(" webnek compiling (first time on this computer can take a minute) …\n");
|
|
153
|
+
const r = spawnSync("cargo", ["build"], {
|
|
154
|
+
cwd: appDir,
|
|
155
|
+
stdio: "inherit",
|
|
156
|
+
env: { ...process.env, CARGO_TERM_COLOR: process.env.CARGO_TERM_COLOR || "always" },
|
|
157
|
+
});
|
|
158
|
+
return r.status === 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
140
161
|
function scaffold(targetArg) {
|
|
141
162
|
const appDir = resolve(process.cwd(), targetArg || ".");
|
|
142
163
|
const pkgName = rustPackageName(
|
|
143
|
-
appDir === process.cwd()
|
|
164
|
+
appDir === process.cwd()
|
|
165
|
+
? process.cwd().split(/[/\\]/).pop() || "app"
|
|
166
|
+
: targetArg.replace(/\/+$/, "").split(/[/\\]/).pop()
|
|
144
167
|
);
|
|
145
168
|
|
|
146
169
|
if (existsSync(join(appDir, "Cargo.toml"))) {
|
|
@@ -156,101 +179,32 @@ function scaffold(targetArg) {
|
|
|
156
179
|
|
|
157
180
|
mkdirSync(appDir, { recursive: true });
|
|
158
181
|
stripNodeManifests(appDir);
|
|
159
|
-
|
|
182
|
+
copyTemplate(templateRoot, appDir, {
|
|
160
183
|
PACKAGE_NAME: pkgName,
|
|
161
184
|
FRAMEWORK_DEP: frameworkDep(appDir),
|
|
162
185
|
PROJECT_NAME: pkgName,
|
|
163
|
-
|
|
164
|
-
};
|
|
165
|
-
copyTemplate(templateRoot, appDir, vars);
|
|
186
|
+
});
|
|
166
187
|
stripNodeManifests(appDir);
|
|
167
188
|
|
|
168
189
|
const rel = relative(process.cwd(), appDir) || ".";
|
|
169
|
-
const
|
|
170
|
-
const lines = [` webnek created ${rel}`, ``, ` Next:`];
|
|
171
|
-
if (rel !== ".") {
|
|
172
|
-
lines.push(` cd ${rel}`);
|
|
173
|
-
}
|
|
174
|
-
lines.push(` ${run} dev`);
|
|
175
|
-
console.log(`\n${lines.join("\n")}\n`);
|
|
176
|
-
}
|
|
190
|
+
const built = cargoBuild(appDir);
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
function invokeCmd() {
|
|
184
|
-
const ua = process.env.npm_config_user_agent || "";
|
|
185
|
-
const argv0 = process.argv[0] || "";
|
|
186
|
-
const argv1 = process.argv[1] || "";
|
|
187
|
-
if (ua.includes("bun") || argv0.includes("bun") || argv1.includes("bun")) {
|
|
188
|
-
return "bunx webnek";
|
|
189
|
-
}
|
|
190
|
-
if (ua.includes("npx") || argv1.includes("npx") || argv1.includes("/npm/")) {
|
|
191
|
-
return "npx webnek";
|
|
192
|
+
const lines = [` webnek ${VERSION} ${built ? "ready" : "created"} ${rel}`, ``, ` Next:`];
|
|
193
|
+
if (rel !== ".") lines.push(` cd ${rel}`);
|
|
194
|
+
lines.push(` cargo dev`);
|
|
195
|
+
if (!built) {
|
|
196
|
+
lines.push(``, ` cargo build failed. Fix that, then cargo dev.`);
|
|
192
197
|
}
|
|
193
|
-
|
|
194
|
-
return "bunx webnek";
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function cargoWebnekOk() {
|
|
198
|
-
const probe = spawnSync("cargo", ["webnek", "--help"], {
|
|
199
|
-
encoding: "utf8",
|
|
200
|
-
stdio: "pipe",
|
|
201
|
-
});
|
|
202
|
-
return probe.status === 0 || Boolean(which("cargo-webnek"));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function installCargoWebnek() {
|
|
206
|
-
const root = findFrameworkRoot();
|
|
207
|
-
if (root && existsSync(join(root, "crates/cli/Cargo.toml"))) {
|
|
208
|
-
console.log(" webnek installing cargo-webnek …");
|
|
209
|
-
const ins = spawnSync(
|
|
210
|
-
"cargo",
|
|
211
|
-
["install", "--path", join(root, "crates/cli"), "--force"],
|
|
212
|
-
{ stdio: "inherit" }
|
|
213
|
-
);
|
|
214
|
-
return ins.status === 0;
|
|
215
|
-
}
|
|
216
|
-
console.log(" webnek installing cargo-webnek (once, from git) …");
|
|
217
|
-
const ins = spawnSync(
|
|
218
|
-
"cargo",
|
|
219
|
-
["install", "--git", GIT, "webnek-cli"],
|
|
220
|
-
{ stdio: "inherit" }
|
|
221
|
-
);
|
|
222
|
-
return ins.status === 0;
|
|
198
|
+
console.log(`\n${lines.join("\n")}\n`);
|
|
223
199
|
}
|
|
224
200
|
|
|
225
|
-
function
|
|
226
|
-
if (cargoWebnekOk()) return;
|
|
227
|
-
|
|
201
|
+
function cargo(args) {
|
|
228
202
|
if (!which("cargo")) {
|
|
229
|
-
console.error(
|
|
230
|
-
✖ cargo is not installed. Install Rust first:
|
|
231
|
-
|
|
232
|
-
https://rustup.rs/
|
|
233
|
-
`);
|
|
203
|
+
console.error(" ✖ cargo is not installed. https://rustup.rs/");
|
|
234
204
|
process.exit(1);
|
|
235
205
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.error(`
|
|
239
|
-
✖ could not install cargo-webnek.
|
|
240
|
-
|
|
241
|
-
cargo install --git ${GIT} webnek-cli
|
|
242
|
-
`);
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function proxyCargoWeb(forward) {
|
|
248
|
-
ensureCargoWeb();
|
|
249
|
-
const result = spawnSync("cargo", ["webnek", ...forward], {
|
|
250
|
-
stdio: "inherit",
|
|
251
|
-
cwd: process.cwd(),
|
|
252
|
-
});
|
|
253
|
-
process.exit(result.status ?? 1);
|
|
206
|
+
const r = spawnSync("cargo", args, { stdio: "inherit", cwd: process.cwd() });
|
|
207
|
+
process.exit(r.status ?? 1);
|
|
254
208
|
}
|
|
255
209
|
|
|
256
210
|
const createInvoked =
|
|
@@ -268,12 +222,15 @@ if (args.length === 0 && createInvoked) {
|
|
|
268
222
|
help();
|
|
269
223
|
} else if (cmd === "new" || cmd === "create" || cmd === "init") {
|
|
270
224
|
scaffold(cmd === "init" ? args[1] || "." : args[1] || ".");
|
|
271
|
-
} else if (
|
|
272
|
-
|
|
225
|
+
} else if (cmd === "dev") {
|
|
226
|
+
cargo(["run", ...args.slice(1)]);
|
|
227
|
+
} else if (cmd === "build") {
|
|
228
|
+
cargo(["build", "--release", ...args.slice(1)]);
|
|
229
|
+
} else if (cmd === "test") {
|
|
230
|
+
cargo(["test", ...args.slice(1)]);
|
|
273
231
|
} else if (cmd.startsWith("-")) {
|
|
274
232
|
help();
|
|
275
233
|
process.exit(1);
|
|
276
234
|
} else {
|
|
277
|
-
// `webnek my-app`
|
|
278
235
|
scaffold(cmd);
|
|
279
236
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webnek",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Webnek app
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Create a Webnek app. bun create webnek@latest my-app && cargo dev",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
7
7
|
"author": "Grenish Rai <mrcoder2033d@gmail.com>",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"framework",
|
|
23
23
|
"create-webnek"
|
|
24
24
|
],
|
|
25
|
+
"exports": {
|
|
26
|
+
"./bin/cli.mjs": "./bin/cli.mjs"
|
|
27
|
+
},
|
|
25
28
|
"engines": {
|
|
26
29
|
"node": ">=18"
|
|
27
30
|
},
|
package/template/Cargo.toml
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name = "{{PACKAGE_NAME}}"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
edition = "2021"
|
|
5
|
+
default-run = "{{PACKAGE_NAME}}"
|
|
5
6
|
|
|
6
7
|
[workspace]
|
|
7
8
|
|
|
@@ -15,4 +16,4 @@ path = "api/index.rs"
|
|
|
15
16
|
|
|
16
17
|
[package.metadata.npm]
|
|
17
18
|
# Frontend NPM packages installed to ~/.cargo/webnek/cache/
|
|
18
|
-
# Add
|
|
19
|
+
# JS packages: bunx <pkg> is not required. Add crates with cargo add.
|
package/template/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
3
|
```bash
|
|
4
|
-
|
|
4
|
+
cargo dev
|
|
5
5
|
```
|
|
6
6
|
|
|
7
|
-
- Edit `src/styles.css` (Tailwind v4).
|
|
7
|
+
- Edit `src/styles.css` (Tailwind v4).
|
|
8
8
|
- `public/` is for files you add (favicon, images).
|
|
9
|
-
-
|
|
9
|
+
- `cargo build`, `cargo test`, and `cargo run` work as usual.
|
|
10
10
|
|
|
11
11
|
## Deploy
|
|
12
12
|
|
package/template/build.rs
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
//! Compile CSS and client JS into OUT_DIR
|
|
2
|
-
//!
|
|
3
|
-
//! Local `cargo webnek dev` still serves from `target/web/` (hot reload).
|
|
4
|
-
//! On Vercel, `npx` compiles Tailwind and esbuild.
|
|
1
|
+
//! Compile CSS and client JS into OUT_DIR on every `cargo build`.
|
|
5
2
|
|
|
6
3
|
use std::env;
|
|
7
4
|
use std::fs;
|
|
@@ -18,88 +15,68 @@ fn main() {
|
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
fn write_css(dest: &Path) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
let out = dest.to_str().unwrap_or("styles.css");
|
|
19
|
+
if run(
|
|
20
|
+
"bunx",
|
|
21
|
+
&[
|
|
22
|
+
"@tailwindcss/cli@4.3.3",
|
|
23
|
+
"-i",
|
|
24
|
+
"src/styles.css",
|
|
25
|
+
"-o",
|
|
26
|
+
out,
|
|
27
|
+
"--minify",
|
|
28
|
+
],
|
|
29
|
+
) || run(
|
|
30
|
+
"npx",
|
|
31
|
+
&[
|
|
32
|
+
"--yes",
|
|
33
|
+
"@tailwindcss/cli@4.3.3",
|
|
34
|
+
"-i",
|
|
35
|
+
"src/styles.css",
|
|
36
|
+
"-o",
|
|
37
|
+
out,
|
|
38
|
+
"--minify",
|
|
39
|
+
],
|
|
40
|
+
) {
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
|
-
let fallback = fs::read_to_string("src/styles.css")
|
|
36
|
-
"/* styles.css
|
|
37
|
-
});
|
|
43
|
+
let fallback = fs::read_to_string("src/styles.css")
|
|
44
|
+
.unwrap_or_else(|_| "/* styles.css: install bun or npx to compile Tailwind */\n".into());
|
|
38
45
|
let _ = fs::write(dest, fallback);
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
fn write_js(dest: &Path) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
&& npx_ok(&[
|
|
47
|
-
"--yes",
|
|
49
|
+
let out = dest.to_str().unwrap_or("client.js");
|
|
50
|
+
if run(
|
|
51
|
+
"bunx",
|
|
52
|
+
&[
|
|
48
53
|
"esbuild",
|
|
49
54
|
"src/client/index.ts",
|
|
50
55
|
"--bundle",
|
|
51
56
|
"--format=esm",
|
|
52
57
|
"--outfile",
|
|
53
|
-
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if on_vercel()
|
|
59
|
-
&& npx_ok(&[
|
|
58
|
+
out,
|
|
59
|
+
],
|
|
60
|
+
) || run(
|
|
61
|
+
"npx",
|
|
62
|
+
&[
|
|
60
63
|
"--yes",
|
|
61
64
|
"esbuild",
|
|
62
65
|
"src/client/index.ts",
|
|
66
|
+
"--bundle",
|
|
63
67
|
"--format=esm",
|
|
64
68
|
"--outfile",
|
|
65
|
-
|
|
66
|
-
]
|
|
67
|
-
{
|
|
69
|
+
out,
|
|
70
|
+
],
|
|
71
|
+
) {
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
70
|
-
let fallback = fs::read_to_string("src/client/index.ts")
|
|
71
|
-
.unwrap_or_else(|_| "// client\n".to_string());
|
|
74
|
+
let fallback = fs::read_to_string("src/client/index.ts").unwrap_or_else(|_| "// client\n".into());
|
|
72
75
|
let _ = fs::write(dest, fallback);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
fn
|
|
76
|
-
|
|
77
|
-
Ok(p) => p,
|
|
78
|
-
Err(_) => return false,
|
|
79
|
-
};
|
|
80
|
-
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()));
|
|
81
|
-
let mut candidates = Vec::new();
|
|
82
|
-
if let Ok(td) = env::var("CARGO_TARGET_DIR") {
|
|
83
|
-
candidates.push(PathBuf::from(td).join("web").join(&pkg).join(relative));
|
|
84
|
-
}
|
|
85
|
-
candidates.push(manifest.join("target/web").join(&pkg).join(relative));
|
|
86
|
-
if let Some(parent) = manifest.parent() {
|
|
87
|
-
candidates.push(parent.join("target/web").join(&pkg).join(relative));
|
|
88
|
-
}
|
|
89
|
-
for src in candidates {
|
|
90
|
-
if src.is_file() && fs::copy(&src, dest).is_ok() {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
false
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
fn on_vercel() -> bool {
|
|
98
|
-
env::var_os("VERCEL").is_some()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
fn npx_ok(args: &[&str]) -> bool {
|
|
102
|
-
Command::new("npx")
|
|
78
|
+
fn run(bin: &str, args: &[&str]) -> bool {
|
|
79
|
+
Command::new(bin)
|
|
103
80
|
.args(args)
|
|
104
81
|
.status()
|
|
105
82
|
.map(|s| s.success())
|