kreftjs 0.0.0-stage → 0.0.2-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/README.md +103 -2
- package/bin/add.js +1074 -0
- package/bin/artisan-more.js +994 -0
- package/bin/artisan.js +1877 -0
- package/bin/backend/app.js +161 -0
- package/bin/backend/cache.js +196 -0
- package/bin/backend/collection.js +117 -0
- package/bin/backend/command.js +45 -0
- package/bin/backend/config.js +234 -0
- package/bin/backend/error-page.js +773 -0
- package/bin/backend/events.js +49 -0
- package/bin/backend/factory.js +223 -0
- package/bin/backend/fake.js +180 -0
- package/bin/backend/form.js +102 -0
- package/bin/backend/gate.js +79 -0
- package/bin/backend/gc.js +69 -0
- package/bin/backend/html.js +322 -0
- package/bin/backend/http.js +652 -0
- package/bin/backend/index.js +452 -0
- package/bin/backend/mail.js +77 -0
- package/bin/backend/mcp.js +433 -0
- package/bin/backend/migrator.js +370 -0
- package/bin/backend/model.js +781 -0
- package/bin/backend/namespace.js +213 -0
- package/bin/backend/notifications.js +68 -0
- package/bin/backend/qr.js +164 -0
- package/bin/backend/query.js +387 -0
- package/bin/backend/queue.js +412 -0
- package/bin/backend/redis.js +479 -0
- package/bin/backend/schedule.js +112 -0
- package/bin/backend/schema.js +321 -0
- package/bin/backend/seo-pages.js +100 -0
- package/bin/backend/storage.js +153 -0
- package/bin/backend/support.js +567 -0
- package/bin/backend/typescript.js +166 -0
- package/bin/build.js +540 -0
- package/bin/db-api.js +249 -0
- package/bin/db.js +275 -0
- package/bin/deps.js +138 -0
- package/bin/electron.js +509 -0
- package/bin/env.js +97 -0
- package/bin/guard.js +80 -0
- package/bin/kreftjs-config.js +40 -0
- package/bin/kreftjs.js +1373 -0
- package/bin/lang.js +241 -0
- package/bin/layout.js +74 -0
- package/bin/no-auth.js +162 -0
- package/bin/prerender.js +258 -0
- package/bin/safe-json.js +24 -0
- package/bin/server-routes.js +154 -0
- package/bin/ssr.js +133 -0
- package/bin/terminal.js +195 -0
- package/bin/to-ts.js +36 -0
- package/bin/update.js +237 -0
- package/bin/urls-file.js +27 -0
- package/bin/vite.js +228 -0
- package/docs/Docs.kreftjs +557 -0
- package/docs/README.md +2302 -0
- package/docs/markdown.js +186 -0
- package/editors/vscode/README.md +13 -0
- package/editors/vscode/extension.js +183 -0
- package/editors/vscode/language-configuration.json +17 -0
- package/editors/vscode/package.json +57 -0
- package/editors/vscode/snippets/kreftjs.json +21 -0
- package/editors/vscode/syntaxes/kreftjs.tmLanguage.json +207 -0
- package/kreftjs.d.ts +579 -0
- package/kreftjs.js +5108 -0
- package/package.json +97 -4
- package/server.d.ts +1028 -0
- package/server.js +8 -0
- package/templates/app/README.md +127 -0
- package/templates/app/_env.example +64 -0
- package/templates/app/_gitignore +5 -0
- package/templates/app/_vscode/settings.json +9 -0
- package/templates/app/backend/app/Auth/Session.js +177 -0
- package/templates/app/backend/app/Auth/Throttle.js +29 -0
- package/templates/app/backend/app/Console/Commands/.gitkeep +0 -0
- package/templates/app/backend/app/Events/.gitkeep +0 -0
- package/templates/app/backend/app/Http/Controllers/Admin/DashboardController.js +34 -0
- package/templates/app/backend/app/Http/Controllers/Admin/UserController.js +53 -0
- package/templates/app/backend/app/Http/Controllers/Auth/ApiTokenController.js +47 -0
- package/templates/app/backend/app/Http/Controllers/Auth/AuthenticatedSessionController.js +41 -0
- package/templates/app/backend/app/Http/Controllers/Auth/ConfirmablePasswordController.js +14 -0
- package/templates/app/backend/app/Http/Controllers/Auth/EmailVerificationController.js +23 -0
- package/templates/app/backend/app/Http/Controllers/Auth/NewPasswordController.js +29 -0
- package/templates/app/backend/app/Http/Controllers/Auth/PasswordResetLinkController.js +20 -0
- package/templates/app/backend/app/Http/Controllers/Auth/RegisteredUserController.js +19 -0
- package/templates/app/backend/app/Http/Controllers/Auth/SessionController.js +36 -0
- package/templates/app/backend/app/Http/Controllers/Auth/TwoFactorChallengeController.js +41 -0
- package/templates/app/backend/app/Http/Controllers/Auth/TwoFactorController.js +63 -0
- package/templates/app/backend/app/Http/Controllers/ProfileController.js +66 -0
- package/templates/app/backend/app/Http/Kernel.js +8 -0
- package/templates/app/backend/app/Http/Middleware/Authenticate.js +84 -0
- package/templates/app/backend/app/Http/Middleware/CheckAbilities.js +17 -0
- package/templates/app/backend/app/Http/Middleware/CheckForAnyAbility.js +12 -0
- package/templates/app/backend/app/Http/Middleware/EnsureEmailIsVerified.js +11 -0
- package/templates/app/backend/app/Http/Middleware/EnsureHasRole.js +11 -0
- package/templates/app/backend/app/Http/Middleware/EnsureIsAdmin.js +11 -0
- package/templates/app/backend/app/Http/Middleware/RequirePassword.js +15 -0
- package/templates/app/backend/app/Http/Requests/Auth/LoginRequest.js +45 -0
- package/templates/app/backend/app/Http/Resources/.gitkeep +1 -0
- package/templates/app/backend/app/Jobs/.gitkeep +0 -0
- package/templates/app/backend/app/Listeners/.gitkeep +0 -0
- package/templates/app/backend/app/Mail/.gitkeep +0 -0
- package/templates/app/backend/app/Models/PersonalAccessToken.js +21 -0
- package/templates/app/backend/app/Models/User.js +64 -0
- package/templates/app/backend/app/Notifications/.gitkeep +0 -0
- package/templates/app/backend/app/Observers/.gitkeep +0 -0
- package/templates/app/backend/app/Policies/.gitkeep +0 -0
- package/templates/app/backend/app/Providers/AppServiceProvider.js +12 -0
- package/templates/app/backend/app/Rules/.gitkeep +1 -0
- package/templates/app/backend/config/app.js +16 -0
- package/templates/app/backend/config/auth.js +34 -0
- package/templates/app/backend/config/database.js +38 -0
- package/templates/app/backend/config/redis.js +28 -0
- package/templates/app/backend/config/session.js +36 -0
- package/templates/app/backend/database/migrations/0001_01_01_000000_create_users_table.js +49 -0
- package/templates/app/backend/database/migrations/0001_01_01_000001_create_cache_table.js +13 -0
- package/templates/app/backend/database/migrations/0001_01_01_000002_create_jobs_table.js +26 -0
- package/templates/app/backend/database/migrations/0001_01_01_000003_create_notifications_table.js +18 -0
- package/templates/app/backend/database/seeders/DatabaseSeeder.js +15 -0
- package/templates/app/backend/lang/ar/messages.json +10 -0
- package/templates/app/backend/lang/ar/validation.json +21 -0
- package/templates/app/backend/lang/en/messages.json +10 -0
- package/templates/app/backend/lang/en/validation.json +19 -0
- package/templates/app/backend/routes/api.js +25 -0
- package/templates/app/backend/routes/auth.js +52 -0
- package/templates/app/backend/routes/console.js +15 -0
- package/templates/app/frontend/App.kreftjs +4 -0
- package/templates/app/frontend/components/Field.kreftjs +19 -0
- package/templates/app/frontend/components/StatCard.kreftjs +47 -0
- package/templates/app/frontend/components/profile/ApiTokens.kreftjs +70 -0
- package/templates/app/frontend/components/profile/Sessions.kreftjs +42 -0
- package/templates/app/frontend/components/profile/TwoFactor.kreftjs +90 -0
- package/templates/app/frontend/errors/ErrorMonitor.kreftjs +264 -0
- package/templates/app/frontend/index.html +79 -0
- package/templates/app/frontend/kreftjs.d.ts +579 -0
- package/templates/app/frontend/kreftjs.js +5108 -0
- package/templates/app/frontend/layouts/app.kreftjs +718 -0
- package/templates/app/frontend/layouts/docs.kreftjs +37 -0
- package/templates/app/frontend/layouts/guest.kreftjs +18 -0
- package/templates/app/frontend/pages/Dashboard.kreftjs +273 -0
- package/templates/app/frontend/pages/ErrorMonitor.kreftjs +96 -0
- package/templates/app/frontend/pages/Home.kreftjs +617 -0
- package/templates/app/frontend/pages/Profile.kreftjs +104 -0
- package/templates/app/frontend/pages/Users.kreftjs +240 -0
- package/templates/app/frontend/pages/auth/ConfirmPassword.kreftjs +33 -0
- package/templates/app/frontend/pages/auth/ForgotPassword.kreftjs +36 -0
- package/templates/app/frontend/pages/auth/Login.kreftjs +89 -0
- package/templates/app/frontend/pages/auth/Register.kreftjs +48 -0
- package/templates/app/frontend/pages/auth/ResetPassword.kreftjs +45 -0
- package/templates/app/frontend/pages/auth/VerifyEmail.kreftjs +59 -0
- package/templates/app/frontend/routes/web.kreftjs +34 -0
- package/templates/app/frontend/tests/components/Counter.kreftjs +15 -0
- package/templates/app/frontend/tests/pages/About.kreftjs +12 -0
- package/templates/app/frontend/tests/pages/Test.kreftjs +31 -0
- package/templates/app/frontend/ui.css +517 -0
- package/templates/app/lang/ar/auth.json +6 -0
- package/templates/app/lang/ar/messages.json +6 -0
- package/templates/app/lang/ar/pagination.json +5 -0
- package/templates/app/lang/ar/passwords.json +8 -0
- package/templates/app/lang/ar/validation.json +22 -0
- package/templates/app/lang/ar.json +10 -0
- package/templates/app/lang/en/auth.json +6 -0
- package/templates/app/lang/en/messages.json +6 -0
- package/templates/app/lang/en/pagination.json +5 -0
- package/templates/app/lang/en/passwords.json +8 -0
- package/templates/app/lang/en/validation.json +22 -0
- package/templates/app/lang/en.json +10 -0
- package/templates/errors/401.kreftjs +11 -0
- package/templates/errors/402.kreftjs +11 -0
- package/templates/errors/403.kreftjs +11 -0
- package/templates/errors/404.kreftjs +11 -0
- package/templates/errors/419.kreftjs +11 -0
- package/templates/errors/429.kreftjs +11 -0
- package/templates/errors/500.kreftjs +11 -0
- package/templates/errors/503.kreftjs +11 -0
- package/templates/errors/ErrorMonitor.kreftjs +502 -0
- package/templates/errors/error.kreftjs +11 -0
- package/templates/errors/minimal.kreftjs +25 -0
- package/tools/dev-client.js +125 -0
- package/tools/devtools.js +172 -0
- package/tools/vite-plugin-kreftjs.d.ts +52 -0
- package/tools/vite-plugin-kreftjs.js +466 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Full-stack CLI commands:
|
|
3
|
+
*
|
|
4
|
+
* kreftjs list every command (yours too)
|
|
5
|
+
* kreftjs about versions, settings, folders
|
|
6
|
+
* kreftjs tinker a console with your models, DB, Cache… (await works)
|
|
7
|
+
* kreftjs key:generate a new APP_KEY in .env
|
|
8
|
+
* kreftjs down [--secret=x] / kreftjs up maintenance mode
|
|
9
|
+
* kreftjs db:wipe drop every table
|
|
10
|
+
* kreftjs migrate:refresh [--seed] undo every migration, then run them again
|
|
11
|
+
* kreftjs migrate:reset undo every migration
|
|
12
|
+
* kreftjs queue:work [--queue=a,b] [--once] [--stop-when-empty]
|
|
13
|
+
* kreftjs queue:failed / queue:retry <id|all> / queue:flush / queue:table
|
|
14
|
+
* kreftjs schedule:work / schedule:run / schedule:list
|
|
15
|
+
* kreftjs cache:clear / cache:table
|
|
16
|
+
* kreftjs notifications:table
|
|
17
|
+
* kreftjs event:list
|
|
18
|
+
* kreftjs make:job, make:event, make:listener, make:mail, make:notification, make:policy, make:request,
|
|
19
|
+
* make:resource, make:factory, make:rule, make:command, make:observer
|
|
20
|
+
* Your own commands: app/Console/Commands/*.js (kreftjs make:command SendReport)
|
|
21
|
+
*/
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
const { commands, make, helpers } = require('./artisan.js');
|
|
26
|
+
const { options, context, withDb, write, nested, green, dim, migrationsOf, nextStamp, runSeed } = helpers;
|
|
27
|
+
const pkg = require('../package.json');
|
|
28
|
+
|
|
29
|
+
const yellow = (s) => (process.stdout.isTTY ? `\x1b[33m${s}\x1b[0m` : s);
|
|
30
|
+
const red = (s) => (process.stdout.isTTY ? `\x1b[31m${s}\x1b[0m` : s);
|
|
31
|
+
const cyan = (s) => (process.stdout.isTTY ? `\x1b[36m${s}\x1b[0m` : s);
|
|
32
|
+
const plural = (s) => require('./backend/schema.js').plural(s);
|
|
33
|
+
const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
34
|
+
|
|
35
|
+
// everything the app needs in one go: its database, its classes, its listeners and schedule
|
|
36
|
+
async function withApp(o, ctx, fn) {
|
|
37
|
+
await withDb(o, ctx, async (db, b) => {
|
|
38
|
+
require('./backend/app.js').boot(ctx.base);
|
|
39
|
+
await fn(db, b);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ------------------------------------------------------------------ built-in migration tables */
|
|
44
|
+
const tableStubs = {
|
|
45
|
+
jobs: `// The queue's tables: jobs waiting (kreftjs queue:work, or kreftjs dev, runs them) and jobs that failed
|
|
46
|
+
module.exports = {
|
|
47
|
+
up(schema) {
|
|
48
|
+
schema.create('jobs', (table) => {
|
|
49
|
+
table.id()
|
|
50
|
+
table.string('queue').index()
|
|
51
|
+
table.longText('payload')
|
|
52
|
+
table.integer('attempts').default(0)
|
|
53
|
+
table.integer('reserved_at').nullable()
|
|
54
|
+
table.integer('available_at')
|
|
55
|
+
table.integer('created_at')
|
|
56
|
+
})
|
|
57
|
+
schema.create('failed_jobs', (table) => {
|
|
58
|
+
table.id()
|
|
59
|
+
table.string('uuid').unique()
|
|
60
|
+
table.string('queue')
|
|
61
|
+
table.longText('payload')
|
|
62
|
+
table.longText('exception')
|
|
63
|
+
table.timestamp('failed_at').useCurrent()
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
down(schema) {
|
|
67
|
+
schema.dropIfExists('failed_jobs')
|
|
68
|
+
schema.dropIfExists('jobs')
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
`,
|
|
72
|
+
cache: `// The cache table (CACHE_STORE=database): Cache.put(), Cache.remember(), Cache.lock()
|
|
73
|
+
module.exports = {
|
|
74
|
+
up(schema) {
|
|
75
|
+
schema.create('cache', (table) => {
|
|
76
|
+
table.string('key').primary()
|
|
77
|
+
table.text('value')
|
|
78
|
+
table.integer('expiration').default(0)
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
down(schema) {
|
|
82
|
+
schema.dropIfExists('cache')
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
`,
|
|
86
|
+
notifications: `// The notifications table: user.notify(…) with the 'database' channel, user.notifications()
|
|
87
|
+
module.exports = {
|
|
88
|
+
up(schema) {
|
|
89
|
+
schema.create('notifications', (table) => {
|
|
90
|
+
table.string('id', 36).primary()
|
|
91
|
+
table.string('type')
|
|
92
|
+
table.string('notifiable_type')
|
|
93
|
+
table.bigInteger('notifiable_id')
|
|
94
|
+
table.text('data')
|
|
95
|
+
table.timestamp('read_at').nullable()
|
|
96
|
+
table.timestamps()
|
|
97
|
+
table.index(['notifiable_type', 'notifiable_id'])
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
down(schema) {
|
|
101
|
+
schema.dropIfExists('notifications')
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
`,
|
|
105
|
+
};
|
|
106
|
+
function tableCommand(kind, fileName) {
|
|
107
|
+
return function (args) {
|
|
108
|
+
const o = options(args), ctx = context(o);
|
|
109
|
+
const dir = migrationsOf(o, ctx);
|
|
110
|
+
const exists = fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.includes(fileName));
|
|
111
|
+
if (exists) return console.log(` There is already a ${fileName} migration.`);
|
|
112
|
+
write(path.join(dir, `${nextStamp(dir)}_${fileName}.js`), tableStubs[kind]);
|
|
113
|
+
console.log(dim(' Then: kreftjs migrate'));
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
commands['queue:table'] = tableCommand('jobs', 'create_jobs_table');
|
|
117
|
+
commands['cache:table'] = tableCommand('cache', 'create_cache_table');
|
|
118
|
+
commands['notifications:table'] = tableCommand('notifications', 'create_notifications_table');
|
|
119
|
+
|
|
120
|
+
/* ------------------------------------------------------------------ queue */
|
|
121
|
+
commands['queue:work'] = async function (args) {
|
|
122
|
+
const o = options(args), ctx = context(o);
|
|
123
|
+
const raw = args.find((a) => a.startsWith('--queue=')) || '';
|
|
124
|
+
const queues = (raw.split('=')[1] || 'default').split(',');
|
|
125
|
+
await withApp(o, ctx, async () => {
|
|
126
|
+
const q = require('./backend/queue.js');
|
|
127
|
+
if (!(await q.tablesReady())) return console.error(' No jobs table. Make it with: kreftjs queue:table, then kreftjs migrate');
|
|
128
|
+
console.log(` Working on the ${queues.join(', ')} queue${queues.length > 1 ? 's' : ''}. Stop with Ctrl+C.\n`);
|
|
129
|
+
await q.work({ queues, once: o.flags.has('once'), stopWhenEmpty: o.flags.has('stop-when-empty'), log: console.log });
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
commands['queue:failed'] = async function (args) {
|
|
133
|
+
const o = options(args), ctx = context(o);
|
|
134
|
+
await withDb(o, ctx, async (db) => {
|
|
135
|
+
const rows = await db.query('SELECT id, queue, payload, exception, failed_at FROM failed_jobs ORDER BY id DESC LIMIT 50');
|
|
136
|
+
if (!rows.length) return console.log(' No failed jobs.');
|
|
137
|
+
for (const r of rows) console.log(` ${String(r.id).padEnd(5)} ${String(r.failed_at).slice(0, 19)} ${JSON.parse(r.payload).class} ${dim(String(r.exception).split('\n')[0].slice(0, 90))}`);
|
|
138
|
+
console.log(dim('\n Try one again: kreftjs queue:retry <id> (or all)'));
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
commands['queue:retry'] = async function (args) {
|
|
142
|
+
const o = options(args), ctx = context(o);
|
|
143
|
+
const which = o._[0];
|
|
144
|
+
if (!which) return console.error(' kreftjs queue:retry <id> or kreftjs queue:retry all');
|
|
145
|
+
await withDb(o, ctx, async (db) => {
|
|
146
|
+
const { Builder } = require('./backend/query.js');
|
|
147
|
+
const rows = which === 'all' ? await new Builder('failed_jobs').get() : await new Builder('failed_jobs').where('id', which).get();
|
|
148
|
+
const t = Math.floor(Date.now() / 1000);
|
|
149
|
+
for (const r of rows) {
|
|
150
|
+
await new Builder('jobs').insert({ queue: r.queue, payload: r.payload, attempts: 0, reserved_at: null, available_at: t, created_at: t });
|
|
151
|
+
await new Builder('failed_jobs').where('id', r.id).delete();
|
|
152
|
+
console.log(` ${green('QUEUED')} ${JSON.parse(r.payload).class} (failed job ${r.id})`);
|
|
153
|
+
}
|
|
154
|
+
if (!rows.length) console.log(' No such failed job.');
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
commands['queue:flush'] = async function (args) {
|
|
158
|
+
const o = options(args), ctx = context(o);
|
|
159
|
+
await withDb(o, ctx, async (db) => { const n = (await db.run('DELETE FROM failed_jobs')).changes; console.log(` Deleted ${n} failed job${n === 1 ? '' : 's'}.`); });
|
|
160
|
+
};
|
|
161
|
+
commands['queue:clear'] = async function (args) {
|
|
162
|
+
const o = options(args), ctx = context(o);
|
|
163
|
+
await withDb(o, ctx, async (db) => { const n = (await db.run('DELETE FROM jobs')).changes; console.log(` Deleted ${n} waiting job${n === 1 ? '' : 's'}.`); });
|
|
164
|
+
};
|
|
165
|
+
commands['queue:stop'] = async function (args) {
|
|
166
|
+
const o = options(args);
|
|
167
|
+
const q = require('./backend/queue.js');
|
|
168
|
+
const target = o.flags.has('all') ? 'all' : (o._[0] || 'all');
|
|
169
|
+
q.stop(target);
|
|
170
|
+
if (target === 'all' || target === '*') {
|
|
171
|
+
console.log(` ${red('STOPPED')} All running queues have been stopped.`);
|
|
172
|
+
console.log(dim(' Resume anytime with: kreftjs queue:resume --all'));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` ${yellow('STOPPED')} Queue "${target}" has been stopped.`);
|
|
175
|
+
console.log(dim(` Resume with: kreftjs queue:resume ${target}`));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
commands['queue:pause'] = commands['queue:stop'];
|
|
179
|
+
|
|
180
|
+
commands['queue:resume'] = async function (args) {
|
|
181
|
+
const o = options(args);
|
|
182
|
+
const q = require('./backend/queue.js');
|
|
183
|
+
const target = o.flags.has('all') ? 'all' : (o._[0] || 'all');
|
|
184
|
+
q.resume(target);
|
|
185
|
+
if (target === 'all' || target === '*') {
|
|
186
|
+
console.log(` ${green('RESUMED')} All queues are now active and running.`);
|
|
187
|
+
} else {
|
|
188
|
+
console.log(` ${green('RESUMED')} Queue "${target}" is now active and running.`);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
commands['queue:start'] = commands['queue:resume'];
|
|
192
|
+
|
|
193
|
+
commands['queue:restart'] = async function () {
|
|
194
|
+
const q = require('./backend/queue.js');
|
|
195
|
+
q.restart();
|
|
196
|
+
console.log(` ${green('SIGNALED')} Queue workers will gracefully finish active jobs and restart.`);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
commands['queue:status'] = async function (args) {
|
|
200
|
+
const o = options(args), ctx = context(o);
|
|
201
|
+
const q = require('./backend/queue.js');
|
|
202
|
+
const ctrl = q.status();
|
|
203
|
+
console.log('\n Queue System Status:\n');
|
|
204
|
+
if (ctrl.stoppedAll) {
|
|
205
|
+
console.log(` Worker State: ${red('STOPPED (ALL QUEUES)')}`);
|
|
206
|
+
} else if (ctrl.stoppedQueues.length > 0) {
|
|
207
|
+
console.log(` Worker State: ${yellow('PARTIALLY STOPPED')}`);
|
|
208
|
+
console.log(` Stopped Queues: ${yellow(ctrl.stoppedQueues.join(', '))}`);
|
|
209
|
+
} else {
|
|
210
|
+
console.log(` Worker State: ${green('RUNNING')}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { readConfig } = require('./kreftjs-config.js');
|
|
214
|
+
const config = readConfig(ctx.base);
|
|
215
|
+
const url = o.db || process.env.DATABASE_URL || config.db;
|
|
216
|
+
if (url) {
|
|
217
|
+
await withDb(o, ctx, async (db) => {
|
|
218
|
+
if (await q.tablesReady()) {
|
|
219
|
+
const rows = await db.query('SELECT queue, count(*) as count FROM jobs GROUP BY queue');
|
|
220
|
+
console.log('\n Pending Jobs:');
|
|
221
|
+
if (!rows.length) {
|
|
222
|
+
console.log(' No jobs waiting.');
|
|
223
|
+
} else {
|
|
224
|
+
for (const r of rows) {
|
|
225
|
+
const stoppedTag = q.isStopped(r.queue) ? ` ${red('[STOPPED]')}` : '';
|
|
226
|
+
console.log(` - ${r.queue}: ${r.count} waiting${stoppedTag}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const failed = await db.query('SELECT count(*) as count FROM failed_jobs');
|
|
230
|
+
console.log(`\n Failed Jobs: ${failed[0] ? failed[0].count : 0}`);
|
|
231
|
+
}
|
|
232
|
+
}).catch(() => {});
|
|
233
|
+
}
|
|
234
|
+
console.log('');
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/* ------------------------------------------------------------------ schedule */
|
|
238
|
+
commands['schedule:list'] = async function (args) {
|
|
239
|
+
const o = options(args), ctx = context(o);
|
|
240
|
+
require('./backend/index.js').installAlias();
|
|
241
|
+
require('./backend/app.js').boot(ctx.base);
|
|
242
|
+
const { loadSchedule, Schedule } = require('./backend/schedule.js');
|
|
243
|
+
if (!loadSchedule(ctx.base)) return console.log(' No routes/console.js. Add tasks there: Schedule.call(fn).daily()');
|
|
244
|
+
const tasks = Schedule.tasks();
|
|
245
|
+
if (!tasks.length) return console.log(' No tasks in routes/console.js.');
|
|
246
|
+
for (const t of tasks) console.log(` ${t.expr.padEnd(16)} ${t.label}`);
|
|
247
|
+
};
|
|
248
|
+
commands['schedule:run'] = async function (args) {
|
|
249
|
+
const o = options(args), ctx = context(o);
|
|
250
|
+
await withApp(o, ctx, async () => {
|
|
251
|
+
const { loadSchedule, Schedule } = require('./backend/schedule.js');
|
|
252
|
+
if (!loadSchedule(ctx.base)) return console.log(' No routes/console.js.');
|
|
253
|
+
const due = Schedule.tasks().filter((t) => t.isDue(new Date()));
|
|
254
|
+
if (!due.length) return console.log(' No tasks are due now.');
|
|
255
|
+
for (const t of due) await t.execute(console.log);
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
commands['schedule:work'] = async function (args) {
|
|
259
|
+
const o = options(args), ctx = context(o);
|
|
260
|
+
await withApp(o, ctx, async () => {
|
|
261
|
+
const { loadSchedule, Schedule } = require('./backend/schedule.js');
|
|
262
|
+
if (!loadSchedule(ctx.base)) return console.log(' No routes/console.js.');
|
|
263
|
+
console.log(` Running ${Schedule.tasks().length} scheduled task${Schedule.tasks().length === 1 ? '' : 's'} on time. Stop with Ctrl+C.\n`);
|
|
264
|
+
for (;;) {
|
|
265
|
+
await new Promise((r) => setTimeout(r, 60000 - (Date.now() % 60000) + 50));
|
|
266
|
+
loadSchedule(ctx.base);
|
|
267
|
+
await Schedule.runDue(console.log);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/* ------------------------------------------------------------------ storage & logs */
|
|
273
|
+
commands['storage:link'] = async function (args) {
|
|
274
|
+
const o = options(args), ctx = context(o);
|
|
275
|
+
const target = fs.existsSync(path.join(ctx.project, 'storage', 'app', 'public'))
|
|
276
|
+
? path.join(ctx.project, 'storage', 'app', 'public')
|
|
277
|
+
: path.join(ctx.base, 'storage', 'app', 'public');
|
|
278
|
+
fs.mkdirSync(target, { recursive: true });
|
|
279
|
+
|
|
280
|
+
const candidateLinks = [
|
|
281
|
+
ctx.frontend && ctx.frontend !== ctx.base ? path.join(ctx.frontend, 'public', 'storage') : null,
|
|
282
|
+
path.join(ctx.project, 'public', 'storage'),
|
|
283
|
+
ctx.base !== ctx.project ? path.join(ctx.base, 'public', 'storage') : null,
|
|
284
|
+
].filter(Boolean);
|
|
285
|
+
|
|
286
|
+
const links = [...new Set(candidateLinks)];
|
|
287
|
+
let created = 0;
|
|
288
|
+
|
|
289
|
+
for (const linkPath of links) {
|
|
290
|
+
const relLink = path.relative(ctx.project, linkPath);
|
|
291
|
+
const relTarget = path.relative(ctx.project, target);
|
|
292
|
+
try {
|
|
293
|
+
const stat = fs.lstatSync(linkPath);
|
|
294
|
+
if (stat) {
|
|
295
|
+
if (o.flags.has('force')) {
|
|
296
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(linkPath);
|
|
297
|
+
else fs.rmSync(linkPath, { recursive: true, force: true });
|
|
298
|
+
} else {
|
|
299
|
+
console.log(` The [${relLink}] link already exists.`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
} catch (_) {
|
|
304
|
+
// link does not exist yet
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
309
|
+
const absTarget = path.resolve(target);
|
|
310
|
+
const symTarget = o.flags.has('relative') ? path.relative(path.dirname(path.resolve(linkPath)), absTarget) : absTarget;
|
|
311
|
+
fs.symlinkSync(symTarget, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
312
|
+
console.log(` ${green('LINKED')} The [${relLink}] link has been connected to [${relTarget}].`);
|
|
313
|
+
created++;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(` ${red('ERROR')} Could not link [${relLink}]: ${e.message}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (created > 0) {
|
|
320
|
+
console.log(`\n The public storage link${created === 1 ? '' : 's'} have been created.`);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
commands['storage:unlink'] = async function (args) {
|
|
325
|
+
const o = options(args), ctx = context(o);
|
|
326
|
+
const candidateLinks = [
|
|
327
|
+
ctx.frontend && ctx.frontend !== ctx.base ? path.join(ctx.frontend, 'public', 'storage') : null,
|
|
328
|
+
path.join(ctx.project, 'public', 'storage'),
|
|
329
|
+
ctx.base !== ctx.project ? path.join(ctx.base, 'public', 'storage') : null,
|
|
330
|
+
].filter(Boolean);
|
|
331
|
+
|
|
332
|
+
const links = [...new Set(candidateLinks)];
|
|
333
|
+
let unlinked = 0;
|
|
334
|
+
|
|
335
|
+
for (const linkPath of links) {
|
|
336
|
+
const relLink = path.relative(ctx.project, linkPath);
|
|
337
|
+
try {
|
|
338
|
+
const stat = fs.lstatSync(linkPath);
|
|
339
|
+
if (stat) {
|
|
340
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(linkPath);
|
|
341
|
+
else fs.rmSync(linkPath, { recursive: true, force: true });
|
|
342
|
+
console.log(` ${green('UNLINKED')} Removed [${relLink}].`);
|
|
343
|
+
unlinked++;
|
|
344
|
+
}
|
|
345
|
+
} catch (_) {
|
|
346
|
+
// not found
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!unlinked) {
|
|
351
|
+
console.log(` No storage links found to remove.`);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
commands['link:storage'] = commands['storage:link'];
|
|
355
|
+
commands['unlink:storage'] = commands['storage:unlink'];
|
|
356
|
+
|
|
357
|
+
/* ------------------------------------------------------------------ SEO & sitemap */
|
|
358
|
+
commands['sitemap'] = async function (args) {
|
|
359
|
+
const o = options(args), ctx = context(o);
|
|
360
|
+
const { createProgressBar } = require('./terminal.js');
|
|
361
|
+
const baseUrl = o.url || process.env.APP_URL || 'http://localhost:8000';
|
|
362
|
+
|
|
363
|
+
const routes = ['/'];
|
|
364
|
+
// a route with a :param or * is a family of pages, not one address: it cannot be listed as it is
|
|
365
|
+
const add = (p) => { if (p && p.startsWith('/') && !/[:*]/.test(p) && !routes.includes(p)) routes.push(p); };
|
|
366
|
+
const webKreft = [
|
|
367
|
+
path.join(ctx.frontend, 'routes', 'web.kreftjs'),
|
|
368
|
+
path.join(ctx.project, 'routes', 'web.kreftjs'),
|
|
369
|
+
].find((f) => fs.existsSync(f));
|
|
370
|
+
|
|
371
|
+
if (webKreft) {
|
|
372
|
+
const src = fs.readFileSync(webKreft, 'utf8');
|
|
373
|
+
for (const m of src.matchAll(/Route\.(?:view|get)\(\s*['"]([^'"]+)['"]/g)) add(m[1]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// routes written in Kreftjs.router({ routes: [{ path: '/about', component: ... }] }) in index.html
|
|
377
|
+
const indexHtml = [path.join(ctx.frontend, 'index.html'), path.join(ctx.project, 'index.html')].find((f) => fs.existsSync(f));
|
|
378
|
+
if (indexHtml) {
|
|
379
|
+
for (const line of fs.readFileSync(indexHtml, 'utf8').split('\n')) {
|
|
380
|
+
if (/redirect\s*:/.test(line) || !/component\s*:/.test(line)) continue; // redirects and guards are not pages
|
|
381
|
+
for (const m of line.matchAll(/path\s*:\s*['"]([^'"]+)['"]/g)) add(m[1]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// pages that come from your data: --urls urls.js, and `urls` in backend/routes/seo.js (it can use your models)
|
|
386
|
+
const extra = [];
|
|
387
|
+
try {
|
|
388
|
+
if (o.urls) extra.push(...await require('./urls-file.js')(o.urls));
|
|
389
|
+
if (fs.existsSync(path.join(ctx.base, 'routes')) && require('./backend/typescript.js').findFile(path.join(ctx.base, 'routes', 'seo'))) {
|
|
390
|
+
const { readConfig } = require('./kreftjs-config.js');
|
|
391
|
+
const dbUrl = o.db || process.env.DATABASE_URL || readConfig(ctx.base).db;
|
|
392
|
+
const seoPages = require('./backend/seo-pages.js');
|
|
393
|
+
const backend = require('./backend/index.js');
|
|
394
|
+
backend.installAlias();
|
|
395
|
+
let db = null;
|
|
396
|
+
if (dbUrl) {
|
|
397
|
+
db = await require('./db.js').connect(dbUrl, { root: readConfig(ctx.base).dir || ctx.project });
|
|
398
|
+
backend.setConnection(db);
|
|
399
|
+
require('./backend/model.js').setBaseDir(ctx.base);
|
|
400
|
+
}
|
|
401
|
+
try { extra.push(...await seoPages.urls(ctx.base)); } finally { if (db && typeof db.close === 'function') await db.close(); }
|
|
402
|
+
}
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.error(' ' + String(e.message || e).replace(/\n/g, '\n '));
|
|
405
|
+
process.exitCode = 1;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const u of extra) {
|
|
409
|
+
const p = typeof u === 'string' ? u : u.path;
|
|
410
|
+
if (!p || /[:*]/.test(p) || routes.some((r) => (typeof r === 'string' ? r : r.path) === p)) continue;
|
|
411
|
+
routes.push(u);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const bar = createProgressBar(routes.length, { label: 'Generating sitemap' });
|
|
415
|
+
for (let i = 0; i < routes.length; i++) {
|
|
416
|
+
bar.update(i + 1, typeof routes[i] === 'string' ? routes[i] : routes[i].path);
|
|
417
|
+
}
|
|
418
|
+
bar.complete(`${routes.length} URLs indexed`);
|
|
419
|
+
|
|
420
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/.test(baseUrl)) console.log(` WARN The sitemap uses ${baseUrl}. Search engines need your real address: kreftjs sitemap --url https://example.com (or set APP_URL)`);
|
|
421
|
+
const { SEO } = require('./backend/support.js');
|
|
422
|
+
const xml = SEO.sitemap(routes, { baseUrl });
|
|
423
|
+
const robots = SEO.robots({ allow: '/', sitemapUrl: `${baseUrl.replace(/\/+$/, '')}/sitemap.xml` });
|
|
424
|
+
|
|
425
|
+
const targetDirs = [
|
|
426
|
+
path.join(ctx.project, 'public'),
|
|
427
|
+
ctx.frontend && ctx.frontend !== ctx.project ? path.join(ctx.frontend, 'public') : null,
|
|
428
|
+
].filter(Boolean);
|
|
429
|
+
|
|
430
|
+
for (const dir of targetDirs) {
|
|
431
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
432
|
+
fs.writeFileSync(path.join(dir, 'sitemap.xml'), xml);
|
|
433
|
+
fs.writeFileSync(path.join(dir, 'robots.txt'), robots);
|
|
434
|
+
console.log(` ${green('DONE')} Created ${path.relative(ctx.project, path.join(dir, 'sitemap.xml'))}`);
|
|
435
|
+
console.log(` ${green('DONE')} Created ${path.relative(ctx.project, path.join(dir, 'robots.txt'))}`);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
commands['log'] = async function (args) {
|
|
440
|
+
const o = options(args), ctx = context(o);
|
|
441
|
+
if (o.flags.has('clear')) return commands['log:clear'](args);
|
|
442
|
+
|
|
443
|
+
const logDir = path.join(ctx.base, 'storage', 'logs');
|
|
444
|
+
const logFile = path.join(logDir, 'kreft.log');
|
|
445
|
+
|
|
446
|
+
if (!fs.existsSync(logFile)) {
|
|
447
|
+
console.log(` No log file at ${path.relative(ctx.project, logFile)}.`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const content = fs.readFileSync(logFile, 'utf8').trim();
|
|
452
|
+
if (!content) {
|
|
453
|
+
console.log(` Log file is empty (${path.relative(ctx.project, logFile)}).`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let lines = content.split('\n');
|
|
458
|
+
const levelFilter = o.named?.level || (args.find((a) => a.startsWith('--level=')) ? args.find((a) => a.startsWith('--level=')).split('=')[1] : null);
|
|
459
|
+
if (levelFilter) {
|
|
460
|
+
const up = levelFilter.toUpperCase();
|
|
461
|
+
lines = lines.filter((l) => l.includes('.' + up + ':'));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const linesCount = Number(args.find((a) => a.startsWith('--lines='))?.split('=')[1] || args.find((a) => /^\d+$/.test(a)) || 50);
|
|
465
|
+
const slice = lines.slice(-linesCount);
|
|
466
|
+
|
|
467
|
+
console.log(`\n Showing last ${slice.length} lines from ${path.relative(ctx.project, logFile)}:\n`);
|
|
468
|
+
for (const line of slice) {
|
|
469
|
+
if (/\.(ERROR|CRITICAL):/.test(line)) console.log(' ' + red(line));
|
|
470
|
+
else if (/\.WARNING:/.test(line)) console.log(' ' + yellow(line));
|
|
471
|
+
else if (/\.(INFO|NOTICE):/.test(line)) console.log(' ' + cyan(line));
|
|
472
|
+
else if (/\.DEBUG:/.test(line)) console.log(' ' + dim(line));
|
|
473
|
+
else console.log(' ' + line);
|
|
474
|
+
}
|
|
475
|
+
console.log('');
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
commands['log:clear'] = async function (args) {
|
|
479
|
+
const o = options(args), ctx = context(o);
|
|
480
|
+
const logDir = path.join(ctx.base, 'storage', 'logs');
|
|
481
|
+
const logFile = path.join(logDir, 'kreft.log');
|
|
482
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
483
|
+
fs.writeFileSync(logFile, '');
|
|
484
|
+
console.log(` ${green('DONE')} The log file (${path.relative(ctx.project, logFile)}) has been cleared.`);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/* ------------------------------------------------------------------ cache, events, database */
|
|
488
|
+
commands['cache:clear'] = async function (args) {
|
|
489
|
+
const o = options(args), ctx = context(o);
|
|
490
|
+
await withApp(o, ctx, async () => {
|
|
491
|
+
await require('./backend/cache.js').Cache.flush();
|
|
492
|
+
fs.rmSync(path.join(ctx.base, 'storage', 'cache'), { recursive: true, force: true });
|
|
493
|
+
console.log(` ${green('DONE')} The cache is empty.`);
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
commands['event:list'] = async function (args) {
|
|
497
|
+
const o = options(args), ctx = context(o);
|
|
498
|
+
require('./backend/index.js').installAlias();
|
|
499
|
+
require('./backend/app.js').boot(ctx.base);
|
|
500
|
+
const list = require('./backend/events.js').Event.listeners();
|
|
501
|
+
if (!list.length) return console.log(' No listeners in app/Listeners.');
|
|
502
|
+
for (const e of list) console.log(` ${e.event}\n${e.listeners.map((l) => ' ⇂ ' + l).join('\n')}`);
|
|
503
|
+
};
|
|
504
|
+
commands['db:wipe'] = async function (args) {
|
|
505
|
+
const o = options(args), ctx = context(o);
|
|
506
|
+
await withDb(o, ctx, async (db, b) => {
|
|
507
|
+
const dropped = await b.migrator.dropAll(db);
|
|
508
|
+
console.log(` Dropped ${dropped.length} table${dropped.length === 1 ? '' : 's'}.`);
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
commands['migrate:reset'] = async function (args) {
|
|
512
|
+
const o = options(args), ctx = context(o);
|
|
513
|
+
await withDb(o, ctx, async (db, b) => {
|
|
514
|
+
let total = 0;
|
|
515
|
+
for (;;) {
|
|
516
|
+
const undone = await b.migrator.rollback(db, migrationsOf(o, ctx), (f) => console.log(` ${green('UNDONE')} ${f}`));
|
|
517
|
+
if (!undone.length) break;
|
|
518
|
+
total += undone.length;
|
|
519
|
+
}
|
|
520
|
+
if (!total) console.log(' Nothing to undo.');
|
|
521
|
+
});
|
|
522
|
+
};
|
|
523
|
+
commands['migrate:refresh'] = async function (args) {
|
|
524
|
+
const o = options(args), ctx = context(o);
|
|
525
|
+
await commands['migrate:reset'](args.filter((a) => a !== '--seed'));
|
|
526
|
+
await withDb(o, ctx, async (db, b) => {
|
|
527
|
+
await b.migrator.migrate(db, migrationsOf(o, ctx), (f) => console.log(` ${green('DONE')} ${f}`));
|
|
528
|
+
if (o.flags.has('seed')) await runSeed(db, b, ctx, o.class);
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
/* ------------------------------------------------------------------ key, maintenance, about, tinker */
|
|
533
|
+
commands['key:generate'] = async function (args) {
|
|
534
|
+
const o = options(args), ctx = context(o);
|
|
535
|
+
const key = 'base64:' + crypto.randomBytes(32).toString('base64');
|
|
536
|
+
if (o.flags.has('show')) return console.log(' ' + key);
|
|
537
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
538
|
+
if (isProd && !o.flags.has('force')) {
|
|
539
|
+
console.warn(' Application is in production! Use --force to generate a new key.');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const file = path.join(ctx.project, '.env');
|
|
543
|
+
let text = '';
|
|
544
|
+
if (fs.existsSync(file)) {
|
|
545
|
+
text = fs.readFileSync(file, 'utf8');
|
|
546
|
+
} else {
|
|
547
|
+
const example = path.join(ctx.project, '.env.example');
|
|
548
|
+
if (fs.existsSync(example)) text = fs.readFileSync(example, 'utf8');
|
|
549
|
+
}
|
|
550
|
+
const hasKey = /^APP_KEY=.*$/m.test(text);
|
|
551
|
+
const updated = hasKey
|
|
552
|
+
? text.replace(/^APP_KEY=.*$/m, () => 'APP_KEY=' + key)
|
|
553
|
+
: text + (text.endsWith('\n') || !text ? '' : '\n') + 'APP_KEY=' + key + '\n';
|
|
554
|
+
fs.writeFileSync(file, updated, { mode: 0o600 });
|
|
555
|
+
process.env.APP_KEY = key;
|
|
556
|
+
|
|
557
|
+
// Revoke all active sessions in the database so all users are forced to log in again
|
|
558
|
+
let revokedSessions = 0;
|
|
559
|
+
try {
|
|
560
|
+
const { readConfig } = require('./kreftjs-config.js');
|
|
561
|
+
const config = readConfig(ctx.base);
|
|
562
|
+
const url = o.db || process.env.DATABASE_URL || config.db;
|
|
563
|
+
if (url) {
|
|
564
|
+
const { connect } = require('./db.js');
|
|
565
|
+
const db = await connect(url, { root: config.dir || ctx.project });
|
|
566
|
+
try {
|
|
567
|
+
const tables = await db.tables();
|
|
568
|
+
if (tables.some((t) => t.name === 'personal_access_tokens')) {
|
|
569
|
+
const res = await db.exec("DELETE FROM personal_access_tokens WHERE type = 'session'");
|
|
570
|
+
revokedSessions += (res && res.changes) || 0;
|
|
571
|
+
}
|
|
572
|
+
if (tables.some((t) => t.name === 'sessions')) {
|
|
573
|
+
const res = await db.exec("DELETE FROM sessions");
|
|
574
|
+
revokedSessions += (res && res.changes) || 0;
|
|
575
|
+
}
|
|
576
|
+
} finally {
|
|
577
|
+
await db.close();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
} catch (_) {}
|
|
581
|
+
|
|
582
|
+
// Invalidate any file-based sessions in storage/framework/sessions
|
|
583
|
+
try {
|
|
584
|
+
const sessDir = path.join(ctx.base, 'storage', 'framework', 'sessions');
|
|
585
|
+
if (fs.existsSync(sessDir)) {
|
|
586
|
+
for (const f of fs.readdirSync(sessDir)) {
|
|
587
|
+
if (!f.startsWith('.')) try { fs.unlinkSync(path.join(sessDir, f)); } catch (_) {}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (_) {}
|
|
591
|
+
|
|
592
|
+
console.log(` ${green('DONE')} A new APP_KEY [${key}] is set in .env.`);
|
|
593
|
+
console.log(` ${yellow('REVOKED')} All active user sessions destroyed. Users must log in again.`);
|
|
594
|
+
};
|
|
595
|
+
commands['key'] = commands['key:generate'];
|
|
596
|
+
|
|
597
|
+
const downFile = (ctx) => path.join(ctx.base, 'storage', 'framework', 'down.json');
|
|
598
|
+
commands.down = function (args) {
|
|
599
|
+
const o = options(args), ctx = context(o);
|
|
600
|
+
const get = (k) => (args.find((a) => a.startsWith(`--${k}=`)) || '').split('=').slice(1).join('=') || null;
|
|
601
|
+
const secret = get('secret') || (o.flags.has('with-secret') ? crypto.randomBytes(12).toString('hex') : null);
|
|
602
|
+
const data = { time: Math.floor(Date.now() / 1000), retry: Number(get('retry')) || null, message: get('message') || 'We are making the site better. Please come back soon.', secret };
|
|
603
|
+
fs.mkdirSync(path.dirname(downFile(ctx)), { recursive: true });
|
|
604
|
+
fs.writeFileSync(downFile(ctx), JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
605
|
+
console.log(` ${yellow('DOWN')} The app is in maintenance mode: visitors see a 503 page.${secret ? `\n You can still use it at: /${secret} (once; a cookie remembers you)` : ''}\n Back with: kreftjs up`);
|
|
606
|
+
};
|
|
607
|
+
commands.up = function (args) {
|
|
608
|
+
const o = options(args), ctx = context(o);
|
|
609
|
+
if (!fs.existsSync(downFile(ctx))) return console.log(' The app is not in maintenance mode.');
|
|
610
|
+
fs.rmSync(downFile(ctx));
|
|
611
|
+
console.log(` ${green('UP')} The app is live again.`);
|
|
612
|
+
};
|
|
613
|
+
commands.about = function (args) {
|
|
614
|
+
const o = options(args), ctx = context(o);
|
|
615
|
+
const lay = require('./layout.js').layout('.');
|
|
616
|
+
const row = (k, v) => console.log(` ${k.padEnd(22, '.')} ${v}`);
|
|
617
|
+
const ok = (b) => (b ? green('yes') : dim('no'));
|
|
618
|
+
const has = (p) => fs.existsSync(p);
|
|
619
|
+
console.log('\n Environment');
|
|
620
|
+
row('Kreft', pkg.version);
|
|
621
|
+
row('Node.js', process.version);
|
|
622
|
+
row('App name', process.env.APP_NAME || '-');
|
|
623
|
+
row('App URL', process.env.APP_URL || '-');
|
|
624
|
+
row('APP_KEY', process.env.APP_KEY ? green('set') : red('missing (kreftjs key:generate)'));
|
|
625
|
+
row('TypeScript', ok(has(path.join(ctx.project, 'tsconfig.json'))));
|
|
626
|
+
row('Maintenance mode', has(downFile(ctx)) ? yellow('down') : 'live');
|
|
627
|
+
console.log('\n Drivers');
|
|
628
|
+
row('Database', (process.env.DATABASE_URL || '-').replace(/\/\/([^:@/]+):[^@/]*@/, '//$1:•••@'));
|
|
629
|
+
row('Queue', process.env.QUEUE_CONNECTION || 'database');
|
|
630
|
+
row('Cache', process.env.CACHE_STORE || 'database');
|
|
631
|
+
row('Mail', process.env.MAIL_URL ? 'smtp' : 'log (printed)');
|
|
632
|
+
console.log('\n Folders');
|
|
633
|
+
row('Frontend', path.relative(process.cwd(), lay.frontend) || '.');
|
|
634
|
+
row('Backend', path.relative(process.cwd(), ctx.base) || '.');
|
|
635
|
+
row('Admin panel', ok(has(path.join(ctx.base, 'admin', 'index.html'))));
|
|
636
|
+
console.log('');
|
|
637
|
+
};
|
|
638
|
+
commands.tinker = async function (args) {
|
|
639
|
+
const o = options(args), ctx = context(o);
|
|
640
|
+
await withApp(o, ctx, async (db, b) => {
|
|
641
|
+
const repl = require('repl');
|
|
642
|
+
const app = require('./backend/app.js');
|
|
643
|
+
const models = {};
|
|
644
|
+
for (const file of app.filesOf('model')) { try { const M = app.unwrap(require(file)); if (M && M.name) models[M.name] = M; } catch (e) { console.error(` ${path.basename(file)}: ${e.message}`); } }
|
|
645
|
+
console.log(` Kreft ${pkg.version} tinker. Your models: ${Object.keys(models).join(', ') || '(none)'}. await works. .exit to leave.\n`);
|
|
646
|
+
const r = repl.start({ prompt: '> ', useGlobal: false, preview: true });
|
|
647
|
+
Object.assign(r.context, b, models, { db });
|
|
648
|
+
await new Promise((res) => r.on('exit', res));
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
/* ------------------------------------------------------------------ make:… */
|
|
653
|
+
const stub = {
|
|
654
|
+
job: (n) => `const { Job } = require('kreftjs/server')
|
|
655
|
+
|
|
656
|
+
// dispatch(new ${n}(…)) puts it on the queue; kreftjs queue:work (or kreftjs dev) runs handle()
|
|
657
|
+
class ${n} extends Job {
|
|
658
|
+
static tries = 3 // try again after an error, up to 3 times
|
|
659
|
+
static backoff = 10 // seconds between tries
|
|
660
|
+
|
|
661
|
+
constructor(data) {
|
|
662
|
+
super()
|
|
663
|
+
this.data = data // models are stored as their id and read again when the job runs
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async handle() {
|
|
667
|
+
// the work
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
failed(error) {
|
|
671
|
+
// after the last try
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
module.exports = ${n}
|
|
676
|
+
`,
|
|
677
|
+
event: (n) => `// await event(new ${n}(…)): every listener with static event = '${n}' runs
|
|
678
|
+
class ${n} {
|
|
679
|
+
constructor(data) {
|
|
680
|
+
this.data = data
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
module.exports = ${n}
|
|
685
|
+
`,
|
|
686
|
+
listener: (n, ev) => `// Runs when the ${ev || 'SomethingHappened'} event is sent: await event(new ${ev || 'SomethingHappened'}(…))
|
|
687
|
+
class ${n} {
|
|
688
|
+
static event = '${ev || 'SomethingHappened'}'
|
|
689
|
+
// static queue = true // run it later, on the queue
|
|
690
|
+
|
|
691
|
+
async handle(event) {
|
|
692
|
+
// event.data …
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
module.exports = ${n}
|
|
697
|
+
`,
|
|
698
|
+
mail: (n) => `const { Mailable } = require('kreftjs/server')
|
|
699
|
+
|
|
700
|
+
// await Mail.to(user.email).send(new ${n}(user)) or .queue(new ${n}(user))
|
|
701
|
+
class ${n} extends Mailable {
|
|
702
|
+
constructor(user) {
|
|
703
|
+
super()
|
|
704
|
+
this.user = user
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
build() {
|
|
708
|
+
return this.subject('${n.replace(/Mail$/, '').replace(/([a-z])([A-Z])/g, '$1 $2')}')
|
|
709
|
+
.markdown({
|
|
710
|
+
greeting: \`Hello \${this.user.name}\`,
|
|
711
|
+
lines: ['Thanks for being with us.'],
|
|
712
|
+
action: { text: 'Open the app', url: process.env.APP_URL || '/' },
|
|
713
|
+
})
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
module.exports = ${n}
|
|
718
|
+
`,
|
|
719
|
+
notification: (n) => `const { Notification, Url } = require('kreftjs/server')
|
|
720
|
+
|
|
721
|
+
// await user.notify(new ${n}(…))
|
|
722
|
+
class ${n} extends Notification {
|
|
723
|
+
// static queue = true // send it later, on the queue
|
|
724
|
+
|
|
725
|
+
constructor(data) {
|
|
726
|
+
super()
|
|
727
|
+
this.data = data
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
via(notifiable) {
|
|
731
|
+
return ['mail', 'database'] // 'database' needs the notifications table: kreftjs notifications:table
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
toMail(notifiable) {
|
|
735
|
+
return {
|
|
736
|
+
subject: '${n.replace(/([a-z])([A-Z])/g, '$1 $2')}',
|
|
737
|
+
greeting: \`Hello \${notifiable.name}\`,
|
|
738
|
+
lines: ['Something happened.'],
|
|
739
|
+
action: { text: 'Open the app', url: Url.to('/') },
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
toDatabase(notifiable) {
|
|
744
|
+
return { message: 'Something happened.' }
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
module.exports = ${n}
|
|
749
|
+
`,
|
|
750
|
+
policy: (n, model) => `// Found by itself for the ${model} model: await req.authorize('update', ${snake(model)}), or .middleware('can:update,${snake(model)}')
|
|
751
|
+
class ${n} {
|
|
752
|
+
// before(user, ability) { if (user.is_admin) return true } // admins may do everything
|
|
753
|
+
|
|
754
|
+
viewAny(user) { return true }
|
|
755
|
+
view(user, ${snake(model)}) { return true }
|
|
756
|
+
create(user) { return Boolean(user) }
|
|
757
|
+
update(user, ${snake(model)}) { return ${snake(model)}.user_id === user.id }
|
|
758
|
+
delete(user, ${snake(model)}) { return ${snake(model)}.user_id === user.id }
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
module.exports = ${n}
|
|
762
|
+
`,
|
|
763
|
+
request: (n) => `const { FormRequest } = require('kreftjs/server')
|
|
764
|
+
|
|
765
|
+
// in a controller: const data = await ${n}.validate(req)
|
|
766
|
+
class ${n} extends FormRequest {
|
|
767
|
+
authorize(req) {
|
|
768
|
+
return true // false: 403
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
rules(req) {
|
|
772
|
+
return {
|
|
773
|
+
// title: 'required|max:200',
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
messages() {
|
|
778
|
+
return {
|
|
779
|
+
// 'title.required': 'Give it a title.',
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
module.exports = ${n}
|
|
785
|
+
`,
|
|
786
|
+
resource: (n) => `const { JsonResource } = require('kreftjs/server')
|
|
787
|
+
|
|
788
|
+
// return new ${n}(model) or ${n}.collection(list) ({ data: … })
|
|
789
|
+
class ${n} extends JsonResource {
|
|
790
|
+
toArray(req) {
|
|
791
|
+
return {
|
|
792
|
+
id: this.id,
|
|
793
|
+
// name: this.name,
|
|
794
|
+
// posts: this.whenLoaded('posts'),
|
|
795
|
+
created_at: this.created_at,
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
module.exports = ${n}
|
|
801
|
+
`,
|
|
802
|
+
factory: (n, model, modelReq) => {
|
|
803
|
+
const m = (model || '').toLowerCase();
|
|
804
|
+
let fields = '';
|
|
805
|
+
if (m.includes('category') || m.includes('section') || m.includes('department')) {
|
|
806
|
+
fields = ` name: fake.unique().category(),
|
|
807
|
+
slug: (attrs) => fake.slug(attrs.name),
|
|
808
|
+
description: fake.sentence(),
|
|
809
|
+
is_active: true,`;
|
|
810
|
+
} else if (m.includes('tag')) {
|
|
811
|
+
fields = ` name: fake.unique().tag(),
|
|
812
|
+
slug: (attrs) => fake.slug(attrs.name),`;
|
|
813
|
+
} else if (m.includes('product') || m.includes('item')) {
|
|
814
|
+
fields = ` name: fake.product(),
|
|
815
|
+
slug: (attrs) => fake.slug(attrs.name),
|
|
816
|
+
sku: fake.sku('PRD'),
|
|
817
|
+
price: fake.price(10, 499),
|
|
818
|
+
description: fake.paragraph(),
|
|
819
|
+
is_active: true,`;
|
|
820
|
+
} else if (m.includes('post') || m.includes('article') || m.includes('blog') || m.includes('news')) {
|
|
821
|
+
fields = ` title: fake.sentence(),
|
|
822
|
+
slug: (attrs) => fake.slug(attrs.title),
|
|
823
|
+
content: fake.paragraphs(3),
|
|
824
|
+
is_published: true,`;
|
|
825
|
+
} else if (m.includes('brand') || m.includes('company')) {
|
|
826
|
+
fields = ` name: fake.unique().brand(),
|
|
827
|
+
slug: (attrs) => fake.slug(attrs.name),
|
|
828
|
+
is_active: true,`;
|
|
829
|
+
} else if (m.includes('user') || m.includes('customer') || m.includes('author') || m.includes('member')) {
|
|
830
|
+
fields = ` name: fake.name(),
|
|
831
|
+
email: fake.unique().email(),
|
|
832
|
+
password: 'password',`;
|
|
833
|
+
} else {
|
|
834
|
+
fields = ` name: fake.name(),
|
|
835
|
+
slug: (attrs) => fake.slug(attrs.name),
|
|
836
|
+
is_active: true,`;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return `const { Factory } = require('kreftjs/server')
|
|
840
|
+
const ${model} = require('${modelReq}')
|
|
841
|
+
|
|
842
|
+
// await ${model}.factory().count(10).create()
|
|
843
|
+
module.exports = Factory.define(${model}, (fake) => ({
|
|
844
|
+
${fields}
|
|
845
|
+
}))
|
|
846
|
+
`;
|
|
847
|
+
},
|
|
848
|
+
rule: (n) => `// In rules: { code: ['required', new ${n}()] }
|
|
849
|
+
class ${n} {
|
|
850
|
+
passes(attribute, value, data) {
|
|
851
|
+
return true // false: the message below
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
message() {
|
|
855
|
+
return 'The :attribute is invalid.'
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
module.exports = ${n}
|
|
860
|
+
`,
|
|
861
|
+
command: (n, sig) => `const { Command } = require('kreftjs/server')
|
|
862
|
+
|
|
863
|
+
// kreftjs ${sig} — your own command
|
|
864
|
+
class ${n} extends Command {
|
|
865
|
+
static signature = '${sig} {name?} {--force}'
|
|
866
|
+
static description = 'What ${sig} does'
|
|
867
|
+
|
|
868
|
+
async handle() {
|
|
869
|
+
const name = this.argument('name') || 'world'
|
|
870
|
+
this.info(\`Hello \${name}\`)
|
|
871
|
+
// this.option('force'), this.line(…), this.error(…), this.table(rows), await this.ask('Question?')
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
module.exports = ${n}
|
|
876
|
+
`,
|
|
877
|
+
observer: (n, model) => `// In app/Models/${model}.js: static observers = [require('../Observers/${n}')]
|
|
878
|
+
class ${n} {
|
|
879
|
+
async creating(${snake(model)}) {}
|
|
880
|
+
async created(${snake(model)}) {}
|
|
881
|
+
async updated(${snake(model)}) {}
|
|
882
|
+
async deleted(${snake(model)}) {}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
module.exports = ${n}
|
|
886
|
+
`,
|
|
887
|
+
};
|
|
888
|
+
function makeIn(folder, suffix, build, usage) {
|
|
889
|
+
return function (args) {
|
|
890
|
+
const o = options(args), ctx = context(o);
|
|
891
|
+
let { dirs, name } = nested(o._[0]);
|
|
892
|
+
if (!name) return console.error(' ' + usage);
|
|
893
|
+
if (suffix && !name.endsWith(suffix)) name += suffix;
|
|
894
|
+
return build(o, ctx, dirs, name, path.join(ctx.base, ...folder.split('/'), ...dirs, name + '.js'));
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
make.job = makeIn('app/Jobs', '', (o, c, d, n, f) => write(f, stub.job(n)), 'kreftjs make:job SendWelcomeEmail');
|
|
898
|
+
make.event = makeIn('app/Events', '', (o, c, d, n, f) => write(f, stub.event(n)), 'kreftjs make:event OrderShipped');
|
|
899
|
+
make.listener = makeIn('app/Listeners', '', (o, c, d, n, f) => write(f, stub.listener(n, o.event)), 'kreftjs make:listener SendShipmentNotice --event OrderShipped');
|
|
900
|
+
make.mail = makeIn('app/Mail', 'Mail', (o, c, d, n, f) => write(f, stub.mail(n)), 'kreftjs make:mail WelcomeMail');
|
|
901
|
+
make.notification = makeIn('app/Notifications', '', (o, c, d, n, f) => write(f, stub.notification(n)), 'kreftjs make:notification InvoicePaid');
|
|
902
|
+
make.policy = makeIn('app/Policies', 'Policy', (o, c, d, n, f) => write(f, stub.policy(n, o.model || n.replace(/Policy$/, ''))), 'kreftjs make:policy PostPolicy [--model Post]');
|
|
903
|
+
make.request = make.request || makeIn('app/Http/Requests', 'Request', (o, c, d, n, f) => write(f, stub.request(n)), 'kreftjs make:request StorePostRequest');
|
|
904
|
+
make.resource = make.resource || makeIn('app/Http/Resources', 'Resource', (o, c, d, n, f) => write(f, stub.resource(n)), 'kreftjs make:resource PostResource');
|
|
905
|
+
make.rule = make.rule || makeIn('app/Rules', '', (o, c, d, n, f) => write(f, stub.rule(n)), 'kreftjs make:rule Uppercase');
|
|
906
|
+
make.observer = makeIn('app/Observers', 'Observer', (o, c, d, n, f) => write(f, stub.observer(n, o.model || n.replace(/Observer$/, ''))), 'kreftjs make:observer PostObserver [--model Post]');
|
|
907
|
+
make.command = makeIn('app/Console/Commands', '', (o, c, d, n, f) => write(f, stub.command(n, o.command || n.replace(/Command$/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/-/, ':'))), 'kreftjs make:command SendReport [--command report:send]');
|
|
908
|
+
make.factory = function (args) {
|
|
909
|
+
const o = options(args), ctx = context(o);
|
|
910
|
+
let { name } = nested(o._[0]);
|
|
911
|
+
if (!name) return console.error(' kreftjs make:factory PostFactory [--model Post]');
|
|
912
|
+
if (!name.endsWith('Factory')) name += 'Factory';
|
|
913
|
+
const model = o.model ? nested(o.model) : { dirs: [], name: name.replace(/Factory$/, '') };
|
|
914
|
+
write(path.join(ctx.base, 'database', 'factories', name + '.js'), stub.factory(name, model.name, '../../app/Models/' + [...model.dirs, model.name].join('/')));
|
|
915
|
+
};
|
|
916
|
+
for (const k of ['job', 'event', 'listener', 'mail', 'notification', 'policy', 'request', 'resource', 'rule', 'observer', 'command', 'factory']) commands['make:' + k] = make[k];
|
|
917
|
+
|
|
918
|
+
/* ------------------------------------------------------------------ your own commands */
|
|
919
|
+
// 'report:send {user} {name?} {--force} {--days=7}' → { name: 'report:send', args: [...], opts: [...] }
|
|
920
|
+
function parseSignature(sig) {
|
|
921
|
+
const name = sig.trim().split(/\s+/)[0];
|
|
922
|
+
const args = [], opts = [];
|
|
923
|
+
for (const m of sig.matchAll(/\{([^}]+)\}/g)) {
|
|
924
|
+
const t = m[1].trim().split(':')[0].trim();
|
|
925
|
+
if (t.startsWith('--')) { const [k, d] = t.slice(2).split('='); opts.push({ name: k.replace(/\|.*$/, ''), def: d === undefined ? null : d, takes: t.includes('=') }); }
|
|
926
|
+
else args.push({ name: t.replace(/[?*]$/, '').replace(/=.*$/, ''), optional: /[?]$/.test(t) || t.includes('='), many: t.endsWith('*'), def: (t.split('=')[1] || null) });
|
|
927
|
+
}
|
|
928
|
+
return { name, args, opts };
|
|
929
|
+
}
|
|
930
|
+
const { Command } = require('./backend/command.js');
|
|
931
|
+
function customCommands(base) {
|
|
932
|
+
const dir = path.join(base, 'app', 'Console', 'Commands');
|
|
933
|
+
const out = [];
|
|
934
|
+
if (!fs.existsSync(dir)) return out;
|
|
935
|
+
require('./backend/index.js').installAlias();
|
|
936
|
+
const walk = (d) => {
|
|
937
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
938
|
+
const f = path.join(d, e.name);
|
|
939
|
+
if (e.isDirectory()) walk(f);
|
|
940
|
+
else if (/\.(c?js|[mc]?ts)$/.test(e.name) && !e.name.endsWith('.d.ts')) {
|
|
941
|
+
try { let C = require(f); if (C && C.default) C = C.default; if (C && C.signature) out.push({ C, ...parseSignature(C.signature), file: f }); }
|
|
942
|
+
catch (e2) { console.error(` ${path.relative(process.cwd(), f)}: ${e2.message}`); }
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
walk(dir);
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
async function runCustom(name, args) {
|
|
950
|
+
const o = options(args), ctx = context(o);
|
|
951
|
+
const found = customCommands(ctx.base).find((c) => c.name === name);
|
|
952
|
+
if (!found) return false;
|
|
953
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
954
|
+
const values = {}, opts = {};
|
|
955
|
+
found.args.forEach((a, i) => { values[a.name] = a.many ? positional.slice(i) : positional[i] ?? a.def ?? null; });
|
|
956
|
+
const missing = found.args.find((a) => !a.optional && (values[a.name] == null || (a.many && !values[a.name].length)));
|
|
957
|
+
if (missing) { console.error(` Missing the "${missing.name}" argument. kreftjs ${found.C.signature.replace(/\s*\{--[^}]+\}/g, '')}`); process.exitCode = 1; return true; }
|
|
958
|
+
for (const op of found.opts) {
|
|
959
|
+
const given = args.find((a) => a === '--' + op.name || a.startsWith(`--${op.name}=`));
|
|
960
|
+
opts[op.name] = op.takes ? (given && given.includes('=') ? given.split('=').slice(1).join('=') : op.def) : Boolean(given);
|
|
961
|
+
}
|
|
962
|
+
await withApp(o, ctx, async () => {
|
|
963
|
+
const cmd = new found.C();
|
|
964
|
+
Object.defineProperty(cmd, '__args', { value: values });
|
|
965
|
+
Object.defineProperty(cmd, '__opts', { value: opts });
|
|
966
|
+
await cmd.handle();
|
|
967
|
+
});
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
// Schedule.command('cache:clear') and this.call('…'): one command line
|
|
971
|
+
async function runCommand(line) {
|
|
972
|
+
const [name, ...args] = String(line).trim().split(/\s+/);
|
|
973
|
+
if (commands[name]) return commands[name](args);
|
|
974
|
+
if (!(await runCustom(name, args))) throw new Error(`Unknown command "${name}".`);
|
|
975
|
+
}
|
|
976
|
+
commands.list = function (args) {
|
|
977
|
+
const o = options(args), ctx = context(o);
|
|
978
|
+
const builtIn = Object.keys(commands).filter((c) => !['list', 'help', '--help', '-h', '--version', '-v'].includes(c)).sort();
|
|
979
|
+
const groups = {};
|
|
980
|
+
for (const c of builtIn) (groups[c.includes(':') ? c.split(':')[0] : ''] = groups[c.includes(':') ? c.split(':')[0] : ''] || []).push(c);
|
|
981
|
+
console.log(`\n Kreft ${pkg.version}\n\n Commands:`);
|
|
982
|
+
console.log(' new dev serve build update db vscode (kreftjs help for their options)');
|
|
983
|
+
console.log(' ' + (groups[''] || []).join(' '));
|
|
984
|
+
for (const [g, list] of Object.entries(groups)) if (g) console.log(` ${g}\n ${list.join(' ')}`);
|
|
985
|
+
const mine = customCommands(ctx.base);
|
|
986
|
+
if (mine.length) {
|
|
987
|
+
console.log(' yours (app/Console/Commands)');
|
|
988
|
+
mine.forEach((c) => console.log(` ${c.name.padEnd(24)} ${dim(c.C.description || '')}`));
|
|
989
|
+
}
|
|
990
|
+
console.log('');
|
|
991
|
+
};
|
|
992
|
+
commands.env = function () { console.log(` The app runs in the ${process.env.APP_ENV || 'local'} environment.`); };
|
|
993
|
+
|
|
994
|
+
module.exports = { Command, runCommand, runCustom, customCommands, parseSignature, withApp, tableStubs };
|