create-kaelum 0.0.0-stage → 2.0.0-alpha.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/README.md +35 -2
- package/package.json +40 -4
- package/src/create.js +184 -0
- package/src/index.js +96 -0
- package/src/utils.js +89 -0
- package/templates/js/api/app.js +44 -0
- package/templates/js/api/controllers/usersController.js +28 -0
- package/templates/js/api/middlewares/authMock.js +13 -0
- package/templates/js/api/package.json +22 -0
- package/templates/js/api/routes.js +43 -0
- package/templates/js/web/app.js +43 -0
- package/templates/js/web/controllers/pagesController.js +24 -0
- package/templates/js/web/middlewares/logger.js +7 -0
- package/templates/js/web/package.json +22 -0
- package/templates/js/web/public/style.css +63 -0
- package/templates/js/web/routes.js +31 -0
- package/templates/js/web/views/index.html +52 -0
- package/templates/ts/api/package.json +28 -0
- package/templates/ts/api/src/app.ts +44 -0
- package/templates/ts/api/src/controllers/usersController.ts +42 -0
- package/templates/ts/api/src/middlewares/authMock.ts +16 -0
- package/templates/ts/api/src/routes.ts +43 -0
- package/templates/ts/api/tsconfig.json +19 -0
- package/templates/ts/web/package.json +28 -0
- package/templates/ts/web/public/style.css +63 -0
- package/templates/ts/web/src/app.ts +43 -0
- package/templates/ts/web/src/controllers/pagesController.ts +29 -0
- package/templates/ts/web/src/middlewares/logger.ts +9 -0
- package/templates/ts/web/src/routes.ts +32 -0
- package/templates/ts/web/tsconfig.json +19 -0
- package/templates/ts/web/views/index.html +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matheus Messias
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-kaelum
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffold a new [Kaelum](https://github.com/kaelumjs/kaelum) project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create kaelum@latest
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Pass a name and a template to skip the questions:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm create kaelum@latest my-api -- --template ts-api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Templates
|
|
16
|
+
|
|
17
|
+
| Template | Description |
|
|
18
|
+
| :------- | :--------------------------------------------- |
|
|
19
|
+
| `js-web` | JavaScript, MVC with views and static files |
|
|
20
|
+
| `js-api` | JavaScript, REST API |
|
|
21
|
+
| `ts-web` | TypeScript, MVC with views and static files |
|
|
22
|
+
| `ts-api` | TypeScript, REST API |
|
|
23
|
+
|
|
24
|
+
## Options
|
|
25
|
+
|
|
26
|
+
| Option | Description |
|
|
27
|
+
| :---------------------- | :-------------------------------------------- |
|
|
28
|
+
| `-t, --template <name>` | Template to use |
|
|
29
|
+
| `--install` | Install dependencies without asking |
|
|
30
|
+
| `--no-install` | Skip installing dependencies without asking |
|
|
31
|
+
| `-h, --help` | Show help |
|
|
32
|
+
| `-v, --version` | Show the installed version |
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kaelum",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.0.0-alpha.0",
|
|
4
|
+
"description": "Scaffold a new Kaelum project.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-kaelum": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test",
|
|
15
|
+
"prepack": "node -e \"require('fs').copyFileSync('../../LICENSE','LICENSE')\""
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"kaelum",
|
|
19
|
+
"create",
|
|
20
|
+
"scaffold",
|
|
21
|
+
"cli",
|
|
22
|
+
"starter",
|
|
23
|
+
"express"
|
|
24
|
+
],
|
|
25
|
+
"author": "Matheus Campagnolo",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24.22.1"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/kaelumjs/kaelum.git",
|
|
33
|
+
"directory": "packages/create-kaelum"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/kaelumjs/kaelum/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://kaelumjs.matthcodes.dev",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"inquirer": "12.11.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/create.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const { spawn } = require("node:child_process");
|
|
3
|
+
const { copyTemplate, pathExists } = require("./utils");
|
|
4
|
+
const { version } = require("../package.json");
|
|
5
|
+
|
|
6
|
+
// kaelum and create-kaelum are released together with the same version, so a
|
|
7
|
+
// project scaffolded by this CLI depends on the matching kaelum release.
|
|
8
|
+
const kaelumVersion = `^${version}`;
|
|
9
|
+
|
|
10
|
+
const templatesDir = path.resolve(__dirname, "..", "templates");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* runInstall - runs `npm ci` if package-lock exists, otherwise `npm install`
|
|
14
|
+
* @param {string} cwd - target directory where install runs
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
*/
|
|
17
|
+
async function runInstall(cwd) {
|
|
18
|
+
const lockExists = await pathExists(path.join(cwd, "package-lock.json"));
|
|
19
|
+
const cmd = "npm";
|
|
20
|
+
const args = lockExists ? ["ci"] : ["install"];
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn(cmd, args, {
|
|
24
|
+
cwd,
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
shell: process.platform === "win32",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
child.on("error", reject);
|
|
30
|
+
child.on("exit", (code) => {
|
|
31
|
+
if (code === 0) return resolve();
|
|
32
|
+
reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ask only for what was not provided up front. Returns an empty object, and
|
|
39
|
+
* never loads the prompt library, when there is nothing to ask.
|
|
40
|
+
* @param {Object} defaults
|
|
41
|
+
*/
|
|
42
|
+
async function askMissing(defaults) {
|
|
43
|
+
const questions = [];
|
|
44
|
+
|
|
45
|
+
if (!defaults.projectName) {
|
|
46
|
+
questions.push({
|
|
47
|
+
type: "input",
|
|
48
|
+
name: "projectName",
|
|
49
|
+
message: "What is your project name?",
|
|
50
|
+
validate: (input) => (input ? true : "Project name cannot be empty."),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!defaults.language) {
|
|
55
|
+
questions.push({
|
|
56
|
+
type: "list",
|
|
57
|
+
name: "language",
|
|
58
|
+
message: "Choose your language:",
|
|
59
|
+
choices: [
|
|
60
|
+
{ name: "JavaScript", value: "js" },
|
|
61
|
+
{ name: "TypeScript", value: "ts" },
|
|
62
|
+
],
|
|
63
|
+
default: "js",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!defaults.template) {
|
|
68
|
+
questions.push({
|
|
69
|
+
type: "list",
|
|
70
|
+
name: "template",
|
|
71
|
+
message: "Choose your template:",
|
|
72
|
+
choices: [
|
|
73
|
+
{ name: "web — MVC with views & static files", value: "web" },
|
|
74
|
+
{ name: "api — REST API ready", value: "api" },
|
|
75
|
+
],
|
|
76
|
+
default: "web",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof defaults.autoInstall !== "boolean") {
|
|
81
|
+
questions.push({
|
|
82
|
+
type: "confirm",
|
|
83
|
+
name: "autoInstall",
|
|
84
|
+
message: "Install dependencies now? (recommended)",
|
|
85
|
+
default: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (questions.length === 0) return {};
|
|
90
|
+
|
|
91
|
+
const inquirer = require("inquirer");
|
|
92
|
+
return (inquirer.default || inquirer).prompt(questions);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* createProject - create project from template
|
|
97
|
+
* @param {Object} defaults - optional { projectName, language, template, autoInstall }
|
|
98
|
+
* @returns {Promise<boolean>} true when the project was created
|
|
99
|
+
*/
|
|
100
|
+
async function createProject(defaults = {}) {
|
|
101
|
+
console.log("🚀 Welcome to Kaelum!\n");
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const answers = await askMissing(defaults);
|
|
105
|
+
|
|
106
|
+
const projectName = defaults.projectName || answers.projectName;
|
|
107
|
+
const language = defaults.language || answers.language;
|
|
108
|
+
const template = defaults.template || answers.template;
|
|
109
|
+
const autoInstall =
|
|
110
|
+
typeof defaults.autoInstall === "boolean" ? defaults.autoInstall : answers.autoInstall;
|
|
111
|
+
|
|
112
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
113
|
+
const templateDir = path.join(templatesDir, language, template);
|
|
114
|
+
|
|
115
|
+
if (!(await pathExists(templateDir))) {
|
|
116
|
+
console.error(`\n❌ Template "${language}/${template}" not found.`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (await pathExists(targetDir)) {
|
|
121
|
+
console.error(
|
|
122
|
+
`\n❌ Folder "${projectName}" already exists. Choose a different name or delete the existing folder.`
|
|
123
|
+
);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Copy template, update package.json, generate .env and .gitignore
|
|
128
|
+
const isTypeScript = language === "ts";
|
|
129
|
+
const result = await copyTemplate(
|
|
130
|
+
templateDir,
|
|
131
|
+
targetDir,
|
|
132
|
+
path.basename(targetDir),
|
|
133
|
+
kaelumVersion
|
|
134
|
+
);
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
console.error(`\n❌ Error copying template: ${result.error}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Post-creation summary
|
|
141
|
+
const langLabel = isTypeScript ? "TypeScript" : "JavaScript";
|
|
142
|
+
console.log(`\n✅ Project "${projectName}" created successfully!`);
|
|
143
|
+
console.log(` Language: ${langLabel}`);
|
|
144
|
+
console.log(` Template: ${template}`);
|
|
145
|
+
console.log(`\n📁 cd ${projectName}`);
|
|
146
|
+
|
|
147
|
+
if (autoInstall) {
|
|
148
|
+
console.log("📦 Installing dependencies... (this may take a few minutes)\n");
|
|
149
|
+
try {
|
|
150
|
+
await runInstall(targetDir);
|
|
151
|
+
console.log("\n✅ Dependencies installed successfully!");
|
|
152
|
+
printNextSteps({ isTypeScript });
|
|
153
|
+
} catch (installErr) {
|
|
154
|
+
console.error(
|
|
155
|
+
"\n❌ Failed to install dependencies automatically:",
|
|
156
|
+
installErr.message || installErr
|
|
157
|
+
);
|
|
158
|
+
console.log(`➡️ Try manually: cd ${projectName} && npm install`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
printNextSteps({ isTypeScript, install: `cd ${projectName} && npm install` });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error("❌ Unexpected error:", err.message || err);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function printNextSteps({ isTypeScript, install }) {
|
|
172
|
+
console.log("\n📌 Next steps:");
|
|
173
|
+
if (install) console.log(` ${install}`);
|
|
174
|
+
if (isTypeScript) {
|
|
175
|
+
console.log(` npm run dev Start development server (tsx watch)`);
|
|
176
|
+
console.log(` npm run build Compile TypeScript to JavaScript`);
|
|
177
|
+
console.log(` npm start Run compiled output\n`);
|
|
178
|
+
} else {
|
|
179
|
+
console.log(` npm start Start the server`);
|
|
180
|
+
console.log(` npm run dev Start with file watching\n`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = { createProject };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { parseArgs } = require("node:util");
|
|
3
|
+
const { createProject } = require("./create");
|
|
4
|
+
const { version } = require("../package.json");
|
|
5
|
+
|
|
6
|
+
const TEMPLATES = {
|
|
7
|
+
"js-web": { language: "js", template: "web" },
|
|
8
|
+
"js-api": { language: "js", template: "api" },
|
|
9
|
+
"ts-web": { language: "ts", template: "web" },
|
|
10
|
+
"ts-api": { language: "ts", template: "api" },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Kept for backward compatibility with `kaelum create --template web|api`.
|
|
14
|
+
const TEMPLATE_ALIASES = { web: "js-web", api: "js-api" };
|
|
15
|
+
|
|
16
|
+
function printHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
create-kaelum v${version}
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
npm create kaelum@latest Interactive mode
|
|
22
|
+
npm create kaelum@latest <name> Ask for language and template
|
|
23
|
+
npm create kaelum@latest <name> -- --template <t> Skip the template questions
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
-t, --template <template> js-web, js-api, ts-web or ts-api (aliases: web, api)
|
|
27
|
+
--install Install dependencies without asking
|
|
28
|
+
--no-install Skip installing dependencies without asking
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
-v, --version Show the installed version
|
|
31
|
+
|
|
32
|
+
Templates:
|
|
33
|
+
js-web JavaScript + MVC with views & static files
|
|
34
|
+
js-api JavaScript + REST API
|
|
35
|
+
ts-web TypeScript + MVC with views & static files
|
|
36
|
+
ts-api TypeScript + REST API
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
npm create kaelum@latest my-app -- --template ts-web
|
|
40
|
+
npm create kaelum@latest my-api -- --template js-api --no-install
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse a template name such as "ts-api" into { language, template }.
|
|
46
|
+
* Returns null when the name is unknown.
|
|
47
|
+
*/
|
|
48
|
+
function parseTemplate(value) {
|
|
49
|
+
return TEMPLATES[TEMPLATE_ALIASES[value] || value] || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const { values, positionals } = parseArgs({
|
|
54
|
+
allowPositionals: true,
|
|
55
|
+
allowNegative: true,
|
|
56
|
+
options: {
|
|
57
|
+
template: { type: "string", short: "t" },
|
|
58
|
+
install: { type: "boolean" },
|
|
59
|
+
help: { type: "boolean", short: "h" },
|
|
60
|
+
version: { type: "boolean", short: "v" },
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (values.version) {
|
|
65
|
+
console.log(`create-kaelum v${version}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (values.help) {
|
|
70
|
+
printHelp();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const defaults = { projectName: positionals[0], autoInstall: values.install };
|
|
75
|
+
|
|
76
|
+
if (values.template) {
|
|
77
|
+
const parsed = parseTemplate(values.template);
|
|
78
|
+
if (!parsed) {
|
|
79
|
+
console.error(`Unknown template: "${values.template}"`);
|
|
80
|
+
console.error(
|
|
81
|
+
`Available templates: ${Object.keys(TEMPLATES).join(", ")} (or aliases: web, api)`
|
|
82
|
+
);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
Object.assign(defaults, parsed);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const created = await createProject(defaults);
|
|
90
|
+
if (!created) process.exitCode = 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch((err) => {
|
|
94
|
+
console.error("Error running create-kaelum:", err.message || err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ENV = `PORT=3000
|
|
5
|
+
NODE_ENV=development
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
const DEFAULT_GITIGNORE = `node_modules/
|
|
9
|
+
dist/
|
|
10
|
+
.env
|
|
11
|
+
*.log
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} target
|
|
16
|
+
* @returns {Promise<boolean>}
|
|
17
|
+
*/
|
|
18
|
+
async function pathExists(target) {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(target);
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Copy a template directory to target and update package.json with projectName.
|
|
29
|
+
* Also generates .env and .gitignore files.
|
|
30
|
+
* @param {string} sourceDir
|
|
31
|
+
* @param {string} targetDir
|
|
32
|
+
* @param {string} projectName (optional) - will override package.json name if present
|
|
33
|
+
* @param {string} kaelumVersion (optional) - range written as the `kaelum` dependency
|
|
34
|
+
*/
|
|
35
|
+
async function copyTemplate(sourceDir, targetDir, projectName, kaelumVersion) {
|
|
36
|
+
try {
|
|
37
|
+
if (!sourceDir || !targetDir) {
|
|
38
|
+
throw new Error("Source and target directories are required.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!(await pathExists(sourceDir))) {
|
|
42
|
+
throw new Error(`Template not found: ${sourceDir}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await fs.cp(sourceDir, targetDir, {
|
|
46
|
+
recursive: true,
|
|
47
|
+
force: false,
|
|
48
|
+
errorOnExist: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Try update package.json in the copied template
|
|
52
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
53
|
+
if (projectName && (await pathExists(pkgPath))) {
|
|
54
|
+
try {
|
|
55
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
56
|
+
// Set sensible defaults for created project
|
|
57
|
+
pkg.name = projectName;
|
|
58
|
+
if (!pkg.version) pkg.version = "0.1.0";
|
|
59
|
+
if (!pkg.description) pkg.description = `${projectName} - generated by create-kaelum`;
|
|
60
|
+
// Ensure type commonjs by default for templates that expect require/module.exports
|
|
61
|
+
if (!pkg.type) pkg.type = "commonjs";
|
|
62
|
+
// Never trust the range baked into the template: it goes stale.
|
|
63
|
+
if (kaelumVersion && pkg.dependencies && pkg.dependencies.kaelum) {
|
|
64
|
+
pkg.dependencies.kaelum = kaelumVersion;
|
|
65
|
+
}
|
|
66
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// Non-fatal: warn but continue
|
|
69
|
+
console.warn("Warning: failed to update package.json in the template:", e.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const envPath = path.join(targetDir, ".env");
|
|
74
|
+
if (!(await pathExists(envPath))) {
|
|
75
|
+
await fs.writeFile(envPath, DEFAULT_ENV, "utf8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
79
|
+
if (!(await pathExists(gitignorePath))) {
|
|
80
|
+
await fs.writeFile(gitignorePath, DEFAULT_GITIGNORE, "utf8");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { ok: true };
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return { ok: false, error: err.message || String(err) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { copyTemplate, pathExists };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const kaelum = require("kaelum");
|
|
2
|
+
|
|
3
|
+
const app = kaelum();
|
|
4
|
+
|
|
5
|
+
// Configure security and logging
|
|
6
|
+
app.setConfig({
|
|
7
|
+
cors: true,
|
|
8
|
+
helmet: true,
|
|
9
|
+
logs: true,
|
|
10
|
+
bodyParser: true,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Observability: X-Request-Id + Server-Timing headers
|
|
14
|
+
app.requestId();
|
|
15
|
+
app.timing();
|
|
16
|
+
|
|
17
|
+
// Semantic response helpers: res.ok(), res.created(), res.notFound(), ...
|
|
18
|
+
app.useResponseHelpers();
|
|
19
|
+
|
|
20
|
+
// ETag + conditional requests (304 Not Modified)
|
|
21
|
+
app.useEtag();
|
|
22
|
+
|
|
23
|
+
// Mount routes
|
|
24
|
+
const routes = require("./routes");
|
|
25
|
+
routes(app);
|
|
26
|
+
|
|
27
|
+
// Health check endpoint
|
|
28
|
+
app.healthCheck("/health");
|
|
29
|
+
|
|
30
|
+
// Graceful shutdown hook
|
|
31
|
+
app.onShutdown(() => {
|
|
32
|
+
console.log("Cleaning up resources...");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 405 for known paths with the wrong method, then 404 for everything else
|
|
36
|
+
app.useMethodNotAllowed();
|
|
37
|
+
app.notFound();
|
|
38
|
+
|
|
39
|
+
// Error handler (JSON responses)
|
|
40
|
+
app.useErrorHandler({ exposeStack: false });
|
|
41
|
+
|
|
42
|
+
// Start server
|
|
43
|
+
const PORT = process.env.PORT || 4000;
|
|
44
|
+
app.start(PORT);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// controllers/usersController.js
|
|
2
|
+
// Simple in-memory controller for Kaelum API demonstration
|
|
3
|
+
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, name: "Alice", role: "admin" },
|
|
6
|
+
{ id: 2, name: "Bob", role: "user" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// Uses res.ok / res.created / res.notFound from app.useResponseHelpers()
|
|
10
|
+
exports.list = (req, res) => res.ok(users);
|
|
11
|
+
|
|
12
|
+
exports.create = (req, res) => {
|
|
13
|
+
const newUser = { id: users.length + 1, ...req.body };
|
|
14
|
+
users.push(newUser);
|
|
15
|
+
res.created({ success: true, user: newUser });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.get = (req, res) => {
|
|
19
|
+
const user = users.find((u) => u.id === Number(req.params.id));
|
|
20
|
+
return user ? res.ok(user) : res.notFound("User not found");
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.posts = (req, res) => {
|
|
24
|
+
res.ok([
|
|
25
|
+
{ id: 101, title: "Kaelum Rocks", userId: req.params.id },
|
|
26
|
+
{ id: 102, title: "Recursive Routing", userId: req.params.id },
|
|
27
|
+
]);
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// middlewares/authMock.js
|
|
2
|
+
// Simple mock "authentication" middleware for demo purposes.
|
|
3
|
+
// Checks for header "x-api-key: secret" — if absent, returns 401.
|
|
4
|
+
|
|
5
|
+
module.exports = function (req, res, next) {
|
|
6
|
+
const key = req.headers["x-api-key"] || req.query.api_key;
|
|
7
|
+
if (!key || key !== "secret") {
|
|
8
|
+
return res
|
|
9
|
+
.status(401)
|
|
10
|
+
.json({ error: { message: "Unauthorized. Provide x-api-key: secret" } });
|
|
11
|
+
}
|
|
12
|
+
next();
|
|
13
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kaelum-api-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "REST API application generated by Kaelum CLI (api template)",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node app.js",
|
|
7
|
+
"dev": "node --watch app.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"kaelum",
|
|
11
|
+
"api"
|
|
12
|
+
],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=24.22.1"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"kaelum": "*"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// routes.js
|
|
2
|
+
const users = require("./controllers/usersController");
|
|
3
|
+
const auth = require("./middlewares/authMock");
|
|
4
|
+
const { validate } = require("kaelum/validate");
|
|
5
|
+
const { version } = require("./package.json");
|
|
6
|
+
|
|
7
|
+
// Request body validation for POST /users
|
|
8
|
+
const createUserSchema = validate({
|
|
9
|
+
body: {
|
|
10
|
+
name: { type: "string", required: true, min: 2, max: 50 },
|
|
11
|
+
role: { type: "string" },
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
module.exports = (app) => {
|
|
16
|
+
// Global middleware for /users path
|
|
17
|
+
app.setMiddleware("/users", (req, res, next) => {
|
|
18
|
+
// Require auth on POST requests
|
|
19
|
+
if (req.method === "POST") return auth(req, res, next);
|
|
20
|
+
next();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// RESTful nested routing example
|
|
24
|
+
app.apiRoute("users", {
|
|
25
|
+
get: users.list,
|
|
26
|
+
post: [createUserSchema, users.create],
|
|
27
|
+
|
|
28
|
+
// Nested parameter: /users/:id
|
|
29
|
+
"/:id": {
|
|
30
|
+
get: users.get,
|
|
31
|
+
|
|
32
|
+
// Nested resource: /users/:id/posts
|
|
33
|
+
"/posts": {
|
|
34
|
+
get: users.posts,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Metadata endpoint
|
|
40
|
+
app.addRoute("/meta", {
|
|
41
|
+
get: (req, res) => res.ok({ version, framework: "Kaelum" }),
|
|
42
|
+
});
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const kaelum = require("kaelum");
|
|
2
|
+
|
|
3
|
+
const app = kaelum();
|
|
4
|
+
|
|
5
|
+
// Configure security, static files, and body parsing
|
|
6
|
+
app.setConfig({
|
|
7
|
+
cors: true,
|
|
8
|
+
helmet: true,
|
|
9
|
+
static: "public",
|
|
10
|
+
bodyParser: true,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Observability: X-Request-Id + Server-Timing headers
|
|
14
|
+
app.requestId();
|
|
15
|
+
app.timing();
|
|
16
|
+
|
|
17
|
+
// Redirect /about/ -> /about
|
|
18
|
+
app.useTrimTrailingSlash();
|
|
19
|
+
|
|
20
|
+
// Register routes
|
|
21
|
+
const routes = require("./routes");
|
|
22
|
+
routes(app);
|
|
23
|
+
|
|
24
|
+
// Health check endpoint
|
|
25
|
+
app.healthCheck("/health");
|
|
26
|
+
|
|
27
|
+
// Graceful shutdown hook
|
|
28
|
+
app.onShutdown(() => {
|
|
29
|
+
console.log("Cleaning up resources...");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 405 for known paths with the wrong method, then a custom HTML 404
|
|
33
|
+
app.useMethodNotAllowed();
|
|
34
|
+
app.notFound((_req, res) => {
|
|
35
|
+
res.status(404).send("<h1>404</h1><p>Page not found.</p>");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Error handler (returns JSON on errors)
|
|
39
|
+
app.useErrorHandler({ exposeStack: false });
|
|
40
|
+
|
|
41
|
+
// Start server
|
|
42
|
+
const PORT = process.env.PORT || 3000;
|
|
43
|
+
app.start(PORT);
|