aegisnode 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -12
- package/package.json +3 -2
- package/scripts/smoke-test.js +148 -0
- package/src/cli/commands/createapp.js +14 -176
- package/src/cli/commands/doctor.js +85 -25
- package/src/cli/commands/fixapp.js +70 -0
- package/src/cli/commands/generate.js +33 -28
- package/src/cli/commands/generateloader.js +10 -4
- package/src/cli/commands/startproject.js +14 -8
- package/src/cli/index.js +33 -3
- package/src/cli/utils/apps.js +260 -0
- package/src/cli/utils/project.js +14 -6
- package/src/cli/utils/scaffolds.js +92 -30
- package/src/index.js +1 -0
- package/src/runtime/config.js +9 -9
- package/src/runtime/kernel.js +49 -32
- package/src/runtime/typescript.js +21 -0
- package/src/utils/source-files.js +78 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { loadProjectConfig } from '../../runtime/config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ensureValidName } from '../utils/fs.js';
|
|
5
|
+
import { getProjectSourceExtension, resolveProjectRoot } from '../utils/project.js';
|
|
6
|
+
import { getAppScaffoldEntries, toImportName } from '../utils/apps.js';
|
|
7
|
+
import { withSourceExtension } from '../utils/scaffolds.js';
|
|
8
|
+
import { resolveSourceFile } from '../../utils/source-files.js';
|
|
5
9
|
|
|
6
10
|
function createCollector() {
|
|
7
11
|
const entries = [];
|
|
@@ -26,25 +30,49 @@ async function fileExists(filePath) {
|
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
function escapeRegExp(value) {
|
|
34
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function runAppChecks(rootDir, config, collector, targetAppName = null) {
|
|
30
38
|
const apps = Array.isArray(config.apps) ? config.apps : [];
|
|
39
|
+
const sourceExtension = getProjectSourceExtension(rootDir);
|
|
40
|
+
const routesFile = resolveSourceFile(path.join(rootDir, 'routes'), [sourceExtension]) || resolveSourceFile(path.join(rootDir, 'routes'));
|
|
41
|
+
const routesLabel = routesFile ? path.basename(routesFile) : withSourceExtension('routes', sourceExtension);
|
|
42
|
+
const declaredApps = new Map();
|
|
43
|
+
|
|
44
|
+
for (const app of apps) {
|
|
45
|
+
const appName = app?.name;
|
|
46
|
+
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
47
|
+
declaredApps.set(appName, app);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
31
50
|
|
|
32
|
-
if (apps.length === 0) {
|
|
51
|
+
if (!targetAppName && apps.length === 0) {
|
|
33
52
|
collector.warn('No apps declared in settings.apps.');
|
|
34
53
|
return;
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
|
|
56
|
+
if (!targetAppName) {
|
|
57
|
+
collector.ok(`Declared apps: ${apps.map((app) => app.name).join(', ')}`);
|
|
58
|
+
}
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
const routesFileExists = await fileExists(routesFile);
|
|
61
|
+
const routesContent = routesFileExists ? await fs.readFile(routesFile, 'utf8') : '';
|
|
62
|
+
const targetApps = targetAppName ? [targetAppName] : apps;
|
|
63
|
+
|
|
64
|
+
for (const appEntry of targetApps) {
|
|
65
|
+
const appName = targetAppName || appEntry?.name;
|
|
66
|
+
const app = targetAppName ? declaredApps.get(appName) || null : appEntry;
|
|
41
67
|
const mount = app?.mount;
|
|
42
68
|
if (typeof appName !== 'string' || appName.trim().length === 0) {
|
|
43
|
-
collector.error(`Invalid app entry: ${JSON.stringify(
|
|
69
|
+
collector.error(`Invalid app entry: ${JSON.stringify(appEntry)}`);
|
|
44
70
|
continue;
|
|
45
71
|
}
|
|
46
72
|
|
|
47
|
-
if (
|
|
73
|
+
if (!app) {
|
|
74
|
+
collector.warn(`App "${appName}" is not declared in settings.apps.`);
|
|
75
|
+
} else if (typeof mount !== 'string' || !mount.startsWith('/')) {
|
|
48
76
|
collector.error(`App "${appName}" has invalid mount "${String(mount)}" (must start with /).`);
|
|
49
77
|
}
|
|
50
78
|
|
|
@@ -55,34 +83,56 @@ async function runAppChecks(rootDir, config, collector) {
|
|
|
55
83
|
continue;
|
|
56
84
|
}
|
|
57
85
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const target = path.join(appRoot, fileName);
|
|
86
|
+
for (const entry of getAppScaffoldEntries(appName, sourceExtension)) {
|
|
87
|
+
const target = path.join(rootDir, entry.target);
|
|
61
88
|
if (!(await fileExists(target))) {
|
|
62
|
-
collector.warn(`App "${appName}" missing ${
|
|
89
|
+
collector.warn(`App "${appName}" missing ${path.relative(appRoot, target)}.`);
|
|
63
90
|
}
|
|
64
91
|
}
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
if (!app) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (config.autoMountApps === true) {
|
|
98
|
+
collector.ok(`App "${appName}" will be mounted automatically from settings.apps.`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!routesFileExists) {
|
|
103
|
+
collector.warn(`Project ${routesLabel} is missing; app "${appName}" cannot be mounted centrally.`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const importPath = `./apps/${appName}/routes${sourceExtension}`;
|
|
108
|
+
const importName = toImportName(appName);
|
|
109
|
+
const routePattern = new RegExp(`route\\.use\\([^\\n]*,\\s*${escapeRegExp(importName)}\\s*\\);`);
|
|
110
|
+
|
|
111
|
+
if (!routesContent.includes(importPath)) {
|
|
112
|
+
collector.warn(`Project ${routesLabel} is missing import for app "${appName}".`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!routePattern.test(routesContent)) {
|
|
116
|
+
collector.warn(`Project ${routesLabel} is missing route.use(...) mount for app "${appName}".`);
|
|
69
117
|
}
|
|
70
118
|
}
|
|
71
119
|
}
|
|
72
120
|
|
|
73
121
|
async function runStartupEntryChecks(rootDir, config, collector) {
|
|
74
122
|
const env = String(config.env || process.env.NODE_ENV || 'development').trim().toLowerCase();
|
|
75
|
-
const
|
|
123
|
+
const sourceExtension = getProjectSourceExtension(rootDir);
|
|
124
|
+
const appEntryPath = resolveSourceFile(path.join(rootDir, 'app'), [sourceExtension])
|
|
125
|
+
|| path.join(rootDir, withSourceExtension('app', sourceExtension));
|
|
76
126
|
const loaderEntryPath = path.join(rootDir, 'loader.cjs');
|
|
77
127
|
const appEntryExists = await fileExists(appEntryPath);
|
|
78
128
|
const loaderEntryExists = await fileExists(loaderEntryPath);
|
|
79
129
|
|
|
80
130
|
if (appEntryExists) {
|
|
81
|
-
collector.ok(
|
|
131
|
+
collector.ok(`${path.basename(appEntryPath)} exists.`);
|
|
82
132
|
} else if (env === 'production') {
|
|
83
|
-
collector.error(
|
|
133
|
+
collector.error(`${path.basename(appEntryPath)} is missing for production startup. Run "aegisnode generateloader" to restore startup entry files.`);
|
|
84
134
|
} else {
|
|
85
|
-
collector.warn(
|
|
135
|
+
collector.warn(`${path.basename(appEntryPath)} is missing. Run "aegisnode generateloader" to restore startup entry files.`);
|
|
86
136
|
}
|
|
87
137
|
|
|
88
138
|
if (loaderEntryExists) {
|
|
@@ -193,7 +243,12 @@ export async function runDoctor({
|
|
|
193
243
|
projectRoot,
|
|
194
244
|
failOnError = true,
|
|
195
245
|
output = console,
|
|
246
|
+
appName = null,
|
|
196
247
|
} = {}) {
|
|
248
|
+
if (appName) {
|
|
249
|
+
ensureValidName(appName, 'app');
|
|
250
|
+
}
|
|
251
|
+
|
|
197
252
|
const resolvedRoot = await resolveProjectRoot(projectRoot || process.cwd());
|
|
198
253
|
const collector = createCollector();
|
|
199
254
|
|
|
@@ -202,12 +257,17 @@ export async function runDoctor({
|
|
|
202
257
|
const config = await loadProjectConfig(resolvedRoot);
|
|
203
258
|
collector.ok(`Environment: ${config.env || 'development'}`);
|
|
204
259
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
260
|
+
if (appName) {
|
|
261
|
+
collector.ok(`Doctor scope: app "${appName}"`);
|
|
262
|
+
await runAppChecks(resolvedRoot, config, collector, appName);
|
|
263
|
+
} else {
|
|
264
|
+
await runAppChecks(resolvedRoot, config, collector);
|
|
265
|
+
await runStartupEntryChecks(resolvedRoot, config, collector);
|
|
266
|
+
runSecurityChecks(config, collector);
|
|
267
|
+
runAuthChecks(config, collector);
|
|
268
|
+
runApiChecks(config, collector);
|
|
269
|
+
await runTemplateChecks(resolvedRoot, config, collector);
|
|
270
|
+
}
|
|
211
271
|
|
|
212
272
|
const summary = printSummary(collector.entries, output);
|
|
213
273
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { ensureValidName, normalizeMountPath } from '../utils/fs.js';
|
|
3
|
+
import { getProjectSourceExtension, resolveProjectRoot } from '../utils/project.js';
|
|
4
|
+
import { withSourceExtension } from '../utils/scaffolds.js';
|
|
5
|
+
import {
|
|
6
|
+
detectSettingsMode,
|
|
7
|
+
ensureAppScaffold,
|
|
8
|
+
readAppsConfig,
|
|
9
|
+
updateAppRegistry,
|
|
10
|
+
updateProjectRoutesFile,
|
|
11
|
+
} from '../utils/apps.js';
|
|
12
|
+
|
|
13
|
+
export async function runFixApp({ appName, projectRoot, mount } = {}) {
|
|
14
|
+
if (!appName) {
|
|
15
|
+
throw new Error('Missing app name. Usage: aegisnode fix --app <app-name>');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ensureValidName(appName, 'app');
|
|
19
|
+
|
|
20
|
+
const resolvedRoot = await resolveProjectRoot(projectRoot || process.cwd());
|
|
21
|
+
const sourceExtension = getProjectSourceExtension(resolvedRoot);
|
|
22
|
+
const settingsMode = await detectSettingsMode(resolvedRoot);
|
|
23
|
+
const existingApps = await readAppsConfig(settingsMode);
|
|
24
|
+
const existingApp = existingApps.find((entry) => entry.name === appName) || null;
|
|
25
|
+
const appMount = existingApp?.mount || normalizeMountPath(mount || `/${appName}`);
|
|
26
|
+
|
|
27
|
+
const scaffoldResult = await ensureAppScaffold(resolvedRoot, appName, {
|
|
28
|
+
overwrite: false,
|
|
29
|
+
sourceExtension,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let registryUpdated = false;
|
|
33
|
+
if (!existingApp) {
|
|
34
|
+
await updateAppRegistry(
|
|
35
|
+
resolvedRoot,
|
|
36
|
+
[...existingApps, { name: appName, mount: appMount }],
|
|
37
|
+
settingsMode,
|
|
38
|
+
);
|
|
39
|
+
registryUpdated = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const routesResult = await updateProjectRoutesFile(resolvedRoot, appName, appMount, sourceExtension);
|
|
43
|
+
const relativeWritten = scaffoldResult.written.map((target) => path.relative(resolvedRoot, target));
|
|
44
|
+
|
|
45
|
+
if (relativeWritten.length === 0 && !registryUpdated && !routesResult.updatedImport && !routesResult.updatedRoute) {
|
|
46
|
+
console.log(`App "${appName}" is already complete.`);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`App "${appName}" repaired at ${resolvedRoot}/apps/${appName}`);
|
|
49
|
+
if (relativeWritten.length > 0) {
|
|
50
|
+
console.log(`Created missing files: ${relativeWritten.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
if (registryUpdated) {
|
|
53
|
+
console.log(`Added app "${appName}" to settings.apps with mount ${appMount}`);
|
|
54
|
+
}
|
|
55
|
+
if (routesResult.updatedImport || routesResult.updatedRoute) {
|
|
56
|
+
console.log(`Updated ${path.basename(routesResult.routesFile || withSourceExtension('routes', sourceExtension))} registration for app "${appName}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
rootDir: resolvedRoot,
|
|
62
|
+
appName,
|
|
63
|
+
mount: appMount,
|
|
64
|
+
createdFiles: scaffoldResult.written,
|
|
65
|
+
skippedFiles: scaffoldResult.skipped,
|
|
66
|
+
registryUpdated,
|
|
67
|
+
routesUpdated: routesResult.updatedImport || routesResult.updatedRoute,
|
|
68
|
+
routesFile: routesResult.routesFile,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { ensureValidName, exists, writeFile } from '../utils/fs.js';
|
|
4
|
-
import { toPascalCase } from '../utils/scaffolds.js';
|
|
4
|
+
import { toPascalCase, withSourceExtension } from '../utils/scaffolds.js';
|
|
5
|
+
import { getProjectSourceExtension } from '../utils/project.js';
|
|
6
|
+
import { resolveSourceFile, resolveSourceIndexFile } from '../../utils/source-files.js';
|
|
5
7
|
|
|
6
8
|
const SUPPORTED_TYPES = new Set(['view', 'controller', 'model', 'validator', 'dto', 'service', 'subscriber', 'route']);
|
|
7
9
|
|
|
@@ -22,12 +24,14 @@ function assertType(type) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
async function assertProjectRoot(projectRoot) {
|
|
25
|
-
const hasSingleSettings =
|
|
26
|
-
const hasLegacySettings = (
|
|
27
|
-
|
|
27
|
+
const hasSingleSettings = Boolean(resolveSourceFile(path.join(projectRoot, 'settings')));
|
|
28
|
+
const hasLegacySettings = Boolean(
|
|
29
|
+
resolveSourceIndexFile(path.join(projectRoot, 'settings'))
|
|
30
|
+
|| resolveSourceFile(path.join(projectRoot, 'settings', 'apps')),
|
|
31
|
+
);
|
|
28
32
|
|
|
29
33
|
if (!hasSingleSettings && !hasLegacySettings) {
|
|
30
|
-
throw new Error('Not an AegisNode project root: missing settings.js');
|
|
34
|
+
throw new Error('Not an AegisNode project root: missing settings.js/settings.ts');
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -110,35 +114,35 @@ function renderSubscriber(name, appName) {
|
|
|
110
114
|
`;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
async function getAppRoutesFile(appRoot) {
|
|
114
|
-
const primary = path.join(appRoot, 'routes.
|
|
115
|
-
if (await exists(primary)) {
|
|
117
|
+
async function getAppRoutesFile(appRoot, sourceExtension) {
|
|
118
|
+
const primary = resolveSourceFile(path.join(appRoot, 'routes'), [sourceExtension]) || resolveSourceFile(path.join(appRoot, 'routes'));
|
|
119
|
+
if (primary && await exists(primary)) {
|
|
116
120
|
return primary;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
const legacy = path.join(appRoot, 'routes',
|
|
120
|
-
if (await exists(legacy)) {
|
|
123
|
+
const legacy = resolveSourceIndexFile(path.join(appRoot, 'routes'), [sourceExtension]) || resolveSourceIndexFile(path.join(appRoot, 'routes'));
|
|
124
|
+
if (legacy && await exists(legacy)) {
|
|
121
125
|
return legacy;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
throw new Error(`Missing app routes file in ${appRoot}. Expected routes
|
|
128
|
+
throw new Error(`Missing app routes file in ${appRoot}. Expected routes${sourceExtension} or routes/index${sourceExtension}`);
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
async function appendRouteToApp({ appRoot, routeName }) {
|
|
128
|
-
const routesFile = await getAppRoutesFile(appRoot);
|
|
129
|
-
const usesFlatRoutesFile = path.basename(routesFile) === 'routes
|
|
130
|
-
const flatViewPath = path.join(appRoot, `${routeName}.view
|
|
131
|
-
const nestedViewPath = path.join(appRoot, 'views', `${routeName}.view
|
|
131
|
+
async function appendRouteToApp({ appRoot, routeName, sourceExtension }) {
|
|
132
|
+
const routesFile = await getAppRoutesFile(appRoot, sourceExtension);
|
|
133
|
+
const usesFlatRoutesFile = path.basename(routesFile) === withSourceExtension('routes', sourceExtension);
|
|
134
|
+
const flatViewPath = path.join(appRoot, `${routeName}.view${sourceExtension}`);
|
|
135
|
+
const nestedViewPath = path.join(appRoot, 'views', `${routeName}.view${sourceExtension}`);
|
|
132
136
|
|
|
133
137
|
let importPath = null;
|
|
134
138
|
if (await exists(flatViewPath)) {
|
|
135
139
|
importPath = usesFlatRoutesFile
|
|
136
|
-
? `./${routeName}.view
|
|
137
|
-
: `../${routeName}.view
|
|
140
|
+
? `./${routeName}.view${sourceExtension}`
|
|
141
|
+
: `../${routeName}.view${sourceExtension}`;
|
|
138
142
|
} else if (await exists(nestedViewPath)) {
|
|
139
143
|
importPath = usesFlatRoutesFile
|
|
140
|
-
? `./views/${routeName}.view
|
|
141
|
-
: `../views/${routeName}.view
|
|
144
|
+
? `./views/${routeName}.view${sourceExtension}`
|
|
145
|
+
: `../views/${routeName}.view${sourceExtension}`;
|
|
142
146
|
} else {
|
|
143
147
|
throw new Error(`Missing view for route generation: ${flatViewPath} (or ${nestedViewPath}). Create it first with generate view ${routeName}.`);
|
|
144
148
|
}
|
|
@@ -193,18 +197,18 @@ async function appendRouteToApp({ appRoot, routeName }) {
|
|
|
193
197
|
return routesFile;
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
function resolveTarget(appRoot, type, name) {
|
|
200
|
+
function resolveTarget(appRoot, type, name, sourceExtension) {
|
|
197
201
|
switch (type) {
|
|
198
202
|
case 'view':
|
|
199
|
-
return path.join(appRoot, `${name}.view
|
|
203
|
+
return path.join(appRoot, `${name}.view${sourceExtension}`);
|
|
200
204
|
case 'model':
|
|
201
|
-
return path.join(appRoot, `${name}.model
|
|
205
|
+
return path.join(appRoot, `${name}.model${sourceExtension}`);
|
|
202
206
|
case 'service':
|
|
203
|
-
return path.join(appRoot, `${name}.service
|
|
207
|
+
return path.join(appRoot, `${name}.service${sourceExtension}`);
|
|
204
208
|
case 'validator':
|
|
205
|
-
return path.join(appRoot, `${name}.validator
|
|
209
|
+
return path.join(appRoot, `${name}.validator${sourceExtension}`);
|
|
206
210
|
case 'subscriber':
|
|
207
|
-
return path.join(appRoot, `${name}.subscriber
|
|
211
|
+
return path.join(appRoot, `${name}.subscriber${sourceExtension}`);
|
|
208
212
|
default:
|
|
209
213
|
throw new Error(`Unsupported type: ${type}`);
|
|
210
214
|
}
|
|
@@ -248,14 +252,15 @@ export async function generateArtifact({ type, name, appName, projectRoot }) {
|
|
|
248
252
|
const resolvedRoot = path.resolve(projectRoot);
|
|
249
253
|
await assertProjectRoot(resolvedRoot);
|
|
250
254
|
const appRoot = await assertAppRoot(resolvedRoot, appName);
|
|
255
|
+
const sourceExtension = getProjectSourceExtension(resolvedRoot);
|
|
251
256
|
|
|
252
257
|
if (normalizedType === 'route') {
|
|
253
|
-
const routesFile = await appendRouteToApp({ appRoot, routeName: name });
|
|
258
|
+
const routesFile = await appendRouteToApp({ appRoot, routeName: name, sourceExtension });
|
|
254
259
|
console.log(`Generated route /${name} in ${routesFile}`);
|
|
255
260
|
return;
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
const targetFile = resolveTarget(appRoot, normalizedType, name);
|
|
263
|
+
const targetFile = resolveTarget(appRoot, normalizedType, name, sourceExtension);
|
|
259
264
|
if (await exists(targetFile)) {
|
|
260
265
|
throw new Error(`File already exists: ${targetFile}`);
|
|
261
266
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { exists, writeFile } from '../utils/fs.js';
|
|
3
|
-
import { resolveProjectRoot } from '../utils/project.js';
|
|
4
|
-
import { renderProjectAppJs, renderProjectLoaderCjs } from '../utils/scaffolds.js';
|
|
3
|
+
import { getProjectSourceExtension, resolveProjectRoot } from '../utils/project.js';
|
|
4
|
+
import { renderProjectAppJs, renderProjectLoaderCjs, withSourceExtension } from '../utils/scaffolds.js';
|
|
5
5
|
|
|
6
6
|
async function ensureStartupFile(rootDir, fileName, content, output) {
|
|
7
7
|
const target = path.join(rootDir, fileName);
|
|
@@ -20,8 +20,14 @@ export async function runGenerateLoader({
|
|
|
20
20
|
output = console,
|
|
21
21
|
} = {}) {
|
|
22
22
|
const resolvedRoot = await resolveProjectRoot(projectRoot || process.cwd());
|
|
23
|
-
const
|
|
24
|
-
const
|
|
23
|
+
const sourceExtension = getProjectSourceExtension(resolvedRoot);
|
|
24
|
+
const createdApp = await ensureStartupFile(
|
|
25
|
+
resolvedRoot,
|
|
26
|
+
withSourceExtension('app', sourceExtension),
|
|
27
|
+
renderProjectAppJs(),
|
|
28
|
+
output,
|
|
29
|
+
);
|
|
30
|
+
const createdLoader = await ensureStartupFile(resolvedRoot, 'loader.cjs', renderProjectLoaderCjs(sourceExtension), output);
|
|
25
31
|
|
|
26
32
|
if (!createdApp && !createdLoader) {
|
|
27
33
|
output.log(`Startup entry files already exist in ${resolvedRoot}`);
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
renderProjectPackageJson,
|
|
11
11
|
renderProjectRoutes,
|
|
12
12
|
renderProjectSettings,
|
|
13
|
+
renderTsConfig,
|
|
14
|
+
withSourceExtension,
|
|
13
15
|
} from '../utils/scaffolds.js';
|
|
14
16
|
|
|
15
17
|
async function createSecret() {
|
|
@@ -37,32 +39,36 @@ async function assertCanCreateProject(projectDir) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
async function createBaseProjectFiles(projectRoot, projectName) {
|
|
42
|
+
async function createBaseProjectFiles(projectRoot, projectName, { typescript = false } = {}) {
|
|
41
43
|
const apps = [];
|
|
42
44
|
const appSecret = await createSecret();
|
|
45
|
+
const sourceExtension = typescript ? '.ts' : '.js';
|
|
43
46
|
|
|
44
47
|
await ensureDir(projectRoot);
|
|
45
48
|
await Promise.all([
|
|
46
49
|
ensureDir(path.join(projectRoot, 'apps')),
|
|
47
50
|
]);
|
|
48
51
|
|
|
49
|
-
await writeFile(path.join(projectRoot, 'app
|
|
50
|
-
await writeFile(path.join(projectRoot, 'loader.cjs'), renderProjectLoaderCjs());
|
|
51
|
-
await writeFile(path.join(projectRoot, 'package.json'), renderProjectPackageJson(projectName));
|
|
52
|
+
await writeFile(path.join(projectRoot, withSourceExtension('app', sourceExtension)), renderProjectAppJs());
|
|
53
|
+
await writeFile(path.join(projectRoot, 'loader.cjs'), renderProjectLoaderCjs(sourceExtension));
|
|
54
|
+
await writeFile(path.join(projectRoot, 'package.json'), renderProjectPackageJson(projectName, { typescript }));
|
|
52
55
|
await writeFile(path.join(projectRoot, '.gitignore'), renderProjectGitIgnore());
|
|
53
56
|
await writeFile(path.join(projectRoot, '.env'), renderProjectEnv(appSecret));
|
|
54
57
|
await writeFile(path.join(projectRoot, '.env.example'), renderEnvExample());
|
|
55
58
|
|
|
56
|
-
await writeFile(path.join(projectRoot, 'settings
|
|
57
|
-
await writeFile(path.join(projectRoot, 'routes
|
|
59
|
+
await writeFile(path.join(projectRoot, withSourceExtension('settings', sourceExtension)), renderProjectSettings(projectName, apps, appSecret));
|
|
60
|
+
await writeFile(path.join(projectRoot, withSourceExtension('routes', sourceExtension)), renderProjectRoutes());
|
|
61
|
+
if (typescript) {
|
|
62
|
+
await writeFile(path.join(projectRoot, 'tsconfig.json'), renderTsConfig());
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
export async function startProject({ projectName, cwd }) {
|
|
66
|
+
export async function startProject({ projectName, cwd, typescript = false }) {
|
|
61
67
|
ensureValidName(projectName, 'project');
|
|
62
68
|
|
|
63
69
|
const projectRoot = path.resolve(cwd, projectName);
|
|
64
70
|
await assertCanCreateProject(projectRoot);
|
|
65
|
-
await createBaseProjectFiles(projectRoot, projectName);
|
|
71
|
+
await createBaseProjectFiles(projectRoot, projectName, { typescript });
|
|
66
72
|
|
|
67
73
|
console.log(`AegisNode project created at ${projectRoot}`);
|
|
68
74
|
console.log('Next steps:');
|
package/src/cli/index.js
CHANGED
|
@@ -5,28 +5,33 @@ import { generateArtifact } from './commands/generate.js';
|
|
|
5
5
|
import { runDoctor } from './commands/doctor.js';
|
|
6
6
|
import { runUpdateDependencies } from './commands/updatedeps.js';
|
|
7
7
|
import { runGenerateLoader } from './commands/generateloader.js';
|
|
8
|
+
import { runFixApp } from './commands/fixapp.js';
|
|
8
9
|
|
|
9
10
|
function printHelp() {
|
|
10
11
|
console.log(`AegisNode CLI
|
|
11
12
|
|
|
12
13
|
Usage:
|
|
13
|
-
aegisnode startproject <project-name>
|
|
14
|
+
aegisnode startproject <project-name> [--typescript]
|
|
14
15
|
aegisnode createapp <app-name> [--project <path>] [--mount </path>]
|
|
16
|
+
aegisnode fix [--app <app-name>] [--project <path>]
|
|
15
17
|
aegisnode generate <type> <name> --app <app-name> [--project <path>]
|
|
16
18
|
aegisnode runserver [--project <path>] [--port <number>]
|
|
17
19
|
aegisnode generateloader [--project <path>]
|
|
18
|
-
aegisnode doctor [--project <path>]
|
|
20
|
+
aegisnode doctor [--app <app-name>] [--project <path>]
|
|
19
21
|
aegisnode updatedeps [--project <path>]
|
|
20
22
|
|
|
21
23
|
Examples:
|
|
22
24
|
aegisnode startproject blog
|
|
25
|
+
aegisnode startproject blog --typescript
|
|
23
26
|
cd blog
|
|
24
27
|
npm install
|
|
25
28
|
aegisnode runserver
|
|
26
29
|
aegisnode createapp users
|
|
30
|
+
aegisnode fix --app users
|
|
27
31
|
aegisnode generate view user --app users
|
|
28
32
|
aegisnode generate validator user --app users
|
|
29
33
|
aegisnode generateloader --project blog
|
|
34
|
+
aegisnode doctor --app users --project blog
|
|
30
35
|
aegisnode updatedeps --project blog
|
|
31
36
|
`);
|
|
32
37
|
}
|
|
@@ -66,6 +71,11 @@ function parseFlags(tokens) {
|
|
|
66
71
|
continue;
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
if (token === '--typescript' || token === '--ts') {
|
|
75
|
+
flags.typescript = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
if (token === '-h' || token === '--help') {
|
|
70
80
|
flags.help = true;
|
|
71
81
|
continue;
|
|
@@ -97,7 +107,11 @@ export async function runCli(argv) {
|
|
|
97
107
|
if (!projectName) {
|
|
98
108
|
throw new Error('Missing project name. Usage: aegisnode startproject <project-name>');
|
|
99
109
|
}
|
|
100
|
-
await startProject({
|
|
110
|
+
await startProject({
|
|
111
|
+
projectName,
|
|
112
|
+
cwd: process.cwd(),
|
|
113
|
+
typescript: flags.typescript === true,
|
|
114
|
+
});
|
|
101
115
|
return;
|
|
102
116
|
}
|
|
103
117
|
|
|
@@ -123,8 +137,24 @@ export async function runCli(argv) {
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
case 'doctor': {
|
|
140
|
+
if (positional.length > 0) {
|
|
141
|
+
throw new Error('Doctor app target must use --app <app-name>. Usage: aegisnode doctor [--app <app-name>] [--project <path>]');
|
|
142
|
+
}
|
|
126
143
|
await runDoctor({
|
|
127
144
|
projectRoot: flags.project ? String(flags.project) : process.cwd(),
|
|
145
|
+
appName: flags.app ? String(flags.app) : null,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case 'fix':
|
|
151
|
+
case 'fixapp': {
|
|
152
|
+
if (positional.length > 0) {
|
|
153
|
+
throw new Error('Fix app target must use --app <app-name>. Usage: aegisnode fix [--app <app-name>] [--project <path>]');
|
|
154
|
+
}
|
|
155
|
+
await runFixApp({
|
|
156
|
+
appName: flags.app ? String(flags.app) : undefined,
|
|
157
|
+
projectRoot: flags.project ? String(flags.project) : process.cwd(),
|
|
128
158
|
});
|
|
129
159
|
return;
|
|
130
160
|
}
|