millas 0.2.28 → 0.2.30
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/bin/millas.js +12 -2
- package/package.json +2 -1
- package/src/cli.js +117 -20
- package/src/commands/call.js +1 -1
- package/src/commands/createsuperuser.js +137 -182
- package/src/commands/key.js +61 -83
- package/src/commands/lang.js +423 -515
- package/src/commands/make.js +88 -62
- package/src/commands/migrate.js +200 -279
- package/src/commands/new.js +55 -50
- package/src/commands/route.js +78 -80
- package/src/commands/schedule.js +52 -150
- package/src/commands/serve.js +158 -191
- package/src/console/AppCommand.js +106 -0
- package/src/console/BaseCommand.js +726 -0
- package/src/console/CommandContext.js +66 -0
- package/src/console/CommandRegistry.js +88 -0
- package/src/console/Style.js +123 -0
- package/src/console/index.js +12 -3
- package/src/container/AppInitializer.js +10 -0
- package/src/facades/DB.js +195 -0
- package/src/index.js +2 -1
- package/src/scaffold/maker.js +102 -42
- package/src/schematics/Collection.js +28 -0
- package/src/schematics/SchematicEngine.js +122 -0
- package/src/schematics/Template.js +99 -0
- package/src/schematics/index.js +7 -0
- package/src/templates/command/default.template.js +14 -0
- package/src/templates/command/schema.json +19 -0
- package/src/templates/controller/default.template.js +10 -0
- package/src/templates/controller/resource.template.js +59 -0
- package/src/templates/controller/schema.json +30 -0
- package/src/templates/job/default.template.js +11 -0
- package/src/templates/job/schema.json +19 -0
- package/src/templates/middleware/default.template.js +11 -0
- package/src/templates/middleware/schema.json +19 -0
- package/src/templates/migration/default.template.js +14 -0
- package/src/templates/migration/schema.json +19 -0
- package/src/templates/model/default.template.js +14 -0
- package/src/templates/model/migration.template.js +17 -0
- package/src/templates/model/schema.json +30 -0
- package/src/templates/service/default.template.js +12 -0
- package/src/templates/service/schema.json +19 -0
- package/src/templates/shape/default.template.js +11 -0
- package/src/templates/shape/schema.json +19 -0
- package/src/validation/BaseValidator.js +3 -0
- package/src/validation/types.js +3 -3
package/src/commands/serve.js
CHANGED
|
@@ -1,219 +1,186 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const chalk = require('chalk');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const fs = require('fs-extra');
|
|
6
|
-
const
|
|
7
|
-
const {fork} = require('child_process');
|
|
5
|
+
const { fork } = require('child_process');
|
|
8
6
|
const chokidar = require('chokidar');
|
|
9
|
-
const
|
|
10
|
-
const
|
|
7
|
+
const BaseCommand = require('../console/BaseCommand');
|
|
8
|
+
const patchConsole = require('../logger/patchConsole');
|
|
9
|
+
const Logger = require('../logger/internal');
|
|
11
10
|
|
|
12
11
|
const WATCH_DIRS = ['app', 'routes', 'config', 'bootstrap', 'providers', 'middleware'];
|
|
13
12
|
const WATCH_EXTS = new Set(['.js', '.mjs', '.cjs', '.json', '.njk', '.env']);
|
|
14
13
|
const DEBOUNCE_MS = 250;
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
// ── HotReloader ───────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
15
|
class HotReloader {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
constructor(bootstrapPath) {
|
|
17
|
+
this._bootstrap = bootstrapPath;
|
|
18
|
+
this._initialised = false;
|
|
19
|
+
this._child = null;
|
|
20
|
+
this._starting = false;
|
|
21
|
+
this._restarts = 0;
|
|
22
|
+
this._queue = [];
|
|
23
|
+
this._watchers = [];
|
|
24
|
+
this._timer = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
start() {
|
|
28
|
+
this._spawnChild();
|
|
29
|
+
this._watch();
|
|
30
|
+
this._handleSignals();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_spawnChild() {
|
|
34
|
+
if (this._starting) return;
|
|
35
|
+
|
|
36
|
+
this._starting = true;
|
|
37
|
+
const extra = {};
|
|
38
|
+
|
|
39
|
+
if (!this._initialised) {
|
|
40
|
+
extra['MILLAS_START_UP'] = true;
|
|
41
|
+
this._initialised = true;
|
|
42
|
+
}
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
this._child = fork(this._bootstrap, [], {
|
|
45
|
+
env: {
|
|
46
|
+
...process.env,
|
|
47
|
+
...extra,
|
|
48
|
+
MILLAS_CHILD: '1',
|
|
49
|
+
},
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this._child.on('message', msg => {
|
|
54
|
+
if (msg && msg.type === 'ready') {
|
|
25
55
|
this._starting = false;
|
|
26
|
-
|
|
56
|
+
}
|
|
57
|
+
});
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this._watchers = [];
|
|
31
|
-
this._timer = null;
|
|
32
|
-
}
|
|
59
|
+
this._child.on('exit', (code, signal) => {
|
|
60
|
+
this._starting = false;
|
|
33
61
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGKILL') {
|
|
63
|
+
console.error(
|
|
64
|
+
this.style?.danger('✖ App crashed') ||
|
|
65
|
+
`✖ App crashed (exit ${code ?? signal}) — fix the error, file watcher will reload…`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this._child.on('error', err => {
|
|
71
|
+
this._starting = false;
|
|
72
|
+
console.error(`✖ ${err.message}`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_killChild(cb) {
|
|
77
|
+
if (!this._child || this._child.exitCode !== null) return cb();
|
|
78
|
+
this._child.once('exit', cb);
|
|
79
|
+
this._child.kill('SIGTERM');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_restart(changedFile) {
|
|
83
|
+
console.warn(`↺ Reloading${changedFile ? ' ' + changedFile : ''}`);
|
|
84
|
+
this._restarts++;
|
|
85
|
+
this._killChild(() => this._spawnChild());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_watch() {
|
|
89
|
+
console.log('✔ Watching for changes…');
|
|
90
|
+
const cwd = process.cwd();
|
|
91
|
+
const watchPaths = [
|
|
92
|
+
...WATCH_DIRS.map(d => path.join(cwd, d)),
|
|
93
|
+
path.join(cwd, '.env'),
|
|
94
|
+
path.join(cwd, '.env.local'),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const watcher = chokidar.watch(watchPaths, {
|
|
98
|
+
persistent: true,
|
|
99
|
+
ignoreInitial: true,
|
|
100
|
+
ignored: /(^|[\/\\])\..(?!env)/,
|
|
101
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
watcher.on('all', (event, filePath) => {
|
|
105
|
+
if (WATCH_EXTS.has(path.extname(filePath))) {
|
|
106
|
+
this._scheduleRestart(filePath);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this._watchers.push(watcher);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_scheduleRestart(changedFile) {
|
|
114
|
+
clearTimeout(this._timer);
|
|
115
|
+
this._timer = setTimeout(() => this._restart(changedFile), DEBOUNCE_MS);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_stopWatching() {
|
|
119
|
+
for (const w of this._watchers) {
|
|
120
|
+
try {
|
|
121
|
+
w.close();
|
|
122
|
+
} catch {}
|
|
38
123
|
}
|
|
124
|
+
this._watchers = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_handleSignals() {
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
clearTimeout(this._timer);
|
|
130
|
+
this._stopWatching();
|
|
131
|
+
if (this._child) this._child.kill('SIGTERM');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
};
|
|
134
|
+
process.once('SIGINT', cleanup);
|
|
135
|
+
process.once('SIGTERM', cleanup);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
39
138
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
this._starting = true;
|
|
45
|
-
const extra = {}
|
|
46
|
-
if (!this._initialised) {
|
|
47
|
-
extra["MILLAS_START_UP"] = true
|
|
48
|
-
this._initialised = true
|
|
49
|
-
}
|
|
139
|
+
class ServeCommand extends BaseCommand {
|
|
140
|
+
static description = 'Start the development server with hot reload';
|
|
50
141
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
MILLAS_HOST: process.env.MILLAS_HOST,
|
|
57
|
-
MILLAS_PORT: process.env.MILLAS_PORT,
|
|
58
|
-
},
|
|
59
|
-
stdio: 'inherit',
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Application.listen() sends { type:'ready' } via IPC once bound
|
|
63
|
-
this._child.on('message', msg => {
|
|
64
|
-
if (msg && msg.type === 'ready') {
|
|
65
|
-
this._starting = false;
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
//
|
|
69
|
-
this._child.on('exit', (code, signal) => {
|
|
70
|
-
this._starting = false;
|
|
71
|
-
|
|
72
|
-
if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGKILL') {
|
|
73
|
-
console.error(chalk.red('✖ App crashed') +
|
|
74
|
-
chalk.dim(` (exit ${code ?? signal})`) +
|
|
75
|
-
chalk.dim(' — fix the error, file watcher will reload…')
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
//
|
|
80
|
-
this._child.on('error', err => {
|
|
81
|
-
this._starting = false;
|
|
82
|
-
console.error(chalk.red(`✖ ${err.message}`));
|
|
83
|
-
});
|
|
84
|
-
}
|
|
142
|
+
async onInit(register) {
|
|
143
|
+
register
|
|
144
|
+
.command(async (port, host, reload) => {
|
|
145
|
+
const restoreAfterPatch = patchConsole(Logger, 'SystemOut');
|
|
146
|
+
const appBootstrap = path.resolve(this.cwd, 'bootstrap/app.js');
|
|
85
147
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this._child.kill('SIGTERM');
|
|
90
|
-
}
|
|
148
|
+
if (!fs.existsSync(appBootstrap)) {
|
|
149
|
+
throw new Error('No Millas project found here. Make sure bootstrap/app.js exists.');
|
|
150
|
+
}
|
|
91
151
|
|
|
92
|
-
|
|
152
|
+
const publicPort = parseInt(port || process.env.APP_PORT || 3000, 10);
|
|
153
|
+
const publicHost = host || process.env.APP_HOST || 'localhost';
|
|
93
154
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
155
|
+
const env = Object.fromEntries(
|
|
156
|
+
Object.entries({
|
|
157
|
+
NODE_ENV: process.env.APP_ENV,
|
|
158
|
+
MILLERS_NODE_ENV: true,
|
|
159
|
+
MILLAS_HOST: publicHost,
|
|
160
|
+
MILLAS_PORT: String(publicPort),
|
|
161
|
+
}).filter(([, v]) => v !== undefined)
|
|
98
162
|
);
|
|
99
163
|
|
|
100
|
-
|
|
101
|
-
this._killChild(() => this._spawnChild());
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ── Watcher ───────────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
_watch() {
|
|
107
|
-
console.log(chalk.green('✔') + ' ' +
|
|
108
|
-
chalk.dim('Watching for changes…')
|
|
109
|
-
);
|
|
110
|
-
const cwd = process.cwd();
|
|
111
|
-
const watchPaths = [
|
|
112
|
-
...WATCH_DIRS.map(d => path.join(cwd, d)),
|
|
113
|
-
path.join(cwd, '.env'),
|
|
114
|
-
path.join(cwd, '.env.local'),
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
const watcher = chokidar.watch(watchPaths, {
|
|
118
|
-
persistent: true,
|
|
119
|
-
ignoreInitial: true,
|
|
120
|
-
ignored: /(^|[\/\\])\..(?!env)/,
|
|
121
|
-
// Wait for the file write to settle before reloading.
|
|
122
|
-
// Prevents double-restarts on editors that truncate then rewrite.
|
|
123
|
-
awaitWriteFinish: {stabilityThreshold: 80, pollInterval: 20},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
watcher.on('all', (event, filePath) => {
|
|
127
|
-
if (WATCH_EXTS.has(path.extname(filePath))) {
|
|
128
|
-
this._scheduleRestart(filePath);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
this._watchers.push(watcher);
|
|
133
|
-
}
|
|
164
|
+
Object.assign(process.env, env);
|
|
134
165
|
|
|
135
|
-
|
|
136
|
-
clearTimeout(this._timer);
|
|
137
|
-
this._timer = setTimeout(() => this._restart(changedFile), DEBOUNCE_MS);
|
|
138
|
-
}
|
|
166
|
+
const enableReload = reload !== false;
|
|
139
167
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
168
|
+
if (enableReload) {
|
|
169
|
+
new HotReloader(appBootstrap).start();
|
|
170
|
+
} else {
|
|
171
|
+
try {
|
|
172
|
+
await require(appBootstrap);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
throw new Error(`Error starting server: ${e.message}`);
|
|
175
|
+
}
|
|
146
176
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
clearTimeout(this._timer);
|
|
155
|
-
this._stopWatching();
|
|
156
|
-
if (this._child) this._child.kill('SIGTERM');
|
|
157
|
-
process.exit(0);
|
|
158
|
-
};
|
|
159
|
-
process.once('SIGINT', cleanup);
|
|
160
|
-
process.once('SIGTERM', cleanup);
|
|
161
|
-
}
|
|
177
|
+
})
|
|
178
|
+
.name('serve')
|
|
179
|
+
.num('port', v => v.optional().min(1).max(65535), 'Port to listen on')
|
|
180
|
+
.str('host', v => v.optional(), 'Host to bind to')
|
|
181
|
+
.bool('reload', v => v.default(true), 'Enable hot reload')
|
|
182
|
+
.description('Start the development server with hot reload');
|
|
183
|
+
}
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
module.exports = function (program) {
|
|
167
|
-
program
|
|
168
|
-
.command('serve')
|
|
169
|
-
.description('Start the development server with hot reload')
|
|
170
|
-
.option('-p, --port <port>', 'Port to listen on')
|
|
171
|
-
.option('-h, --host <host>', 'Host to bind to')
|
|
172
|
-
.option('--no-reload', 'Disable hot reload (run once, like production)')
|
|
173
|
-
.action((options) => {
|
|
174
|
-
|
|
175
|
-
require('dotenv').config({ path: path.resolve(process.cwd(), '.env') });
|
|
176
|
-
|
|
177
|
-
const restoreAfterPatch = patchConsole(Logger,"SystemOut")
|
|
178
|
-
let appBootstrap = path.resolve(process.cwd(), 'bootstrap/app.js');
|
|
179
|
-
|
|
180
|
-
if (!fs.existsSync(appBootstrap)) {
|
|
181
|
-
process.stderr.write(chalk.red('\n ✖ No Millas project found here.\n'));
|
|
182
|
-
process.stderr.write(chalk.dim(' Make sure bootstrap/app.js exists.\n\n'));
|
|
183
|
-
process.exit(1);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const publicPort = parseInt(options.port ||process.env.APP_PORT, 10);
|
|
187
|
-
const publicHost = options.host;
|
|
188
|
-
|
|
189
|
-
const env = Object.fromEntries(
|
|
190
|
-
Object.entries({
|
|
191
|
-
NODE_ENV: process.env.APP_ENV,
|
|
192
|
-
MILLERS_NODE_ENV: true,
|
|
193
|
-
MILLAS_HOST: publicHost,
|
|
194
|
-
MILLAS_PORT: String(publicPort),
|
|
195
|
-
}).filter(([, v]) => v !== undefined)
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
Object.assign(process.env, env)
|
|
199
|
-
|
|
200
|
-
if (options.reload !== false) {
|
|
201
|
-
new HotReloader(appBootstrap, publicPort, publicHost).start();
|
|
202
|
-
} else {
|
|
203
|
-
try {
|
|
204
|
-
require(appBootstrap);
|
|
205
|
-
} catch (e) {
|
|
206
|
-
console.log("Error starting server: ", +e)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
module.exports.requireProject = function (command) {
|
|
214
|
-
const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
|
|
215
|
-
if (!fsnative.existsSync(bootstrapPath)) {
|
|
216
|
-
process.stderr.write(chalk.red(`\n ✖ Not inside a Millas project (${command}).\n\n`));
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
};
|
|
186
|
+
module.exports = ServeCommand;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AppCommand - Base class for commands that need app bootstrapping
|
|
8
|
+
*
|
|
9
|
+
* Provides app context loading for commands that need access to:
|
|
10
|
+
* - Routes
|
|
11
|
+
* - Models
|
|
12
|
+
* - Services
|
|
13
|
+
* - Configuration
|
|
14
|
+
*/
|
|
15
|
+
class AppCommand {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param context
|
|
20
|
+
*/
|
|
21
|
+
constructor(context) {
|
|
22
|
+
this.context = context;
|
|
23
|
+
this.program = context.program;
|
|
24
|
+
this.container = context.container;
|
|
25
|
+
this.logger = context.logger;
|
|
26
|
+
this.cwd = context.cwd;
|
|
27
|
+
this._app = null;
|
|
28
|
+
this._appBootstrapped = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the app bootstrap path
|
|
33
|
+
* Override this to customize the bootstrap location
|
|
34
|
+
*/
|
|
35
|
+
getAppBootstrapPath() {
|
|
36
|
+
return path.resolve(this.cwd, 'bootstrap/app.js');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if app bootstrap exists
|
|
41
|
+
*/
|
|
42
|
+
hasAppBootstrap() {
|
|
43
|
+
return fs.existsSync(this.getAppBootstrapPath());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bootstrap the application
|
|
48
|
+
* Override this method to customize bootstrapping behavior
|
|
49
|
+
*
|
|
50
|
+
* @returns {Object} The bootstrapped app
|
|
51
|
+
*/
|
|
52
|
+
async #appBoot() {
|
|
53
|
+
if (this._appBootstrapped) {
|
|
54
|
+
return this._app;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const bootstrapPath = this.getAppBootstrapPath();
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(bootstrapPath)) {
|
|
60
|
+
throw new Error('Not inside a Millas project. bootstrap/app.js not found.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
this._app = await require(bootstrapPath);
|
|
65
|
+
this._appBootstrapped = true;
|
|
66
|
+
return this._app;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
throw new Error(`Failed to bootstrap app: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the bootstrapped app (lazy loads if needed)
|
|
74
|
+
* @returns Application
|
|
75
|
+
*/
|
|
76
|
+
async getApp() {
|
|
77
|
+
if (!this._appBootstrapped) {
|
|
78
|
+
await this.#appBoot();
|
|
79
|
+
}
|
|
80
|
+
return this._app;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a specific export from the bootstrapped app
|
|
85
|
+
*
|
|
86
|
+
* @param {string} key - The export key (e.g., 'route', 'app', 'db')
|
|
87
|
+
* @returns {*} The exported value
|
|
88
|
+
*/
|
|
89
|
+
async getAppExport(key) {
|
|
90
|
+
const app = await this.getApp();
|
|
91
|
+
return app[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Require app bootstrap (throws if not found)
|
|
96
|
+
* Use this in commands that MUST have an app context
|
|
97
|
+
*/
|
|
98
|
+
async requireApp() {
|
|
99
|
+
if (!this.hasAppBootstrap()) {
|
|
100
|
+
throw new Error('This command requires a Millas project. Run inside a project directory.');
|
|
101
|
+
}
|
|
102
|
+
return await this.getApp();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = AppCommand;
|