create-orbit-app 0.0.1 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asahi Kawanobe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from "node:fs";
4
+ import { resolve, basename, join } from "node:path";
5
+ import { createInterface } from "node:readline";
6
+ import { execSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
10
+ const TEMPLATE_DIR = resolve(__dirname, "..", "template");
11
+
12
+ const CLAUDE_MD = `# CLAUDE.md
13
+
14
+ ## プロジェクト概要
15
+
16
+ <!-- プロジェクトの説明を書く -->
17
+
18
+ ## コマンド
19
+
20
+ \`\`\`bash
21
+ pnpm install # 依存インストール
22
+ pnpm dev # 開発サーバー起動 (http://localhost:5173)
23
+ pnpm build # プロダクションビルド
24
+ pnpm preview # ビルド結果プレビュー
25
+ \`\`\`
26
+
27
+ ## 技術スタック
28
+
29
+ - **ツールチェーン**: Vite+(Rolldown, Oxlint, Oxfmt)
30
+ - **UI**: React 19
31
+ - **ルーティング**: orbit-router(ディレクトリベース)
32
+ - **データ取得**: orbit-query
33
+ - **フォーム**: orbit-form(React Compiler 互換)
34
+ - **バリデーション**: Zod
35
+ - **CSS**: Tailwind CSS
36
+
37
+ ## ディレクトリ規約
38
+
39
+ \`\`\`
40
+ src/routes/page.tsx → /
41
+ src/routes/layout.tsx → ルートレイアウト
42
+ src/routes/about/page.tsx → /about
43
+ src/routes/users/[id]/page.tsx → /users/:id
44
+ \`\`\`
45
+
46
+ ### ファイル規約
47
+
48
+ - \`page.tsx\` — ページコンポーネント(「目次」として読めること)
49
+ - \`hooks.ts\` — カスタムフック(1フック1関心事)
50
+ - \`server.ts\` — サーバー側データアクセス関数
51
+ - \`schema.ts\` — Zod スキーマ + 型定義
52
+ - \`layout.tsx\` — レイアウト(\`{children}\` で子を囲む)
53
+ - \`loading.tsx\` — ローディング状態
54
+ - \`error.tsx\` — エラー境界
55
+ - \`not-found.tsx\` — 404 ページ
56
+
57
+ ### page.tsx のデータフロー
58
+
59
+ \`\`\`tsx
60
+ const [search, setSearch] = useSearchParams(searchSchema); // State
61
+ const { data: tasks } = useTasks(); // Fetch
62
+ const filtered = useTaskFilter(tasks, search.q); // Transform
63
+ const { mutate: toggle } = useToggleTask(); // Mutate
64
+ return <div>...</div>; // Render
65
+ \`\`\`
66
+
67
+ ## 設計思想
68
+
69
+ - **読みやすさ > 書きやすさ** — 短さのために処理を隠さない
70
+ - **隠すな、揃えろ** — 暗黙の動作より明示的なコード
71
+ - **React Compiler 前提** — useMemo / useCallback / React.memo は書かない
72
+ - **YAGNI** — 必要になるまで作らない
73
+ `;
74
+
75
+ const AGENTS_MD = `# AGENTS.md
76
+
77
+ ## Project Overview
78
+
79
+ <!-- Describe your project here -->
80
+
81
+ ## Commands
82
+
83
+ \`\`\`bash
84
+ pnpm install # Install dependencies
85
+ pnpm dev # Start dev server (http://localhost:5173)
86
+ pnpm build # Production build
87
+ pnpm preview # Preview build
88
+ \`\`\`
89
+
90
+ ## Tech Stack
91
+
92
+ - **Toolchain**: Vite+ (Rolldown, Oxlint, Oxfmt)
93
+ - **UI**: React 19
94
+ - **Routing**: orbit-router (directory-based)
95
+ - **Data Fetching**: orbit-query
96
+ - **Forms**: orbit-form (React Compiler compatible)
97
+ - **Validation**: Zod
98
+ - **CSS**: Tailwind CSS
99
+
100
+ ## Directory Conventions
101
+
102
+ \`\`\`
103
+ src/routes/page.tsx → /
104
+ src/routes/layout.tsx → Root layout
105
+ src/routes/about/page.tsx → /about
106
+ src/routes/users/[id]/page.tsx → /users/:id
107
+ \`\`\`
108
+
109
+ ### File Conventions
110
+
111
+ - \`page.tsx\` — Page component (should read like a table of contents)
112
+ - \`hooks.ts\` — Custom hooks (one concern per hook)
113
+ - \`server.ts\` — Server-side data access functions
114
+ - \`schema.ts\` — Zod schemas + type definitions
115
+ - \`layout.tsx\` — Layout (\`{children}\` wrapper)
116
+ - \`loading.tsx\` — Loading state
117
+ - \`error.tsx\` — Error boundary
118
+ - \`not-found.tsx\` — 404 page
119
+
120
+ ### Data Flow in page.tsx
121
+
122
+ \`\`\`tsx
123
+ const [search, setSearch] = useSearchParams(searchSchema); // State
124
+ const { data: tasks } = useTasks(); // Fetch
125
+ const filtered = useTaskFilter(tasks, search.q); // Transform
126
+ const { mutate: toggle } = useToggleTask(); // Mutate
127
+ return <div>...</div>; // Render
128
+ \`\`\`
129
+
130
+ ## Design Philosophy
131
+
132
+ - **Readability > Writability** — Don't hide logic for brevity
133
+ - **Explicit over implicit** — No magic, align everything
134
+ - **React Compiler first** — No useMemo / useCallback / React.memo
135
+ - **YAGNI** — Don't build it until you need it
136
+ `;
137
+
138
+ // --- Helpers ---
139
+
140
+ function prompt(rl, question) {
141
+ return new Promise((resolve) => rl.question(question, resolve));
142
+ }
143
+
144
+ function select(rl, question, options) {
145
+ return new Promise((resolve) => {
146
+ console.log(question);
147
+ options.forEach((opt, i) => console.log(` ${i + 1}. ${opt.label}`));
148
+ rl.question(`\n Choose [1-${options.length}]: `, (answer) => {
149
+ const idx = parseInt(answer, 10) - 1;
150
+ resolve(options[idx]?.value ?? options[0].value);
151
+ });
152
+ });
153
+ }
154
+
155
+ // --- Main ---
156
+
157
+ async function main() {
158
+ const args = process.argv.slice(2);
159
+ const projectName = args[0];
160
+
161
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
162
+
163
+ console.log();
164
+ console.log(" ◎ create-orbit");
165
+ console.log();
166
+
167
+ const name = projectName || (await prompt(rl, " Project name: "));
168
+ if (!name) {
169
+ console.log(" Project name is required.");
170
+ rl.close();
171
+ process.exit(1);
172
+ }
173
+
174
+ const agent = await select(rl, "\n AI instructions file:", [
175
+ { label: "CLAUDE.md (Claude Code)", value: "claude" },
176
+ { label: "AGENTS.md (Other AI tools)", value: "agents" },
177
+ { label: "None", value: "none" },
178
+ ]);
179
+
180
+ rl.close();
181
+
182
+ const targetDir = resolve(process.cwd(), name);
183
+
184
+ if (existsSync(targetDir)) {
185
+ console.log(`\n Directory "${name}" already exists.`);
186
+ process.exit(1);
187
+ }
188
+
189
+ // Copy template
190
+ cpSync(TEMPLATE_DIR, targetDir, { recursive: true });
191
+
192
+ // Rename _gitignore to .gitignore (npm strips .gitignore from published packages)
193
+ const gitignoreSrc = join(targetDir, "_gitignore");
194
+ const gitignoreDest = join(targetDir, ".gitignore");
195
+ if (existsSync(gitignoreSrc)) {
196
+ const { renameSync } = await import("node:fs");
197
+ renameSync(gitignoreSrc, gitignoreDest);
198
+ }
199
+
200
+ // Update package.json name
201
+ const pkgPath = join(targetDir, "package.json");
202
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
203
+ pkg.name = basename(name);
204
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
205
+
206
+ // Update index.html title
207
+ const htmlPath = join(targetDir, "index.html");
208
+ let html = readFileSync(htmlPath, "utf-8");
209
+ html = html.replace("{{PROJECT_NAME}}", basename(name));
210
+ writeFileSync(htmlPath, html);
211
+
212
+ // Write agent file
213
+ if (agent === "claude") {
214
+ writeFileSync(join(targetDir, "CLAUDE.md"), CLAUDE_MD);
215
+ } else if (agent === "agents") {
216
+ writeFileSync(join(targetDir, "AGENTS.md"), AGENTS_MD);
217
+ }
218
+
219
+ // Install dependencies
220
+ console.log();
221
+ console.log(" Installing dependencies...");
222
+ try {
223
+ execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
224
+ } catch {
225
+ console.log(" ⚠ pnpm install failed. Run it manually after setup.");
226
+ }
227
+
228
+ // Git init
229
+ try {
230
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
231
+ execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
232
+ execSync('git commit -m "init: create-orbit"', {
233
+ cwd: targetDir,
234
+ stdio: "ignore",
235
+ });
236
+ } catch {
237
+ // git not available, skip silently
238
+ }
239
+
240
+ console.log();
241
+ console.log(` ✓ Project created at ./${name}`);
242
+ console.log();
243
+ console.log(" Next steps:");
244
+ console.log(` cd ${name}`);
245
+ console.log(" pnpm dev");
246
+ console.log();
247
+ }
248
+
249
+ main().catch((err) => {
250
+ console.error(err);
251
+ process.exit(1);
252
+ });
package/package.json CHANGED
@@ -1,11 +1,22 @@
1
1
  {
2
2
  "name": "create-orbit-app",
3
- "version": "0.0.1",
4
- "description": "Create a new Orbit app — directory-based, AI-friendly React framework on Vite+",
5
- "author": "ashunar0",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/ashunar0/orbit-router"
10
- }
11
- }
3
+ "version": "0.1.0",
4
+ "description": "Create a new Orbit project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-orbit": "./bin/create-orbit.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "keywords": [
14
+ "orbit",
15
+ "react",
16
+ "vite",
17
+ "router",
18
+ "form",
19
+ "query"
20
+ ],
21
+ "license": "MIT"
22
+ }
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .orbit
4
+ *.local
@@ -0,0 +1,12 @@
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>{{PROJECT_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "my-orbit-app",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vp dev",
8
+ "build": "vp build",
9
+ "preview": "vp preview",
10
+ "check": "vp check",
11
+ "fmt": "vp fmt",
12
+ "lint": "vp lint"
13
+ },
14
+ "dependencies": {
15
+ "orbit-form": "^0.1.6",
16
+ "orbit-query": "^0.1.1",
17
+ "orbit-router": "^0.2.2",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0",
20
+ "zod": "^3.24.0"
21
+ },
22
+ "devDependencies": {
23
+ "@tailwindcss/vite": "^4.0.0",
24
+ "@types/react": "^19.0.0",
25
+ "@types/react-dom": "^19.0.0",
26
+ "@vitejs/plugin-react": "^4.0.0",
27
+ "tailwindcss": "^4.0.0",
28
+ "typescript": "^5.9.0",
29
+ "vite-plus": "^0.1.12"
30
+ }
31
+ }
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,13 @@
1
+ import { routes, NotFound } from "virtual:orbit-router/routes";
2
+ import { Router } from "orbit-router";
3
+ import { createQueryClient, QueryProvider } from "orbit-query";
4
+
5
+ const queryClient = createQueryClient();
6
+
7
+ export function App() {
8
+ return (
9
+ <QueryProvider client={queryClient}>
10
+ <Router routes={routes} NotFound={NotFound} />
11
+ </QueryProvider>
12
+ );
13
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="orbit-router/client" />
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./app.css";
4
+ import { App } from "./app";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,11 @@
1
+ export default function About() {
2
+ return (
3
+ <div>
4
+ <h1 className="text-3xl font-bold mb-2">About</h1>
5
+ <p className="text-gray-500">
6
+ This project was created with{" "}
7
+ <code className="text-sm bg-gray-100 px-1.5 py-0.5 rounded">create-orbit</code>.
8
+ </p>
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,24 @@
1
+ import { Link } from "orbit-router";
2
+
3
+ export default function RootLayout({
4
+ children,
5
+ }: {
6
+ children: React.ReactNode;
7
+ }) {
8
+ return (
9
+ <div className="min-h-screen bg-gray-50 text-gray-900">
10
+ <nav className="flex items-center gap-6 px-6 py-4 border-b bg-white">
11
+ <Link href="/" className="font-semibold text-lg">
12
+
13
+ </Link>
14
+ <Link href="/" className="text-sm hover:text-blue-600">
15
+ Home
16
+ </Link>
17
+ <Link href="/about" className="text-sm hover:text-blue-600">
18
+ About
19
+ </Link>
20
+ </nav>
21
+ <main className="max-w-2xl mx-auto px-6 py-8">{children}</main>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,13 @@
1
+ import { Link } from "orbit-router";
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div className="text-center py-16">
6
+ <h1 className="text-5xl font-bold mb-2">404</h1>
7
+ <p className="text-gray-500 mb-6">Page not found</p>
8
+ <Link href="/" className="text-blue-600 hover:underline">
9
+ Back to Home
10
+ </Link>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,25 @@
1
+ import { Link } from "orbit-router";
2
+
3
+ export default function Home() {
4
+ return (
5
+ <div>
6
+ <h1 className="text-3xl font-bold mb-2">Welcome to Orbit</h1>
7
+ <p className="text-gray-500 mb-6">
8
+ Edit <code className="text-sm bg-gray-100 px-1.5 py-0.5 rounded">src/routes/page.tsx</code> to get started.
9
+ </p>
10
+ <div className="flex gap-4 text-sm">
11
+ <Link href="/about" className="text-blue-600 hover:underline">
12
+ About
13
+ </Link>
14
+ <a
15
+ href="https://github.com/asahi-and/orbit"
16
+ target="_blank"
17
+ rel="noopener noreferrer"
18
+ className="text-blue-600 hover:underline"
19
+ >
20
+ GitHub
21
+ </a>
22
+ </div>
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "erasableSyntaxOnly": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noUncheckedSideEffectImports": true
25
+ },
26
+ "include": ["src", ".orbit"]
27
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import { orbitRouter } from "orbit-router";
5
+
6
+ export default defineConfig({
7
+ plugins: [tailwindcss(), react(), orbitRouter()],
8
+ });