create-hum 0.0.0-stage → 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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
4
+ import { basename, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const TEMPLATE_DIR = fileURLToPath(new URL("../templates/basic", import.meta.url));
7
+ /**
8
+ * Template files whose name is only valid once they land in the new project
9
+ * (a `_package.json` inside this package would confuse tooling).
10
+ */
11
+ const RENAMES = {
12
+ "_gitignore": ".gitignore",
13
+ "_package.json": "package.json",
14
+ "_README.md": "README.md",
15
+ };
16
+ const USAGE = `create-hum <project-directory>
17
+
18
+ Scaffolds a new Hum project into <project-directory>.
19
+
20
+ pnpm dlx create-hum my-app
21
+ npm create hum@latest my-app
22
+ `;
23
+ async function walk(dir) {
24
+ const out = [];
25
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
26
+ const full = join(dir, entry.name);
27
+ if (entry.isDirectory()) {
28
+ out.push(...await walk(full));
29
+ }
30
+ else {
31
+ out.push(full);
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ async function copyTemplate(target, name) {
37
+ for (const file of await walk(TEMPLATE_DIR)) {
38
+ const rel = file
39
+ .slice(TEMPLATE_DIR.length)
40
+ .replace(/\\/g, "/")
41
+ .replace(/^\//, "");
42
+ const parts = rel.split("/");
43
+ const fileName = parts[parts.length - 1];
44
+ parts[parts.length - 1] = RENAMES[fileName] ?? fileName;
45
+ const dest = join(target, ...parts);
46
+ await mkdir(join(dest, ".."), { recursive: true });
47
+ // `replaceAll` is a no-op for the files that do not use the placeholder,
48
+ // so every file can be treated the same way.
49
+ const raw = await readFile(file, "utf8");
50
+ await writeFile(dest, raw.replaceAll("{{name}}", name), "utf8");
51
+ }
52
+ }
53
+ async function isEmptyDir(dir) {
54
+ if (!existsSync(dir))
55
+ return true;
56
+ return (await readdir(dir)).length === 0;
57
+ }
58
+ const arg = process.argv[2];
59
+ if (arg === undefined || arg === "--help" || arg === "-h") {
60
+ console.log(USAGE);
61
+ process.exit(arg === undefined ? 1 : 0);
62
+ }
63
+ const target = resolve(process.cwd(), arg);
64
+ if (!await isEmptyDir(target)) {
65
+ console.error(`create-hum: "${arg}" already exists and is not empty.`);
66
+ process.exit(1);
67
+ }
68
+ const name = basename(target);
69
+ await mkdir(target, { recursive: true });
70
+ await copyTemplate(target, name);
71
+ console.log(`\nCreated "${name}" in ${target}\n`);
72
+ console.log("Next steps:");
73
+ console.log(` cd ${arg}`);
74
+ console.log(" npm install");
75
+ console.log(" npm run dev\n");
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,25 @@
1
1
  {
2
2
  "name": "create-hum",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Scaffolds a new Hum project",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/oaly2000/hum.git"
9
+ },
10
+ "license": "MIT",
11
+ "bin": {
12
+ "create-hum": "./dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "templates"
17
+ ],
18
+ "devDependencies": {
19
+ "@types/node": "^26.6.5"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc --noEmit"
24
+ }
6
25
  }
@@ -0,0 +1,62 @@
1
+ # {{name}}
2
+
3
+ A [Hum](https://github.com/) app: static HTML first, islands for interaction.
4
+
5
+ ```bash
6
+ npm install
7
+ npm run dev # Custom Hono server with Vite SSR + HMR middleware
8
+ npm run build # client + server bundles into _hum/
9
+ npm start # run the built server
10
+ ```
11
+
12
+ The production server bundle includes its npm dependencies, so the runtime
13
+ deployment does not need the project's `node_modules`. The generated `_hum/`
14
+ directory can move or even be renamed — it locates itself next to the server
15
+ bundle — as long as its internal structure is kept and `server/server.js` is the
16
+ entry point. Client assets stay reachable under `/_hum/`, the URL recorded in
17
+ the build manifest. Node.js 20 or newer is still required.
18
+
19
+ ## Hono server
20
+
21
+ `server.ts` exports a Hono app. Configure middleware and API routes with the
22
+ normal Hono API; Hum mounts its file-based routes directly onto that app and
23
+ handles unmatched requests with its `_404` page.
24
+ Hum page errors use `_error`; API errors keep using the app's Hono `onError`.
25
+ Vite owns the development listener, preserving SSR and HMR. In production the
26
+ generated entry starts the exported app. The example includes a `/api/health`
27
+ route.
28
+
29
+ ## Layout
30
+
31
+ ```
32
+ routes/
33
+ _app.tsx document shell (html/head/body)
34
+ _404.tsx not found page
35
+ index.tsx /
36
+ seo.tsx /seo
37
+ blog/[id].tsx /blog/:id
38
+ islands/
39
+ Counter.tsx interactive component
40
+ client/
41
+ main.ts client entry; imports global CSS
42
+ style.css Tailwind CSS entry point
43
+ server.ts Hono app with middleware and API routes
44
+ ```
45
+
46
+ ## Tailwind CSS
47
+
48
+ The basic template includes Tailwind CSS v4. Global styles are imported from
49
+ `client/main.ts`; add or edit utility classes in your routes and islands.
50
+
51
+ ## Head
52
+
53
+ Route files export `head` — either a JSX fragment or a function of the route
54
+ props. `_app.tsx` provides the defaults, pages override them by tag.
55
+
56
+ ```tsx
57
+ export const head = <title>Home</title>;
58
+
59
+ export function head({ params }: RouteProps) {
60
+ return <title>Blog {params.id}</title>;
61
+ }
62
+ ```
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ dist
3
+ _hum
4
+ .hum
5
+ *.log
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "start": "node _hum/server/server.js"
10
+ },
11
+ "dependencies": {
12
+ "@hono/node-server": "^2.1.4",
13
+ "@humjs/core": "^0.0.1",
14
+ "@humjs/vite-plugin": "^0.0.1",
15
+ "@preact/signals": "^2.11.3",
16
+ "hono": "^4.13.13",
17
+ "preact": "^11.0.1"
18
+ },
19
+ "devDependencies": {
20
+ "@tailwindcss/vite": "^4.3.3",
21
+ "@types/node": "^26.6.5",
22
+ "tailwindcss": "^4.3.3",
23
+ "vite": "^8.3.4"
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ import "./style.css";
2
+
3
+ export {};
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,14 @@
1
+ import { useSignal } from "@preact/signals";
2
+
3
+ /** Interactive island: everything around it stays static HTML. */
4
+ export default function Counter({ start = 0 }: { start?: number }) {
5
+ const count = useSignal(start);
6
+
7
+ return (
8
+ <div class="counter">
9
+ <button type="button" onClick={() => count.value--}>-</button>
10
+ <output>{count.value}</output>
11
+ <button type="button" onClick={() => count.value++}>+</button>
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,3 @@
1
+ export default function NotFound() {
2
+ return <h1>404 — not found</h1>;
3
+ }
@@ -0,0 +1,22 @@
1
+ import type { ComponentChildren } from "preact";
2
+ import { define } from "@humjs/core";
3
+
4
+ /** Application wide head tags. Pages override them by tag. */
5
+ export const head = define.head(
6
+ <>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <title>{{ name }}</title>
9
+ </>,
10
+ );
11
+
12
+ /** Document shell. Hum injects the merged head tags into `<head>`. */
13
+ export default function App({ children }: { children?: ComponentChildren }) {
14
+ return (
15
+ <html lang="en">
16
+ <head>
17
+ <meta charSet="utf-8" />
18
+ </head>
19
+ <body>{children}</body>
20
+ </html>
21
+ );
22
+ }
@@ -0,0 +1,19 @@
1
+ import { define, type Handlers, page, type RouteProps } from "@humjs/core";
2
+
3
+ interface BlogPageData {
4
+ post: { id: string; title: string };
5
+ }
6
+
7
+ export const head = define.head(({ params }) => <title>Blog {params.id}
8
+ </title>);
9
+
10
+ export const handlers: Handlers = {
11
+ GET: (c) => {
12
+ const id = c.req.param("id");
13
+ return page({ post: { id, title: `Post ${id}` } });
14
+ },
15
+ };
16
+
17
+ export default define.page<BlogPageData>(({ data }) => (
18
+ <h1>Blog post {data.post.title}</h1>
19
+ ));
@@ -0,0 +1,14 @@
1
+ import { define } from "@humjs/core";
2
+ import Counter from "../islands/Counter.js";
3
+
4
+ export const head = define.head(<title>{{ name }}</title>);
5
+
6
+ export default function Home() {
7
+ return (
8
+ <section class="mx-auto max-w-3xl space-y-6 p-8">
9
+ <h1 class="text-3xl font-bold">{{ name }}</h1>
10
+ {/* Only this island ships JavaScript; the rest stays static HTML. */}
11
+ <Counter start={0} />
12
+ </section>
13
+ );
14
+ }
@@ -0,0 +1,36 @@
1
+ import { define, Partial, type RouteProps } from "@humjs/core";
2
+
3
+ const TOPICS: Record<string, string> = {
4
+ "": "Pick a topic from the list.",
5
+ a: "You are reading topic A.",
6
+ b: "You are reading topic B.",
7
+ c: "You are reading topic C.",
8
+ };
9
+
10
+ export const head = define.head(({ url }) => (
11
+ <title>Partials — {url.searchParams.get("topic") || "home"}</title>
12
+ ));
13
+
14
+ /**
15
+ * `f-client-nav` on the wrapper makes every link/submit inside it a client side
16
+ * navigation: the browser does not do a full reload, only the `<Partial>` regions
17
+ * of the target page are fetched and swapped in. The target page reuses the same
18
+ * partial `name`, so the region updates without touching the rest of the layout.
19
+ */
20
+ export default function PartialsPage(props: RouteProps) {
21
+ const topic = props.url.searchParams.get("topic") ?? "";
22
+ return (
23
+ <div f-client-nav>
24
+ <p>Client side navigation swaps only the partial region below.</p>
25
+ <nav>
26
+ <a href="/partials">Home</a>
27
+ <a href="/partials?topic=a">A</a>
28
+ <a href="/partials?topic=b">B</a>
29
+ <a href="/partials?topic=c">C</a>
30
+ </nav>
31
+ <Partial name="content">
32
+ <p>{TOPICS[topic] ?? "Unknown topic."}</p>
33
+ </Partial>
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,14 @@
1
+ import { define } from "@humjs/core";
2
+
3
+ /** `head` as a plain JSX fragment: no props needed. */
4
+ export const head = define.head(
5
+ <>
6
+ <title>SEO</title>
7
+ <meta name="description" content="head exported as a JSX fragment" />
8
+ <link rel="canonical" href="/seo" />
9
+ </>,
10
+ );
11
+
12
+ export default function Seo() {
13
+ return <h1>SEO</h1>;
14
+ }
@@ -0,0 +1,16 @@
1
+ import { Hono } from "hono";
2
+
3
+ const app = new Hono();
4
+
5
+ configure(app);
6
+
7
+ export default app;
8
+
9
+ function configure(app: Hono) {
10
+ app.use("*", async (c, next) => {
11
+ c.header("x-powered-by", "Hum + Hono");
12
+ await next();
13
+ });
14
+
15
+ app.get("/api/health", (c) => c.json({ status: "ok" }));
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "jsx": "react-jsx",
8
+ "jsxImportSource": "preact",
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "verbatimModuleSyntax": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["**/*.ts", "**/*.tsx"]
16
+ }
@@ -0,0 +1,7 @@
1
+ import tailwindcss from "@tailwindcss/vite";
2
+ import { defineConfig } from "vite";
3
+ import { hum } from "@humjs/vite-plugin";
4
+
5
+ export default defineConfig({
6
+ plugins: [hum(), tailwindcss()],
7
+ });
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.