create-zoijs 0.1.3 → 0.2.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +127 -74
  2. package/LICENSE +21 -21
  3. package/README.md +98 -97
  4. package/bin/create-zoijs.js +197 -169
  5. package/core-cdn.json +36 -0
  6. package/package.json +44 -43
  7. package/templates/app/README.md +68 -48
  8. package/templates/app/_gitignore +1 -1
  9. package/templates/app/_vscode/extensions.json +7 -0
  10. package/templates/app/dev-server.mjs +124 -74
  11. package/templates/app/index.html +24 -22
  12. package/templates/app/jsconfig.json +16 -0
  13. package/templates/app/package.json +12 -12
  14. package/templates/app/src/app.js +98 -98
  15. package/templates/app/src/components/Header.js +16 -16
  16. package/templates/app/src/components/StatCard.js +13 -13
  17. package/templates/app/src/components/TaskItem.js +18 -18
  18. package/templates/app/src/style.css +241 -241
  19. package/templates/basic/README.md +50 -30
  20. package/templates/basic/_gitignore +1 -1
  21. package/templates/basic/_vscode/extensions.json +7 -0
  22. package/templates/basic/dev-server.mjs +124 -74
  23. package/templates/basic/index.html +24 -22
  24. package/templates/basic/jsconfig.json +16 -0
  25. package/templates/basic/package.json +12 -12
  26. package/templates/basic/src/app.js +18 -18
  27. package/templates/basic/src/style.css +33 -33
  28. package/templates/library/_vscode/extensions.json +7 -0
  29. package/templates/library/package.json +2 -2
  30. package/templates/minimal/README.md +29 -8
  31. package/templates/minimal/dev-server.mjs +124 -0
  32. package/templates/minimal/index.html +5 -3
  33. package/templates/minimal/package.json +9 -0
  34. package/templates/typescript/README.md +20 -0
  35. package/templates/typescript/_gitignore +1 -1
  36. package/templates/typescript/_vscode/extensions.json +7 -0
  37. package/templates/typescript/dev-server.mjs +124 -74
  38. package/templates/typescript/index.html +24 -22
  39. package/templates/typescript/package.json +1 -1
  40. package/templates/typescript/src/style.css +33 -33
@@ -1 +1 @@
1
- node_modules
1
+ node_modules
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "bierner.lit-html",
4
+ "dbaeumer.vscode-eslint",
5
+ "esbenp.prettier-vscode"
6
+ ]
7
+ }
@@ -1,74 +1,124 @@
1
- // dev-server.mjs — a tiny static file server for this Zoijs app.
2
- //
3
- // Zero dependencies (Node built-ins only), no build step, no bundler. It serves
4
- // the project files so the import map in index.html can load @zoijs/core from
5
- // node_modules. Tries port 7310, then 7311 / 7312 / 7313 if one is busy.
6
- //
7
- // node dev-server.mjs (this is what `npm run dev` runs)
8
-
9
- import { createServer } from "node:http";
10
- import { readFile } from "node:fs/promises";
11
- import { extname, join, normalize } from "node:path";
12
-
13
- const PORTS = [7310, 7311, 7312, 7313];
14
- const ROOT = process.cwd();
15
-
16
- const TYPES = {
17
- ".html": "text/html; charset=utf-8",
18
- ".js": "text/javascript; charset=utf-8",
19
- ".mjs": "text/javascript; charset=utf-8",
20
- ".css": "text/css; charset=utf-8",
21
- ".json": "application/json; charset=utf-8",
22
- ".svg": "image/svg+xml",
23
- ".ico": "image/x-icon",
24
- ".png": "image/png",
25
- ".woff2": "font/woff2",
26
- };
27
-
28
- const server = createServer(async (req, res) => {
29
- let pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
30
- if (pathname === "/") pathname = "/index.html";
31
-
32
- // Resolve inside ROOT and refuse anything that climbs out of it.
33
- const filePath = join(ROOT, normalize(pathname));
34
- if (!filePath.startsWith(ROOT)) {
35
- res.writeHead(403).end("Forbidden");
36
- return;
37
- }
38
- try {
39
- const body = await readFile(filePath);
40
- res.writeHead(200, { "Content-Type": TYPES[extname(filePath)] || "application/octet-stream" });
41
- res.end(body);
42
- } catch {
43
- res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }).end("Not found");
44
- }
45
- });
46
-
47
- function start(i = 0) {
48
- if (i >= PORTS.length) {
49
- console.error(`\nAll dev ports are busy (${PORTS.join(", ")}). Free one and try again.\n`);
50
- process.exit(1);
51
- }
52
- // Paired once-listeners: each attempt removes its sibling so a failed bind
53
- // can't leave a stale "listening" handler that prints the wrong port later.
54
- const onError = (err) => {
55
- server.removeListener("listening", onListening);
56
- if (err.code === "EADDRINUSE") start(i + 1);
57
- else {
58
- console.error(err);
59
- process.exit(1);
60
- }
61
- };
62
- const onListening = () => {
63
- server.removeListener("error", onError);
64
- const lines = ["", " Zoijs dev server", "", ` - Local: http://localhost:${PORTS[i]}`];
65
- if (i > 0) lines.push(` (ports ${PORTS.slice(0, i).join(", ")} were busy)`);
66
- lines.push("");
67
- console.log(lines.join("\n"));
68
- };
69
- server.once("error", onError);
70
- server.once("listening", onListening);
71
- server.listen(PORTS[i]);
72
- }
73
-
74
- start();
1
+ // dev-server.mjs — a tiny static file server for this Zoijs app. DEVELOPMENT ONLY.
2
+ //
3
+ // Zero dependencies (Node built-ins only), no build step, no bundler. It serves
4
+ // the project files — index.html, your modules, and whatever its import map points
5
+ // at inside this folder (node_modules/, if you installed). Tries port 7310, then
6
+ // 7311 / 7312 / 7313 if one is busy. The same file ships in every Zoijs template.
7
+ //
8
+ // node dev-server.mjs (this is what `npm run dev` runs)
9
+ //
10
+ // Safe defaults for a dev server:
11
+ // - listens on 127.0.0.1 only — other devices on your network can't reach it.
12
+ // To test from a phone on your LAN, opt in explicitly: ZOIJS_HOST=0.0.0.0 npm run dev
13
+ // (anyone on that network can then read your project files while it runs).
14
+ // - never serves dotfiles or dot-directories (.env, .git/, .npmrc, …);
15
+ // - never serves anything outside this folder (no `..`, no symlinks pointing out —
16
+ // except package-manager links inside node_modules/);
17
+ // - answers malformed URLs with 400 instead of crashing.
18
+ // It is not a production server: deploy the static files to a real host.
19
+
20
+ import { createServer } from "node:http";
21
+ import { readFile, realpath } from "node:fs/promises";
22
+ import { extname, resolve, relative, isAbsolute } from "node:path";
23
+
24
+ const PORTS = [7310, 7311, 7312, 7313];
25
+ const HOST = process.env.ZOIJS_HOST || "127.0.0.1";
26
+ const ROOT = resolve(process.cwd());
27
+
28
+ const TYPES = {
29
+ ".html": "text/html; charset=utf-8",
30
+ ".js": "text/javascript; charset=utf-8",
31
+ ".mjs": "text/javascript; charset=utf-8",
32
+ ".css": "text/css; charset=utf-8",
33
+ ".json": "application/json; charset=utf-8",
34
+ ".svg": "image/svg+xml",
35
+ ".ico": "image/x-icon",
36
+ ".png": "image/png",
37
+ ".woff2": "font/woff2",
38
+ };
39
+
40
+ const HEADERS = { "X-Content-Type-Options": "nosniff", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" };
41
+
42
+ // Is `path` ROOT itself or inside it? (relative() handles separators, case and
43
+ // sibling folders like `app-secrets` correctly — unlike a string-prefix check.)
44
+ const inside = (base, path) => {
45
+ const rel = relative(base, path);
46
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
47
+ };
48
+
49
+ function send(res, status, body, type = "text/plain; charset=utf-8") {
50
+ res.writeHead(status, { ...HEADERS, "Content-Type": type });
51
+ res.end(body);
52
+ }
53
+
54
+ const server = createServer(async (req, res) => {
55
+ try {
56
+ let pathname;
57
+ try {
58
+ // Only the path (never the query string) maps to a file.
59
+ pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
60
+ } catch {
61
+ return send(res, 400, "Bad request");
62
+ }
63
+ if (pathname.includes("\0")) return send(res, 400, "Bad request");
64
+ if (pathname === "/") pathname = "/index.html";
65
+
66
+ const segments = pathname.split(/[\\/]+/).filter(Boolean);
67
+ // Hidden files and folders (.env, .git/, .npmrc, node_modules/.bin, …) are never
68
+ // served. 404, not 403, so it doesn't reveal whether they exist.
69
+ if (segments.some((s) => s.startsWith("."))) return send(res, 404, "Not found");
70
+
71
+ const filePath = resolve(ROOT, ...segments);
72
+ if (!inside(ROOT, filePath)) return send(res, 404, "Not found");
73
+
74
+ // Don't follow symlinks out of the project. Package managers (npm link, pnpm)
75
+ // legitimately link packages into node_modules/, so links there are allowed.
76
+ let real;
77
+ try {
78
+ real = await realpath(filePath);
79
+ } catch {
80
+ return send(res, 404, "Not found");
81
+ }
82
+ if (!inside(ROOT, real) && segments[0] !== "node_modules") return send(res, 404, "Not found");
83
+
84
+ const body = await readFile(real);
85
+ send(res, 200, body, TYPES[extname(filePath)] || "application/octet-stream");
86
+ } catch {
87
+ // Directories, permission errors, files deleted mid-request: a generic answer
88
+ // that never echoes a filesystem path.
89
+ if (!res.headersSent) send(res, 404, "Not found");
90
+ else res.destroy();
91
+ }
92
+ });
93
+
94
+ function start(i = 0) {
95
+ if (i >= PORTS.length) {
96
+ console.error(`\nAll dev ports are busy (${PORTS.join(", ")}). Free one and try again.\n`);
97
+ process.exit(1);
98
+ }
99
+ // Paired once-listeners: each attempt removes its sibling so a failed bind
100
+ // can't leave a stale "listening" handler that prints the wrong port later.
101
+ const onError = (err) => {
102
+ server.removeListener("listening", onListening);
103
+ if (err.code === "EADDRINUSE") start(i + 1);
104
+ else {
105
+ console.error(err);
106
+ process.exit(1);
107
+ }
108
+ };
109
+ const onListening = () => {
110
+ server.removeListener("error", onError);
111
+ const lines = ["", " Zoijs dev server", "", ` - Local: http://localhost:${PORTS[i]}`];
112
+ if (i > 0) lines.push(` (ports ${PORTS.slice(0, i).join(", ")} were busy)`);
113
+ if (HOST !== "127.0.0.1" && HOST !== "localhost" && HOST !== "::1") {
114
+ lines.push(` ⚠ Listening on ${HOST} (ZOIJS_HOST): other devices on your network can read`, ` this project's files while the server runs. Development only.`);
115
+ }
116
+ lines.push("");
117
+ console.log(lines.join("\n"));
118
+ };
119
+ server.once("error", onError);
120
+ server.once("listening", onListening);
121
+ server.listen(PORTS[i], HOST);
122
+ }
123
+
124
+ start();
@@ -1,22 +1,24 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>{{APP_TITLE}}</title>
7
- <link rel="stylesheet" href="./src/style.css" />
8
- <!-- No build step: an import map points the @zoijs/core name at the package
9
- installed in node_modules. `npm install` puts it there. -->
10
- <script type="importmap">
11
- {
12
- "imports": {
13
- "@zoijs/core": "./node_modules/@zoijs/core/src/index.js"
14
- }
15
- }
16
- </script>
17
- </head>
18
- <body>
19
- <div id="app"></div>
20
- <script type="module" src="./src/app.js"></script>
21
- </body>
22
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{APP_TITLE}}</title>
7
+ <link rel="stylesheet" href="./src/style.css" />
8
+ <!-- No build step: an import map points the @zoijs/core name at the package
9
+ installed in node_modules. `npm install` puts it there. -->
10
+ <!-- Development entry (warnings + devtools). When you deploy, point "@zoijs/core"
11
+ at ./node_modules/@zoijs/core/src/prod.js — the production entry. -->
12
+ <script type="importmap">
13
+ {
14
+ "imports": {
15
+ "@zoijs/core": "./node_modules/@zoijs/core/src/index.js"
16
+ }
17
+ }
18
+ </script>
19
+ </head>
20
+ <body>
21
+ <div id="app"></div>
22
+ <script type="module" src="./src/app.js"></script>
23
+ </body>
24
+ </html>
@@ -8,7 +8,7 @@
8
8
  "typecheck": "tsc --noEmit"
9
9
  },
10
10
  "dependencies": {
11
- "@zoijs/core": "^1.2.0"
11
+ "@zoijs/core": "^{{ZOIJS_CORE_VERSION}}"
12
12
  },
13
13
  "devDependencies": {
14
14
  "typescript": "^5.9.3"
@@ -1,33 +1,33 @@
1
- :root {
2
- color-scheme: light dark;
3
- font-family: system-ui, sans-serif;
4
- }
5
-
6
- body {
7
- margin: 0;
8
- display: grid;
9
- place-items: center;
10
- min-height: 100vh;
11
- }
12
-
13
- main {
14
- text-align: center;
15
- padding: 2rem;
16
- }
17
-
18
- h1 {
19
- margin: 0 0 0.5rem;
20
- }
21
-
22
- button {
23
- font: inherit;
24
- padding: 0.6rem 1.2rem;
25
- border: 1px solid currentColor;
26
- border-radius: 8px;
27
- background: transparent;
28
- cursor: pointer;
29
- }
30
-
31
- button:hover {
32
- background: rgba(127, 127, 127, 0.12);
33
- }
1
+ :root {
2
+ color-scheme: light dark;
3
+ font-family: system-ui, sans-serif;
4
+ }
5
+
6
+ body {
7
+ margin: 0;
8
+ display: grid;
9
+ place-items: center;
10
+ min-height: 100vh;
11
+ }
12
+
13
+ main {
14
+ text-align: center;
15
+ padding: 2rem;
16
+ }
17
+
18
+ h1 {
19
+ margin: 0 0 0.5rem;
20
+ }
21
+
22
+ button {
23
+ font: inherit;
24
+ padding: 0.6rem 1.2rem;
25
+ border: 1px solid currentColor;
26
+ border-radius: 8px;
27
+ background: transparent;
28
+ cursor: pointer;
29
+ }
30
+
31
+ button:hover {
32
+ background: rgba(127, 127, 127, 0.12);
33
+ }