create-nanoka-app 1.0.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.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # create-nanoka-app
2
+
3
+ Create a new Nanoka (Hono + Drizzle + D1) project in seconds.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ pnpm create nanoka-app my-app
9
+ # or
10
+ npx create-nanoka-app my-app
11
+ ```
12
+
13
+ ## Getting started
14
+
15
+ After scaffolding:
16
+
17
+ ```bash
18
+ cd my-app
19
+ pnpm install
20
+ pnpm exec nanoka generate
21
+ pnpm exec drizzle-kit generate
22
+ pnpm dev
23
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { cp, mkdir, readdir, readFile, writeFile } from "fs/promises";
5
+ import { createRequire } from "module";
6
+ import { basename, dirname, extname, join, resolve } from "path";
7
+ import { fileURLToPath } from "url";
8
+ function parseArgs(argv) {
9
+ const args = argv.slice(2);
10
+ if (args.includes("--help") || args.includes("-h")) {
11
+ return { kind: "help" };
12
+ }
13
+ if (args.includes("--version") || args.includes("-v")) {
14
+ return { kind: "version" };
15
+ }
16
+ let targetDir = "";
17
+ let template = "default";
18
+ let force = false;
19
+ for (let i = 0; i < args.length; i++) {
20
+ const arg = args[i];
21
+ if (arg === "--template") {
22
+ const next = args[i + 1];
23
+ if (next !== "default") {
24
+ throw new Error(`Unknown template: "${next ?? ""}". Available templates: default`);
25
+ }
26
+ template = next;
27
+ i++;
28
+ } else if (arg === "--force") {
29
+ force = true;
30
+ } else if (!arg.startsWith("-")) {
31
+ targetDir = arg;
32
+ }
33
+ }
34
+ if (!targetDir) {
35
+ throw new Error("Please specify a project directory: create-nanoka-app <dir>");
36
+ }
37
+ return { targetDir, template, force };
38
+ }
39
+ async function copyTemplate(srcDir, destDir, vars) {
40
+ await mkdir(destDir, { recursive: true });
41
+ const entries = await readdir(srcDir, { withFileTypes: true });
42
+ for (const entry of entries) {
43
+ const srcPath = join(srcDir, entry.name);
44
+ const isTmpl = extname(entry.name) === ".tmpl";
45
+ const destName = isTmpl ? basename(entry.name, ".tmpl") : entry.name;
46
+ const destPath = join(destDir, destName);
47
+ if (entry.isDirectory()) {
48
+ await copyTemplate(srcPath, destPath, vars);
49
+ } else if (isTmpl) {
50
+ let content = await readFile(srcPath, "utf-8");
51
+ for (const [key, value] of Object.entries(vars)) {
52
+ content = content.replaceAll(`{{${key}}}`, value);
53
+ }
54
+ await writeFile(destPath, content, "utf-8");
55
+ } else {
56
+ await cp(srcPath, destPath);
57
+ }
58
+ }
59
+ }
60
+ async function scaffold(opts) {
61
+ const targetDir = resolve(process.cwd(), opts.targetDir);
62
+ const packageName = basename(targetDir);
63
+ const packageNameRegex = /^[a-z0-9][a-z0-9\-_.]*$/;
64
+ if (!packageNameRegex.test(packageName)) {
65
+ throw new Error(
66
+ `Invalid package name: '${packageName}'. Must match /^[a-z0-9][a-z0-9-_.]*$/ (lowercase letters, digits, hyphens, dots, underscores).`
67
+ );
68
+ }
69
+ try {
70
+ const entries = await readdir(targetDir);
71
+ if (entries.length > 0 && !opts.force) {
72
+ throw new Error(
73
+ `Directory "${opts.targetDir}" already exists and is not empty. Use --force to overwrite.`
74
+ );
75
+ }
76
+ } catch (err) {
77
+ if (err.code !== "ENOENT") {
78
+ throw err;
79
+ }
80
+ }
81
+ const selfDir = dirname(fileURLToPath(import.meta.url));
82
+ const templateDir = join(selfDir, "../templates", opts.template);
83
+ const require2 = createRequire(import.meta.url);
84
+ const { version } = require2("../package.json");
85
+ const vars = {
86
+ packageName,
87
+ nanokaVersion: version
88
+ };
89
+ await copyTemplate(templateDir, targetDir, vars);
90
+ console.log(`
91
+ Created "${packageName}" at ${targetDir}`);
92
+ console.log("\nNext steps:");
93
+ console.log(` cd ${opts.targetDir}`);
94
+ console.log(" pnpm install");
95
+ console.log(" pnpm exec nanoka generate");
96
+ console.log(" pnpm exec drizzle-kit generate");
97
+ console.log(" pnpm dev");
98
+ }
99
+ async function main() {
100
+ const result = parseArgs(process.argv);
101
+ if ("kind" in result) {
102
+ if (result.kind === "help") {
103
+ console.log("Usage: create-nanoka-app <dir> [--template default] [--force]");
104
+ console.log("\nOptions:");
105
+ console.log(" --template <name> Template to use (default: default)");
106
+ console.log(" --force Overwrite existing directory");
107
+ console.log(" --help, -h Show this help message");
108
+ console.log(" --version, -v Show version number");
109
+ return;
110
+ }
111
+ if (result.kind === "version") {
112
+ const require2 = createRequire(import.meta.url);
113
+ const { version } = require2("../package.json");
114
+ console.log(version);
115
+ return;
116
+ }
117
+ }
118
+ await scaffold(result);
119
+ }
120
+ if (process.argv[1] != null && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
121
+ main().catch((err) => {
122
+ console.error(err.message ?? err);
123
+ process.exit(1);
124
+ });
125
+ }
126
+ export {
127
+ copyTemplate,
128
+ parseArgs,
129
+ scaffold
130
+ };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "create-nanoka-app",
3
+ "version": "1.0.0",
4
+ "description": "Create a new Nanoka (Hono + D1) project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-nanoka-app": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup && node ./scripts/prepend-shebang.mjs",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "prepublishOnly": "pnpm build && pnpm typecheck && pnpm test"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^25.6.0",
28
+ "tsup": "^8.3.0",
29
+ "typescript": "^5.6.0",
30
+ "vitest": "^2.1.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/nanokajs/nanoka.git"
35
+ },
36
+ "license": "MIT"
37
+ }
@@ -0,0 +1,12 @@
1
+ # My Nanoka App
2
+
3
+ A Hono + Drizzle + D1 project scaffolded with `create-nanoka-app`.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm exec nanoka generate
10
+ pnpm exec drizzle-kit generate
11
+ pnpm dev
12
+ ```
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'drizzle-kit'
2
+
3
+ export default defineConfig({
4
+ schema: './drizzle/schema.ts',
5
+ out: './drizzle/migrations',
6
+ dialect: 'sqlite',
7
+ driver: 'd1-http',
8
+ })
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from '@nanokajs/core/config'
2
+ import { userFields, userTableName } from './src/models/user'
3
+
4
+ export default defineConfig({
5
+ models: [
6
+ {
7
+ name: userTableName,
8
+ fields: userFields,
9
+ },
10
+ ],
11
+ output: './drizzle/schema.ts',
12
+ })
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "wrangler dev",
7
+ "db:generate": "drizzle-kit generate",
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "@nanokajs/core": "{{nanokaVersion}}",
12
+ "hono": "^4.0.0",
13
+ "drizzle-orm": "^0.45.2",
14
+ "zod": "^3.23.0 || ^4.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@cloudflare/workers-types": "^4.20240925.0",
18
+ "drizzle-kit": "^0.28.0",
19
+ "typescript": "^5.6.0",
20
+ "wrangler": "^3.80.0"
21
+ }
22
+ }
@@ -0,0 +1,38 @@
1
+ import { d1Adapter, nanoka } from '@nanokajs/core'
2
+ import { z } from 'zod'
3
+ import { userFields, userTableName } from './models/user'
4
+
5
+ export interface Env {
6
+ DB: D1Database
7
+ }
8
+
9
+ export default {
10
+ async fetch(req: Request, env: Env, ctx: ExecutionContext) {
11
+ const app = nanoka<{ Bindings: Env }>(d1Adapter(env.DB))
12
+ const User = app.model(userTableName, userFields)
13
+
14
+ app.post(
15
+ '/users',
16
+ User.validator('json', { omit: ['id', 'passwordHash', 'createdAt'] }),
17
+ async (c) => {
18
+ const body = c.req.valid('json')
19
+ const created = await User.create({
20
+ ...body,
21
+ id: crypto.randomUUID(),
22
+ passwordHash: 'hashed_value_here',
23
+ createdAt: new Date(),
24
+ })
25
+ const user = User.schema({ omit: ['passwordHash'] }).parse(created)
26
+ return c.json(user, 201)
27
+ },
28
+ )
29
+
30
+ app.get('/users', async (c) => {
31
+ const users = await User.findMany({ limit: 20 })
32
+ const result = z.array(User.schema({ omit: ['passwordHash'] })).parse(users)
33
+ return c.json(result)
34
+ })
35
+
36
+ return app.fetch(req, env, ctx)
37
+ },
38
+ }
@@ -0,0 +1,12 @@
1
+ import { t } from '@nanokajs/core'
2
+
3
+ export const userTableName = 'users'
4
+
5
+ export const userFields = {
6
+ id: t.uuid().primary(),
7
+ name: t.string().min(1).max(100),
8
+ email: t.string().email().unique(),
9
+ passwordHash: t.string().writeOnly(),
10
+ serverToken: t.string().serverOnly().optional(),
11
+ createdAt: t.timestamp().default(() => new Date()),
12
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "verbatimModuleSyntax": true,
12
+ "isolatedModules": true,
13
+ "resolveJsonModule": true,
14
+ "types": ["@cloudflare/workers-types"]
15
+ },
16
+ "include": ["src/**/*", "*.config.ts", "nanoka.config.ts"]
17
+ }
@@ -0,0 +1,13 @@
1
+ name = "{{packageName}}"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2025-04-01"
4
+ compatibility_flags = ["nodejs_compat"]
5
+
6
+ [[d1_databases]]
7
+ binding = "DB"
8
+ database_name = "{{packageName}}"
9
+ database_id = "00000000-0000-0000-0000-000000000000"
10
+ migrations_dir = "drizzle/migrations"
11
+
12
+ [vars]
13
+ ENVIRONMENT = "development"