@ribershamoelias/forge 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/LICENSE +203 -0
- package/README.md +374 -0
- package/bin/forge.js +2 -0
- package/dist/cli.js +100 -0
- package/dist/core/clean.js +9 -0
- package/dist/core/doctor.js +42 -0
- package/dist/core/list.js +22 -0
- package/dist/core/profile.js +136 -0
- package/dist/core/setup.js +67 -0
- package/dist/lib/config.js +22 -0
- package/dist/lib/detect.js +34 -0
- package/dist/lib/editor.js +102 -0
- package/dist/lib/exec.js +21 -0
- package/dist/lib/logger.js +77 -0
- package/dist/lib/terminal.js +65 -0
- package/dist/lib/timer.js +15 -0
- package/dist/lib/tools.js +67 -0
- package/dist/lib/version.js +50 -0
- package/forge.json +13 -0
- package/package.json +30 -0
- package/presets/backend.json +14 -0
- package/presets/minimal.json +11 -0
- package/presets/web-dev.json +14 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDoctor = runDoctor;
|
|
4
|
+
const logger_js_1 = require("../lib/logger.js");
|
|
5
|
+
const version_js_1 = require("../lib/version.js");
|
|
6
|
+
function runDoctor() {
|
|
7
|
+
logger_js_1.Logger.info('System Status:');
|
|
8
|
+
let missing = [];
|
|
9
|
+
let outdated = [];
|
|
10
|
+
let ok = [];
|
|
11
|
+
for (const tool of version_js_1.TOOL_VERSION_CHECKS) {
|
|
12
|
+
const { version, error } = (0, version_js_1.getToolVersion)(tool);
|
|
13
|
+
if (error) {
|
|
14
|
+
logger_js_1.Logger.error(`${tool.name} (missing)`, [
|
|
15
|
+
`Try manually: install ${tool.name}`
|
|
16
|
+
]);
|
|
17
|
+
missing.push(tool.name);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const cmp = (0, version_js_1.compareVersions)(version, tool.recommended);
|
|
21
|
+
if (cmp < 0) {
|
|
22
|
+
logger_js_1.Logger.warn(`${tool.name} (${version}, outdated)`);
|
|
23
|
+
outdated.push(tool.name);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
logger_js_1.Logger.success(`${tool.name} (${version})`);
|
|
27
|
+
ok.push(tool.name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
logger_js_1.Logger.info('');
|
|
31
|
+
if (missing.length || outdated.length) {
|
|
32
|
+
logger_js_1.Logger.warn('Some issues detected.');
|
|
33
|
+
if (missing.length)
|
|
34
|
+
logger_js_1.Logger.error(`Missing: ${missing.join(', ')}`);
|
|
35
|
+
if (outdated.length)
|
|
36
|
+
logger_js_1.Logger.warn(`Outdated: ${outdated.join(', ')}`);
|
|
37
|
+
logger_js_1.Logger.info('To fix, run: forge setup --preset web-dev');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
logger_js_1.Logger.success('All essential tools are installed and up to date!');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runList = runList;
|
|
4
|
+
const logger_js_1 = require("../lib/logger.js");
|
|
5
|
+
const version_js_1 = require("../lib/version.js");
|
|
6
|
+
function runList() {
|
|
7
|
+
logger_js_1.Logger.info('Listing installed tools:');
|
|
8
|
+
for (const tool of version_js_1.TOOL_VERSION_CHECKS) {
|
|
9
|
+
const { version, error } = (0, version_js_1.getToolVersion)(tool);
|
|
10
|
+
if (error) {
|
|
11
|
+
logger_js_1.Logger.error(`${tool.name}: Not installed`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const cmp = (0, version_js_1.compareVersions)(version, tool.recommended);
|
|
15
|
+
if (cmp < 0) {
|
|
16
|
+
logger_js_1.Logger.warn(`${tool.name}: ${version} (outdated, recommended: ${tool.recommended})`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
logger_js_1.Logger.success(`${tool.name}: ${version} (OK)`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveProfile = saveProfile;
|
|
7
|
+
exports.listProfiles = listProfiles;
|
|
8
|
+
exports.applyProfile = applyProfile;
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const logger_js_1 = require("../lib/logger.js");
|
|
13
|
+
const version_js_1 = require("../lib/version.js");
|
|
14
|
+
const tools_js_1 = require("../lib/tools.js");
|
|
15
|
+
const terminal_js_1 = require("../lib/terminal.js");
|
|
16
|
+
const PROFILE_DIR = path_1.default.join(os_1.default.homedir(), '.forge', 'profiles');
|
|
17
|
+
function ensureProfileDir() {
|
|
18
|
+
if (!fs_extra_1.default.existsSync(PROFILE_DIR))
|
|
19
|
+
fs_extra_1.default.mkdirpSync(PROFILE_DIR);
|
|
20
|
+
}
|
|
21
|
+
async function saveProfile(name) {
|
|
22
|
+
ensureProfileDir();
|
|
23
|
+
logger_js_1.Logger.info(`Saving profile: ${name}`);
|
|
24
|
+
// Detect installed tools and versions
|
|
25
|
+
const tools = version_js_1.TOOL_VERSION_CHECKS.map(tool => {
|
|
26
|
+
const { version } = (0, version_js_1.getToolVersion)(tool);
|
|
27
|
+
return { name: tool.name, version: version || null };
|
|
28
|
+
});
|
|
29
|
+
// Detect shell and aliases
|
|
30
|
+
const shell = process.env.SHELL?.split('/').pop() || 'zsh';
|
|
31
|
+
const rcFile = shell === 'zsh' ? '.zshrc' : '.bashrc';
|
|
32
|
+
const rcPath = path_1.default.join(os_1.default.homedir(), rcFile);
|
|
33
|
+
let aliases = {};
|
|
34
|
+
if (fs_extra_1.default.existsSync(rcPath)) {
|
|
35
|
+
const content = fs_extra_1.default.readFileSync(rcPath, 'utf8');
|
|
36
|
+
const aliasLines = content.split('\n').filter(l => l.trim().startsWith('alias '));
|
|
37
|
+
for (const line of aliasLines) {
|
|
38
|
+
const match = line.match(/^alias (\w+)='(.+)'$/);
|
|
39
|
+
if (match)
|
|
40
|
+
aliases[match[1]] = match[2];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const profile = {
|
|
44
|
+
tools,
|
|
45
|
+
terminal: {
|
|
46
|
+
shell,
|
|
47
|
+
aliases
|
|
48
|
+
},
|
|
49
|
+
created: new Date().toISOString()
|
|
50
|
+
};
|
|
51
|
+
const filePath = path_1.default.join(PROFILE_DIR, `${name}.json`);
|
|
52
|
+
fs_extra_1.default.writeJsonSync(filePath, profile, { spaces: 2 });
|
|
53
|
+
logger_js_1.Logger.success(`Profile saved: ${filePath}`);
|
|
54
|
+
}
|
|
55
|
+
function listProfiles() {
|
|
56
|
+
ensureProfileDir();
|
|
57
|
+
const files = fs_extra_1.default.readdirSync(PROFILE_DIR).filter(f => f.endsWith('.json'));
|
|
58
|
+
if (!files.length) {
|
|
59
|
+
logger_js_1.Logger.info('No profiles found');
|
|
60
|
+
logger_js_1.Logger.info('→ Run `forge profile save <name>` to create one');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
logger_js_1.Logger.info('Available profiles:');
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const full = path_1.default.join(PROFILE_DIR, file);
|
|
66
|
+
const data = fs_extra_1.default.readJsonSync(full);
|
|
67
|
+
logger_js_1.Logger.info(`- ${file.replace('.json', '')} (created: ${data.created})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function applyProfile(name) {
|
|
71
|
+
ensureProfileDir();
|
|
72
|
+
const filePath = path_1.default.join(PROFILE_DIR, `${name}.json`);
|
|
73
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
|
74
|
+
logger_js_1.Logger.error(`Profile not found: ${name}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
logger_js_1.Logger.info(`Applying profile: ${name}`);
|
|
78
|
+
const profile = fs_extra_1.default.readJsonSync(filePath);
|
|
79
|
+
// Step 1: Install missing tools
|
|
80
|
+
logger_js_1.Logger.step('Installing missing tools...', 1, 3);
|
|
81
|
+
let allToolsOk = true;
|
|
82
|
+
for (const tool of profile.tools) {
|
|
83
|
+
// Find tool check definition
|
|
84
|
+
const check = version_js_1.TOOL_VERSION_CHECKS.find(t => t.name === tool.name);
|
|
85
|
+
if (!check) {
|
|
86
|
+
logger_js_1.Logger.warn(`Unknown tool in profile: ${tool.name}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const { version: currentVersion, error } = (0, version_js_1.getToolVersion)(check);
|
|
90
|
+
if (error) {
|
|
91
|
+
// Not installed, install
|
|
92
|
+
const ok = await (0, tools_js_1.installTool)(tool.name, { os: os_1.default.platform(), pkg: detectPkgMgr(), dryRun: false });
|
|
93
|
+
if (!ok) {
|
|
94
|
+
logger_js_1.Logger.error(`Failed to install ${tool.name}`, [
|
|
95
|
+
`Try manually: brew install ${tool.name}`
|
|
96
|
+
]);
|
|
97
|
+
allToolsOk = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Already installed, check version
|
|
102
|
+
if (tool.version && currentVersion && (0, version_js_1.compareVersions)(currentVersion, tool.version) !== 0) {
|
|
103
|
+
logger_js_1.Logger.warn(`${tool.name} version differs (profile: ${tool.version}, current: ${currentVersion})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Step 2: Apply terminal config
|
|
108
|
+
logger_js_1.Logger.step('Configuring terminal...', 2, 3);
|
|
109
|
+
try {
|
|
110
|
+
await (0, terminal_js_1.setupTerminal)(profile.terminal, false);
|
|
111
|
+
logger_js_1.Logger.success('Aliases and terminal config applied');
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
logger_js_1.Logger.error('Failed to apply terminal config', [e.message]);
|
|
115
|
+
allToolsOk = false;
|
|
116
|
+
}
|
|
117
|
+
// Step 3: Finalizing
|
|
118
|
+
logger_js_1.Logger.step('Finalizing...', 3, 3);
|
|
119
|
+
if (allToolsOk) {
|
|
120
|
+
logger_js_1.Logger.success('Profile applied successfully');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
logger_js_1.Logger.warn('Profile applied with some issues. See above.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Helper to detect package manager (reuse from setup if possible)
|
|
127
|
+
function detectPkgMgr() {
|
|
128
|
+
// Simple detection for demo; in real code, reuse detectPackageManager from setup
|
|
129
|
+
if (fs_extra_1.default.existsSync('/opt/homebrew/bin/brew') || fs_extra_1.default.existsSync('/usr/local/bin/brew'))
|
|
130
|
+
return 'brew';
|
|
131
|
+
if (fs_extra_1.default.existsSync('/usr/bin/apt'))
|
|
132
|
+
return 'apt';
|
|
133
|
+
if (fs_extra_1.default.existsSync('/usr/bin/pacman'))
|
|
134
|
+
return 'pacman';
|
|
135
|
+
return 'brew';
|
|
136
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSetup = runSetup;
|
|
4
|
+
const detect_js_1 = require("../lib/detect.js");
|
|
5
|
+
const config_js_1 = require("../lib/config.js");
|
|
6
|
+
const logger_js_1 = require("../lib/logger.js");
|
|
7
|
+
const tools_js_1 = require("../lib/tools.js");
|
|
8
|
+
const editor_js_1 = require("../lib/editor.js");
|
|
9
|
+
const terminal_js_1 = require("../lib/terminal.js");
|
|
10
|
+
async function runSetup(opts) {
|
|
11
|
+
logger_js_1.Logger.info('Forge setup (Unix-only)');
|
|
12
|
+
const totalTimer = new logger_js_1.Logger.Timer();
|
|
13
|
+
let config = (0, config_js_1.loadConfig)();
|
|
14
|
+
if (opts.preset) {
|
|
15
|
+
config = (0, config_js_1.mergePreset)(config, opts.preset);
|
|
16
|
+
logger_js_1.Logger.info(`Using preset: ${opts.preset}`);
|
|
17
|
+
}
|
|
18
|
+
const os = (0, detect_js_1.detectOS)();
|
|
19
|
+
const pkg = (0, detect_js_1.detectPackageManager)();
|
|
20
|
+
const ctx = { os, pkg, dryRun: !!opts.dryRun };
|
|
21
|
+
const steps = [
|
|
22
|
+
...config.tools.map((tool) => ({ type: 'tool', name: tool })),
|
|
23
|
+
{ type: 'editor', name: 'Configuring editor' },
|
|
24
|
+
{ type: 'terminal', name: 'Configuring terminal' },
|
|
25
|
+
];
|
|
26
|
+
const totalSteps = steps.length;
|
|
27
|
+
const installed = [];
|
|
28
|
+
const warnings = [];
|
|
29
|
+
for (let i = 0; i < steps.length; i++) {
|
|
30
|
+
const step = steps[i];
|
|
31
|
+
const stepTimer = new logger_js_1.Logger.Timer();
|
|
32
|
+
if (step.type === 'tool') {
|
|
33
|
+
logger_js_1.Logger.step(`Installing ${step.name}`, i + 1, totalSteps);
|
|
34
|
+
const ok = await (0, tools_js_1.installTool)(step.name, ctx);
|
|
35
|
+
if (!ok) {
|
|
36
|
+
logger_js_1.Logger.error(`Failed to install ${step.name}`, [
|
|
37
|
+
'Check your internet connection',
|
|
38
|
+
`Try manually: ${pkg} install ${step.name}`
|
|
39
|
+
]);
|
|
40
|
+
warnings.push(step.name);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
installed.push(step.name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (step.type === 'editor') {
|
|
47
|
+
logger_js_1.Logger.step('Configuring editor', i + 1, totalSteps);
|
|
48
|
+
await (0, editor_js_1.setupEditor)(config.editor, ctx.dryRun);
|
|
49
|
+
logger_js_1.Logger.success('Editor configured', stepTimer.elapsed());
|
|
50
|
+
}
|
|
51
|
+
else if (step.type === 'terminal') {
|
|
52
|
+
logger_js_1.Logger.step('Configuring terminal', i + 1, totalSteps);
|
|
53
|
+
await (0, terminal_js_1.setupTerminal)(config.terminal, ctx.dryRun);
|
|
54
|
+
logger_js_1.Logger.success('Terminal configured', stepTimer.elapsed());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Final summary
|
|
58
|
+
logger_js_1.Logger.success(`Setup complete in ${totalTimer.elapsed()}`);
|
|
59
|
+
if (installed.length)
|
|
60
|
+
logger_js_1.Logger.info(`Installed: ${installed.join(', ')}`);
|
|
61
|
+
if (warnings.length)
|
|
62
|
+
logger_js_1.Logger.warn(`Warnings: ${warnings.length}`);
|
|
63
|
+
// Smart suggestions
|
|
64
|
+
if (!installed.length) {
|
|
65
|
+
logger_js_1.Logger.info('Tip: Try `forge setup --preset web-dev`');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
exports.mergePreset = mergePreset;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function loadConfig() {
|
|
11
|
+
const configPath = path_1.default.resolve(process.cwd(), 'forge.json');
|
|
12
|
+
if (!fs_extra_1.default.existsSync(configPath))
|
|
13
|
+
throw new Error('forge.json not found');
|
|
14
|
+
return fs_extra_1.default.readJsonSync(configPath);
|
|
15
|
+
}
|
|
16
|
+
function mergePreset(config, presetName) {
|
|
17
|
+
const presetPath = path_1.default.resolve(process.cwd(), 'presets', `${presetName}.json`);
|
|
18
|
+
if (!fs_extra_1.default.existsSync(presetPath))
|
|
19
|
+
throw new Error(`Preset ${presetName} not found`);
|
|
20
|
+
const preset = fs_extra_1.default.readJsonSync(presetPath);
|
|
21
|
+
return { ...config, ...preset };
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectOS = detectOS;
|
|
4
|
+
exports.detectPackageManager = detectPackageManager;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
function detectOS() {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
if (platform === 'darwin')
|
|
9
|
+
return 'macos';
|
|
10
|
+
if (platform === 'linux')
|
|
11
|
+
return 'linux';
|
|
12
|
+
console.error('Forge only supports macOS and Linux.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
function detectPackageManager() {
|
|
16
|
+
// Only support brew (macOS), apt/pacman (Linux)
|
|
17
|
+
try {
|
|
18
|
+
(0, child_process_1.execSync)('which brew', { stdio: 'ignore' });
|
|
19
|
+
return 'brew';
|
|
20
|
+
}
|
|
21
|
+
catch { }
|
|
22
|
+
try {
|
|
23
|
+
(0, child_process_1.execSync)('which apt', { stdio: 'ignore' });
|
|
24
|
+
return 'apt';
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
try {
|
|
28
|
+
(0, child_process_1.execSync)('which pacman', { stdio: 'ignore' });
|
|
29
|
+
return 'pacman';
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
console.error('No supported package manager found (brew, apt, pacman).');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupEditor = setupEditor;
|
|
4
|
+
const logger_js_1 = require("./logger.js");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
async function setupEditor(editorConfig, dryRun) {
|
|
7
|
+
if (editorConfig.name !== 'vscode') {
|
|
8
|
+
logger_js_1.Logger.warn('Only VS Code is supported for now.');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// Check if VS Code is already installed
|
|
12
|
+
let vsCodeInstalled = false;
|
|
13
|
+
if (process.platform === 'darwin') {
|
|
14
|
+
// Check for app bundle
|
|
15
|
+
try {
|
|
16
|
+
(0, child_process_1.execSync)('test -d "/Applications/Visual Studio Code.app"');
|
|
17
|
+
vsCodeInstalled = true;
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
}
|
|
21
|
+
else if (process.platform === 'linux') {
|
|
22
|
+
try {
|
|
23
|
+
(0, child_process_1.execSync)('command -v code');
|
|
24
|
+
vsCodeInstalled = true;
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
if (vsCodeInstalled) {
|
|
29
|
+
logger_js_1.Logger.success('VS Code already installed');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
let cmd = '';
|
|
33
|
+
if (process.platform === 'darwin') {
|
|
34
|
+
cmd = 'brew install --cask visual-studio-code';
|
|
35
|
+
}
|
|
36
|
+
else if (process.platform === 'linux') {
|
|
37
|
+
cmd = 'sudo snap install --classic code || sudo apt-get install -y code';
|
|
38
|
+
}
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
logger_js_1.Logger.info(`[dry-run] Would run: ${cmd}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
try {
|
|
44
|
+
// Suppress brew warnings by capturing output
|
|
45
|
+
(0, child_process_1.execSync)(cmd, { stdio: 'pipe' });
|
|
46
|
+
logger_js_1.Logger.success('VS Code installed');
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
// If already installed, treat as success
|
|
50
|
+
const msg = e?.stdout?.toString() || e?.message || '';
|
|
51
|
+
if (/already installed|already exists|is already installed/i.test(msg)) {
|
|
52
|
+
logger_js_1.Logger.success('VS Code already installed');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
logger_js_1.Logger.error('Failed to install VS Code', [
|
|
56
|
+
'Check your internet connection',
|
|
57
|
+
'Try manually: brew install --cask visual-studio-code'
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Install extensions
|
|
64
|
+
for (const ext of editorConfig.extensions || []) {
|
|
65
|
+
let extInstalled = false;
|
|
66
|
+
if (!dryRun) {
|
|
67
|
+
try {
|
|
68
|
+
// Check if extension is already installed
|
|
69
|
+
const list = (0, child_process_1.execSync)('code --list-extensions', { encoding: 'utf8' });
|
|
70
|
+
if (list.split('\n').includes(ext)) {
|
|
71
|
+
extInstalled = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
}
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
logger_js_1.Logger.info(`[dry-run] Would run: code --install-extension ${ext}`);
|
|
78
|
+
}
|
|
79
|
+
else if (extInstalled) {
|
|
80
|
+
logger_js_1.Logger.success(`Extension ${ext} already installed`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
try {
|
|
84
|
+
// Suppress warnings by capturing output
|
|
85
|
+
(0, child_process_1.execSync)(`code --install-extension ${ext}`, { stdio: 'pipe' });
|
|
86
|
+
logger_js_1.Logger.success(`Extension ${ext} installed`);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
const msg = e?.stdout?.toString() || e?.message || '';
|
|
90
|
+
if (/already installed|already exists|is already installed/i.test(msg)) {
|
|
91
|
+
logger_js_1.Logger.success(`Extension ${ext} already installed`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
logger_js_1.Logger.error(`Failed to install extension ${ext}`, [
|
|
95
|
+
'Check your internet connection',
|
|
96
|
+
`Try manually: code --install-extension ${ext}`
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/lib/exec.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCommand = runCommand;
|
|
4
|
+
const logger_js_1 = require("./logger.js");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
function runCommand(command, options = {}) {
|
|
7
|
+
logger_js_1.Logger.info(`${options.dryRun ? '[dry-run] ' : ''}Running: ${command}`);
|
|
8
|
+
if (options.dryRun)
|
|
9
|
+
return true;
|
|
10
|
+
try {
|
|
11
|
+
(0, child_process_1.execSync)(command, { stdio: options.stdio || 'inherit', cwd: options.cwd });
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
logger_js_1.Logger.error(`Command failed: ${command}`, [
|
|
16
|
+
'Check your internet connection',
|
|
17
|
+
'Try running the command manually for more details.'
|
|
18
|
+
]);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Logger = void 0;
|
|
7
|
+
exports.setLogLevel = setLogLevel;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const timer_js_1 = require("./timer.js");
|
|
10
|
+
let logLevel = 'normal';
|
|
11
|
+
function setLogLevel(level) {
|
|
12
|
+
logLevel = level;
|
|
13
|
+
}
|
|
14
|
+
function shouldLog(type) {
|
|
15
|
+
if (logLevel === 'silent')
|
|
16
|
+
return type === 'error';
|
|
17
|
+
if (logLevel === 'normal')
|
|
18
|
+
return type !== 'info' || type === 'info';
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
function icon(type) {
|
|
22
|
+
switch (type) {
|
|
23
|
+
case 'success': return chalk_1.default.green('✔');
|
|
24
|
+
case 'error': return chalk_1.default.red('✖');
|
|
25
|
+
case 'warn': return chalk_1.default.yellow('⚠');
|
|
26
|
+
case 'info': return chalk_1.default.blue('ℹ');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class Logger {
|
|
30
|
+
static info(msg) {
|
|
31
|
+
if (!shouldLog('info'))
|
|
32
|
+
return;
|
|
33
|
+
console.log(`${icon('info')} ${msg}`);
|
|
34
|
+
}
|
|
35
|
+
static step(msg, current, total, timing) {
|
|
36
|
+
if (!shouldLog('step'))
|
|
37
|
+
return;
|
|
38
|
+
if (typeof current === 'number' && typeof total === 'number') {
|
|
39
|
+
// Align step numbers for up to 99 steps
|
|
40
|
+
const pad = (n) => n.toString().padStart(2, ' ');
|
|
41
|
+
const time = timing ? chalk_1.default.gray(`(${timing})`) : '';
|
|
42
|
+
console.log(chalk_1.default.cyan(`[${pad(current)}/${pad(total)}]`), msg, time);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(chalk_1.default.cyan('→'), msg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
static success(msg, timing) {
|
|
49
|
+
if (!shouldLog('success'))
|
|
50
|
+
return;
|
|
51
|
+
const time = timing ? chalk_1.default.gray(`(${timing})`) : '';
|
|
52
|
+
console.log(`${icon('success')} ${msg} ${time}`.trim());
|
|
53
|
+
}
|
|
54
|
+
static warn(msg) {
|
|
55
|
+
if (!shouldLog('warn'))
|
|
56
|
+
return;
|
|
57
|
+
console.log(`${icon('warn')} ${msg}`);
|
|
58
|
+
}
|
|
59
|
+
static error(msg, suggestions) {
|
|
60
|
+
if (!shouldLog('error'))
|
|
61
|
+
return;
|
|
62
|
+
// Only log the main error message, never stack traces or raw error objects
|
|
63
|
+
console.error(`${icon('error')} ${msg}`);
|
|
64
|
+
if (suggestions && suggestions.length) {
|
|
65
|
+
for (const s of suggestions) {
|
|
66
|
+
console.error(chalk_1.default.gray('→'), s);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
static verbose(msg) {
|
|
71
|
+
if (logLevel === 'verbose') {
|
|
72
|
+
console.log(chalk_1.default.gray(msg));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.Logger = Logger;
|
|
77
|
+
Logger.Timer = timer_js_1.Timer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupTerminal = setupTerminal;
|
|
7
|
+
const logger_js_1 = require("./logger.js");
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function detectUserShell() {
|
|
12
|
+
const shellEnv = process.env.SHELL || '';
|
|
13
|
+
if (shellEnv.includes('zsh'))
|
|
14
|
+
return 'zsh';
|
|
15
|
+
if (shellEnv.includes('bash'))
|
|
16
|
+
return 'bash';
|
|
17
|
+
// Fallback: check for .zshrc, then .bashrc
|
|
18
|
+
const home = os_1.default.homedir();
|
|
19
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(home, '.zshrc')))
|
|
20
|
+
return 'zsh';
|
|
21
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(home, '.bashrc')))
|
|
22
|
+
return 'bash';
|
|
23
|
+
return 'zsh'; // Default to zsh
|
|
24
|
+
}
|
|
25
|
+
function ensureLineInFile(filePath, line) {
|
|
26
|
+
const content = fs_extra_1.default.existsSync(filePath) ? fs_extra_1.default.readFileSync(filePath, 'utf8') : '';
|
|
27
|
+
if (!content.includes(line)) {
|
|
28
|
+
fs_extra_1.default.appendFileSync(filePath, line + '\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function setupTerminal(terminalConfig, dryRun) {
|
|
32
|
+
const shell = terminalConfig.shell || detectUserShell();
|
|
33
|
+
const home = os_1.default.homedir();
|
|
34
|
+
const rcFile = shell === 'zsh' ? '.zshrc' : '.bashrc';
|
|
35
|
+
const rcPath = path_1.default.join(home, rcFile);
|
|
36
|
+
// Backup
|
|
37
|
+
if (!dryRun && fs_extra_1.default.existsSync(rcPath)) {
|
|
38
|
+
fs_extra_1.default.copySync(rcPath, rcPath + '.forge.bak');
|
|
39
|
+
logger_js_1.Logger.info(`Backed up ${rcFile} to ${rcFile}.forge.bak`);
|
|
40
|
+
}
|
|
41
|
+
// Add aliases (idempotent)
|
|
42
|
+
const aliases = terminalConfig.aliases || {};
|
|
43
|
+
for (const [k, v] of Object.entries(aliases)) {
|
|
44
|
+
const aliasLine = `alias ${k}='${v}'`;
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
logger_js_1.Logger.info(`[dry-run] Would add alias to ${rcFile}: ${aliasLine}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
ensureLineInFile(rcPath, aliasLine);
|
|
50
|
+
logger_js_1.Logger.success(`Alias added: ${aliasLine}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Add PATH handling if specified
|
|
54
|
+
if (terminalConfig.PATH && typeof terminalConfig.PATH === 'string') {
|
|
55
|
+
const exportLine = `export PATH="${terminalConfig.PATH}:$PATH"`;
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
logger_js_1.Logger.info(`[dry-run] Would add to PATH in ${rcFile}: ${exportLine}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
ensureLineInFile(rcPath, exportLine);
|
|
61
|
+
logger_js_1.Logger.success(`PATH updated in ${rcFile}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
logger_js_1.Logger.info(`Terminal configuration complete for ${rcFile}`);
|
|
65
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Timer = void 0;
|
|
4
|
+
class Timer {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.startTime = Date.now();
|
|
7
|
+
}
|
|
8
|
+
elapsed() {
|
|
9
|
+
const ms = Date.now() - this.startTime;
|
|
10
|
+
if (ms < 1000)
|
|
11
|
+
return `${ms}ms`;
|
|
12
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.Timer = Timer;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installTool = installTool;
|
|
4
|
+
const logger_js_1 = require("./logger.js");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const toolInstallers = {
|
|
7
|
+
git: (ctx) => installViaPkg('git', ctx),
|
|
8
|
+
node: (ctx) => installViaPkg('node', ctx),
|
|
9
|
+
python: (ctx) => installViaPkg('python3', ctx),
|
|
10
|
+
docker: (ctx) => installViaPkg('docker', ctx),
|
|
11
|
+
zsh: (ctx) => installViaPkg('zsh', ctx),
|
|
12
|
+
};
|
|
13
|
+
function installViaPkg(tool, ctx) {
|
|
14
|
+
// Check if tool is already installed
|
|
15
|
+
let checkCmd = '';
|
|
16
|
+
if (tool === 'python3' || tool === 'python')
|
|
17
|
+
checkCmd = 'python3 --version';
|
|
18
|
+
else
|
|
19
|
+
checkCmd = `${tool} --version`;
|
|
20
|
+
let alreadyInstalled = false;
|
|
21
|
+
try {
|
|
22
|
+
(0, child_process_1.execSync)(checkCmd, { stdio: 'ignore' });
|
|
23
|
+
alreadyInstalled = true;
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
if (alreadyInstalled) {
|
|
27
|
+
logger_js_1.Logger.success(`${tool.replace('python3', 'python')} already installed`);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
let cmd = '';
|
|
31
|
+
if (ctx.pkg === 'brew')
|
|
32
|
+
cmd = `brew install ${tool}`;
|
|
33
|
+
else if (ctx.pkg === 'apt')
|
|
34
|
+
cmd = `sudo apt-get update && sudo apt-get install -y ${tool}`;
|
|
35
|
+
else if (ctx.pkg === 'pacman')
|
|
36
|
+
cmd = `sudo pacman -Sy --noconfirm ${tool}`;
|
|
37
|
+
else
|
|
38
|
+
throw new Error('Unsupported package manager');
|
|
39
|
+
logger_js_1.Logger.info(`Installing ${tool} using ${ctx.pkg}...`);
|
|
40
|
+
try {
|
|
41
|
+
// Suppress brew/apt/pacman warnings by capturing output
|
|
42
|
+
(0, child_process_1.execSync)(cmd, { stdio: 'pipe' });
|
|
43
|
+
logger_js_1.Logger.success(`${tool.replace('python3', 'python')} installed`);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
const msg = e?.stdout?.toString() || e?.message || '';
|
|
48
|
+
if (/already installed|already exists|is already installed/i.test(msg)) {
|
|
49
|
+
logger_js_1.Logger.success(`${tool.replace('python3', 'python')} already installed`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
logger_js_1.Logger.error(`Failed to install ${tool}`, [
|
|
53
|
+
'Check your internet connection',
|
|
54
|
+
`Try manually: ${cmd}`
|
|
55
|
+
]);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function installTool(tool, ctx) {
|
|
60
|
+
if (toolInstallers[tool]) {
|
|
61
|
+
return toolInstallers[tool](ctx);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
logger_js_1.Logger.warn(`No installer for tool: ${tool}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|