rulesync 6.6.1 → 6.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/index.cjs +120 -71
- package/dist/index.js +120 -71
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,9 @@ Get-FileHash rulesync.exe -Algorithm SHA256 | ForEach-Object {
|
|
|
120
120
|
```bash
|
|
121
121
|
# Create necessary directories, sample rule files, and configuration file
|
|
122
122
|
npx rulesync init
|
|
123
|
+
|
|
124
|
+
# Install official skills (recommended)
|
|
125
|
+
npx rulesync fetch dyoshikawa/rulesync --features skills
|
|
123
126
|
```
|
|
124
127
|
|
|
125
128
|
On the other hand, if you already have AI tool configurations:
|
|
@@ -231,7 +234,7 @@ npx rulesync generate --targets "*" --features rules
|
|
|
231
234
|
# Generate simulated commands and subagents
|
|
232
235
|
npx rulesync generate --targets copilot,cursor,codexcli --features commands,subagents --simulate-commands --simulate-subagents
|
|
233
236
|
|
|
234
|
-
#
|
|
237
|
+
# Dry run: show changes without writing files
|
|
235
238
|
npx rulesync generate --dry-run --targets claudecode --features rules
|
|
236
239
|
|
|
237
240
|
# Check if files are up to date (for CI/CD pipelines)
|
|
@@ -250,13 +253,13 @@ npx rulesync update --check
|
|
|
250
253
|
npx rulesync update --force
|
|
251
254
|
```
|
|
252
255
|
|
|
253
|
-
##
|
|
256
|
+
## Dry Run
|
|
254
257
|
|
|
255
|
-
Rulesync provides two
|
|
258
|
+
Rulesync provides two dry run options for the `generate` command that allow you to see what changes would be made without actually writing files:
|
|
256
259
|
|
|
257
260
|
### `--dry-run`
|
|
258
261
|
|
|
259
|
-
|
|
262
|
+
Show what would be written or deleted without actually writing any files. Changes are displayed with a `[DRY RUN]` prefix.
|
|
260
263
|
|
|
261
264
|
```bash
|
|
262
265
|
npx rulesync generate --dry-run --targets claudecode --features rules
|
|
@@ -333,6 +336,9 @@ npx rulesync fetch owner/repo@v1.0.0 --features rules,commands
|
|
|
333
336
|
export GITHUB_TOKEN=ghp_xxxx
|
|
334
337
|
npx rulesync fetch owner/private-repo
|
|
335
338
|
|
|
339
|
+
# Or use GitHub CLI to get the token
|
|
340
|
+
GITHUB_TOKEN=$(gh auth token) npx rulesync fetch owner/private-repo
|
|
341
|
+
|
|
336
342
|
# Preserve existing files (skip conflicts)
|
|
337
343
|
npx rulesync fetch owner/repo --conflict skip
|
|
338
344
|
|
package/dist/index.cjs
CHANGED
|
@@ -125,6 +125,7 @@ var Logger = class {
|
|
|
125
125
|
var logger = new Logger();
|
|
126
126
|
|
|
127
127
|
// src/lib/fetch.ts
|
|
128
|
+
var import_promise = require("es-toolkit/promise");
|
|
128
129
|
var import_node_path107 = require("path");
|
|
129
130
|
|
|
130
131
|
// src/constants/rulesync-paths.ts
|
|
@@ -13546,6 +13547,17 @@ var GitHubClientError = class extends Error {
|
|
|
13546
13547
|
this.name = "GitHubClientError";
|
|
13547
13548
|
}
|
|
13548
13549
|
};
|
|
13550
|
+
function logGitHubAuthHints(error) {
|
|
13551
|
+
logger.error(`GitHub API Error: ${error.message}`);
|
|
13552
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
13553
|
+
logger.info(
|
|
13554
|
+
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories or better rate limits."
|
|
13555
|
+
);
|
|
13556
|
+
logger.info(
|
|
13557
|
+
"Tip: If you use GitHub CLI, you can use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`"
|
|
13558
|
+
);
|
|
13559
|
+
}
|
|
13560
|
+
}
|
|
13549
13561
|
var GitHubClient = class {
|
|
13550
13562
|
octokit;
|
|
13551
13563
|
hasToken;
|
|
@@ -13863,6 +13875,15 @@ function parseSource(source) {
|
|
|
13863
13875
|
var GITHUB_HOSTS = /* @__PURE__ */ new Set(["github.com", "www.github.com"]);
|
|
13864
13876
|
var GITLAB_HOSTS = /* @__PURE__ */ new Set(["gitlab.com", "www.gitlab.com"]);
|
|
13865
13877
|
var MAX_RECURSION_DEPTH = 15;
|
|
13878
|
+
var FETCH_CONCURRENCY_LIMIT = 10;
|
|
13879
|
+
async function withSemaphore(semaphore, fn) {
|
|
13880
|
+
await semaphore.acquire();
|
|
13881
|
+
try {
|
|
13882
|
+
return await fn();
|
|
13883
|
+
} finally {
|
|
13884
|
+
semaphore.release();
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13866
13887
|
function parseUrl(url) {
|
|
13867
13888
|
const urlObj = new URL(url);
|
|
13868
13889
|
const host = urlObj.hostname.toLowerCase();
|
|
@@ -14002,13 +14023,15 @@ async function fetchFiles(params) {
|
|
|
14002
14023
|
conflictStrategy
|
|
14003
14024
|
});
|
|
14004
14025
|
}
|
|
14026
|
+
const semaphore = new import_promise.Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
14005
14027
|
const filesToFetch = await collectFeatureFiles({
|
|
14006
14028
|
client,
|
|
14007
14029
|
owner: parsed.owner,
|
|
14008
14030
|
repo: parsed.repo,
|
|
14009
14031
|
basePath: resolvedPath,
|
|
14010
14032
|
ref,
|
|
14011
|
-
enabledFeatures
|
|
14033
|
+
enabledFeatures,
|
|
14034
|
+
semaphore
|
|
14012
14035
|
});
|
|
14013
14036
|
if (filesToFetch.length === 0) {
|
|
14014
14037
|
logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
@@ -14021,9 +14044,8 @@ async function fetchFiles(params) {
|
|
|
14021
14044
|
skipped: 0
|
|
14022
14045
|
};
|
|
14023
14046
|
}
|
|
14024
|
-
const results = [];
|
|
14025
14047
|
const outputBasePath = (0, import_node_path107.join)(baseDir, outputDir);
|
|
14026
|
-
for (const {
|
|
14048
|
+
for (const { relativePath, size } of filesToFetch) {
|
|
14027
14049
|
checkPathTraversal({
|
|
14028
14050
|
relativePath,
|
|
14029
14051
|
intendedRootDir: outputBasePath
|
|
@@ -14033,20 +14055,25 @@ async function fetchFiles(params) {
|
|
|
14033
14055
|
`File "${relativePath}" exceeds maximum size limit (${(size / 1024 / 1024).toFixed(2)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB)`
|
|
14034
14056
|
);
|
|
14035
14057
|
}
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14058
|
+
}
|
|
14059
|
+
const results = await Promise.all(
|
|
14060
|
+
filesToFetch.map(async ({ remotePath, relativePath }) => {
|
|
14061
|
+
const localPath = (0, import_node_path107.join)(outputBasePath, relativePath);
|
|
14062
|
+
const exists = await fileExists(localPath);
|
|
14063
|
+
if (exists && conflictStrategy === "skip") {
|
|
14064
|
+
logger.debug(`Skipping existing file: ${relativePath}`);
|
|
14065
|
+
return { relativePath, status: "skipped" };
|
|
14066
|
+
}
|
|
14067
|
+
const content = await withSemaphore(
|
|
14068
|
+
semaphore,
|
|
14069
|
+
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
14070
|
+
);
|
|
14044
14071
|
await writeFileContent(localPath, content);
|
|
14045
|
-
status = exists ? "overwritten" : "created";
|
|
14072
|
+
const status = exists ? "overwritten" : "created";
|
|
14046
14073
|
logger.debug(`Wrote: ${relativePath} (${status})`);
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14074
|
+
return { relativePath, status };
|
|
14075
|
+
})
|
|
14076
|
+
);
|
|
14050
14077
|
const summary = {
|
|
14051
14078
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
14052
14079
|
ref,
|
|
@@ -14058,24 +14085,29 @@ async function fetchFiles(params) {
|
|
|
14058
14085
|
return summary;
|
|
14059
14086
|
}
|
|
14060
14087
|
async function collectFeatureFiles(params) {
|
|
14061
|
-
const { client, owner, repo, basePath, ref, enabledFeatures } = params;
|
|
14062
|
-
const
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14088
|
+
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore } = params;
|
|
14089
|
+
const tasks = enabledFeatures.flatMap(
|
|
14090
|
+
(feature) => FEATURE_PATHS[feature].map((featurePath) => ({ feature, featurePath }))
|
|
14091
|
+
);
|
|
14092
|
+
const results = await Promise.all(
|
|
14093
|
+
tasks.map(async ({ featurePath }) => {
|
|
14066
14094
|
const fullPath = basePath === "." || basePath === "" ? featurePath : (0, import_node_path107.join)(basePath, featurePath);
|
|
14095
|
+
const collected = [];
|
|
14067
14096
|
try {
|
|
14068
14097
|
if (featurePath.includes(".")) {
|
|
14069
14098
|
try {
|
|
14070
|
-
const entries = await
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14099
|
+
const entries = await withSemaphore(
|
|
14100
|
+
semaphore,
|
|
14101
|
+
() => client.listDirectory(
|
|
14102
|
+
owner,
|
|
14103
|
+
repo,
|
|
14104
|
+
basePath === "." || basePath === "" ? "." : basePath,
|
|
14105
|
+
ref
|
|
14106
|
+
)
|
|
14075
14107
|
);
|
|
14076
14108
|
const fileEntry = entries.find((e) => e.name === featurePath && e.type === "file");
|
|
14077
14109
|
if (fileEntry) {
|
|
14078
|
-
|
|
14110
|
+
collected.push({
|
|
14079
14111
|
remotePath: fileEntry.path,
|
|
14080
14112
|
relativePath: featurePath,
|
|
14081
14113
|
size: fileEntry.size
|
|
@@ -14089,10 +14121,17 @@ async function collectFeatureFiles(params) {
|
|
|
14089
14121
|
}
|
|
14090
14122
|
}
|
|
14091
14123
|
} else {
|
|
14092
|
-
const dirFiles = await listDirectoryRecursive(
|
|
14124
|
+
const dirFiles = await listDirectoryRecursive({
|
|
14125
|
+
client,
|
|
14126
|
+
owner,
|
|
14127
|
+
repo,
|
|
14128
|
+
path: fullPath,
|
|
14129
|
+
ref,
|
|
14130
|
+
semaphore
|
|
14131
|
+
});
|
|
14093
14132
|
for (const file of dirFiles) {
|
|
14094
14133
|
const relativePath = basePath === "." || basePath === "" ? file.path : file.path.substring(basePath.length + 1);
|
|
14095
|
-
|
|
14134
|
+
collected.push({
|
|
14096
14135
|
remotePath: file.path,
|
|
14097
14136
|
relativePath,
|
|
14098
14137
|
size: file.size
|
|
@@ -14102,38 +14141,49 @@ async function collectFeatureFiles(params) {
|
|
|
14102
14141
|
} catch (error) {
|
|
14103
14142
|
if (isNotFoundError(error)) {
|
|
14104
14143
|
logger.debug(`Feature not found: ${fullPath}`);
|
|
14105
|
-
|
|
14144
|
+
return collected;
|
|
14106
14145
|
}
|
|
14107
14146
|
throw error;
|
|
14108
14147
|
}
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14148
|
+
return collected;
|
|
14149
|
+
})
|
|
14150
|
+
);
|
|
14151
|
+
return results.flat();
|
|
14112
14152
|
}
|
|
14113
|
-
async function listDirectoryRecursive(
|
|
14153
|
+
async function listDirectoryRecursive(params) {
|
|
14154
|
+
const { client, owner, repo, path: path4, ref, depth = 0, semaphore } = params;
|
|
14114
14155
|
if (depth > MAX_RECURSION_DEPTH) {
|
|
14115
14156
|
throw new Error(
|
|
14116
14157
|
`Maximum recursion depth (${MAX_RECURSION_DEPTH}) exceeded while listing directory: ${path4}`
|
|
14117
14158
|
);
|
|
14118
14159
|
}
|
|
14119
|
-
const entries = await
|
|
14160
|
+
const entries = await withSemaphore(
|
|
14161
|
+
semaphore,
|
|
14162
|
+
() => client.listDirectory(owner, repo, path4, ref)
|
|
14163
|
+
);
|
|
14120
14164
|
const files = [];
|
|
14165
|
+
const directories = [];
|
|
14121
14166
|
for (const entry of entries) {
|
|
14122
14167
|
if (entry.type === "file") {
|
|
14123
14168
|
files.push(entry);
|
|
14124
14169
|
} else if (entry.type === "dir") {
|
|
14125
|
-
|
|
14170
|
+
directories.push(entry);
|
|
14171
|
+
}
|
|
14172
|
+
}
|
|
14173
|
+
const subResults = await Promise.all(
|
|
14174
|
+
directories.map(
|
|
14175
|
+
(dir) => listDirectoryRecursive({
|
|
14126
14176
|
client,
|
|
14127
14177
|
owner,
|
|
14128
14178
|
repo,
|
|
14129
|
-
|
|
14179
|
+
path: dir.path,
|
|
14130
14180
|
ref,
|
|
14131
|
-
depth + 1
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
return files;
|
|
14181
|
+
depth: depth + 1,
|
|
14182
|
+
semaphore
|
|
14183
|
+
})
|
|
14184
|
+
)
|
|
14185
|
+
);
|
|
14186
|
+
return [...files, ...subResults.flat()];
|
|
14137
14187
|
}
|
|
14138
14188
|
async function fetchAndConvertToolFiles(params) {
|
|
14139
14189
|
const {
|
|
@@ -14149,6 +14199,7 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14149
14199
|
} = params;
|
|
14150
14200
|
const tempDir = await createTempDirectory();
|
|
14151
14201
|
logger.debug(`Created temp directory: ${tempDir}`);
|
|
14202
|
+
const semaphore = new import_promise.Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
14152
14203
|
try {
|
|
14153
14204
|
const filesToFetch = await collectFeatureFiles({
|
|
14154
14205
|
client,
|
|
@@ -14156,7 +14207,8 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14156
14207
|
repo: parsed.repo,
|
|
14157
14208
|
basePath: resolvedPath,
|
|
14158
14209
|
ref,
|
|
14159
|
-
enabledFeatures
|
|
14210
|
+
enabledFeatures,
|
|
14211
|
+
semaphore
|
|
14160
14212
|
});
|
|
14161
14213
|
if (filesToFetch.length === 0) {
|
|
14162
14214
|
logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
@@ -14169,25 +14221,31 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14169
14221
|
skipped: 0
|
|
14170
14222
|
};
|
|
14171
14223
|
}
|
|
14172
|
-
const
|
|
14173
|
-
const fetchedFiles = [];
|
|
14174
|
-
for (const { remotePath, relativePath, size } of filesToFetch) {
|
|
14224
|
+
for (const { relativePath, size } of filesToFetch) {
|
|
14175
14225
|
if (size > MAX_FILE_SIZE) {
|
|
14176
14226
|
throw new GitHubClientError(
|
|
14177
14227
|
`File "${relativePath}" exceeds maximum size limit (${(size / 1024 / 1024).toFixed(2)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB)`
|
|
14178
14228
|
);
|
|
14179
14229
|
}
|
|
14180
|
-
const toolRelativePath = mapToToolPath(relativePath, toolPaths);
|
|
14181
|
-
checkPathTraversal({
|
|
14182
|
-
relativePath: toolRelativePath,
|
|
14183
|
-
intendedRootDir: tempDir
|
|
14184
|
-
});
|
|
14185
|
-
const localPath = (0, import_node_path107.join)(tempDir, toolRelativePath);
|
|
14186
|
-
const content = await client.getFileContent(parsed.owner, parsed.repo, remotePath, ref);
|
|
14187
|
-
await writeFileContent(localPath, content);
|
|
14188
|
-
fetchedFiles.push(toolRelativePath);
|
|
14189
|
-
logger.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
14190
14230
|
}
|
|
14231
|
+
const toolPaths = getToolPathMapping(target);
|
|
14232
|
+
await Promise.all(
|
|
14233
|
+
filesToFetch.map(async ({ remotePath, relativePath }) => {
|
|
14234
|
+
const toolRelativePath = mapToToolPath(relativePath, toolPaths);
|
|
14235
|
+
checkPathTraversal({
|
|
14236
|
+
relativePath: toolRelativePath,
|
|
14237
|
+
intendedRootDir: tempDir
|
|
14238
|
+
});
|
|
14239
|
+
const localPath = (0, import_node_path107.join)(tempDir, toolRelativePath);
|
|
14240
|
+
const content = await withSemaphore(
|
|
14241
|
+
semaphore,
|
|
14242
|
+
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
14243
|
+
);
|
|
14244
|
+
await writeFileContent(localPath, content);
|
|
14245
|
+
logger.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
14246
|
+
return toolRelativePath;
|
|
14247
|
+
})
|
|
14248
|
+
);
|
|
14191
14249
|
const outputBasePath = (0, import_node_path107.join)(baseDir, outputDir);
|
|
14192
14250
|
const { converted, convertedPaths } = await convertFetchedFilesToRulesync({
|
|
14193
14251
|
tempDir,
|
|
@@ -14325,12 +14383,7 @@ async function fetchCommand(options) {
|
|
|
14325
14383
|
}
|
|
14326
14384
|
} catch (error) {
|
|
14327
14385
|
if (error instanceof GitHubClientError) {
|
|
14328
|
-
|
|
14329
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
14330
|
-
logger.info(
|
|
14331
|
-
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories."
|
|
14332
|
-
);
|
|
14333
|
-
}
|
|
14386
|
+
logGitHubAuthHints(error);
|
|
14334
14387
|
} else {
|
|
14335
14388
|
logger.error(formatError(error));
|
|
14336
14389
|
}
|
|
@@ -17201,7 +17254,8 @@ var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
|
|
|
17201
17254
|
var ALLOWED_DOWNLOAD_DOMAINS = [
|
|
17202
17255
|
"github.com",
|
|
17203
17256
|
"objects.githubusercontent.com",
|
|
17204
|
-
"github-releases.githubusercontent.com"
|
|
17257
|
+
"github-releases.githubusercontent.com",
|
|
17258
|
+
"release-assets.githubusercontent.com"
|
|
17205
17259
|
];
|
|
17206
17260
|
var UpdatePermissionError = class extends Error {
|
|
17207
17261
|
constructor(message) {
|
|
@@ -17527,12 +17581,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
17527
17581
|
logger.success(message);
|
|
17528
17582
|
} catch (error) {
|
|
17529
17583
|
if (error instanceof GitHubClientError) {
|
|
17530
|
-
|
|
17531
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
17532
|
-
logger.info(
|
|
17533
|
-
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for better rate limits."
|
|
17534
|
-
);
|
|
17535
|
-
}
|
|
17584
|
+
logGitHubAuthHints(error);
|
|
17536
17585
|
} else if (error instanceof UpdatePermissionError) {
|
|
17537
17586
|
logger.error(error.message);
|
|
17538
17587
|
logger.info("Tip: Run with elevated privileges (e.g., sudo rulesync update)");
|
|
@@ -17544,7 +17593,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
17544
17593
|
}
|
|
17545
17594
|
|
|
17546
17595
|
// src/cli/index.ts
|
|
17547
|
-
var getVersion = () => "6.6.
|
|
17596
|
+
var getVersion = () => "6.6.2";
|
|
17548
17597
|
var main = async () => {
|
|
17549
17598
|
const program = new import_commander.Command();
|
|
17550
17599
|
const version = getVersion();
|
|
@@ -17644,7 +17693,7 @@ var main = async () => {
|
|
|
17644
17693
|
).option(
|
|
17645
17694
|
"--modular-mcp",
|
|
17646
17695
|
"Generate modular-mcp configuration for context compression (experimental)"
|
|
17647
|
-
).option("--dry-run", "
|
|
17696
|
+
).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(async (options) => {
|
|
17648
17697
|
try {
|
|
17649
17698
|
await generateCommand({
|
|
17650
17699
|
targets: options.targets,
|
package/dist/index.js
CHANGED
|
@@ -102,6 +102,7 @@ var Logger = class {
|
|
|
102
102
|
var logger = new Logger();
|
|
103
103
|
|
|
104
104
|
// src/lib/fetch.ts
|
|
105
|
+
import { Semaphore } from "es-toolkit/promise";
|
|
105
106
|
import { join as join106 } from "path";
|
|
106
107
|
|
|
107
108
|
// src/constants/rulesync-paths.ts
|
|
@@ -13523,6 +13524,17 @@ var GitHubClientError = class extends Error {
|
|
|
13523
13524
|
this.name = "GitHubClientError";
|
|
13524
13525
|
}
|
|
13525
13526
|
};
|
|
13527
|
+
function logGitHubAuthHints(error) {
|
|
13528
|
+
logger.error(`GitHub API Error: ${error.message}`);
|
|
13529
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
13530
|
+
logger.info(
|
|
13531
|
+
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories or better rate limits."
|
|
13532
|
+
);
|
|
13533
|
+
logger.info(
|
|
13534
|
+
"Tip: If you use GitHub CLI, you can use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`"
|
|
13535
|
+
);
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13526
13538
|
var GitHubClient = class {
|
|
13527
13539
|
octokit;
|
|
13528
13540
|
hasToken;
|
|
@@ -13840,6 +13852,15 @@ function parseSource(source) {
|
|
|
13840
13852
|
var GITHUB_HOSTS = /* @__PURE__ */ new Set(["github.com", "www.github.com"]);
|
|
13841
13853
|
var GITLAB_HOSTS = /* @__PURE__ */ new Set(["gitlab.com", "www.gitlab.com"]);
|
|
13842
13854
|
var MAX_RECURSION_DEPTH = 15;
|
|
13855
|
+
var FETCH_CONCURRENCY_LIMIT = 10;
|
|
13856
|
+
async function withSemaphore(semaphore, fn) {
|
|
13857
|
+
await semaphore.acquire();
|
|
13858
|
+
try {
|
|
13859
|
+
return await fn();
|
|
13860
|
+
} finally {
|
|
13861
|
+
semaphore.release();
|
|
13862
|
+
}
|
|
13863
|
+
}
|
|
13843
13864
|
function parseUrl(url) {
|
|
13844
13865
|
const urlObj = new URL(url);
|
|
13845
13866
|
const host = urlObj.hostname.toLowerCase();
|
|
@@ -13979,13 +14000,15 @@ async function fetchFiles(params) {
|
|
|
13979
14000
|
conflictStrategy
|
|
13980
14001
|
});
|
|
13981
14002
|
}
|
|
14003
|
+
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
13982
14004
|
const filesToFetch = await collectFeatureFiles({
|
|
13983
14005
|
client,
|
|
13984
14006
|
owner: parsed.owner,
|
|
13985
14007
|
repo: parsed.repo,
|
|
13986
14008
|
basePath: resolvedPath,
|
|
13987
14009
|
ref,
|
|
13988
|
-
enabledFeatures
|
|
14010
|
+
enabledFeatures,
|
|
14011
|
+
semaphore
|
|
13989
14012
|
});
|
|
13990
14013
|
if (filesToFetch.length === 0) {
|
|
13991
14014
|
logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
@@ -13998,9 +14021,8 @@ async function fetchFiles(params) {
|
|
|
13998
14021
|
skipped: 0
|
|
13999
14022
|
};
|
|
14000
14023
|
}
|
|
14001
|
-
const results = [];
|
|
14002
14024
|
const outputBasePath = join106(baseDir, outputDir);
|
|
14003
|
-
for (const {
|
|
14025
|
+
for (const { relativePath, size } of filesToFetch) {
|
|
14004
14026
|
checkPathTraversal({
|
|
14005
14027
|
relativePath,
|
|
14006
14028
|
intendedRootDir: outputBasePath
|
|
@@ -14010,20 +14032,25 @@ async function fetchFiles(params) {
|
|
|
14010
14032
|
`File "${relativePath}" exceeds maximum size limit (${(size / 1024 / 1024).toFixed(2)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB)`
|
|
14011
14033
|
);
|
|
14012
14034
|
}
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14035
|
+
}
|
|
14036
|
+
const results = await Promise.all(
|
|
14037
|
+
filesToFetch.map(async ({ remotePath, relativePath }) => {
|
|
14038
|
+
const localPath = join106(outputBasePath, relativePath);
|
|
14039
|
+
const exists = await fileExists(localPath);
|
|
14040
|
+
if (exists && conflictStrategy === "skip") {
|
|
14041
|
+
logger.debug(`Skipping existing file: ${relativePath}`);
|
|
14042
|
+
return { relativePath, status: "skipped" };
|
|
14043
|
+
}
|
|
14044
|
+
const content = await withSemaphore(
|
|
14045
|
+
semaphore,
|
|
14046
|
+
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
14047
|
+
);
|
|
14021
14048
|
await writeFileContent(localPath, content);
|
|
14022
|
-
status = exists ? "overwritten" : "created";
|
|
14049
|
+
const status = exists ? "overwritten" : "created";
|
|
14023
14050
|
logger.debug(`Wrote: ${relativePath} (${status})`);
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14051
|
+
return { relativePath, status };
|
|
14052
|
+
})
|
|
14053
|
+
);
|
|
14027
14054
|
const summary = {
|
|
14028
14055
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
14029
14056
|
ref,
|
|
@@ -14035,24 +14062,29 @@ async function fetchFiles(params) {
|
|
|
14035
14062
|
return summary;
|
|
14036
14063
|
}
|
|
14037
14064
|
async function collectFeatureFiles(params) {
|
|
14038
|
-
const { client, owner, repo, basePath, ref, enabledFeatures } = params;
|
|
14039
|
-
const
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14065
|
+
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore } = params;
|
|
14066
|
+
const tasks = enabledFeatures.flatMap(
|
|
14067
|
+
(feature) => FEATURE_PATHS[feature].map((featurePath) => ({ feature, featurePath }))
|
|
14068
|
+
);
|
|
14069
|
+
const results = await Promise.all(
|
|
14070
|
+
tasks.map(async ({ featurePath }) => {
|
|
14043
14071
|
const fullPath = basePath === "." || basePath === "" ? featurePath : join106(basePath, featurePath);
|
|
14072
|
+
const collected = [];
|
|
14044
14073
|
try {
|
|
14045
14074
|
if (featurePath.includes(".")) {
|
|
14046
14075
|
try {
|
|
14047
|
-
const entries = await
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14076
|
+
const entries = await withSemaphore(
|
|
14077
|
+
semaphore,
|
|
14078
|
+
() => client.listDirectory(
|
|
14079
|
+
owner,
|
|
14080
|
+
repo,
|
|
14081
|
+
basePath === "." || basePath === "" ? "." : basePath,
|
|
14082
|
+
ref
|
|
14083
|
+
)
|
|
14052
14084
|
);
|
|
14053
14085
|
const fileEntry = entries.find((e) => e.name === featurePath && e.type === "file");
|
|
14054
14086
|
if (fileEntry) {
|
|
14055
|
-
|
|
14087
|
+
collected.push({
|
|
14056
14088
|
remotePath: fileEntry.path,
|
|
14057
14089
|
relativePath: featurePath,
|
|
14058
14090
|
size: fileEntry.size
|
|
@@ -14066,10 +14098,17 @@ async function collectFeatureFiles(params) {
|
|
|
14066
14098
|
}
|
|
14067
14099
|
}
|
|
14068
14100
|
} else {
|
|
14069
|
-
const dirFiles = await listDirectoryRecursive(
|
|
14101
|
+
const dirFiles = await listDirectoryRecursive({
|
|
14102
|
+
client,
|
|
14103
|
+
owner,
|
|
14104
|
+
repo,
|
|
14105
|
+
path: fullPath,
|
|
14106
|
+
ref,
|
|
14107
|
+
semaphore
|
|
14108
|
+
});
|
|
14070
14109
|
for (const file of dirFiles) {
|
|
14071
14110
|
const relativePath = basePath === "." || basePath === "" ? file.path : file.path.substring(basePath.length + 1);
|
|
14072
|
-
|
|
14111
|
+
collected.push({
|
|
14073
14112
|
remotePath: file.path,
|
|
14074
14113
|
relativePath,
|
|
14075
14114
|
size: file.size
|
|
@@ -14079,38 +14118,49 @@ async function collectFeatureFiles(params) {
|
|
|
14079
14118
|
} catch (error) {
|
|
14080
14119
|
if (isNotFoundError(error)) {
|
|
14081
14120
|
logger.debug(`Feature not found: ${fullPath}`);
|
|
14082
|
-
|
|
14121
|
+
return collected;
|
|
14083
14122
|
}
|
|
14084
14123
|
throw error;
|
|
14085
14124
|
}
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14125
|
+
return collected;
|
|
14126
|
+
})
|
|
14127
|
+
);
|
|
14128
|
+
return results.flat();
|
|
14089
14129
|
}
|
|
14090
|
-
async function listDirectoryRecursive(
|
|
14130
|
+
async function listDirectoryRecursive(params) {
|
|
14131
|
+
const { client, owner, repo, path: path4, ref, depth = 0, semaphore } = params;
|
|
14091
14132
|
if (depth > MAX_RECURSION_DEPTH) {
|
|
14092
14133
|
throw new Error(
|
|
14093
14134
|
`Maximum recursion depth (${MAX_RECURSION_DEPTH}) exceeded while listing directory: ${path4}`
|
|
14094
14135
|
);
|
|
14095
14136
|
}
|
|
14096
|
-
const entries = await
|
|
14137
|
+
const entries = await withSemaphore(
|
|
14138
|
+
semaphore,
|
|
14139
|
+
() => client.listDirectory(owner, repo, path4, ref)
|
|
14140
|
+
);
|
|
14097
14141
|
const files = [];
|
|
14142
|
+
const directories = [];
|
|
14098
14143
|
for (const entry of entries) {
|
|
14099
14144
|
if (entry.type === "file") {
|
|
14100
14145
|
files.push(entry);
|
|
14101
14146
|
} else if (entry.type === "dir") {
|
|
14102
|
-
|
|
14147
|
+
directories.push(entry);
|
|
14148
|
+
}
|
|
14149
|
+
}
|
|
14150
|
+
const subResults = await Promise.all(
|
|
14151
|
+
directories.map(
|
|
14152
|
+
(dir) => listDirectoryRecursive({
|
|
14103
14153
|
client,
|
|
14104
14154
|
owner,
|
|
14105
14155
|
repo,
|
|
14106
|
-
|
|
14156
|
+
path: dir.path,
|
|
14107
14157
|
ref,
|
|
14108
|
-
depth + 1
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
return files;
|
|
14158
|
+
depth: depth + 1,
|
|
14159
|
+
semaphore
|
|
14160
|
+
})
|
|
14161
|
+
)
|
|
14162
|
+
);
|
|
14163
|
+
return [...files, ...subResults.flat()];
|
|
14114
14164
|
}
|
|
14115
14165
|
async function fetchAndConvertToolFiles(params) {
|
|
14116
14166
|
const {
|
|
@@ -14126,6 +14176,7 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14126
14176
|
} = params;
|
|
14127
14177
|
const tempDir = await createTempDirectory();
|
|
14128
14178
|
logger.debug(`Created temp directory: ${tempDir}`);
|
|
14179
|
+
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
14129
14180
|
try {
|
|
14130
14181
|
const filesToFetch = await collectFeatureFiles({
|
|
14131
14182
|
client,
|
|
@@ -14133,7 +14184,8 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14133
14184
|
repo: parsed.repo,
|
|
14134
14185
|
basePath: resolvedPath,
|
|
14135
14186
|
ref,
|
|
14136
|
-
enabledFeatures
|
|
14187
|
+
enabledFeatures,
|
|
14188
|
+
semaphore
|
|
14137
14189
|
});
|
|
14138
14190
|
if (filesToFetch.length === 0) {
|
|
14139
14191
|
logger.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
@@ -14146,25 +14198,31 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
14146
14198
|
skipped: 0
|
|
14147
14199
|
};
|
|
14148
14200
|
}
|
|
14149
|
-
const
|
|
14150
|
-
const fetchedFiles = [];
|
|
14151
|
-
for (const { remotePath, relativePath, size } of filesToFetch) {
|
|
14201
|
+
for (const { relativePath, size } of filesToFetch) {
|
|
14152
14202
|
if (size > MAX_FILE_SIZE) {
|
|
14153
14203
|
throw new GitHubClientError(
|
|
14154
14204
|
`File "${relativePath}" exceeds maximum size limit (${(size / 1024 / 1024).toFixed(2)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB)`
|
|
14155
14205
|
);
|
|
14156
14206
|
}
|
|
14157
|
-
const toolRelativePath = mapToToolPath(relativePath, toolPaths);
|
|
14158
|
-
checkPathTraversal({
|
|
14159
|
-
relativePath: toolRelativePath,
|
|
14160
|
-
intendedRootDir: tempDir
|
|
14161
|
-
});
|
|
14162
|
-
const localPath = join106(tempDir, toolRelativePath);
|
|
14163
|
-
const content = await client.getFileContent(parsed.owner, parsed.repo, remotePath, ref);
|
|
14164
|
-
await writeFileContent(localPath, content);
|
|
14165
|
-
fetchedFiles.push(toolRelativePath);
|
|
14166
|
-
logger.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
14167
14207
|
}
|
|
14208
|
+
const toolPaths = getToolPathMapping(target);
|
|
14209
|
+
await Promise.all(
|
|
14210
|
+
filesToFetch.map(async ({ remotePath, relativePath }) => {
|
|
14211
|
+
const toolRelativePath = mapToToolPath(relativePath, toolPaths);
|
|
14212
|
+
checkPathTraversal({
|
|
14213
|
+
relativePath: toolRelativePath,
|
|
14214
|
+
intendedRootDir: tempDir
|
|
14215
|
+
});
|
|
14216
|
+
const localPath = join106(tempDir, toolRelativePath);
|
|
14217
|
+
const content = await withSemaphore(
|
|
14218
|
+
semaphore,
|
|
14219
|
+
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
14220
|
+
);
|
|
14221
|
+
await writeFileContent(localPath, content);
|
|
14222
|
+
logger.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
14223
|
+
return toolRelativePath;
|
|
14224
|
+
})
|
|
14225
|
+
);
|
|
14168
14226
|
const outputBasePath = join106(baseDir, outputDir);
|
|
14169
14227
|
const { converted, convertedPaths } = await convertFetchedFilesToRulesync({
|
|
14170
14228
|
tempDir,
|
|
@@ -14302,12 +14360,7 @@ async function fetchCommand(options) {
|
|
|
14302
14360
|
}
|
|
14303
14361
|
} catch (error) {
|
|
14304
14362
|
if (error instanceof GitHubClientError) {
|
|
14305
|
-
|
|
14306
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
14307
|
-
logger.info(
|
|
14308
|
-
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories."
|
|
14309
|
-
);
|
|
14310
|
-
}
|
|
14363
|
+
logGitHubAuthHints(error);
|
|
14311
14364
|
} else {
|
|
14312
14365
|
logger.error(formatError(error));
|
|
14313
14366
|
}
|
|
@@ -17178,7 +17231,8 @@ var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
|
|
|
17178
17231
|
var ALLOWED_DOWNLOAD_DOMAINS = [
|
|
17179
17232
|
"github.com",
|
|
17180
17233
|
"objects.githubusercontent.com",
|
|
17181
|
-
"github-releases.githubusercontent.com"
|
|
17234
|
+
"github-releases.githubusercontent.com",
|
|
17235
|
+
"release-assets.githubusercontent.com"
|
|
17182
17236
|
];
|
|
17183
17237
|
var UpdatePermissionError = class extends Error {
|
|
17184
17238
|
constructor(message) {
|
|
@@ -17504,12 +17558,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
17504
17558
|
logger.success(message);
|
|
17505
17559
|
} catch (error) {
|
|
17506
17560
|
if (error instanceof GitHubClientError) {
|
|
17507
|
-
|
|
17508
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
17509
|
-
logger.info(
|
|
17510
|
-
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for better rate limits."
|
|
17511
|
-
);
|
|
17512
|
-
}
|
|
17561
|
+
logGitHubAuthHints(error);
|
|
17513
17562
|
} else if (error instanceof UpdatePermissionError) {
|
|
17514
17563
|
logger.error(error.message);
|
|
17515
17564
|
logger.info("Tip: Run with elevated privileges (e.g., sudo rulesync update)");
|
|
@@ -17521,7 +17570,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
17521
17570
|
}
|
|
17522
17571
|
|
|
17523
17572
|
// src/cli/index.ts
|
|
17524
|
-
var getVersion = () => "6.6.
|
|
17573
|
+
var getVersion = () => "6.6.2";
|
|
17525
17574
|
var main = async () => {
|
|
17526
17575
|
const program = new Command();
|
|
17527
17576
|
const version = getVersion();
|
|
@@ -17621,7 +17670,7 @@ var main = async () => {
|
|
|
17621
17670
|
).option(
|
|
17622
17671
|
"--modular-mcp",
|
|
17623
17672
|
"Generate modular-mcp configuration for context compression (experimental)"
|
|
17624
|
-
).option("--dry-run", "
|
|
17673
|
+
).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(async (options) => {
|
|
17625
17674
|
try {
|
|
17626
17675
|
await generateCommand({
|
|
17627
17676
|
targets: options.targets,
|