@tukuyomil032/bricklayer 1.0.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 +92 -0
- package/dist/create/file-writer.js +96 -0
- package/dist/create/index.js +91 -0
- package/dist/create/installer.js +167 -0
- package/dist/create/licenses.js +1024 -0
- package/dist/create/package-versions.js +63 -0
- package/dist/create/prompts.js +249 -0
- package/dist/create/templates.js +410 -0
- package/dist/create/types.js +1 -0
- package/dist/index.js +16 -0
- package/dist/sample.js +11 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# bricklayer
|
|
2
|
+
|
|
3
|
+
🧱 Interactive TypeScript CLI project scaffolder
|
|
4
|
+
|
|
5
|
+
Quickly generate a well-structured TypeScript CLI project with best practices built-in.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
✨ **Interactive Setup** - Guided prompts for project configuration
|
|
10
|
+
📦 **Latest Packages** - Automatically fetches the latest npm package versions
|
|
11
|
+
🎨 **Optional Tools** - Choose Prettier and ESLint during setup
|
|
12
|
+
🪝 **Git Hooks** - Pre-configured Husky hooks with lint-staged
|
|
13
|
+
🏗️ **Clean Structure** - Role-separated files and commands
|
|
14
|
+
⚡ **Multiple Package Managers** - Support for pnpm, npm, yarn, and bun
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g bricklayer
|
|
20
|
+
# or
|
|
21
|
+
pnpm add -g bricklayer
|
|
22
|
+
# or
|
|
23
|
+
yarn global add bricklayer
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bricklayer create
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Follow the interactive prompts to configure your project:
|
|
33
|
+
|
|
34
|
+
- Project name
|
|
35
|
+
- Module system (ESM / CommonJS)
|
|
36
|
+
- Package manager
|
|
37
|
+
- Git repository details
|
|
38
|
+
- Optional tools (Prettier, ESLint)
|
|
39
|
+
|
|
40
|
+
## Generated Project Structure
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
your-cli/
|
|
44
|
+
├── src/
|
|
45
|
+
│ ├── commands/
|
|
46
|
+
│ │ └── hello.ts
|
|
47
|
+
│ └── index.ts
|
|
48
|
+
├── .husky/
|
|
49
|
+
│ ├── pre-commit
|
|
50
|
+
│ └── pre-push
|
|
51
|
+
├── .gitignore
|
|
52
|
+
├── .prettierrc
|
|
53
|
+
├── .prettierignore
|
|
54
|
+
├── .editorconfig
|
|
55
|
+
├── .npmignore
|
|
56
|
+
├── eslint.config.js
|
|
57
|
+
├── package.json
|
|
58
|
+
├── tsconfig.json
|
|
59
|
+
└── README.md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Clone the repository
|
|
66
|
+
git clone https://github.com/tukuyomil032/bricklayer.git
|
|
67
|
+
cd bricklayer
|
|
68
|
+
|
|
69
|
+
# Install dependencies
|
|
70
|
+
pnpm install
|
|
71
|
+
|
|
72
|
+
# Build
|
|
73
|
+
pnpm run build
|
|
74
|
+
|
|
75
|
+
# Test locally
|
|
76
|
+
node dist/index.js create
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Scripts
|
|
80
|
+
|
|
81
|
+
- `pnpm run build` - Build the TypeScript project
|
|
82
|
+
- `pnpm run dev` - Run in development mode
|
|
83
|
+
- `pnpm run lint` - Lint the code
|
|
84
|
+
- `pnpm run format` - Format the code with Prettier
|
|
85
|
+
|
|
86
|
+
## Requirements
|
|
87
|
+
|
|
88
|
+
- Node.js >= 18.0.0
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import cliProgress from 'cli-progress';
|
|
4
|
+
import * as templates from './templates.js';
|
|
5
|
+
export async function writeProjectFiles(targetDir, answers, versions) {
|
|
6
|
+
const progressBar = new cliProgress.SingleBar({
|
|
7
|
+
format: 'Creating files |{bar}| {percentage}% | {value}/{total} files',
|
|
8
|
+
barCompleteChar: '\u2588',
|
|
9
|
+
barIncompleteChar: '\u2591',
|
|
10
|
+
hideCursor: true,
|
|
11
|
+
});
|
|
12
|
+
const tasks = [
|
|
13
|
+
'package.json',
|
|
14
|
+
'tsconfig.json',
|
|
15
|
+
'src/index.ts',
|
|
16
|
+
'src/commands/hello.ts',
|
|
17
|
+
'README.md',
|
|
18
|
+
'.gitignore',
|
|
19
|
+
// husky hooks are optional and added conditionally below
|
|
20
|
+
'.prettierignore',
|
|
21
|
+
'.npmignore',
|
|
22
|
+
'.editorconfig',
|
|
23
|
+
'LICENSE',
|
|
24
|
+
];
|
|
25
|
+
// Always include ESLint and Prettier config files by default
|
|
26
|
+
tasks.push('.prettierrc');
|
|
27
|
+
tasks.push('eslint.config.js');
|
|
28
|
+
// Add .npmrc when using pnpm
|
|
29
|
+
const shouldAddNpmrc = answers.packageManager === 'pnpm';
|
|
30
|
+
if (shouldAddNpmrc)
|
|
31
|
+
tasks.push('.npmrc');
|
|
32
|
+
// Add husky hooks entries only if requested
|
|
33
|
+
if (answers.useHusky) {
|
|
34
|
+
tasks.unshift('.husky/pre-push');
|
|
35
|
+
tasks.unshift('.husky/pre-commit');
|
|
36
|
+
}
|
|
37
|
+
progressBar.start(tasks.length, 0);
|
|
38
|
+
let completed = 0;
|
|
39
|
+
// Create directory structure
|
|
40
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
41
|
+
await fs.mkdir(path.join(targetDir, 'src', 'commands'), { recursive: true });
|
|
42
|
+
if (answers.useHusky) {
|
|
43
|
+
await fs.mkdir(path.join(targetDir, '.husky'), { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
// Write package.json
|
|
46
|
+
const pkg = templates.generatePackageJson(answers, versions);
|
|
47
|
+
await fs.writeFile(path.join(targetDir, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
48
|
+
progressBar.update(++completed);
|
|
49
|
+
// Write tsconfig.json
|
|
50
|
+
const tsconfig = templates.generateTsConfig(answers);
|
|
51
|
+
await fs.writeFile(path.join(targetDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
52
|
+
progressBar.update(++completed);
|
|
53
|
+
// Write source files
|
|
54
|
+
await fs.writeFile(path.join(targetDir, 'src', 'index.ts'), templates.generateIndexTs(answers));
|
|
55
|
+
progressBar.update(++completed);
|
|
56
|
+
await fs.writeFile(path.join(targetDir, 'src', 'commands', 'hello.ts'), templates.generateHelloCommandTs());
|
|
57
|
+
progressBar.update(++completed);
|
|
58
|
+
// Write README
|
|
59
|
+
await fs.writeFile(path.join(targetDir, 'README.md'), templates.generateReadme(answers));
|
|
60
|
+
progressBar.update(++completed);
|
|
61
|
+
// Write .gitignore
|
|
62
|
+
await fs.writeFile(path.join(targetDir, '.gitignore'), templates.generateGitignore());
|
|
63
|
+
progressBar.update(++completed);
|
|
64
|
+
// Write Husky hooks (only if requested)
|
|
65
|
+
if (answers.useHusky) {
|
|
66
|
+
await fs.writeFile(path.join(targetDir, '.husky', 'pre-commit'), templates.generatePreCommitHook());
|
|
67
|
+
progressBar.update(++completed);
|
|
68
|
+
await fs.writeFile(path.join(targetDir, '.husky', 'pre-push'), templates.generatePrePushHook());
|
|
69
|
+
progressBar.update(++completed);
|
|
70
|
+
}
|
|
71
|
+
// Always add .prettierignore
|
|
72
|
+
await fs.writeFile(path.join(targetDir, '.prettierignore'), templates.generatePrettierIgnore());
|
|
73
|
+
progressBar.update(++completed);
|
|
74
|
+
// Always add .npmignore
|
|
75
|
+
await fs.writeFile(path.join(targetDir, '.npmignore'), templates.generateNpmIgnore());
|
|
76
|
+
progressBar.update(++completed);
|
|
77
|
+
// Conditionally add .npmrc for pnpm
|
|
78
|
+
if (shouldAddNpmrc) {
|
|
79
|
+
await fs.writeFile(path.join(targetDir, '.npmrc'), templates.generateNpmrc());
|
|
80
|
+
progressBar.update(++completed);
|
|
81
|
+
}
|
|
82
|
+
// Always add .editorconfig
|
|
83
|
+
await fs.writeFile(path.join(targetDir, '.editorconfig'), templates.generateEditorConfig());
|
|
84
|
+
progressBar.update(++completed);
|
|
85
|
+
// Write LICENSE
|
|
86
|
+
const licenseText = await templates.generateLicenseText(answers.license, answers.author, new Date().getFullYear());
|
|
87
|
+
await fs.writeFile(path.join(targetDir, 'LICENSE'), licenseText);
|
|
88
|
+
progressBar.update(++completed);
|
|
89
|
+
// Write Prettier and ESLint configs (default included)
|
|
90
|
+
await fs.writeFile(path.join(targetDir, '.prettierrc'), templates.generatePrettierConfig());
|
|
91
|
+
progressBar.update(++completed);
|
|
92
|
+
await fs.writeFile(path.join(targetDir, 'eslint.config.js'), templates.generateEslintConfig());
|
|
93
|
+
progressBar.update(++completed);
|
|
94
|
+
progressBar.stop();
|
|
95
|
+
console.log('');
|
|
96
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { promptProjectDetails } from './prompts.js';
|
|
7
|
+
import { writeProjectFiles } from './file-writer.js';
|
|
8
|
+
import { installDependencies } from './installer.js';
|
|
9
|
+
import { getLatestVersions } from './package-versions.js';
|
|
10
|
+
export function createCommand() {
|
|
11
|
+
const cmd = new Command('create');
|
|
12
|
+
cmd
|
|
13
|
+
.description('Create a new TypeScript CLI project (interactive)')
|
|
14
|
+
.option('-d, --destination [path]', 'Project destination directory');
|
|
15
|
+
cmd.action(async (options) => {
|
|
16
|
+
console.log(chalk.green('Welcome to bricklayer — TypeScript CLI scaffold generator'));
|
|
17
|
+
// Show spinner
|
|
18
|
+
const initSpinner = ora('Initializing project setup...').start();
|
|
19
|
+
// Small delay for better UX
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
21
|
+
initSpinner.stop();
|
|
22
|
+
// Determine destination behavior
|
|
23
|
+
const flagProvided = Boolean(options.destination);
|
|
24
|
+
const flagHasArg = typeof options.destination === 'string';
|
|
25
|
+
const askDestination = flagProvided && !flagHasArg;
|
|
26
|
+
// Prompt once: if -d present, always skip the `name` question; if -d without arg, also ask destination interactively first
|
|
27
|
+
const answers = await promptProjectDetails({ skipName: flagProvided, askDestination });
|
|
28
|
+
// Resolve target directory
|
|
29
|
+
let target;
|
|
30
|
+
if (flagHasArg) {
|
|
31
|
+
const dest = options.destination.replace(/^~/, os.homedir());
|
|
32
|
+
target = path.resolve(dest);
|
|
33
|
+
if (!answers.name)
|
|
34
|
+
answers.name = path.basename(target);
|
|
35
|
+
}
|
|
36
|
+
else if (answers.destination) {
|
|
37
|
+
const dest = answers.destination.replace(/^~/, os.homedir());
|
|
38
|
+
target = path.resolve(dest);
|
|
39
|
+
if (!answers.name)
|
|
40
|
+
answers.name = path.basename(target);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const baseDir = process.cwd();
|
|
44
|
+
target = path.resolve(baseDir, answers.name);
|
|
45
|
+
}
|
|
46
|
+
// Fetch latest package versions
|
|
47
|
+
const versionSpinner = ora('Fetching latest package versions...').start();
|
|
48
|
+
let versions;
|
|
49
|
+
try {
|
|
50
|
+
versions = await getLatestVersions();
|
|
51
|
+
versionSpinner.succeed('Fetched latest package versions');
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
versionSpinner.warn('Failed to fetch latest versions, using defaults');
|
|
55
|
+
console.debug(err);
|
|
56
|
+
}
|
|
57
|
+
const fileSpinner = ora('Creating project files...').start();
|
|
58
|
+
try {
|
|
59
|
+
await writeProjectFiles(target, answers, versions);
|
|
60
|
+
fileSpinner.succeed('Project scaffold created at ' + target);
|
|
61
|
+
// Install dependencies if user opted in
|
|
62
|
+
if (answers.autoInstall) {
|
|
63
|
+
await installDependencies(target, answers.packageManager);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(chalk.yellow('Dependencies were not installed automatically.'));
|
|
67
|
+
}
|
|
68
|
+
// Show next steps
|
|
69
|
+
console.log(chalk.blue('Next steps:'));
|
|
70
|
+
console.log(` - cd ${answers.name}`);
|
|
71
|
+
const buildCmd = answers.packageManager === 'pnpm'
|
|
72
|
+
? 'pnpm run build'
|
|
73
|
+
: answers.packageManager === 'yarn'
|
|
74
|
+
? 'yarn build'
|
|
75
|
+
: answers.packageManager === 'bun'
|
|
76
|
+
? 'bun run build'
|
|
77
|
+
: 'npm run build';
|
|
78
|
+
console.log(' - Build: ' + buildCmd);
|
|
79
|
+
if (!answers.autoInstall) {
|
|
80
|
+
const installCmd = answers.packageManager === 'yarn' ? 'yarn install' : `${answers.packageManager} install`;
|
|
81
|
+
console.log(' - Install dependencies: ' + installCmd);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
fileSpinner.fail('Failed to create project');
|
|
86
|
+
console.error(err);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return cmd;
|
|
91
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { exec as execCb, spawn } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import cliProgress from 'cli-progress';
|
|
5
|
+
const exec = promisify(execCb);
|
|
6
|
+
const INSTALL_COMMANDS = {
|
|
7
|
+
npm: 'npm install',
|
|
8
|
+
pnpm: 'pnpm install',
|
|
9
|
+
yarn: 'yarn install',
|
|
10
|
+
bun: 'bun install',
|
|
11
|
+
};
|
|
12
|
+
async function isCommandAvailable(cmd) {
|
|
13
|
+
try {
|
|
14
|
+
await exec(`command -v ${cmd}`);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function installDependencies(targetDir, packageManager) {
|
|
22
|
+
const mgr = packageManager || 'pnpm';
|
|
23
|
+
const installCmd = INSTALL_COMMANDS[mgr] || 'pnpm install';
|
|
24
|
+
const spinner = ora(`Running ${installCmd}...`);
|
|
25
|
+
const bin = mgr;
|
|
26
|
+
if (!(await isCommandAvailable(bin))) {
|
|
27
|
+
spinner.fail(`${bin} not found on PATH.`);
|
|
28
|
+
// Try sensible fallback order
|
|
29
|
+
const fallbacks = ['pnpm', 'npm'];
|
|
30
|
+
let usedFallback = null;
|
|
31
|
+
for (const f of fallbacks) {
|
|
32
|
+
if (await isCommandAvailable(f)) {
|
|
33
|
+
usedFallback = f;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (usedFallback) {
|
|
38
|
+
const fallbackCmd = `${usedFallback} install`;
|
|
39
|
+
const fallbackSpinner = ora(`Attempting fallback: ${fallbackCmd}`);
|
|
40
|
+
try {
|
|
41
|
+
// use spawn to stream and show progress
|
|
42
|
+
await runCommandWithProgress(fallbackCmd, targetDir);
|
|
43
|
+
fallbackSpinner.succeed('Dependencies installed (fallback)');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
fallbackSpinner.fail('Fallback install failed — please run manually');
|
|
48
|
+
console.error(e);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.error(`Package manager '${bin}' not found. Please install it or run 'npm install' in ${targetDir} manually.`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await runCommandWithProgress(installCmd, targetDir);
|
|
59
|
+
spinner.succeed('Dependencies installed');
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
spinner.fail('Dependency installation failed — please run manually');
|
|
63
|
+
console.error(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function runCommandWithProgress(command, cwd) {
|
|
67
|
+
const parts = command.split(' ').filter(Boolean);
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const bar = new cliProgress.SingleBar({
|
|
70
|
+
format: 'Installing dependencies |{bar}| {percentage}%',
|
|
71
|
+
hideCursor: true,
|
|
72
|
+
barsize: 40,
|
|
73
|
+
}, cliProgress.Presets.shades_classic);
|
|
74
|
+
bar.start(100, 0);
|
|
75
|
+
// Progress state
|
|
76
|
+
let progress = 0;
|
|
77
|
+
let lastRenderedFloor = 0;
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
// Interval drives internal progress target; we only redraw when integer percent changes
|
|
80
|
+
let lastOutputAt = 0;
|
|
81
|
+
const tickInterval = 150; // ms
|
|
82
|
+
const maxHold = 95; // hold at 95% until process completes
|
|
83
|
+
const timer = setInterval(() => {
|
|
84
|
+
const elapsed = Date.now() - start;
|
|
85
|
+
// Ease-out target that slowly approaches maxHold
|
|
86
|
+
const target = maxHold * (1 - Math.exp(-elapsed / 6000));
|
|
87
|
+
// Advance progress a bit toward target, ensuring monotonic increase
|
|
88
|
+
// If we've recently seen installer output, move faster
|
|
89
|
+
const sinceOutput = lastOutputAt ? (Date.now() - lastOutputAt) : Infinity;
|
|
90
|
+
const speedMultiplier = sinceOutput < 1000 ? 2.0 : 1.0;
|
|
91
|
+
progress = Math.min(target, progress + 0.6 * speedMultiplier);
|
|
92
|
+
const floor = Math.floor(progress);
|
|
93
|
+
if (floor > lastRenderedFloor) {
|
|
94
|
+
lastRenderedFloor = floor;
|
|
95
|
+
try {
|
|
96
|
+
bar.update(floor);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
}
|
|
100
|
+
}, tickInterval);
|
|
101
|
+
const child = spawn(parts[0], parts.slice(1), { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
102
|
+
const stdoutChunks = [];
|
|
103
|
+
const stderrChunks = [];
|
|
104
|
+
const onOutput = () => {
|
|
105
|
+
lastOutputAt = Date.now();
|
|
106
|
+
// Aggressively advance progress toward maxHold on real installer output
|
|
107
|
+
const remaining = maxHold - progress;
|
|
108
|
+
if (remaining <= 0)
|
|
109
|
+
return;
|
|
110
|
+
// Add a chunk that's a fraction of the remaining, clamped
|
|
111
|
+
const advance = Math.min(remaining, Math.max(4, Math.round(remaining * 0.18)));
|
|
112
|
+
progress = progress + advance;
|
|
113
|
+
const floor = Math.floor(progress);
|
|
114
|
+
if (floor > lastRenderedFloor) {
|
|
115
|
+
lastRenderedFloor = floor;
|
|
116
|
+
try {
|
|
117
|
+
bar.update(floor);
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
if (child.stdout)
|
|
123
|
+
child.stdout.on('data', (c) => { stdoutChunks.push(Buffer.from(c)); onOutput(); });
|
|
124
|
+
if (child.stderr)
|
|
125
|
+
child.stderr.on('data', (c) => { stderrChunks.push(Buffer.from(c)); onOutput(); });
|
|
126
|
+
child.on('error', (err) => {
|
|
127
|
+
clearInterval(timer);
|
|
128
|
+
try {
|
|
129
|
+
bar.stop();
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
reject(err);
|
|
133
|
+
});
|
|
134
|
+
child.on('close', (code) => {
|
|
135
|
+
clearInterval(timer);
|
|
136
|
+
// Smoothly ramp to 100% to avoid a sudden jump
|
|
137
|
+
const finishInterval = 40; // ms
|
|
138
|
+
const finishTimer = setInterval(() => {
|
|
139
|
+
const remaining = 100 - progress;
|
|
140
|
+
if (remaining <= 0.5) {
|
|
141
|
+
try {
|
|
142
|
+
bar.update(100);
|
|
143
|
+
bar.stop();
|
|
144
|
+
}
|
|
145
|
+
catch { }
|
|
146
|
+
clearInterval(finishTimer);
|
|
147
|
+
if (code === 0)
|
|
148
|
+
return resolve();
|
|
149
|
+
const out = Buffer.concat(stdoutChunks).toString('utf8');
|
|
150
|
+
const errOut = Buffer.concat(stderrChunks).toString('utf8');
|
|
151
|
+
const e = new Error(`Command exited with code ${code}\n${errOut || out}`);
|
|
152
|
+
return reject(e);
|
|
153
|
+
}
|
|
154
|
+
// advance by a fraction of remaining to create ease-out
|
|
155
|
+
progress = progress + Math.max(1, Math.round(remaining * 0.18));
|
|
156
|
+
const floor = Math.floor(progress);
|
|
157
|
+
if (floor > lastRenderedFloor) {
|
|
158
|
+
lastRenderedFloor = floor;
|
|
159
|
+
try {
|
|
160
|
+
bar.update(floor);
|
|
161
|
+
}
|
|
162
|
+
catch { }
|
|
163
|
+
}
|
|
164
|
+
}, finishInterval);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|