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.
@@ -1,7 +1,11 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { pathToFileURL } from 'url';
4
3
  import { ensureDir, ensureValidName, exists, normalizeMountPath, writeFile } from './fs.js';
4
+ import { importProjectModule } from '../../runtime/typescript.js';
5
+ import {
6
+ detectProjectSourceExtension,
7
+ resolveSourceFile,
8
+ } from '../../utils/source-files.js';
5
9
  import {
6
10
  renderAppModelTest,
7
11
  renderAppModelsFile,
@@ -15,6 +19,7 @@ import {
15
19
  renderAppValidatorsFile,
16
20
  renderAppViewsFile,
17
21
  renderSettingsApps,
22
+ withSourceExtension,
18
23
  } from './scaffolds.js';
19
24
 
20
25
  export const APPS_START = '// AEGIS_APPS_START';
@@ -45,24 +50,23 @@ export function toImportName(appName) {
45
50
  }
46
51
 
47
52
  async function readDefaultExport(filePath) {
48
- const moduleUrl = `${pathToFileURL(filePath).href}?t=${Date.now()}`;
49
- const loaded = await import(moduleUrl);
53
+ const loaded = await importProjectModule(filePath);
50
54
  return loaded?.default;
51
55
  }
52
56
 
53
57
  export async function detectSettingsMode(projectRoot) {
54
- const single = path.join(projectRoot, 'settings.js');
55
- const split = path.join(projectRoot, 'settings', 'apps.js');
58
+ const single = resolveSourceFile(path.join(projectRoot, 'settings'));
59
+ const split = resolveSourceFile(path.join(projectRoot, 'settings', 'apps'));
56
60
 
57
- if (await exists(single)) {
61
+ if (single && await exists(single)) {
58
62
  return { mode: 'single', file: single };
59
63
  }
60
64
 
61
- if (await exists(split)) {
65
+ if (split && await exists(split)) {
62
66
  return { mode: 'split', file: split };
63
67
  }
64
68
 
65
- throw new Error(`Not an AegisNode project root: missing ${single} (or legacy ${split})`);
69
+ throw new Error(`Not an AegisNode project root: missing settings.js/settings.ts (or legacy settings/apps.js/settings/apps.ts)`);
66
70
  }
67
71
 
68
72
  export async function readAppsConfig(settingsMode) {
@@ -111,65 +115,67 @@ async function updateSingleSettingsApps(settingsFile, apps) {
111
115
  }
112
116
 
113
117
  export async function updateAppRegistry(projectRoot, apps, settingsMode) {
118
+ const sourceExtension = detectProjectSourceExtension(projectRoot);
119
+
114
120
  if (settingsMode.mode === 'single') {
115
121
  await updateSingleSettingsApps(settingsMode.file, apps);
116
122
  return;
117
123
  }
118
124
 
119
- await writeFile(path.join(projectRoot, 'settings', 'apps.js'), renderSettingsApps(apps));
125
+ await writeFile(path.join(projectRoot, 'settings', withSourceExtension('apps', sourceExtension)), renderSettingsApps(apps));
120
126
  }
121
127
 
122
- export function getAppScaffoldEntries(appName) {
128
+ export function getAppScaffoldEntries(appName, sourceExtension = '.js') {
123
129
  const appRoot = path.join('apps', appName);
124
130
  return [
125
131
  {
126
- target: path.join(appRoot, 'views.js'),
127
- content: renderAppViewsFile(appName),
132
+ target: path.join(appRoot, withSourceExtension('views', sourceExtension)),
133
+ content: renderAppViewsFile(appName, sourceExtension),
128
134
  },
129
135
  {
130
- target: path.join(appRoot, 'models.js'),
136
+ target: path.join(appRoot, withSourceExtension('models', sourceExtension)),
131
137
  content: renderAppModelsFile(appName),
132
138
  },
133
139
  {
134
- target: path.join(appRoot, 'validators.js'),
140
+ target: path.join(appRoot, withSourceExtension('validators', sourceExtension)),
135
141
  content: renderAppValidatorsFile(appName),
136
142
  },
137
143
  {
138
- target: path.join(appRoot, 'services.js'),
144
+ target: path.join(appRoot, withSourceExtension('services', sourceExtension)),
139
145
  content: renderAppServicesFile(appName),
140
146
  },
141
147
  {
142
- target: path.join(appRoot, 'utils.js'),
148
+ target: path.join(appRoot, withSourceExtension('utils', sourceExtension)),
143
149
  content: renderAppUtilsFile(),
144
150
  },
145
151
  {
146
- target: path.join(appRoot, 'subscribers.js'),
152
+ target: path.join(appRoot, withSourceExtension('subscribers', sourceExtension)),
147
153
  content: renderAppSubscribersFile(appName),
148
154
  },
149
155
  {
150
- target: path.join(appRoot, 'routes.js'),
151
- content: renderAppRoutes(appName),
156
+ target: path.join(appRoot, withSourceExtension('routes', sourceExtension)),
157
+ content: renderAppRoutes(appName, sourceExtension),
152
158
  },
153
159
  {
154
- target: path.join(appRoot, 'tests', 'models.test.js'),
155
- content: renderAppModelTest(appName),
160
+ target: path.join(appRoot, 'tests', withSourceExtension('models.test', sourceExtension)),
161
+ content: renderAppModelTest(appName, sourceExtension),
156
162
  },
157
163
  {
158
- target: path.join(appRoot, 'tests', 'validators.test.js'),
159
- content: renderAppValidatorTest(appName),
164
+ target: path.join(appRoot, 'tests', withSourceExtension('validators.test', sourceExtension)),
165
+ content: renderAppValidatorTest(appName, sourceExtension),
160
166
  },
161
167
  {
162
- target: path.join(appRoot, 'tests', 'services.test.js'),
163
- content: renderAppServiceTest(appName),
168
+ target: path.join(appRoot, 'tests', withSourceExtension('services.test', sourceExtension)),
169
+ content: renderAppServiceTest(appName, sourceExtension),
164
170
  },
165
171
  {
166
- target: path.join(appRoot, 'tests', 'routes.test.js'),
167
- content: renderAppRoutesTest(appName),
172
+ target: path.join(appRoot, 'tests', withSourceExtension('routes.test', sourceExtension)),
173
+ content: renderAppRoutesTest(appName, sourceExtension),
168
174
  },
169
175
  ];
170
176
  }
171
177
 
172
- export async function ensureAppScaffold(projectRoot, appName, { overwrite = false } = {}) {
178
+ export async function ensureAppScaffold(projectRoot, appName, { overwrite = false, sourceExtension = '.js' } = {}) {
173
179
  ensureValidName(appName, 'app');
174
180
 
175
181
  const appRoot = path.join(projectRoot, 'apps', appName);
@@ -179,7 +185,7 @@ export async function ensureAppScaffold(projectRoot, appName, { overwrite = fals
179
185
  const written = [];
180
186
  const skipped = [];
181
187
 
182
- for (const entry of getAppScaffoldEntries(appName)) {
188
+ for (const entry of getAppScaffoldEntries(appName, sourceExtension)) {
183
189
  const target = path.join(projectRoot, entry.target);
184
190
  if (!overwrite && await exists(target)) {
185
191
  skipped.push(target);
@@ -197,12 +203,13 @@ export async function ensureAppScaffold(projectRoot, appName, { overwrite = fals
197
203
  };
198
204
  }
199
205
 
200
- export async function updateProjectRoutesFile(projectRoot, appName, mountPath) {
201
- const routesFile = path.join(projectRoot, 'routes.js');
202
- if (!(await exists(routesFile))) {
206
+ export async function updateProjectRoutesFile(projectRoot, appName, mountPath, sourceExtension = null) {
207
+ const effectiveExtension = sourceExtension || detectProjectSourceExtension(projectRoot);
208
+ const routesFile = resolveSourceFile(path.join(projectRoot, 'routes'), [effectiveExtension]) || resolveSourceFile(path.join(projectRoot, 'routes'));
209
+ if (!routesFile || !(await exists(routesFile))) {
203
210
  // Keep backward compatibility for legacy projects that still use routes/index.js.
204
211
  return {
205
- routesFile,
212
+ routesFile: routesFile || path.join(projectRoot, withSourceExtension('routes', effectiveExtension)),
206
213
  updatedImport: false,
207
214
  updatedRoute: false,
208
215
  skipped: true,
@@ -210,7 +217,7 @@ export async function updateProjectRoutesFile(projectRoot, appName, mountPath) {
210
217
  }
211
218
 
212
219
  const importName = toImportName(appName);
213
- const importLine = `import ${importName} from './apps/${appName}/routes.js';`;
220
+ const importLine = `import ${importName} from './apps/${appName}/routes${effectiveExtension}';`;
214
221
  const routeLine = ` route.use(${JSON.stringify(mountPath)}, ${importName});`;
215
222
  const routePattern = new RegExp(`route\\.use\\([^\\n]*,\\s*${escapeRegExp(importName)}\\s*\\);`);
216
223
 
@@ -1,16 +1,20 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { exists } from './fs.js';
3
+ import { detectProjectSourceExtension, resolveSourceFile, resolveSourceIndexFile } from '../../utils/source-files.js';
4
4
 
5
5
  export async function hasRoutesFile(projectRoot) {
6
- return (await exists(path.join(projectRoot, 'routes.js')))
7
- || (await exists(path.join(projectRoot, 'routes', 'index.js')));
6
+ return Boolean(
7
+ resolveSourceFile(path.join(projectRoot, 'routes'))
8
+ || resolveSourceIndexFile(path.join(projectRoot, 'routes')),
9
+ );
8
10
  }
9
11
 
10
12
  export async function hasSettingsFile(projectRoot) {
11
- return (await exists(path.join(projectRoot, 'settings.js')))
12
- || (await exists(path.join(projectRoot, 'settings', 'index.js')))
13
- || (await exists(path.join(projectRoot, 'settings', 'apps.js')));
13
+ return Boolean(
14
+ resolveSourceFile(path.join(projectRoot, 'settings'))
15
+ || resolveSourceIndexFile(path.join(projectRoot, 'settings'))
16
+ || resolveSourceFile(path.join(projectRoot, 'settings', 'apps')),
17
+ );
14
18
  }
15
19
 
16
20
  export async function isProjectRoot(projectRoot) {
@@ -65,3 +69,7 @@ export async function resolveProjectRoot(projectRootHint) {
65
69
 
66
70
  throw new Error(`Could not find an AegisNode project from ${startDir}. Run inside project or use --project <path>.`);
67
71
  }
72
+
73
+ export function getProjectSourceExtension(projectRoot) {
74
+ return detectProjectSourceExtension(projectRoot);
75
+ }
@@ -6,31 +6,42 @@ export function toPascalCase(value) {
6
6
  .join('');
7
7
  }
8
8
 
9
+ export function withSourceExtension(baseName, extension = '.js') {
10
+ return `${baseName}${extension}`;
11
+ }
12
+
9
13
  function renderAppEntries(apps, indent = ' ') {
10
14
  return apps
11
15
  .map((app) => `${indent}{ name: ${JSON.stringify(app.name)}, mount: ${JSON.stringify(app.mount)} },`)
12
16
  .join('\n');
13
17
  }
14
18
 
15
- export function renderProjectPackageJson(projectName) {
16
- return `${JSON.stringify(
17
- {
18
- name: projectName,
19
- version: '1.0.0',
20
- private: true,
21
- type: 'module',
22
- scripts: {
23
- dev: 'aegisnode runserver',
24
- start: 'node loader.cjs',
25
- test: 'node --test',
26
- },
27
- dependencies: {
28
- aegisnode: '^0.1.0',
29
- },
19
+ export function renderProjectPackageJson(projectName, { typescript = false } = {}) {
20
+ const packageJson = {
21
+ name: projectName,
22
+ version: '1.0.0',
23
+ private: true,
24
+ type: 'module',
25
+ scripts: {
26
+ dev: 'aegisnode runserver',
27
+ start: 'node loader.cjs',
28
+ test: typescript ? 'node --import tsx/esm --test' : 'node --test',
30
29
  },
31
- null,
32
- 2,
33
- )}\n`;
30
+ dependencies: {
31
+ aegisnode: '^0.1.0',
32
+ },
33
+ };
34
+
35
+ if (typescript) {
36
+ packageJson.scripts.typecheck = 'tsc --noEmit';
37
+ packageJson.devDependencies = {
38
+ '@types/node': '^24.0.0',
39
+ tsx: '^4.21.0',
40
+ typescript: '^5.9.3',
41
+ };
42
+ }
43
+
44
+ return `${JSON.stringify(packageJson, null, 2)}\n`;
34
45
  }
35
46
 
36
47
  export function renderProjectAppJs() {
@@ -45,7 +56,18 @@ runProject({ rootDir: __dirname });
45
56
  `;
46
57
  }
47
58
 
48
- export function renderProjectLoaderCjs() {
59
+ export function renderProjectLoaderCjs(sourceExtension = '.js') {
60
+ if (sourceExtension === '.ts') {
61
+ return `import('aegisnode').then(async ({ registerTypeScriptRuntime }) => {
62
+ await registerTypeScriptRuntime();
63
+ return import('./app.ts');
64
+ }).catch((error) => {
65
+ console.error(error?.stack || error?.message || String(error));
66
+ process.exitCode = 1;
67
+ });
68
+ `;
69
+ }
70
+
49
71
  return `import('./app.js').catch((error) => {
50
72
  console.error(error?.stack || error?.message || String(error));
51
73
  process.exitCode = 1;
@@ -53,6 +75,33 @@ export function renderProjectLoaderCjs() {
53
75
  `;
54
76
  }
55
77
 
78
+ export function renderTsConfig() {
79
+ return `${JSON.stringify(
80
+ {
81
+ compilerOptions: {
82
+ target: 'ES2022',
83
+ module: 'NodeNext',
84
+ moduleResolution: 'NodeNext',
85
+ strict: true,
86
+ esModuleInterop: true,
87
+ skipLibCheck: true,
88
+ forceConsistentCasingInFileNames: true,
89
+ noEmit: true,
90
+ allowImportingTsExtensions: true,
91
+ types: ['node'],
92
+ },
93
+ include: [
94
+ 'app.ts',
95
+ 'settings.ts',
96
+ 'routes.ts',
97
+ 'apps/**/*.ts',
98
+ ],
99
+ },
100
+ null,
101
+ 2,
102
+ )}\n`;
103
+ }
104
+
56
105
  export function renderProjectSettings(projectName, apps, appSecret = '') {
57
106
  return `export default {
58
107
  appName: '${projectName}',
@@ -60,6 +109,13 @@ export function renderProjectSettings(projectName, apps, appSecret = '') {
60
109
  host: process.env.HOST || '0.0.0.0',
61
110
  port: process.env.PORT ? Number(process.env.PORT) : 3000,
62
111
  trustProxy: false,
112
+ staticDir: 'public',
113
+ templates: {
114
+ enabled: true,
115
+ engine: 'ejs',
116
+ dir: 'templates',
117
+ base: 'base',
118
+ },
63
119
  security: {
64
120
  // Loaded from .env by default. Scaffold also embeds the generated secret as a fallback.
65
121
  // Replace or rotate APP_SECRET in production.
@@ -81,7 +137,7 @@ export function renderProjectSettings(projectName, apps, appSecret = '') {
81
137
  },
82
138
 
83
139
  // Optional sections you can add manually when needed:
84
- // maintenance, https, templates, i18n, helpers, staticDir, websocket, uploads, auth, api, mail, swagger,
140
+ // maintenance, https, i18n, helpers, websocket, uploads, auth, api, mail, swagger,
85
141
  // architecture, loaders, environments, security.headers/ddos/csrf
86
142
 
87
143
  apps: [
@@ -177,7 +233,7 @@ export function renderController(appName) {
177
233
  return renderView(appName);
178
234
  }
179
235
 
180
- export function renderAppViewsFile(appName) {
236
+ export function renderAppViewsFile(appName, sourceExtension = '.js') {
181
237
  const className = `${toPascalCase(appName)}View`;
182
238
  return `class ${className} {
183
239
  static home(_context, req, res, next) {
@@ -470,9 +526,9 @@ export default {
470
526
  `;
471
527
  }
472
528
 
473
- export function renderAppRoutes(appName) {
529
+ export function renderAppRoutes(appName, sourceExtension = '.js') {
474
530
  const viewClass = `${toPascalCase(appName)}View`;
475
- return `import ${viewClass} from './views.js';
531
+ return `import ${viewClass} from './views${sourceExtension}';
476
532
 
477
533
  export default {
478
534
  appName: ${JSON.stringify(appName)},
@@ -492,10 +548,10 @@ export default {
492
548
  `;
493
549
  }
494
550
 
495
- export function renderAppModelTest(appName) {
551
+ export function renderAppModelTest(appName, sourceExtension = '.js') {
496
552
  return `import test from 'node:test';
497
553
  import assert from 'node:assert/strict';
498
- import models from '../models.js';
554
+ import models from '../models${sourceExtension}';
499
555
 
500
556
  test('${appName} model exposes basic CRUD methods', async () => {
501
557
  const Model = models[${JSON.stringify(appName)}];
@@ -517,10 +573,10 @@ test('${appName} model exposes basic CRUD methods', async () => {
517
573
  `;
518
574
  }
519
575
 
520
- export function renderAppValidatorTest(appName) {
576
+ export function renderAppValidatorTest(appName, sourceExtension = '.js') {
521
577
  return `import test from 'node:test';
522
578
  import assert from 'node:assert/strict';
523
- import validators from '../validators.js';
579
+ import validators from '../validators${sourceExtension}';
524
580
 
525
581
  test('${appName} validator validates id and payload', () => {
526
582
  const Validator = validators[${JSON.stringify(appName)}];
@@ -537,10 +593,10 @@ test('${appName} validator validates id and payload', () => {
537
593
  `;
538
594
  }
539
595
 
540
- export function renderAppServiceTest(appName) {
596
+ export function renderAppServiceTest(appName, sourceExtension = '.js') {
541
597
  return `import test from 'node:test';
542
598
  import assert from 'node:assert/strict';
543
- import services from '../services.js';
599
+ import services from '../services${sourceExtension}';
544
600
 
545
601
  test('${appName} service delegates to model layer', async () => {
546
602
  const Service = services[${JSON.stringify(appName)}];
@@ -580,10 +636,10 @@ test('${appName} service delegates to model layer', async () => {
580
636
  `;
581
637
  }
582
638
 
583
- export function renderAppRoutesTest(appName) {
639
+ export function renderAppRoutesTest(appName, sourceExtension = '.js') {
584
640
  return `import test from 'node:test';
585
641
  import assert from 'node:assert/strict';
586
- import routes from '../routes.js';
642
+ import routes from '../routes${sourceExtension}';
587
643
 
588
644
  test('${appName} routes register expected CRUD endpoints', () => {
589
645
  const calls = [];
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ export { createLogger } from './runtime/logger.js';
5
5
  export { deepMerge, normalizeApps } from './runtime/config.js';
6
6
  export { createAuthManager, normalizeAuthConfig, createAuthGuard } from './runtime/auth.js';
7
7
  export { createMailManager, normalizeMailConfig } from './runtime/mail.js';
8
+ export { registerTypeScriptRuntime } from './runtime/typescript.js';
8
9
  export {
9
10
  money,
10
11
  number,
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { pathToFileURL } from 'url';
3
+ import { resolveSourceFile, resolveSourceIndexFile } from '../utils/source-files.js';
4
+ import { importProjectModule } from './typescript.js';
4
5
 
5
6
  const BASE_PROCESS_ENV = new Map(Object.entries(process.env));
6
7
  const FRAMEWORK_LOADED_ENV_KEYS = new Set();
@@ -286,7 +287,7 @@ export function defaultConfig(rootDir) {
286
287
  ddos: {
287
288
  enabled: true,
288
289
  windowMs: 60000,
289
- maxRequests: 120,
290
+ maxRequests: 300,
290
291
  message: 'Too many requests, please try again later.',
291
292
  statusCode: 429,
292
293
  standardHeaders: true,
@@ -419,12 +420,11 @@ export function defaultConfig(rootDir) {
419
420
  }
420
421
 
421
422
  async function importDefaultIfExists(filePath) {
422
- if (!fs.existsSync(filePath)) {
423
+ if (!filePath || !fs.existsSync(filePath)) {
423
424
  return null;
424
425
  }
425
426
 
426
- const moduleUrl = `${pathToFileURL(filePath).href}?t=${Date.now()}`;
427
- const loaded = await import(moduleUrl);
427
+ const loaded = await importProjectModule(filePath);
428
428
  return loaded?.default ?? null;
429
429
  }
430
430
 
@@ -432,12 +432,12 @@ export async function loadProjectConfig(rootDir, logger = null) {
432
432
  loadEnvironmentFiles(rootDir, logger);
433
433
  const config = defaultConfig(rootDir);
434
434
 
435
- const settingsFile = path.join(rootDir, 'settings.js');
435
+ const settingsFile = resolveSourceFile(path.join(rootDir, 'settings'));
436
436
  const settingsDir = path.join(rootDir, 'settings');
437
- const indexFile = path.join(settingsDir, 'index.js');
438
- const dbFile = path.join(settingsDir, 'db.js');
439
- const cacheFile = path.join(settingsDir, 'cache.js');
440
- const appsFile = path.join(settingsDir, 'apps.js');
437
+ const indexFile = resolveSourceIndexFile(settingsDir);
438
+ const dbFile = resolveSourceFile(path.join(settingsDir, 'db'));
439
+ const cacheFile = resolveSourceFile(path.join(settingsDir, 'cache'));
440
+ const appsFile = resolveSourceFile(path.join(settingsDir, 'apps'));
441
441
 
442
442
  const [settingsConfig, indexConfig, dbConfig, cacheConfig, appsConfig] = await Promise.all([
443
443
  importDefaultIfExists(settingsFile),