create-nyoworks 2.3.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 +72 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +227 -0
- package/dist/replace.d.ts +1 -0
- package/dist/replace.js +31 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# create-nyoworks
|
|
2
|
+
|
|
3
|
+
Create a new NYOWORKS project with one command.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-nyoworks my-project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Interactive Setup
|
|
12
|
+
|
|
13
|
+
The CLI will ask you:
|
|
14
|
+
|
|
15
|
+
1. **Project name** - Your project's display name
|
|
16
|
+
2. **Platforms** - Web, Mobile, Desktop (multi-select)
|
|
17
|
+
3. **Features** - Optional features to enable
|
|
18
|
+
|
|
19
|
+
## What Gets Created
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
my-project/
|
|
23
|
+
├── apps/
|
|
24
|
+
│ ├── server/ # Hono + tRPC API
|
|
25
|
+
│ ├── web/ # Next.js 16 (if selected)
|
|
26
|
+
│ ├── mobile/ # Expo SDK 54 (if selected)
|
|
27
|
+
│ └── desktop/ # Tauri 2.0 (if selected)
|
|
28
|
+
├── packages/
|
|
29
|
+
│ ├── api/ # tRPC routers
|
|
30
|
+
│ ├── api-client/ # React hooks
|
|
31
|
+
│ ├── database/ # Drizzle ORM
|
|
32
|
+
│ ├── validators/ # Zod schemas
|
|
33
|
+
│ ├── shared/ # Utilities
|
|
34
|
+
│ ├── ui/ # shadcn/ui
|
|
35
|
+
│ └── assets/ # Images, icons
|
|
36
|
+
├── docs/
|
|
37
|
+
│ └── bible/ # Project documentation
|
|
38
|
+
├── mcp-server/ # AI agent orchestration
|
|
39
|
+
└── nyoworks.config.yaml # Project config
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Automatic Placeholder Replacement
|
|
43
|
+
|
|
44
|
+
These placeholders are replaced during project creation:
|
|
45
|
+
|
|
46
|
+
| Placeholder | Example Value |
|
|
47
|
+
|-------------|---------------|
|
|
48
|
+
| `${PROJECT_NAME}` | My Project |
|
|
49
|
+
| `${PROJECT_CODE}` | MYPR |
|
|
50
|
+
| `${PROJECT_SLUG}` | my-project |
|
|
51
|
+
| `${DATABASE_NAME}` | my_project_dev |
|
|
52
|
+
|
|
53
|
+
## Core vs Optional
|
|
54
|
+
|
|
55
|
+
**Always Included (Core):**
|
|
56
|
+
- Authentication (JWT + Argon2id)
|
|
57
|
+
- Database (PostgreSQL + Drizzle)
|
|
58
|
+
- API (Hono + tRPC)
|
|
59
|
+
|
|
60
|
+
**Optional Features:**
|
|
61
|
+
- Analytics, CRM, Payments, Notifications
|
|
62
|
+
- Appointments, Audit, Export, Realtime
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
- Node.js 20+
|
|
67
|
+
- pnpm 9+
|
|
68
|
+
- Docker (for PostgreSQL)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createProject(projectName?: string): Promise<void>;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import { replacePlaceholders } from "./replace.js";
|
|
7
|
+
const REPO = "naimozcan/nyoworks-framework";
|
|
8
|
+
const BRANCH = "main";
|
|
9
|
+
const AVAILABLE_FEATURES = [
|
|
10
|
+
{ title: "Analytics", value: "analytics", description: "User behavior tracking" },
|
|
11
|
+
{ title: "CRM", value: "crm", description: "Customer relationship management" },
|
|
12
|
+
{ title: "Payments", value: "payments", description: "Stripe integration" },
|
|
13
|
+
{ title: "Notifications", value: "notifications", description: "Email, SMS, Push" },
|
|
14
|
+
{ title: "Appointments", value: "appointments", description: "Booking system" },
|
|
15
|
+
{ title: "Audit", value: "audit", description: "Activity logging" },
|
|
16
|
+
{ title: "Export", value: "export", description: "PDF/CSV export" },
|
|
17
|
+
{ title: "Realtime", value: "realtime", description: "WebSocket support" },
|
|
18
|
+
];
|
|
19
|
+
const AVAILABLE_PLATFORMS = [
|
|
20
|
+
{ title: "Web", value: "web", description: "Next.js 16" },
|
|
21
|
+
{ title: "Mobile", value: "mobile", description: "Expo SDK 54" },
|
|
22
|
+
{ title: "Desktop", value: "desktop", description: "Tauri 2.0" },
|
|
23
|
+
];
|
|
24
|
+
function generateCode(name) {
|
|
25
|
+
return name
|
|
26
|
+
.toUpperCase()
|
|
27
|
+
.replace(/[^A-Z0-9]/g, "")
|
|
28
|
+
.slice(0, 4);
|
|
29
|
+
}
|
|
30
|
+
function generateSlug(name) {
|
|
31
|
+
return name
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
34
|
+
.replace(/^-|-$/g, "");
|
|
35
|
+
}
|
|
36
|
+
function generateDatabaseName(name) {
|
|
37
|
+
return name
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
40
|
+
.replace(/^_|_$/g, "") + "_dev";
|
|
41
|
+
}
|
|
42
|
+
async function downloadFromGitHub(repo, subPath, targetDir, branch = "main") {
|
|
43
|
+
const url = `https://codeload.github.com/${repo}/tar.gz/${branch}`;
|
|
44
|
+
const tempDir = path.join(targetDir, ".download-temp");
|
|
45
|
+
await fs.ensureDir(tempDir);
|
|
46
|
+
try {
|
|
47
|
+
await execa("curl", ["-sL", url, "-o", `${tempDir}/repo.tar.gz`]);
|
|
48
|
+
await execa("tar", ["-xzf", `${tempDir}/repo.tar.gz`, "-C", tempDir]);
|
|
49
|
+
const extractedDir = path.join(tempDir, `nyoworks-framework-${branch}`);
|
|
50
|
+
const sourcePath = subPath ? path.join(extractedDir, subPath) : extractedDir;
|
|
51
|
+
if (await fs.pathExists(sourcePath)) {
|
|
52
|
+
await fs.copy(sourcePath, targetDir, { overwrite: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await fs.remove(tempDir);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function createProject(projectName) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(pc.cyan(pc.bold(" NYOWORKS Framework")));
|
|
62
|
+
console.log(pc.dim(" Create a new project"));
|
|
63
|
+
console.log();
|
|
64
|
+
const response = await prompts([
|
|
65
|
+
{
|
|
66
|
+
type: projectName ? null : "text",
|
|
67
|
+
name: "name",
|
|
68
|
+
message: "Project name:",
|
|
69
|
+
initial: projectName || "my-project",
|
|
70
|
+
validate: (value) => (value.length > 0 ? true : "Project name is required"),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "multiselect",
|
|
74
|
+
name: "platforms",
|
|
75
|
+
message: "Select platforms:",
|
|
76
|
+
choices: AVAILABLE_PLATFORMS,
|
|
77
|
+
min: 1,
|
|
78
|
+
hint: "- Space to select. Return to submit",
|
|
79
|
+
instructions: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "multiselect",
|
|
83
|
+
name: "features",
|
|
84
|
+
message: "Select features:",
|
|
85
|
+
choices: AVAILABLE_FEATURES,
|
|
86
|
+
hint: "- Space to select. Return to submit",
|
|
87
|
+
instructions: false,
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
if (!response.name && !projectName) {
|
|
91
|
+
console.log(pc.red("Aborted."));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const name = response.name || projectName;
|
|
95
|
+
const code = generateCode(name);
|
|
96
|
+
const slug = generateSlug(name);
|
|
97
|
+
const databaseName = generateDatabaseName(name);
|
|
98
|
+
const platforms = response.platforms || ["web"];
|
|
99
|
+
const features = response.features || [];
|
|
100
|
+
const targetDir = path.resolve(process.cwd(), slug);
|
|
101
|
+
if (fs.existsSync(targetDir)) {
|
|
102
|
+
console.log(pc.red(`Directory ${slug} already exists.`));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
await fs.ensureDir(targetDir);
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(pc.cyan("Downloading from GitHub..."));
|
|
108
|
+
const coreItems = [
|
|
109
|
+
{ path: "packages/api", desc: "tRPC routers" },
|
|
110
|
+
{ path: "packages/api-client", desc: "API client" },
|
|
111
|
+
{ path: "packages/database", desc: "Database" },
|
|
112
|
+
{ path: "packages/validators", desc: "Validators" },
|
|
113
|
+
{ path: "packages/shared", desc: "Shared utils" },
|
|
114
|
+
{ path: "packages/ui", desc: "UI components" },
|
|
115
|
+
{ path: "packages/assets", desc: "Assets" },
|
|
116
|
+
{ path: "apps/server", desc: "API server" },
|
|
117
|
+
{ path: "docs", desc: "Documentation" },
|
|
118
|
+
{ path: "mcp-server", desc: "MCP server" },
|
|
119
|
+
{ path: ".claude", desc: "Agent commands" },
|
|
120
|
+
];
|
|
121
|
+
const rootFiles = [
|
|
122
|
+
"package.json",
|
|
123
|
+
"pnpm-workspace.yaml",
|
|
124
|
+
"turbo.json",
|
|
125
|
+
"tsconfig.json",
|
|
126
|
+
".env.example",
|
|
127
|
+
".gitignore",
|
|
128
|
+
"nyoworks.config.yaml",
|
|
129
|
+
];
|
|
130
|
+
for (const item of coreItems) {
|
|
131
|
+
process.stdout.write(pc.dim(` Downloading ${item.desc}...`));
|
|
132
|
+
await downloadFromGitHub(REPO, item.path, path.join(targetDir, item.path), BRANCH);
|
|
133
|
+
console.log(pc.green(" ✓"));
|
|
134
|
+
}
|
|
135
|
+
for (const platform of platforms) {
|
|
136
|
+
process.stdout.write(pc.dim(` Downloading apps/${platform}...`));
|
|
137
|
+
await downloadFromGitHub(REPO, `apps/${platform}`, path.join(targetDir, `apps/${platform}`), BRANCH);
|
|
138
|
+
console.log(pc.green(" ✓"));
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(pc.dim(" Downloading root files..."));
|
|
141
|
+
await downloadFromGitHub(REPO, "", targetDir, BRANCH);
|
|
142
|
+
const allPlatforms = ["web", "mobile", "desktop"];
|
|
143
|
+
for (const platform of allPlatforms) {
|
|
144
|
+
if (!platforms.includes(platform)) {
|
|
145
|
+
const platformDir = path.join(targetDir, "apps", platform);
|
|
146
|
+
if (await fs.pathExists(platformDir)) {
|
|
147
|
+
await fs.remove(platformDir);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const createNyoworksDir = path.join(targetDir, "packages", "create-nyoworks");
|
|
152
|
+
if (await fs.pathExists(createNyoworksDir)) {
|
|
153
|
+
await fs.remove(createNyoworksDir);
|
|
154
|
+
}
|
|
155
|
+
console.log(pc.green(" ✓"));
|
|
156
|
+
const placeholders = {
|
|
157
|
+
"${PROJECT_NAME}": name,
|
|
158
|
+
"${PROJECT_CODE}": code,
|
|
159
|
+
"${PROJECT_SLUG}": slug,
|
|
160
|
+
"${DATABASE_NAME}": databaseName,
|
|
161
|
+
};
|
|
162
|
+
process.stdout.write(pc.dim(" Replacing placeholders..."));
|
|
163
|
+
await replacePlaceholders(targetDir, placeholders);
|
|
164
|
+
console.log(pc.green(" ✓"));
|
|
165
|
+
for (const feature of features) {
|
|
166
|
+
const featureDoc = path.join(targetDir, "docs", "bible", "features", `${feature}.md`);
|
|
167
|
+
const content = `# Feature: ${feature.charAt(0).toUpperCase() + feature.slice(1)}
|
|
168
|
+
|
|
169
|
+
## Overview
|
|
170
|
+
|
|
171
|
+
> Describe what this feature does
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
- [ ] Requirement 1
|
|
176
|
+
- [ ] Requirement 2
|
|
177
|
+
|
|
178
|
+
## API Endpoints
|
|
179
|
+
|
|
180
|
+
| Method | Path | Description |
|
|
181
|
+
|--------|------|-------------|
|
|
182
|
+
| GET | /api/${feature} | List items |
|
|
183
|
+
| POST | /api/${feature} | Create item |
|
|
184
|
+
|
|
185
|
+
## Data Model
|
|
186
|
+
|
|
187
|
+
See \`docs/bible/data/schema.md\`
|
|
188
|
+
|
|
189
|
+
## UI Components
|
|
190
|
+
|
|
191
|
+
- [ ] Component 1
|
|
192
|
+
- [ ] Component 2
|
|
193
|
+
|
|
194
|
+
## Decisions
|
|
195
|
+
|
|
196
|
+
| ID | Decision | Rationale |
|
|
197
|
+
|----|----------|-----------|
|
|
198
|
+
| T-xxx | [Decision] | [Why] |
|
|
199
|
+
`;
|
|
200
|
+
await fs.outputFile(featureDoc, content);
|
|
201
|
+
console.log(pc.dim(` Created docs/bible/features/${feature}.md`));
|
|
202
|
+
}
|
|
203
|
+
const configPath = path.join(targetDir, "nyoworks.config.yaml");
|
|
204
|
+
if (await fs.pathExists(configPath)) {
|
|
205
|
+
let config = await fs.readFile(configPath, "utf8");
|
|
206
|
+
if (features.length > 0) {
|
|
207
|
+
config = config.replace(/enabled: \[\]/, `enabled:\n${features.map((f) => ` - ${f}`).join("\n")}`);
|
|
208
|
+
}
|
|
209
|
+
config = config.replace(/targets:\n - web/, `targets:\n${platforms.map((p) => ` - ${p}`).join("\n")}`);
|
|
210
|
+
await fs.writeFile(configPath, config);
|
|
211
|
+
}
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(pc.green(pc.bold("Project created successfully!")));
|
|
214
|
+
console.log();
|
|
215
|
+
console.log(" Next steps:");
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(pc.cyan(` cd ${slug}`));
|
|
218
|
+
console.log(pc.cyan(" pnpm install"));
|
|
219
|
+
console.log(pc.cyan(" pnpm dev"));
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(pc.dim(" Configuration:"));
|
|
222
|
+
console.log(pc.dim(` Name: ${name}`));
|
|
223
|
+
console.log(pc.dim(` Code: ${code}`));
|
|
224
|
+
console.log(pc.dim(` Platforms: ${platforms.join(", ")}`));
|
|
225
|
+
console.log(pc.dim(` Features: ${features.join(", ") || "none"}`));
|
|
226
|
+
console.log();
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function replacePlaceholders(dir: string, placeholders: Record<string, string>): Promise<void>;
|
package/dist/replace.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
const EXTENSIONS = [".md", ".json", ".yaml", ".yml", ".ts", ".tsx", ".js", ".jsx", ".env", ".example"];
|
|
4
|
+
const IGNORE_DIRS = ["node_modules", ".git", "dist", ".next", ".turbo"];
|
|
5
|
+
export async function replacePlaceholders(dir, placeholders) {
|
|
6
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
const fullPath = path.join(dir, entry.name);
|
|
9
|
+
if (entry.isDirectory()) {
|
|
10
|
+
if (!IGNORE_DIRS.includes(entry.name)) {
|
|
11
|
+
await replacePlaceholders(fullPath, placeholders);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else if (entry.isFile()) {
|
|
15
|
+
const ext = path.extname(entry.name);
|
|
16
|
+
if (EXTENSIONS.includes(ext) || entry.name.startsWith(".env")) {
|
|
17
|
+
let content = await fs.readFile(fullPath, "utf8");
|
|
18
|
+
let modified = false;
|
|
19
|
+
for (const [placeholder, value] of Object.entries(placeholders)) {
|
|
20
|
+
if (content.includes(placeholder)) {
|
|
21
|
+
content = content.split(placeholder).join(value);
|
|
22
|
+
modified = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (modified) {
|
|
26
|
+
await fs.writeFile(fullPath, content);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-nyoworks",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Create a new NYOWORKS project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-nyoworks": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"create",
|
|
19
|
+
"nyoworks",
|
|
20
|
+
"boilerplate",
|
|
21
|
+
"monorepo",
|
|
22
|
+
"typescript",
|
|
23
|
+
"nextjs",
|
|
24
|
+
"hono",
|
|
25
|
+
"trpc"
|
|
26
|
+
],
|
|
27
|
+
"author": "NYOWORKS",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"prompts": "^2.4.2",
|
|
31
|
+
"picocolors": "^1.1.1",
|
|
32
|
+
"fs-extra": "^11.2.0",
|
|
33
|
+
"execa": "^9.5.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/fs-extra": "^11.0.4",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"@types/prompts": "^2.4.9",
|
|
39
|
+
"typescript": "^5.7.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
}
|
|
44
|
+
}
|