create-runlot 0.1.0-rc.11
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/index.js +414 -0
- package/package.json +34 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
var TEMPLATE_NAMES = ["next", "hono", "static"];
|
|
8
|
+
function isTemplateName(v) {
|
|
9
|
+
return TEMPLATE_NAMES.includes(v);
|
|
10
|
+
}
|
|
11
|
+
var TEMPLATE_LIST = [
|
|
12
|
+
{ name: "next", desc: "Next.js App Router \u2014 \uC11C\uBC84 \uCEF4\uD3EC\uB10C\uD2B8\uC5D0\uC11C env.db\xB7env.auth \uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4" },
|
|
13
|
+
{ name: "hono", desc: "Hono \uC6CC\uCEE4 \u2014 REST API \uC608\uC81C (/posts, /upload)" },
|
|
14
|
+
{ name: "static", desc: "\uC815\uC801 HTML \uD558\uB098 \u2014 \uC6CC\uCEE4 \uC5C6\uC774 \uC790\uC0B0\uB9CC \uBC30\uD3EC\uD569\uB2C8\uB2E4" }
|
|
15
|
+
];
|
|
16
|
+
var USAGE = [
|
|
17
|
+
"create-runlot <dir> [--org <slug>] [--template next|hono|static]",
|
|
18
|
+
"",
|
|
19
|
+
" Next.js\xB7Hono\xB7\uC815\uC801 \uC0AC\uC774\uD2B8 \uC911 \uD558\uB098\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4ED\uB2C8\uB2E4.",
|
|
20
|
+
" --template \uC774 \uC5C6\uACE0 \uD130\uBBF8\uB110\uC774\uBA74 \uD15C\uD50C\uB9BF \uC120\uD0DD\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4.",
|
|
21
|
+
" \uB9CC\uB4E0 \uB4A4: cd <dir> && runlot add pg auth storage && runlot deploy"
|
|
22
|
+
].join("\n");
|
|
23
|
+
var NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
24
|
+
function pkgJson(obj) {
|
|
25
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
26
|
+
}
|
|
27
|
+
var NEXT_TSCONFIG = pkgJson({
|
|
28
|
+
compilerOptions: {
|
|
29
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
30
|
+
allowJs: true,
|
|
31
|
+
skipLibCheck: true,
|
|
32
|
+
strict: true,
|
|
33
|
+
noEmit: true,
|
|
34
|
+
esModuleInterop: true,
|
|
35
|
+
module: "esnext",
|
|
36
|
+
moduleResolution: "bundler",
|
|
37
|
+
resolveJsonModule: true,
|
|
38
|
+
isolatedModules: true,
|
|
39
|
+
jsx: "preserve",
|
|
40
|
+
incremental: true,
|
|
41
|
+
plugins: [{ name: "next" }]
|
|
42
|
+
},
|
|
43
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
44
|
+
exclude: ["node_modules"]
|
|
45
|
+
});
|
|
46
|
+
var NEXT_CONFIG_TS = [
|
|
47
|
+
'import type { NextConfig } from "next";',
|
|
48
|
+
"",
|
|
49
|
+
"const config: NextConfig = {};",
|
|
50
|
+
"",
|
|
51
|
+
"export default config;",
|
|
52
|
+
""
|
|
53
|
+
].join("\n");
|
|
54
|
+
function nextLayoutTsx(name) {
|
|
55
|
+
return [
|
|
56
|
+
'import type { Metadata } from "next";',
|
|
57
|
+
'import type { ReactNode } from "react";',
|
|
58
|
+
"",
|
|
59
|
+
"export const metadata: Metadata = {",
|
|
60
|
+
` title: "${name}",`,
|
|
61
|
+
"};",
|
|
62
|
+
"",
|
|
63
|
+
"export default function RootLayout({ children }: { children: ReactNode }) {",
|
|
64
|
+
" return (",
|
|
65
|
+
' <html lang="ko">',
|
|
66
|
+
" <body>{children}</body>",
|
|
67
|
+
" </html>",
|
|
68
|
+
" );",
|
|
69
|
+
"}",
|
|
70
|
+
""
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
73
|
+
var NEXT_PAGE_TSX = [
|
|
74
|
+
'import { Pool } from "@runlot/pg";',
|
|
75
|
+
'import { env } from "@runlot/next";',
|
|
76
|
+
"",
|
|
77
|
+
"export default async function PostsPage() {",
|
|
78
|
+
" const user = await env.auth.user();",
|
|
79
|
+
" if (!user) {",
|
|
80
|
+
" return (",
|
|
81
|
+
" <p>",
|
|
82
|
+
' \uB85C\uADF8\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. <a href="/login">\uB85C\uADF8\uC778\uD558\uAE30</a>',
|
|
83
|
+
" </p>",
|
|
84
|
+
" );",
|
|
85
|
+
" }",
|
|
86
|
+
"",
|
|
87
|
+
" const db = new Pool({ db: env.db });",
|
|
88
|
+
"",
|
|
89
|
+
" const { rows } = await db.query(",
|
|
90
|
+
' "select id, title from posts where owner = $1 limit 20",',
|
|
91
|
+
" [user.id],",
|
|
92
|
+
" );",
|
|
93
|
+
"",
|
|
94
|
+
" return (",
|
|
95
|
+
" <ul>",
|
|
96
|
+
" {rows.map((p) => <li key={p.id}>{p.title}</li>)}",
|
|
97
|
+
" </ul>",
|
|
98
|
+
" );",
|
|
99
|
+
"}",
|
|
100
|
+
""
|
|
101
|
+
].join("\n");
|
|
102
|
+
function nextReadme(name) {
|
|
103
|
+
return [
|
|
104
|
+
`# ${name}`,
|
|
105
|
+
"",
|
|
106
|
+
"Next.js App Router \uD15C\uD50C\uB9BF\uC785\uB2C8\uB2E4. `app/page.tsx` \uAC00 `env.db`\xB7`env.auth.user()` \uB85C",
|
|
107
|
+
"posts 20\uAC1C\uB97C \uADF8\uB9AC\uB294 \uC11C\uBC84 \uCEF4\uD3EC\uB10C\uD2B8 \uC608\uC81C\uC785\uB2C8\uB2E4.",
|
|
108
|
+
"",
|
|
109
|
+
"```",
|
|
110
|
+
"runlot add pg auth storage",
|
|
111
|
+
"runlot deploy",
|
|
112
|
+
"```",
|
|
113
|
+
"",
|
|
114
|
+
"Postgres\xB7\uC778\uC99D\xB7\uC624\uBE0C\uC81D\uD2B8 \uC2A4\uD1A0\uB9AC\uC9C0\uAC00 \uBD99\uC740 \uB2E4\uC74C `runlot deploy` \uD558\uBA74 App Router \uAC00",
|
|
115
|
+
"\uADF8\uB300\uB85C workerd \uC704\uC5D0\uC11C \uC2E4\uD589\uB429\uB2C8\uB2E4 (OpenNext, docs/nextjs.md).",
|
|
116
|
+
""
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
function nextTemplate(ctx) {
|
|
120
|
+
const { name, org } = ctx;
|
|
121
|
+
const runlotConfig = { name };
|
|
122
|
+
if (org) runlotConfig["org"] = org;
|
|
123
|
+
runlotConfig["framework"] = "next";
|
|
124
|
+
runlotConfig["assets"] = "public";
|
|
125
|
+
const packageJson = {
|
|
126
|
+
name,
|
|
127
|
+
version: "0.0.0",
|
|
128
|
+
private: true,
|
|
129
|
+
scripts: {
|
|
130
|
+
dev: "next dev",
|
|
131
|
+
build: "next build",
|
|
132
|
+
start: "next start"
|
|
133
|
+
},
|
|
134
|
+
dependencies: {
|
|
135
|
+
next: "^16.3.4",
|
|
136
|
+
react: "^19.2.8",
|
|
137
|
+
"react-dom": "^19.2.8",
|
|
138
|
+
// 버전 범위는 release.yml 의 npm 잡이 태그와 같은 번호로 다시 쓴다 —
|
|
139
|
+
// 여기 적힌 값은 마지막 공개 버전이다.
|
|
140
|
+
"@runlot/next": "^0.1.0-rc.11",
|
|
141
|
+
"@runlot/pg": "^0.1.0-rc.11"
|
|
142
|
+
},
|
|
143
|
+
devDependencies: {
|
|
144
|
+
typescript: "^5.7.2",
|
|
145
|
+
"@types/react": "^19.2.18",
|
|
146
|
+
"@types/react-dom": "^19.2.5",
|
|
147
|
+
"@types/node": "^22.10.2"
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
"package.json": pkgJson(packageJson),
|
|
152
|
+
"runlot.json": pkgJson(runlotConfig),
|
|
153
|
+
"tsconfig.json": NEXT_TSCONFIG,
|
|
154
|
+
"next.config.ts": NEXT_CONFIG_TS,
|
|
155
|
+
"app/layout.tsx": nextLayoutTsx(name),
|
|
156
|
+
"app/page.tsx": NEXT_PAGE_TSX,
|
|
157
|
+
"public/robots.txt": "User-agent: *\nAllow: /\n",
|
|
158
|
+
// runlot deploy 가 `.runlot-next` 에 생성된 OpenNext 설정을 쓴다
|
|
159
|
+
// (docs/nextjs.md §2) — 소스가 아니니 커밋 대상이 아니다.
|
|
160
|
+
".gitignore": "node_modules\n.next\n.open-next\n.runlot-next\n",
|
|
161
|
+
"README.md": nextReadme(name)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
var HONO_INDEX_TS = [
|
|
165
|
+
'import { Hono } from "hono";',
|
|
166
|
+
'import { Pool } from "@runlot/pg";',
|
|
167
|
+
'import type { Db, Storage } from "@runlot/pg";',
|
|
168
|
+
'import type { AuthBinding } from "@runlot/auth";',
|
|
169
|
+
"",
|
|
170
|
+
"// db\xB7auth\xB7storage \uB294 `runlot add pg auth storage` \uB4A4\uC5D0 \uC2E4\uC81C \uAC12\uC774 \uC628\uB2E4.",
|
|
171
|
+
"interface Env {",
|
|
172
|
+
" db: Db;",
|
|
173
|
+
" auth: AuthBinding;",
|
|
174
|
+
" storage: Storage;",
|
|
175
|
+
"}",
|
|
176
|
+
"",
|
|
177
|
+
"const app = new Hono<{ Bindings: Env }>();",
|
|
178
|
+
"",
|
|
179
|
+
'app.get("/posts", async (c) => {',
|
|
180
|
+
" const user = await c.env.auth.user(c.req.raw);",
|
|
181
|
+
" const db = new Pool({ db: c.env.db });",
|
|
182
|
+
"",
|
|
183
|
+
" const { rows } = await db.query(",
|
|
184
|
+
' "select id, title from posts where owner = $1 limit 20",',
|
|
185
|
+
" [user.id],",
|
|
186
|
+
" );",
|
|
187
|
+
" return c.json(rows);",
|
|
188
|
+
"});",
|
|
189
|
+
"",
|
|
190
|
+
'app.post("/upload", async (c) => {',
|
|
191
|
+
" const form = await c.req.formData();",
|
|
192
|
+
' const file = form.get("file") as File;',
|
|
193
|
+
' const url = await c.env.storage.put("covers/" + file.name, file);',
|
|
194
|
+
" return c.json({ url });",
|
|
195
|
+
"});",
|
|
196
|
+
"",
|
|
197
|
+
"export default app;",
|
|
198
|
+
""
|
|
199
|
+
].join("\n");
|
|
200
|
+
function honoReadme(name) {
|
|
201
|
+
return [
|
|
202
|
+
`# ${name}`,
|
|
203
|
+
"",
|
|
204
|
+
"Hono \uC6CC\uCEE4 \uD15C\uD50C\uB9BF\uC785\uB2C8\uB2E4. `src/index.ts` \uC5D0 `/posts`(\uBAA9\uB85D)\uC640 `/upload`(\uC624\uBE0C\uC81D\uD2B8",
|
|
205
|
+
"\uC2A4\uD1A0\uB9AC\uC9C0 \uC5C5\uB85C\uB4DC) \uB450 \uACBD\uB85C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
206
|
+
"",
|
|
207
|
+
"```",
|
|
208
|
+
"runlot add pg auth storage",
|
|
209
|
+
"runlot deploy",
|
|
210
|
+
"```",
|
|
211
|
+
"",
|
|
212
|
+
"\uC704 \uB458\uC744 \uC2E4\uD589\uD558\uBA74 `Env` \uC758 `db`\xB7`auth`\xB7`storage` \uAC00 \uC2E4\uC81C \uBC14\uC778\uB529\uC744 \uBC1B\uC2B5\uB2C8\uB2E4.",
|
|
213
|
+
""
|
|
214
|
+
].join("\n");
|
|
215
|
+
}
|
|
216
|
+
function honoTemplate(ctx) {
|
|
217
|
+
const { name, org } = ctx;
|
|
218
|
+
const runlotConfig = { name };
|
|
219
|
+
if (org) runlotConfig["org"] = org;
|
|
220
|
+
runlotConfig["main"] = "src/index.ts";
|
|
221
|
+
runlotConfig["assets"] = "public";
|
|
222
|
+
const packageJson = {
|
|
223
|
+
name,
|
|
224
|
+
version: "0.0.0",
|
|
225
|
+
private: true,
|
|
226
|
+
type: "module",
|
|
227
|
+
dependencies: {
|
|
228
|
+
hono: "^4.10.7",
|
|
229
|
+
"@runlot/pg": "^0.1.0-rc.11",
|
|
230
|
+
"@runlot/auth": "^0.1.0-rc.11"
|
|
231
|
+
},
|
|
232
|
+
devDependencies: {
|
|
233
|
+
typescript: "^5.7.2",
|
|
234
|
+
"@types/node": "^22.10.2"
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const tsconfig = pkgJson({
|
|
238
|
+
compilerOptions: {
|
|
239
|
+
target: "ES2022",
|
|
240
|
+
lib: ["ES2022", "DOM"],
|
|
241
|
+
module: "ESNext",
|
|
242
|
+
moduleResolution: "bundler",
|
|
243
|
+
strict: true,
|
|
244
|
+
skipLibCheck: true,
|
|
245
|
+
noEmit: true
|
|
246
|
+
},
|
|
247
|
+
include: ["src"]
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
"package.json": pkgJson(packageJson),
|
|
251
|
+
"runlot.json": pkgJson(runlotConfig),
|
|
252
|
+
"tsconfig.json": tsconfig,
|
|
253
|
+
"src/index.ts": HONO_INDEX_TS,
|
|
254
|
+
"public/hello.txt": "\uC815\uC801 \uC790\uC0B0\uB3C4 \uAC19\uC740 \uBC88\uB4E4\uB85C \uBC30\uD3EC\uB429\uB2C8\uB2E4.\n",
|
|
255
|
+
".gitignore": "node_modules\n",
|
|
256
|
+
"README.md": honoReadme(name)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function staticIndexHtml(name) {
|
|
260
|
+
return [
|
|
261
|
+
"<!doctype html>",
|
|
262
|
+
'<html lang="ko">',
|
|
263
|
+
" <head>",
|
|
264
|
+
' <meta charset="utf-8" />',
|
|
265
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
266
|
+
` <title>${name}</title>`,
|
|
267
|
+
" </head>",
|
|
268
|
+
" <body>",
|
|
269
|
+
" <main>",
|
|
270
|
+
` <h1>${name}</h1>`,
|
|
271
|
+
" <p>",
|
|
272
|
+
" \uC774 \uD398\uC774\uC9C0\uB294 \uC815\uC801 \uC790\uC0B0\uC73C\uB85C \uBC30\uD3EC\uB429\uB2C8\uB2E4. <code>public/</code> \uC544\uB798 \uD30C\uC77C\uC744",
|
|
273
|
+
" \uACE0\uCE58\uACE0 <code>runlot deploy</code>\uB97C \uC2E4\uD589\uD558\uBA74 \uADF8\uB300\uB85C \uBC18\uC601\uB429\uB2C8\uB2E4.",
|
|
274
|
+
" </p>",
|
|
275
|
+
" </main>",
|
|
276
|
+
" </body>",
|
|
277
|
+
"</html>",
|
|
278
|
+
""
|
|
279
|
+
].join("\n");
|
|
280
|
+
}
|
|
281
|
+
function staticReadme(name) {
|
|
282
|
+
return [
|
|
283
|
+
`# ${name}`,
|
|
284
|
+
"",
|
|
285
|
+
"\uC815\uC801 \uC0AC\uC774\uD2B8 \uD15C\uD50C\uB9BF\uC785\uB2C8\uB2E4. `public/index.html` \uD558\uB098\uBFD0\uC774\uACE0 \uC6CC\uCEE4\uB294 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
286
|
+
"",
|
|
287
|
+
"```",
|
|
288
|
+
"runlot add pg auth storage",
|
|
289
|
+
"runlot deploy",
|
|
290
|
+
"```",
|
|
291
|
+
"",
|
|
292
|
+
"\uC6CC\uCEE4\uAC00 \uD544\uC694 \uC5C6\uC73C\uBA74 \uC704 \uCCAB \uC904\uC740 \uAC74\uB108\uB6F0\uC5B4\uB3C4 \uB429\uB2C8\uB2E4 \u2014 \uC790\uC0B0\uB9CC \uADF8\uB300\uB85C \uBC30\uD3EC\uB429\uB2C8\uB2E4.",
|
|
293
|
+
""
|
|
294
|
+
].join("\n");
|
|
295
|
+
}
|
|
296
|
+
function staticTemplate(ctx) {
|
|
297
|
+
const { name, org } = ctx;
|
|
298
|
+
const runlotConfig = { name };
|
|
299
|
+
if (org) runlotConfig["org"] = org;
|
|
300
|
+
runlotConfig["assets"] = "public";
|
|
301
|
+
return {
|
|
302
|
+
// 클라이언트 라우팅(React Router 등)을 쓰면 `"notFound": "spa"` 를 더한다 —
|
|
303
|
+
// 확장자 없는 경로가 index.html 로 답한다.
|
|
304
|
+
"runlot.json": pkgJson(runlotConfig),
|
|
305
|
+
"public/index.html": staticIndexHtml(name),
|
|
306
|
+
".gitignore": "node_modules\n",
|
|
307
|
+
"README.md": staticReadme(name)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
var TEMPLATES = {
|
|
311
|
+
next: nextTemplate,
|
|
312
|
+
hono: honoTemplate,
|
|
313
|
+
static: staticTemplate
|
|
314
|
+
};
|
|
315
|
+
function scaffold(opts) {
|
|
316
|
+
const { target, org, template } = opts;
|
|
317
|
+
const dir = resolve(target);
|
|
318
|
+
const name = target.split("/").filter(Boolean).pop();
|
|
319
|
+
if (!NAME_RE.test(name)) {
|
|
320
|
+
throw new Error(`${name} \uC740 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC73C\uB85C \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508)`);
|
|
321
|
+
}
|
|
322
|
+
if (existsSync(dir)) throw new Error(`${dir} \uC774 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4`);
|
|
323
|
+
const files = TEMPLATES[template]({ name, org });
|
|
324
|
+
const relPaths = Object.keys(files).sort();
|
|
325
|
+
for (const rel of relPaths) {
|
|
326
|
+
const path = join(dir, rel);
|
|
327
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
328
|
+
writeFileSync(path, files[rel]);
|
|
329
|
+
}
|
|
330
|
+
console.log(`${dir} \uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
331
|
+
console.log(` cd ${target}`);
|
|
332
|
+
console.log(" runlot add pg auth storage");
|
|
333
|
+
console.log(org ? " runlot deploy" : " runlot deploy --org <slug>");
|
|
334
|
+
return { dir, files: relPaths };
|
|
335
|
+
}
|
|
336
|
+
async function defaultAsk(question) {
|
|
337
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
338
|
+
try {
|
|
339
|
+
return await rl.question(question);
|
|
340
|
+
} finally {
|
|
341
|
+
rl.close();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
var MAX_PROMPT_TRIES = 3;
|
|
345
|
+
async function chooseTemplate(ask) {
|
|
346
|
+
const listText = TEMPLATE_LIST.map((t, i) => ` ${i + 1}. ${t.name.padEnd(6)} ${t.desc}`).join("\n");
|
|
347
|
+
const question = `\uC5B4\uB5A4 \uD15C\uD50C\uB9BF\uC744 \uC4F8\uAE4C\uC694?
|
|
348
|
+
|
|
349
|
+
${listText}
|
|
350
|
+
|
|
351
|
+
\uC774\uB984\uC774\uB098 \uBC88\uD638\uB97C \uC785\uB825\uD558\uC138\uC694: `;
|
|
352
|
+
for (let attempt = 1; attempt <= MAX_PROMPT_TRIES; attempt++) {
|
|
353
|
+
const answer = (await ask(question)).trim();
|
|
354
|
+
const byIndex = TEMPLATE_LIST[Number(answer) - 1];
|
|
355
|
+
const byName = TEMPLATE_LIST.find((t) => t.name === answer);
|
|
356
|
+
const picked = byIndex ?? byName;
|
|
357
|
+
if (picked) return picked.name;
|
|
358
|
+
console.error(`\uBAA8\uB974\uB294 \uB2F5\uC785\uB2C8\uB2E4: "${answer}". ${TEMPLATE_NAMES.join(", ")} \uC911\uC5D0\uC11C \uACE0\uB974\uC138\uC694.`);
|
|
359
|
+
}
|
|
360
|
+
throw new Error(`${MAX_PROMPT_TRIES}\uBC88 \uC2DC\uB3C4\uD574\uB3C4 \uACE0\uB974\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --template \uC73C\uB85C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`);
|
|
361
|
+
}
|
|
362
|
+
async function run(argv, deps) {
|
|
363
|
+
const args = [];
|
|
364
|
+
let org;
|
|
365
|
+
let templateArg;
|
|
366
|
+
for (let i = 0; i < argv.length; i++) {
|
|
367
|
+
const a = argv[i];
|
|
368
|
+
if (a === "--org") {
|
|
369
|
+
org = argv[++i];
|
|
370
|
+
if (org === void 0) throw new Error("--org \uC5D0 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
371
|
+
} else if (a === "--template") {
|
|
372
|
+
templateArg = argv[++i];
|
|
373
|
+
if (templateArg === void 0) throw new Error("--template \uC5D0 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
374
|
+
} else if (a === "-h" || a === "--help") {
|
|
375
|
+
console.log(USAGE);
|
|
376
|
+
return 0;
|
|
377
|
+
} else {
|
|
378
|
+
args.push(a);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const target = args[0];
|
|
382
|
+
if (!target) {
|
|
383
|
+
console.error(USAGE);
|
|
384
|
+
return 2;
|
|
385
|
+
}
|
|
386
|
+
let template;
|
|
387
|
+
if (templateArg !== void 0) {
|
|
388
|
+
if (!isTemplateName(templateArg)) {
|
|
389
|
+
throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD15C\uD50C\uB9BF\uC785\uB2C8\uB2E4: "${templateArg}" (${TEMPLATE_NAMES.join(", ")} \uC911\uC5D0\uC11C \uACE0\uB974\uC138\uC694)`);
|
|
390
|
+
}
|
|
391
|
+
template = templateArg;
|
|
392
|
+
} else if (deps.isTTY ?? process.stdin.isTTY === true) {
|
|
393
|
+
template = await chooseTemplate(deps.ask ?? defaultAsk);
|
|
394
|
+
} else {
|
|
395
|
+
throw new Error(`--template \uC774 \uD544\uC694\uD569\uB2C8\uB2E4 (${TEMPLATE_NAMES.join(", ")} \uC911\uC5D0\uC11C \uACE0\uB974\uC138\uC694)`);
|
|
396
|
+
}
|
|
397
|
+
scaffold({ target, org, template });
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
async function main(argv, deps = {}) {
|
|
401
|
+
try {
|
|
402
|
+
return await run(argv, deps);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error(`\uC624\uB958: ${err instanceof Error ? err.message : String(err)}`);
|
|
405
|
+
return 1;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (process.argv[1] && process.argv[1].endsWith("index.js")) {
|
|
409
|
+
main(process.argv.slice(2)).then((code) => process.exit(code));
|
|
410
|
+
}
|
|
411
|
+
export {
|
|
412
|
+
main,
|
|
413
|
+
scaffold
|
|
414
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-runlot",
|
|
3
|
+
"version": "0.1.0-rc.11",
|
|
4
|
+
"description": "npm create runlot — Next.js·Hono·정적 사이트 템플릿",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/runlot/runlot",
|
|
11
|
+
"directory": "web/apps/create-runlot"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"bin": {
|
|
15
|
+
"create-runlot": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"esbuild": "^0.24.2",
|
|
22
|
+
"typescript": "^5.7.2",
|
|
23
|
+
"@types/node": "^22.10.2",
|
|
24
|
+
"@runlot/config": "0.1.0-rc.11"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js='#!/usr/bin/env node'",
|
|
31
|
+
"test": "node build.mjs",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
}
|
|
34
|
+
}
|