@slats/claude-assets-sync 0.0.3 → 0.0.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/commands/remove.cjs +7 -7
- package/dist/commands/remove.mjs +1 -1
- package/dist/core/assetStructure.d.ts +1 -1
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/filesystem.cjs +20 -8
- package/dist/core/filesystem.d.ts +3 -2
- package/dist/core/filesystem.mjs +20 -8
- package/dist/core/github.cjs +31 -3
- package/dist/core/github.d.ts +14 -1
- package/dist/core/github.mjs +31 -4
- package/dist/core/migration.cjs +7 -6
- package/dist/core/migration.mjs +2 -1
- package/dist/core/packageScanner.cjs +34 -29
- package/dist/core/packageScanner.mjs +34 -30
- package/dist/core/sync.cjs +30 -29
- package/dist/core/sync.mjs +2 -1
- package/dist/core/syncMeta.d.ts +1 -1
- package/dist/utils/nameTransform.cjs +7 -6
- package/dist/utils/nameTransform.d.ts +22 -33
- package/dist/utils/nameTransform.mjs +8 -6
- package/dist/version.cjs +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +2 -2
- package/CHANGELOG.md +0 -189
package/dist/commands/remove.cjs
CHANGED
|
@@ -6,7 +6,7 @@ var readline = require('node:readline/promises');
|
|
|
6
6
|
var pc = require('picocolors');
|
|
7
7
|
var syncMeta = require('../core/syncMeta.cjs');
|
|
8
8
|
var logger = require('../utils/logger.cjs');
|
|
9
|
-
var
|
|
9
|
+
var packageName = require('../utils/packageName.cjs');
|
|
10
10
|
|
|
11
11
|
function _interopNamespaceDefault(e) {
|
|
12
12
|
var n = Object.create(null);
|
|
@@ -30,17 +30,17 @@ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
|
30
30
|
var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
31
31
|
|
|
32
32
|
const runRemoveCommand = async (options, cwd = process.cwd()) => {
|
|
33
|
-
const { package: packageName, yes, dryRun } = options;
|
|
34
|
-
const prefix =
|
|
33
|
+
const { package: packageName$1, yes, dryRun } = options;
|
|
34
|
+
const prefix = packageName.packageNameToPrefix(packageName$1);
|
|
35
35
|
const meta = syncMeta.readUnifiedSyncMeta(cwd);
|
|
36
36
|
if (!meta || !meta.packages || !meta.packages[prefix]) {
|
|
37
|
-
logger.logger.error(`Package ${packageName} is not synced.`);
|
|
37
|
+
logger.logger.error(`Package ${packageName$1} is not synced.`);
|
|
38
38
|
process.exit(1);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
const packageInfo = meta.packages[prefix];
|
|
42
42
|
if (!packageInfo || !packageInfo.files) {
|
|
43
|
-
logger.logger.error(`Package ${packageName} has no files to remove.`);
|
|
43
|
+
logger.logger.error(`Package ${packageName$1} has no files to remove.`);
|
|
44
44
|
process.exit(1);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -57,7 +57,7 @@ const runRemoveCommand = async (options, cwd = process.cwd()) => {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
|
-
const dirPath = path__namespace.join(cwd, '.claude', assetType, packageName);
|
|
60
|
+
const dirPath = path__namespace.join(cwd, '.claude', assetType, packageName$1);
|
|
61
61
|
filesToRemove.push({ assetType, path: dirPath });
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -103,7 +103,7 @@ const runRemoveCommand = async (options, cwd = process.cwd()) => {
|
|
|
103
103
|
delete meta.packages[prefix];
|
|
104
104
|
meta.syncedAt = new Date().toISOString();
|
|
105
105
|
syncMeta.writeUnifiedSyncMeta(cwd, meta);
|
|
106
|
-
logger.logger.success(`\nRemoved package ${packageName}`);
|
|
106
|
+
logger.logger.success(`\nRemoved package ${packageName$1}`);
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
exports.runRemoveCommand = runRemoveCommand;
|
package/dist/commands/remove.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import * as readline from 'node:readline/promises';
|
|
|
4
4
|
import pc from 'picocolors';
|
|
5
5
|
import { readUnifiedSyncMeta, writeUnifiedSyncMeta } from '../core/syncMeta.mjs';
|
|
6
6
|
import { logger } from '../utils/logger.mjs';
|
|
7
|
-
import { packageNameToPrefix } from '../utils/
|
|
7
|
+
import { packageNameToPrefix } from '../utils/packageName.mjs';
|
|
8
8
|
|
|
9
9
|
const runRemoveCommand = async (options, cwd = process.cwd()) => {
|
|
10
10
|
const { package: packageName, yes, dryRun } = options;
|
package/dist/core/constants.d.ts
CHANGED
package/dist/core/filesystem.cjs
CHANGED
|
@@ -58,24 +58,36 @@ const cleanFlatAssetFiles = (cwd, assetType, prefix, existingMeta) => {
|
|
|
58
58
|
const packageInfo = existingMeta.packages[prefix];
|
|
59
59
|
const filesToRemove = packageInfo.files[assetType];
|
|
60
60
|
if (Array.isArray(filesToRemove)) {
|
|
61
|
+
const skillDirs = new Set();
|
|
61
62
|
for (const fileMapping of filesToRemove) {
|
|
62
63
|
const fileName = typeof fileMapping === 'string'
|
|
63
64
|
? fileMapping
|
|
64
65
|
: fileMapping.transformed;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
if (fileName.includes('/')) {
|
|
67
|
+
skillDirs.add(fileName.split('/')[0]);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const filePath = path.join(destDir, fileName);
|
|
71
|
+
if (io.fileExists(filePath)) {
|
|
72
|
+
fs.rmSync(filePath, { force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const dir of skillDirs) {
|
|
77
|
+
const dirPath = path.join(destDir, dir);
|
|
78
|
+
if (io.fileExists(dirPath)) {
|
|
79
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
else {
|
|
73
85
|
const pattern = `${prefix}_`;
|
|
74
|
-
const
|
|
75
|
-
for (const
|
|
76
|
-
if (
|
|
77
|
-
const
|
|
78
|
-
fs.rmSync(
|
|
86
|
+
const entries = io.listDirectory(destDir);
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if (entry.startsWith(pattern)) {
|
|
89
|
+
const entryPath = path.join(destDir, entry);
|
|
90
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetType, SyncMeta, UnifiedSyncMeta } from '../utils/types';
|
|
1
|
+
import type { AssetType, SyncMeta, UnifiedSyncMeta } from '../utils/types.js';
|
|
2
2
|
import { ensureDirectory, writeTextFile } from './io';
|
|
3
3
|
/**
|
|
4
4
|
* Ensure directory exists (creates recursively if needed)
|
|
@@ -71,7 +71,8 @@ export declare const createSyncMeta: (version: string, files: string[]) => SyncM
|
|
|
71
71
|
export declare const writeFlatAssetFile: (cwd: string, assetType: AssetType, flatFileName: string, content: string) => void;
|
|
72
72
|
/**
|
|
73
73
|
* Clean flat asset files with specific prefix
|
|
74
|
-
* Removes only files belonging to the specified package, preserving others
|
|
74
|
+
* Removes only files belonging to the specified package, preserving others.
|
|
75
|
+
* Handles both single flat files (prefix_file.md) and directory-based skills (prefix_dir/).
|
|
75
76
|
* @param cwd - Current working directory
|
|
76
77
|
* @param assetType - Asset type (commands, skills, agents, or any custom string)
|
|
77
78
|
* @param prefix - Package prefix (e.g., "canard-schemaForm")
|
package/dist/core/filesystem.mjs
CHANGED
|
@@ -56,24 +56,36 @@ const cleanFlatAssetFiles = (cwd, assetType, prefix, existingMeta) => {
|
|
|
56
56
|
const packageInfo = existingMeta.packages[prefix];
|
|
57
57
|
const filesToRemove = packageInfo.files[assetType];
|
|
58
58
|
if (Array.isArray(filesToRemove)) {
|
|
59
|
+
const skillDirs = new Set();
|
|
59
60
|
for (const fileMapping of filesToRemove) {
|
|
60
61
|
const fileName = typeof fileMapping === 'string'
|
|
61
62
|
? fileMapping
|
|
62
63
|
: fileMapping.transformed;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if (fileName.includes('/')) {
|
|
65
|
+
skillDirs.add(fileName.split('/')[0]);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const filePath = join(destDir, fileName);
|
|
69
|
+
if (fileExists(filePath)) {
|
|
70
|
+
rmSync(filePath, { force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const dir of skillDirs) {
|
|
75
|
+
const dirPath = join(destDir, dir);
|
|
76
|
+
if (fileExists(dirPath)) {
|
|
77
|
+
rmSync(dirPath, { recursive: true, force: true });
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
else {
|
|
71
83
|
const pattern = `${prefix}_`;
|
|
72
|
-
const
|
|
73
|
-
for (const
|
|
74
|
-
if (
|
|
75
|
-
const
|
|
76
|
-
rmSync(
|
|
84
|
+
const entries = listDirectory(destDir);
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.startsWith(pattern)) {
|
|
87
|
+
const entryPath = join(destDir, entry);
|
|
88
|
+
rmSync(entryPath, { recursive: true, force: true });
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
}
|
package/dist/core/github.cjs
CHANGED
|
@@ -37,17 +37,44 @@ const fetchDirectoryContents = async (repoInfo, path, tag) => {
|
|
|
37
37
|
if (!response.ok)
|
|
38
38
|
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
39
39
|
const data = await response.json();
|
|
40
|
-
return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'))
|
|
40
|
+
return data.filter((entry) => (entry.type === 'file' && entry.name.endsWith('.md')) ||
|
|
41
|
+
entry.type === 'dir');
|
|
42
|
+
};
|
|
43
|
+
const expandDirectoryEntries = async (repoInfo, parentPath, entries, tag, prefix = '') => {
|
|
44
|
+
const result = [];
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
47
|
+
if (entry.type === 'file') {
|
|
48
|
+
result.push({
|
|
49
|
+
...entry,
|
|
50
|
+
name: prefix ? entryPrefix : entry.name,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else if (entry.type === 'dir') {
|
|
54
|
+
const subEntries = await fetchDirectoryContents(repoInfo, `${parentPath}/${entry.name}`, tag);
|
|
55
|
+
if (subEntries) {
|
|
56
|
+
const expanded = await expandDirectoryEntries(repoInfo, `${parentPath}/${entry.name}`, subEntries, tag, entryPrefix);
|
|
57
|
+
result.push(...expanded);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
41
62
|
};
|
|
42
63
|
const fetchAssetFiles = async (repoInfo, assetPath, tag, assetTypes) => {
|
|
43
64
|
const basePath = repoInfo.directory
|
|
44
65
|
? `${repoInfo.directory}/${assetPath}`
|
|
45
66
|
: assetPath;
|
|
46
67
|
const fetchPromises = assetTypes.map((assetType) => fetchDirectoryContents(repoInfo, `${basePath}/${assetType}`, tag));
|
|
47
|
-
const
|
|
68
|
+
const rawResults = await Promise.all(fetchPromises);
|
|
69
|
+
const expandedResults = await Promise.all(rawResults.map((entries, index) => {
|
|
70
|
+
if (!entries)
|
|
71
|
+
return Promise.resolve([]);
|
|
72
|
+
const assetDirPath = `${basePath}/${assetTypes[index]}`;
|
|
73
|
+
return expandDirectoryEntries(repoInfo, assetDirPath, entries, tag);
|
|
74
|
+
}));
|
|
48
75
|
const assetFiles = {};
|
|
49
76
|
assetTypes.forEach((assetType, index) => {
|
|
50
|
-
assetFiles[assetType] =
|
|
77
|
+
assetFiles[assetType] = expandedResults[index] || [];
|
|
51
78
|
});
|
|
52
79
|
return assetFiles;
|
|
53
80
|
};
|
|
@@ -83,5 +110,6 @@ exports.NotFoundError = NotFoundError;
|
|
|
83
110
|
exports.RateLimitError = RateLimitError;
|
|
84
111
|
exports.downloadAssetFiles = downloadAssetFiles;
|
|
85
112
|
exports.downloadFile = downloadFile;
|
|
113
|
+
exports.expandDirectoryEntries = expandDirectoryEntries;
|
|
86
114
|
exports.fetchAssetFiles = fetchAssetFiles;
|
|
87
115
|
exports.fetchDirectoryContents = fetchDirectoryContents;
|
package/dist/core/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetType, GitHubEntry, GitHubRepoInfo } from '../utils/types';
|
|
1
|
+
import type { AssetType, GitHubEntry, GitHubRepoInfo } from '../utils/types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Error thrown when GitHub API rate limit is exceeded
|
|
4
4
|
*/
|
|
@@ -19,6 +19,19 @@ export declare class NotFoundError extends Error {
|
|
|
19
19
|
* @returns Array of GitHubEntry or null if directory doesn't exist
|
|
20
20
|
*/
|
|
21
21
|
export declare const fetchDirectoryContents: (repoInfo: GitHubRepoInfo, path: string, tag: string) => Promise<GitHubEntry[] | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Expand directory entries into flat file entries with recursive traversal.
|
|
24
|
+
* Fetches contents of each directory and prefixes file names with the directory path.
|
|
25
|
+
* Recursively traverses subdirectories to collect all nested files.
|
|
26
|
+
*
|
|
27
|
+
* @param repoInfo - GitHub repository information
|
|
28
|
+
* @param parentPath - Parent directory path in the repository
|
|
29
|
+
* @param entries - Array of GitHubEntry (may contain both file and dir types)
|
|
30
|
+
* @param tag - Git tag or ref to fetch from
|
|
31
|
+
* @param prefix - Accumulated path prefix for nested entries
|
|
32
|
+
* @returns Flat array of file GitHubEntry with dir-prefixed names
|
|
33
|
+
*/
|
|
34
|
+
export declare const expandDirectoryEntries: (repoInfo: GitHubRepoInfo, parentPath: string, entries: GitHubEntry[], tag: string, prefix?: string) => Promise<GitHubEntry[]>;
|
|
22
35
|
/**
|
|
23
36
|
* Fetch asset files dynamically from GitHub
|
|
24
37
|
* @param repoInfo - GitHub repository information
|
package/dist/core/github.mjs
CHANGED
|
@@ -35,17 +35,44 @@ const fetchDirectoryContents = async (repoInfo, path, tag) => {
|
|
|
35
35
|
if (!response.ok)
|
|
36
36
|
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
37
37
|
const data = await response.json();
|
|
38
|
-
return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'))
|
|
38
|
+
return data.filter((entry) => (entry.type === 'file' && entry.name.endsWith('.md')) ||
|
|
39
|
+
entry.type === 'dir');
|
|
40
|
+
};
|
|
41
|
+
const expandDirectoryEntries = async (repoInfo, parentPath, entries, tag, prefix = '') => {
|
|
42
|
+
const result = [];
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
45
|
+
if (entry.type === 'file') {
|
|
46
|
+
result.push({
|
|
47
|
+
...entry,
|
|
48
|
+
name: prefix ? entryPrefix : entry.name,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else if (entry.type === 'dir') {
|
|
52
|
+
const subEntries = await fetchDirectoryContents(repoInfo, `${parentPath}/${entry.name}`, tag);
|
|
53
|
+
if (subEntries) {
|
|
54
|
+
const expanded = await expandDirectoryEntries(repoInfo, `${parentPath}/${entry.name}`, subEntries, tag, entryPrefix);
|
|
55
|
+
result.push(...expanded);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
39
60
|
};
|
|
40
61
|
const fetchAssetFiles = async (repoInfo, assetPath, tag, assetTypes) => {
|
|
41
62
|
const basePath = repoInfo.directory
|
|
42
63
|
? `${repoInfo.directory}/${assetPath}`
|
|
43
64
|
: assetPath;
|
|
44
65
|
const fetchPromises = assetTypes.map((assetType) => fetchDirectoryContents(repoInfo, `${basePath}/${assetType}`, tag));
|
|
45
|
-
const
|
|
66
|
+
const rawResults = await Promise.all(fetchPromises);
|
|
67
|
+
const expandedResults = await Promise.all(rawResults.map((entries, index) => {
|
|
68
|
+
if (!entries)
|
|
69
|
+
return Promise.resolve([]);
|
|
70
|
+
const assetDirPath = `${basePath}/${assetTypes[index]}`;
|
|
71
|
+
return expandDirectoryEntries(repoInfo, assetDirPath, entries, tag);
|
|
72
|
+
}));
|
|
46
73
|
const assetFiles = {};
|
|
47
74
|
assetTypes.forEach((assetType, index) => {
|
|
48
|
-
assetFiles[assetType] =
|
|
75
|
+
assetFiles[assetType] = expandedResults[index] || [];
|
|
49
76
|
});
|
|
50
77
|
return assetFiles;
|
|
51
78
|
};
|
|
@@ -77,4 +104,4 @@ const downloadAssetFiles = async (repoInfo, assetPath, assetType, entries, tag)
|
|
|
77
104
|
return results;
|
|
78
105
|
};
|
|
79
106
|
|
|
80
|
-
export { NotFoundError, RateLimitError, downloadAssetFiles, downloadFile, fetchAssetFiles, fetchDirectoryContents };
|
|
107
|
+
export { NotFoundError, RateLimitError, downloadAssetFiles, downloadFile, expandDirectoryEntries, fetchAssetFiles, fetchDirectoryContents };
|
package/dist/core/migration.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var fs = require('node:fs');
|
|
4
4
|
var path = require('node:path');
|
|
5
5
|
var nameTransform = require('../utils/nameTransform.cjs');
|
|
6
|
+
var packageName = require('../utils/packageName.cjs');
|
|
6
7
|
var filesystem = require('./filesystem.cjs');
|
|
7
8
|
var syncMeta = require('./syncMeta.cjs');
|
|
8
9
|
|
|
@@ -62,8 +63,8 @@ async function migrateToFlat(cwd, options = {}) {
|
|
|
62
63
|
});
|
|
63
64
|
for (const packageDir of packageDirs) {
|
|
64
65
|
const packagePath = path.join(scopePath, packageDir);
|
|
65
|
-
const packageName = `${scopeDir}/${packageDir}`;
|
|
66
|
-
console.log(` 📦 Processing ${packageName}...`);
|
|
66
|
+
const packageName$1 = `${scopeDir}/${packageDir}`;
|
|
67
|
+
console.log(` 📦 Processing ${packageName$1}...`);
|
|
67
68
|
try {
|
|
68
69
|
const metaPath = path.join(packagePath, '.sync-meta.json');
|
|
69
70
|
let legacyMeta = null;
|
|
@@ -75,7 +76,7 @@ async function migrateToFlat(cwd, options = {}) {
|
|
|
75
76
|
console.log(` ⚠️ No .sync-meta.json found, skipping`);
|
|
76
77
|
continue;
|
|
77
78
|
}
|
|
78
|
-
const prefix =
|
|
79
|
+
const prefix = packageName.packageNameToPrefix(packageName$1);
|
|
79
80
|
const commandFiles = [];
|
|
80
81
|
const fileMappings = [];
|
|
81
82
|
for (const fileName of legacyMeta.files) {
|
|
@@ -112,7 +113,7 @@ async function migrateToFlat(cwd, options = {}) {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
const packageInfo = {
|
|
115
|
-
originalName: packageName,
|
|
116
|
+
originalName: packageName$1,
|
|
116
117
|
version: legacyMeta.version,
|
|
117
118
|
files: {
|
|
118
119
|
commands: assetType === 'commands' ? commandFiles : [],
|
|
@@ -136,11 +137,11 @@ async function migrateToFlat(cwd, options = {}) {
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
unifiedMeta = syncMeta.updatePackageInMeta(unifiedMeta, prefix, packageInfo);
|
|
139
|
-
migratedPackages.push(packageName);
|
|
140
|
+
migratedPackages.push(packageName$1);
|
|
140
141
|
legacyDirs.push(packagePath);
|
|
141
142
|
}
|
|
142
143
|
catch (error) {
|
|
143
|
-
const errorMsg = `Failed to migrate ${packageName}: ${error}`;
|
|
144
|
+
const errorMsg = `Failed to migrate ${packageName$1}: ${error}`;
|
|
144
145
|
console.error(` ❌ ${errorMsg}`);
|
|
145
146
|
errors.push(errorMsg);
|
|
146
147
|
}
|
package/dist/core/migration.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync, readFileSync, rmSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { toFlatFileName } from '../utils/nameTransform.mjs';
|
|
4
|
+
import { packageNameToPrefix } from '../utils/packageName.mjs';
|
|
4
5
|
import { writeFlatAssetFile } from './filesystem.mjs';
|
|
5
6
|
import { readUnifiedSyncMeta, createEmptyUnifiedMeta, updatePackageInMeta, writeUnifiedSyncMeta } from './syncMeta.mjs';
|
|
6
7
|
|
|
@@ -72,15 +72,25 @@ async function scanRemoteAssets(packageName, ref) {
|
|
|
72
72
|
throw new Error(`Invalid GitHub repository URL in package ${packageName}`);
|
|
73
73
|
}
|
|
74
74
|
const assetBasePath = pkgInfo.claude.assetPath;
|
|
75
|
+
const tag = ref ?? 'HEAD';
|
|
75
76
|
const trees = [];
|
|
76
77
|
for (const assetType of constants.DEFAULT_ASSET_TYPES) {
|
|
77
78
|
const assetPath = repoInfo.directory
|
|
78
79
|
? `${repoInfo.directory}/${assetBasePath}/${assetType}`
|
|
79
80
|
: `${assetBasePath}/${assetType}`;
|
|
80
81
|
try {
|
|
81
|
-
const entries = await github.fetchDirectoryContents(repoInfo, assetPath,
|
|
82
|
+
const entries = await github.fetchDirectoryContents(repoInfo, assetPath, tag);
|
|
82
83
|
if (entries && entries.length > 0) {
|
|
83
|
-
const
|
|
84
|
+
const dirContentsMap = new Map();
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.type === 'dir') {
|
|
87
|
+
const dirEntries = await github.fetchDirectoryContents(repoInfo, `${assetPath}/${entry.name}`, tag);
|
|
88
|
+
if (dirEntries) {
|
|
89
|
+
dirContentsMap.set(entry.name, dirEntries);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const tree = buildTreeFromGitHubEntries(assetType, entries, assetType, dirContentsMap);
|
|
84
94
|
if (tree.children && tree.children.length > 0) {
|
|
85
95
|
trees.push(tree);
|
|
86
96
|
}
|
|
@@ -100,7 +110,8 @@ function buildTreeFromLocalDir(label, dirPath, basePath) {
|
|
|
100
110
|
const stat = fs.statSync(fullPath);
|
|
101
111
|
const relativePath = path.join(basePath, entry);
|
|
102
112
|
if (stat.isDirectory()) {
|
|
103
|
-
const isSkill = fs.existsSync(path.join(fullPath, '
|
|
113
|
+
const isSkill = fs.existsSync(path.join(fullPath, 'SKILL.md')) ||
|
|
114
|
+
fs.existsSync(path.join(fullPath, 'Skill.md'));
|
|
104
115
|
if (isSkill) {
|
|
105
116
|
children.push({
|
|
106
117
|
id: relativePath,
|
|
@@ -139,36 +150,37 @@ function buildTreeFromLocalDir(label, dirPath, basePath) {
|
|
|
139
150
|
expanded: true,
|
|
140
151
|
};
|
|
141
152
|
}
|
|
142
|
-
function buildTreeFromGitHubEntries(label, entries, basePath) {
|
|
153
|
+
function buildTreeFromGitHubEntries(label, entries, basePath, dirContentsMap) {
|
|
143
154
|
const children = [];
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
if (items.length === 1 && items[0].type === 'file') {
|
|
147
|
-
const filePath = items[0].path;
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (entry.type === 'file') {
|
|
148
157
|
children.push({
|
|
149
|
-
id:
|
|
150
|
-
label: name,
|
|
151
|
-
path:
|
|
158
|
+
id: entry.path,
|
|
159
|
+
label: entry.name,
|
|
160
|
+
path: entry.path,
|
|
152
161
|
type: 'file',
|
|
153
162
|
selected: true,
|
|
154
163
|
expanded: false,
|
|
155
164
|
});
|
|
156
165
|
}
|
|
157
|
-
else {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
else if (entry.type === 'dir') {
|
|
167
|
+
const dirEntries = dirContentsMap?.get(entry.name);
|
|
168
|
+
const hasSkillMd = dirEntries
|
|
169
|
+
? isDirectorySkill(dirEntries)
|
|
170
|
+
: false;
|
|
171
|
+
if (hasSkillMd) {
|
|
172
|
+
const skillPath = `${basePath}/${entry.name}`;
|
|
161
173
|
children.push({
|
|
162
174
|
id: skillPath,
|
|
163
|
-
label: name,
|
|
175
|
+
label: entry.name,
|
|
164
176
|
path: skillPath,
|
|
165
177
|
type: 'skill-directory',
|
|
166
178
|
selected: true,
|
|
167
179
|
expanded: false,
|
|
168
180
|
});
|
|
169
181
|
}
|
|
170
|
-
else {
|
|
171
|
-
const subTree = buildTreeFromGitHubEntries(name,
|
|
182
|
+
else if (dirEntries && dirEntries.length > 0) {
|
|
183
|
+
const subTree = buildTreeFromGitHubEntries(entry.name, dirEntries, `${basePath}/${entry.name}`);
|
|
172
184
|
if (subTree.children && subTree.children.length > 0) {
|
|
173
185
|
children.push(subTree);
|
|
174
186
|
}
|
|
@@ -185,17 +197,9 @@ function buildTreeFromGitHubEntries(label, entries, basePath) {
|
|
|
185
197
|
expanded: true,
|
|
186
198
|
};
|
|
187
199
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const parts = entry.path.split('/');
|
|
192
|
-
const topLevel = parts[parts.length - (entry.type === 'file' ? 1 : 0)];
|
|
193
|
-
if (!groups[topLevel]) {
|
|
194
|
-
groups[topLevel] = [];
|
|
195
|
-
}
|
|
196
|
-
groups[topLevel].push(entry);
|
|
197
|
-
}
|
|
198
|
-
return groups;
|
|
200
|
+
function isDirectorySkill(entries) {
|
|
201
|
+
return entries.some((entry) => entry.type === 'file' &&
|
|
202
|
+
(entry.name === 'SKILL.md' || entry.name === 'Skill.md'));
|
|
199
203
|
}
|
|
200
204
|
function findLocalPackage(packageName, cwd) {
|
|
201
205
|
const monorepoRoot = findMonorepoRoot(cwd);
|
|
@@ -256,4 +260,5 @@ function searchPackagesRecursively(dir, packageName) {
|
|
|
256
260
|
return null;
|
|
257
261
|
}
|
|
258
262
|
|
|
263
|
+
exports.isDirectorySkill = isDirectorySkill;
|
|
259
264
|
exports.scanPackageAssets = scanPackageAssets;
|
|
@@ -70,15 +70,25 @@ async function scanRemoteAssets(packageName, ref) {
|
|
|
70
70
|
throw new Error(`Invalid GitHub repository URL in package ${packageName}`);
|
|
71
71
|
}
|
|
72
72
|
const assetBasePath = pkgInfo.claude.assetPath;
|
|
73
|
+
const tag = ref ?? 'HEAD';
|
|
73
74
|
const trees = [];
|
|
74
75
|
for (const assetType of DEFAULT_ASSET_TYPES) {
|
|
75
76
|
const assetPath = repoInfo.directory
|
|
76
77
|
? `${repoInfo.directory}/${assetBasePath}/${assetType}`
|
|
77
78
|
: `${assetBasePath}/${assetType}`;
|
|
78
79
|
try {
|
|
79
|
-
const entries = await fetchDirectoryContents(repoInfo, assetPath,
|
|
80
|
+
const entries = await fetchDirectoryContents(repoInfo, assetPath, tag);
|
|
80
81
|
if (entries && entries.length > 0) {
|
|
81
|
-
const
|
|
82
|
+
const dirContentsMap = new Map();
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (entry.type === 'dir') {
|
|
85
|
+
const dirEntries = await fetchDirectoryContents(repoInfo, `${assetPath}/${entry.name}`, tag);
|
|
86
|
+
if (dirEntries) {
|
|
87
|
+
dirContentsMap.set(entry.name, dirEntries);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const tree = buildTreeFromGitHubEntries(assetType, entries, assetType, dirContentsMap);
|
|
82
92
|
if (tree.children && tree.children.length > 0) {
|
|
83
93
|
trees.push(tree);
|
|
84
94
|
}
|
|
@@ -98,7 +108,8 @@ function buildTreeFromLocalDir(label, dirPath, basePath) {
|
|
|
98
108
|
const stat = statSync(fullPath);
|
|
99
109
|
const relativePath = join(basePath, entry);
|
|
100
110
|
if (stat.isDirectory()) {
|
|
101
|
-
const isSkill = existsSync(join(fullPath, '
|
|
111
|
+
const isSkill = existsSync(join(fullPath, 'SKILL.md')) ||
|
|
112
|
+
existsSync(join(fullPath, 'Skill.md'));
|
|
102
113
|
if (isSkill) {
|
|
103
114
|
children.push({
|
|
104
115
|
id: relativePath,
|
|
@@ -137,36 +148,37 @@ function buildTreeFromLocalDir(label, dirPath, basePath) {
|
|
|
137
148
|
expanded: true,
|
|
138
149
|
};
|
|
139
150
|
}
|
|
140
|
-
function buildTreeFromGitHubEntries(label, entries, basePath) {
|
|
151
|
+
function buildTreeFromGitHubEntries(label, entries, basePath, dirContentsMap) {
|
|
141
152
|
const children = [];
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
if (items.length === 1 && items[0].type === 'file') {
|
|
145
|
-
const filePath = items[0].path;
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (entry.type === 'file') {
|
|
146
155
|
children.push({
|
|
147
|
-
id:
|
|
148
|
-
label: name,
|
|
149
|
-
path:
|
|
156
|
+
id: entry.path,
|
|
157
|
+
label: entry.name,
|
|
158
|
+
path: entry.path,
|
|
150
159
|
type: 'file',
|
|
151
160
|
selected: true,
|
|
152
161
|
expanded: false,
|
|
153
162
|
});
|
|
154
163
|
}
|
|
155
|
-
else {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
else if (entry.type === 'dir') {
|
|
165
|
+
const dirEntries = dirContentsMap?.get(entry.name);
|
|
166
|
+
const hasSkillMd = dirEntries
|
|
167
|
+
? isDirectorySkill(dirEntries)
|
|
168
|
+
: false;
|
|
169
|
+
if (hasSkillMd) {
|
|
170
|
+
const skillPath = `${basePath}/${entry.name}`;
|
|
159
171
|
children.push({
|
|
160
172
|
id: skillPath,
|
|
161
|
-
label: name,
|
|
173
|
+
label: entry.name,
|
|
162
174
|
path: skillPath,
|
|
163
175
|
type: 'skill-directory',
|
|
164
176
|
selected: true,
|
|
165
177
|
expanded: false,
|
|
166
178
|
});
|
|
167
179
|
}
|
|
168
|
-
else {
|
|
169
|
-
const subTree = buildTreeFromGitHubEntries(name,
|
|
180
|
+
else if (dirEntries && dirEntries.length > 0) {
|
|
181
|
+
const subTree = buildTreeFromGitHubEntries(entry.name, dirEntries, `${basePath}/${entry.name}`);
|
|
170
182
|
if (subTree.children && subTree.children.length > 0) {
|
|
171
183
|
children.push(subTree);
|
|
172
184
|
}
|
|
@@ -183,17 +195,9 @@ function buildTreeFromGitHubEntries(label, entries, basePath) {
|
|
|
183
195
|
expanded: true,
|
|
184
196
|
};
|
|
185
197
|
}
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const parts = entry.path.split('/');
|
|
190
|
-
const topLevel = parts[parts.length - (entry.type === 'file' ? 1 : 0)];
|
|
191
|
-
if (!groups[topLevel]) {
|
|
192
|
-
groups[topLevel] = [];
|
|
193
|
-
}
|
|
194
|
-
groups[topLevel].push(entry);
|
|
195
|
-
}
|
|
196
|
-
return groups;
|
|
198
|
+
function isDirectorySkill(entries) {
|
|
199
|
+
return entries.some((entry) => entry.type === 'file' &&
|
|
200
|
+
(entry.name === 'SKILL.md' || entry.name === 'Skill.md'));
|
|
197
201
|
}
|
|
198
202
|
function findLocalPackage(packageName, cwd) {
|
|
199
203
|
const monorepoRoot = findMonorepoRoot(cwd);
|
|
@@ -254,4 +258,4 @@ function searchPackagesRecursively(dir, packageName) {
|
|
|
254
258
|
return null;
|
|
255
259
|
}
|
|
256
260
|
|
|
257
|
-
export { scanPackageAssets };
|
|
261
|
+
export { isDirectorySkill, scanPackageAssets };
|
package/dist/core/sync.cjs
CHANGED
|
@@ -3,23 +3,24 @@
|
|
|
3
3
|
var logger = require('../utils/logger.cjs');
|
|
4
4
|
var nameTransform = require('../utils/nameTransform.cjs');
|
|
5
5
|
var _package = require('../utils/package.cjs');
|
|
6
|
+
var packageName = require('../utils/packageName.cjs');
|
|
6
7
|
var paths = require('../utils/paths.cjs');
|
|
7
8
|
var filesystem = require('./filesystem.cjs');
|
|
8
9
|
var github = require('./github.cjs');
|
|
9
10
|
var syncMeta = require('./syncMeta.cjs');
|
|
10
11
|
var assetStructure = require('./assetStructure.cjs');
|
|
11
12
|
|
|
12
|
-
const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions, outputDir) => {
|
|
13
|
-
logger.logger.packageStart(packageName);
|
|
13
|
+
const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusions, outputDir) => {
|
|
14
|
+
logger.logger.packageStart(packageName$1);
|
|
14
15
|
try {
|
|
15
16
|
const destDir = outputDir ?? _package.findGitRoot(cwd) ?? cwd;
|
|
16
17
|
const packageInfo = options.local
|
|
17
|
-
? _package.readLocalPackageJson(packageName, cwd)
|
|
18
|
-
: _package.readPackageJson(packageName, cwd);
|
|
18
|
+
? _package.readLocalPackageJson(packageName$1, cwd)
|
|
19
|
+
: _package.readPackageJson(packageName$1, cwd);
|
|
19
20
|
if (!packageInfo) {
|
|
20
21
|
const location = options.local ? 'workspace' : 'node_modules';
|
|
21
22
|
return {
|
|
22
|
-
packageName,
|
|
23
|
+
packageName: packageName$1,
|
|
23
24
|
success: false,
|
|
24
25
|
skipped: true,
|
|
25
26
|
reason: `Package not found in ${location}`,
|
|
@@ -27,7 +28,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
27
28
|
}
|
|
28
29
|
if (!packageInfo.claude?.assetPath)
|
|
29
30
|
return {
|
|
30
|
-
packageName,
|
|
31
|
+
packageName: packageName$1,
|
|
31
32
|
success: false,
|
|
32
33
|
skipped: true,
|
|
33
34
|
reason: 'Package does not have claude.assetPath in package.json',
|
|
@@ -35,7 +36,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
35
36
|
const repoInfo = _package.parseGitHubRepo(packageInfo.repository);
|
|
36
37
|
if (!repoInfo) {
|
|
37
38
|
return {
|
|
38
|
-
packageName,
|
|
39
|
+
packageName: packageName$1,
|
|
39
40
|
success: false,
|
|
40
41
|
skipped: true,
|
|
41
42
|
reason: 'Unable to parse GitHub repository URL',
|
|
@@ -43,18 +44,18 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
43
44
|
}
|
|
44
45
|
const useFlat = options.flat !== false;
|
|
45
46
|
if (useFlat) {
|
|
46
|
-
const prefix =
|
|
47
|
+
const prefix = packageName.packageNameToPrefix(packageName$1);
|
|
47
48
|
const unifiedMeta = syncMeta.readUnifiedSyncMeta(destDir) ?? syncMeta.createEmptyUnifiedMeta();
|
|
48
49
|
if (!options.force &&
|
|
49
50
|
!syncMeta.needsSyncUnified(unifiedMeta, prefix, packageInfo.version)) {
|
|
50
51
|
return {
|
|
51
|
-
packageName,
|
|
52
|
+
packageName: packageName$1,
|
|
52
53
|
success: true,
|
|
53
54
|
skipped: true,
|
|
54
55
|
reason: `Already synced at version ${packageInfo.version}`,
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
|
-
const tag = options.ref ?? _package.buildVersionTag(packageName, packageInfo.version);
|
|
58
|
+
const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
|
|
58
59
|
const assetPath = _package.buildAssetPath(packageInfo.claude.assetPath);
|
|
59
60
|
logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
|
|
60
61
|
const assetTypes = _package.getAssetTypes(packageInfo.claude);
|
|
@@ -65,7 +66,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
65
66
|
}
|
|
66
67
|
if (totalFiles === 0)
|
|
67
68
|
return {
|
|
68
|
-
packageName,
|
|
69
|
+
packageName: packageName$1,
|
|
69
70
|
success: false,
|
|
70
71
|
skipped: true,
|
|
71
72
|
reason: `No assets found in package (checked: ${assetTypes.join(', ')})`,
|
|
@@ -105,7 +106,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
105
106
|
continue;
|
|
106
107
|
const structure = assetStructure.getAssetStructure(assetType, packageInfo.claude);
|
|
107
108
|
if (structure === 'nested') {
|
|
108
|
-
logger.logger.step(`Would sync ${assetType} to`, paths.getDestinationDir(destDir, packageName, assetType));
|
|
109
|
+
logger.logger.step(`Would sync ${assetType} to`, paths.getDestinationDir(destDir, packageName$1, assetType));
|
|
109
110
|
mappings.forEach((fileName) => {
|
|
110
111
|
logger.logger.file('create', fileName);
|
|
111
112
|
});
|
|
@@ -130,7 +131,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
return {
|
|
133
|
-
packageName,
|
|
134
|
+
packageName: packageName$1,
|
|
134
135
|
success: true,
|
|
135
136
|
skipped: false,
|
|
136
137
|
syncedFiles,
|
|
@@ -139,7 +140,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
139
140
|
for (const assetType of assetTypes) {
|
|
140
141
|
const structure = assetStructure.getAssetStructure(assetType, packageInfo.claude);
|
|
141
142
|
if (structure === 'nested') {
|
|
142
|
-
filesystem.cleanAssetDir(destDir, packageName, assetType);
|
|
143
|
+
filesystem.cleanAssetDir(destDir, packageName$1, assetType);
|
|
143
144
|
}
|
|
144
145
|
else {
|
|
145
146
|
filesystem.cleanFlatAssetFiles(destDir, assetType, prefix, unifiedMeta);
|
|
@@ -162,7 +163,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
162
163
|
const downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
|
|
163
164
|
if (structure === 'nested') {
|
|
164
165
|
for (const [fileName, content] of downloadedFiles) {
|
|
165
|
-
filesystem.writeAssetFile(destDir, packageName, assetType, fileName, content);
|
|
166
|
+
filesystem.writeAssetFile(destDir, packageName$1, assetType, fileName, content);
|
|
166
167
|
logger.logger.file('create', fileName);
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -177,7 +178,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
const updatedMeta = syncMeta.updatePackageInMeta(unifiedMeta, prefix, {
|
|
180
|
-
originalName: packageName,
|
|
181
|
+
originalName: packageName$1,
|
|
181
182
|
version: packageInfo.version,
|
|
182
183
|
local: options.local,
|
|
183
184
|
files: fileMappings,
|
|
@@ -200,14 +201,14 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
return {
|
|
203
|
-
packageName,
|
|
204
|
+
packageName: packageName$1,
|
|
204
205
|
success: true,
|
|
205
206
|
skipped: false,
|
|
206
207
|
syncedFiles,
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
210
|
else {
|
|
210
|
-
const tag = options.ref ?? _package.buildVersionTag(packageName, packageInfo.version);
|
|
211
|
+
const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
|
|
211
212
|
const assetPath = _package.buildAssetPath(packageInfo.claude.assetPath);
|
|
212
213
|
logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
|
|
213
214
|
const assetTypes = _package.getAssetTypes(packageInfo.claude);
|
|
@@ -218,7 +219,7 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
218
219
|
}
|
|
219
220
|
if (totalFiles === 0)
|
|
220
221
|
return {
|
|
221
|
-
packageName,
|
|
222
|
+
packageName: packageName$1,
|
|
222
223
|
success: false,
|
|
223
224
|
skipped: true,
|
|
224
225
|
reason: `No assets found in package (checked: ${assetTypes.join(', ')})`,
|
|
@@ -228,9 +229,9 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
228
229
|
.join(', ');
|
|
229
230
|
logger.logger.step('Found', foundSummary);
|
|
230
231
|
if (!options.force &&
|
|
231
|
-
!filesystem.needsSync(destDir, packageName, packageInfo.version, assetTypes)) {
|
|
232
|
+
!filesystem.needsSync(destDir, packageName$1, packageInfo.version, assetTypes)) {
|
|
232
233
|
return {
|
|
233
|
-
packageName,
|
|
234
|
+
packageName: packageName$1,
|
|
234
235
|
success: true,
|
|
235
236
|
skipped: true,
|
|
236
237
|
reason: `Already synced at version ${packageInfo.version}`,
|
|
@@ -250,12 +251,12 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
250
251
|
});
|
|
251
252
|
if (filteredEntries.length === 0)
|
|
252
253
|
continue;
|
|
253
|
-
logger.logger.step(`Would sync ${assetType} to`, paths.getDestinationDir(destDir, packageName, assetType));
|
|
254
|
+
logger.logger.step(`Would sync ${assetType} to`, paths.getDestinationDir(destDir, packageName$1, assetType));
|
|
254
255
|
filteredEntries.forEach((entry) => logger.logger.file('create', entry.name));
|
|
255
256
|
syncedFiles[assetType] = filteredEntries.map((e) => e.name);
|
|
256
257
|
}
|
|
257
258
|
return {
|
|
258
|
-
packageName,
|
|
259
|
+
packageName: packageName$1,
|
|
259
260
|
success: true,
|
|
260
261
|
skipped: false,
|
|
261
262
|
syncedFiles,
|
|
@@ -276,17 +277,17 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
276
277
|
continue;
|
|
277
278
|
logger.logger.step('Downloading', assetType);
|
|
278
279
|
const downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
|
|
279
|
-
filesystem.cleanAssetDir(destDir, packageName, assetType);
|
|
280
|
+
filesystem.cleanAssetDir(destDir, packageName$1, assetType);
|
|
280
281
|
syncedFiles[assetType] = [];
|
|
281
282
|
for (const [fileName, content] of downloadedFiles) {
|
|
282
|
-
filesystem.writeAssetFile(destDir, packageName, assetType, fileName, content);
|
|
283
|
+
filesystem.writeAssetFile(destDir, packageName$1, assetType, fileName, content);
|
|
283
284
|
logger.logger.file('create', fileName);
|
|
284
285
|
syncedFiles[assetType].push(fileName);
|
|
285
286
|
}
|
|
286
|
-
filesystem.writeSyncMeta(destDir, packageName, assetType, filesystem.createSyncMeta(packageInfo.version, syncedFiles[assetType]));
|
|
287
|
+
filesystem.writeSyncMeta(destDir, packageName$1, assetType, filesystem.createSyncMeta(packageInfo.version, syncedFiles[assetType]));
|
|
287
288
|
}
|
|
288
289
|
return {
|
|
289
|
-
packageName,
|
|
290
|
+
packageName: packageName$1,
|
|
290
291
|
success: true,
|
|
291
292
|
skipped: false,
|
|
292
293
|
syncedFiles,
|
|
@@ -296,14 +297,14 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
|
|
|
296
297
|
catch (error) {
|
|
297
298
|
if (error instanceof github.RateLimitError)
|
|
298
299
|
return {
|
|
299
|
-
packageName,
|
|
300
|
+
packageName: packageName$1,
|
|
300
301
|
success: false,
|
|
301
302
|
skipped: false,
|
|
302
303
|
reason: error.message,
|
|
303
304
|
};
|
|
304
305
|
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
305
306
|
return {
|
|
306
|
-
packageName,
|
|
307
|
+
packageName: packageName$1,
|
|
307
308
|
success: false,
|
|
308
309
|
skipped: false,
|
|
309
310
|
reason: message,
|
package/dist/core/sync.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { toFlatFileName } from '../utils/nameTransform.mjs';
|
|
3
3
|
import { findGitRoot, readLocalPackageJson, readPackageJson, parseGitHubRepo, buildVersionTag, buildAssetPath, getAssetTypes } from '../utils/package.mjs';
|
|
4
|
+
import { packageNameToPrefix } from '../utils/packageName.mjs';
|
|
4
5
|
import { getDestinationDir, getFlatDestinationDir } from '../utils/paths.mjs';
|
|
5
6
|
import { cleanAssetDir, cleanFlatAssetFiles, writeAssetFile, writeFlatAssetFile, needsSync, writeSyncMeta, createSyncMeta } from './filesystem.mjs';
|
|
6
7
|
import { fetchAssetFiles, downloadAssetFiles, RateLimitError } from './github.mjs';
|
package/dist/core/syncMeta.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { PackageSyncInfo, UnifiedSyncMeta } from '../utils/types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Schema version for the unified metadata format
|
|
4
4
|
*/
|
|
5
|
-
export declare const SCHEMA_VERSION: "0.0.
|
|
5
|
+
export declare const SCHEMA_VERSION: "0.0.4";
|
|
6
6
|
/**
|
|
7
7
|
* Read unified sync metadata from .claude/.sync-meta.json
|
|
8
8
|
*
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var packageName = require('./packageName.cjs');
|
|
4
|
-
|
|
5
|
-
const packageNameToPrefix = packageName.packageNameToPrefix;
|
|
6
3
|
function toFlatFileName(prefix, fileName) {
|
|
7
|
-
const
|
|
8
|
-
|
|
4
|
+
const slashIndex = fileName.indexOf('/');
|
|
5
|
+
if (slashIndex === -1) {
|
|
6
|
+
return `${prefix}_${fileName}`;
|
|
7
|
+
}
|
|
8
|
+
const dirName = fileName.substring(0, slashIndex);
|
|
9
|
+
const rest = fileName.substring(slashIndex);
|
|
10
|
+
return `${prefix}_${dirName}${rest}`;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
exports.packageNameToPrefix = packageNameToPrefix;
|
|
12
13
|
exports.toFlatFileName = toFlatFileName;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Name transformation utilities for Claude assets synchronization
|
|
3
|
-
* Handles conversion between package names and flat file naming conventions
|
|
4
|
-
*/
|
|
5
|
-
import { packageNameToPrefix as packageNameToPrefixUtil } from './packageName';
|
|
6
1
|
/**
|
|
7
2
|
* Converts kebab-case string to camelCase
|
|
8
3
|
* All hyphens are removed and the following character is capitalized
|
|
@@ -19,40 +14,34 @@ import { packageNameToPrefix as packageNameToPrefixUtil } from './packageName';
|
|
|
19
14
|
*/
|
|
20
15
|
export declare function kebabToCamel(str: string): string;
|
|
21
16
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* packageNameToPrefix('@canard/schema-form') // 'canard-schemaForm'
|
|
30
|
-
* packageNameToPrefix('@canard/schema-form-plugin') // 'canard-schemaFormPlugin'
|
|
31
|
-
* packageNameToPrefix('@winglet/react-utils') // 'winglet-reactUtils'
|
|
32
|
-
* packageNameToPrefix('@lerx/promise-modal') // 'lerx-promiseModal'
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export declare const packageNameToPrefix: typeof packageNameToPrefixUtil;
|
|
36
|
-
/**
|
|
37
|
-
* Creates a flat file name by combining prefix and original file name
|
|
17
|
+
* Creates a flat file name by combining prefix and original file name.
|
|
18
|
+
* For single files (no path separator), creates prefix_filename.
|
|
19
|
+
* For directory-based entries (with path separator), applies prefix only
|
|
20
|
+
* to the top-level directory name, preserving internal path structure.
|
|
38
21
|
*
|
|
39
22
|
* @param prefix - Package prefix (e.g., 'canard-schemaForm')
|
|
40
|
-
* @param fileName - Original file name (e.g., 'guide.md')
|
|
41
|
-
* @returns Flat file name
|
|
23
|
+
* @param fileName - Original file name (e.g., 'guide.md' or 'expert/SKILL.md')
|
|
24
|
+
* @returns Flat file name with prefix
|
|
42
25
|
*
|
|
43
26
|
* @example
|
|
44
27
|
* ```ts
|
|
45
|
-
* toFlatFileName('canard-schemaForm', 'guide.md')
|
|
46
|
-
*
|
|
47
|
-
*
|
|
28
|
+
* toFlatFileName('canard-schemaForm', 'guide.md')
|
|
29
|
+
* // 'canard-schemaForm_guide.md'
|
|
30
|
+
*
|
|
31
|
+
* toFlatFileName('canard-schemaForm', 'expert/SKILL.md')
|
|
32
|
+
* // 'canard-schemaForm_expert/SKILL.md'
|
|
33
|
+
*
|
|
34
|
+
* toFlatFileName('canard-schemaForm', 'expert/knowledge/api.md')
|
|
35
|
+
* // 'canard-schemaForm_expert/knowledge/api.md'
|
|
48
36
|
* ```
|
|
49
37
|
*/
|
|
50
38
|
export declare function toFlatFileName(prefix: string, fileName: string): string;
|
|
51
39
|
/**
|
|
52
|
-
* Parses a flat file name back into prefix and original file name
|
|
53
|
-
*
|
|
40
|
+
* Parses a flat file name back into prefix and original file name.
|
|
41
|
+
* Reverses the transformation done by toFlatFileName.
|
|
42
|
+
* Returns null if the file name doesn't match the expected pattern.
|
|
54
43
|
*
|
|
55
|
-
* @param flatName - Flat file name (e.g., 'canard-schemaForm_guide.md')
|
|
44
|
+
* @param flatName - Flat file name (e.g., 'canard-schemaForm_guide.md' or 'canard-schemaForm_expert/SKILL.md')
|
|
56
45
|
* @returns Object with prefix and original name, or null if invalid
|
|
57
46
|
*
|
|
58
47
|
* @example
|
|
@@ -60,11 +49,11 @@ export declare function toFlatFileName(prefix: string, fileName: string): string
|
|
|
60
49
|
* parseFlatFileName('canard-schemaForm_guide.md')
|
|
61
50
|
* // { prefix: 'canard-schemaForm', original: 'guide.md' }
|
|
62
51
|
*
|
|
63
|
-
* parseFlatFileName('
|
|
64
|
-
* // { prefix: '
|
|
52
|
+
* parseFlatFileName('canard-schemaForm_expert/SKILL.md')
|
|
53
|
+
* // { prefix: 'canard-schemaForm', original: 'expert/SKILL.md' }
|
|
65
54
|
*
|
|
66
|
-
* parseFlatFileName('
|
|
67
|
-
* // { prefix: '
|
|
55
|
+
* parseFlatFileName('canard-schemaForm_expert/knowledge/api.md')
|
|
56
|
+
* // { prefix: 'canard-schemaForm', original: 'expert/knowledge/api.md' }
|
|
68
57
|
*
|
|
69
58
|
* parseFlatFileName('invalid-name.md')
|
|
70
59
|
* // null (no underscore separator)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { packageNameToPrefix as packageNameToPrefix$1 } from './packageName.mjs';
|
|
2
|
-
|
|
3
|
-
const packageNameToPrefix = packageNameToPrefix$1;
|
|
4
1
|
function toFlatFileName(prefix, fileName) {
|
|
5
|
-
const
|
|
6
|
-
|
|
2
|
+
const slashIndex = fileName.indexOf('/');
|
|
3
|
+
if (slashIndex === -1) {
|
|
4
|
+
return `${prefix}_${fileName}`;
|
|
5
|
+
}
|
|
6
|
+
const dirName = fileName.substring(0, slashIndex);
|
|
7
|
+
const rest = fileName.substring(slashIndex);
|
|
8
|
+
return `${prefix}_${dirName}${rest}`;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
export {
|
|
11
|
+
export { toFlatFileName };
|
package/dist/version.cjs
CHANGED
package/dist/version.d.ts
CHANGED
package/dist/version.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slats/claude-assets-sync",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "CLI tool to sync Claude commands and skills from npm packages to your project's .claude directory",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"build": "node scripts/inject-version.js && rollup -c && yarn build:types",
|
|
44
44
|
"build:publish:npm": "yarn build && yarn publish:npm",
|
|
45
45
|
"build:types": "tsc -p ./tsconfig.declarations.json && tsc-alias -p ./tsconfig.declarations.json",
|
|
46
|
-
"dev": "node scripts/inject-version.js && tsx src/
|
|
46
|
+
"dev": "node scripts/inject-version.js && tsx src/cli.ts",
|
|
47
47
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
48
48
|
"lint": "eslint \"src/**/*.ts\"",
|
|
49
49
|
"publish:npm": "yarn npm publish --access public",
|
package/CHANGELOG.md
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to @slats/claude-assets-sync will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [0.0.1] - 2025-02-05
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
|
|
12
|
-
#### Phase 1: Version Management Unification
|
|
13
|
-
- Unified version management system across all packages
|
|
14
|
-
- Single `.sync-meta.json` file replacing per-package metadata
|
|
15
|
-
- Comprehensive package tracking with original names and file mappings
|
|
16
|
-
- Timestamp tracking for all sync operations
|
|
17
|
-
- Version comparison logic to skip unnecessary syncs
|
|
18
|
-
|
|
19
|
-
#### Phase 2: Code Consolidation & Architecture
|
|
20
|
-
- Modular command architecture with pluggable command system
|
|
21
|
-
- New `src/commands/` directory for command implementations
|
|
22
|
-
- Command registry system (`COMMANDS` export) with metadata
|
|
23
|
-
- Centralized command types in `src/commands/types.ts`
|
|
24
|
-
- Unified CLI structure using Commander.js
|
|
25
|
-
- Better separation of concerns between CLI and command logic
|
|
26
|
-
|
|
27
|
-
#### Phase 3: Flat Directory Structure Support
|
|
28
|
-
- Modern flat file organization with prefixed filenames
|
|
29
|
-
- Name transformation system for scoped packages
|
|
30
|
-
- Support for both flat and nested directory structures
|
|
31
|
-
- `--no-flat` flag to maintain backward compatibility with legacy structure
|
|
32
|
-
- Smart detection of directory structure type during sync operations
|
|
33
|
-
|
|
34
|
-
#### Phase 4: Package Management Features
|
|
35
|
-
- **list command**: List all synced packages with asset details
|
|
36
|
-
- Human-readable output with package names, versions, and asset counts
|
|
37
|
-
- JSON output support for scripting and automation
|
|
38
|
-
- Asset breakdown by type (commands, skills, etc.)
|
|
39
|
-
- Sort by package name for consistency
|
|
40
|
-
|
|
41
|
-
- **remove command**: Remove synced packages safely
|
|
42
|
-
- Support for both flat and nested structures
|
|
43
|
-
- Confirmation prompts (skip with `-y/--yes`)
|
|
44
|
-
- Dry-run mode for preview
|
|
45
|
-
- Automatic metadata cleanup after removal
|
|
46
|
-
- Graceful error handling for missing files
|
|
47
|
-
|
|
48
|
-
- **status command**: Monitor sync status and check for updates
|
|
49
|
-
- Real-time remote version checking via npm registry
|
|
50
|
-
- Version mismatch detection with visual indicators
|
|
51
|
-
- Cached remote version checks (5-minute TTL)
|
|
52
|
-
- `--no-remote` flag to skip remote checks
|
|
53
|
-
- Summary statistics of sync status
|
|
54
|
-
|
|
55
|
-
- **migrate command**: Migrate from legacy to flat structure
|
|
56
|
-
- Automatic conversion of nested directories to flat naming
|
|
57
|
-
- Comprehensive dry-run support
|
|
58
|
-
- Metadata preservation during migration
|
|
59
|
-
- Safe multi-run operation
|
|
60
|
-
|
|
61
|
-
#### Phase 5: Interactive UI Infrastructure
|
|
62
|
-
- Ink + React configuration for interactive CLI components
|
|
63
|
-
- TypeScript JSX support (jsx: "react-jsx")
|
|
64
|
-
- UI component infrastructure with fallback to plain text
|
|
65
|
-
- ink (^4.4.1), ink-spinner (^5.0.0), react (^18.2.0) dependencies
|
|
66
|
-
- Type definitions for React components (@types/react)
|
|
67
|
-
- ESM compatibility maintained with interactive UI support
|
|
68
|
-
|
|
69
|
-
#### Phase 6: Testing & Documentation
|
|
70
|
-
- Comprehensive README documentation for all commands
|
|
71
|
-
- Korean translation (README-ko_kr.md) with all features
|
|
72
|
-
- Detailed command usage examples and workflows
|
|
73
|
-
- Environment variable documentation
|
|
74
|
-
- Troubleshooting guide with common issues and solutions
|
|
75
|
-
- Architecture documentation explaining data flow
|
|
76
|
-
- CI/CD integration examples
|
|
77
|
-
- Rate limit documentation with mitigation strategies
|
|
78
|
-
|
|
79
|
-
### Enhanced
|
|
80
|
-
|
|
81
|
-
- **Sync Logic**: Extended to support both flat and nested structures
|
|
82
|
-
- **Error Handling**: Improved error messages with context-aware suggestions
|
|
83
|
-
- **Logging**: Color-coded output with picocolors for better visibility
|
|
84
|
-
- **GitHub Integration**: Support for custom git refs (branches, tags, commits)
|
|
85
|
-
- **Local Workspace Support**: Option to read packages from local workspace
|
|
86
|
-
- **File System Operations**: Safe handling of both file and directory removal
|
|
87
|
-
|
|
88
|
-
### Changed
|
|
89
|
-
|
|
90
|
-
- CLI structure: Main sync is now default command with sub-commands (list, remove, status, migrate)
|
|
91
|
-
- Version checking: Now compares against unified metadata instead of per-package files
|
|
92
|
-
- Directory organization: Flat structure is now default (use `--no-flat` for legacy)
|
|
93
|
-
- Metadata format: Unified schema with package prefixes as keys
|
|
94
|
-
- File naming: Scoped packages now use hyphen-separated prefixes (e.g., @scope-package-file.md)
|
|
95
|
-
|
|
96
|
-
### Fixed
|
|
97
|
-
|
|
98
|
-
- Version comparison for flat structure packages
|
|
99
|
-
- Metadata update timing during removal operations
|
|
100
|
-
- Directory creation for deeply nested legacy structures
|
|
101
|
-
- File path handling for Windows compatibility
|
|
102
|
-
|
|
103
|
-
### Technical Details
|
|
104
|
-
|
|
105
|
-
#### New File Mappings
|
|
106
|
-
Each file is now tracked with original and transformed names:
|
|
107
|
-
```json
|
|
108
|
-
{
|
|
109
|
-
"original": "my-command.md",
|
|
110
|
-
"transformed": "@scope-package-my-command.md"
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### Updated Metadata Structure
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"version": "0.0.1",
|
|
118
|
-
"syncedAt": "2025-02-05T10:30:00.000Z",
|
|
119
|
-
"packages": {
|
|
120
|
-
"@scope-package": {
|
|
121
|
-
"originalName": "@scope/package",
|
|
122
|
-
"version": "1.0.0",
|
|
123
|
-
"files": {
|
|
124
|
-
"commands": [...],
|
|
125
|
-
"skills": [...]
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
#### Command Dependencies
|
|
133
|
-
- **sync**: Core functionality, no command dependencies
|
|
134
|
-
- **list**: Depends on unified metadata reading
|
|
135
|
-
- **remove**: Depends on unified metadata, file system operations
|
|
136
|
-
- **status**: Depends on npm registry API, version caching
|
|
137
|
-
- **migrate**: Depends on legacy structure detection, transformation logic
|
|
138
|
-
|
|
139
|
-
### Dependencies Added
|
|
140
|
-
|
|
141
|
-
- **ink** (^4.4.1): React renderer for terminal UIs
|
|
142
|
-
- **ink-spinner** (^5.0.0): Loading spinner component for ink
|
|
143
|
-
- **react** (^18.2.0): UI component framework
|
|
144
|
-
- **@types/react** (^18.2.0): TypeScript types for React
|
|
145
|
-
|
|
146
|
-
### Dependencies Unchanged
|
|
147
|
-
|
|
148
|
-
- **commander** (^12.1.0): CLI argument parsing
|
|
149
|
-
- **picocolors** (^1.1.1): Terminal color output
|
|
150
|
-
|
|
151
|
-
### Breaking Changes
|
|
152
|
-
|
|
153
|
-
None. The tool maintains backward compatibility:
|
|
154
|
-
- `--no-flat` flag allows using legacy nested structure
|
|
155
|
-
- Existing metadata files are automatically migrated
|
|
156
|
-
- All previous commands and options continue to work
|
|
157
|
-
|
|
158
|
-
### Security
|
|
159
|
-
|
|
160
|
-
- No security vulnerabilities introduced
|
|
161
|
-
- Confirmation prompts for destructive operations
|
|
162
|
-
- Dry-run mode for all write operations
|
|
163
|
-
- Safe file system operations with error handling
|
|
164
|
-
|
|
165
|
-
### Performance
|
|
166
|
-
|
|
167
|
-
- Efficient metadata reading and writing
|
|
168
|
-
- Cached remote version checks reduce API calls
|
|
169
|
-
- Parallel package processing capability ready (infrastructure in place)
|
|
170
|
-
- Minimal memory overhead for large package lists
|
|
171
|
-
|
|
172
|
-
### Compatibility
|
|
173
|
-
|
|
174
|
-
- Node.js: Compatible with modern Node versions supporting ES modules
|
|
175
|
-
- Operating Systems: Windows, macOS, Linux
|
|
176
|
-
- npm Packages: Works with all npm packages providing claude assets
|
|
177
|
-
|
|
178
|
-
## [Unreleased]
|
|
179
|
-
|
|
180
|
-
### Planned
|
|
181
|
-
|
|
182
|
-
- Interactive UI components for progress visualization
|
|
183
|
-
- Batch operations with progress indication
|
|
184
|
-
- Configuration file support (.claude-sync.json)
|
|
185
|
-
- Pre/post sync hooks
|
|
186
|
-
- Asset validation and schema checking
|
|
187
|
-
- Custom asset type support
|
|
188
|
-
- Asset versioning and conflict resolution
|
|
189
|
-
- Cloud storage integration (optional)
|