cskit-cli 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/commands/init.js +156 -63
- package/src/commands/status.js +2 -1
- package/src/commands/update.js +3 -3
- package/src/index.js +1 -0
- package/src/lib/github.js +94 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cskit-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Content Suite Kit CLI - Download and manage CSK skills from private repository",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/tohaitrieu/
|
|
28
|
+
"url": "git+https://github.com/tohaitrieu/content-suite-kit.git"
|
|
29
29
|
},
|
|
30
30
|
"bugs": {
|
|
31
|
-
"url": "https://github.com/tohaitrieu/
|
|
31
|
+
"url": "https://github.com/tohaitrieu/content-suite-kit/issues"
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://cskit.net",
|
|
34
34
|
"dependencies": {
|
package/src/commands/init.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Init Command
|
|
5
5
|
*
|
|
6
|
-
* Downloads CSK from private GitHub repository
|
|
7
|
-
*
|
|
6
|
+
* Downloads CSK from private GitHub repository.
|
|
7
|
+
* Features: version selection, progress bar, Python setup.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
@@ -12,8 +12,16 @@ const path = require('path');
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const ora = require('ora');
|
|
14
14
|
const crypto = require('crypto');
|
|
15
|
+
const inquirer = require('inquirer');
|
|
16
|
+
const { execSync, spawn } = require('child_process');
|
|
15
17
|
const { getToken } = require('../lib/keychain');
|
|
16
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
verifyAccess,
|
|
20
|
+
getRepoTreeFromRef,
|
|
21
|
+
downloadFileFromRef,
|
|
22
|
+
getAllReleases,
|
|
23
|
+
getLatestRelease
|
|
24
|
+
} = require('../lib/github');
|
|
17
25
|
const {
|
|
18
26
|
isProtected,
|
|
19
27
|
shouldExclude,
|
|
@@ -23,9 +31,73 @@ const {
|
|
|
23
31
|
ensureDir
|
|
24
32
|
} = require('../lib/merge');
|
|
25
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Format date for display
|
|
36
|
+
*/
|
|
37
|
+
function formatDate(isoDate) {
|
|
38
|
+
const d = new Date(isoDate);
|
|
39
|
+
return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create progress bar string
|
|
44
|
+
*/
|
|
45
|
+
function progressBar(current, total, width = 30) {
|
|
46
|
+
const percent = Math.round((current / total) * 100);
|
|
47
|
+
const filled = Math.round((current / total) * width);
|
|
48
|
+
const empty = width - filled;
|
|
49
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
|
|
50
|
+
return `${bar} ${percent}%`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Setup Python virtual environment
|
|
55
|
+
*/
|
|
56
|
+
async function setupPythonEnv(projectDir, spinner) {
|
|
57
|
+
const libPythonDir = path.join(projectDir, 'lib', 'python');
|
|
58
|
+
const requirementsFile = path.join(libPythonDir, 'requirements.txt');
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(requirementsFile)) {
|
|
61
|
+
return { success: true, skipped: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
spinner.start('Setting up Python environment...');
|
|
65
|
+
|
|
66
|
+
const venvPath = path.join(libPythonDir, '.venv');
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Check if Python 3 is available
|
|
70
|
+
try {
|
|
71
|
+
execSync('python3 --version', { stdio: 'pipe' });
|
|
72
|
+
} catch {
|
|
73
|
+
spinner.warn('Python 3 not found, skipping venv setup');
|
|
74
|
+
return { success: true, skipped: true, reason: 'python3 not found' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create venv if not exists
|
|
78
|
+
if (!fs.existsSync(venvPath)) {
|
|
79
|
+
spinner.text = 'Creating virtual environment...';
|
|
80
|
+
execSync(`python3 -m venv "${venvPath}"`, { stdio: 'pipe', cwd: libPythonDir });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Install requirements
|
|
84
|
+
spinner.text = 'Installing Python packages...';
|
|
85
|
+
const pipPath = path.join(venvPath, 'bin', 'pip');
|
|
86
|
+
execSync(`"${pipPath}" install -r requirements.txt -q`, {
|
|
87
|
+
stdio: 'pipe',
|
|
88
|
+
cwd: libPythonDir
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
spinner.succeed('Python environment ready');
|
|
92
|
+
return { success: true };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.warn(`Python setup failed: ${error.message}`);
|
|
95
|
+
return { success: false, error: error.message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
26
99
|
/**
|
|
27
100
|
* Main init command handler
|
|
28
|
-
* @param {Object} options - Command options
|
|
29
101
|
*/
|
|
30
102
|
async function initCommand(options) {
|
|
31
103
|
const projectDir = process.cwd();
|
|
@@ -38,11 +110,10 @@ async function initCommand(options) {
|
|
|
38
110
|
const token = await getToken();
|
|
39
111
|
if (!token) {
|
|
40
112
|
spinner.fail(chalk.red('Not authenticated'));
|
|
41
|
-
console.log(chalk.dim('\n Run `
|
|
113
|
+
console.log(chalk.dim('\n Run `cskit auth --login` first.\n'));
|
|
42
114
|
process.exit(1);
|
|
43
115
|
}
|
|
44
116
|
|
|
45
|
-
// Verify access
|
|
46
117
|
const { valid, error } = await verifyAccess(token);
|
|
47
118
|
if (!valid) {
|
|
48
119
|
spinner.fail(chalk.red('Access denied'));
|
|
@@ -52,51 +123,70 @@ async function initCommand(options) {
|
|
|
52
123
|
|
|
53
124
|
spinner.succeed('Authenticated');
|
|
54
125
|
|
|
55
|
-
// Check
|
|
126
|
+
// Check existing installation
|
|
56
127
|
const manifest = loadManifest(projectDir);
|
|
57
128
|
const isUpdate = manifest.version !== null;
|
|
58
129
|
|
|
59
130
|
if (isUpdate) {
|
|
60
|
-
|
|
61
|
-
console.log(chalk.dim(
|
|
131
|
+
const currentVer = manifest.version.replace(/^v/, '');
|
|
132
|
+
console.log(chalk.dim(`\n Current: v${currentVer} (${formatDate(manifest.installedAt)})\n`));
|
|
62
133
|
}
|
|
63
134
|
|
|
64
|
-
//
|
|
65
|
-
spinner.start('Fetching
|
|
135
|
+
// Fetch available versions
|
|
136
|
+
spinner.start('Fetching available versions...');
|
|
66
137
|
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
spinner.succeed(`Latest version: ${release.tag}`);
|
|
72
|
-
} else {
|
|
138
|
+
const releases = await getAllReleases(token);
|
|
139
|
+
const latest = await getLatestRelease(token);
|
|
140
|
+
|
|
141
|
+
if (releases.length === 0) {
|
|
73
142
|
spinner.info('No releases found, using main branch');
|
|
143
|
+
} else {
|
|
144
|
+
spinner.succeed(`Found ${releases.length} release(s)`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Version selection
|
|
148
|
+
let selectedVersion = 'main';
|
|
149
|
+
|
|
150
|
+
if (releases.length > 0 && !options.latest) {
|
|
151
|
+
const choices = releases.map((r, i) => ({
|
|
152
|
+
name: `${r.tag}${i === 0 ? chalk.green(' (latest)') : ''} - ${formatDate(r.published)}`,
|
|
153
|
+
value: r.tag
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
choices.push({ name: chalk.dim('main branch (development)'), value: 'main' });
|
|
157
|
+
|
|
158
|
+
const answer = await inquirer.prompt([{
|
|
159
|
+
type: 'list',
|
|
160
|
+
name: 'version',
|
|
161
|
+
message: 'Select version to install:',
|
|
162
|
+
choices,
|
|
163
|
+
default: releases[0]?.tag || 'main'
|
|
164
|
+
}]);
|
|
165
|
+
|
|
166
|
+
selectedVersion = answer.version;
|
|
167
|
+
} else if (latest) {
|
|
168
|
+
selectedVersion = latest.tag;
|
|
74
169
|
}
|
|
75
170
|
|
|
76
|
-
|
|
171
|
+
console.log(chalk.dim(`\n Installing: ${selectedVersion}\n`));
|
|
172
|
+
|
|
173
|
+
// Fetch file tree
|
|
77
174
|
spinner.start('Fetching file list...');
|
|
78
175
|
|
|
79
|
-
const tree = await
|
|
176
|
+
const tree = await getRepoTreeFromRef(token, selectedVersion);
|
|
80
177
|
const files = tree.filter(item =>
|
|
81
178
|
item.type === 'blob' && !shouldExclude(item.path)
|
|
82
179
|
);
|
|
83
180
|
|
|
84
181
|
spinner.succeed(`Found ${files.length} files`);
|
|
85
182
|
|
|
86
|
-
// Download
|
|
87
|
-
console.log(
|
|
88
|
-
|
|
89
|
-
const stats = {
|
|
90
|
-
created: 0,
|
|
91
|
-
updated: 0,
|
|
92
|
-
skipped: 0,
|
|
93
|
-
protected: 0,
|
|
94
|
-
userModified: 0
|
|
95
|
-
};
|
|
183
|
+
// Download files with progress bar
|
|
184
|
+
console.log('');
|
|
96
185
|
|
|
186
|
+
const stats = { created: 0, updated: 0, skipped: 0, protected: 0, userModified: 0 };
|
|
97
187
|
const newManifest = {
|
|
98
188
|
files: {},
|
|
99
|
-
version:
|
|
189
|
+
version: selectedVersion,
|
|
100
190
|
installedAt: new Date().toISOString()
|
|
101
191
|
};
|
|
102
192
|
|
|
@@ -105,25 +195,21 @@ async function initCommand(options) {
|
|
|
105
195
|
const targetPath = path.join(projectDir, file.path);
|
|
106
196
|
const relativePath = file.path;
|
|
107
197
|
|
|
108
|
-
//
|
|
109
|
-
|
|
198
|
+
// Update progress bar
|
|
199
|
+
process.stdout.write(`\r ${progressBar(i + 1, files.length)} ${chalk.dim(`(${i + 1}/${files.length})`)}`);
|
|
110
200
|
|
|
111
201
|
try {
|
|
112
|
-
|
|
113
|
-
const content = await downloadFile(token, file.path);
|
|
202
|
+
const content = await downloadFileFromRef(token, file.path, selectedVersion);
|
|
114
203
|
const contentHash = crypto.createHash('md5').update(content).digest('hex');
|
|
115
204
|
|
|
116
|
-
// Determine action
|
|
117
205
|
const { action, reason } = options.force
|
|
118
206
|
? { action: isProtected(relativePath) ? 'skip' : 'update', reason: 'forced' }
|
|
119
207
|
: determineMergeAction(targetPath, relativePath, content, manifest.files);
|
|
120
208
|
|
|
121
|
-
// Execute action
|
|
122
209
|
switch (action) {
|
|
123
210
|
case 'create':
|
|
124
211
|
ensureDir(path.dirname(targetPath));
|
|
125
212
|
fs.writeFileSync(targetPath, content);
|
|
126
|
-
console.log(chalk.green(` ${progress} + ${relativePath}`));
|
|
127
213
|
stats.created++;
|
|
128
214
|
newManifest.files[relativePath] = contentHash;
|
|
129
215
|
break;
|
|
@@ -131,52 +217,59 @@ async function initCommand(options) {
|
|
|
131
217
|
case 'update':
|
|
132
218
|
ensureDir(path.dirname(targetPath));
|
|
133
219
|
fs.writeFileSync(targetPath, content);
|
|
134
|
-
console.log(chalk.blue(` ${progress} ~ ${relativePath}`));
|
|
135
220
|
stats.updated++;
|
|
136
221
|
newManifest.files[relativePath] = contentHash;
|
|
137
222
|
break;
|
|
138
223
|
|
|
139
224
|
case 'skip':
|
|
140
|
-
if (reason === 'protected')
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.log(chalk.yellow(` ${progress} ! ${relativePath} (modified)`));
|
|
145
|
-
stats.userModified++;
|
|
146
|
-
} else {
|
|
147
|
-
// Unchanged, don't log to reduce noise
|
|
148
|
-
stats.skipped++;
|
|
149
|
-
}
|
|
150
|
-
// Preserve existing hash in manifest
|
|
225
|
+
if (reason === 'protected') stats.protected++;
|
|
226
|
+
else if (reason === 'user-modified') stats.userModified++;
|
|
227
|
+
else stats.skipped++;
|
|
228
|
+
|
|
151
229
|
if (manifest.files[relativePath]) {
|
|
152
230
|
newManifest.files[relativePath] = manifest.files[relativePath];
|
|
153
231
|
}
|
|
154
232
|
break;
|
|
155
233
|
}
|
|
156
234
|
} catch (err) {
|
|
157
|
-
|
|
235
|
+
// Silent fail for individual files
|
|
158
236
|
}
|
|
159
237
|
}
|
|
160
238
|
|
|
239
|
+
// Clear progress line
|
|
240
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
241
|
+
|
|
161
242
|
// Save manifest
|
|
162
243
|
saveManifest(projectDir, newManifest);
|
|
163
244
|
|
|
164
245
|
// Summary
|
|
165
|
-
console.log(chalk.cyan('
|
|
246
|
+
console.log(chalk.cyan(' Summary\n'));
|
|
166
247
|
console.log(` ${chalk.green('+')} Created: ${stats.created}`);
|
|
167
248
|
console.log(` ${chalk.blue('~')} Updated: ${stats.updated}`);
|
|
168
|
-
console.log(` ${chalk.yellow('!')} Protected: ${stats.protected}`);
|
|
169
|
-
console.log(` ${chalk.yellow('!')} User modified: ${stats.userModified}`);
|
|
170
|
-
console.log(` ${chalk.dim('-')} Unchanged: ${stats.skipped}`);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
249
|
+
if (stats.protected > 0) console.log(` ${chalk.yellow('!')} Protected: ${stats.protected}`);
|
|
250
|
+
if (stats.userModified > 0) console.log(` ${chalk.yellow('!')} User modified: ${stats.userModified}`);
|
|
251
|
+
if (stats.skipped > 0) console.log(` ${chalk.dim('-')} Unchanged: ${stats.skipped}`);
|
|
252
|
+
|
|
253
|
+
// Setup Python environment
|
|
254
|
+
console.log('');
|
|
255
|
+
const pythonResult = await setupPythonEnv(projectDir, spinner);
|
|
256
|
+
|
|
257
|
+
// Success message
|
|
258
|
+
const ver = selectedVersion.replace(/^v/, '');
|
|
259
|
+
console.log(chalk.green(`\n ✓ CSK v${ver} ${isUpdate ? 'updated' : 'installed'} successfully!\n`));
|
|
260
|
+
|
|
261
|
+
// Quick start guide
|
|
262
|
+
console.log(chalk.cyan(' Quick Start\n'));
|
|
263
|
+
console.log(chalk.dim(' 1. Open Claude Code in this directory:'));
|
|
264
|
+
console.log(` ${chalk.white('claude')}\n`);
|
|
265
|
+
console.log(chalk.dim(' 2. Start with CSK command:'));
|
|
266
|
+
console.log(` ${chalk.white('/csk')}\n`);
|
|
267
|
+
console.log(chalk.dim(' 3. Or explore available skills:'));
|
|
268
|
+
console.log(` ${chalk.white('./cli/csk list')}\n`);
|
|
269
|
+
|
|
270
|
+
if (pythonResult.skipped && pythonResult.reason) {
|
|
271
|
+
console.log(chalk.dim(` Note: ${pythonResult.reason}`));
|
|
272
|
+
console.log(chalk.dim(' Run manually: cd lib/python && python3 -m venv .venv && .venv/bin/pip install -r requirements.txt\n'));
|
|
180
273
|
}
|
|
181
274
|
}
|
|
182
275
|
|
package/src/commands/status.js
CHANGED
|
@@ -39,7 +39,8 @@ async function statusCommand() {
|
|
|
39
39
|
|
|
40
40
|
console.log(chalk.dim(' Installation:'));
|
|
41
41
|
if (manifest.version) {
|
|
42
|
-
|
|
42
|
+
const version = manifest.version.replace(/^v/, '');
|
|
43
|
+
console.log(chalk.green(` Installed: v${version}`));
|
|
43
44
|
console.log(chalk.dim(` Date: ${manifest.installedAt}`));
|
|
44
45
|
console.log(chalk.dim(` Files: ${Object.keys(manifest.files).length}`));
|
|
45
46
|
} else {
|
package/src/commands/update.js
CHANGED
|
@@ -23,7 +23,7 @@ async function updateCommand() {
|
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
25
|
// Get latest version from npm
|
|
26
|
-
const latestVersion = execSync('npm view
|
|
26
|
+
const latestVersion = execSync('npm view cskit-cli version', {
|
|
27
27
|
encoding: 'utf-8',
|
|
28
28
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
29
29
|
}).trim();
|
|
@@ -36,7 +36,7 @@ async function updateCommand() {
|
|
|
36
36
|
spinner.text = `Updating to v${latestVersion}...`;
|
|
37
37
|
|
|
38
38
|
// Run npm update
|
|
39
|
-
execSync('npm install -g
|
|
39
|
+
execSync('npm install -g cskit-cli@latest', {
|
|
40
40
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -50,7 +50,7 @@ async function updateCommand() {
|
|
|
50
50
|
} else {
|
|
51
51
|
spinner.fail(chalk.red('Update failed'));
|
|
52
52
|
console.log(chalk.dim(`\n ${error.message}`));
|
|
53
|
-
console.log(chalk.dim('\n Try manually: npm install -g
|
|
53
|
+
console.log(chalk.dim('\n Try manually: npm install -g cskit-cli@latest\n'));
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
package/src/index.js
CHANGED
|
@@ -42,6 +42,7 @@ program
|
|
|
42
42
|
.command('init')
|
|
43
43
|
.description('Initialize or update CSK in current project')
|
|
44
44
|
.option('-f, --force', 'Force overwrite existing files')
|
|
45
|
+
.option('-l, --latest', 'Skip version selection, use latest')
|
|
45
46
|
.option('--no-merge', 'Skip smart merge, overwrite all')
|
|
46
47
|
.action(initCommand);
|
|
47
48
|
|
package/src/lib/github.js
CHANGED
|
@@ -172,15 +172,107 @@ async function getLatestRelease(token) {
|
|
|
172
172
|
published: data.published_at
|
|
173
173
|
};
|
|
174
174
|
} catch (error) {
|
|
175
|
-
// No releases, use branch
|
|
176
175
|
return null;
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Get all releases
|
|
181
|
+
* @param {string} token - GitHub PAT
|
|
182
|
+
* @param {number} limit - Max releases to fetch
|
|
183
|
+
* @returns {Promise<Array<{tag: string, name: string, published: string}>>}
|
|
184
|
+
*/
|
|
185
|
+
async function getAllReleases(token, limit = 10) {
|
|
186
|
+
try {
|
|
187
|
+
const data = await apiRequest(
|
|
188
|
+
token,
|
|
189
|
+
`/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=${limit}`
|
|
190
|
+
);
|
|
191
|
+
return data.map(r => ({
|
|
192
|
+
tag: r.tag_name,
|
|
193
|
+
name: r.name || r.tag_name,
|
|
194
|
+
published: r.published_at
|
|
195
|
+
}));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Download file from specific ref (tag/branch)
|
|
203
|
+
* @param {string} token - GitHub PAT
|
|
204
|
+
* @param {string} filePath - Path to file
|
|
205
|
+
* @param {string} ref - Tag or branch name
|
|
206
|
+
* @returns {Promise<Buffer>}
|
|
207
|
+
*/
|
|
208
|
+
async function downloadFileFromRef(token, filePath, ref) {
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
const options = {
|
|
211
|
+
hostname: 'raw.githubusercontent.com',
|
|
212
|
+
path: `/${GITHUB_OWNER}/${GITHUB_REPO}/${ref}/${filePath}`,
|
|
213
|
+
method: 'GET',
|
|
214
|
+
headers: {
|
|
215
|
+
'Authorization': `Bearer ${token}`,
|
|
216
|
+
'User-Agent': 'csk-cli'
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const req = https.request(options, (res) => {
|
|
221
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
222
|
+
const redirectUrl = new URL(res.headers.location);
|
|
223
|
+
const redirectOptions = {
|
|
224
|
+
hostname: redirectUrl.hostname,
|
|
225
|
+
path: redirectUrl.pathname + redirectUrl.search,
|
|
226
|
+
method: 'GET',
|
|
227
|
+
headers: { 'User-Agent': 'csk-cli' }
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const redirectReq = https.request(redirectOptions, (redirectRes) => {
|
|
231
|
+
const chunks = [];
|
|
232
|
+
redirectRes.on('data', chunk => chunks.push(chunk));
|
|
233
|
+
redirectRes.on('end', () => resolve(Buffer.concat(chunks)));
|
|
234
|
+
});
|
|
235
|
+
redirectReq.on('error', reject);
|
|
236
|
+
redirectReq.end();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (res.statusCode !== 200) {
|
|
241
|
+
reject(new Error(`Failed to download: ${res.statusCode}`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const chunks = [];
|
|
246
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
247
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
req.on('error', reject);
|
|
251
|
+
req.end();
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get repo tree from specific ref
|
|
257
|
+
* @param {string} token - GitHub PAT
|
|
258
|
+
* @param {string} ref - Tag or branch
|
|
259
|
+
* @returns {Promise<Array>}
|
|
260
|
+
*/
|
|
261
|
+
async function getRepoTreeFromRef(token, ref) {
|
|
262
|
+
const data = await apiRequest(
|
|
263
|
+
token,
|
|
264
|
+
`/repos/${GITHUB_OWNER}/${GITHUB_REPO}/git/trees/${ref}?recursive=1`
|
|
265
|
+
);
|
|
266
|
+
return data.tree || [];
|
|
267
|
+
}
|
|
268
|
+
|
|
180
269
|
module.exports = {
|
|
181
270
|
verifyAccess,
|
|
182
271
|
getRepoTree,
|
|
272
|
+
getRepoTreeFromRef,
|
|
183
273
|
getFileContent,
|
|
184
274
|
downloadFile,
|
|
185
|
-
|
|
275
|
+
downloadFileFromRef,
|
|
276
|
+
getLatestRelease,
|
|
277
|
+
getAllReleases
|
|
186
278
|
};
|