kreftjs 0.0.3-beta → 0.0.5-beta
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 +95 -0
- package/README.md +13 -13
- package/bin/add.js +11 -11
- package/bin/artisan-more.js +41 -41
- package/bin/artisan.js +41 -41
- package/bin/backend/command.js +1 -1
- package/bin/backend/factory.js +1 -1
- package/bin/backend/index.js +3 -3
- package/bin/backend/model.js +2 -2
- package/bin/backend/notifications.js +1 -1
- package/bin/backend/queue.js +2 -2
- package/bin/backend/schedule.js +2 -2
- package/bin/backend/seo-pages.js +1 -1
- package/bin/backend/storage.js +1 -1
- package/bin/backend/support.js +4 -4
- package/bin/backend/typescript.js +1 -1
- package/bin/build.js +11 -11
- package/bin/db-api.js +3 -3
- package/bin/db.js +1 -1
- package/bin/deps.js +6 -6
- package/bin/electron.js +11 -11
- package/bin/env.js +2 -2
- package/bin/guard.js +2 -2
- package/bin/kreftjs-config.js +1 -1
- package/bin/kreftjs.js +105 -105
- package/bin/layout.js +1 -1
- package/bin/no-auth.js +4 -4
- package/bin/prerender.js +4 -4
- package/bin/server-routes.js +2 -2
- package/bin/ssr.js +2 -2
- package/bin/terminal.js +1 -1
- package/bin/to-ts.js +1 -1
- package/bin/update.js +14 -14
- package/bin/urls-file.js +1 -1
- package/bin/vite.js +11 -11
- package/docs/Docs.kreftjs +1 -1
- package/docs/README.md +140 -123
- package/kreftjs.d.ts +1 -1
- package/kreftjs.js +18 -18
- package/package.json +2 -1
- package/templates/app/README.md +20 -20
- package/templates/app/backend/database/migrations/0001_01_01_000000_create_users_table.js +1 -1
- package/templates/app/backend/database/migrations/0001_01_01_000002_create_jobs_table.js +1 -1
- package/templates/app/backend/database/seeders/DatabaseSeeder.js +1 -1
- package/templates/app/backend/routes/api.js +1 -1
- package/templates/app/backend/routes/console.js +2 -2
- package/templates/app/frontend/errors/ErrorMonitor.kreftjs +2 -2
- package/templates/app/frontend/index.html +1 -1
- package/templates/app/frontend/kreftjs.d.ts +1 -1
- package/templates/app/frontend/kreftjs.js +18 -18
- package/templates/app/frontend/pages/Dashboard.kreftjs +1 -1
- package/templates/app/frontend/pages/ErrorMonitor.kreftjs +1 -1
- package/templates/app/frontend/pages/Home.kreftjs +2 -2
- package/templates/errors/ErrorMonitor.kreftjs +1 -1
package/bin/artisan.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The backend commands:
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
4
|
+
* kreft migrate run new migrations
|
|
5
|
+
* kreft migrate:rollback [--step 2] undo the last batch (or the last 2 migrations)
|
|
6
|
+
* kreft migrate:fresh [--seed] drop every table, migrate again (and seed)
|
|
7
|
+
* kreft migrate:status which migrations ran
|
|
8
|
+
* kreft db:seed [--class PostSeeder] run database/seeders/DatabaseSeeder.js (or another seeder)
|
|
9
|
+
* kreft route:list the routes in routes/api.js
|
|
10
|
+
* kreft make:model Post [-m -f -s -c -p -r] [--route=file] [--auth] [--all]
|
|
11
|
+
* kreft make:controller PostController [--api] [--model Post] [--route=file] [--auth]
|
|
12
|
+
* kreft make:page Post [--model Post] [--route=file] [--auth]
|
|
13
|
+
* kreft make:migration create_posts_table
|
|
14
|
+
* kreft make:seeder PostSeeder
|
|
15
|
+
* kreft make:factory PostFactory
|
|
16
|
+
* kreft make:middleware EnsureIsAdmin
|
|
17
17
|
*
|
|
18
18
|
* Every command takes --db <url> (or DATABASE_URL in .env) and --backend <folder> (default: the project).
|
|
19
19
|
*/
|
|
@@ -168,13 +168,13 @@ async function runSeed(db, b, ctx, className) {
|
|
|
168
168
|
// an older project: database/seed.sql
|
|
169
169
|
const sql = path.join(ctx.base, 'database', 'seed.sql');
|
|
170
170
|
if (!className && fs.existsSync(sql)) { await db.exec(fs.readFileSync(sql, 'utf8')); return console.log(' Seeded from database/seed.sql'); }
|
|
171
|
-
console.log(` No database/seeders/${className || 'DatabaseSeeder'}.js. Make one with:
|
|
171
|
+
console.log(` No database/seeders/${className || 'DatabaseSeeder'}.js. Make one with: kreft make:seeder ${className || 'DatabaseSeeder'}`);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/* ------------------------------------------------------------------ make:… */
|
|
175
175
|
const pascal = (s) => s.replace(/(^|[-_\s])(\w)/g, (_, __, c) => c.toUpperCase()).replace(/[^\w]/g, '');
|
|
176
176
|
// 'User/ProfileController', 'user\\profile-controller' or 'User.ProfileController' → { dirs: ['User'], name: 'ProfileController' }
|
|
177
|
-
// (folders are made when they don't exist yet, e.g.
|
|
177
|
+
// (folders are made when they don't exist yet, e.g. kreft make:controller User/ProfileController)
|
|
178
178
|
function nested(raw) {
|
|
179
179
|
const parts = String(raw || '').split(/[\\/]+/).map((p) => pascal(p)).filter(Boolean);
|
|
180
180
|
const name = parts.pop() || '';
|
|
@@ -298,7 +298,7 @@ module.exports = ${name}
|
|
|
298
298
|
migration: (name) => {
|
|
299
299
|
const create = name.match(/^create_(\w+?)_table$/);
|
|
300
300
|
const change = name.match(/^\w+_(?:to|from|in)_(\w+?)_table$/);
|
|
301
|
-
if (create) return `//
|
|
301
|
+
if (create) return `// kreft migrate runs up(); kreft migrate:rollback runs down()
|
|
302
302
|
module.exports = {
|
|
303
303
|
up(schema) {
|
|
304
304
|
schema.create('${create[1]}', (table) => {
|
|
@@ -313,7 +313,7 @@ module.exports = {
|
|
|
313
313
|
}
|
|
314
314
|
`;
|
|
315
315
|
const t = change ? change[1] : 'table_name';
|
|
316
|
-
return `//
|
|
316
|
+
return `// kreft migrate runs up(); kreft migrate:rollback runs down()
|
|
317
317
|
module.exports = {
|
|
318
318
|
up(schema) {
|
|
319
319
|
schema.table('${t}', (table) => {
|
|
@@ -331,7 +331,7 @@ module.exports = {
|
|
|
331
331
|
},
|
|
332
332
|
seeder: (name, model, modelReq) => `const { Seeder, DB } = require('kreftjs/server')
|
|
333
333
|
${model ? `const ${model} = require('${modelReq || '../../app/Models/' + model}')\n` : ''}
|
|
334
|
-
//
|
|
334
|
+
// kreft db:seed --class ${name} (or this.call(${name}) from DatabaseSeeder)
|
|
335
335
|
class ${name} extends Seeder {
|
|
336
336
|
async run() {
|
|
337
337
|
${model ? ` // await ${model}.factory().count(10).create()\n // await ${model}.create({ … })\n` : name === 'DatabaseSeeder' ? ' // await this.call([UserSeeder, PostSeeder])\n' : " // await DB.table('posts').insert({ … })\n"} }
|
|
@@ -1186,15 +1186,15 @@ const make = {
|
|
|
1186
1186
|
model(args) {
|
|
1187
1187
|
const o = options(args), ctx = context(o);
|
|
1188
1188
|
let { dirs, name, rel } = nested(o._[0]);
|
|
1189
|
-
if (!name) return console.error('
|
|
1189
|
+
if (!name) return console.error(' kreft make:model Post [-m -f -s -c -p -r] [--route=file] [--auth] [--all] [--folder=Admin] (Shop/Product: app/Models/Shop/Product.js)');
|
|
1190
1190
|
const all = o.flags.has('a') || o.flags.has('all');
|
|
1191
1191
|
const auth = o.flags.has('auth');
|
|
1192
1192
|
const hasRoute = all || o.flags.has('r') || o.flags.has('route') || Boolean(o.route);
|
|
1193
1193
|
|
|
1194
1194
|
// Support placing all auxiliary files (controllers, pages, routes) in a specific folder [except model]:
|
|
1195
|
-
// e.g.
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1195
|
+
// e.g. kreft make:model Post --all --folder=Admin
|
|
1196
|
+
// kreft make:model Post --all Admin
|
|
1197
|
+
// kreft make:model Admin/Post --all --flat-model
|
|
1198
1198
|
const targetFolder = o.folder || o.subfolder || o['in'] || o.group || o.path || (o._[1] && !o._[1].startsWith('-') ? o._[1] : null);
|
|
1199
1199
|
const flatModel = o.flags.has('flat-model') || o.flags.has('except-model') || o.flags.has('no-model-dir') || Boolean(targetFolder);
|
|
1200
1200
|
|
|
@@ -1222,7 +1222,7 @@ const make = {
|
|
|
1222
1222
|
controller(args) {
|
|
1223
1223
|
const o = options(args), ctx = context(o);
|
|
1224
1224
|
let { dirs, name } = nested(o._[0]);
|
|
1225
|
-
if (!name) return console.error('
|
|
1225
|
+
if (!name) return console.error(' kreft make:controller PostController [--api] [--model Post] [--route=file] [--auth]');
|
|
1226
1226
|
if (!/Controller$/.test(name)) name += 'Controller';
|
|
1227
1227
|
const m = o.model ? nested(o.model) : o.flags.has('api') || o.flags.has('resource') ? { dirs: [], name: name.replace(/Controller$/, '') } : null;
|
|
1228
1228
|
const model = m ? m.name : null;
|
|
@@ -1250,7 +1250,7 @@ const make = {
|
|
|
1250
1250
|
page(args) {
|
|
1251
1251
|
const o = options(args), ctx = context(o);
|
|
1252
1252
|
let { dirs, name } = nested(o._[0]);
|
|
1253
|
-
if (!name) return console.error('
|
|
1253
|
+
if (!name) return console.error(' kreft make:page Post [--model Post] [--route=file] [--auth]');
|
|
1254
1254
|
const m = o.model ? nested(o.model) : { dirs, name };
|
|
1255
1255
|
const modelDirs = m.dirs && m.dirs.length ? m.dirs : dirs;
|
|
1256
1256
|
const endpoint = [...(modelDirs.length ? modelDirs.map((d) => snake(d).replace(/_/g, '-')) : []), plural(snake(m.name)).replace(/_/g, '-')].join('/');
|
|
@@ -1270,7 +1270,7 @@ const make = {
|
|
|
1270
1270
|
factory(args) {
|
|
1271
1271
|
const o = options(args), ctx = context(o);
|
|
1272
1272
|
let { dirs, name } = nested(o._[0]);
|
|
1273
|
-
if (!name) return console.error('
|
|
1273
|
+
if (!name) return console.error(' kreft make:factory PostFactory [--model Post]');
|
|
1274
1274
|
if (!name.endsWith('Factory')) name += 'Factory';
|
|
1275
1275
|
const m = o.model ? nested(o.model) : { dirs: [], name: name.replace(/Factory$/, '') };
|
|
1276
1276
|
const modelReq = '../../app/Models/' + [...m.dirs, m.name].join('/');
|
|
@@ -1279,14 +1279,14 @@ const make = {
|
|
|
1279
1279
|
migration(args) {
|
|
1280
1280
|
const o = options(args), ctx = context(o);
|
|
1281
1281
|
const name = snake(o._[0] || '').replace(/[^a-z0-9_]/g, '_');
|
|
1282
|
-
if (!name) return console.error('
|
|
1282
|
+
if (!name) return console.error(' kreft make:migration create_posts_table');
|
|
1283
1283
|
const dir = migrationsOf(o, ctx);
|
|
1284
1284
|
write(path.join(dir, `${nextStamp(dir)}_${name}.js`), stubs.migration(name));
|
|
1285
1285
|
},
|
|
1286
1286
|
seeder(args) {
|
|
1287
1287
|
const o = options(args), ctx = context(o);
|
|
1288
1288
|
let { dirs, name } = nested(o._[0]);
|
|
1289
|
-
if (!name) return console.error('
|
|
1289
|
+
if (!name) return console.error(' kreft make:seeder PostSeeder');
|
|
1290
1290
|
if (!/Seeder$/.test(name)) name += 'Seeder';
|
|
1291
1291
|
const m = o.model ? nested(o.model) : null;
|
|
1292
1292
|
const modelReq = m ? up(2 + dirs.length) + ['app', 'Models', ...m.dirs, m.name].join('/') : null;
|
|
@@ -1295,14 +1295,14 @@ const make = {
|
|
|
1295
1295
|
middleware(args) {
|
|
1296
1296
|
const o = options(args), ctx = context(o);
|
|
1297
1297
|
const { dirs, name } = nested(o._[0]);
|
|
1298
|
-
if (!name) return console.error('
|
|
1298
|
+
if (!name) return console.error(' kreft make:middleware EnsureIsAdmin');
|
|
1299
1299
|
write(path.join(ctx.base, 'app', 'Http', 'Middleware', ...dirs, name + '.js'), stubs.middleware(name));
|
|
1300
1300
|
if (dirs.length) console.log(dim(` In a folder, give it a name in app/Http/Kernel.js: aliases: { '${snake(name).replace(/_/g, '-')}': 'Middleware/${[...dirs, name].join('/')}.js' }`));
|
|
1301
1301
|
},
|
|
1302
1302
|
request(args) {
|
|
1303
1303
|
const o = options(args), ctx = context(o);
|
|
1304
1304
|
let { dirs, name } = nested(o._[0]);
|
|
1305
|
-
if (!name) return console.error('
|
|
1305
|
+
if (!name) return console.error(' kreft make:request StorePostRequest [--model Post]');
|
|
1306
1306
|
if (!/Request$/.test(name)) name += 'Request';
|
|
1307
1307
|
const reqDir = path.join(ctx.base, 'app', 'Http', 'Requests');
|
|
1308
1308
|
if (!fs.existsSync(reqDir)) fs.mkdirSync(reqDir, { recursive: true });
|
|
@@ -1317,7 +1317,7 @@ const make = {
|
|
|
1317
1317
|
resource(args) {
|
|
1318
1318
|
const o = options(args), ctx = context(o);
|
|
1319
1319
|
let { dirs, name } = nested(o._[0]);
|
|
1320
|
-
if (!name) return console.error('
|
|
1320
|
+
if (!name) return console.error(' kreft make:resource PostResource');
|
|
1321
1321
|
if (!/Resource$/.test(name)) name += 'Resource';
|
|
1322
1322
|
const resDir = path.join(ctx.base, 'app', 'Http', 'Resources');
|
|
1323
1323
|
if (!fs.existsSync(resDir)) fs.mkdirSync(resDir, { recursive: true });
|
|
@@ -1331,7 +1331,7 @@ const make = {
|
|
|
1331
1331
|
rule(args) {
|
|
1332
1332
|
const o = options(args), ctx = context(o);
|
|
1333
1333
|
let { dirs, name } = nested(o._[0]);
|
|
1334
|
-
if (!name) return console.error('
|
|
1334
|
+
if (!name) return console.error(' kreft make:rule Uppercase');
|
|
1335
1335
|
const ruleDir = path.join(ctx.base, 'app', 'Rules');
|
|
1336
1336
|
if (!fs.existsSync(ruleDir)) fs.mkdirSync(ruleDir, { recursive: true });
|
|
1337
1337
|
const isTs = isTsProject(path.join(ruleDir, ...dirs, name + '.js'));
|
|
@@ -1345,7 +1345,7 @@ const make = {
|
|
|
1345
1345
|
const o = options(args), ctx = context(o);
|
|
1346
1346
|
const locale = (o._[0] || '').toLowerCase().trim();
|
|
1347
1347
|
if (!locale || /[\/\\]|\.\./.test(locale)) {
|
|
1348
|
-
console.error('
|
|
1348
|
+
console.error(' kreft make:lang <locale> (e.g. kreft make:lang ar)');
|
|
1349
1349
|
return;
|
|
1350
1350
|
}
|
|
1351
1351
|
// Place in project root lang/ (matching standard Laravel 9/10/11) or backend/lang/ if already existing
|
|
@@ -1494,7 +1494,7 @@ const make = {
|
|
|
1494
1494
|
console.log(dim(` Use in frontend: { __('auth.failed') } or { t('Sign In') }`));
|
|
1495
1495
|
},
|
|
1496
1496
|
};
|
|
1497
|
-
//
|
|
1497
|
+
// kreft make:errors — the error pages (errors/404.kreftjs, 403, 500…)
|
|
1498
1498
|
make.errors = function (args) {
|
|
1499
1499
|
const o = options(args), ctx = context(o);
|
|
1500
1500
|
const from = path.join(__dirname, '..', 'templates', 'errors');
|
|
@@ -1511,12 +1511,12 @@ make.errors = function (args) {
|
|
|
1511
1511
|
}
|
|
1512
1512
|
};
|
|
1513
1513
|
|
|
1514
|
-
//
|
|
1515
|
-
//
|
|
1514
|
+
// kreft make:routes admin → frontend/routes/admin.kreftjs (pages), listed in Kreftjs.router({ routes: […] }) in index.html
|
|
1515
|
+
// kreft make:routes admin --api → backend/routes/admin.js (API), loaded by routes/api.js
|
|
1516
1516
|
make.routes = function (args) {
|
|
1517
1517
|
const o = options(args), ctx = context(o);
|
|
1518
1518
|
const name = String(o._[0] || '').replace(/\.(kreftjs|kreft|som|js)$/, '').replace(/[^\w-]/g, '');
|
|
1519
|
-
if (!name) return console.error('
|
|
1519
|
+
if (!name) return console.error(' kreft make:routes admin (pages)\n kreft make:routes admin --api (API)');
|
|
1520
1520
|
if (o.flags.has('api')) {
|
|
1521
1521
|
const file = path.join(ctx.base, 'routes', name + '.js');
|
|
1522
1522
|
if (!write(file, stubs.apiRoutes(name))) return;
|
|
@@ -1549,12 +1549,12 @@ make.routes = function (args) {
|
|
|
1549
1549
|
console.log(` ${green('UPDATED')} ${path.relative(process.cwd(), index)} (${entry} in Kreftjs.router)`);
|
|
1550
1550
|
};
|
|
1551
1551
|
|
|
1552
|
-
//
|
|
1553
|
-
//
|
|
1552
|
+
// kreft make:admin you@example.com → that user may open the admin panel (/admin)
|
|
1553
|
+
// kreft make:admin you@example.com --remove
|
|
1554
1554
|
commands['make:admin'] = async function (args) {
|
|
1555
1555
|
const o = options(args), ctx = context(o);
|
|
1556
1556
|
const email = String(o._[0] || '').trim().toLowerCase(); // Register keeps emails in lowercase
|
|
1557
|
-
if (!email) return console.error('
|
|
1557
|
+
if (!email) return console.error(' kreft make:admin you@example.com (--remove takes the rights away)');
|
|
1558
1558
|
await withDb(o, ctx, async (db) => {
|
|
1559
1559
|
const value = o.flags.has('remove') ? 0 : 1;
|
|
1560
1560
|
const r = await db.run(`UPDATE ${db.q('users')} SET ${db.q('is_admin')} = ? WHERE LOWER(${db.q('email')}) = ?`, [db.kind === 'postgres' ? Boolean(value) : value, email]);
|
|
@@ -1615,8 +1615,8 @@ async function removeModel(args) {
|
|
|
1615
1615
|
}
|
|
1616
1616
|
|
|
1617
1617
|
if (!targetModelName) {
|
|
1618
|
-
console.error(' Usage:
|
|
1619
|
-
console.error('
|
|
1618
|
+
console.error(' Usage: kreft remove:model <Name> [--all] [--controller] [--request] [--route] [--migration] [--seeder] [--factory] [--page] [--folder=Folder]');
|
|
1619
|
+
console.error(' kreft remove model <Name> [options]');
|
|
1620
1620
|
console.error(' kreft:remove model <Name> [options]');
|
|
1621
1621
|
return;
|
|
1622
1622
|
}
|
package/bin/backend/command.js
CHANGED
package/bin/backend/factory.js
CHANGED
|
@@ -144,7 +144,7 @@ class Factory {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
if (!def) throw new Error(`No factory for ${Model && Model.name ? Model.name : 'Model'}. Make one:
|
|
147
|
+
if (!def) throw new Error(`No factory for ${Model && Model.name ? Model.name : 'Model'}. Make one: kreft make:factory ${(Model && Model.name) || 'Model'}Factory`);
|
|
148
148
|
return new Factory(def);
|
|
149
149
|
}
|
|
150
150
|
constructor(def) {
|
package/bin/backend/index.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* app/Http/Controllers/PostController.js
|
|
6
6
|
* app/Http/Middleware/Authenticate.js 'auth' in a route finds Authenticate.js (or Auth.js)
|
|
7
7
|
* app/Models/Post.js
|
|
8
|
-
* database/migrations/*.js
|
|
9
|
-
* database/seeders/DatabaseSeeder.js
|
|
8
|
+
* database/migrations/*.js kreft migrate, kreft migrate:rollback, kreft migrate:fresh --seed
|
|
9
|
+
* database/seeders/DatabaseSeeder.js kreft db:seed
|
|
10
10
|
*
|
|
11
11
|
* In your files: const { Route, Model, DB, abort, response } = require('kreftjs/server')
|
|
12
12
|
*/
|
|
@@ -171,7 +171,7 @@ function createBackend(baseDir, { db = null, production = false, log = console,
|
|
|
171
171
|
const known = { auth: 'Authenticate', guest: 'RedirectIfAuthenticated', verified: 'EnsureEmailIsVerified', 'password.confirm': 'RequirePassword', admin: 'EnsureIsAdmin', role: 'EnsureHasRole', abilities: 'CheckAbilities', ability: 'CheckForAnyAbility' }[name];
|
|
172
172
|
const names = [name, p, p + 'Middleware', known].filter(Boolean);
|
|
173
173
|
const file = names.map((n) => findFile(path.join(dir, n))).find(Boolean);
|
|
174
|
-
if (!file) throw new Error(`No middleware "${name}". Make it with:
|
|
174
|
+
if (!file) throw new Error(`No middleware "${name}". Make it with: kreft make:middleware ${p}`);
|
|
175
175
|
if (!isInsideDir(file, path.resolve(baseDir, 'app'))) throw new Error(`Forbidden middleware path.`);
|
|
176
176
|
return { fn: await loadModuleAsync(file), args };
|
|
177
177
|
}
|
package/bin/backend/model.js
CHANGED
|
@@ -61,7 +61,7 @@ class ModelNotFoundError extends Error {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// where the project's backend is (for factories); set by
|
|
64
|
+
// where the project's backend is (for factories); set by kreft dev / kreft serve / the kreftjs commands
|
|
65
65
|
let baseDir = null;
|
|
66
66
|
const setBaseDir = (dir) => { baseDir = dir; };
|
|
67
67
|
const getBaseDir = () => baseDir;
|
|
@@ -454,7 +454,7 @@ class Model {
|
|
|
454
454
|
if ((await fire(C, 'saving', this)) === false) return false;
|
|
455
455
|
if ((await fire(C, creating ? 'creating' : 'updating', this)) === false) return false;
|
|
456
456
|
if (!(await tableExists(C.table))) {
|
|
457
|
-
throw new Error(`Table "${C.table}" does not exist in the database. Run "
|
|
457
|
+
throw new Error(`Table "${C.table}" does not exist in the database. Run "kreft migrate" to create it.`);
|
|
458
458
|
}
|
|
459
459
|
const cols = await columnsOf(C.table);
|
|
460
460
|
const has = (c) => cols.some((x) => x.name === c);
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* await user.notify(new InvoicePaid(invoice))
|
|
18
18
|
* await Notification.send(users, new InvoicePaid(invoice))
|
|
19
|
-
* const list = await user.notifications().get() // the notifications table (
|
|
19
|
+
* const list = await user.notifications().get() // the notifications table (kreft notifications:table)
|
|
20
20
|
* await user.unreadNotifications().update({ read_at: new Date() })
|
|
21
21
|
*/
|
|
22
22
|
const crypto = require('crypto');
|
package/bin/backend/queue.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* await dispatch(new SendWelcomeEmail(user), { delay: 60 }) // in a minute
|
|
16
16
|
* await dispatchSync(new SendWelcomeEmail(user)) // now
|
|
17
17
|
*
|
|
18
|
-
* QUEUE_CONNECTION in .env: database (the jobs table;
|
|
18
|
+
* QUEUE_CONNECTION in .env: database (the jobs table; kreft dev and `kreft queue:work` run them) or sync (at once).
|
|
19
19
|
*/
|
|
20
20
|
const crypto = require('crypto');
|
|
21
21
|
const fs = require('fs');
|
|
@@ -377,7 +377,7 @@ async function workOne({ queues = ['default'], log = () => {} } = {}) {
|
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
//
|
|
380
|
+
// kreft queue:work — runs jobs as they come (stop with Ctrl+C)
|
|
381
381
|
async function work({ queues = ['default'], once = false, sleep = 1, maxJobs = Infinity, log = console.log, stopWhenEmpty = false } = {}) {
|
|
382
382
|
const workerStartedAt = Date.now();
|
|
383
383
|
let n = 0;
|
package/bin/backend/schedule.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Schedule.command('cache:clear').hourly()
|
|
8
8
|
* Schedule.call(fn).cron('* /15 * * * *') (no space: every 15 minutes)
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* kreft dev runs them while it is on; for a real server: kreft schedule:work (or `kreft schedule:run` from cron each minute).
|
|
11
11
|
*/
|
|
12
12
|
const tasks = [];
|
|
13
13
|
|
|
@@ -96,7 +96,7 @@ function loadSchedule(baseDir) {
|
|
|
96
96
|
require(file);
|
|
97
97
|
return file;
|
|
98
98
|
}
|
|
99
|
-
//
|
|
99
|
+
// kreft dev / kreftjs schedule:work: check every minute, on the minute
|
|
100
100
|
function startScheduler(baseDir, log = console.log) {
|
|
101
101
|
let timer = null;
|
|
102
102
|
const tick = async () => {
|
package/bin/backend/seo-pages.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* if (!post) return null // answered as 404 (and noindex)
|
|
13
13
|
* return { title: post.title, description: post.excerpt, og: { image: post.image }, schema: { '@type': 'Article', headline: post.title } }
|
|
14
14
|
* },
|
|
15
|
-
* // the addresses of your data pages, for `
|
|
15
|
+
* // the addresses of your data pages, for `kreft sitemap`: '/path' or { path, lastmod }
|
|
16
16
|
* urls: async () => (await Post.all()).map((p) => ({ path: '/posts/' + p.slug, lastmod: p.updated_at })),
|
|
17
17
|
* }
|
|
18
18
|
*
|
package/bin/backend/storage.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* const data = await req.validate({ avatar: 'required|image|max:2048' }) // KB
|
|
6
6
|
* const file = req.file('avatar') // { name, size, type, extension() }
|
|
7
7
|
* const stored = await file.store('avatars') // 'avatars/3f9c…e1.jpg' on the public disk
|
|
8
|
-
* Storage.url(stored) // '/storage/avatars/3f9c…e1.jpg' —
|
|
8
|
+
* Storage.url(stored) // '/storage/avatars/3f9c…e1.jpg' — kreft dev and kreft serve send it
|
|
9
9
|
*
|
|
10
10
|
* await Storage.put('reports/today.txt', 'hello') // the local disk: backend/storage/app (private)
|
|
11
11
|
* await Storage.get('reports/today.txt') Storage.exists(…) Storage.delete(…) Storage.files('reports')
|
package/bin/backend/support.js
CHANGED
|
@@ -98,11 +98,11 @@ function appKey() {
|
|
|
98
98
|
if (k) {
|
|
99
99
|
const buf = k.startsWith('base64:') ? Buffer.from(k.slice(7), 'base64') : Buffer.from(k);
|
|
100
100
|
if (buf.length >= 16) return buf;
|
|
101
|
-
if (production) throw new Error('APP_KEY is shorter than 16 bytes. Run `
|
|
102
|
-
console.warn(' APP_KEY in .env is shorter than 16 bytes; run `
|
|
101
|
+
if (production) throw new Error('APP_KEY is shorter than 16 bytes. Run `kreft key:generate` and put the new key in .env.');
|
|
102
|
+
console.warn(' APP_KEY in .env is shorter than 16 bytes; run `kreft key:generate` for a 256-bit key.');
|
|
103
103
|
return buf;
|
|
104
104
|
}
|
|
105
|
-
if (production) throw new Error('APP_KEY is not set. Run `
|
|
105
|
+
if (production) throw new Error('APP_KEY is not set. Run `kreft key:generate` and put the key in .env (production needs one that every process shares).');
|
|
106
106
|
if (!fallbackKey) {
|
|
107
107
|
fallbackKey = crypto.randomBytes(32);
|
|
108
108
|
console.warn(' No APP_KEY in .env: signed links stop working when the server restarts.');
|
|
@@ -170,7 +170,7 @@ const Mail = {
|
|
|
170
170
|
if (!transport) {
|
|
171
171
|
let nodemailer;
|
|
172
172
|
try { nodemailer = require(require.resolve('nodemailer', { paths: [process.cwd()] })); } catch (_) {
|
|
173
|
-
throw new Error('MAIL_URL is set: install the mailer with
|
|
173
|
+
throw new Error('MAIL_URL is set: install the mailer with kreft add nodemailer');
|
|
174
174
|
}
|
|
175
175
|
transport = nodemailer.createTransport(process.env.MAIL_URL);
|
|
176
176
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TypeScript support across backend and frontend:
|
|
3
3
|
* - Backend: routes/api.ts, config/*.ts, server/*.ts, and every .ts file in app/ and database/ load like .js files.
|
|
4
4
|
* - Frontend: .ts modules (e.g. frontend/logic/*.ts) and .kreftjs components with TypeScript syntax work natively in
|
|
5
|
-
* both `
|
|
5
|
+
* both `kreft dev` and `kreft build`.
|
|
6
6
|
*
|
|
7
7
|
* Uses `esbuild` when installed in the project, or falls back to Kreft's built-in zero-dependency TypeScript
|
|
8
8
|
* compiler (`Kreftjs.stripTypeScript` + ESM-to-CJS transform) so TypeScript projects work out of the box with zero setup.
|
package/bin/build.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
2
|
+
* kreft build — turns a Kreft app into production files.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* kreft build build this folder into ./dist
|
|
5
|
+
* kreft build ./site build another folder
|
|
6
|
+
* kreft build --out public choose the output folder
|
|
7
|
+
* kreft build --base / change <base href> in index.html
|
|
8
|
+
* kreft build --prerender also save every page as ready HTML (needs: npm i -D jsdom)
|
|
9
|
+
* kreft build --prerender --urls urls.js ... and the addresses in that file (pages that come from your data)
|
|
10
|
+
* kreft build --include docs also copy another folder into the output
|
|
11
11
|
*
|
|
12
12
|
* What it does:
|
|
13
13
|
* 1. Compiles every .kreftjs file ahead of time into app.js (no .kreftjs files are downloaded in production).
|
|
@@ -113,7 +113,7 @@ module.exports = function build(args) {
|
|
|
113
113
|
const langInject = Object.keys(langData).length ? `Kreftjs.translations(${JSON.stringify(langData)});\n` : '';
|
|
114
114
|
|
|
115
115
|
const precompiled = [...fns].map(([key, src]) => `F[${JSON.stringify(key)}] = ${src};`).join('\n');
|
|
116
|
-
const appJs = `/* Built by
|
|
116
|
+
const appJs = `/* Built by kreft build — do not edit. Rebuild from your .kreftjs files instead. */
|
|
117
117
|
${env.envScript(env.publicEnv())}
|
|
118
118
|
(function (Kreftjs) {
|
|
119
119
|
if (!Kreftjs) throw new Error('Load kreftjs.js before app.js');
|
|
@@ -204,7 +204,7 @@ ${indent(components.join('\n\n'))}
|
|
|
204
204
|
const links = cssLinks.map(({ spec, local }) => {
|
|
205
205
|
if (local) return spec;
|
|
206
206
|
const file = deps.packageFile(project, spec);
|
|
207
|
-
if (!file) fail(`The CSS file "${spec}" was not found in node_modules. Run:
|
|
207
|
+
if (!file) fail(`The CSS file "${spec}" was not found in node_modules. Run: kreft add ${(spec.match(deps.NAME) || [spec])[0]}`);
|
|
208
208
|
const info = deps.packageJson(project, spec);
|
|
209
209
|
copyCss(file, info.dir, path.join(dist, 'vendor', info.name));
|
|
210
210
|
return toUrlPath(path.join('vendor', info.name, path.relative(info.dir, file)));
|
|
@@ -237,7 +237,7 @@ ${indent(components.join('\n\n'))}
|
|
|
237
237
|
...files.sort().map(([f, n]) => ` ${cyan(kb(n))} ${toUrlPath(f)}`),
|
|
238
238
|
'---',
|
|
239
239
|
` ${bold('Output:')} ${dist}`,
|
|
240
|
-
` ${bold('Preview:')}
|
|
240
|
+
` ${bold('Preview:')} kreft dev ${path.relative(process.cwd(), dist) || '.'}`,
|
|
241
241
|
];
|
|
242
242
|
console.log('\n' + renderBox(lines, { width: 66, borderColor: green }) + '\n');
|
|
243
243
|
};
|
package/bin/db-api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* A REST API made from the tables of a database (used by `
|
|
2
|
+
* A REST API made from the tables of a database (used by `kreft dev --db` and `kreft serve --db`).
|
|
3
3
|
*
|
|
4
4
|
* GET /api the tables you can use
|
|
5
5
|
* GET /api/posts rows ?userId=3 ?title_like=som ?likes_gte=5 ?q=text
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Which tables, and what each allows, comes from `tables` (kreftjs.config.json):
|
|
17
17
|
* { "posts": true, "users": { "write": false, "hidden": ["password"] }, "logs": false }
|
|
18
|
-
* `
|
|
18
|
+
* `kreft dev` shows every table unless you list them; `kreft serve` shows only the tables you list.
|
|
19
19
|
*/
|
|
20
20
|
const { validate } = require('../kreftjs.js'); // the same rules as Kreftjs.validate in the browser
|
|
21
21
|
const MAX_BODY = 1024 * 1024;
|
|
@@ -48,7 +48,7 @@ function createDbApi(db, { tables: allow = null, production = false, prefix = '/
|
|
|
48
48
|
}
|
|
49
49
|
return out;
|
|
50
50
|
};
|
|
51
|
-
// `
|
|
51
|
+
// `kreft serve` reads the tables once; `kreft dev` looks again every 2 s, so a new table shows up by itself
|
|
52
52
|
let loadedAt = 0;
|
|
53
53
|
const tables = async () => {
|
|
54
54
|
if (schema && (production || Date.now() - loadedAt < 2000)) return schema;
|
package/bin/db.js
CHANGED
|
@@ -33,7 +33,7 @@ function parseUrl(url) {
|
|
|
33
33
|
function driver(name, root) {
|
|
34
34
|
const paths = [root || process.cwd(), process.cwd(), __dirname];
|
|
35
35
|
try { return require(require.resolve(name, { paths })); }
|
|
36
|
-
catch (_) { throw new Error(`To use this database, install its driver in your project:\n\n
|
|
36
|
+
catch (_) { throw new Error(`To use this database, install its driver in your project:\n\n kreft add ${name}\n`); }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const TEXT = /char|text|clob|string|uuid|enum|json/i;
|
package/bin/deps.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* import { debounce } from 'lodash'
|
|
6
6
|
* import 'bootstrap/dist/css/bootstrap.min.css'
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* kreft dev answers /__kreft/deps/<package> with the package as one browser-ready ES module, made by
|
|
9
9
|
* esbuild (npm i -D esbuild), and adds an import map so the browser knows where 'dayjs' is.
|
|
10
|
-
*
|
|
11
|
-
* ES modules still work in
|
|
10
|
+
* kreft build writes the same files into dist/deps/. Without esbuild, packages that already ship
|
|
11
|
+
* ES modules still work in kreft dev; older CommonJS ones (like lodash) need esbuild.
|
|
12
12
|
*/
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
@@ -57,14 +57,14 @@ function isEsm(root, spec) {
|
|
|
57
57
|
|
|
58
58
|
const cache = new Map();
|
|
59
59
|
// one package (or a file inside it) as a single ES module for the browser.
|
|
60
|
-
// bundle() is async (
|
|
60
|
+
// bundle() is async (kreft dev); bundleSync() is the same for kreft build.
|
|
61
61
|
function prepare(root, spec, minify) {
|
|
62
62
|
const esbuild = loadEsbuild(root);
|
|
63
|
-
if (!esbuild) throw Object.assign(new Error(`To use "${spec}" in the browser, install esbuild once:
|
|
63
|
+
if (!esbuild) throw Object.assign(new Error(`To use "${spec}" in the browser, install esbuild once: kreft add -D esbuild`), { code: 'NO_ESBUILD' });
|
|
64
64
|
// only packages by name: never a path that would bundle (and show) one of your own server files
|
|
65
65
|
if (!isBare(spec) || spec.split('/').some((p) => p === '..' || p === '.')) throw Object.assign(new Error(`"${spec}" is not a package name.`), { code: 'MISSING' });
|
|
66
66
|
const file = resolveFrom(root, spec);
|
|
67
|
-
if (!file) throw Object.assign(new Error(`The package "${spec}" is not installed. Run:
|
|
67
|
+
if (!file) throw Object.assign(new Error(`The package "${spec}" is not installed. Run: kreft add ${(spec.match(NAME) || [spec])[0]}`), { code: 'MISSING' });
|
|
68
68
|
const key = spec + '|' + fs.statSync(file).mtimeMs + '|' + minify + '|' + root;
|
|
69
69
|
const s = JSON.stringify(spec);
|
|
70
70
|
let tries;
|
package/bin/electron.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Electron.js Desktop Support for Kreft
|
|
3
3
|
*
|
|
4
4
|
* Commands:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* kreft electron:init Add Electron files to an existing project (alias: kreft make:electron)
|
|
6
|
+
* kreft electron:dev Run the app in an Electron desktop window with hot reload (alias: kreft electron)
|
|
7
|
+
* kreft electron:build Build static bundle (kreft build) and package desktop installer (electron-builder)
|
|
8
8
|
*
|
|
9
9
|
* CLI flag:
|
|
10
|
-
*
|
|
10
|
+
* kreft new my-app --electron
|
|
11
11
|
*/
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
@@ -286,9 +286,9 @@ function init(args = []) {
|
|
|
286
286
|
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
|
|
287
287
|
pkg.main = 'electron/main.js';
|
|
288
288
|
pkg.scripts = pkg.scripts || {};
|
|
289
|
-
pkg.scripts['electron'] = '
|
|
290
|
-
pkg.scripts['electron:dev'] = '
|
|
291
|
-
pkg.scripts['electron:build'] = '
|
|
289
|
+
pkg.scripts['electron'] = 'kreft electron';
|
|
290
|
+
pkg.scripts['electron:dev'] = 'kreft electron:dev';
|
|
291
|
+
pkg.scripts['electron:build'] = 'kreft electron:build';
|
|
292
292
|
|
|
293
293
|
pkg.devDependencies = pkg.devDependencies || {};
|
|
294
294
|
if (!pkg.devDependencies['electron']) pkg.devDependencies['electron'] = '^34.2.0';
|
|
@@ -317,8 +317,8 @@ function init(args = []) {
|
|
|
317
317
|
|
|
318
318
|
Next:
|
|
319
319
|
npm install (install electron & electron-builder)
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
kreft electron:dev (start dev server & launch desktop window)
|
|
321
|
+
kreft electron:build (build production installer)
|
|
322
322
|
`);
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -433,7 +433,7 @@ function startDev(args, port) {
|
|
|
433
433
|
|
|
434
434
|
electronProcess.on('error', (err) => {
|
|
435
435
|
if (err.code === 'ENOENT') {
|
|
436
|
-
console.error('\n Electron is not installed locally. Run:
|
|
436
|
+
console.error('\n Electron is not installed locally. Run: kreft add -D electron\n');
|
|
437
437
|
} else {
|
|
438
438
|
console.error(' Electron launch error: ' + err.message);
|
|
439
439
|
}
|
|
@@ -489,7 +489,7 @@ function build(args = []) {
|
|
|
489
489
|
|
|
490
490
|
packProc.on('error', (err) => {
|
|
491
491
|
if (err.code === 'ENOENT') {
|
|
492
|
-
console.error('\n electron-builder is not installed. Run:
|
|
492
|
+
console.error('\n electron-builder is not installed. Run: kreft add -D electron-builder\n');
|
|
493
493
|
} else {
|
|
494
494
|
console.error(' Packaging error: ' + err.message);
|
|
495
495
|
}
|
package/bin/env.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* .env files and environment management.
|
|
3
3
|
*
|
|
4
|
-
* # .env (never sent to the browser, never copied by
|
|
4
|
+
* # .env (never sent to the browser, never copied by kreft build)
|
|
5
5
|
* APP_NAME="My shop"
|
|
6
6
|
* DATABASE_URL=sqlite:database/app.db
|
|
7
7
|
* MAIL_PASSWORD=secret
|
|
8
8
|
* PUBLIC_APP_NAME=${APP_NAME} ← PUBLIC_ values also reach the browser: env.PUBLIC_APP_NAME
|
|
9
9
|
*
|
|
10
|
-
* Read by
|
|
10
|
+
* Read by kreft dev, kreft serve, kreft build and kreft db. Values already set in the real environment win,
|
|
11
11
|
* so a server can override the file. In server/*.js: process.env.MAIL_PASSWORD (or ctx.env).
|
|
12
12
|
*/
|
|
13
13
|
const fs = require('fs');
|
package/bin/guard.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Folder guard: Kreft notices when the folder it works in is deleted, moved or put in the Trash.
|
|
3
3
|
*
|
|
4
4
|
* A running process keeps working on a folder that was moved away (the files are still there, under a new
|
|
5
|
-
* name), so `
|
|
5
|
+
* name), so `kreft dev` would carry on serving a project you thought was gone, and a terminal opened in the
|
|
6
6
|
* old folder would run commands against the Trash. The operating system cannot be asked to refuse the
|
|
7
7
|
* delete, so Kreft says it loudly instead:
|
|
8
8
|
* - every command first checks that the folder it runs in is not deleted or in the Trash;
|
|
9
|
-
* - `
|
|
9
|
+
* - `kreft dev`, `kreft serve` and the queue / schedule workers check every second and stop with a message.
|
|
10
10
|
*/
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
package/bin/kreftjs-config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* kreftjs.config.json: { "db": "sqlite:database/app.db", "tables": { "posts": true, "users": { "write": false, "hidden": ["password"] } } }
|
|
3
|
-
* Looked for in the folder being served, then its parents (so `
|
|
3
|
+
* Looked for in the folder being served, then its parents (so `kreft serve dist` finds the project's file).
|
|
4
4
|
* "${NAME}" in a value is replaced with that value from .env or the environment.
|
|
5
5
|
*/
|
|
6
6
|
const fs = require('fs');
|