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 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
- # Temporary Holding Version
1
+ # create-kaelum
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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);