webnek 0.1.3 → 0.1.5
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/cli.mjs +22 -1
- package/package.json +1 -1
- package/template/.cargo/config.toml +4 -0
- package/template/.vercelignore +3 -0
- package/template/README.md +4 -0
- package/template/build.rs +118 -41
- package/template/gitignore +8 -0
- package/template/src/client/{index.ts → index.js} +5 -5
- package/template/src/routes/page.rs +2 -2
package/bin/cli.mjs
CHANGED
|
@@ -101,7 +101,8 @@ function copyTemplate(from, to, vars) {
|
|
|
101
101
|
for (const name of readdirSync(from)) {
|
|
102
102
|
if (name === ".gitkeep" && existsSync(join(to, name))) continue;
|
|
103
103
|
const src = join(from, name);
|
|
104
|
-
|
|
104
|
+
let destName = name.endsWith(".tmpl") ? name.slice(0, -5) : name;
|
|
105
|
+
if (name === "gitignore") destName = ".gitignore";
|
|
105
106
|
const dest = join(to, destName);
|
|
106
107
|
if (statSync(src).isDirectory()) {
|
|
107
108
|
copyTemplate(src, dest, vars);
|
|
@@ -116,6 +117,25 @@ function copyTemplate(from, to, vars) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// Vercel's Rust builder (@vercel/rust) parses .cargo/config.toml whenever it
|
|
121
|
+
// exists and reads `build.target` without checking that a [build] table is
|
|
122
|
+
// present. An alias-only config kills every deploy with
|
|
123
|
+
// "Cannot read properties of undefined (reading 'target')".
|
|
124
|
+
// Guarantee scaffolded apps never ship that footgun.
|
|
125
|
+
function healCargoConfig(appDir) {
|
|
126
|
+
const p = join(appDir, ".cargo", "config.toml");
|
|
127
|
+
if (!existsSync(p)) return;
|
|
128
|
+
const text = readFileSync(p, "utf8");
|
|
129
|
+
const hasBuild = text
|
|
130
|
+
.split("\n")
|
|
131
|
+
.some((l) => /^\s*(\[build[\].]|build\.)/.test(l));
|
|
132
|
+
if (hasBuild) return;
|
|
133
|
+
writeFileSync(
|
|
134
|
+
p,
|
|
135
|
+
`${text.endsWith("\n") ? text : `${text}\n`}\n# Vercel's Rust builder requires a [build] table to exist when this file does.\n[build]\n`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
119
139
|
function stripNodeManifests(appDir) {
|
|
120
140
|
for (const leftover of [
|
|
121
141
|
"package.json",
|
|
@@ -185,6 +205,7 @@ function scaffold(targetArg) {
|
|
|
185
205
|
PROJECT_NAME: pkgName,
|
|
186
206
|
});
|
|
187
207
|
stripNodeManifests(appDir);
|
|
208
|
+
healCargoConfig(appDir);
|
|
188
209
|
|
|
189
210
|
const rel = relative(process.cwd(), appDir) || ".";
|
|
190
211
|
const built = cargoBuild(appDir);
|
package/package.json
CHANGED
package/template/.vercelignore
CHANGED
package/template/README.md
CHANGED
|
@@ -13,3 +13,7 @@ cargo dev
|
|
|
13
13
|
```bash
|
|
14
14
|
vercel
|
|
15
15
|
```
|
|
16
|
+
|
|
17
|
+
Vercel compiles `api/index.rs` into a Rust function; everything else rewrites to it (`vercel.json`).
|
|
18
|
+
|
|
19
|
+
Note: `.cargo/config.toml` must keep a `[build]` table (even empty) — Vercel's Rust builder crashes on configs without one. `.vercelignore` also keeps `.cargo` out of deploys.
|
package/template/build.rs
CHANGED
|
@@ -1,53 +1,111 @@
|
|
|
1
1
|
//! Compile CSS and client JS into OUT_DIR on every `cargo build`.
|
|
2
|
+
//! Tailwind lives in ~/.cargo/webnek/cache (no project node_modules).
|
|
2
3
|
|
|
3
4
|
use std::env;
|
|
4
5
|
use std::fs;
|
|
5
6
|
use std::path::{Path, PathBuf};
|
|
6
|
-
use std::process::Command;
|
|
7
|
+
use std::process::{Command, Stdio};
|
|
8
|
+
|
|
9
|
+
const TW: &str = "4.3.3";
|
|
7
10
|
|
|
8
11
|
fn main() {
|
|
9
12
|
println!("cargo:rerun-if-changed=src/styles.css");
|
|
13
|
+
println!("cargo:rerun-if-changed=src/client/index.js");
|
|
10
14
|
println!("cargo:rerun-if-changed=src/client/index.ts");
|
|
11
15
|
|
|
16
|
+
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()));
|
|
12
17
|
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
13
|
-
write_css(&out.join("styles.css"));
|
|
14
|
-
write_js(&out.join("client.js"));
|
|
18
|
+
write_css(&manifest, &out.join("styles.css"));
|
|
19
|
+
write_js(&manifest, &out.join("client.js"));
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
fn
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
fn cache_dir() -> PathBuf {
|
|
23
|
+
if let Ok(h) = env::var("CARGO_HOME") {
|
|
24
|
+
PathBuf::from(h).join("webnek").join("cache")
|
|
25
|
+
} else if let Ok(h) = env::var("HOME") {
|
|
26
|
+
PathBuf::from(h).join(".cargo").join("webnek").join("cache")
|
|
27
|
+
} else {
|
|
28
|
+
PathBuf::from(".webnek-cache")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn ensure_tailwind(cache: &Path) -> bool {
|
|
33
|
+
let _ = fs::create_dir_all(cache);
|
|
34
|
+
let pkg = cache.join("package.json");
|
|
35
|
+
if !pkg.exists() {
|
|
36
|
+
let _ = fs::write(&pkg, "{\"name\":\"webnek-cache\",\"private\":true}");
|
|
37
|
+
}
|
|
38
|
+
let cli = cache.join("node_modules/@tailwindcss/cli/dist/index.mjs");
|
|
39
|
+
let tw = cache.join("node_modules/tailwindcss");
|
|
40
|
+
if cli.exists() && tw.exists() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
let spec_tw = format!("tailwindcss@{TW}");
|
|
44
|
+
let spec_cli = format!("@tailwindcss/cli@{TW}");
|
|
45
|
+
run(
|
|
46
|
+
cache,
|
|
47
|
+
"bun",
|
|
48
|
+
&["add", &spec_tw, &spec_cli],
|
|
49
|
+
None,
|
|
29
50
|
) || run(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
) {
|
|
51
|
+
cache,
|
|
52
|
+
"npm",
|
|
53
|
+
&["install", &spec_tw, &spec_cli],
|
|
54
|
+
None,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn link_node_modules(manifest: &Path, cache: &Path) -> bool {
|
|
59
|
+
let dest = manifest.join("node_modules");
|
|
60
|
+
let src = cache.join("node_modules");
|
|
61
|
+
if dest.exists() || !src.exists() {
|
|
62
|
+
return dest.exists();
|
|
63
|
+
}
|
|
64
|
+
#[cfg(unix)]
|
|
65
|
+
{
|
|
66
|
+
std::os::unix::fs::symlink(&src, &dest).is_ok()
|
|
67
|
+
}
|
|
68
|
+
#[cfg(not(unix))]
|
|
69
|
+
{
|
|
70
|
+
std::os::windows::fs::symlink_dir(&src, &dest).is_ok()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn write_css(manifest: &Path, dest: &Path) {
|
|
75
|
+
let cache = cache_dir();
|
|
76
|
+
if !ensure_tailwind(&cache) {
|
|
77
|
+
fail();
|
|
78
|
+
}
|
|
79
|
+
let _ = link_node_modules(manifest, &cache);
|
|
80
|
+
let entry = cache.join("node_modules/@tailwindcss/cli/dist/index.mjs");
|
|
81
|
+
let node_modules = cache.join("node_modules");
|
|
82
|
+
let out = dest.to_string_lossy().into_owned();
|
|
83
|
+
let args = [
|
|
84
|
+
entry.to_str().unwrap_or(""),
|
|
85
|
+
"-i",
|
|
86
|
+
"src/styles.css",
|
|
87
|
+
"-o",
|
|
88
|
+
&out,
|
|
89
|
+
"--minify",
|
|
90
|
+
];
|
|
91
|
+
let ok = run(manifest, "bun", &args, Some(&node_modules))
|
|
92
|
+
|| run(manifest, "node", &args, Some(&node_modules));
|
|
93
|
+
|
|
94
|
+
let css = fs::read_to_string(dest).unwrap_or_default();
|
|
95
|
+
if ok && compiled(&css) {
|
|
41
96
|
return;
|
|
42
97
|
}
|
|
43
|
-
|
|
44
|
-
.unwrap_or_else(|_| "/* styles.css: install bun or npx to compile Tailwind */\n".into());
|
|
45
|
-
let _ = fs::write(dest, fallback);
|
|
98
|
+
fail();
|
|
46
99
|
}
|
|
47
100
|
|
|
48
|
-
fn write_js(dest: &Path) {
|
|
49
|
-
let
|
|
101
|
+
fn write_js(manifest: &Path, dest: &Path) {
|
|
102
|
+
let js = manifest.join("src/client/index.js");
|
|
103
|
+
if js.exists() && fs::copy(&js, dest).is_ok() {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let out = dest.to_string_lossy().into_owned();
|
|
50
107
|
if run(
|
|
108
|
+
manifest,
|
|
51
109
|
"bunx",
|
|
52
110
|
&[
|
|
53
111
|
"esbuild",
|
|
@@ -55,9 +113,11 @@ fn write_js(dest: &Path) {
|
|
|
55
113
|
"--bundle",
|
|
56
114
|
"--format=esm",
|
|
57
115
|
"--outfile",
|
|
58
|
-
out,
|
|
116
|
+
&out,
|
|
59
117
|
],
|
|
118
|
+
None,
|
|
60
119
|
) || run(
|
|
120
|
+
manifest,
|
|
61
121
|
"npx",
|
|
62
122
|
&[
|
|
63
123
|
"--yes",
|
|
@@ -66,19 +126,36 @@ fn write_js(dest: &Path) {
|
|
|
66
126
|
"--bundle",
|
|
67
127
|
"--format=esm",
|
|
68
128
|
"--outfile",
|
|
69
|
-
out,
|
|
129
|
+
&out,
|
|
70
130
|
],
|
|
131
|
+
None,
|
|
71
132
|
) {
|
|
72
133
|
return;
|
|
73
134
|
}
|
|
74
|
-
let
|
|
75
|
-
|
|
135
|
+
let _ = fs::write(dest, "// client\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn compiled(css: &str) -> bool {
|
|
139
|
+
let t = css.trim_start();
|
|
140
|
+
!t.is_empty()
|
|
141
|
+
&& !t.starts_with("@import \"tailwindcss\"")
|
|
142
|
+
&& !t.starts_with("@import 'tailwindcss'")
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn run(dir: &Path, bin: &str, args: &[&str], node_path: Option<&Path>) -> bool {
|
|
146
|
+
let mut cmd = Command::new(bin);
|
|
147
|
+
cmd.args(args)
|
|
148
|
+
.current_dir(dir)
|
|
149
|
+
.stdin(Stdio::null());
|
|
150
|
+
if let Some(p) = node_path {
|
|
151
|
+
cmd.env("NODE_PATH", p);
|
|
152
|
+
}
|
|
153
|
+
cmd.status().map(|s| s.success()).unwrap_or(false)
|
|
76
154
|
}
|
|
77
155
|
|
|
78
|
-
fn
|
|
79
|
-
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.unwrap_or(false)
|
|
156
|
+
fn fail() -> ! {
|
|
157
|
+
panic!(
|
|
158
|
+
"webnek: could not compile src/styles.css.\n\
|
|
159
|
+
Install bun or npm, then run `cargo build` again."
|
|
160
|
+
);
|
|
84
161
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
console.log("⚡ [webnek] Client island initialized");
|
|
2
|
-
|
|
3
1
|
document.addEventListener("click", (e) => {
|
|
4
|
-
const
|
|
2
|
+
const el = e.target;
|
|
3
|
+
if (!(el instanceof Element)) return;
|
|
4
|
+
const target = el.closest("[data-action]");
|
|
5
5
|
if (!target) return;
|
|
6
6
|
|
|
7
7
|
const action = target.getAttribute("data-action");
|
|
@@ -14,6 +14,6 @@ document.addEventListener("click", (e) => {
|
|
|
14
14
|
if (action === "dec") val -= 1;
|
|
15
15
|
if (action === "reset") val = 0;
|
|
16
16
|
|
|
17
|
-
counterEl.textContent = val
|
|
18
|
-
if (doubleEl) doubleEl.textContent = (val * 2)
|
|
17
|
+
counterEl.textContent = String(val);
|
|
18
|
+
if (doubleEl) doubleEl.textContent = String(val * 2);
|
|
19
19
|
});
|
|
@@ -44,14 +44,14 @@ pub fn home_page() -> View {
|
|
|
44
44
|
<div className="flex gap-2 pb-1">
|
|
45
45
|
<button
|
|
46
46
|
data-action="dec"
|
|
47
|
-
className="w-10 h-10 border border-line bg-paper text-ink text-lg leading-none hover:bg-white transition-colors"
|
|
47
|
+
className="w-10 h-10 border border-line bg-paper text-ink text-lg leading-none cursor-pointer hover:bg-white transition-colors"
|
|
48
48
|
onClick={move || set_count.update(|c| *c -= 1)}
|
|
49
49
|
>
|
|
50
50
|
"−"
|
|
51
51
|
</button>
|
|
52
52
|
<button
|
|
53
53
|
data-action="inc"
|
|
54
|
-
className="w-10 h-10 bg-ink text-paper text-lg leading-none hover:bg-patina transition-colors"
|
|
54
|
+
className="w-10 h-10 bg-ink text-paper text-lg leading-none cursor-pointer hover:bg-patina transition-colors"
|
|
55
55
|
onClick={move || set_count.update(|c| *c += 1)}
|
|
56
56
|
>
|
|
57
57
|
"+"
|