@enderworld/onlyapi 1.5.1 → 1.7.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/dist/cli.js +418 -11
- package/package.json +1 -1
- package/src/cli/commands/init.ts +79 -95
- package/src/cli/index.ts +1 -1
- package/src/cli/template.ts +1191 -0
package/src/cli/commands/init.ts
CHANGED
|
@@ -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.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
10
|
-
* 6.
|
|
11
|
-
* 7.
|
|
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,
|
|
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:
|
|
141
|
-
const
|
|
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
|
-
|
|
147
|
+
const name = projectName === "." ? "my-api" : projectName;
|
|
148
|
+
const files = generateTemplate(name);
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
202
|
+
step("Initialized git repository");
|
|
268
203
|
}
|
|
269
204
|
|
|
270
205
|
// ── Success banner ──
|
|
@@ -276,6 +211,42 @@ 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
|
+
`│ ├── logger.ts ${dim("← colored structured logger")}`,
|
|
224
|
+
`│ ├── router.ts ${dim("← route table + matching")}`,
|
|
225
|
+
`│ ├── server.ts ${dim("← HTTP server + middleware")}`,
|
|
226
|
+
"│ ├── handlers/",
|
|
227
|
+
`│ │ ├── auth.handler.ts ${dim("← register/login/logout")}`,
|
|
228
|
+
"│ │ ├── health.handler.ts",
|
|
229
|
+
`│ │ └── user.handler.ts ${dim("← profile CRUD")}`,
|
|
230
|
+
"│ ├── middleware/",
|
|
231
|
+
`│ │ └── auth.ts ${dim("← JWT guard")}`,
|
|
232
|
+
"│ ├── services/",
|
|
233
|
+
"│ │ ├── auth.service.ts",
|
|
234
|
+
"│ │ └── user.service.ts",
|
|
235
|
+
"│ └── utils/",
|
|
236
|
+
`│ ├── password.ts ${dim("← Argon2id")}`,
|
|
237
|
+
`│ ├── token.ts ${dim("← JWT sign/verify")}`,
|
|
238
|
+
"│ └── response.ts",
|
|
239
|
+
"├── tests/",
|
|
240
|
+
"├── Dockerfile",
|
|
241
|
+
`├── .env ${dim("← auto-generated")}`,
|
|
242
|
+
"└── package.json",
|
|
243
|
+
];
|
|
244
|
+
for (const line of tree) {
|
|
245
|
+
log(` ${line}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
blank();
|
|
249
|
+
|
|
279
250
|
// Next steps
|
|
280
251
|
section("Next steps");
|
|
281
252
|
blank();
|
|
@@ -293,7 +264,20 @@ export const initCommand = async (args: string[], version: string): Promise<void
|
|
|
293
264
|
}
|
|
294
265
|
|
|
295
266
|
blank();
|
|
296
|
-
|
|
267
|
+
|
|
268
|
+
// Endpoints
|
|
269
|
+
section("API endpoints");
|
|
270
|
+
blank();
|
|
271
|
+
log(` ${dim("GET")} /health ${dim("← health check")}`);
|
|
272
|
+
log(` ${dim("POST")} /api/v1/auth/register ${dim("← create account")}`);
|
|
273
|
+
log(` ${dim("POST")} /api/v1/auth/login ${dim("← get JWT token")}`);
|
|
274
|
+
log(` ${dim("POST")} /api/v1/auth/logout ${dim("← revoke token")} ${dim("🔒")}`);
|
|
275
|
+
log(` ${dim("GET")} /api/v1/users/me ${dim("← get profile")} ${dim("🔒")}`);
|
|
276
|
+
log(` ${dim("PATCH")} /api/v1/users/me ${dim("← update profile")} ${dim("🔒")}`);
|
|
277
|
+
log(` ${dim("DELETE")} /api/v1/users/me ${dim("← delete account")} ${dim("🔒")}`);
|
|
278
|
+
blank();
|
|
279
|
+
|
|
280
|
+
log(` ${dim("Docs:")} ${cyan("https://github.com/lysari/onlyapi#readme")}`);
|
|
297
281
|
log(` ${dim("Issues:")} ${cyan("https://github.com/lysari/onlyapi/issues")}`);
|
|
298
282
|
blank();
|
|
299
283
|
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.
|
|
20
|
+
const VERSION = "1.7.0";
|
|
21
21
|
|
|
22
22
|
// ── Arg parsing ─────────────────────────────────────────────────────────
|
|
23
23
|
|