ocs-stats 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +24 -13
- package/package.json +1 -1
- package/src/init.js +45 -28
- package/src/merge.js +351 -0
- package/templates/.ocs-version +1 -0
package/bin/cli.js
CHANGED
|
@@ -7,6 +7,8 @@ const args = process.argv.slice(2);
|
|
|
7
7
|
const command = args[0];
|
|
8
8
|
const isGlobal = args.includes('--global') || args.includes('-g');
|
|
9
9
|
const isHelp = args.includes('--help') || args.includes('-h');
|
|
10
|
+
const isForce = args.includes('--force') || args.includes('-f');
|
|
11
|
+
const isCheck = args.includes('--check') || args.includes('-c');
|
|
10
12
|
|
|
11
13
|
if (isHelp) {
|
|
12
14
|
console.log(`
|
|
@@ -15,7 +17,9 @@ ocs-stats - Install OpenCode skills and agents
|
|
|
15
17
|
Usage:
|
|
16
18
|
npx ocs-stats Install to current project
|
|
17
19
|
npx ocs-stats --global Install globally (~/.opencode)
|
|
18
|
-
npx ocs-stats update Update skills (
|
|
20
|
+
npx ocs-stats update Update skills (smart merge)
|
|
21
|
+
npx ocs-stats update --check Check for updates
|
|
22
|
+
npx ocs-stats update --force Force fresh install
|
|
19
23
|
npx ocs-stats stats Show security agent progress
|
|
20
24
|
npx ocs-stats stats testing Show testing agent progress
|
|
21
25
|
npx ocs-stats display-xp <amount> "<reason>"
|
|
@@ -23,11 +27,15 @@ Usage:
|
|
|
23
27
|
|
|
24
28
|
Options:
|
|
25
29
|
-g, --global Install to user home directory
|
|
30
|
+
-f, --force Force fresh install (delete + copy)
|
|
31
|
+
-c, --check Check for updates without applying
|
|
26
32
|
-h, --help Show this help message
|
|
27
33
|
|
|
28
34
|
Examples:
|
|
29
35
|
npx ocs-stats
|
|
30
36
|
npx ocs-stats update
|
|
37
|
+
npx ocs-stats update --check
|
|
38
|
+
npx ocs-stats update --force
|
|
31
39
|
npx ocs-stats stats
|
|
32
40
|
npx ocs-stats stats testing
|
|
33
41
|
npx ocs-stats display-xp 35 "Fixed high issue"
|
|
@@ -36,21 +44,22 @@ Examples:
|
|
|
36
44
|
process.exit(0);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
if (command === 'update') {
|
|
40
|
-
update({ isGlobal })
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
else if (command === 'update') {
|
|
48
|
+
update({ isGlobal, force: isForce, checkOnly: isCheck })
|
|
49
|
+
.then(() => process.exit(0))
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
console.error('Error:', err.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
if (command === 'stats') {
|
|
56
|
+
else if (command === 'stats') {
|
|
48
57
|
const category = args[1] || 'security';
|
|
49
58
|
stats(category);
|
|
50
59
|
process.exit(0);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
if (command === 'display-xp') {
|
|
62
|
+
else if (command === 'display-xp') {
|
|
54
63
|
const amount = args[1];
|
|
55
64
|
const reason = args.slice(2).join(' ') || 'XP earned';
|
|
56
65
|
const category = reason.includes('[testing]') ? 'testing' : 'security';
|
|
@@ -59,7 +68,9 @@ if (command === 'display-xp') {
|
|
|
59
68
|
process.exit(0);
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
else {
|
|
72
|
+
init({ isGlobal }).catch((err) => {
|
|
73
|
+
console.error('Error:', err.message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
76
|
+
}
|
package/package.json
CHANGED
package/src/init.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { performUpdate, checkForUpdates, formatUpdateSummary } from './merge.js';
|
|
4
5
|
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = path.dirname(__filename);
|
|
7
8
|
|
|
8
9
|
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
9
10
|
|
|
11
|
+
function copyDir(src, dest) {
|
|
12
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
15
|
+
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (entry.name === '.git') continue;
|
|
18
|
+
|
|
19
|
+
const srcPath = path.join(src, entry.name);
|
|
20
|
+
const destPath = path.join(dest, entry.name);
|
|
21
|
+
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
copyDir(srcPath, destPath);
|
|
24
|
+
} else {
|
|
25
|
+
fs.copyFileSync(srcPath, destPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
export async function init({ isGlobal = false } = {}) {
|
|
11
31
|
const targetDir = isGlobal
|
|
12
32
|
? path.join(process.env.HOME || process.env.USERPROFILE, '.opencode')
|
|
@@ -43,7 +63,7 @@ export async function init({ isGlobal = false } = {}) {
|
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
export async function update({ isGlobal = false } = {}) {
|
|
66
|
+
export async function update({ isGlobal = false, force = false, checkOnly = false } = {}) {
|
|
47
67
|
const targetDir = isGlobal
|
|
48
68
|
? path.join(process.env.HOME || process.env.USERPROFILE, '.opencode')
|
|
49
69
|
: path.join(process.cwd(), '.opencode');
|
|
@@ -55,35 +75,32 @@ export async function update({ isGlobal = false } = {}) {
|
|
|
55
75
|
process.exit(1);
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
if (force) {
|
|
79
|
+
console.log(' Force mode: Removing existing .opencode folder');
|
|
80
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
81
|
+
copyDir(TEMPLATES_DIR, targetDir);
|
|
82
|
+
console.log(' Fresh install complete\n');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
65
85
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
86
|
+
if (checkOnly) {
|
|
87
|
+
console.log(' Checking for updates...\n');
|
|
88
|
+
const updateInfo = await checkForUpdates(targetDir);
|
|
89
|
+
if (!updateInfo.hasUpdates) {
|
|
90
|
+
console.log(` Already up to date (v${updateInfo.currentVersion})\n`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
formatUpdateSummary({ ...updateInfo, results: updateInfo.changes }, true);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
73
96
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
97
|
+
console.log(' Updating...\n');
|
|
98
|
+
const updateResult = await performUpdate(targetDir, { force, checkOnly });
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (entry.isDirectory()) {
|
|
84
|
-
copyDir(srcPath, destPath);
|
|
85
|
-
} else {
|
|
86
|
-
fs.copyFileSync(srcPath, destPath);
|
|
87
|
-
}
|
|
100
|
+
if (!updateResult.updated) {
|
|
101
|
+
console.log(` Already up to date (v${updateResult.currentVersion})\n`);
|
|
102
|
+
return;
|
|
88
103
|
}
|
|
104
|
+
|
|
105
|
+
formatUpdateSummary(updateResult, false);
|
|
89
106
|
}
|
package/src/merge.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
10
|
+
|
|
11
|
+
export const FILE_STATE = {
|
|
12
|
+
NEW: 'new',
|
|
13
|
+
UNMODIFIED: 'unmodified',
|
|
14
|
+
MODIFIED: 'modified',
|
|
15
|
+
DELETED: 'deleted',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const FILE_CATEGORY = {
|
|
19
|
+
XP_DATA: 'xp_data',
|
|
20
|
+
MEMORIES: 'memories',
|
|
21
|
+
KNOWLEDGE: 'knowledge',
|
|
22
|
+
SKILL: 'skill',
|
|
23
|
+
AGENT: 'agent',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const PROTECTED_CATEGORIES = [FILE_CATEGORY.XP_DATA, FILE_CATEGORY.MEMORIES];
|
|
27
|
+
const MERGE_CATEGORIES = [FILE_CATEGORY.KNOWLEDGE];
|
|
28
|
+
|
|
29
|
+
function hashFile(filePath) {
|
|
30
|
+
if (!fs.existsSync(filePath)) return null;
|
|
31
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
32
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getCategory(relativePath) {
|
|
36
|
+
const pathLower = relativePath.toLowerCase();
|
|
37
|
+
|
|
38
|
+
if (pathLower.includes('/xp.json') || pathLower.endsWith('xp.json')) {
|
|
39
|
+
return FILE_CATEGORY.XP_DATA;
|
|
40
|
+
}
|
|
41
|
+
if (pathLower.includes('/memories/') || pathLower.includes('memories/')) {
|
|
42
|
+
return FILE_CATEGORY.MEMORIES;
|
|
43
|
+
}
|
|
44
|
+
if (pathLower.includes('/knowledge.md')) {
|
|
45
|
+
return FILE_CATEGORY.KNOWLEDGE;
|
|
46
|
+
}
|
|
47
|
+
if (pathLower.includes('/skills/')) {
|
|
48
|
+
return FILE_CATEGORY.SKILL;
|
|
49
|
+
}
|
|
50
|
+
if (pathLower.includes('/agents/')) {
|
|
51
|
+
return FILE_CATEGORY.AGENT;
|
|
52
|
+
}
|
|
53
|
+
return FILE_CATEGORY.SKILL;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function compareVersion(v1, v2) {
|
|
57
|
+
const parts1 = v1.split('.').map(Number);
|
|
58
|
+
const parts2 = v2.split('.').map(Number);
|
|
59
|
+
for (let i = 0; i < 3; i++) {
|
|
60
|
+
if (parts1[i] > parts2[i]) return 1;
|
|
61
|
+
if (parts1[i] < parts2[i]) return -1;
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getAllTemplateFiles(srcDir, baseDir = '') {
|
|
67
|
+
const files = [];
|
|
68
|
+
if (!fs.existsSync(srcDir)) return files;
|
|
69
|
+
|
|
70
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.name === '.git') continue;
|
|
73
|
+
|
|
74
|
+
const relativePath = path.join(baseDir, entry.name);
|
|
75
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
76
|
+
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
files.push(...getAllTemplateFiles(srcPath, relativePath));
|
|
79
|
+
} else {
|
|
80
|
+
files.push(relativePath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return files;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function findNewSections(oldContent, newContent) {
|
|
87
|
+
const newLines = newContent.split('\n');
|
|
88
|
+
const oldLines = oldContent.split('\n');
|
|
89
|
+
const newSections = [];
|
|
90
|
+
|
|
91
|
+
let inNewSection = false;
|
|
92
|
+
let newSectionStart = -1;
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
95
|
+
const line = newLines[i];
|
|
96
|
+
const isHeader = /^#{1,3}\s+/.test(line);
|
|
97
|
+
|
|
98
|
+
if (isHeader && !oldContent.includes(line)) {
|
|
99
|
+
if (inNewSection && newSectionStart > -1) {
|
|
100
|
+
newSections.push(newLines.slice(newSectionStart, i).join('\n'));
|
|
101
|
+
}
|
|
102
|
+
newSectionStart = i;
|
|
103
|
+
inNewSection = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (inNewSection && newSectionStart > -1) {
|
|
108
|
+
newSections.push(newLines.slice(newSectionStart).join('\n'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return newSections;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function mergeKnowledgeFiles(userContent, templateContent) {
|
|
115
|
+
const newSections = findNewSections(userContent, templateContent);
|
|
116
|
+
|
|
117
|
+
if (newSections.length === 0) {
|
|
118
|
+
return { action: 'unchanged', content: userContent };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let merged = userContent;
|
|
122
|
+
|
|
123
|
+
for (const section of newSections) {
|
|
124
|
+
merged += '\n\n---\n\n' + section;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { action: 'merged', content: merged };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function checkForUpdates(targetDir) {
|
|
131
|
+
const versionFile = path.join(targetDir, '.ocs-version');
|
|
132
|
+
const currentVersion = fs.existsSync(versionFile)
|
|
133
|
+
? fs.readFileSync(versionFile, 'utf-8').trim()
|
|
134
|
+
: '0.0.0';
|
|
135
|
+
|
|
136
|
+
const templateVersion = fs.readFileSync(path.join(TEMPLATES_DIR, '.ocs-version'), 'utf-8').trim();
|
|
137
|
+
|
|
138
|
+
const comparison = compareVersion(templateVersion, currentVersion);
|
|
139
|
+
|
|
140
|
+
if (comparison <= 0) {
|
|
141
|
+
return {
|
|
142
|
+
hasUpdates: false,
|
|
143
|
+
currentVersion,
|
|
144
|
+
templateVersion,
|
|
145
|
+
changes: []
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const templateFiles = getAllTemplateFiles(TEMPLATES_DIR);
|
|
150
|
+
const changes = [];
|
|
151
|
+
|
|
152
|
+
for (const relativePath of templateFiles) {
|
|
153
|
+
const templatePath = path.join(TEMPLATES_DIR, relativePath);
|
|
154
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
155
|
+
|
|
156
|
+
const templateHash = hashFile(templatePath);
|
|
157
|
+
const targetHash = hashFile(targetPath);
|
|
158
|
+
const category = getCategory(relativePath);
|
|
159
|
+
|
|
160
|
+
let state;
|
|
161
|
+
if (!targetHash) {
|
|
162
|
+
state = FILE_STATE.NEW;
|
|
163
|
+
} else if (templateHash === targetHash) {
|
|
164
|
+
state = FILE_STATE.UNMODIFIED;
|
|
165
|
+
} else {
|
|
166
|
+
state = FILE_STATE.MODIFIED;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
changes.push({
|
|
170
|
+
relativePath,
|
|
171
|
+
category,
|
|
172
|
+
state,
|
|
173
|
+
templateHash,
|
|
174
|
+
targetHash
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
hasUpdates: comparison > 0,
|
|
180
|
+
currentVersion,
|
|
181
|
+
templateVersion,
|
|
182
|
+
changes
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function performUpdate(targetDir, options = {}) {
|
|
187
|
+
const { force = false, checkOnly = false } = options;
|
|
188
|
+
|
|
189
|
+
const versionFile = path.join(targetDir, '.ocs-version');
|
|
190
|
+
const currentVersion = fs.existsSync(versionFile)
|
|
191
|
+
? fs.readFileSync(versionFile, 'utf-8').trim()
|
|
192
|
+
: '0.0.0';
|
|
193
|
+
|
|
194
|
+
const templateVersion = fs.readFileSync(path.join(TEMPLATES_DIR, '.ocs-version'), 'utf-8').trim();
|
|
195
|
+
|
|
196
|
+
if (compareVersion(templateVersion, currentVersion) <= 0 && !force) {
|
|
197
|
+
console.log(` Already up to date (v${currentVersion})\n`);
|
|
198
|
+
return { updated: false, changes: [] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const templateFiles = getAllTemplateFiles(TEMPLATES_DIR);
|
|
202
|
+
|
|
203
|
+
const results = {
|
|
204
|
+
updated: [],
|
|
205
|
+
added: [],
|
|
206
|
+
conflicts: [],
|
|
207
|
+
skipped: [],
|
|
208
|
+
merged: []
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
for (const relativePath of templateFiles) {
|
|
212
|
+
const templatePath = path.join(TEMPLATES_DIR, relativePath);
|
|
213
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
214
|
+
const category = getCategory(relativePath);
|
|
215
|
+
|
|
216
|
+
const isVersionFile = relativePath === '.ocs-version';
|
|
217
|
+
const templateHash = hashFile(templatePath);
|
|
218
|
+
const targetHash = hashFile(targetPath);
|
|
219
|
+
|
|
220
|
+
if (!targetHash) {
|
|
221
|
+
if (!checkOnly) {
|
|
222
|
+
const destDir = path.dirname(targetPath);
|
|
223
|
+
if (!fs.existsSync(destDir)) {
|
|
224
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
225
|
+
}
|
|
226
|
+
fs.copyFileSync(templatePath, targetPath);
|
|
227
|
+
}
|
|
228
|
+
results.added.push(relativePath);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (isVersionFile) {
|
|
233
|
+
if (!checkOnly) {
|
|
234
|
+
fs.copyFileSync(templatePath, targetPath);
|
|
235
|
+
}
|
|
236
|
+
results.updated.push(relativePath);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (PROTECTED_CATEGORIES.includes(category)) {
|
|
241
|
+
results.skipped.push({ path: relativePath, reason: 'protected (user data)' });
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (templateHash === targetHash) {
|
|
246
|
+
if (!checkOnly) {
|
|
247
|
+
fs.copyFileSync(templatePath, targetPath);
|
|
248
|
+
}
|
|
249
|
+
results.updated.push(relativePath);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (MERGE_CATEGORIES.includes(category)) {
|
|
254
|
+
const userContent = fs.readFileSync(targetPath, 'utf-8');
|
|
255
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
256
|
+
|
|
257
|
+
const mergeResult = mergeKnowledgeFiles(userContent, templateContent);
|
|
258
|
+
|
|
259
|
+
if (mergeResult.action === 'merged' && !checkOnly) {
|
|
260
|
+
fs.writeFileSync(targetPath, mergeResult.content, 'utf-8');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
results.merged.push({ path: relativePath, sectionsAdded: mergeResult.action === 'merged' ? 1 : 0 });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (checkOnly) {
|
|
268
|
+
results.conflicts.push({ path: relativePath, category });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
results.conflicts.push({ path: relativePath, category, needsPrompt: true });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!checkOnly) {
|
|
276
|
+
fs.writeFileSync(versionFile, templateVersion, 'utf-8');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
updated: true,
|
|
281
|
+
currentVersion,
|
|
282
|
+
templateVersion,
|
|
283
|
+
results
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function formatUpdateSummary(updateResult, checkOnly = false) {
|
|
288
|
+
const { results, templateVersion } = updateResult;
|
|
289
|
+
|
|
290
|
+
console.log('');
|
|
291
|
+
|
|
292
|
+
if (checkOnly) {
|
|
293
|
+
console.log(` Template version: v${templateVersion}`);
|
|
294
|
+
console.log(` Status: ${updateResult.hasUpdates ? 'Update available' : 'Up to date'}`);
|
|
295
|
+
} else {
|
|
296
|
+
console.log(` Updated to v${templateVersion}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (results.added.length > 0) {
|
|
300
|
+
console.log(`\n Added (${results.added.length}):`);
|
|
301
|
+
for (const file of results.added.slice(0, 5)) {
|
|
302
|
+
console.log(` + ${file}`);
|
|
303
|
+
}
|
|
304
|
+
if (results.added.length > 5) {
|
|
305
|
+
console.log(` ... and ${results.added.length - 5} more`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (results.updated.length > 0) {
|
|
310
|
+
console.log(`\n Updated (${results.updated.length}):`);
|
|
311
|
+
for (const file of results.updated.slice(0, 5)) {
|
|
312
|
+
console.log(` ~ ${file}`);
|
|
313
|
+
}
|
|
314
|
+
if (results.updated.length > 5) {
|
|
315
|
+
console.log(` ... and ${results.updated.length - 5} more`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (results.merged.length > 0) {
|
|
320
|
+
console.log(`\n Merged (${results.merged.length}):`);
|
|
321
|
+
for (const file of results.merged.slice(0, 3)) {
|
|
322
|
+
console.log(` ◊ ${file.path}`);
|
|
323
|
+
}
|
|
324
|
+
if (results.merged.length > 3) {
|
|
325
|
+
console.log(` ... and ${results.merged.length - 3} more`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (results.skipped.length > 0) {
|
|
330
|
+
console.log(`\n Skipped - protected (${results.skipped.length}):`);
|
|
331
|
+
for (const file of results.skipped.slice(0, 3)) {
|
|
332
|
+
console.log(` ⊘ ${file.path} (${file.reason})`);
|
|
333
|
+
}
|
|
334
|
+
if (results.skipped.length > 3) {
|
|
335
|
+
console.log(` ... and ${results.skipped.length - 3} more`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (results.conflicts.length > 0) {
|
|
340
|
+
console.log(`\n Conflicts (${results.conflicts.length}):`);
|
|
341
|
+
for (const file of results.conflicts.slice(0, 5)) {
|
|
342
|
+
console.log(` ⚠ ${file.path}`);
|
|
343
|
+
}
|
|
344
|
+
if (results.conflicts.length > 5) {
|
|
345
|
+
console.log(` ... and ${results.conflicts.length - 5} more`);
|
|
346
|
+
}
|
|
347
|
+
console.log(`\n Run without --check to resolve conflicts`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
console.log('');
|
|
351
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.1.4
|