@appxdigital/appx-core-cli 1.0.10 → 1.0.12
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/package.json +4 -3
- package/scaffold/.env.template +33 -0
- package/scaffold/.eslintrc.js +26 -0
- package/scaffold/.gitattributes +2 -0
- package/scaffold/.prettierrc +9 -0
- package/scaffold/prisma/schema.prisma.template +47 -0
- package/scaffold/src/app.controller.ts +13 -0
- package/{templates/app.module.template.js → scaffold/src/app.module.ts} +6 -2
- package/scaffold/src/app.service.ts +8 -0
- package/{templates/adminjs/dashboard.template.js → scaffold/src/backoffice/components/dashboard.js} +10 -10
- package/{templates/admin.config.template.js → scaffold/src/config/admin.config.ts} +26 -26
- package/{templates/bootstrap.template.js → scaffold/src/main.ts} +3 -3
- package/wizard.js +406 -430
- package/README.md +0 -159
- package/utils/dependency-versions.json +0 -28
- /package/{templates/permissions.config.template.js → scaffold/src/config/permissions.config.ts} +0 -0
- /package/{templates/prisma.module.template.js → scaffold/src/prisma/prisma.module.ts} +0 -0
package/wizard.js
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Ensure node 20 at least
|
|
4
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
5
|
+
if (major < 20) {
|
|
6
|
+
console.error('❌ Node.js version 20 or higher is required to run this script.');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Dependencies
|
|
11
|
+
import {program} from 'commander';
|
|
4
12
|
import inquirer from 'inquirer';
|
|
5
13
|
import fs from 'fs-extra';
|
|
6
14
|
import fsCore from 'fs';
|
|
7
|
-
import {
|
|
15
|
+
import {execSync} from 'child_process';
|
|
8
16
|
import path from 'path';
|
|
9
|
-
import {
|
|
17
|
+
import {fileURLToPath} from 'url';
|
|
10
18
|
import cliProgress from 'cli-progress';
|
|
11
|
-
import { gatherFileUploadSettings, insertFileUploadConfiguration } from './utils/fileUploadConfig.js';
|
|
12
|
-
import packageJson from './package.json' assert { type: 'json' };
|
|
13
19
|
import crypto from 'crypto';
|
|
20
|
+
import pg from "pg";
|
|
21
|
+
import mysql from "mysql2/promise";
|
|
22
|
+
|
|
23
|
+
// Utils and data
|
|
24
|
+
import {gatherFileUploadSettings, insertFileUploadConfiguration} from './utils/fileUploadConfig.js';
|
|
25
|
+
import packageJson from './package.json' with {type: 'json'};
|
|
14
26
|
|
|
15
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
28
|
const __dirname = path.dirname(__filename);
|
|
17
29
|
|
|
18
|
-
const
|
|
19
|
-
fsCore.readFileSync(path.join(__dirname, 'utils', 'dependency-versions.json'), 'utf-8')
|
|
20
|
-
);
|
|
30
|
+
const CORE_VERSION = '0.1.115';
|
|
21
31
|
|
|
22
32
|
const phaseDurations = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
installDependencies: 30,
|
|
34
|
+
setupProjectStructure: 5,
|
|
35
|
+
createPrismaMigration: 5,
|
|
36
|
+
generateCoreModels: 10,
|
|
37
|
+
gitInit: 1,
|
|
38
|
+
migrateDatabase: 5,
|
|
39
|
+
fileUploadSetup: 5
|
|
30
40
|
};
|
|
31
41
|
|
|
32
|
-
const calculateTotalETA = (
|
|
33
|
-
|
|
34
|
-
phaseDurations.appmodule +
|
|
35
|
-
phaseDurations.installDependencies +
|
|
36
|
-
phaseDurations.installCore +
|
|
37
|
-
phaseDurations.prismaSetup +
|
|
38
|
-
phaseDurations.setupConcluded
|
|
39
|
-
+ phaseDurations.end;
|
|
40
|
-
return totalTime;
|
|
42
|
+
const calculateTotalETA = () => {
|
|
43
|
+
return Object.values(phaseDurations).reduce((acc, curr) => acc + curr, 0);
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
const logo = `\x1b[36m
|
|
@@ -52,34 +55,38 @@ CLI Version: ${packageJson.version}
|
|
|
52
55
|
`;
|
|
53
56
|
|
|
54
57
|
console.log(logo);
|
|
55
|
-
program.version(packageJson.version).description('
|
|
58
|
+
program.version(packageJson.version).description('AppX Core Project Initialization Wizard');
|
|
56
59
|
let progressBar;
|
|
57
60
|
let remainingETA = 0;
|
|
58
61
|
program
|
|
59
62
|
.command('create')
|
|
60
|
-
.description('Create a new
|
|
63
|
+
.description('Create a new AppX Core project')
|
|
61
64
|
.action(() => {
|
|
62
65
|
console.log('Starting AppX Core...');
|
|
63
66
|
promptUser();
|
|
64
67
|
});
|
|
65
68
|
|
|
69
|
+
function coreGenerate (showOutput) {
|
|
70
|
+
// run file inside node_modules/appx-core/dist/config/generate-all.js
|
|
71
|
+
const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'generate-all.js');
|
|
72
|
+
if (!fsCore.existsSync(generatePath)) {
|
|
73
|
+
console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
// Execute the script
|
|
77
|
+
try {
|
|
78
|
+
executeCommand(`node ${generatePath}`, showOutput);
|
|
79
|
+
console.log('✅ Prisma client and GraphQL schema generated successfully.');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('❌ An error occurred while generating Prisma client and GraphQL schema:', error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
program.command('generate')
|
|
67
87
|
.description('Updates Prisma client and generates the GraphQL schema')
|
|
68
88
|
.action(() => {
|
|
69
|
-
|
|
70
|
-
const generatePath = path.join(process.cwd(), 'node_modules', '@appxdigital/appx-core', 'dist', 'config', 'generate-all.js');
|
|
71
|
-
if (!fsCore.existsSync(generatePath)) {
|
|
72
|
-
console.error('❌ Not inside a valid AppX Core project directory. Please run this command inside your project directory.');
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
// Execute the script
|
|
76
|
-
try {
|
|
77
|
-
execSync(`node ${generatePath}`, { stdio: 'inherit' });
|
|
78
|
-
console.log('✅ Prisma client and GraphQL schema generated successfully.');
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('❌ An error occurred while generating Prisma client and GraphQL schema:', error.message);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
89
|
+
coreGenerate(true);
|
|
83
90
|
});
|
|
84
91
|
|
|
85
92
|
program.command('setup:fileupload')
|
|
@@ -93,7 +100,7 @@ program.command('setup:fileupload')
|
|
|
93
100
|
}
|
|
94
101
|
// Execute the script
|
|
95
102
|
try {
|
|
96
|
-
execSync(`node ${generatePath}`, {
|
|
103
|
+
execSync(`node ${generatePath}`, {stdio: 'inherit'});
|
|
97
104
|
console.log('✅ File upload configuration added successfully.');
|
|
98
105
|
} catch (error) {
|
|
99
106
|
console.error('❌ An error occurred while adding file upload configuration:', error.message);
|
|
@@ -101,82 +108,248 @@ program.command('setup:fileupload')
|
|
|
101
108
|
}
|
|
102
109
|
});
|
|
103
110
|
|
|
104
|
-
async function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
111
|
+
async function checkDb (config) {
|
|
112
|
+
const {dbProvider, dbHost, dbPort, dbUser, dbPassword, dbName} = config;
|
|
113
|
+
|
|
114
|
+
if (dbProvider === "mysql") {
|
|
115
|
+
const conn = await mysql.createConnection({
|
|
116
|
+
host: dbHost,
|
|
117
|
+
port: Number(dbPort),
|
|
118
|
+
user: dbUser,
|
|
119
|
+
password: dbPassword,
|
|
120
|
+
// Important: don't set database here yet; we first check it exists
|
|
121
|
+
connectTimeout: 5000,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// 1) DB exists?
|
|
126
|
+
const [dbRows] = await conn.query(
|
|
127
|
+
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?",
|
|
128
|
+
[dbName]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (dbRows.length === 0) {
|
|
132
|
+
return {ok: false, reason: `Database "${dbName}" does not exist.`};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2) Any tables?
|
|
136
|
+
const [tableRows] = await conn.query(
|
|
137
|
+
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? LIMIT 1",
|
|
138
|
+
[dbName]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (tableRows.length > 0) {
|
|
142
|
+
return {ok: false, reason: `Database "${dbName}" is not empty (has tables).`};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {ok: true};
|
|
146
|
+
} finally {
|
|
147
|
+
await conn.end();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (dbProvider === "postgresql") {
|
|
152
|
+
const client = new pg.Client({
|
|
153
|
+
host: dbHost,
|
|
154
|
+
port: Number(dbPort),
|
|
155
|
+
user: dbUser,
|
|
156
|
+
password: dbPassword,
|
|
157
|
+
database: "postgres", // connect to a known db first to check existence
|
|
158
|
+
connectionTimeoutMillis: 5000,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await client.connect();
|
|
162
|
+
try {
|
|
163
|
+
// 1) DB exists?
|
|
164
|
+
const dbRes = await client.query(
|
|
165
|
+
"SELECT 1 FROM pg_database WHERE datname = $1",
|
|
166
|
+
[dbName]
|
|
167
|
+
);
|
|
168
|
+
if (dbRes.rowCount === 0) {
|
|
169
|
+
return {ok: false, reason: `Database "${dbName}" does not exist.`};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2) Connect to target db and check tables in public schema (and others if you want)
|
|
173
|
+
const client2 = new pg.Client({
|
|
174
|
+
host: dbHost,
|
|
175
|
+
port: Number(dbPort),
|
|
176
|
+
user: dbUser,
|
|
177
|
+
password: dbPassword,
|
|
178
|
+
database: dbName,
|
|
179
|
+
connectionTimeoutMillis: 5000,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await client2.connect();
|
|
183
|
+
try {
|
|
184
|
+
const tableRes = await client2.query(
|
|
185
|
+
`
|
|
186
|
+
SELECT 1
|
|
187
|
+
FROM information_schema.tables
|
|
188
|
+
WHERE table_type = 'BASE TABLE'
|
|
189
|
+
AND table_schema NOT IN ('pg_catalog', 'information_schema') LIMIT 1
|
|
190
|
+
`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (tableRes.rowCount > 0) {
|
|
194
|
+
return {ok: false, reason: `Database "${dbName}" is not empty (has tables).`};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {ok: true};
|
|
198
|
+
} finally {
|
|
199
|
+
await client2.end();
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
await client.end();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {ok: false, reason: "Unsupported dbProvider."};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function promptDbConfig () {
|
|
210
|
+
return inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: "list",
|
|
213
|
+
name: "dbProvider",
|
|
214
|
+
message: "Database provider:",
|
|
215
|
+
choices: ["mysql", "postgresql"],
|
|
216
|
+
default: "mysql",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: "input",
|
|
220
|
+
name: "dbHost",
|
|
221
|
+
message: "Database Host:",
|
|
222
|
+
default: "127.0.0.1",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: "input",
|
|
226
|
+
name: "dbPort",
|
|
227
|
+
message: "Database Port:",
|
|
228
|
+
default: (answers) => (answers.dbProvider === "mysql" ? "3306" : "5432"),
|
|
229
|
+
validate: (v) => (!Number.isNaN(Number(v)) ? true : "Port must be a number"),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "input",
|
|
233
|
+
name: "dbUser",
|
|
234
|
+
message: "Database User:",
|
|
235
|
+
default: "root",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: "password",
|
|
239
|
+
name: "dbPassword",
|
|
240
|
+
message: "Database Password:",
|
|
241
|
+
mask: "*",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: "input",
|
|
245
|
+
name: "dbName",
|
|
246
|
+
message: "Database Name:",
|
|
247
|
+
default: "generic",
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function promptDbUntilValid () {
|
|
253
|
+
while (true) {
|
|
254
|
+
const dbConfig = await promptDbConfig();
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const result = await checkDb(dbConfig);
|
|
258
|
+
|
|
259
|
+
if (result.ok) {
|
|
260
|
+
return dbConfig;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
process.stdout.write(`❌ Database check failed: ${result.reason}\n`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
process.stdout.write(`❌ Database check failed: ${err.message}\n`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const {retry} = await inquirer.prompt([
|
|
133
269
|
{
|
|
134
|
-
type:
|
|
135
|
-
name:
|
|
136
|
-
message:
|
|
137
|
-
default:
|
|
270
|
+
type: "confirm",
|
|
271
|
+
name: "retry",
|
|
272
|
+
message: "Re-enter all database settings?",
|
|
273
|
+
default: true,
|
|
138
274
|
},
|
|
275
|
+
]);
|
|
276
|
+
|
|
277
|
+
if (!retry) {
|
|
278
|
+
throw new Error("User aborted database configuration.");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function promptUser () {
|
|
284
|
+
let fileUploadConfigData = {};
|
|
285
|
+
try {
|
|
286
|
+
const baseAnswers = await inquirer.prompt([
|
|
139
287
|
{
|
|
140
|
-
type:
|
|
141
|
-
name:
|
|
142
|
-
message:
|
|
143
|
-
|
|
288
|
+
type: "input",
|
|
289
|
+
name: "projectName",
|
|
290
|
+
message: "Project name:",
|
|
291
|
+
default: "nestjs-project",
|
|
144
292
|
},
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const dbAnswers = await promptDbUntilValid();
|
|
296
|
+
|
|
297
|
+
const rest = await inquirer.prompt([
|
|
145
298
|
{
|
|
146
|
-
type:
|
|
147
|
-
name:
|
|
148
|
-
message:
|
|
149
|
-
default:
|
|
299
|
+
type: "confirm",
|
|
300
|
+
name: "showOutput",
|
|
301
|
+
message: "Show installation output?",
|
|
302
|
+
default: false,
|
|
150
303
|
},
|
|
151
304
|
{
|
|
152
|
-
type:
|
|
153
|
-
name:
|
|
154
|
-
message:
|
|
155
|
-
default: false
|
|
305
|
+
type: "confirm",
|
|
306
|
+
name: "configureFileUpload",
|
|
307
|
+
message: "Configure file upload?",
|
|
308
|
+
default: false,
|
|
156
309
|
},
|
|
157
|
-
{
|
|
158
|
-
type: 'confirm',
|
|
159
|
-
name: 'configureFileUpload',
|
|
160
|
-
message: 'Configure file upload?',
|
|
161
|
-
default: false
|
|
162
|
-
}
|
|
163
310
|
]);
|
|
164
311
|
|
|
312
|
+
const answers = {...baseAnswers, ...dbAnswers, ...rest};
|
|
313
|
+
|
|
165
314
|
if (answers.configureFileUpload) {
|
|
166
315
|
fileUploadConfigData = await gatherFileUploadSettings();
|
|
167
316
|
}
|
|
168
317
|
|
|
169
|
-
remainingETA = calculateTotalETA(
|
|
318
|
+
remainingETA = calculateTotalETA();
|
|
170
319
|
createProject(answers, fileUploadConfigData);
|
|
171
320
|
} catch (error) {
|
|
172
321
|
console.error('Error prompting user:', error);
|
|
173
322
|
}
|
|
174
323
|
}
|
|
175
324
|
|
|
176
|
-
async function createProject(answers, fileUploadConfigData) {
|
|
177
|
-
const {
|
|
325
|
+
async function createProject (answers, fileUploadConfigData) {
|
|
326
|
+
const {projectName, showOutput, backoffice} = answers;
|
|
327
|
+
|
|
178
328
|
console.log(`Creating project: ${projectName}`);
|
|
179
|
-
|
|
329
|
+
|
|
330
|
+
// If current directory is empty, use it as project directory
|
|
331
|
+
let projectPath = process.cwd();
|
|
332
|
+
|
|
333
|
+
// Ignore DS_STORE files when checking if directory is empty
|
|
334
|
+
if (fs.readdirSync(projectPath).filter(f => f.toLowerCase() !== '.DS_STORE').length > 0) {
|
|
335
|
+
projectPath += `/${projectName.replace(/\s+/g, '_').toLowerCase()}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(`Project path: ${projectPath}`);
|
|
339
|
+
|
|
340
|
+
const project_config = {
|
|
341
|
+
DB_PROVIDER: answers.dbProvider === 'mysql' ? 'mysql' : 'postgresql',
|
|
342
|
+
DB_HOST: answers.dbHost,
|
|
343
|
+
DB_PORT: answers.dbPort,
|
|
344
|
+
DB_USER: answers.dbUser,
|
|
345
|
+
DB_PASSWORD: answers.dbPassword,
|
|
346
|
+
DB_NAME: answers.dbName,
|
|
347
|
+
SESSION_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
348
|
+
SESSION_COOKIE_NAME: 'session_' + projectName.replace(/\s+/g, '_').toLowerCase(),
|
|
349
|
+
JWT_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
350
|
+
JWT_REFRESH_SECRET: crypto.randomBytes(32).toString('hex'),
|
|
351
|
+
}
|
|
352
|
+
|
|
180
353
|
if (!showOutput) {
|
|
181
354
|
const phases = backoffice ? 8 : 7;
|
|
182
355
|
progressBar = new cliProgress.SingleBar(
|
|
@@ -187,69 +360,79 @@ async function createProject(answers, fileUploadConfigData) {
|
|
|
187
360
|
},
|
|
188
361
|
cliProgress.Presets.shades_classic
|
|
189
362
|
);
|
|
190
|
-
progressBar.update(0, {
|
|
363
|
+
progressBar.update(0, {remainingETA});
|
|
191
364
|
progressBar.start(phases, 0);
|
|
192
365
|
}
|
|
193
366
|
|
|
194
367
|
try {
|
|
195
368
|
fs.ensureDirSync(projectPath);
|
|
196
|
-
|
|
369
|
+
|
|
370
|
+
// Directory must be empty
|
|
371
|
+
if (fs.readdirSync(projectPath).filter(f => f.toLowerCase() !== '.DS_STORE').length > 0) {
|
|
372
|
+
console.error('🚫 The target directory is not empty. Please choose an empty directory or remove existing files. Check for hidden files as well.');
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
197
376
|
process.chdir(projectPath);
|
|
198
377
|
|
|
199
|
-
|
|
200
|
-
incrementProgress(answers?.showOutput, "setupConcluded");
|
|
201
|
-
const envContent = `
|
|
202
|
-
##DataBase Configurations##
|
|
203
|
-
DB_HOST=${dbHost}
|
|
204
|
-
DB_PORT=${dbPort}
|
|
205
|
-
DB_USER=${dbUser}
|
|
206
|
-
DB_PASSWORD=${dbPassword}
|
|
207
|
-
DB_NAME=${dbName}
|
|
208
|
-
DB_PROVIDER=${dbProvider}
|
|
209
|
-
DB_URL="\${DB_PROVIDER}://\${DB_USER}:\${DB_PASSWORD}@\${DB_HOST}:\${DB_PORT}/\${DB_NAME}"
|
|
378
|
+
// TODO test database connection here and exit if it fails
|
|
210
379
|
|
|
211
380
|
|
|
212
|
-
|
|
381
|
+
// Install dependencies
|
|
382
|
+
createPackageJson(projectPath, projectName);
|
|
383
|
+
executeCommand('npm --logevel=error install', answers.showOutput);
|
|
384
|
+
incrementProgress(answers?.showOutput, "installDependencies");
|
|
213
385
|
|
|
214
|
-
|
|
215
|
-
|
|
386
|
+
// Update PATH to include local node_modules/.bin
|
|
387
|
+
const localBinPath = path.join(projectPath, 'node_modules', '.bin');
|
|
388
|
+
process.env.PATH = `${localBinPath}${path.delimiter}${process.env.PATH}`;
|
|
216
389
|
|
|
217
|
-
|
|
218
|
-
|
|
390
|
+
// Create Core project structure
|
|
391
|
+
setupProjectStructure(projectPath, project_config);
|
|
219
392
|
|
|
220
|
-
|
|
221
|
-
|
|
393
|
+
// Create .gitignore
|
|
394
|
+
await createGitignore(projectPath);
|
|
222
395
|
|
|
223
|
-
|
|
224
|
-
|
|
396
|
+
// Create tsconfig.json and tsconfig.build.json
|
|
397
|
+
createTsConfig(projectPath);
|
|
225
398
|
|
|
226
|
-
|
|
227
|
-
SESSION_TTL=86400
|
|
399
|
+
incrementProgress(answers?.showOutput, "setupProjectStructure");
|
|
228
400
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
401
|
+
// Create prisma migration file
|
|
402
|
+
executeCommand(`prisma migrate dev --name init --create-only`, answers?.showOutput);
|
|
403
|
+
incrementProgress(answers?.showOutput, "createPrismaMigration");
|
|
404
|
+
|
|
405
|
+
// Generate Core Models
|
|
406
|
+
coreGenerate(answers?.showOutput);
|
|
407
|
+
incrementProgress(answers?.showOutput, "generateCoreModels");
|
|
408
|
+
|
|
409
|
+
// Format code
|
|
410
|
+
executeCommand('npx --yes prettier --write .', answers?.showOutput);
|
|
411
|
+
|
|
412
|
+
// Initialize git repo
|
|
413
|
+
initializeGitRepo(projectPath, answers?.showOutput);
|
|
414
|
+
incrementProgress(answers?.showOutput, "gitInit");
|
|
415
|
+
|
|
416
|
+
// Migrate database
|
|
417
|
+
executeCommand(`prisma migrate dev`, answers?.showOutput);
|
|
418
|
+
|
|
419
|
+
incrementProgress(answers?.showOutput, "migrateDatabase");
|
|
233
420
|
|
|
234
|
-
fs.writeFileSync(`${projectPath}/.env`, envContent);
|
|
235
421
|
if (fileUploadConfigData.provider) {
|
|
236
422
|
await insertFileUploadConfiguration(fileUploadConfigData, projectPath);
|
|
237
423
|
incrementProgress(showOutput, "fileUploadSetup");
|
|
238
424
|
}
|
|
239
|
-
|
|
240
|
-
configureProjectSettings(projectPath);
|
|
241
|
-
executeCommand('npx prettier --write .', answers?.showOutput);
|
|
242
|
-
initializeGitRepo(projectPath);
|
|
243
|
-
incrementProgress(answers?.showOutput, "end");
|
|
425
|
+
|
|
244
426
|
if (!showOutput) {
|
|
245
427
|
progressBar.stop();
|
|
246
428
|
}
|
|
247
429
|
console.log('Project created successfully!');
|
|
430
|
+
|
|
248
431
|
} catch (error) {
|
|
249
432
|
console.error('❌ An error occurred during project creation:', error.message);
|
|
250
433
|
if (fs.existsSync(projectPath)) {
|
|
251
|
-
console.log('🧹 Cleaning up incomplete project files...');
|
|
252
|
-
fs.removeSync(projectPath);
|
|
434
|
+
//console.log('🧹 Cleaning up incomplete project files...');
|
|
435
|
+
//fs.removeSync(projectPath);
|
|
253
436
|
}
|
|
254
437
|
console.error('🚫 Project creation aborted due to the above error.');
|
|
255
438
|
if (!showOutput && progressBar) {
|
|
@@ -259,297 +442,87 @@ JWT_REFRESH_SECRET="${crypto.randomBytes(32).toString('hex')}"
|
|
|
259
442
|
}
|
|
260
443
|
}
|
|
261
444
|
|
|
262
|
-
function
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
445
|
+
function createPackageJson (projectPath, projectName) {
|
|
446
|
+
const packageJsonContent = {
|
|
447
|
+
name: projectName,
|
|
448
|
+
version: "0.0.1",
|
|
449
|
+
description: "",
|
|
450
|
+
author: "Powered by AppX Digital",
|
|
451
|
+
private: true,
|
|
452
|
+
license: "UNLICENSED",
|
|
453
|
+
scripts: {
|
|
454
|
+
"build": "nest build",
|
|
455
|
+
"start": "nest start",
|
|
456
|
+
"start:dev": "cross-env NODE_ENV=development nest start --watch",
|
|
457
|
+
"start:prod": "cross-env NODE_ENV=production node dist/main",
|
|
458
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
459
|
+
"db-pull": "npm run db-pull --prefix ./node_modules/appx-core"
|
|
460
|
+
},
|
|
461
|
+
dependencies: {
|
|
462
|
+
"@appxdigital/appx-core": CORE_VERSION,
|
|
463
|
+
},
|
|
464
|
+
devDependencies: {
|
|
465
|
+
"@nestjs/cli": "~11.0.0",
|
|
466
|
+
"cross-env": "~10.1.0",
|
|
279
467
|
}
|
|
280
|
-
|
|
281
|
-
fs.writeJsonSync(tsConfigPath, tsConfig, { spaces: 2 });
|
|
282
|
-
} else {
|
|
283
|
-
console.warn('Could not find generated tsconfig.json to modify.');
|
|
284
|
-
}
|
|
468
|
+
};
|
|
285
469
|
|
|
286
|
-
|
|
287
|
-
fs.writeFileSync(`${projectPath}/src/app.module.ts`, appModuleContent);
|
|
288
|
-
incrementProgress(answers?.showOutput, "appmodule");
|
|
289
|
-
installDependenciesFromManifest(answers?.showOutput);
|
|
290
|
-
executeCommand('npm install --save-dev @types/multer @types/express cross-env', answers?.showOutput);
|
|
291
|
-
incrementProgress(answers?.showOutput, "installDependencies");
|
|
292
|
-
|
|
293
|
-
// const tgzPath = path.resolve('D:/Projects/core/appx-core', fs.readdirSync('D:/Projects/core/appx-core').find(f => f.startsWith('appx-core-package') && f.endsWith('.tgz')));
|
|
294
|
-
// executeCommand(`npm install "${tgzPath}"`, answers?.showOutput);
|
|
295
|
-
executeCommand('npm install @appxdigital/appx-core@latest', answers?.showOutput);
|
|
296
|
-
incrementProgress(answers?.showOutput, "installCore");
|
|
297
|
-
|
|
298
|
-
//Prisma setup
|
|
299
|
-
const prismaVersion = dependencyVersions.devDependencies?.prisma;
|
|
300
|
-
if (!prismaVersion) {
|
|
301
|
-
console.error('Prisma version not found in dependency-versions.json devDependencies. Cannot run prisma init.');
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
executeCommand(`npx prisma@${prismaVersion} init`, answers?.showOutput);
|
|
305
|
-
customizePrismaSchema(projectPath, answers);
|
|
306
|
-
incrementProgress(answers?.showOutput, "prismaSetup");
|
|
307
|
-
createPrismaModule(projectPath);
|
|
308
|
-
createPermissionsConfig(projectPath);
|
|
309
|
-
setupAdminJS(projectPath);
|
|
310
|
-
createAdminConfig(projectPath);
|
|
311
|
-
addScriptsToPackageJson(projectPath);
|
|
470
|
+
fs.writeJsonSync(path.join(projectPath, 'package.json'), packageJsonContent, {spaces: 2});
|
|
312
471
|
}
|
|
313
472
|
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
473
|
+
function setupProjectStructure (projectPath, project_config) {
|
|
474
|
+
// Create scaffold files from scaffold directory recursively, replacing placeholders from *.template files
|
|
475
|
+
const scaffoldDir = path.join(__dirname, 'scaffold');
|
|
476
|
+
|
|
477
|
+
function copyAndReplaceTemplates (srcDir, destDir) {
|
|
478
|
+
fs.readdirSync(srcDir).forEach((item) => {
|
|
479
|
+
const srcPath = path.join(srcDir, item);
|
|
480
|
+
let destPath = path.join(destDir, item.replace('.template', ''));
|
|
481
|
+
const stats = fs.statSync(srcPath);
|
|
482
|
+
if (stats.isDirectory()) {
|
|
483
|
+
fs.ensureDirSync(destPath);
|
|
484
|
+
copyAndReplaceTemplates(srcPath, destPath);
|
|
485
|
+
} else {
|
|
486
|
+
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
487
|
+
|
|
488
|
+
// Replace placeholders
|
|
489
|
+
for (const [key, value] of Object.entries(project_config)) {
|
|
490
|
+
const placeholder = `{{${key}}}`;
|
|
491
|
+
content = content.replace(new RegExp(placeholder, 'g'), value);
|
|
492
|
+
}
|
|
493
|
+
fs.writeFileSync(destPath, content, 'utf-8');
|
|
494
|
+
}
|
|
495
|
+
});
|
|
332
496
|
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function getBootstrapContent() {
|
|
336
|
-
const bootstrapFilePath = path.join(__dirname, 'templates', 'bootstrap.template.js');
|
|
337
|
-
return fs.readFileSync(bootstrapFilePath, 'utf8');
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function getAppModuleTemplate() {
|
|
341
|
-
const appModuleTemplatePath = path.join(__dirname, 'templates', 'app.module.template.js');
|
|
342
|
-
return fs.readFileSync(appModuleTemplatePath, 'utf8');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function setupAdminJS(projectPath) {
|
|
346
|
-
const backoffice = path.join(projectPath, 'src/backoffice');
|
|
347
|
-
fs.ensureDirSync(backoffice);
|
|
348
|
-
|
|
349
|
-
const backofficeComponents = path.join(backoffice, 'components');
|
|
350
|
-
fs.ensureDirSync(backofficeComponents);
|
|
351
|
-
|
|
352
|
-
const dashboardContent = getDashboardTemplate();
|
|
353
|
-
fs.writeFileSync(path.join(backofficeComponents, 'dashboard.tsx'), dashboardContent);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function getComponentLoaderTemplate() {
|
|
357
|
-
const template = path.join(__dirname, 'templates', 'adminjs', 'component-loader.template.js');
|
|
358
|
-
return fs.readFileSync(template, 'utf8');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function getDashboardTemplate() {
|
|
362
|
-
const template = path.join(__dirname, 'templates', 'adminjs', 'dashboard.template.js');
|
|
363
|
-
return fs.readFileSync(template, 'utf8');
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function getUtilsTemplate() {
|
|
367
|
-
const template = path.join(__dirname, 'templates', 'adminjs', 'utils.template.js');
|
|
368
|
-
return fs.readFileSync(template, 'utf8');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function getAdminTemplate() {
|
|
372
|
-
const template = path.join(__dirname, 'templates', 'adminjs', 'admin.template.js');
|
|
373
|
-
return fs.readFileSync(template, 'utf8');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function getAdminAppModuleTemplate() {
|
|
377
|
-
const appModuleTemplatePath = path.join(__dirname, 'templates', 'adminjs', 'adminjs-app.module.template.js');
|
|
378
|
-
return fs.readFileSync(appModuleTemplatePath, 'utf8');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function createPermissionsConfig(projectPath) {
|
|
382
|
-
const configDir = path.join(projectPath, 'src/config');
|
|
383
|
-
fs.ensureDirSync(configDir);
|
|
384
|
-
|
|
385
|
-
const templatePath = path.join(__dirname, 'templates', 'permissions.config.template.js');
|
|
386
|
-
const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
|
|
387
|
-
|
|
388
|
-
fs.writeFileSync(path.join(configDir, 'permissions.config.ts'), permissionsConfigContent);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function createAdminConfig (projectPath) {
|
|
392
|
-
const configDir = path.join(projectPath, 'src/config');
|
|
393
|
-
fs.ensureDirSync(configDir);
|
|
394
|
-
|
|
395
|
-
const templatePath = path.join(__dirname, 'templates', 'admin.config.template.js');
|
|
396
|
-
const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
|
|
397
|
-
|
|
398
|
-
fs.writeFileSync(path.join(configDir, 'admin.config.ts'), permissionsConfigContent);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function customizePrismaSchema(projectPath, answers) {
|
|
402
|
-
const prismaDir = `${projectPath}/prisma`;
|
|
403
|
-
const schemaFile = `${prismaDir}/schema.prisma`;
|
|
404
|
-
|
|
405
|
-
fs.ensureDirSync(prismaDir);
|
|
406
|
-
|
|
407
|
-
const schemaContent = `
|
|
408
|
-
datasource db {
|
|
409
|
-
provider = "${answers.dbProvider}"
|
|
410
|
-
url = env("DB_URL")
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
generator client {
|
|
414
|
-
provider = "prisma-client-js"
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
generator nestgraphql {
|
|
418
|
-
provider = "node node_modules/prisma-nestjs-graphql"
|
|
419
|
-
output = "../src/generated/"
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
model User {
|
|
423
|
-
id Int @id @default(autoincrement())
|
|
424
|
-
email String @unique
|
|
425
|
-
name String?
|
|
426
|
-
password String? /// @Role(none)
|
|
427
|
-
role Role @default(GUEST)
|
|
428
|
-
refreshTokens UserRefreshToken[]
|
|
429
|
-
}
|
|
430
497
|
|
|
431
|
-
|
|
432
|
-
id String @id
|
|
433
|
-
sid String @unique
|
|
434
|
-
data String
|
|
435
|
-
expiresAt DateTime
|
|
436
|
-
userId Int?
|
|
498
|
+
copyAndReplaceTemplates(scaffoldDir, projectPath);
|
|
437
499
|
}
|
|
438
500
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
@@index([userId])
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
enum Role {
|
|
452
|
-
ADMIN
|
|
453
|
-
GUEST
|
|
454
|
-
}
|
|
455
|
-
`;
|
|
456
|
-
|
|
457
|
-
fs.writeFileSync(schemaFile, schemaContent);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function createPrismaModule(projectPath) {
|
|
461
|
-
const prismaDir = path.join(projectPath, 'src/prisma');
|
|
462
|
-
fs.ensureDirSync(prismaDir);
|
|
463
|
-
|
|
464
|
-
const templatePath = path.join(__dirname, 'templates', 'prisma.module.template.js');
|
|
465
|
-
const prismaModuleContent = fs.readFileSync(templatePath, 'utf-8');
|
|
466
|
-
|
|
467
|
-
fs.writeFileSync(path.join(prismaDir, 'prisma.module.ts'), prismaModuleContent);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function addScriptsToPackageJson(projectPath) {
|
|
471
|
-
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
472
|
-
const packageJson = fs.readJsonSync(packageJsonPath);
|
|
473
|
-
|
|
474
|
-
packageJson.scripts = packageJson.scripts || {};
|
|
475
|
-
packageJson.scripts["start:dev"] = "cross-env NODE_ENV=development nest start --watch";
|
|
476
|
-
packageJson.scripts["start:prod"] = "cross-env NODE_ENV=production node dist/main";
|
|
477
|
-
packageJson.scripts["db-pull"] = "npm run db-pull --prefix ./node_modules/appx-core";
|
|
478
|
-
|
|
479
|
-
fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
480
|
-
}
|
|
501
|
+
function initializeGitRepo (projectPath, showOutput = false) {
|
|
502
|
+
// If git is installed in the system, initialize a git repository and make the initial commit
|
|
503
|
+
let installed = true;
|
|
504
|
+
try {
|
|
505
|
+
execSync('git --version', {stdio: 'ignore'});
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.log('Git is not installed. Skipping git initialization.');
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
481
510
|
|
|
482
|
-
function initializeGitRepo(projectPath) {
|
|
483
511
|
try {
|
|
484
|
-
|
|
485
|
-
|
|
512
|
+
executeCommand('git init', showOutput);
|
|
513
|
+
executeCommand('git add .', showOutput);
|
|
514
|
+
executeCommand('git commit -m "Project init"', showOutput);
|
|
486
515
|
} catch (error) {
|
|
487
516
|
console.error("Failed to initialize git repository or make the initial commit:", error);
|
|
517
|
+
throw error;
|
|
488
518
|
}
|
|
489
519
|
}
|
|
490
520
|
|
|
491
|
-
|
|
492
|
-
function configureProjectSettings(projectPath) {
|
|
493
|
-
const prettierConfig = {
|
|
494
|
-
singleQuote: true,
|
|
495
|
-
trailingComma: 'all',
|
|
496
|
-
endOfLine: 'lf',
|
|
497
|
-
tabWidth: 2,
|
|
498
|
-
semi: true,
|
|
499
|
-
bracketSpacing: true,
|
|
500
|
-
jsxBracketSameLine: true
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
fs.writeFileSync(
|
|
504
|
-
path.join(projectPath, '.prettierrc'),
|
|
505
|
-
JSON.stringify(prettierConfig, null, 2)
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
const eslintConfig = `module.exports = {
|
|
509
|
-
parser: '@typescript-eslint/parser',
|
|
510
|
-
parserOptions: {
|
|
511
|
-
project: 'tsconfig.json',
|
|
512
|
-
tsconfigRootDir: __dirname,
|
|
513
|
-
sourceType: 'module',
|
|
514
|
-
},
|
|
515
|
-
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
516
|
-
extends: [
|
|
517
|
-
'plugin:@typescript-eslint/recommended',
|
|
518
|
-
'plugin:prettier/recommended',
|
|
519
|
-
],
|
|
520
|
-
root: true,
|
|
521
|
-
env: {
|
|
522
|
-
node: true,
|
|
523
|
-
jest: true,
|
|
524
|
-
},
|
|
525
|
-
ignorePatterns: ['.eslintrc.js'],
|
|
526
|
-
rules: {
|
|
527
|
-
'@typescript-eslint/interface-name-prefix': 'off',
|
|
528
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
529
|
-
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
530
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
531
|
-
'prettier/prettier': ['error', { 'endOfLine': 'lf' }],
|
|
532
|
-
},
|
|
533
|
-
};`;
|
|
534
|
-
|
|
535
|
-
fs.writeFileSync(path.join(projectPath, '.eslintrc.js'), eslintConfig);
|
|
536
|
-
|
|
537
|
-
const gitattributesContent = `
|
|
538
|
-
* text=auto eol=lf
|
|
539
|
-
`;
|
|
540
|
-
fs.writeFileSync(path.join(projectPath, '.gitattributes'), gitattributesContent);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function updateGitignore(projectPath) {
|
|
544
|
-
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
521
|
+
async function createGitignore (projectPath) {
|
|
545
522
|
const envFilesToAdd = ['.env', '.env.production'];
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
} else {
|
|
550
|
-
console.log(`.gitignore file not found. Creating a new one.`);
|
|
551
|
-
}
|
|
552
|
-
const existingLines = gitignoreContent.split('\n');
|
|
523
|
+
|
|
524
|
+
const {defaultGitIgnore} = await import(path.join(projectPath, 'node_modules', '@nestjs/cli/lib/configuration/defaults.js'));
|
|
525
|
+
const existingLines = defaultGitIgnore.split('\n');
|
|
553
526
|
const updatedLines = new Set(existingLines);
|
|
554
527
|
envFilesToAdd.forEach((file) => {
|
|
555
528
|
if (!existingLines.includes(file)) {
|
|
@@ -557,42 +530,45 @@ function updateGitignore(projectPath) {
|
|
|
557
530
|
}
|
|
558
531
|
});
|
|
559
532
|
const finalContent = Array.from(updatedLines).join('\n').trim() + '\n';
|
|
533
|
+
|
|
534
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
560
535
|
fs.writeFileSync(gitignorePath, finalContent, 'utf-8');
|
|
561
536
|
}
|
|
562
537
|
|
|
538
|
+
function createTsConfig (projectPath) {
|
|
539
|
+
let tsConfig = fs.readFileSync(path.join(projectPath, 'node_modules/@nestjs/schematics/dist/lib/application/files/ts/tsconfig.json'), 'utf-8');
|
|
540
|
+
// Replace '<%= strict %>' with 'false'
|
|
541
|
+
tsConfig = tsConfig.replaceAll("<%= strict %>", 'false');
|
|
542
|
+
|
|
543
|
+
let tsConfigBuild = fs.readFileSync(path.join(projectPath, 'node_modules/@nestjs/schematics/dist/lib/application/files/ts/tsconfig.build.json'), 'utf-8');
|
|
563
544
|
|
|
564
|
-
|
|
545
|
+
// Ensure target is ES2017
|
|
546
|
+
tsConfig = JSON.parse(tsConfig);
|
|
547
|
+
if (!tsConfig.compilerOptions) {
|
|
548
|
+
tsConfig.compilerOptions = {};
|
|
549
|
+
}
|
|
550
|
+
tsConfig.compilerOptions.target = 'ES2017';
|
|
551
|
+
tsConfig = JSON.stringify(tsConfig, null, 2);
|
|
552
|
+
|
|
553
|
+
fs.writeFileSync(path.join(projectPath, 'tsconfig.json'), tsConfig);
|
|
554
|
+
fs.writeFileSync(path.join(projectPath, 'tsconfig.build.json'), tsConfigBuild);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
function incrementProgress (showOutput, phase) {
|
|
565
559
|
if (!showOutput) {
|
|
566
560
|
remainingETA -= phaseDurations[phase];
|
|
567
|
-
progressBar.update(progressBar.value + phase === 'end' ? 0 : 1, {
|
|
561
|
+
progressBar.update(progressBar.value + phase === 'end' ? 0 : 1, {remainingETA});
|
|
568
562
|
}
|
|
569
563
|
}
|
|
570
564
|
|
|
571
|
-
function executeCommand(command, showOutput = answers?.showOutput) {
|
|
572
|
-
const options = showOutput ? {
|
|
565
|
+
function executeCommand (command, showOutput = answers?.showOutput) {
|
|
566
|
+
const options = showOutput ? {stdio: 'inherit'} : {stdio: 'ignore'};
|
|
573
567
|
try {
|
|
574
568
|
execSync(command, options);
|
|
575
569
|
} catch (error) {
|
|
576
570
|
console.error(`Failed to execute command: ${command}`, error);
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function installDependenciesFromManifest(showOutput = true) {
|
|
581
|
-
const { dependencies, devDependencies } = dependencyVersions;
|
|
582
|
-
const depList = Object.entries(dependencies)
|
|
583
|
-
.map(([pkg, version]) => `${pkg}@${version}`)
|
|
584
|
-
.join(' ');
|
|
585
|
-
|
|
586
|
-
const devDepList = Object.entries(devDependencies)
|
|
587
|
-
.map(([pkg, version]) => `${pkg}@${version}`)
|
|
588
|
-
.join(' ');
|
|
589
|
-
|
|
590
|
-
if (depList) {
|
|
591
|
-
executeCommand(`npm install ${depList}`, showOutput);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (devDepList) {
|
|
595
|
-
executeCommand(`npm install -D ${devDepList}`, showOutput);
|
|
571
|
+
throw error;
|
|
596
572
|
}
|
|
597
573
|
}
|
|
598
574
|
|