jun-claude-code 0.6.3 → 0.6.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/dist/__tests__/metadata.test.d.ts +1 -0
- package/dist/__tests__/metadata.test.js +178 -0
- package/dist/__tests__/update.test.d.ts +1 -0
- package/dist/__tests__/update.test.js +154 -0
- package/dist/cli.js +25 -0
- package/dist/copy.d.ts +36 -0
- package/dist/copy.js +26 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/metadata.d.ts +29 -0
- package/dist/metadata.js +112 -0
- package/dist/update.d.ts +16 -0
- package/dist/update.js +433 -0
- package/package.json +1 -1
- package/templates/global/skills/Backend/SKILL.md +3 -76
- package/templates/global/skills/TypeORM/SKILL.md +189 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const metadata_1 = require("../metadata");
|
|
42
|
+
function createTmpDir() {
|
|
43
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'metadata-test-'));
|
|
44
|
+
}
|
|
45
|
+
function cleanupDir(dir) {
|
|
46
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
function writeFile(dir, relativePath, content) {
|
|
49
|
+
const filePath = path.join(dir, relativePath);
|
|
50
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
function hashContent(content) {
|
|
54
|
+
return crypto.createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
55
|
+
}
|
|
56
|
+
(0, vitest_1.describe)('metadata', () => {
|
|
57
|
+
let tmpDir;
|
|
58
|
+
(0, vitest_1.beforeEach)(() => {
|
|
59
|
+
tmpDir = createTmpDir();
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.afterEach)(() => {
|
|
62
|
+
cleanupDir(tmpDir);
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.describe)('getMetadataPath', () => {
|
|
65
|
+
(0, vitest_1.it)('should return correct path', () => {
|
|
66
|
+
(0, vitest_1.expect)((0, metadata_1.getMetadataPath)('/home/user/.claude')).toBe('/home/user/.claude/.jun-installed.json');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.describe)('loadMetadata', () => {
|
|
70
|
+
(0, vitest_1.it)('should return null when file does not exist', () => {
|
|
71
|
+
(0, vitest_1.expect)((0, metadata_1.loadMetadata)(tmpDir)).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)('should return null when file is invalid JSON', () => {
|
|
74
|
+
fs.writeFileSync(path.join(tmpDir, '.jun-installed.json'), 'not json', 'utf-8');
|
|
75
|
+
(0, vitest_1.expect)((0, metadata_1.loadMetadata)(tmpDir)).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)('should return metadata when valid', () => {
|
|
78
|
+
const meta = {
|
|
79
|
+
version: '1.0.0',
|
|
80
|
+
installedAt: '2025-01-01T00:00:00.000Z',
|
|
81
|
+
files: { 'agents/test.md': { hash: 'abc123' } },
|
|
82
|
+
};
|
|
83
|
+
fs.writeFileSync(path.join(tmpDir, '.jun-installed.json'), JSON.stringify(meta), 'utf-8');
|
|
84
|
+
(0, vitest_1.expect)((0, metadata_1.loadMetadata)(tmpDir)).toEqual(meta);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
(0, vitest_1.describe)('saveMetadata', () => {
|
|
88
|
+
(0, vitest_1.it)('should write metadata as formatted JSON', () => {
|
|
89
|
+
const meta = {
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
installedAt: '2025-01-01T00:00:00.000Z',
|
|
92
|
+
files: { 'agents/test.md': { hash: 'abc123' } },
|
|
93
|
+
};
|
|
94
|
+
(0, metadata_1.saveMetadata)(tmpDir, meta);
|
|
95
|
+
const content = fs.readFileSync(path.join(tmpDir, '.jun-installed.json'), 'utf-8');
|
|
96
|
+
(0, vitest_1.expect)(JSON.parse(content)).toEqual(meta);
|
|
97
|
+
(0, vitest_1.expect)(content.endsWith('\n')).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.describe)('buildMetadata', () => {
|
|
101
|
+
(0, vitest_1.it)('should create metadata with correct hashes', () => {
|
|
102
|
+
const sourceDir = createTmpDir();
|
|
103
|
+
try {
|
|
104
|
+
writeFile(sourceDir, 'agents/test.md', 'hello');
|
|
105
|
+
writeFile(sourceDir, 'skills/Git/SKILL.md', 'world');
|
|
106
|
+
const meta = (0, metadata_1.buildMetadata)(['agents/test.md', 'skills/Git/SKILL.md'], sourceDir, '1.0.0');
|
|
107
|
+
(0, vitest_1.expect)(meta.version).toBe('1.0.0');
|
|
108
|
+
(0, vitest_1.expect)(meta.files['agents/test.md'].hash).toBe(hashContent('hello'));
|
|
109
|
+
(0, vitest_1.expect)(meta.files['skills/Git/SKILL.md'].hash).toBe(hashContent('world'));
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
cleanupDir(sourceDir);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
(0, vitest_1.it)('should skip non-existent files', () => {
|
|
116
|
+
const sourceDir = createTmpDir();
|
|
117
|
+
try {
|
|
118
|
+
writeFile(sourceDir, 'agents/test.md', 'hello');
|
|
119
|
+
const meta = (0, metadata_1.buildMetadata)(['agents/test.md', 'agents/missing.md'], sourceDir, '1.0.0');
|
|
120
|
+
(0, vitest_1.expect)(Object.keys(meta.files)).toHaveLength(1);
|
|
121
|
+
(0, vitest_1.expect)(meta.files['agents/test.md']).toBeDefined();
|
|
122
|
+
(0, vitest_1.expect)(meta.files['agents/missing.md']).toBeUndefined();
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
cleanupDir(sourceDir);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.describe)('mergeMetadata', () => {
|
|
130
|
+
(0, vitest_1.it)('should create new metadata when existing is null', () => {
|
|
131
|
+
const sourceDir = createTmpDir();
|
|
132
|
+
try {
|
|
133
|
+
writeFile(sourceDir, 'agents/test.md', 'hello');
|
|
134
|
+
const meta = (0, metadata_1.mergeMetadata)(null, ['agents/test.md'], sourceDir, '1.0.0');
|
|
135
|
+
(0, vitest_1.expect)(meta.version).toBe('1.0.0');
|
|
136
|
+
(0, vitest_1.expect)(meta.files['agents/test.md'].hash).toBe(hashContent('hello'));
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
cleanupDir(sourceDir);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)('should preserve existing entries and add new ones', () => {
|
|
143
|
+
const sourceDir = createTmpDir();
|
|
144
|
+
try {
|
|
145
|
+
writeFile(sourceDir, 'agents/new.md', 'new content');
|
|
146
|
+
const existing = {
|
|
147
|
+
version: '0.9.0',
|
|
148
|
+
installedAt: '2025-01-01T00:00:00.000Z',
|
|
149
|
+
files: { 'agents/old.md': { hash: 'oldhash' } },
|
|
150
|
+
};
|
|
151
|
+
const meta = (0, metadata_1.mergeMetadata)(existing, ['agents/new.md'], sourceDir, '1.0.0');
|
|
152
|
+
(0, vitest_1.expect)(meta.version).toBe('1.0.0');
|
|
153
|
+
(0, vitest_1.expect)(meta.files['agents/old.md'].hash).toBe('oldhash');
|
|
154
|
+
(0, vitest_1.expect)(meta.files['agents/new.md'].hash).toBe(hashContent('new content'));
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
cleanupDir(sourceDir);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
(0, vitest_1.it)('should override existing entries for the same file', () => {
|
|
161
|
+
const sourceDir = createTmpDir();
|
|
162
|
+
try {
|
|
163
|
+
writeFile(sourceDir, 'agents/test.md', 'updated content');
|
|
164
|
+
const existing = {
|
|
165
|
+
version: '0.9.0',
|
|
166
|
+
installedAt: '2025-01-01T00:00:00.000Z',
|
|
167
|
+
files: { 'agents/test.md': { hash: 'oldhash' } },
|
|
168
|
+
};
|
|
169
|
+
const meta = (0, metadata_1.mergeMetadata)(existing, ['agents/test.md'], sourceDir, '1.0.0');
|
|
170
|
+
(0, vitest_1.expect)(meta.files['agents/test.md'].hash).toBe(hashContent('updated content'));
|
|
171
|
+
(0, vitest_1.expect)(meta.files['agents/test.md'].hash).not.toBe('oldhash');
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
cleanupDir(sourceDir);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const update_1 = require("../update");
|
|
42
|
+
function createTmpDir() {
|
|
43
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'update-test-'));
|
|
44
|
+
}
|
|
45
|
+
function cleanupDir(dir) {
|
|
46
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
function writeFile(dir, relativePath, content) {
|
|
49
|
+
const filePath = path.join(dir, relativePath);
|
|
50
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
function hashContent(content) {
|
|
54
|
+
return crypto.createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
55
|
+
}
|
|
56
|
+
function createMetadata(files, version = '1.0.0') {
|
|
57
|
+
const fileEntries = {};
|
|
58
|
+
for (const [name, content] of Object.entries(files)) {
|
|
59
|
+
fileEntries[name] = { hash: hashContent(content) };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
version,
|
|
63
|
+
installedAt: '2025-01-01T00:00:00.000Z',
|
|
64
|
+
files: fileEntries,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
(0, vitest_1.describe)('computeUpdateStatus', () => {
|
|
68
|
+
let sourceDir;
|
|
69
|
+
let destDir;
|
|
70
|
+
(0, vitest_1.beforeEach)(() => {
|
|
71
|
+
sourceDir = createTmpDir();
|
|
72
|
+
destDir = createTmpDir();
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.afterEach)(() => {
|
|
75
|
+
cleanupDir(sourceDir);
|
|
76
|
+
cleanupDir(destDir);
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.it)('should return new-file when no base and no current file exists', () => {
|
|
79
|
+
writeFile(sourceDir, 'agents/new.md', 'template content');
|
|
80
|
+
const status = (0, update_1.computeUpdateStatus)('agents/new.md', sourceDir, destDir, null);
|
|
81
|
+
(0, vitest_1.expect)(status).toBe('new-file');
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)('should return new-file when base exists but user deleted file', () => {
|
|
84
|
+
writeFile(sourceDir, 'agents/deleted.md', 'template content');
|
|
85
|
+
const metadata = createMetadata({ 'agents/deleted.md': 'old content' });
|
|
86
|
+
const status = (0, update_1.computeUpdateStatus)('agents/deleted.md', sourceDir, destDir, metadata);
|
|
87
|
+
(0, vitest_1.expect)(status).toBe('new-file');
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)('should return unchanged when no base but current matches new template', () => {
|
|
90
|
+
const content = 'same content';
|
|
91
|
+
writeFile(sourceDir, 'agents/test.md', content);
|
|
92
|
+
writeFile(destDir, 'agents/test.md', content);
|
|
93
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, null);
|
|
94
|
+
(0, vitest_1.expect)(status).toBe('unchanged');
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)('should return user-modified for legacy file (no base, current differs from new)', () => {
|
|
97
|
+
writeFile(sourceDir, 'agents/test.md', 'new template');
|
|
98
|
+
writeFile(destDir, 'agents/test.md', 'user customized');
|
|
99
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, null);
|
|
100
|
+
(0, vitest_1.expect)(status).toBe('user-modified');
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)('should return unchanged when all three hashes match', () => {
|
|
103
|
+
const content = 'same for all';
|
|
104
|
+
writeFile(sourceDir, 'agents/test.md', content);
|
|
105
|
+
writeFile(destDir, 'agents/test.md', content);
|
|
106
|
+
const metadata = createMetadata({ 'agents/test.md': content });
|
|
107
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, metadata);
|
|
108
|
+
(0, vitest_1.expect)(status).toBe('unchanged');
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)('should return update-available when base==current but new is different', () => {
|
|
111
|
+
const originalContent = 'original';
|
|
112
|
+
const newContent = 'updated template';
|
|
113
|
+
writeFile(sourceDir, 'agents/test.md', newContent);
|
|
114
|
+
writeFile(destDir, 'agents/test.md', originalContent);
|
|
115
|
+
const metadata = createMetadata({ 'agents/test.md': originalContent });
|
|
116
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, metadata);
|
|
117
|
+
(0, vitest_1.expect)(status).toBe('update-available');
|
|
118
|
+
});
|
|
119
|
+
(0, vitest_1.it)('should return user-modified when base!=current but base==new', () => {
|
|
120
|
+
const originalContent = 'original';
|
|
121
|
+
const userContent = 'user changed this';
|
|
122
|
+
writeFile(sourceDir, 'agents/test.md', originalContent);
|
|
123
|
+
writeFile(destDir, 'agents/test.md', userContent);
|
|
124
|
+
const metadata = createMetadata({ 'agents/test.md': originalContent });
|
|
125
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, metadata);
|
|
126
|
+
(0, vitest_1.expect)(status).toBe('user-modified');
|
|
127
|
+
});
|
|
128
|
+
(0, vitest_1.it)('should return conflict when base!=current and base!=new', () => {
|
|
129
|
+
const originalContent = 'original';
|
|
130
|
+
const userContent = 'user changed this';
|
|
131
|
+
const newContent = 'template also changed';
|
|
132
|
+
writeFile(sourceDir, 'agents/test.md', newContent);
|
|
133
|
+
writeFile(destDir, 'agents/test.md', userContent);
|
|
134
|
+
const metadata = createMetadata({ 'agents/test.md': originalContent });
|
|
135
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, metadata);
|
|
136
|
+
(0, vitest_1.expect)(status).toBe('conflict');
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.it)('should return unchanged when base!=current but current==new (user applied same change)', () => {
|
|
139
|
+
const originalContent = 'original';
|
|
140
|
+
const newContent = 'both changed to same';
|
|
141
|
+
writeFile(sourceDir, 'agents/test.md', newContent);
|
|
142
|
+
writeFile(destDir, 'agents/test.md', newContent);
|
|
143
|
+
const metadata = createMetadata({ 'agents/test.md': originalContent });
|
|
144
|
+
const status = (0, update_1.computeUpdateStatus)('agents/test.md', sourceDir, destDir, metadata);
|
|
145
|
+
// base != current, base != new → conflict (even though current == new)
|
|
146
|
+
(0, vitest_1.expect)(status).toBe('conflict');
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)('should handle metadata with no entry for the file', () => {
|
|
149
|
+
writeFile(sourceDir, 'agents/new.md', 'template content');
|
|
150
|
+
const metadata = createMetadata({ 'agents/other.md': 'other content' });
|
|
151
|
+
const status = (0, update_1.computeUpdateStatus)('agents/new.md', sourceDir, destDir, metadata);
|
|
152
|
+
(0, vitest_1.expect)(status).toBe('new-file');
|
|
153
|
+
});
|
|
154
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -105,6 +105,31 @@ program
|
|
|
105
105
|
process.exit(1);
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
+
program
|
|
109
|
+
.command('update')
|
|
110
|
+
.description('Update installed templates while preserving user customizations')
|
|
111
|
+
.option('-p, --project', 'Update project .claude/')
|
|
112
|
+
.option('-f, --force', 'Overwrite all including user-modified')
|
|
113
|
+
.option('-d, --dry-run', 'Preview changes without applying')
|
|
114
|
+
.action(async (options) => {
|
|
115
|
+
try {
|
|
116
|
+
const { updateClaudeFiles } = await Promise.resolve().then(() => __importStar(require('./update')));
|
|
117
|
+
await updateClaudeFiles({
|
|
118
|
+
dryRun: options.dryRun,
|
|
119
|
+
force: options.force,
|
|
120
|
+
project: options.project,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
console.error('Error:', error.message);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.error('An unexpected error occurred');
|
|
129
|
+
}
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
108
133
|
program
|
|
109
134
|
.command('validate')
|
|
110
135
|
.description('Validate template directory structure')
|
package/dist/copy.d.ts
CHANGED
|
@@ -3,6 +3,42 @@ export interface CopyOptions {
|
|
|
3
3
|
force?: boolean;
|
|
4
4
|
project?: boolean;
|
|
5
5
|
}
|
|
6
|
+
export type FileStatus = 'new' | 'changed' | 'unchanged';
|
|
7
|
+
export interface CategorizedFiles {
|
|
8
|
+
agents: string[];
|
|
9
|
+
skills: string[];
|
|
10
|
+
others: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const EXCLUDE_ALWAYS: string[];
|
|
13
|
+
export declare const EXCLUDE_FROM_PROJECT: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Calculate SHA-256 hash of a file
|
|
16
|
+
*/
|
|
17
|
+
export declare function getFileHash(filePath: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get all files recursively from a directory
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAllFiles(dirPath: string, basePath?: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Ensure directory exists
|
|
24
|
+
*/
|
|
25
|
+
export declare function ensureDir(dirPath: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Copy a single file
|
|
28
|
+
*/
|
|
29
|
+
export declare function copyFile(src: string, dest: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Get the source templates/global directory path (from package installation)
|
|
32
|
+
*/
|
|
33
|
+
export declare function getSourceGlobalDir(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Get the destination .claude directory path (user's home)
|
|
36
|
+
*/
|
|
37
|
+
export declare function getDestClaudeDir(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Categorize files into agents, skills (top-level dirs), and others
|
|
40
|
+
*/
|
|
41
|
+
export declare function categorizeFiles(files: string[]): CategorizedFiles;
|
|
6
42
|
/**
|
|
7
43
|
* Merge settings.json from source into destination.
|
|
8
44
|
* Hooks are merged per event key; duplicate hook entries (by deep equality) are skipped.
|
package/dist/copy.js
CHANGED
|
@@ -36,14 +36,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.EXCLUDE_FROM_PROJECT = exports.EXCLUDE_ALWAYS = void 0;
|
|
40
|
+
exports.getFileHash = getFileHash;
|
|
41
|
+
exports.getAllFiles = getAllFiles;
|
|
42
|
+
exports.ensureDir = ensureDir;
|
|
43
|
+
exports.copyFile = copyFile;
|
|
44
|
+
exports.getSourceGlobalDir = getSourceGlobalDir;
|
|
45
|
+
exports.getDestClaudeDir = getDestClaudeDir;
|
|
46
|
+
exports.categorizeFiles = categorizeFiles;
|
|
39
47
|
exports.mergeSettingsJson = mergeSettingsJson;
|
|
40
48
|
exports.copyClaudeFiles = copyClaudeFiles;
|
|
41
49
|
const fs = __importStar(require("fs"));
|
|
42
50
|
const path = __importStar(require("path"));
|
|
43
51
|
const crypto = __importStar(require("crypto"));
|
|
44
52
|
const chalk_1 = __importDefault(require("chalk"));
|
|
53
|
+
const metadata_1 = require("./metadata");
|
|
45
54
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
46
55
|
const { MultiSelect } = require('enquirer');
|
|
56
|
+
// Files to exclude from all copies (merge-handled separately)
|
|
57
|
+
exports.EXCLUDE_ALWAYS = [
|
|
58
|
+
'settings.json',
|
|
59
|
+
'statusline-command.sh',
|
|
60
|
+
];
|
|
61
|
+
// Files to exclude only when installing to project
|
|
62
|
+
exports.EXCLUDE_FROM_PROJECT = [
|
|
63
|
+
'hooks/_dedup.sh',
|
|
64
|
+
];
|
|
47
65
|
/**
|
|
48
66
|
* Calculate SHA-256 hash of a file
|
|
49
67
|
*/
|
|
@@ -387,21 +405,12 @@ async function copyClaudeFiles(options = {}) {
|
|
|
387
405
|
console.error(chalk_1.default.red('Error:'), 'Source templates/global directory not found');
|
|
388
406
|
process.exit(1);
|
|
389
407
|
}
|
|
390
|
-
// Files to exclude from all copies (merge-handled separately)
|
|
391
|
-
const EXCLUDE_ALWAYS = [
|
|
392
|
-
'settings.json',
|
|
393
|
-
'statusline-command.sh',
|
|
394
|
-
];
|
|
395
|
-
// Files to exclude only when installing to project
|
|
396
|
-
const EXCLUDE_FROM_PROJECT = [
|
|
397
|
-
'hooks/_dedup.sh',
|
|
398
|
-
];
|
|
399
408
|
// Get all files to copy
|
|
400
409
|
const allFiles = getAllFiles(sourceDir);
|
|
401
410
|
const files = allFiles.filter((file) => {
|
|
402
|
-
if (EXCLUDE_ALWAYS.includes(file))
|
|
411
|
+
if (exports.EXCLUDE_ALWAYS.includes(file))
|
|
403
412
|
return false;
|
|
404
|
-
if (project && EXCLUDE_FROM_PROJECT.includes(file))
|
|
413
|
+
if (project && exports.EXCLUDE_FROM_PROJECT.includes(file))
|
|
405
414
|
return false;
|
|
406
415
|
return true;
|
|
407
416
|
});
|
|
@@ -524,6 +533,12 @@ async function copyClaudeFiles(options = {}) {
|
|
|
524
533
|
const skippedCount = files.length - copiedCount;
|
|
525
534
|
// Merge settings.json (hooks are merged, not overwritten)
|
|
526
535
|
mergeSettingsJson(sourceDir, destDir, { project });
|
|
536
|
+
// Save installation metadata
|
|
537
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
538
|
+
const currentVersion = require('../package.json').version;
|
|
539
|
+
const existingMeta = (0, metadata_1.loadMetadata)(destDir);
|
|
540
|
+
const updatedMeta = (0, metadata_1.mergeMetadata)(existingMeta, allFilesToCopy, sourceDir, currentVersion);
|
|
541
|
+
(0, metadata_1.saveMetadata)(destDir, updatedMeta);
|
|
527
542
|
console.log();
|
|
528
543
|
console.log(chalk_1.default.green(`Done! Copied ${copiedCount} files, skipped ${skippedCount} files.`));
|
|
529
544
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.initContext = exports.initProject = exports.copyClaudeFiles = void 0;
|
|
3
|
+
exports.initContext = exports.initProject = exports.updateClaudeFiles = exports.copyClaudeFiles = void 0;
|
|
4
4
|
var copy_1 = require("./copy");
|
|
5
5
|
Object.defineProperty(exports, "copyClaudeFiles", { enumerable: true, get: function () { return copy_1.copyClaudeFiles; } });
|
|
6
|
+
var update_1 = require("./update");
|
|
7
|
+
Object.defineProperty(exports, "updateClaudeFiles", { enumerable: true, get: function () { return update_1.updateClaudeFiles; } });
|
|
6
8
|
var init_project_1 = require("./init-project");
|
|
7
9
|
Object.defineProperty(exports, "initProject", { enumerable: true, get: function () { return init_project_1.initProject; } });
|
|
8
10
|
var init_context_1 = require("./init-context");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface FileMetadata {
|
|
2
|
+
hash: string;
|
|
3
|
+
}
|
|
4
|
+
export interface InstalledMetadata {
|
|
5
|
+
version: string;
|
|
6
|
+
installedAt: string;
|
|
7
|
+
files: Record<string, FileMetadata>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get metadata file path for a destination directory
|
|
11
|
+
*/
|
|
12
|
+
export declare function getMetadataPath(destDir: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Load installation metadata. Returns null if not found or invalid.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadMetadata(destDir: string): InstalledMetadata | null;
|
|
17
|
+
/**
|
|
18
|
+
* Save installation metadata
|
|
19
|
+
*/
|
|
20
|
+
export declare function saveMetadata(destDir: string, metadata: InstalledMetadata): void;
|
|
21
|
+
/**
|
|
22
|
+
* Build metadata from a list of files in the source directory
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildMetadata(files: string[], sourceDir: string, version: string): InstalledMetadata;
|
|
25
|
+
/**
|
|
26
|
+
* Merge new file entries into existing metadata.
|
|
27
|
+
* Existing entries are preserved; new entries override matching keys.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeMetadata(existing: InstalledMetadata | null, newFiles: string[], sourceDir: string, version: string): InstalledMetadata;
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getMetadataPath = getMetadataPath;
|
|
37
|
+
exports.loadMetadata = loadMetadata;
|
|
38
|
+
exports.saveMetadata = saveMetadata;
|
|
39
|
+
exports.buildMetadata = buildMetadata;
|
|
40
|
+
exports.mergeMetadata = mergeMetadata;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const crypto = __importStar(require("crypto"));
|
|
44
|
+
const METADATA_FILENAME = '.jun-installed.json';
|
|
45
|
+
/**
|
|
46
|
+
* Compute SHA-256 hash of a file
|
|
47
|
+
*/
|
|
48
|
+
function computeFileHash(filePath) {
|
|
49
|
+
const content = fs.readFileSync(filePath);
|
|
50
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get metadata file path for a destination directory
|
|
54
|
+
*/
|
|
55
|
+
function getMetadataPath(destDir) {
|
|
56
|
+
return path.join(destDir, METADATA_FILENAME);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load installation metadata. Returns null if not found or invalid.
|
|
60
|
+
*/
|
|
61
|
+
function loadMetadata(destDir) {
|
|
62
|
+
const metaPath = getMetadataPath(destDir);
|
|
63
|
+
if (!fs.existsSync(metaPath))
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Save installation metadata
|
|
74
|
+
*/
|
|
75
|
+
function saveMetadata(destDir, metadata) {
|
|
76
|
+
const metaPath = getMetadataPath(destDir);
|
|
77
|
+
fs.writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build metadata from a list of files in the source directory
|
|
81
|
+
*/
|
|
82
|
+
function buildMetadata(files, sourceDir, version) {
|
|
83
|
+
const fileEntries = {};
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const filePath = path.join(sourceDir, file);
|
|
86
|
+
if (fs.existsSync(filePath)) {
|
|
87
|
+
fileEntries[file] = { hash: computeFileHash(filePath) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
version,
|
|
92
|
+
installedAt: new Date().toISOString(),
|
|
93
|
+
files: fileEntries,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Merge new file entries into existing metadata.
|
|
98
|
+
* Existing entries are preserved; new entries override matching keys.
|
|
99
|
+
*/
|
|
100
|
+
function mergeMetadata(existing, newFiles, sourceDir, version) {
|
|
101
|
+
const newMeta = buildMetadata(newFiles, sourceDir, version);
|
|
102
|
+
if (!existing)
|
|
103
|
+
return newMeta;
|
|
104
|
+
return {
|
|
105
|
+
version,
|
|
106
|
+
installedAt: new Date().toISOString(),
|
|
107
|
+
files: {
|
|
108
|
+
...existing.files,
|
|
109
|
+
...newMeta.files,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InstalledMetadata } from './metadata';
|
|
2
|
+
export type UpdateFileStatus = 'update-available' | 'user-modified' | 'conflict' | 'new-file' | 'unchanged' | 'removed-upstream';
|
|
3
|
+
export interface UpdateOptions {
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
project?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Compute 3-way update status for a file.
|
|
10
|
+
* Compares base (from metadata), current (on disk), and new (from template).
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeUpdateStatus(file: string, sourceDir: string, destDir: string, metadata: InstalledMetadata | null): UpdateFileStatus;
|
|
13
|
+
/**
|
|
14
|
+
* Update installed templates while preserving user customizations
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateClaudeFiles(options?: UpdateOptions): Promise<void>;
|