@mandors/cli 0.0.1
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/.npmrc.example +18 -0
- package/.pre-commit-config.yaml +22 -0
- package/README.md +317 -0
- package/logo.png +0 -0
- package/npm/bin/mandor +32 -0
- package/npm/lib/api.js +216 -0
- package/npm/lib/config.js +153 -0
- package/npm/lib/download.js +83 -0
- package/npm/lib/index.js +138 -0
- package/npm/lib/install.js +92 -0
- package/npm/lib/resolve.js +148 -0
- package/npm/scripts/build.js +183 -0
- package/package.json +58 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mandor configuration management
|
|
3
|
+
* @description Reads and writes Mandor configuration using .mandorrc.json
|
|
4
|
+
* @version 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
/** @type {string} Default config filename */
|
|
11
|
+
const CONFIG_FILENAME = '.mandorrc.json';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration management class for Mandor projects
|
|
15
|
+
* @class
|
|
16
|
+
* @version 0.0.1
|
|
17
|
+
* @example
|
|
18
|
+
* const config = new MandorConfig('/path/to/project');
|
|
19
|
+
* const defaultPriority = config.get('priority.default', 'P3');
|
|
20
|
+
* config.set('theme', 'dark');
|
|
21
|
+
*/
|
|
22
|
+
class MandorConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new MandorConfig instance
|
|
25
|
+
* @constructor
|
|
26
|
+
* @param {string} projectRoot - Root directory of the project
|
|
27
|
+
* @example
|
|
28
|
+
* const config = new MandorConfig('/my/project');
|
|
29
|
+
*/
|
|
30
|
+
constructor(projectRoot) {
|
|
31
|
+
/** @type {string} Path to config file */
|
|
32
|
+
this.configPath = path.join(projectRoot, CONFIG_FILENAME);
|
|
33
|
+
/** @type {string} Project root directory */
|
|
34
|
+
this.projectRoot = projectRoot;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gets a configuration value
|
|
39
|
+
* @param {string} key - Configuration key (supports dot notation, e.g., 'priority.default')
|
|
40
|
+
* @param {*} [defaultValue] - Default value if key not found
|
|
41
|
+
* @returns {*} The configuration value or default
|
|
42
|
+
* @example
|
|
43
|
+
* const priority = config.get('priority.default', 'P3');
|
|
44
|
+
* const strict = config.get('strictMode', false);
|
|
45
|
+
*/
|
|
46
|
+
get(key, defaultValue = undefined) {
|
|
47
|
+
if (!fs.existsSync(this.configPath)) return defaultValue;
|
|
48
|
+
const config = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
|
|
49
|
+
|
|
50
|
+
// Support dot notation for nested keys
|
|
51
|
+
const keys = key.split('.');
|
|
52
|
+
let value = config;
|
|
53
|
+
for (const k of keys) {
|
|
54
|
+
if (value && typeof value === 'object' && k in value) {
|
|
55
|
+
value = value[k];
|
|
56
|
+
} else {
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return value !== undefined ? value : defaultValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sets a configuration value
|
|
65
|
+
* @param {string} key - Configuration key (supports dot notation)
|
|
66
|
+
* @param {*} value - Value to set
|
|
67
|
+
* @returns {void}
|
|
68
|
+
* @example
|
|
69
|
+
* config.set('priority.default', 'P2');
|
|
70
|
+
* config.set('theme', 'dark');
|
|
71
|
+
*/
|
|
72
|
+
set(key, value) {
|
|
73
|
+
const config = fs.existsSync(this.configPath)
|
|
74
|
+
? JSON.parse(fs.readFileSync(this.configPath, 'utf-8'))
|
|
75
|
+
: {};
|
|
76
|
+
|
|
77
|
+
// Support dot notation for nested keys
|
|
78
|
+
.split('.');
|
|
79
|
+
const keys = key let current = config;
|
|
80
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
81
|
+
const k = keys[i];
|
|
82
|
+
if (!current[k] || typeof current[k] !== 'object') {
|
|
83
|
+
current[k] = {};
|
|
84
|
+
}
|
|
85
|
+
current = current[k];
|
|
86
|
+
}
|
|
87
|
+
current[keys[keys.length - 1]] = value;
|
|
88
|
+
|
|
89
|
+
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Deletes a configuration key
|
|
94
|
+
* @param {string} key - Configuration key to delete
|
|
95
|
+
* @returns {boolean} True if key was deleted
|
|
96
|
+
* @example
|
|
97
|
+
* config.delete('theme');
|
|
98
|
+
*/
|
|
99
|
+
delete(key) {
|
|
100
|
+
if (!fs.existsSync(this.configPath)) return false;
|
|
101
|
+
const config = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
|
|
102
|
+
|
|
103
|
+
const keys = key.split('.');
|
|
104
|
+
let current = config;
|
|
105
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
106
|
+
if (!current[keys[i]]) return false;
|
|
107
|
+
current = current[keys[i]];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (current[keys[keys.length - 1]] !== undefined) {
|
|
111
|
+
delete current[keys[keys.length - 1]];
|
|
112
|
+
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a configuration key exists
|
|
120
|
+
* @param {string} key - Configuration key to check
|
|
121
|
+
* @returns {boolean} True if key exists
|
|
122
|
+
* @example
|
|
123
|
+
* if (config.has('theme')) { console.log('Theme is set'); }
|
|
124
|
+
*/
|
|
125
|
+
has(key) {
|
|
126
|
+
return this.get(key) !== undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gets all configuration as an object
|
|
131
|
+
* @returns {Object} Full configuration object
|
|
132
|
+
* @example
|
|
133
|
+
* const allConfig = config.getAll();
|
|
134
|
+
*/
|
|
135
|
+
getAll() {
|
|
136
|
+
if (!fs.existsSync(this.configPath)) return {};
|
|
137
|
+
return JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Clears all configuration
|
|
142
|
+
* @returns {void}
|
|
143
|
+
* @example
|
|
144
|
+
* config.clear();
|
|
145
|
+
*/
|
|
146
|
+
clear() {
|
|
147
|
+
if (fs.existsSync(this.configPath)) {
|
|
148
|
+
fs.unlinkSync(this.configPath);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = MandorConfig;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Binary download module for Mandor CLI
|
|
3
|
+
* @description Handles downloading and caching Mandor binaries for the current platform
|
|
4
|
+
* @version 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
/** @type {string} GitHub releases API URL */
|
|
13
|
+
const RELEASES_URL = 'https://api.github.com/repos/sanxzy/mandor/releases';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Downloads the Mandor binary for the specified platform and architecture
|
|
17
|
+
* @async
|
|
18
|
+
* @param {string} version - The Mandor version to download (e.g., '1.0.0', 'latest')
|
|
19
|
+
* @param {string} platform - Target platform (e.g., 'darwin', 'linux', 'win32')
|
|
20
|
+
* @param {string} arch - Target architecture (e.g., 'x64', 'arm64')
|
|
21
|
+
* @returns {Promise<string>} Path to the downloaded and executable binary
|
|
22
|
+
* @throws {Error} If download fails or platform is unsupported
|
|
23
|
+
* @example
|
|
24
|
+
* // Download Mandor v1.0.0 for macOS x64
|
|
25
|
+
* const binaryPath = await downloadBinary('1.0.0', 'darwin', 'x64');
|
|
26
|
+
* console.log(`Binary downloaded to: ${binaryPath}`);
|
|
27
|
+
*/
|
|
28
|
+
async function downloadBinary(version, platform, arch) {
|
|
29
|
+
const filename = `mandor-${platform}-${arch}`;
|
|
30
|
+
const url = `${RELEASES_URL}/download/${version}/${filename}`;
|
|
31
|
+
const dest = path.join(os.homedir(), '.mandor', 'bin', filename);
|
|
32
|
+
|
|
33
|
+
// Download and make executable
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
https.get(url, (response) => {
|
|
36
|
+
if (response.statusCode === 302) {
|
|
37
|
+
return downloadBinary(response.headers.location, platform, arch);
|
|
38
|
+
}
|
|
39
|
+
const file = fs.createWriteStream(dest);
|
|
40
|
+
response.pipe(file);
|
|
41
|
+
file.on('finish', () => {
|
|
42
|
+
fs.chmodSync(dest, '755');
|
|
43
|
+
resolve(dest);
|
|
44
|
+
});
|
|
45
|
+
}).on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets the platform identifier for the current system
|
|
51
|
+
* @returns {{platform: string, arch: string}} Platform and architecture info
|
|
52
|
+
* @example
|
|
53
|
+
* const { platform, arch } = getCurrentPlatform();
|
|
54
|
+
* console.log(`Running on ${platform}-${arch}`);
|
|
55
|
+
*/
|
|
56
|
+
function getCurrentPlatform() {
|
|
57
|
+
const platform = os.platform(); // 'darwin', 'linux', 'win32'
|
|
58
|
+
const arch = os.arch(); // 'x64', 'arm64'
|
|
59
|
+
return { platform, arch };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Checks if a binary already exists and is up-to-date
|
|
64
|
+
* @param {string} version - Expected version
|
|
65
|
+
* @param {string} platform - Target platform
|
|
66
|
+
* @param {string} arch - Target architecture
|
|
67
|
+
* @returns {Promise<boolean>} True if binary exists and is valid
|
|
68
|
+
* @example
|
|
69
|
+
* const exists = await binaryExists('1.0.0', 'darwin', 'x64');
|
|
70
|
+
* if (exists) { console.log('Binary cached'); }
|
|
71
|
+
*/
|
|
72
|
+
async function binaryExists(version, platform, arch) {
|
|
73
|
+
const filename = `mandor-${platform}-${arch}`;
|
|
74
|
+
const dest = path.join(os.homedir(), '.mandor', 'bin', filename);
|
|
75
|
+
return fs.existsSync(dest);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
downloadBinary,
|
|
80
|
+
getCurrentPlatform,
|
|
81
|
+
binaryExists,
|
|
82
|
+
RELEASES_URL
|
|
83
|
+
};
|
package/npm/lib/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mandor CLI npm package entry point
|
|
3
|
+
* @description Main export for programmatic usage and CLI access
|
|
4
|
+
* @version 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const Mandor = require('./api');
|
|
8
|
+
const MandorConfig = require('./config');
|
|
9
|
+
const { resolve, listCachedBinaries, clearCache } = require('./resolve');
|
|
10
|
+
const { downloadBinary, getCurrentPlatform } = require('./download');
|
|
11
|
+
const { install } = require('./install');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Main export object containing all public APIs
|
|
15
|
+
* @namespace mandor
|
|
16
|
+
* @version 0.0.1
|
|
17
|
+
* @example
|
|
18
|
+
* const mandor = require('@mandor/cli');
|
|
19
|
+
*
|
|
20
|
+
* // CLI access via npx or npm script
|
|
21
|
+
* // $ npx mandor init "My Project"
|
|
22
|
+
*
|
|
23
|
+
* // Programmatic usage
|
|
24
|
+
* const cli = new mandor.Mandor({ json: true });
|
|
25
|
+
* await cli.init('My Project');
|
|
26
|
+
*/
|
|
27
|
+
module.exports = {
|
|
28
|
+
/**
|
|
29
|
+
* Mandor CLI wrapper class for programmatic access
|
|
30
|
+
* @type {typeof Mandor}
|
|
31
|
+
* @memberof mandor
|
|
32
|
+
* @example
|
|
33
|
+
* const mandor = require('@mandor/cli');
|
|
34
|
+
* const cli = new mandor.Mandor({ json: true });
|
|
35
|
+
*/
|
|
36
|
+
Mandor,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Configuration management class
|
|
40
|
+
* @type {typeof MandorConfig}
|
|
41
|
+
* @memberof mandor
|
|
42
|
+
* @example
|
|
43
|
+
* const config = new mandor.MandorConfig('/path/to/project');
|
|
44
|
+
* const priority = config.get('priority.default', 'P3');
|
|
45
|
+
*/
|
|
46
|
+
MandorConfig,
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the Mandor binary path
|
|
50
|
+
* @function
|
|
51
|
+
* @param {Object} [options] - Resolution options
|
|
52
|
+
* @param {string} [options.version] - Version to use
|
|
53
|
+
* @returns {Promise<string>} Path to binary
|
|
54
|
+
* @memberof mandor
|
|
55
|
+
* @example
|
|
56
|
+
* const binaryPath = await mandor.resolve({ version: 'latest' });
|
|
57
|
+
*/
|
|
58
|
+
resolve,
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Lists all cached binary versions
|
|
62
|
+
* @function
|
|
63
|
+
* @returns {Object[]} Cached binary info
|
|
64
|
+
* @memberof mandor
|
|
65
|
+
* @example
|
|
66
|
+
* const cached = mandor.listCachedBinaries();
|
|
67
|
+
*/
|
|
68
|
+
listCachedBinaries,
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Clears all cached binaries
|
|
72
|
+
* @function
|
|
73
|
+
* @returns {number} Number of binaries removed
|
|
74
|
+
* @memberof mandor
|
|
75
|
+
* @example
|
|
76
|
+
* const removed = mandor.clearCache();
|
|
77
|
+
*/
|
|
78
|
+
clearCache,
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Downloads a Mandor binary
|
|
82
|
+
* @function
|
|
83
|
+
* @param {string} version - Version to download
|
|
84
|
+
* @param {string} [platform] - Target platform
|
|
85
|
+
* @param {string} [arch] - Target architecture
|
|
86
|
+
* @returns {Promise<string>} Path to downloaded binary
|
|
87
|
+
* @memberof mandor
|
|
88
|
+
* @example
|
|
89
|
+
* const binary = await mandor.downloadBinary('1.0.0', 'darwin', 'x64');
|
|
90
|
+
*/
|
|
91
|
+
downloadBinary,
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gets current platform information
|
|
95
|
+
* @function
|
|
96
|
+
* @returns {{platform: string, arch: string}} Platform info
|
|
97
|
+
* @memberof mandor
|
|
98
|
+
* @example
|
|
99
|
+
* const { platform, arch } = mandor.getCurrentPlatform();
|
|
100
|
+
*/
|
|
101
|
+
getCurrentPlatform,
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Runs post-install setup
|
|
105
|
+
* @function
|
|
106
|
+
* @param {Object} [options] - Install options
|
|
107
|
+
* @returns {Promise<string>} Path to installed binary
|
|
108
|
+
* @memberof mandor
|
|
109
|
+
* @example
|
|
110
|
+
* await mandor.install({ version: 'latest' });
|
|
111
|
+
*/
|
|
112
|
+
install,
|
|
113
|
+
|
|
114
|
+
// Version info
|
|
115
|
+
/** @type {string} Package version */
|
|
116
|
+
version: '0.0.1',
|
|
117
|
+
|
|
118
|
+
/** @type {string} Supported Mandor version range */
|
|
119
|
+
mandorVersionRange: '>=0.0.1'
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// CLI entry point when bin/mandor is executed
|
|
123
|
+
if (require.main === module) {
|
|
124
|
+
resolve()
|
|
125
|
+
.then(binaryPath => {
|
|
126
|
+
const { spawn } = require('child_process');
|
|
127
|
+
const args = process.argv.slice(2);
|
|
128
|
+
const proc = spawn(binaryPath, args, {
|
|
129
|
+
stdio: 'inherit',
|
|
130
|
+
cwd: process.cwd()
|
|
131
|
+
});
|
|
132
|
+
proc.on('exit', process.exit);
|
|
133
|
+
})
|
|
134
|
+
.catch(error => {
|
|
135
|
+
console.error('Failed to start Mandor:', error.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Post-install hook for Mandor CLI
|
|
3
|
+
* @description Handles binary download and caching during npm install
|
|
4
|
+
* @version 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { downloadBinary, getCurrentPlatform } = require('./download');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/** @type {string} Cache directory for binaries */
|
|
12
|
+
const CACHE_DIR = path.join(__dirname, '..', '.cache');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Installs the Mandor binary for the current platform
|
|
16
|
+
* @async
|
|
17
|
+
* @param {Object} [options] - Installation options
|
|
18
|
+
* @param {string} [options.version] - Version to install (default: 'latest')
|
|
19
|
+
* @returns {Promise<string>} Path to the installed binary
|
|
20
|
+
* @throws {Error} If download fails
|
|
21
|
+
* @example
|
|
22
|
+
* // Called automatically by npm postinstall
|
|
23
|
+
* await install();
|
|
24
|
+
*/
|
|
25
|
+
async function install(options = {}) {
|
|
26
|
+
const version = options.version || 'latest';
|
|
27
|
+
const { platform, arch } = getCurrentPlatform();
|
|
28
|
+
|
|
29
|
+
console.log(`Installing Mandor ${version} for ${platform}-${arch}...`);
|
|
30
|
+
|
|
31
|
+
const binaryPath = await downloadBinary(version, platform, arch);
|
|
32
|
+
console.log(`✓ Mandor installed: ${binaryPath}`);
|
|
33
|
+
|
|
34
|
+
return binaryPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cleans up old binary caches
|
|
39
|
+
* @returns {number} Number of files removed
|
|
40
|
+
* @example
|
|
41
|
+
* const removed = cleanupCache();
|
|
42
|
+
* console.log(`Removed ${removed} old binary files`);
|
|
43
|
+
*/
|
|
44
|
+
function cleanupCache() {
|
|
45
|
+
if (!fs.existsSync(CACHE_DIR)) return 0;
|
|
46
|
+
|
|
47
|
+
const files = fs.readdirSync(CACHE_DIR);
|
|
48
|
+
let removed = 0;
|
|
49
|
+
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const filePath = path.join(CACHE_DIR, file);
|
|
52
|
+
const stats = fs.statSync(filePath);
|
|
53
|
+
|
|
54
|
+
// Remove files older than 30 days
|
|
55
|
+
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
56
|
+
if (stats.mtimeMs < thirtyDaysAgo) {
|
|
57
|
+
fs.unlinkSync(filePath);
|
|
58
|
+
removed++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return removed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Gets the installed binary version
|
|
67
|
+
* @returns {string|null} Version string or null if not installed
|
|
68
|
+
* @example
|
|
69
|
+
* const version = getInstalledVersion();
|
|
70
|
+
* if (version) { console.log(`Using Mandor ${version}`); }
|
|
71
|
+
*/
|
|
72
|
+
function getInstalledVersion() {
|
|
73
|
+
const versionPath = path.join(CACHE_DIR, 'version.txt');
|
|
74
|
+
if (fs.existsSync(versionPath)) {
|
|
75
|
+
return fs.readFileSync(versionPath, 'utf-8').trim();
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Run install on postinstall
|
|
81
|
+
if (require.main === module || process.env.npm_lifecycle_event === 'postinstall') {
|
|
82
|
+
install().catch(error => {
|
|
83
|
+
console.error('Failed to install Mandor:', error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
install,
|
|
90
|
+
cleanupCache,
|
|
91
|
+
getInstalledVersion
|
|
92
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Version resolution module for Mandor CLI
|
|
3
|
+
* @description Resolves the correct binary path based on version and platform
|
|
4
|
+
* @version 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { downloadBinary, getCurrentPlatform, binaryExists } = require('./download');
|
|
11
|
+
|
|
12
|
+
/** @type {string} Default version to use */
|
|
13
|
+
const DEFAULT_VERSION = 'latest';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the binary path for the requested version
|
|
17
|
+
* @async
|
|
18
|
+
* @param {Object} [options] - Resolution options
|
|
19
|
+
* @param {string} [options.version] - Requested version (default: 'latest')
|
|
20
|
+
* @param {boolean} [options.forceDownload] - Force re-download even if cached
|
|
21
|
+
* @returns {Promise<string>} Path to the Mandor binary
|
|
22
|
+
* @throws {Error} If binary cannot be resolved or downloaded
|
|
23
|
+
* @example
|
|
24
|
+
* const binaryPath = await resolve({ version: '1.0.0' });
|
|
25
|
+
* console.log(`Using: ${binaryPath}`);
|
|
26
|
+
*/
|
|
27
|
+
async function resolve(options = {}) {
|
|
28
|
+
const version = options.version || DEFAULT_VERSION;
|
|
29
|
+
const { platform, arch } = getCurrentPlatform();
|
|
30
|
+
|
|
31
|
+
// Check cache first
|
|
32
|
+
const cachedPath = getCachedBinary(version, platform, arch);
|
|
33
|
+
if (cachedPath && !options.forceDownload) {
|
|
34
|
+
return cachedPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Download if not cached
|
|
38
|
+
const binaryPath = await downloadBinary(version, platform, arch);
|
|
39
|
+
cacheBinary(binaryPath, version, platform, arch);
|
|
40
|
+
|
|
41
|
+
return binaryPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets the cached binary path for a specific version
|
|
46
|
+
* @param {string} version - Version to look for
|
|
47
|
+
* @param {string} platform - Target platform
|
|
48
|
+
* @param {string} arch - Target architecture
|
|
49
|
+
* @returns {string|null} Path to cached binary or null
|
|
50
|
+
* @example
|
|
51
|
+
* const cached = getCachedBinary('1.0.0', 'darwin', 'x64');
|
|
52
|
+
*/
|
|
53
|
+
function getCachedBinary(version, platform, arch) {
|
|
54
|
+
const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
|
|
55
|
+
const binaryName = platform === 'win32' ? 'mandor.exe' : 'mandor';
|
|
56
|
+
const binaryPath = path.join(cacheDir, `${version}-${platform}-${arch}`, binaryName);
|
|
57
|
+
|
|
58
|
+
if (fs.existsSync(binaryPath)) {
|
|
59
|
+
return binaryPath;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Caches a binary for future use
|
|
66
|
+
* @param {string} binaryPath - Path to the binary
|
|
67
|
+
* @param {string} version - Version identifier
|
|
68
|
+
* @param {string} platform - Target platform
|
|
69
|
+
* @param {string} arch - Target architecture
|
|
70
|
+
* @returns {void}
|
|
71
|
+
* @example
|
|
72
|
+
* cacheBinary('/home/user/.mandor/bin/1.0.0-darwin-x64/mandor', '1.0.0', 'darwin', 'x64');
|
|
73
|
+
*/
|
|
74
|
+
function cacheBinary(binaryPath, version, platform, arch) {
|
|
75
|
+
const cacheDir = path.join(os.homedir(), '.mandor', 'bin', `${version}-${platform}-${arch}`);
|
|
76
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
77
|
+
|
|
78
|
+
const binaryName = platform === 'win32' ? 'mandor.exe' : 'mandor';
|
|
79
|
+
const destPath = path.join(cacheDir, binaryName);
|
|
80
|
+
|
|
81
|
+
fs.copyFileSync(binaryPath, destPath);
|
|
82
|
+
fs.chmodSync(destPath, '755');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Lists all cached binary versions
|
|
87
|
+
* @returns {Object[]} Array of cached binary info
|
|
88
|
+
* @example
|
|
89
|
+
* const cached = listCachedBinaries();
|
|
90
|
+
* cached.forEach(b => console.log(`${b.version} (${b.platform}-${b.arch})`));
|
|
91
|
+
*/
|
|
92
|
+
function listCachedBinaries() {
|
|
93
|
+
const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(cacheDir)) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const versions = [];
|
|
100
|
+
const entries = fs.readdirSync(cacheDir);
|
|
101
|
+
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const entryPath = path.join(cacheDir, entry);
|
|
104
|
+
if (fs.statSync(entryPath).isDirectory()) {
|
|
105
|
+
const [version, platform, arch] = entry.split('-');
|
|
106
|
+
versions.push({ version, platform, arch, path: entryPath });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return versions;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clears all cached binaries
|
|
115
|
+
* @returns {number} Number of binaries removed
|
|
116
|
+
* @example
|
|
117
|
+
* const removed = clearCache();
|
|
118
|
+
* console.log(`Cleared ${removed} cached binaries`);
|
|
119
|
+
*/
|
|
120
|
+
function clearCache() {
|
|
121
|
+
const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
|
|
122
|
+
|
|
123
|
+
if (!fs.existsSync(cacheDir)) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const entries = fs.readdirSync(cacheDir);
|
|
128
|
+
let removed = 0;
|
|
129
|
+
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const entryPath = path.join(cacheDir, entry);
|
|
132
|
+
if (fs.statSync(entryPath).isDirectory()) {
|
|
133
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
134
|
+
removed++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return removed;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
resolve,
|
|
143
|
+
getCachedBinary,
|
|
144
|
+
cacheBinary,
|
|
145
|
+
listCachedBinaries,
|
|
146
|
+
clearCache,
|
|
147
|
+
DEFAULT_VERSION
|
|
148
|
+
};
|