@enderworld/onlyapi 1.5.1 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enderworld/onlyapi",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Zero-dependency, enterprise-grade REST API built on Bun — fastest runtime, strictest TypeScript, cleanest architecture.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,19 +1,24 @@
1
1
  /**
2
2
  * `onlyapi init <project-name>` — scaffold a new onlyApi project.
3
3
  *
4
+ * Generates a minimal ~15-file project with:
5
+ * - Health check, Auth (register/login/logout), User profile
6
+ * - SQLite (zero-config), JWT, CORS, rate limiting
7
+ * - Dockerfile, tests, README
8
+ *
4
9
  * Steps:
5
10
  * 1. Validate project name
6
11
  * 2. Create project directory
7
- * 3. Clone from GitHub (or download tarball as fallback)
8
- * 4. Clean up git history (.git removed, fresh git init)
9
- * 5. Install dependencies via `bun install`
10
- * 6. Generate secure JWT_SECRET
11
- * 7. Create .env from .env.example
12
- * 8. Print success banner with next steps
12
+ * 3. Generate template files (no git clone!)
13
+ * 4. Install dependencies via `bun install`
14
+ * 5. Generate secure JWT_SECRET in .env
15
+ * 6. Initialize git repo
16
+ * 7. Print success banner
13
17
  */
14
18
 
15
- import { existsSync, mkdirSync, rmSync } from "node:fs";
16
- import { join, resolve } from "node:path";
19
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { generateTemplate } from "../template.js";
17
22
  import {
18
23
  blank,
19
24
  bold,
@@ -38,8 +43,6 @@ import {
38
43
 
39
44
  // ── Constants ───────────────────────────────────────────────────────────
40
45
 
41
- const REPO_URL = "https://github.com/lysari/onlyapi.git";
42
- const TARBALL_URL = "https://github.com/lysari/onlyapi/archive/refs/heads/main.tar.gz";
43
46
  const VALID_NAME_RE = /^[a-zA-Z0-9_-]+$/;
44
47
 
45
48
  // ── Helpers ─────────────────────────────────────────────────────────────
@@ -137,93 +140,23 @@ export const initCommand = async (args: string[], version: string): Promise<void
137
140
  step(`Created directory ${bold(cyan(projectName))}`);
138
141
  }
139
142
 
140
- // ── Step 2: Clone or download ──
141
- const hasGit = await hasCommand("git");
142
- const spinner = createSpinner("Downloading template...");
143
+ // ── Step 2: Generate template files ──
144
+ const spinner = createSpinner("Generating project files...");
143
145
  spinner.start();
144
146
 
145
- let cloneSuccess = false;
147
+ const name = projectName === "." ? "my-api" : projectName;
148
+ const files = generateTemplate(name);
146
149
 
147
- if (hasGit) {
148
- spinner.update("Cloning from GitHub...");
149
- const { exitCode } = await exec(
150
- [
151
- "git",
152
- "clone",
153
- "--depth=1",
154
- "--single-branch",
155
- REPO_URL,
156
- projectName === "." ? "." : projectName,
157
- ],
158
- projectName === "." ? targetDir : process.cwd(),
159
- );
160
- cloneSuccess = exitCode === 0;
161
- }
162
-
163
- if (!cloneSuccess) {
164
- // Fallback: download tarball
165
- spinner.update("Downloading release archive...");
166
- try {
167
- const response = await fetch(TARBALL_URL);
168
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
169
-
170
- const tarPath = join(targetDir, "__onlyapi.tar.gz");
171
- await Bun.write(tarPath, response);
172
-
173
- // Extract
174
- spinner.update("Extracting...");
175
- await exec(["tar", "xzf", tarPath, "--strip-components=1"], targetDir);
176
- rmSync(tarPath, { force: true });
177
- cloneSuccess = true;
178
- } catch (e) {
179
- spinner.stop();
180
- error(`Failed to download template: ${e instanceof Error ? e.message : String(e)}`);
181
- error("Please check your network connection and try again.");
182
- blank();
183
- info(`You can also clone manually: ${dim(`git clone ${REPO_URL} ${projectName}`)}`);
184
- process.exit(1);
185
- }
150
+ for (const file of files) {
151
+ const filePath = join(targetDir, file.path);
152
+ const fileDir = dirname(filePath);
153
+ if (!existsSync(fileDir)) mkdirSync(fileDir, { recursive: true });
154
+ writeFileSync(filePath, file.content, "utf-8");
186
155
  }
187
156
 
188
- spinner.stop("Template downloaded");
189
-
190
- // ── Step 3: Clean up git history ──
191
- const gitDir = join(targetDir, ".git");
192
- if (existsSync(gitDir)) {
193
- rmSync(gitDir, { recursive: true, force: true });
194
- }
195
-
196
- // Initialize fresh git repo
197
- if (hasGit) {
198
- await exec(["git", "init"], targetDir);
199
- step("Initialized fresh git repository");
200
- }
201
-
202
- // ── Step 4: Update package.json with project name ──
203
- const pkgPath = join(targetDir, "package.json");
204
- if (existsSync(pkgPath)) {
205
- try {
206
- const pkgContent = await Bun.file(pkgPath).text();
207
- const pkg = JSON.parse(pkgContent);
208
-
209
- if (projectName !== ".") {
210
- pkg.name = projectName;
211
- }
212
- pkg.version = "0.1.0";
213
- pkg.description = "";
214
- pkg.author = "";
215
- pkg.repository = undefined;
216
- pkg.bugs = undefined;
217
- pkg.homepage = undefined;
218
-
219
- await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
220
- step(`Updated ${bold(cyan("package.json"))}`);
221
- } catch {
222
- warn("Could not update package.json — you can edit it manually");
223
- }
224
- }
157
+ spinner.stop(`Generated ${bold(cyan(String(files.length)))} files`);
225
158
 
226
- // ── Step 5: Generate .env ──
159
+ // ── Step 3: Generate .env with secure secret ──
227
160
  const envExamplePath = join(targetDir, ".env.example");
228
161
  const envPath = join(targetDir, ".env");
229
162
 
@@ -239,7 +172,7 @@ export const initCommand = async (args: string[], version: string): Promise<void
239
172
  }
240
173
  }
241
174
 
242
- // ── Step 6: Install dependencies ──
175
+ // ── Step 4: Install dependencies ──
243
176
  section("Installing dependencies");
244
177
 
245
178
  const installSpinner = createSpinner("Running bun install...");
@@ -257,14 +190,16 @@ export const initCommand = async (args: string[], version: string): Promise<void
257
190
  installSpinner.stop("Dependencies installed");
258
191
  }
259
192
 
260
- // ── Step 7: Initial git commit ──
193
+ // ── Step 5: Initialize git repo ──
194
+ const hasGit = await hasCommand("git");
261
195
  if (hasGit) {
196
+ await exec(["git", "init"], targetDir);
262
197
  await exec(["git", "add", "-A"], targetDir);
263
198
  await exec(
264
199
  ["git", "commit", "-m", "Initial commit from onlyApi CLI", "--no-verify"],
265
200
  targetDir,
266
201
  );
267
- step("Created initial commit");
202
+ step("Initialized git repository");
268
203
  }
269
204
 
270
205
  // ── Success banner ──
@@ -276,6 +211,40 @@ export const initCommand = async (args: string[], version: string): Promise<void
276
211
  );
277
212
  blank();
278
213
 
214
+ // File tree
215
+ section("Project structure");
216
+ blank();
217
+ const tree = [
218
+ `${bold(cyan(name))}/`,
219
+ "├── src/",
220
+ `│ ├── main.ts ${dim("← entry point")}`,
221
+ `│ ├── config.ts ${dim("← env config")}`,
222
+ `│ ├── database.ts ${dim("← SQLite + migrations")}`,
223
+ `│ ├── server.ts ${dim("← HTTP server + routing")}`,
224
+ "│ ├── handlers/",
225
+ `│ │ ├── auth.handler.ts ${dim("← register/login/logout")}`,
226
+ "│ │ ├── health.handler.ts",
227
+ `│ │ └── user.handler.ts ${dim("← profile CRUD")}`,
228
+ "│ ├── middleware/",
229
+ `│ │ └── auth.ts ${dim("← JWT guard")}`,
230
+ "│ ├── services/",
231
+ "│ │ ├── auth.service.ts",
232
+ "│ │ └── user.service.ts",
233
+ "│ └── utils/",
234
+ `│ ├── password.ts ${dim("← Argon2id")}`,
235
+ `│ ├── token.ts ${dim("← JWT sign/verify")}`,
236
+ "│ └── response.ts",
237
+ "├── tests/",
238
+ "├── Dockerfile",
239
+ `├── .env ${dim("← auto-generated")}`,
240
+ "└── package.json",
241
+ ];
242
+ for (const line of tree) {
243
+ log(` ${line}`);
244
+ }
245
+
246
+ blank();
247
+
279
248
  // Next steps
280
249
  section("Next steps");
281
250
  blank();
@@ -293,7 +262,20 @@ export const initCommand = async (args: string[], version: string): Promise<void
293
262
  }
294
263
 
295
264
  blank();
296
- log(` ${dim("Docs:")} ${cyan("https://github.com/lysari/onlyapi#readme")}`);
265
+
266
+ // Endpoints
267
+ section("API endpoints");
268
+ blank();
269
+ log(` ${dim("GET")} /health ${dim("← health check")}`);
270
+ log(` ${dim("POST")} /api/v1/auth/register ${dim("← create account")}`);
271
+ log(` ${dim("POST")} /api/v1/auth/login ${dim("← get JWT token")}`);
272
+ log(` ${dim("POST")} /api/v1/auth/logout ${dim("← revoke token")} ${dim("🔒")}`);
273
+ log(` ${dim("GET")} /api/v1/users/me ${dim("← get profile")} ${dim("🔒")}`);
274
+ log(` ${dim("PATCH")} /api/v1/users/me ${dim("← update profile")} ${dim("🔒")}`);
275
+ log(` ${dim("DELETE")} /api/v1/users/me ${dim("← delete account")} ${dim("🔒")}`);
276
+ blank();
277
+
278
+ log(` ${dim("Docs:")} ${cyan("https://github.com/lysari/onlyapi#readme")}`);
297
279
  log(` ${dim("Issues:")} ${cyan("https://github.com/lysari/onlyapi/issues")}`);
298
280
  blank();
299
281
  log(` ${dim("Happy hacking!")} ${icons.bolt}`);
package/src/cli/index.ts CHANGED
@@ -17,7 +17,7 @@ import { blank, bold, cyan, dim, error, log, white } from "./ui.js";
17
17
 
18
18
  // ── Version ─────────────────────────────────────────────────────────────
19
19
 
20
- const VERSION = "1.5.1";
20
+ const VERSION = "1.6.0";
21
21
 
22
22
  // ── Arg parsing ─────────────────────────────────────────────────────────
23
23