aegisnode 0.0.5 → 0.1.1
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 +190 -218
- package/package.json +12 -11
- package/scripts/smoke-test.js +87 -5
- package/src/cli/commands/createapp.js +4 -3
- package/src/cli/commands/doctor.js +17 -11
- package/src/cli/commands/fixapp.js +9 -4
- package/src/cli/commands/generate.js +33 -28
- package/src/cli/commands/generateloader.js +10 -4
- package/src/cli/commands/startproject.js +19 -12
- package/src/cli/index.js +16 -6
- package/src/cli/utils/apps.js +41 -34
- package/src/cli/utils/project.js +14 -6
- package/src/cli/utils/scaffolds.js +87 -31
- package/src/index.js +1 -0
- package/src/runtime/config.js +10 -10
- package/src/runtime/kernel.js +49 -32
- package/src/runtime/typescript.js +21 -0
- package/src/utils/source-files.js +78 -0
package/src/runtime/kernel.js
CHANGED
|
@@ -5,7 +5,7 @@ import https from 'https';
|
|
|
5
5
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import { fileURLToPath
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import ejs from 'ejs';
|
|
11
11
|
import helmet from 'helmet';
|
|
@@ -23,6 +23,13 @@ import { runLoaders } from './loaders.js';
|
|
|
23
23
|
import { createRuntimeHelpers } from './helpers.js';
|
|
24
24
|
import { createUploadManager, isMultipartRequestContentType, normalizeUploadsConfig } from './upload.js';
|
|
25
25
|
import { createMailManager, normalizeMailConfig } from './mail.js';
|
|
26
|
+
import { importProjectModule } from './typescript.js';
|
|
27
|
+
import {
|
|
28
|
+
hasNamedSourceSuffix,
|
|
29
|
+
isSourceFileName,
|
|
30
|
+
resolveSourceFile,
|
|
31
|
+
resolveSourceIndexFile,
|
|
32
|
+
} from '../utils/source-files.js';
|
|
26
33
|
|
|
27
34
|
const ROUTE_DEFINITION = 'aegis:routes';
|
|
28
35
|
const PROJECT_ROUTE_DEFINITION = 'aegis:project-routes';
|
|
@@ -33,7 +40,7 @@ const EMPTY_ROUTE_CONTEXT = Object.freeze({});
|
|
|
33
40
|
const REQUEST_I18N_CONTEXT = new AsyncLocalStorage();
|
|
34
41
|
|
|
35
42
|
function exists(filePath) {
|
|
36
|
-
return fs.existsSync(filePath);
|
|
43
|
+
return typeof filePath === 'string' && fs.existsSync(filePath);
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function isRouterInstance(value) {
|
|
@@ -653,7 +660,7 @@ const DB_LIBRARY_PATTERN = /\b(querymesh|mongoose|pg|postgres|postgresql|mysql|m
|
|
|
653
660
|
const IMPORT_FROM_PATTERN = /import\s+[\s\S]*?\sfrom\s+['"]([^'"]+)['"]/g;
|
|
654
661
|
const IMPORT_SIDE_EFFECT_PATTERN = /import\s+['"]([^'"]+)['"]/g;
|
|
655
662
|
const REQUIRE_PATTERN = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
656
|
-
const MODEL_IMPORT_PATH_PATTERN = /(?:^|\/)(models(?:\.js)?|[^/]+\.model(?:\.js)?)$/i;
|
|
663
|
+
const MODEL_IMPORT_PATH_PATTERN = /(?:^|\/)(models(?:\.(?:js|ts))?|[^/]+\.model(?:\.(?:js|ts))?)$/i;
|
|
657
664
|
|
|
658
665
|
function extractImportSpecifiers(source) {
|
|
659
666
|
const imports = [];
|
|
@@ -693,26 +700,26 @@ async function collectStrictLayerFiles(appRoot) {
|
|
|
693
700
|
const routeFiles = [];
|
|
694
701
|
const serviceFiles = [];
|
|
695
702
|
|
|
696
|
-
const routesFile = path.join(appRoot, 'routes
|
|
703
|
+
const routesFile = resolveSourceFile(path.join(appRoot, 'routes'));
|
|
697
704
|
if (exists(routesFile)) {
|
|
698
705
|
routeFiles.push(routesFile);
|
|
699
706
|
}
|
|
700
707
|
|
|
701
708
|
const routesDir = path.join(appRoot, 'routes');
|
|
702
709
|
for (const fileName of await loadDirectoryFiles(routesDir)) {
|
|
703
|
-
if (!fileName
|
|
710
|
+
if (!isSourceFileName(fileName)) {
|
|
704
711
|
continue;
|
|
705
712
|
}
|
|
706
713
|
routeFiles.push(path.join(routesDir, fileName));
|
|
707
714
|
}
|
|
708
715
|
|
|
709
|
-
const servicesFile = path.join(appRoot, 'services
|
|
716
|
+
const servicesFile = resolveSourceFile(path.join(appRoot, 'services'));
|
|
710
717
|
if (exists(servicesFile)) {
|
|
711
718
|
serviceFiles.push(servicesFile);
|
|
712
719
|
}
|
|
713
720
|
|
|
714
721
|
for (const fileName of await loadDirectoryFiles(appRoot)) {
|
|
715
|
-
if (!fileName
|
|
722
|
+
if (!hasNamedSourceSuffix(fileName, '.service')) {
|
|
716
723
|
continue;
|
|
717
724
|
}
|
|
718
725
|
serviceFiles.push(path.join(appRoot, fileName));
|
|
@@ -720,7 +727,7 @@ async function collectStrictLayerFiles(appRoot) {
|
|
|
720
727
|
|
|
721
728
|
const servicesDir = path.join(appRoot, 'services');
|
|
722
729
|
for (const fileName of await loadDirectoryFiles(servicesDir)) {
|
|
723
|
-
if (!fileName
|
|
730
|
+
if (!isSourceFileName(fileName)) {
|
|
724
731
|
continue;
|
|
725
732
|
}
|
|
726
733
|
serviceFiles.push(path.join(servicesDir, fileName));
|
|
@@ -1829,8 +1836,7 @@ function extractCsrfToken(req, csrfConfig) {
|
|
|
1829
1836
|
}
|
|
1830
1837
|
|
|
1831
1838
|
async function importModule(filePath) {
|
|
1832
|
-
|
|
1833
|
-
return import(moduleUrl);
|
|
1839
|
+
return importProjectModule(filePath);
|
|
1834
1840
|
}
|
|
1835
1841
|
|
|
1836
1842
|
export function defineRoutes(register) {
|
|
@@ -2357,7 +2363,7 @@ async function registerControllers({ appName, appRoot, container, logger }) {
|
|
|
2357
2363
|
const files = await loadDirectoryFiles(directory);
|
|
2358
2364
|
|
|
2359
2365
|
for (const fileName of files) {
|
|
2360
|
-
if (!fileName
|
|
2366
|
+
if (!isSourceFileName(fileName)) {
|
|
2361
2367
|
continue;
|
|
2362
2368
|
}
|
|
2363
2369
|
|
|
@@ -2370,23 +2376,23 @@ async function registerControllers({ appName, appRoot, container, logger }) {
|
|
|
2370
2376
|
}
|
|
2371
2377
|
}
|
|
2372
2378
|
|
|
2373
|
-
const singleFiles = ['controllers
|
|
2374
|
-
for (const
|
|
2375
|
-
const filePath = path.join(appRoot,
|
|
2379
|
+
const singleFiles = ['controllers', 'views'];
|
|
2380
|
+
for (const baseName of singleFiles) {
|
|
2381
|
+
const filePath = resolveSourceFile(path.join(appRoot, baseName));
|
|
2376
2382
|
if (!exists(filePath)) {
|
|
2377
2383
|
continue;
|
|
2378
2384
|
}
|
|
2379
2385
|
|
|
2380
2386
|
const loaded = await importModule(filePath);
|
|
2381
2387
|
const controller = loaded.default ?? loaded;
|
|
2382
|
-
const controllerName = normalizeControllerName(
|
|
2388
|
+
const controllerName = normalizeControllerName(path.basename(filePath));
|
|
2383
2389
|
container.set(`controller:${appName}.${controllerName}`, controller);
|
|
2384
2390
|
logger.debug('Controller registered: controller:%s.%s', appName, controllerName);
|
|
2385
2391
|
}
|
|
2386
2392
|
}
|
|
2387
2393
|
|
|
2388
2394
|
async function registerModels({ appName, appRoot, container, logger }) {
|
|
2389
|
-
const modelsFile = path.join(appRoot, 'models
|
|
2395
|
+
const modelsFile = resolveSourceFile(path.join(appRoot, 'models'));
|
|
2390
2396
|
if (exists(modelsFile)) {
|
|
2391
2397
|
const loaded = await importModule(modelsFile);
|
|
2392
2398
|
const exported = loaded.default ?? loaded;
|
|
@@ -2403,7 +2409,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
|
|
|
2403
2409
|
|
|
2404
2410
|
const rootFiles = await loadDirectoryFiles(appRoot);
|
|
2405
2411
|
for (const fileName of rootFiles) {
|
|
2406
|
-
if (!fileName
|
|
2412
|
+
if (!hasNamedSourceSuffix(fileName, '.model')) {
|
|
2407
2413
|
continue;
|
|
2408
2414
|
}
|
|
2409
2415
|
|
|
@@ -2419,7 +2425,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
|
|
|
2419
2425
|
const files = await loadDirectoryFiles(modelsDir);
|
|
2420
2426
|
|
|
2421
2427
|
for (const fileName of files) {
|
|
2422
|
-
if (!fileName
|
|
2428
|
+
if (!isSourceFileName(fileName)) {
|
|
2423
2429
|
continue;
|
|
2424
2430
|
}
|
|
2425
2431
|
|
|
@@ -2433,7 +2439,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
|
|
|
2433
2439
|
}
|
|
2434
2440
|
|
|
2435
2441
|
async function registerValidators({ appName, appRoot, container, logger }) {
|
|
2436
|
-
const validatorsFile = path.join(appRoot, 'validators
|
|
2442
|
+
const validatorsFile = resolveSourceFile(path.join(appRoot, 'validators'));
|
|
2437
2443
|
if (exists(validatorsFile)) {
|
|
2438
2444
|
const loaded = await importModule(validatorsFile);
|
|
2439
2445
|
const exported = loaded.default ?? loaded;
|
|
@@ -2450,7 +2456,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
|
|
|
2450
2456
|
|
|
2451
2457
|
const rootFiles = await loadDirectoryFiles(appRoot);
|
|
2452
2458
|
for (const fileName of rootFiles) {
|
|
2453
|
-
if (!fileName
|
|
2459
|
+
if (!hasNamedSourceSuffix(fileName, '.validator')) {
|
|
2454
2460
|
continue;
|
|
2455
2461
|
}
|
|
2456
2462
|
|
|
@@ -2466,7 +2472,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
|
|
|
2466
2472
|
const files = await loadDirectoryFiles(validatorsDir);
|
|
2467
2473
|
|
|
2468
2474
|
for (const fileName of files) {
|
|
2469
|
-
if (!fileName
|
|
2475
|
+
if (!isSourceFileName(fileName)) {
|
|
2470
2476
|
continue;
|
|
2471
2477
|
}
|
|
2472
2478
|
|
|
@@ -2480,7 +2486,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
|
|
|
2480
2486
|
}
|
|
2481
2487
|
|
|
2482
2488
|
async function registerServices({ appName, appRoot, container, logger }) {
|
|
2483
|
-
const servicesFile = path.join(appRoot, 'services
|
|
2489
|
+
const servicesFile = resolveSourceFile(path.join(appRoot, 'services'));
|
|
2484
2490
|
if (exists(servicesFile)) {
|
|
2485
2491
|
const loaded = await importModule(servicesFile);
|
|
2486
2492
|
const exported = loaded.default ?? loaded;
|
|
@@ -2495,11 +2501,25 @@ async function registerServices({ appName, appRoot, container, logger }) {
|
|
|
2495
2501
|
}
|
|
2496
2502
|
}
|
|
2497
2503
|
|
|
2504
|
+
const rootFiles = await loadDirectoryFiles(appRoot);
|
|
2505
|
+
for (const fileName of rootFiles) {
|
|
2506
|
+
if (!hasNamedSourceSuffix(fileName, '.service')) {
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
const filePath = path.join(appRoot, fileName);
|
|
2511
|
+
const loaded = await importModule(filePath);
|
|
2512
|
+
const service = loaded.default ?? loaded;
|
|
2513
|
+
const serviceName = normalizeServiceName(fileName);
|
|
2514
|
+
container.set(`service:${appName}.${serviceName}`, service);
|
|
2515
|
+
logger.debug('Service registered: service:%s.%s', appName, serviceName);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2498
2518
|
const servicesDir = path.join(appRoot, 'services');
|
|
2499
2519
|
const files = await loadDirectoryFiles(servicesDir);
|
|
2500
2520
|
|
|
2501
2521
|
for (const fileName of files) {
|
|
2502
|
-
if (!fileName
|
|
2522
|
+
if (!isSourceFileName(fileName)) {
|
|
2503
2523
|
continue;
|
|
2504
2524
|
}
|
|
2505
2525
|
|
|
@@ -2513,9 +2533,8 @@ async function registerServices({ appName, appRoot, container, logger }) {
|
|
|
2513
2533
|
}
|
|
2514
2534
|
|
|
2515
2535
|
async function registerSubscribers({ appName, appRoot, context, logger }) {
|
|
2516
|
-
const subscribersFile =
|
|
2517
|
-
|
|
2518
|
-
: path.join(appRoot, 'subscribers', 'index.js');
|
|
2536
|
+
const subscribersFile = resolveSourceFile(path.join(appRoot, 'subscribers'))
|
|
2537
|
+
|| resolveSourceIndexFile(path.join(appRoot, 'subscribers'));
|
|
2519
2538
|
|
|
2520
2539
|
if (!exists(subscribersFile)) {
|
|
2521
2540
|
return;
|
|
@@ -2531,9 +2550,8 @@ async function registerSubscribers({ appName, appRoot, context, logger }) {
|
|
|
2531
2550
|
}
|
|
2532
2551
|
|
|
2533
2552
|
async function mountAppRoutes({ appDefinition, appRoot, context, expressApp, routeContext = null }) {
|
|
2534
|
-
const routesFile =
|
|
2535
|
-
|
|
2536
|
-
: path.join(appRoot, 'routes', 'index.js');
|
|
2553
|
+
const routesFile = resolveSourceFile(path.join(appRoot, 'routes'))
|
|
2554
|
+
|| resolveSourceIndexFile(path.join(appRoot, 'routes'));
|
|
2537
2555
|
if (!exists(routesFile)) {
|
|
2538
2556
|
return;
|
|
2539
2557
|
}
|
|
@@ -2599,9 +2617,8 @@ async function mountAppRoutes({ appDefinition, appRoot, context, expressApp, rou
|
|
|
2599
2617
|
}
|
|
2600
2618
|
|
|
2601
2619
|
async function loadProjectRoutes(rootDir) {
|
|
2602
|
-
const routesFile =
|
|
2603
|
-
|
|
2604
|
-
: path.join(rootDir, 'routes', 'index.js');
|
|
2620
|
+
const routesFile = resolveSourceFile(path.join(rootDir, 'routes'))
|
|
2621
|
+
|| resolveSourceIndexFile(path.join(rootDir, 'routes'));
|
|
2605
2622
|
|
|
2606
2623
|
if (!exists(routesFile)) {
|
|
2607
2624
|
return null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { pathToFileURL } from 'url';
|
|
2
|
+
import { isTypeScriptFile } from '../utils/source-files.js';
|
|
3
|
+
|
|
4
|
+
let registrationPromise = null;
|
|
5
|
+
|
|
6
|
+
export async function registerTypeScriptRuntime() {
|
|
7
|
+
if (!registrationPromise) {
|
|
8
|
+
registrationPromise = import('tsx/esm/api').then(({ register }) => register());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return registrationPromise;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function importProjectModule(filePath) {
|
|
15
|
+
if (isTypeScriptFile(filePath)) {
|
|
16
|
+
await registerTypeScriptRuntime();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const moduleUrl = `${pathToFileURL(filePath).href}?t=${Date.now()}`;
|
|
20
|
+
return import(moduleUrl);
|
|
21
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export const SOURCE_EXTENSIONS = Object.freeze(['.js', '.ts']);
|
|
5
|
+
export const DEFAULT_SOURCE_EXTENSION = '.js';
|
|
6
|
+
export const TYPESCRIPT_SOURCE_EXTENSION = '.ts';
|
|
7
|
+
|
|
8
|
+
const SOURCE_FILE_PATTERN = /\.(?:js|ts)$/i;
|
|
9
|
+
|
|
10
|
+
function escapeRegExp(value) {
|
|
11
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isSourceFileName(fileName) {
|
|
15
|
+
return SOURCE_FILE_PATTERN.test(String(fileName || ''));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isTypeScriptFile(filePath) {
|
|
19
|
+
return String(filePath || '').toLowerCase().endsWith(TYPESCRIPT_SOURCE_EXTENSION);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function stripSourceExtension(fileName) {
|
|
23
|
+
return String(fileName || '').replace(/\.(?:js|ts)$/i, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function hasNamedSourceSuffix(fileName, suffix) {
|
|
27
|
+
return new RegExp(`${escapeRegExp(suffix)}\\.(?:js|ts)$`, 'i').test(String(fileName || ''));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveSourceFile(basePath, extensions = SOURCE_EXTENSIONS) {
|
|
31
|
+
for (const extension of extensions) {
|
|
32
|
+
const candidate = `${basePath}${extension}`;
|
|
33
|
+
if (fs.existsSync(candidate)) {
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveSourceIndexFile(directoryPath, extensions = SOURCE_EXTENSIONS) {
|
|
42
|
+
for (const extension of extensions) {
|
|
43
|
+
const candidate = path.join(directoryPath, `index${extension}`);
|
|
44
|
+
if (fs.existsSync(candidate)) {
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function detectProjectSourceExtension(rootDir) {
|
|
53
|
+
const tsSignals = [
|
|
54
|
+
resolveSourceFile(path.join(rootDir, 'settings'), [TYPESCRIPT_SOURCE_EXTENSION]),
|
|
55
|
+
resolveSourceFile(path.join(rootDir, 'app'), [TYPESCRIPT_SOURCE_EXTENSION]),
|
|
56
|
+
resolveSourceFile(path.join(rootDir, 'routes'), [TYPESCRIPT_SOURCE_EXTENSION]),
|
|
57
|
+
resolveSourceIndexFile(path.join(rootDir, 'settings'), [TYPESCRIPT_SOURCE_EXTENSION]),
|
|
58
|
+
];
|
|
59
|
+
if (tsSignals.some(Boolean)) {
|
|
60
|
+
return TYPESCRIPT_SOURCE_EXTENSION;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const jsSignals = [
|
|
64
|
+
resolveSourceFile(path.join(rootDir, 'settings'), [DEFAULT_SOURCE_EXTENSION]),
|
|
65
|
+
resolveSourceFile(path.join(rootDir, 'app'), [DEFAULT_SOURCE_EXTENSION]),
|
|
66
|
+
resolveSourceFile(path.join(rootDir, 'routes'), [DEFAULT_SOURCE_EXTENSION]),
|
|
67
|
+
resolveSourceIndexFile(path.join(rootDir, 'settings'), [DEFAULT_SOURCE_EXTENSION]),
|
|
68
|
+
];
|
|
69
|
+
if (jsSignals.some(Boolean)) {
|
|
70
|
+
return DEFAULT_SOURCE_EXTENSION;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (fs.existsSync(path.join(rootDir, 'tsconfig.json'))) {
|
|
74
|
+
return TYPESCRIPT_SOURCE_EXTENSION;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return DEFAULT_SOURCE_EXTENSION;
|
|
78
|
+
}
|