@ilanalimanjs/open_pack 2.4.3 → 2.4.4
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/index.js +129 -119
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -8,34 +8,37 @@ import { parseArgs } from 'node:util';
|
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
10
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
11
|
-
import { downloadTemplate } from 'giget';
|
|
12
11
|
import * as prompts from '@clack/prompts';
|
|
13
12
|
import fse from 'fs-extra';
|
|
14
13
|
import chalk from 'chalk';
|
|
15
14
|
import ora from 'ora';
|
|
16
15
|
|
|
16
|
+
process.noDeprecation = true;
|
|
17
|
+
|
|
17
18
|
const { version: CLI_VERSION } = createRequire(import.meta.url)('../package.json');
|
|
18
19
|
|
|
19
20
|
const TEMPLATE_SOURCE = 'github:DevIlannn/open_pack/Source';
|
|
20
21
|
const REGISTRY_URL = 'https://registry.npmjs.org/@ilanalimanjs%2Fopen_pack/latest';
|
|
21
22
|
const PACKAGE_COMMAND = 'npx @ilanalimanjs/open_pack@latest';
|
|
22
23
|
const NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
23
|
-
const DATABASE_URL_PATTERN = /^postgres(ql)?:\/\/.+/;
|
|
24
24
|
const ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
|
|
25
25
|
const ENV_EXAMPLE_FILE = '.env.examples';
|
|
26
26
|
const ENV_FILE = '.env';
|
|
27
|
+
const LINUX_CHASSIS_FILE = '/sys/class/dmi/id/chassis_type';
|
|
28
|
+
const LINUX_POWER_SUPPLY_DIRECTORY = '/sys/class/power_supply';
|
|
29
|
+
const WINDOWS_CHASSIS_SCRIPT = '(Get-CimInstance -ClassName Win32_SystemEnclosure).ChassisTypes';
|
|
27
30
|
const DEFAULT_PORT = 3000;
|
|
28
|
-
const DEFAULT_DATABASE_PORT = 5432;
|
|
29
31
|
const MAX_PORT = 65535;
|
|
30
32
|
const PORT_SEARCH_RANGE = 50;
|
|
31
33
|
const MIN_NODE_VERSION = { major: 20, minor: 11 };
|
|
32
34
|
const BANNER_DELAY_MS = 70;
|
|
33
35
|
const STEP_MIN_DURATION_MS = 450;
|
|
34
36
|
const DETECT_TIMEOUT_MS = 4000;
|
|
35
|
-
const DATABASE_TIMEOUT_MS = 2500;
|
|
36
37
|
const UPDATE_TIMEOUT_MS = 2500;
|
|
37
38
|
const EXIT_INTERRUPTED = 130;
|
|
38
39
|
const GRADIENT = ['#22d3ee', '#38bdf8', '#60a5fa', '#818cf8', '#a78bfa'];
|
|
40
|
+
const CHASSIS_LAPTOP = new Set([8, 9, 10, 14, 31, 32]);
|
|
41
|
+
const CHASSIS_PC = new Set([3, 4, 5, 6, 7, 13, 15, 16, 24, 35, 36]);
|
|
39
42
|
|
|
40
43
|
const GLYPHS = [
|
|
41
44
|
[String.raw` ___ `, String.raw` / _ \ `, String.raw`| | | |`, String.raw`| |_| |`, String.raw` \___/ `],
|
|
@@ -60,7 +63,15 @@ const PACKAGE_MANAGERS = {
|
|
|
60
63
|
const PLATFORM_LABELS = {
|
|
61
64
|
darwin: 'macOS',
|
|
62
65
|
win32: 'Windows',
|
|
63
|
-
linux: 'Linux'
|
|
66
|
+
linux: 'Linux',
|
|
67
|
+
android: 'Android'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DEVICE_TYPES = {
|
|
71
|
+
phone: 'Phone',
|
|
72
|
+
laptop: 'Laptop',
|
|
73
|
+
pc: 'PC',
|
|
74
|
+
other: 'Other'
|
|
64
75
|
};
|
|
65
76
|
|
|
66
77
|
const MODES = {
|
|
@@ -72,11 +83,9 @@ const MODES = {
|
|
|
72
83
|
const CLI_OPTIONS = {
|
|
73
84
|
yes: { type: 'boolean', short: 'y' },
|
|
74
85
|
pm: { type: 'string' },
|
|
75
|
-
'database-url': { type: 'string' },
|
|
76
86
|
port: { type: 'string' },
|
|
77
87
|
'no-install': { type: 'boolean' },
|
|
78
88
|
'no-run': { type: 'boolean' },
|
|
79
|
-
'no-db-check': { type: 'boolean' },
|
|
80
89
|
'no-update-check': { type: 'boolean' },
|
|
81
90
|
help: { type: 'boolean', short: 'h' },
|
|
82
91
|
version: { type: 'boolean', short: 'v' }
|
|
@@ -85,11 +94,9 @@ const CLI_OPTIONS = {
|
|
|
85
94
|
const HELP_OPTIONS = [
|
|
86
95
|
['-y, --yes', 'Skip prompts and use defaults'],
|
|
87
96
|
['--pm <name>', 'Package manager: npm, pnpm, yarn, bun'],
|
|
88
|
-
['--database-url <url>', 'PostgreSQL connection string'],
|
|
89
97
|
['--port <number>', `Development server port (default ${DEFAULT_PORT})`],
|
|
90
98
|
['--no-install', 'Create the project without installing dependencies'],
|
|
91
99
|
['--no-run', 'Install dependencies without starting the server'],
|
|
92
|
-
['--no-db-check', 'Skip the database reachability check'],
|
|
93
100
|
['--no-update-check', 'Skip the CLI update check'],
|
|
94
101
|
['-h, --help', 'Show this help'],
|
|
95
102
|
['-v, --version', 'Show the CLI version']
|
|
@@ -99,6 +106,7 @@ const accent = chalk.hex('#38bdf8');
|
|
|
99
106
|
const muted = chalk.gray;
|
|
100
107
|
const isWindows = process.platform === 'win32';
|
|
101
108
|
const isTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
109
|
+
const SHELL_TOOLS = new Set(Object.keys(PACKAGE_MANAGERS));
|
|
102
110
|
|
|
103
111
|
const STATE_ICONS = {
|
|
104
112
|
done: chalk.green('✔'),
|
|
@@ -113,6 +121,11 @@ const pause = (ms) => (isTTY ? sleep(ms) : Promise.resolve());
|
|
|
113
121
|
|
|
114
122
|
const visibleLength = (value) => value.replace(ANSI_PATTERN, '').length;
|
|
115
123
|
|
|
124
|
+
const spawnTool = (command, args, options) =>
|
|
125
|
+
isWindows && SHELL_TOOLS.has(command)
|
|
126
|
+
? spawn([command, ...args].join(' '), { ...options, shell: true })
|
|
127
|
+
: spawn(command, args, options);
|
|
128
|
+
|
|
116
129
|
const renderCard = (title, rows) => {
|
|
117
130
|
const labelWidth = Math.max(...rows.map((row) => visibleLength(row.label)));
|
|
118
131
|
const lines = rows.map((row) => `${row.label}${' '.repeat(labelWidth - visibleLength(row.label))} ${row.value}`);
|
|
@@ -141,9 +154,6 @@ const validateProjectName = (value) => {
|
|
|
141
154
|
return undefined;
|
|
142
155
|
};
|
|
143
156
|
|
|
144
|
-
const validateDatabaseUrl = (value) =>
|
|
145
|
-
DATABASE_URL_PATTERN.test(value ?? '') ? undefined : 'Use the format postgresql://user:password@host:port/db';
|
|
146
|
-
|
|
147
157
|
const validatePort = (value) => {
|
|
148
158
|
const port = Number(value);
|
|
149
159
|
return /^\d+$/.test(String(value ?? '')) && port >= 1 && port <= MAX_PORT
|
|
@@ -155,8 +165,7 @@ const validateFlags = ({ values, positionals }) => {
|
|
|
155
165
|
const checks = [
|
|
156
166
|
positionals[0] === undefined ? undefined : validateProjectName(positionals[0]),
|
|
157
167
|
values.pm === undefined || PACKAGE_MANAGERS[values.pm] ? undefined : '--pm must be one of npm, pnpm, yarn, bun',
|
|
158
|
-
values.port === undefined ? undefined : validatePort(values.port)
|
|
159
|
-
values['database-url'] === undefined ? undefined : validateDatabaseUrl(values['database-url'])
|
|
168
|
+
values.port === undefined ? undefined : validatePort(values.port)
|
|
160
169
|
];
|
|
161
170
|
return checks.find(Boolean);
|
|
162
171
|
};
|
|
@@ -170,26 +179,23 @@ const resolveFixedMode = (values) => {
|
|
|
170
179
|
const readFixedOptions = ({ values, positionals }) => ({
|
|
171
180
|
name: positionals[0],
|
|
172
181
|
packageManager: values.pm,
|
|
173
|
-
databaseUrl: values['database-url'],
|
|
174
182
|
port: values.port === undefined ? undefined : Number(values.port),
|
|
175
183
|
mode: resolveFixedMode(values)
|
|
176
184
|
});
|
|
177
185
|
|
|
178
186
|
const captureCommand = (command, args) =>
|
|
179
187
|
new Promise((resolve) => {
|
|
180
|
-
const child =
|
|
181
|
-
shell: isWindows,
|
|
182
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
183
|
-
timeout: DETECT_TIMEOUT_MS
|
|
184
|
-
});
|
|
188
|
+
const child = spawnTool(command, args, { stdio: ['ignore', 'pipe', 'ignore'], timeout: DETECT_TIMEOUT_MS });
|
|
185
189
|
let output = '';
|
|
186
190
|
child.stdout.on('data', (chunk) => {
|
|
187
191
|
output += chunk.toString();
|
|
188
192
|
});
|
|
189
193
|
child.on('error', () => resolve(null));
|
|
190
|
-
child.on('close', (code) => resolve(code === 0 ?
|
|
194
|
+
child.on('close', (code) => resolve(code === 0 ? output.trim() : null));
|
|
191
195
|
});
|
|
192
196
|
|
|
197
|
+
const captureFirstLine = async (command, args) => (await captureCommand(command, args))?.split('\n')[0].trim() ?? null;
|
|
198
|
+
|
|
193
199
|
const isPortFree = (port) =>
|
|
194
200
|
new Promise((resolve) => {
|
|
195
201
|
const server = net.createServer();
|
|
@@ -205,20 +211,6 @@ const findFreePort = async (start) => {
|
|
|
205
211
|
throw new Error(`No free port found from ${start}`);
|
|
206
212
|
};
|
|
207
213
|
|
|
208
|
-
const isTcpReachable = (host, port) =>
|
|
209
|
-
new Promise((resolve) => {
|
|
210
|
-
const socket = net.connect({ host, port, timeout: DATABASE_TIMEOUT_MS });
|
|
211
|
-
socket.once('connect', () => {
|
|
212
|
-
socket.destroy();
|
|
213
|
-
resolve(true);
|
|
214
|
-
});
|
|
215
|
-
socket.once('timeout', () => {
|
|
216
|
-
socket.destroy();
|
|
217
|
-
resolve(false);
|
|
218
|
-
});
|
|
219
|
-
socket.once('error', () => resolve(false));
|
|
220
|
-
});
|
|
221
|
-
|
|
222
214
|
const fetchLatestVersion = async () => {
|
|
223
215
|
try {
|
|
224
216
|
const response = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS) });
|
|
@@ -241,27 +233,82 @@ const isNodeSupported = () => {
|
|
|
241
233
|
return major > MIN_NODE_VERSION.major || (major === MIN_NODE_VERSION.major && minor >= MIN_NODE_VERSION.minor);
|
|
242
234
|
};
|
|
243
235
|
|
|
236
|
+
const classifyChassis = (code) => {
|
|
237
|
+
if (CHASSIS_LAPTOP.has(code)) return DEVICE_TYPES.laptop;
|
|
238
|
+
if (CHASSIS_PC.has(code)) return DEVICE_TYPES.pc;
|
|
239
|
+
return DEVICE_TYPES.other;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const isAndroid = () =>
|
|
243
|
+
process.platform === 'android' || Boolean(process.env.TERMUX_VERSION) || (process.env.PREFIX ?? '').includes('com.termux');
|
|
244
|
+
|
|
245
|
+
const readLinuxChassis = async () => {
|
|
246
|
+
try {
|
|
247
|
+
return Number((await fse.readFile(LINUX_CHASSIS_FILE, 'utf8')).trim());
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const hasLinuxBattery = async () => {
|
|
254
|
+
try {
|
|
255
|
+
return (await fse.readdir(LINUX_POWER_SUPPLY_DIRECTORY)).some((entry) => /^BAT/i.test(entry));
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const detectDeviceType = async () => {
|
|
262
|
+
if (isAndroid()) return DEVICE_TYPES.phone;
|
|
263
|
+
|
|
264
|
+
if (process.platform === 'win32') {
|
|
265
|
+
const output = await captureCommand('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', WINDOWS_CHASSIS_SCRIPT]);
|
|
266
|
+
return classifyChassis(Number((output ?? '').split(/\s+/).find((token) => /^\d+$/.test(token))));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (process.platform === 'darwin') {
|
|
270
|
+
const output = await captureCommand('pmset', ['-g', 'batt']);
|
|
271
|
+
return output?.includes('InternalBattery') ? DEVICE_TYPES.laptop : DEVICE_TYPES.pc;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (process.platform === 'linux') {
|
|
275
|
+
const code = await readLinuxChassis();
|
|
276
|
+
if (code !== null) return classifyChassis(code);
|
|
277
|
+
return (await hasLinuxBattery()) ? DEVICE_TYPES.laptop : DEVICE_TYPES.other;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return DEVICE_TYPES.other;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const detectIpAddress = () => {
|
|
284
|
+
const [entry] = Object.values(os.networkInterfaces())
|
|
285
|
+
.flat()
|
|
286
|
+
.filter((item) => item && item.family === 'IPv4' && !item.internal);
|
|
287
|
+
return entry?.address ?? null;
|
|
288
|
+
};
|
|
289
|
+
|
|
244
290
|
const detectEnvironment = async ({ port, checkUpdate }) => {
|
|
245
291
|
const managerNames = Object.keys(PACKAGE_MANAGERS);
|
|
246
|
-
const [
|
|
247
|
-
|
|
292
|
+
const [gitVersion, portFree, latestVersion, device, ...managerVersions] = await Promise.all([
|
|
293
|
+
captureFirstLine('git', ['--version']),
|
|
248
294
|
isPortFree(port),
|
|
249
295
|
checkUpdate ? fetchLatestVersion() : Promise.resolve(null),
|
|
250
|
-
|
|
296
|
+
detectDeviceType(),
|
|
297
|
+
...managerNames.map((name) => captureFirstLine(name, ['--version']))
|
|
251
298
|
]);
|
|
252
299
|
const managers = Object.fromEntries(
|
|
253
300
|
managerNames.map((name, index) => [name, managerVersions[index]]).filter(([, version]) => version)
|
|
254
301
|
);
|
|
255
|
-
const cpus = os.cpus();
|
|
256
302
|
|
|
257
303
|
return {
|
|
258
304
|
system: `${PLATFORM_LABELS[process.platform] ?? process.platform} ${os.release()} (${os.arch()})`,
|
|
259
|
-
device
|
|
305
|
+
device,
|
|
306
|
+
ipAddress: detectIpAddress(),
|
|
260
307
|
node: process.versions.node,
|
|
261
308
|
nodeSupported: isNodeSupported(),
|
|
262
309
|
launcher: parseLauncher(),
|
|
263
310
|
managers,
|
|
264
|
-
git:
|
|
311
|
+
git: gitVersion ? gitVersion.replace(/^git version\s*/, '') : null,
|
|
265
312
|
port,
|
|
266
313
|
portFree,
|
|
267
314
|
latestVersion
|
|
@@ -269,36 +316,25 @@ const detectEnvironment = async ({ port, checkUpdate }) => {
|
|
|
269
316
|
};
|
|
270
317
|
|
|
271
318
|
const printEnvironmentCard = (environment) => {
|
|
272
|
-
const managerList = Object.entries(environment.managers).map(([name, version]) => `${name} ${version}`);
|
|
273
|
-
const nodeStatus = environment.nodeSupported
|
|
274
|
-
? chalk.green('✔')
|
|
275
|
-
: chalk.red(`✖ requires >=${MIN_NODE_VERSION.major}.${MIN_NODE_VERSION.minor}`);
|
|
276
319
|
const hasUpdate = environment.latestVersion && compareVersions(environment.latestVersion, CLI_VERSION) > 0;
|
|
320
|
+
const nodeValue = environment.nodeSupported
|
|
321
|
+
? `v${environment.node}`
|
|
322
|
+
: chalk.red(`v${environment.node} (requires >=${MIN_NODE_VERSION.major}.${MIN_NODE_VERSION.minor})`);
|
|
323
|
+
const portValue = environment.portFree
|
|
324
|
+
? `${environment.port} available`
|
|
325
|
+
: chalk.yellow(`${environment.port} in use`);
|
|
277
326
|
const rows = [
|
|
278
327
|
{ label: muted('System'), value: environment.system },
|
|
279
328
|
{ label: muted('Device'), value: environment.device },
|
|
280
|
-
{ label: muted('
|
|
281
|
-
{
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
label: muted('Package managers'),
|
|
287
|
-
value: managerList.length ? managerList.join(', ') : chalk.yellow('none detected')
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
label: muted('Git'),
|
|
291
|
-
value: environment.git ? `${environment.git} ${chalk.green('✔')}` : muted('not found')
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
label: muted('Port'),
|
|
295
|
-
value: `${environment.port} ${environment.portFree ? `${chalk.green('✔')} free` : chalk.yellow('in use')}`
|
|
296
|
-
},
|
|
329
|
+
{ label: muted('IP address'), value: environment.ipAddress ?? muted('unavailable') },
|
|
330
|
+
{ label: muted('Node.js'), value: nodeValue },
|
|
331
|
+
{ label: muted('Git'), value: environment.git ?? muted('not found') },
|
|
332
|
+
{ label: muted('Port'), value: portValue },
|
|
297
333
|
{
|
|
298
334
|
label: muted('Openpack'),
|
|
299
335
|
value: hasUpdate
|
|
300
|
-
? `v${CLI_VERSION} ${chalk.yellow(`update available: ${environment.latestVersion}`)}`
|
|
301
|
-
: `v${CLI_VERSION}
|
|
336
|
+
? `v${CLI_VERSION} ${chalk.yellow(`(update available: ${environment.latestVersion})`)}`
|
|
337
|
+
: `v${CLI_VERSION}`
|
|
302
338
|
}
|
|
303
339
|
];
|
|
304
340
|
console.log(renderCard('Environment', rows));
|
|
@@ -339,7 +375,7 @@ const printHelp = () => {
|
|
|
339
375
|
});
|
|
340
376
|
console.log();
|
|
341
377
|
console.log(` ${muted('Example')}`);
|
|
342
|
-
console.log(` ${PACKAGE_COMMAND} ${accent('my-project --yes --pm pnpm --
|
|
378
|
+
console.log(` ${PACKAGE_COMMAND} ${accent('my-project --yes --pm pnpm --no-run')}`);
|
|
343
379
|
console.log();
|
|
344
380
|
};
|
|
345
381
|
|
|
@@ -353,7 +389,7 @@ const printFailure = (title, detail) => {
|
|
|
353
389
|
|
|
354
390
|
const runCommand = (command, args, cwd) =>
|
|
355
391
|
new Promise((resolve, reject) => {
|
|
356
|
-
const child =
|
|
392
|
+
const child = spawnTool(command, args, { cwd, stdio: ['ignore', 'ignore', 'pipe'] });
|
|
357
393
|
const errors = [];
|
|
358
394
|
child.stderr.on('data', (chunk) => errors.push(chunk.toString()));
|
|
359
395
|
child.on('error', reject);
|
|
@@ -366,7 +402,7 @@ const runCommand = (command, args, cwd) =>
|
|
|
366
402
|
|
|
367
403
|
const runInteractive = (command, args, cwd) =>
|
|
368
404
|
new Promise((resolve, reject) => {
|
|
369
|
-
const child =
|
|
405
|
+
const child = spawnTool(command, args, { cwd, stdio: 'inherit' });
|
|
370
406
|
child.on('error', reject);
|
|
371
407
|
child.on('close', () => resolve());
|
|
372
408
|
});
|
|
@@ -421,26 +457,19 @@ const askOptions = async (cli, environment) => {
|
|
|
421
457
|
placeholder: 'my-project',
|
|
422
458
|
validate: validateProjectName
|
|
423
459
|
}),
|
|
424
|
-
packageManager: () =>
|
|
425
|
-
fixed.packageManager !== undefined
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
fixed.databaseUrl !== undefined
|
|
438
|
-
? Promise.resolve(fixed.databaseUrl)
|
|
439
|
-
: prompts.text({
|
|
440
|
-
message: 'PostgreSQL DATABASE_URL',
|
|
441
|
-
placeholder: 'postgresql://user:password@localhost:5432/mydb',
|
|
442
|
-
validate: validateDatabaseUrl
|
|
443
|
-
}),
|
|
460
|
+
packageManager: () => {
|
|
461
|
+
if (fixed.packageManager !== undefined) return Promise.resolve(fixed.packageManager);
|
|
462
|
+
if (managers.names.length === 1) return Promise.resolve(managers.names[0]);
|
|
463
|
+
return prompts.select({
|
|
464
|
+
message: 'Choose a package manager',
|
|
465
|
+
initialValue: managers.initial,
|
|
466
|
+
options: managers.names.map((value) => ({
|
|
467
|
+
value,
|
|
468
|
+
label: PACKAGE_MANAGERS[value].label,
|
|
469
|
+
hint: environment.managers[value] ? `v${environment.managers[value]}` : undefined
|
|
470
|
+
}))
|
|
471
|
+
});
|
|
472
|
+
},
|
|
444
473
|
mode: () =>
|
|
445
474
|
fixed.mode !== undefined
|
|
446
475
|
? Promise.resolve(fixed.mode)
|
|
@@ -477,7 +506,6 @@ const askOptions = async (cli, environment) => {
|
|
|
477
506
|
const resolveDefaultOptions = async (cli, environment) => {
|
|
478
507
|
const fixed = readFixedOptions(cli);
|
|
479
508
|
if (fixed.name === undefined) throw new UsageError('Project name is required in non-interactive mode');
|
|
480
|
-
if (fixed.databaseUrl === undefined) throw new UsageError('--database-url is required in non-interactive mode');
|
|
481
509
|
|
|
482
510
|
const mode = fixed.mode ?? (process.stdout.isTTY ? MODES.run : MODES.install);
|
|
483
511
|
const desired = fixed.port ?? DEFAULT_PORT;
|
|
@@ -491,23 +519,12 @@ const resolveDefaultOptions = async (cli, environment) => {
|
|
|
491
519
|
return {
|
|
492
520
|
name: fixed.name,
|
|
493
521
|
packageManager: fixed.packageManager ?? resolveManagerChoices(environment).initial,
|
|
494
|
-
databaseUrl: fixed.databaseUrl,
|
|
495
522
|
mode,
|
|
496
523
|
port
|
|
497
524
|
};
|
|
498
525
|
};
|
|
499
526
|
|
|
500
|
-
const
|
|
501
|
-
const url = new URL(databaseUrl);
|
|
502
|
-
const host = url.hostname.replace(/^\[|\]$/g, '');
|
|
503
|
-
const port = Number(url.port) || DEFAULT_DATABASE_PORT;
|
|
504
|
-
const target = `${host}:${port}`;
|
|
505
|
-
return (await isTcpReachable(host, port))
|
|
506
|
-
? { label: `Database reachable at ${target}` }
|
|
507
|
-
: { state: 'warning', label: `Database unreachable at ${target}` };
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
const buildSteps = ({ name, packageManager, databaseUrl, mode, port }, { dbCheck }) => {
|
|
527
|
+
const buildSteps = ({ name, packageManager, mode, port }) => {
|
|
511
528
|
const targetDirectory = path.resolve(process.cwd(), name);
|
|
512
529
|
const manager = PACKAGE_MANAGERS[packageManager];
|
|
513
530
|
const customPort = port !== DEFAULT_PORT;
|
|
@@ -515,7 +532,10 @@ const buildSteps = ({ name, packageManager, databaseUrl, mode, port }, { dbCheck
|
|
|
515
532
|
{
|
|
516
533
|
active: 'Fetching template',
|
|
517
534
|
done: 'Template fetched',
|
|
518
|
-
task: () =>
|
|
535
|
+
task: async () => {
|
|
536
|
+
const { downloadTemplate } = await import('giget');
|
|
537
|
+
await downloadTemplate(TEMPLATE_SOURCE, { dir: targetDirectory });
|
|
538
|
+
}
|
|
519
539
|
},
|
|
520
540
|
{
|
|
521
541
|
active: 'Preparing environment',
|
|
@@ -528,31 +548,21 @@ const buildSteps = ({ name, packageManager, databaseUrl, mode, port }, { dbCheck
|
|
|
528
548
|
},
|
|
529
549
|
{
|
|
530
550
|
active: 'Injecting template configuration',
|
|
531
|
-
done: customPort ? '
|
|
551
|
+
done: customPort ? 'SESSION_SECRET and PORT injected' : 'SESSION_SECRET injected',
|
|
532
552
|
task: async () => {
|
|
533
553
|
const envPath = path.join(targetDirectory, ENV_FILE);
|
|
534
554
|
const current = await fse.readFile(envPath, 'utf8');
|
|
535
|
-
const
|
|
536
|
-
const withSecret = setEnvValue(withDatabase, 'SESSION_SECRET', randomBytes(32).toString('hex'));
|
|
555
|
+
const withSecret = setEnvValue(current, 'SESSION_SECRET', randomBytes(32).toString('hex'));
|
|
537
556
|
await fse.writeFile(envPath, customPort ? setEnvValue(withSecret, 'PORT', String(port)) : withSecret);
|
|
538
557
|
}
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
active: 'Entering project directory',
|
|
561
|
+
done: `Entered ${name}`,
|
|
562
|
+
task: async () => process.chdir(targetDirectory)
|
|
539
563
|
}
|
|
540
564
|
];
|
|
541
565
|
|
|
542
|
-
if (dbCheck) {
|
|
543
|
-
steps.push({
|
|
544
|
-
active: 'Checking database connection',
|
|
545
|
-
done: 'Database reachable',
|
|
546
|
-
task: () => checkDatabase(databaseUrl)
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
steps.push({
|
|
551
|
-
active: 'Entering project directory',
|
|
552
|
-
done: `Entered ${name}`,
|
|
553
|
-
task: async () => process.chdir(targetDirectory)
|
|
554
|
-
});
|
|
555
|
-
|
|
556
566
|
if (mode !== MODES.none) {
|
|
557
567
|
steps.push({
|
|
558
568
|
active: `Installing dependencies with ${manager.label}`,
|
|
@@ -663,7 +673,7 @@ async function main() {
|
|
|
663
673
|
|
|
664
674
|
const options = interactive ? await askOptions(cli, environment) : await resolveDefaultOptions(cli, environment);
|
|
665
675
|
const originalCwd = process.cwd();
|
|
666
|
-
const { steps, targetDirectory, manager } = buildSteps(options
|
|
676
|
+
const { steps, targetDirectory, manager } = buildSteps(options);
|
|
667
677
|
const rollback = createRollback(targetDirectory, originalCwd, !(await fse.pathExists(targetDirectory)));
|
|
668
678
|
const results = steps.map((step) => ({ label: step.done, state: 'pending', duration: null }));
|
|
669
679
|
const startedAt = performance.now();
|