create-rebe 1.0.0 → 3.0.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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/template/.env.example +4 -0
  3. package/template/README.md +63 -8
  4. package/template/SECURITY.md +39 -0
  5. package/template/app/routes/api.route.js +1 -1
  6. package/template/app/routes/register.route.js +1 -1
  7. package/template/app/routes/web.route.js +1 -1
  8. package/template/app/socket/register.socket.js +0 -2
  9. package/template/config/express.config.js +8 -0
  10. package/template/core/common/string.js +1 -1
  11. package/template/core/cron.core.js +1 -0
  12. package/template/core/database.core.js +30 -17
  13. package/template/core/error.core.js +8 -4
  14. package/template/core/express.core.js +16 -4
  15. package/template/core/hooks.core.js +10 -7
  16. package/template/core/migrator.core.js +201 -0
  17. package/template/core/modules.core.js +167 -0
  18. package/template/core/queue.core.js +1 -0
  19. package/template/core/routing.core.d.ts +273 -0
  20. package/template/core/routing.core.js +666 -0
  21. package/template/core/seeder.core.js +105 -0
  22. package/template/core/socket.core.js +1 -0
  23. package/template/database/migrations/.gitkeep +0 -0
  24. package/template/database/seeders/.gitkeep +0 -0
  25. package/template/docs/Database.md +14 -8
  26. package/template/docs/Express.md +5 -2
  27. package/template/docs/Make.md +46 -0
  28. package/template/docs/Migration.md +56 -0
  29. package/template/docs/Modules.md +96 -0
  30. package/template/docs/README.md +5 -0
  31. package/template/docs/Routing.md +116 -0
  32. package/template/docs/Seeder.md +54 -0
  33. package/template/eslint.config.js +52 -0
  34. package/template/package-lock.json +1068 -70
  35. package/template/package.json +15 -8
  36. package/template/scripts/cli/args.js +39 -0
  37. package/template/scripts/cli/bootstrap.js +16 -0
  38. package/template/scripts/cli/db.js +79 -0
  39. package/template/scripts/cli/help.js +58 -0
  40. package/template/scripts/cli/keys.js +100 -0
  41. package/template/scripts/cli/log.js +58 -0
  42. package/template/scripts/cli/make.js +249 -0
  43. package/template/scripts/cli/names.js +51 -0
  44. package/template/scripts/cli/templates.js +358 -0
  45. package/template/scripts/cli.js +75 -234
  46. package/template/tests/http.test.js +99 -0
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ // Name + timestamp helpers shared by the scaffolding commands.
4
+
5
+ // Split an arbitrary identifier (PascalCase, camelCase, snake_case, kebab-case,
6
+ // "space separated") into lowercase words.
7
+ function words(input) {
8
+ return String(input)
9
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
10
+ .replace(/[_\-./\\]+/g, ' ')
11
+ .trim()
12
+ .split(/\s+/)
13
+ .filter(Boolean)
14
+ .map((w) => w.toLowerCase())
15
+ }
16
+
17
+ function toPascal(input) {
18
+ return words(input)
19
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
20
+ .join('')
21
+ }
22
+
23
+ function toSnake(input) {
24
+ return words(input).join('_')
25
+ }
26
+
27
+ function toKebab(input) {
28
+ return words(input).join('-')
29
+ }
30
+
31
+ // Naive English pluralizer — enough for table names; override with --table when wrong.
32
+ function pluralize(word) {
33
+ if (/[^aeiou]y$/i.test(word)) return word.replace(/y$/i, 'ies')
34
+ if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`
35
+ return `${word}s`
36
+ }
37
+
38
+ function tableName(input) {
39
+ const parts = words(input)
40
+ if (!parts.length) return ''
41
+ const last = parts.pop()
42
+ return [...parts, pluralize(last)].join('_')
43
+ }
44
+
45
+ // Filesystem timestamp prefix: YYYYMMDDHHmmss (local time).
46
+ function timestamp(date = new Date()) {
47
+ const pad = (n) => String(n).padStart(2, '0')
48
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`
49
+ }
50
+
51
+ module.exports = { words, toPascal, toSnake, toKebab, pluralize, tableName, timestamp }
@@ -0,0 +1,358 @@
1
+ 'use strict'
2
+
3
+ const { toPascal, toKebab } = require('./names')
4
+
5
+ // Scaffolding templates. Each returns a file's contents as a string. Output mirrors
6
+ // the hand-written examples in database/models and the framework's code style
7
+ // (tabs, single quotes, no semicolons).
8
+
9
+ function modelTemplate(name, table) {
10
+ const Model = toPascal(name)
11
+ return `'use strict'
12
+
13
+ // Sequelize model. database.core loads every file in database/models/*.js (and each
14
+ // module's models/ dir) when DB_ENABLED=true; files prefixed with "_" are ignored.
15
+ // The factory receives (sequelize, DataTypes) and returns the model; associations
16
+ // are wired in a second pass via the static associate(models).
17
+ module.exports = (sequelize, DataTypes) => {
18
+ const ${Model} = sequelize.define(
19
+ '${Model}',
20
+ {
21
+ id: { type: DataTypes.BIGINT.UNSIGNED, primaryKey: true, autoIncrement: true },
22
+ // TODO: declare columns
23
+ },
24
+ {
25
+ tableName: '${table}',
26
+ underscored: true,
27
+ },
28
+ )
29
+
30
+ ${Model}.associate = (models) => {
31
+ // e.g. ${Model}.belongsTo(models.User, { foreignKey: 'user_id', as: 'user' })
32
+ }
33
+
34
+ return ${Model}
35
+ }
36
+ `
37
+ }
38
+
39
+ function createMigrationTemplate(table) {
40
+ return `'use strict'
41
+
42
+ // Auto-generated create-table migration. Edit the column definitions to match the
43
+ // model. Run with: npm run cli -- db:migrate / roll back: db:rollback
44
+ module.exports = {
45
+ async up(queryInterface, Sequelize) {
46
+ await queryInterface.createTable(
47
+ '${table}',
48
+ {
49
+ id: { type: Sequelize.BIGINT.UNSIGNED, primaryKey: true, autoIncrement: true },
50
+ // TODO: declare columns to match the model
51
+ created_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') },
52
+ updated_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') },
53
+ },
54
+ { charset: 'utf8mb4' },
55
+ )
56
+ },
57
+
58
+ async down(queryInterface) {
59
+ await queryInterface.dropTable('${table}')
60
+ },
61
+ }
62
+ `
63
+ }
64
+
65
+ function blankMigrationTemplate(name) {
66
+ return `'use strict'
67
+
68
+ // Migration: ${name}
69
+ // up() applies the change, down() reverts it. queryInterface is the Sequelize
70
+ // QueryInterface; Sequelize exposes DataTypes (Sequelize.STRING, etc.).
71
+ module.exports = {
72
+ async up(queryInterface, Sequelize) {
73
+ // e.g. await queryInterface.addColumn('users', 'status', { type: Sequelize.STRING(20) })
74
+ },
75
+
76
+ async down(queryInterface, Sequelize) {
77
+ // e.g. await queryInterface.removeColumn('users', 'status')
78
+ },
79
+ }
80
+ `
81
+ }
82
+
83
+ function seederTemplate(name) {
84
+ return `'use strict'
85
+
86
+ // Seeder: ${name}
87
+ // Seeders are not tracked and may run repeatedly — write them idempotently
88
+ // (findOrCreate / upsert). Receives { sequelize, Database, Sequelize, models }.
89
+ // Run with: npm run cli -- db:seed --class=${name} / all: db:seed --all
90
+ module.exports = async ({ models, sequelize, Database }) => {
91
+ // const User = Database.model('User')
92
+ // await User.findOrCreate({ where: { email: 'admin@example.com' }, defaults: { name: 'Admin' } })
93
+ }
94
+ `
95
+ }
96
+
97
+ // Full module entry — a self-contained mini-app. Discovered by modules.core at
98
+ // runtime; the flat layout (database/models, app/routes…) keeps working alongside it.
99
+ // Dir fields (models/migrations/seeders/routes) are scanned; the function/object
100
+ // fields (middlewares/jobs/queue/socket/hooks) mirror the app/.../register.*.js shapes.
101
+ // Remove any field — and its file — for a layer this module does not need.
102
+ function moduleEntryTemplate(name) {
103
+ return `'use strict'
104
+
105
+ module.exports = {
106
+ name: '${name}',
107
+
108
+ // Scanned directories
109
+ models: './models', // (sequelize, DataTypes) factories
110
+ migrations: './migrations', // up/down migration files
111
+ seeders: './seeders', // seeder files
112
+ routes: './routes', // required for its routing side-effect
113
+
114
+ // Register hooks (same facades as app/.../register.*.js)
115
+ middlewares: require('./http/middlewares/register.middleware'), // (app) => {}
116
+ jobs: require('./jobs/register.job'), // (Cron) => {}
117
+ queue: require('./queue/register.queue'), // (Queue) => {}
118
+ socket: require('./socket/register.socket'), // (io, Socket) => {}
119
+ hooks: require('./hooks/register.hook'), // { before, after, shutdown }
120
+ }
121
+ `
122
+ }
123
+
124
+ // Global middleware register — (app) => {}. Mirrors app/http/middlewares/register.middleware.js.
125
+ function appMiddlewareTemplate(name) {
126
+ return `'use strict'
127
+
128
+ // Global middleware for the "${name}" module. Receives the Express app, applied
129
+ // during boot after the security/parsing stack.
130
+ module.exports = (app) => {
131
+ // app.use((req, res, next) => next())
132
+ }
133
+ `
134
+ }
135
+
136
+ function moduleRouteTemplate(name) {
137
+ return `'use strict'
138
+
139
+ // Routes for the "${name}" module. Uses the same Routes facade as app/routes.
140
+ const Routes = require('@core/routing.core')
141
+
142
+ Routes.group('${name}', () => {
143
+ Routes.get('', ({ res }) => {
144
+ res.json({ status: true, code: 200, message: '${name} module ready', data: null, meta: null })
145
+ })
146
+ })
147
+
148
+ module.exports = Routes
149
+ `
150
+ }
151
+
152
+ // ── App-layer scaffolds ──────────────────────────────────────────────────────
153
+
154
+ function controllerTemplate(name, { resource = false, api = false } = {}) {
155
+ const Ctrl = `${toPascal(name)}Controller`
156
+ const ok = (message, data = 'null') => `res.json({ status: true, code: 200, message: '${message}', data: ${data}, meta: null })`
157
+
158
+ if (!resource) {
159
+ return `'use strict'
160
+
161
+ // HTTP controller. Static methods receive the context { req, res } and return the
162
+ // manual response envelope. Wire it in a route file, e.g.:
163
+ // Routes.get('${toKebab(name)}', ${Ctrl}.index)
164
+ class ${Ctrl} {
165
+ static async index({ req, res }) {
166
+ return ${ok('OK', '[]')}
167
+ }
168
+ }
169
+
170
+ module.exports = ${Ctrl}
171
+ `
172
+ }
173
+
174
+ const methods = []
175
+ methods.push(` // GET /${toKebab(name)}
176
+ static async index({ req, res }) {
177
+ return ${ok('OK', '[]')}
178
+ }`)
179
+ if (!api) {
180
+ methods.push(` // GET /${toKebab(name)}/create
181
+ static async create({ req, res }) {
182
+ return ${ok('OK')}
183
+ }`)
184
+ }
185
+ methods.push(` // POST /${toKebab(name)}
186
+ static async store({ req, res }) {
187
+ return res.status(201).json({ status: true, code: 201, message: 'Created', data: req.body, meta: null })
188
+ }`)
189
+ methods.push(` // GET /${toKebab(name)}/:id
190
+ static async show({ req, res }) {
191
+ return ${ok('OK', '{ id: req.params.id }')}
192
+ }`)
193
+ if (!api) {
194
+ methods.push(` // GET /${toKebab(name)}/:id/edit
195
+ static async edit({ req, res }) {
196
+ return ${ok('OK', '{ id: req.params.id }')}
197
+ }`)
198
+ }
199
+ methods.push(` // PUT|PATCH /${toKebab(name)}/:id
200
+ static async update({ req, res }) {
201
+ return ${ok('Updated', '{ id: req.params.id, ...req.body }')}
202
+ }`)
203
+ methods.push(` // DELETE /${toKebab(name)}/:id
204
+ static async destroy({ req, res }) {
205
+ return ${ok('Deleted')}
206
+ }`)
207
+
208
+ return `'use strict'
209
+
210
+ // RESTful resource controller. Register every action at once with:
211
+ // Routes.${api ? 'apiResource' : 'resource'}('${toKebab(name)}', ${Ctrl})
212
+ // Only the actions present here are mounted; rename/remove as needed.
213
+ class ${Ctrl} {
214
+ ${methods.join('\n\n')}
215
+ }
216
+
217
+ module.exports = ${Ctrl}
218
+ `
219
+ }
220
+
221
+ function middlewareTemplate(name) {
222
+ const Mw = toPascal(name)
223
+ return `'use strict'
224
+
225
+ // Route middleware. Implements handle({ req, res, next, error }); call next() to pass
226
+ // control on, or send a response to short-circuit. Use per-route or as an alias:
227
+ // Routes.get('path', Controller.action, [${Mw}])
228
+ // Routes.registerMiddleware('${toKebab(name)}', ${Mw})
229
+ class ${Mw} {
230
+ static handle({ req, res, next }) {
231
+ // TODO: guard logic
232
+ next()
233
+ }
234
+ }
235
+
236
+ module.exports = ${Mw}
237
+ `
238
+ }
239
+
240
+ function validatorTemplate(name) {
241
+ const base = toPascal(name)
242
+ return `'use strict'
243
+
244
+ const { z } = require('zod')
245
+
246
+ // Zod schemas. Apply per-route via the validator core:
247
+ // Routes.post('${toKebab(name)}', ${base}Controller.store, [Validator.make(create${base}Schema)])
248
+ const create${base}Schema = z.object({
249
+ // name: z.string().min(1),
250
+ })
251
+
252
+ const update${base}Schema = create${base}Schema.partial()
253
+
254
+ module.exports = { create${base}Schema, update${base}Schema }
255
+ `
256
+ }
257
+
258
+ function routeTemplate(name) {
259
+ return moduleRouteTemplate(toKebab(name))
260
+ }
261
+
262
+ // Resource route file wiring a controller to Routes.resource/apiResource. The
263
+ // controllerRequire path differs for flat (@http alias) vs module (relative) layouts.
264
+ function resourceRouteTemplate(name, { api = false, controllerRequire } = {}) {
265
+ const Ctrl = `${toPascal(name)}Controller`
266
+ const slug = toKebab(name)
267
+ return `'use strict'
268
+
269
+ const Routes = require('@core/routing.core')
270
+ const ${Ctrl} = require('${controllerRequire}')
271
+
272
+ // Registers index/show/store/update/destroy${api ? '' : '/create/edit'} for any of those
273
+ // actions the controller implements.
274
+ Routes.${api ? 'apiResource' : 'resource'}('${slug}', ${Ctrl})
275
+
276
+ module.exports = Routes
277
+ `
278
+ }
279
+
280
+ function jobTemplate(name) {
281
+ return `'use strict'
282
+
283
+ const logger = require('@core/logger.core')
284
+
285
+ // Cron job module. Receives the Cron facade. Register with Cron.define(name, expr, fn).
286
+ // Expression: second? minute hour day-of-month month day-of-week
287
+ module.exports = (Cron) => {
288
+ Cron.define('${toKebab(name)}', '0 * * * *', async () => {
289
+ logger.debug('${toKebab(name)} tick')
290
+ })
291
+ }
292
+ `
293
+ }
294
+
295
+ function queueTemplate(name) {
296
+ return `'use strict'
297
+
298
+ const logger = require('@core/logger.core')
299
+
300
+ // Queue worker module. Receives the Queue facade. Register handlers with
301
+ // Queue.define(name, handler, options); dispatch jobs with Queue.dispatch(name, payload).
302
+ module.exports = (Queue) => {
303
+ Queue.define('${toKebab(name)}', async (payload, job) => {
304
+ logger.debug(\`${toKebab(name)} processing job \${job.id}\`)
305
+ })
306
+ }
307
+ `
308
+ }
309
+
310
+ function socketTemplate(name) {
311
+ return `'use strict'
312
+
313
+ const logger = require('@core/logger.core')
314
+
315
+ // Socket handlers. Receives (io, Socket). Register connection logic here.
316
+ module.exports = (io, Socket) => {
317
+ io.on('connection', (socket) => {
318
+ logger.debug(\`[${toKebab(name)}] socket connected: \${socket.id}\`)
319
+
320
+ socket.on('${toKebab(name)}:ping', (data, ack) => {
321
+ if (typeof ack === 'function') ack({ ok: true, data })
322
+ })
323
+ })
324
+ }
325
+ `
326
+ }
327
+
328
+ function hookTemplate() {
329
+ return `'use strict'
330
+
331
+ // Lifecycle hooks. before() runs before the server starts, after() once it is ready,
332
+ // and shutdown() during graceful shutdown. Each receives { config }.
333
+ module.exports = {
334
+ async before({ config }) {},
335
+ async after({ config }) {},
336
+ async shutdown({ config }) {},
337
+ }
338
+ `
339
+ }
340
+
341
+ module.exports = {
342
+ modelTemplate,
343
+ createMigrationTemplate,
344
+ blankMigrationTemplate,
345
+ seederTemplate,
346
+ moduleEntryTemplate,
347
+ moduleRouteTemplate,
348
+ controllerTemplate,
349
+ middlewareTemplate,
350
+ validatorTemplate,
351
+ routeTemplate,
352
+ jobTemplate,
353
+ queueTemplate,
354
+ socketTemplate,
355
+ hookTemplate,
356
+ resourceRouteTemplate,
357
+ appMiddlewareTemplate,
358
+ }