create-celsian 0.2.0 → 0.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/LICENSE +21 -0
- package/dist/index.js +65 -53
- package/dist/index.js.map +1 -1
- package/dist/templates/basic.d.ts +3 -3
- package/dist/templates/basic.d.ts.map +1 -1
- package/dist/templates/basic.js +35 -18
- package/dist/templates/basic.js.map +1 -1
- package/dist/templates/full.d.ts.map +1 -1
- package/dist/templates/full.js +106 -101
- package/dist/templates/full.js.map +1 -1
- package/dist/templates/rest-api.d.ts +3 -3
- package/dist/templates/rest-api.d.ts.map +1 -1
- package/dist/templates/rest-api.js +42 -28
- package/dist/templates/rest-api.js.map +1 -1
- package/dist/templates/rpc-api.d.ts +3 -3
- package/dist/templates/rpc-api.d.ts.map +1 -1
- package/dist/templates/rpc-api.js +37 -20
- package/dist/templates/rpc-api.js.map +1 -1
- package/package.json +7 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ThenJS
|
|
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.
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// create-celsian — Project scaffolder
|
|
3
3
|
// Zero external dependencies. Interactive prompts via raw stdin.
|
|
4
|
-
import { mkdirSync, writeFileSync } from
|
|
5
|
-
import {
|
|
6
|
-
import { createInterface } from
|
|
7
|
-
import { basicTemplate } from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
7
|
+
import { basicTemplate } from "./templates/basic.js";
|
|
8
|
+
import { fullTemplate } from "./templates/full.js";
|
|
9
|
+
import { restApiTemplate } from "./templates/rest-api.js";
|
|
10
|
+
import { rpcApiTemplate } from "./templates/rpc-api.js";
|
|
11
11
|
// ─── Template Registry ───
|
|
12
12
|
const templates = {
|
|
13
13
|
full: fullTemplate,
|
|
14
14
|
basic: basicTemplate,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
"rest-api": restApiTemplate,
|
|
16
|
+
"rpc-api": rpcApiTemplate,
|
|
17
17
|
};
|
|
18
18
|
const templateDescriptions = {
|
|
19
|
-
full:
|
|
20
|
-
basic:
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
full: "Full-stack API with auth, CRUD, RPC, tasks, cron, OpenAPI, Docker",
|
|
20
|
+
basic: "Minimal API server",
|
|
21
|
+
"rest-api": "REST API with TypeBox schemas",
|
|
22
|
+
"rpc-api": "RPC-first with typed client",
|
|
23
23
|
};
|
|
24
24
|
// ─── CLI Argument Parsing ───
|
|
25
25
|
const args = process.argv.slice(2);
|
|
26
26
|
// Handle --help
|
|
27
|
-
if (args.includes(
|
|
27
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
28
28
|
printUsage();
|
|
29
29
|
process.exit(0);
|
|
30
30
|
}
|
|
31
31
|
// Extract flags
|
|
32
|
-
const templateFlag = args.indexOf(
|
|
32
|
+
const templateFlag = args.indexOf("--template");
|
|
33
33
|
const templateArg = templateFlag !== -1 ? args[templateFlag + 1] : undefined;
|
|
34
|
-
const nameArg = args.find(a => !a.startsWith(
|
|
34
|
+
const nameArg = args.find((a) => !a.startsWith("--") && (templateFlag === -1 || args.indexOf(a) !== templateFlag + 1));
|
|
35
35
|
// ─── Interactive Mode ───
|
|
36
36
|
async function prompt(question, defaultValue) {
|
|
37
37
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -43,29 +43,29 @@ async function prompt(question, defaultValue) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
function detectPackageManager() {
|
|
46
|
-
const userAgent = process.env.npm_config_user_agent ??
|
|
47
|
-
if (userAgent.startsWith(
|
|
48
|
-
return
|
|
49
|
-
if (userAgent.startsWith(
|
|
50
|
-
return
|
|
51
|
-
return
|
|
46
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
47
|
+
if (userAgent.startsWith("pnpm"))
|
|
48
|
+
return "pnpm";
|
|
49
|
+
if (userAgent.startsWith("bun"))
|
|
50
|
+
return "bun";
|
|
51
|
+
return "npm";
|
|
52
52
|
}
|
|
53
53
|
async function interactiveMode() {
|
|
54
|
-
console.log(
|
|
55
|
-
console.log(
|
|
56
|
-
console.log(
|
|
57
|
-
console.log(
|
|
58
|
-
const name = await prompt(
|
|
59
|
-
console.log(
|
|
60
|
-
console.log(
|
|
54
|
+
console.log("");
|
|
55
|
+
console.log(" Create a new Celsian project");
|
|
56
|
+
console.log(" ────────────────────────────");
|
|
57
|
+
console.log("");
|
|
58
|
+
const name = await prompt("Project name", "my-celsian-app");
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log(" Available templates:");
|
|
61
61
|
for (const [key, desc] of Object.entries(templateDescriptions)) {
|
|
62
|
-
const marker = key ===
|
|
62
|
+
const marker = key === "full" ? " (recommended)" : "";
|
|
63
63
|
console.log(` ${key.padEnd(12)} ${desc}${marker}`);
|
|
64
64
|
}
|
|
65
|
-
console.log(
|
|
66
|
-
const template = await prompt(
|
|
65
|
+
console.log("");
|
|
66
|
+
const template = await prompt("Template", "full");
|
|
67
67
|
const detected = detectPackageManager();
|
|
68
|
-
const pm = await prompt(
|
|
68
|
+
const pm = await prompt("Package manager", detected);
|
|
69
69
|
return { name, template, pm };
|
|
70
70
|
}
|
|
71
71
|
// ─── Scaffold ───
|
|
@@ -73,14 +73,26 @@ function scaffold(name, template, pm) {
|
|
|
73
73
|
const files = templates[template];
|
|
74
74
|
if (!files) {
|
|
75
75
|
console.error(`\n Unknown template: ${template}`);
|
|
76
|
-
console.error(` Available: ${Object.keys(templates).join(
|
|
76
|
+
console.error(` Available: ${Object.keys(templates).join(", ")}\n`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
// Sanitize: reject names containing path traversal
|
|
80
|
+
if (name.includes("..")) {
|
|
81
|
+
console.error("\n Invalid project name: must not contain '..'.\n");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const cwd = process.cwd();
|
|
85
|
+
const dir = isAbsolute(name) ? name : join(cwd, name);
|
|
86
|
+
const resolved = resolve(dir);
|
|
87
|
+
// Ensure the resolved path is a child of cwd
|
|
88
|
+
if (!resolved.startsWith(cwd + "/") && resolved !== cwd) {
|
|
89
|
+
console.error("\n Invalid project name: resolved path must be inside the current directory.\n");
|
|
77
90
|
process.exit(1);
|
|
78
91
|
}
|
|
79
|
-
const dir = isAbsolute(name) ? name : join(process.cwd(), name);
|
|
80
92
|
const projectName = basename(dir);
|
|
81
93
|
console.log(`\n Creating Celsian project: ${projectName}`);
|
|
82
94
|
console.log(` Template: ${template}`);
|
|
83
|
-
console.log(
|
|
95
|
+
console.log("");
|
|
84
96
|
for (const [filePath, content] of Object.entries(files)) {
|
|
85
97
|
const fullPath = join(dir, filePath);
|
|
86
98
|
const fileDir = dirname(fullPath);
|
|
@@ -88,26 +100,26 @@ function scaffold(name, template, pm) {
|
|
|
88
100
|
writeFileSync(fullPath, content.replace(/\{\{name\}\}/g, projectName));
|
|
89
101
|
console.log(` + ${filePath}`);
|
|
90
102
|
}
|
|
91
|
-
const install = pm ===
|
|
92
|
-
const dev = pm ===
|
|
103
|
+
const install = pm === "npm" ? "npm install" : `${pm} install`;
|
|
104
|
+
const dev = pm === "npm" ? "npm run dev" : `${pm} run dev`;
|
|
93
105
|
console.log(`\n Done! Next steps:\n`);
|
|
94
106
|
console.log(` cd ${projectName}`);
|
|
95
107
|
console.log(` ${install}`);
|
|
96
|
-
if (template ===
|
|
97
|
-
console.log(
|
|
108
|
+
if (template === "full") {
|
|
109
|
+
console.log(" cp .env.example .env");
|
|
98
110
|
}
|
|
99
111
|
console.log(` ${dev}`);
|
|
100
|
-
if (template ===
|
|
101
|
-
console.log(
|
|
102
|
-
console.log(
|
|
112
|
+
if (template === "full") {
|
|
113
|
+
console.log("");
|
|
114
|
+
console.log(" Open http://localhost:3000/docs for API documentation");
|
|
103
115
|
}
|
|
104
|
-
console.log(
|
|
116
|
+
console.log("");
|
|
105
117
|
}
|
|
106
118
|
// ─── Main ───
|
|
107
119
|
async function main() {
|
|
108
120
|
// If both name and template are provided via CLI args, skip interactive mode
|
|
109
121
|
if (nameArg) {
|
|
110
|
-
const template = templateArg ??
|
|
122
|
+
const template = templateArg ?? "full";
|
|
111
123
|
const pm = detectPackageManager();
|
|
112
124
|
scaffold(nameArg, template, pm);
|
|
113
125
|
return;
|
|
@@ -124,17 +136,17 @@ async function main() {
|
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
function printUsage() {
|
|
127
|
-
console.log(
|
|
128
|
-
console.log(
|
|
129
|
-
console.log(
|
|
130
|
-
console.log(
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(" Usage: create-celsian <project-name> [--template full|basic|rest-api|rpc-api]");
|
|
141
|
+
console.log("");
|
|
142
|
+
console.log(" Templates:");
|
|
131
143
|
for (const [key, desc] of Object.entries(templateDescriptions)) {
|
|
132
|
-
const defaultMarker = key ===
|
|
144
|
+
const defaultMarker = key === "full" ? " (default)" : "";
|
|
133
145
|
console.log(` ${key.padEnd(12)} ${desc}${defaultMarker}`);
|
|
134
146
|
}
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(
|
|
137
|
-
console.log(
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log(" Run without arguments for interactive mode.");
|
|
149
|
+
console.log("");
|
|
138
150
|
}
|
|
139
151
|
main().catch((err) => {
|
|
140
152
|
console.error(err);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,sCAAsC;AACtC,iEAAiE;AAEjE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;
|
|
1
|
+
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;CAsEzB,CAAC"}
|
package/dist/templates/basic.js
CHANGED
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
export const basicTemplate = {
|
|
2
|
-
|
|
3
|
-
name:
|
|
4
|
-
version:
|
|
5
|
-
type:
|
|
2
|
+
"package.json": JSON.stringify({
|
|
3
|
+
name: "{{name}}",
|
|
4
|
+
version: "0.0.1",
|
|
5
|
+
type: "module",
|
|
6
6
|
scripts: {
|
|
7
|
-
dev:
|
|
8
|
-
build:
|
|
9
|
-
start:
|
|
7
|
+
dev: "npx tsx --watch src/index.ts",
|
|
8
|
+
build: "tsc",
|
|
9
|
+
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian:
|
|
12
|
+
celsian: "latest",
|
|
13
13
|
},
|
|
14
14
|
devDependencies: {
|
|
15
|
-
typescript:
|
|
16
|
-
tsx:
|
|
15
|
+
typescript: "^5.7.0",
|
|
16
|
+
tsx: "^4.0.0",
|
|
17
17
|
},
|
|
18
18
|
}, null, 2),
|
|
19
|
-
|
|
19
|
+
"tsconfig.json": JSON.stringify({
|
|
20
20
|
compilerOptions: {
|
|
21
|
-
target:
|
|
22
|
-
module:
|
|
23
|
-
moduleResolution:
|
|
21
|
+
target: "ES2022",
|
|
22
|
+
module: "ESNext",
|
|
23
|
+
moduleResolution: "bundler",
|
|
24
24
|
strict: true,
|
|
25
25
|
esModuleInterop: true,
|
|
26
26
|
skipLibCheck: true,
|
|
27
|
-
outDir:
|
|
28
|
-
rootDir:
|
|
27
|
+
outDir: "dist",
|
|
28
|
+
rootDir: "src",
|
|
29
29
|
},
|
|
30
|
-
include: [
|
|
30
|
+
include: ["src"],
|
|
31
31
|
}, null, 2),
|
|
32
|
-
|
|
32
|
+
"src/index.ts": `import { createApp, serve, cors, security } from 'celsian';
|
|
33
33
|
|
|
34
34
|
const app = createApp();
|
|
35
35
|
|
|
36
|
+
// ─── Security (CORS + security headers) ───
|
|
37
|
+
|
|
38
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? 'http://localhost:3000';
|
|
39
|
+
|
|
40
|
+
await app.register(cors({
|
|
41
|
+
origin: CORS_ORIGIN,
|
|
42
|
+
credentials: true,
|
|
43
|
+
maxAge: 86400,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
await app.register(security({
|
|
47
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
48
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// ─── Routes ───
|
|
52
|
+
|
|
36
53
|
app.get('/health', (req, reply) => {
|
|
37
54
|
return reply.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
38
55
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.js","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"basic.js","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;SAClB;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;SACd;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BjB;CACA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"full.d.ts","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"full.d.ts","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAg4B/C,CAAC"}
|
package/dist/templates/full.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
export const fullTemplate = {
|
|
2
|
-
|
|
3
|
-
name:
|
|
4
|
-
version:
|
|
5
|
-
type:
|
|
2
|
+
"package.json": JSON.stringify({
|
|
3
|
+
name: "{{name}}",
|
|
4
|
+
version: "0.1.0",
|
|
5
|
+
type: "module",
|
|
6
6
|
scripts: {
|
|
7
|
-
dev:
|
|
8
|
-
build:
|
|
9
|
-
start:
|
|
10
|
-
test:
|
|
11
|
-
lint:
|
|
7
|
+
dev: "npx tsx --watch src/index.ts",
|
|
8
|
+
build: "tsc",
|
|
9
|
+
start: "node dist/index.js",
|
|
10
|
+
test: "npx vitest run",
|
|
11
|
+
lint: "npx tsc --noEmit",
|
|
12
12
|
},
|
|
13
13
|
dependencies: {
|
|
14
|
-
celsian:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
celsian: "latest",
|
|
15
|
+
"@celsian/core": "latest",
|
|
16
|
+
"@celsian/jwt": "latest",
|
|
17
|
+
"@celsian/rpc": "latest",
|
|
18
|
+
"@celsian/rate-limit": "latest",
|
|
19
|
+
"@sinclair/typebox": "^0.34.0",
|
|
20
20
|
},
|
|
21
21
|
devDependencies: {
|
|
22
|
-
typescript:
|
|
23
|
-
tsx:
|
|
24
|
-
vitest:
|
|
25
|
-
|
|
22
|
+
typescript: "^5.7.0",
|
|
23
|
+
tsx: "^4.0.0",
|
|
24
|
+
vitest: "^3.0.0",
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
26
|
},
|
|
27
27
|
}, null, 2),
|
|
28
|
-
|
|
28
|
+
"tsconfig.json": JSON.stringify({
|
|
29
29
|
compilerOptions: {
|
|
30
|
-
target:
|
|
31
|
-
module:
|
|
32
|
-
moduleResolution:
|
|
33
|
-
lib: [
|
|
34
|
-
types: [
|
|
30
|
+
target: "ES2022",
|
|
31
|
+
module: "ESNext",
|
|
32
|
+
moduleResolution: "bundler",
|
|
33
|
+
lib: ["ES2022"],
|
|
34
|
+
types: ["node"],
|
|
35
35
|
strict: true,
|
|
36
36
|
esModuleInterop: true,
|
|
37
37
|
skipLibCheck: true,
|
|
@@ -39,15 +39,15 @@ export const fullTemplate = {
|
|
|
39
39
|
resolveJsonModule: true,
|
|
40
40
|
isolatedModules: true,
|
|
41
41
|
declaration: true,
|
|
42
|
-
outDir:
|
|
43
|
-
rootDir:
|
|
42
|
+
outDir: "dist",
|
|
43
|
+
rootDir: "src",
|
|
44
44
|
},
|
|
45
|
-
include: [
|
|
45
|
+
include: ["src"],
|
|
46
46
|
}, null, 2),
|
|
47
|
-
|
|
47
|
+
".env.example": `# Server
|
|
48
48
|
PORT=3000
|
|
49
49
|
HOST=0.0.0.0
|
|
50
|
-
CORS_ORIGIN
|
|
50
|
+
CORS_ORIGIN=http://localhost:3000
|
|
51
51
|
|
|
52
52
|
# Auth
|
|
53
53
|
JWT_SECRET=change-me-to-a-real-secret-at-least-32-chars
|
|
@@ -58,14 +58,14 @@ DATABASE_URL=file:./data.db
|
|
|
58
58
|
# Environment
|
|
59
59
|
NODE_ENV=development
|
|
60
60
|
`,
|
|
61
|
-
|
|
61
|
+
".gitignore": `node_modules/
|
|
62
62
|
dist/
|
|
63
63
|
*.tsbuildinfo
|
|
64
64
|
.env
|
|
65
65
|
data.db
|
|
66
66
|
`,
|
|
67
67
|
// ─── src/types.ts ───
|
|
68
|
-
|
|
68
|
+
"src/types.ts": `// Shared types for {{name}}
|
|
69
69
|
|
|
70
70
|
export interface User {
|
|
71
71
|
id: string;
|
|
@@ -98,7 +98,7 @@ export interface JWTPayload {
|
|
|
98
98
|
}
|
|
99
99
|
`,
|
|
100
100
|
// ─── src/plugins/database.ts ───
|
|
101
|
-
|
|
101
|
+
"src/plugins/database.ts": `// Database module — in-memory store for development
|
|
102
102
|
// Replace with a real database (PostgreSQL, SQLite, etc.) for production
|
|
103
103
|
|
|
104
104
|
import type { User, Session } from '../types.js';
|
|
@@ -133,7 +133,7 @@ const demoUser: User = {
|
|
|
133
133
|
db.users.set(demoUser.id, demoUser);
|
|
134
134
|
`,
|
|
135
135
|
// ─── src/plugins/auth.ts ───
|
|
136
|
-
|
|
136
|
+
"src/plugins/auth.ts": `// JWT auth plugin — guards protected routes via Bearer token
|
|
137
137
|
// Uses @celsian/jwt under the hood
|
|
138
138
|
|
|
139
139
|
import { jwt, createJWTGuard } from '@celsian/jwt';
|
|
@@ -141,6 +141,15 @@ import type { PluginFunction, HookHandler } from '@celsian/core';
|
|
|
141
141
|
|
|
142
142
|
const JWT_SECRET = process.env.JWT_SECRET ?? 'dev-secret-change-me';
|
|
143
143
|
|
|
144
|
+
// Refuse to start in production with the default dev secret
|
|
145
|
+
if (process.env.NODE_ENV === 'production' && JWT_SECRET === 'dev-secret-change-me') {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'[celsian] FATAL: JWT_SECRET is set to the default dev value. ' +
|
|
148
|
+
'Set a strong, unique JWT_SECRET environment variable before running in production. ' +
|
|
149
|
+
'Generate one with: node -e "console.log(require(\\'crypto\\').randomBytes(32).toString(\\'base64\\'))"'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
144
153
|
/**
|
|
145
154
|
* Register the JWT plugin. After this, \`app.jwt\` is available for
|
|
146
155
|
* signing and verifying tokens.
|
|
@@ -158,19 +167,30 @@ export const requireAuth: HookHandler = createJWTGuard({
|
|
|
158
167
|
});
|
|
159
168
|
`,
|
|
160
169
|
// ─── src/plugins/security.ts ───
|
|
161
|
-
|
|
170
|
+
"src/plugins/security.ts": `// Security plugin — CORS + CSRF + security headers + rate limiting
|
|
162
171
|
// Combines multiple @celsian/core plugins into a single registration
|
|
163
172
|
|
|
164
173
|
import { cors, security, csrf } from '@celsian/core';
|
|
165
174
|
import { rateLimit } from '@celsian/rate-limit';
|
|
166
175
|
import type { PluginFunction } from '@celsian/core';
|
|
167
176
|
|
|
168
|
-
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? '
|
|
177
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? 'http://localhost:3000';
|
|
169
178
|
|
|
170
179
|
/**
|
|
171
180
|
* Register all security-related plugins in one call.
|
|
172
181
|
*/
|
|
173
182
|
export function securityPlugins(): PluginFunction[] {
|
|
183
|
+
// WARNING: credentials:true is incompatible with origin:'*'.
|
|
184
|
+
// Browsers will reject Set-Cookie headers when the CORS origin is a wildcard.
|
|
185
|
+
// Always set CORS_ORIGIN to a specific origin (e.g. 'http://localhost:3000')
|
|
186
|
+
// when credentials:true is enabled.
|
|
187
|
+
if (CORS_ORIGIN === '*') {
|
|
188
|
+
console.warn(
|
|
189
|
+
'[celsian] WARNING: CORS_ORIGIN=* with credentials:true is insecure and will not work in browsers. ' +
|
|
190
|
+
'Set CORS_ORIGIN to a specific origin.'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
174
194
|
return [
|
|
175
195
|
// CORS — allow cross-origin requests
|
|
176
196
|
cors({
|
|
@@ -201,7 +221,7 @@ export function securityPlugins(): PluginFunction[] {
|
|
|
201
221
|
}
|
|
202
222
|
`,
|
|
203
223
|
// ─── src/routes/health.ts ───
|
|
204
|
-
|
|
224
|
+
"src/routes/health.ts": `// Health check route — GET /health
|
|
205
225
|
// Returns server status and uptime for load balancers and monitoring
|
|
206
226
|
|
|
207
227
|
import type { PluginFunction } from '@celsian/core';
|
|
@@ -223,12 +243,12 @@ export default function healthRoutes(): PluginFunction {
|
|
|
223
243
|
}
|
|
224
244
|
`,
|
|
225
245
|
// ─── src/routes/users.ts ───
|
|
226
|
-
|
|
246
|
+
"src/routes/users.ts": `// User CRUD routes — /users
|
|
227
247
|
// Full REST: GET (list), POST (create), GET/:id, PUT/:id, DELETE/:id
|
|
228
248
|
|
|
229
249
|
import { Type } from '@sinclair/typebox';
|
|
230
|
-
import type { PluginFunction
|
|
231
|
-
import type { User
|
|
250
|
+
import type { PluginFunction } from '@celsian/core';
|
|
251
|
+
import type { User } from '../types.js';
|
|
232
252
|
import { db } from '../plugins/database.js';
|
|
233
253
|
import { requireAuth } from '../plugins/auth.js';
|
|
234
254
|
|
|
@@ -250,22 +270,19 @@ export default function userRoutes(): PluginFunction {
|
|
|
250
270
|
return reply.json(allUsers);
|
|
251
271
|
});
|
|
252
272
|
|
|
253
|
-
// POST /users — create a new user
|
|
254
|
-
app.
|
|
255
|
-
method: 'POST',
|
|
256
|
-
url: '/users',
|
|
273
|
+
// POST /users — create a new user (typed body from schema)
|
|
274
|
+
app.post('/users', {
|
|
257
275
|
schema: { body: CreateUserSchema },
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
},
|
|
276
|
+
}, (req, reply) => {
|
|
277
|
+
const { name, email } = req.parsedBody;
|
|
278
|
+
const user: User = {
|
|
279
|
+
id: db.generateId(),
|
|
280
|
+
name,
|
|
281
|
+
email,
|
|
282
|
+
createdAt: new Date().toISOString(),
|
|
283
|
+
};
|
|
284
|
+
db.users.set(user.id, user);
|
|
285
|
+
return reply.status(201).json(user);
|
|
269
286
|
});
|
|
270
287
|
|
|
271
288
|
// GET /users/:id — get a single user
|
|
@@ -275,21 +292,18 @@ export default function userRoutes(): PluginFunction {
|
|
|
275
292
|
return reply.json(user);
|
|
276
293
|
});
|
|
277
294
|
|
|
278
|
-
// PUT /users/:id — update a user (protected)
|
|
279
|
-
app.
|
|
280
|
-
method: 'PUT',
|
|
281
|
-
url: '/users/:id',
|
|
295
|
+
// PUT /users/:id — update a user (protected, typed body from schema)
|
|
296
|
+
app.put('/users/:id', {
|
|
282
297
|
schema: { body: UpdateUserSchema },
|
|
283
298
|
onRequest: requireAuth,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
},
|
|
299
|
+
}, (req, reply) => {
|
|
300
|
+
const user = db.users.get(req.params.id);
|
|
301
|
+
if (!user) return reply.status(404).json({ error: 'User not found' });
|
|
302
|
+
const updates = req.parsedBody;
|
|
303
|
+
if (updates.name !== undefined) user.name = updates.name;
|
|
304
|
+
if (updates.email !== undefined) user.email = updates.email;
|
|
305
|
+
db.users.set(user.id, user);
|
|
306
|
+
return reply.json(user);
|
|
293
307
|
});
|
|
294
308
|
|
|
295
309
|
// DELETE /users/:id — delete a user (protected)
|
|
@@ -307,7 +321,7 @@ export default function userRoutes(): PluginFunction {
|
|
|
307
321
|
}
|
|
308
322
|
`,
|
|
309
323
|
// ─── src/routes/rpc.ts ───
|
|
310
|
-
|
|
324
|
+
"src/routes/rpc.ts": `// RPC endpoint — type-safe procedures at /_rpc/*
|
|
311
325
|
// Demonstrates queries and mutations with typed schemas
|
|
312
326
|
|
|
313
327
|
import { procedure, router, RPCHandler } from '@celsian/rpc';
|
|
@@ -318,24 +332,21 @@ import type { PluginFunction } from '@celsian/core';
|
|
|
318
332
|
const appRouter = router({
|
|
319
333
|
greeting: {
|
|
320
334
|
hello: procedure
|
|
321
|
-
.input(Type.Object({ name: Type.String() }))
|
|
335
|
+
.input<{ name: string }>(Type.Object({ name: Type.String() }))
|
|
322
336
|
.query(({ input }) => {
|
|
323
|
-
|
|
324
|
-
return { message: \`Hello, \${name}!\` };
|
|
337
|
+
return { message: \`Hello, \${input.name}!\` };
|
|
325
338
|
}),
|
|
326
339
|
},
|
|
327
340
|
math: {
|
|
328
341
|
add: procedure
|
|
329
|
-
.input(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
342
|
+
.input<{ a: number; b: number }>(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
330
343
|
.query(({ input }) => {
|
|
331
|
-
|
|
332
|
-
return { result: a + b };
|
|
344
|
+
return { result: input.a + input.b };
|
|
333
345
|
}),
|
|
334
346
|
multiply: procedure
|
|
335
|
-
.input(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
347
|
+
.input<{ a: number; b: number }>(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
336
348
|
.mutation(({ input }) => {
|
|
337
|
-
|
|
338
|
-
return { result: a * b };
|
|
349
|
+
return { result: input.a * input.b };
|
|
339
350
|
}),
|
|
340
351
|
},
|
|
341
352
|
system: {
|
|
@@ -363,7 +374,7 @@ export default function rpcRoutes(): PluginFunction {
|
|
|
363
374
|
}
|
|
364
375
|
`,
|
|
365
376
|
// ─── src/tasks/cleanup.ts ───
|
|
366
|
-
|
|
377
|
+
"src/tasks/cleanup.ts": `// Background task: clean up expired sessions
|
|
367
378
|
// Registered with app.task() and runs when enqueued or on a schedule
|
|
368
379
|
|
|
369
380
|
import type { TaskDefinition } from '@celsian/core';
|
|
@@ -391,7 +402,7 @@ export const cleanupTask: TaskDefinition = {
|
|
|
391
402
|
};
|
|
392
403
|
`,
|
|
393
404
|
// ─── src/tasks/report.ts ───
|
|
394
|
-
|
|
405
|
+
"src/tasks/report.ts": `// Cron job: daily report generation
|
|
395
406
|
// Runs every day at midnight via app.cron()
|
|
396
407
|
|
|
397
408
|
/**
|
|
@@ -411,7 +422,7 @@ export async function generateDailyReport(): Promise<void> {
|
|
|
411
422
|
}
|
|
412
423
|
`,
|
|
413
424
|
// ─── src/index.ts ───
|
|
414
|
-
|
|
425
|
+
"src/index.ts": `// {{name}} — Full-stack Celsian API
|
|
415
426
|
// Routes, plugins, background tasks, and cron — all wired up
|
|
416
427
|
|
|
417
428
|
import { createApp, serve, openapi } from 'celsian';
|
|
@@ -439,12 +450,12 @@ const app = createApp({ logger: true });
|
|
|
439
450
|
// ─── Security (CORS, CSRF, headers, rate limiting) ───
|
|
440
451
|
|
|
441
452
|
for (const plugin of securityPlugins()) {
|
|
442
|
-
await app.register(plugin);
|
|
453
|
+
await app.register(plugin, { encapsulate: false });
|
|
443
454
|
}
|
|
444
455
|
|
|
445
456
|
// ─── Auth (JWT signing & verification) ───
|
|
446
457
|
|
|
447
|
-
await app.register(authPlugin());
|
|
458
|
+
await app.register(authPlugin(), { encapsulate: false });
|
|
448
459
|
|
|
449
460
|
// ─── API Documentation (OpenAPI + Swagger UI) ───
|
|
450
461
|
|
|
@@ -481,7 +492,7 @@ const port = parseInt(process.env.PORT ?? '3000', 10);
|
|
|
481
492
|
serve(app, { port });
|
|
482
493
|
`,
|
|
483
494
|
// ─── test/api.test.ts ───
|
|
484
|
-
|
|
495
|
+
"test/api.test.ts": `// Integration tests using app.inject() — no server needed
|
|
485
496
|
// Run with: npm test
|
|
486
497
|
|
|
487
498
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
@@ -642,7 +653,7 @@ EXPOSE 3000
|
|
|
642
653
|
CMD ["node", "dist/index.js"]
|
|
643
654
|
`,
|
|
644
655
|
// ─── README.md ───
|
|
645
|
-
|
|
656
|
+
"README.md": `# {{name}}
|
|
646
657
|
|
|
647
658
|
A full-stack API built with [CelsianJS](https://github.com/CelsianJs/celsian) — the fast, modular Node.js framework.
|
|
648
659
|
|
|
@@ -772,22 +783,16 @@ Add schemas to your routes for richer documentation:
|
|
|
772
783
|
\`\`\`typescript
|
|
773
784
|
import { Type } from '@sinclair/typebox';
|
|
774
785
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
},
|
|
786
|
-
},
|
|
787
|
-
handler(req, reply) {
|
|
788
|
-
// req.parsedBody is validated against the schema
|
|
789
|
-
return reply.status(201).json({ id: 1, ...req.parsedBody });
|
|
790
|
-
},
|
|
786
|
+
const CreateProductSchema = Type.Object({
|
|
787
|
+
name: Type.String(),
|
|
788
|
+
price: Type.Number({ minimum: 0 }),
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// parsedBody is fully typed — no cast needed!
|
|
792
|
+
app.post('/products', {
|
|
793
|
+
schema: { body: CreateProductSchema },
|
|
794
|
+
}, (req, reply) => {
|
|
795
|
+
return reply.status(201).json({ id: 1, name: req.parsedBody.name });
|
|
791
796
|
});
|
|
792
797
|
\`\`\`
|
|
793
798
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"full.js","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,kBAAkB;SACzB;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,eAAe,EAAE,QAAQ;YACzB,cAAc,EAAE,QAAQ;YACxB,cAAc,EAAE,QAAQ;YACxB,qBAAqB,EAAE,QAAQ;YAC/B,mBAAmB,EAAE,SAAS;SAC/B;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;YACb,MAAM,EAAE,QAAQ;YAChB,aAAa,EAAE,SAAS;SACzB;KACF,EACD,IAAI,EACJ,CAAC,CACF;IAED,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,GAAG,EAAE,CAAC,QAAQ,CAAC;YACf,KAAK,EAAE,CAAC,MAAM,CAAC;YACf,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,gCAAgC,EAAE,IAAI;YACtC,iBAAiB,EAAE,IAAI;YACvB,eAAe,EAAE,IAAI;YACrB,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IAED,cAAc,EAAE;;;;;;;;;;;;;CAajB;IAEC,YAAY,EAAE;;;;;CAKf;IAEC,uBAAuB;IACvB,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BjB;IAEC,kCAAkC;IAClC,yBAAyB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC5B;IAEC,8BAA8B;IAC9B,qBAAqB,EAAE
|
|
1
|
+
{"version":3,"file":"full.js","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;CA2F3B,CAAC"}
|
|
@@ -1,40 +1,57 @@
|
|
|
1
1
|
export const restApiTemplate = {
|
|
2
|
-
|
|
3
|
-
name:
|
|
4
|
-
version:
|
|
5
|
-
type:
|
|
2
|
+
"package.json": JSON.stringify({
|
|
3
|
+
name: "{{name}}",
|
|
4
|
+
version: "0.0.1",
|
|
5
|
+
type: "module",
|
|
6
6
|
scripts: {
|
|
7
|
-
dev:
|
|
8
|
-
build:
|
|
9
|
-
start:
|
|
7
|
+
dev: "npx tsx --watch src/index.ts",
|
|
8
|
+
build: "tsc",
|
|
9
|
+
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian:
|
|
13
|
-
|
|
12
|
+
celsian: "latest",
|
|
13
|
+
"@sinclair/typebox": "^0.34.0",
|
|
14
14
|
},
|
|
15
15
|
devDependencies: {
|
|
16
|
-
typescript:
|
|
17
|
-
tsx:
|
|
16
|
+
typescript: "^5.7.0",
|
|
17
|
+
tsx: "^4.0.0",
|
|
18
18
|
},
|
|
19
19
|
}, null, 2),
|
|
20
|
-
|
|
20
|
+
"tsconfig.json": JSON.stringify({
|
|
21
21
|
compilerOptions: {
|
|
22
|
-
target:
|
|
23
|
-
module:
|
|
24
|
-
moduleResolution:
|
|
22
|
+
target: "ES2022",
|
|
23
|
+
module: "ESNext",
|
|
24
|
+
moduleResolution: "bundler",
|
|
25
25
|
strict: true,
|
|
26
26
|
esModuleInterop: true,
|
|
27
27
|
skipLibCheck: true,
|
|
28
|
-
outDir:
|
|
29
|
-
rootDir:
|
|
28
|
+
outDir: "dist",
|
|
29
|
+
rootDir: "src",
|
|
30
30
|
},
|
|
31
|
-
include: [
|
|
31
|
+
include: ["src"],
|
|
32
32
|
}, null, 2),
|
|
33
|
-
|
|
33
|
+
"src/index.ts": `import { createApp, serve, cors, security } from 'celsian';
|
|
34
34
|
import { Type } from '@sinclair/typebox';
|
|
35
35
|
|
|
36
36
|
const app = createApp();
|
|
37
37
|
|
|
38
|
+
// ─── Security (CORS + security headers) ───
|
|
39
|
+
|
|
40
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? 'http://localhost:3000';
|
|
41
|
+
|
|
42
|
+
await app.register(cors({
|
|
43
|
+
origin: CORS_ORIGIN,
|
|
44
|
+
credentials: true,
|
|
45
|
+
maxAge: 86400,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
await app.register(security({
|
|
49
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
50
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// ─── Routes ───
|
|
54
|
+
|
|
38
55
|
const CreateUserSchema = Type.Object({
|
|
39
56
|
name: Type.String(),
|
|
40
57
|
email: Type.String({ format: 'email' }),
|
|
@@ -47,16 +64,13 @@ app.get('/users', (req, reply) => {
|
|
|
47
64
|
return reply.json(users);
|
|
48
65
|
});
|
|
49
66
|
|
|
50
|
-
app.
|
|
51
|
-
method: 'POST',
|
|
52
|
-
url: '/users',
|
|
67
|
+
app.post('/users', {
|
|
53
68
|
schema: { body: CreateUserSchema },
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
69
|
+
}, (req, reply) => {
|
|
70
|
+
const { name, email } = req.parsedBody;
|
|
71
|
+
const user = { id: nextId++, name, email };
|
|
72
|
+
users.push(user);
|
|
73
|
+
return reply.status(201).json(user);
|
|
60
74
|
});
|
|
61
75
|
|
|
62
76
|
app.get('/users/:id', (req, reply) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,mBAAmB,EAAE,SAAS;SAC/B;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;SACd;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDjB;CACA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-api.d.ts","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;
|
|
1
|
+
{"version":3,"file":"rpc-api.d.ts","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;CA+F1B,CAAC"}
|
|
@@ -1,42 +1,59 @@
|
|
|
1
1
|
export const rpcApiTemplate = {
|
|
2
|
-
|
|
3
|
-
name:
|
|
4
|
-
version:
|
|
5
|
-
type:
|
|
2
|
+
"package.json": JSON.stringify({
|
|
3
|
+
name: "{{name}}",
|
|
4
|
+
version: "0.0.1",
|
|
5
|
+
type: "module",
|
|
6
6
|
scripts: {
|
|
7
|
-
dev:
|
|
8
|
-
build:
|
|
9
|
-
start:
|
|
7
|
+
dev: "npx tsx --watch src/index.ts",
|
|
8
|
+
build: "tsc",
|
|
9
|
+
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian:
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
celsian: "latest",
|
|
13
|
+
"@celsian/rpc": "latest",
|
|
14
|
+
"@sinclair/typebox": "^0.34.0",
|
|
15
15
|
},
|
|
16
16
|
devDependencies: {
|
|
17
|
-
typescript:
|
|
18
|
-
tsx:
|
|
17
|
+
typescript: "^5.7.0",
|
|
18
|
+
tsx: "^4.0.0",
|
|
19
19
|
},
|
|
20
20
|
}, null, 2),
|
|
21
|
-
|
|
21
|
+
"tsconfig.json": JSON.stringify({
|
|
22
22
|
compilerOptions: {
|
|
23
|
-
target:
|
|
24
|
-
module:
|
|
25
|
-
moduleResolution:
|
|
23
|
+
target: "ES2022",
|
|
24
|
+
module: "ESNext",
|
|
25
|
+
moduleResolution: "bundler",
|
|
26
26
|
strict: true,
|
|
27
27
|
esModuleInterop: true,
|
|
28
28
|
skipLibCheck: true,
|
|
29
|
-
outDir:
|
|
30
|
-
rootDir:
|
|
29
|
+
outDir: "dist",
|
|
30
|
+
rootDir: "src",
|
|
31
31
|
},
|
|
32
|
-
include: [
|
|
32
|
+
include: ["src"],
|
|
33
33
|
}, null, 2),
|
|
34
|
-
|
|
34
|
+
"src/index.ts": `import { createApp, serve, cors, security } from 'celsian';
|
|
35
35
|
import { procedure, router, RPCHandler } from '@celsian/rpc';
|
|
36
36
|
import { Type } from '@sinclair/typebox';
|
|
37
37
|
|
|
38
38
|
const app = createApp();
|
|
39
39
|
|
|
40
|
+
// ─── Security (CORS + security headers) ───
|
|
41
|
+
|
|
42
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? 'http://localhost:3000';
|
|
43
|
+
|
|
44
|
+
await app.register(cors({
|
|
45
|
+
origin: CORS_ORIGIN,
|
|
46
|
+
credentials: true,
|
|
47
|
+
maxAge: 86400,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
await app.register(security({
|
|
51
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
52
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
// ─── Routes ───
|
|
56
|
+
|
|
40
57
|
const appRouter = router({
|
|
41
58
|
greeting: {
|
|
42
59
|
hello: procedure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-api.js","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"rpc-api.js","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,cAAc,EAAE,QAAQ;YACxB,mBAAmB,EAAE,SAAS;SAC/B;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;SACd;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDjB;CACA,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-celsian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-celsian": "dist/index.js"
|
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20"
|
|
16
|
+
},
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "tsc -b"
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
}
|
|
19
|
+
}
|
|
20
|
+
}
|