rulesync 16.0.0 → 16.2.0
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 +7 -7
- package/dist/cli/index.cjs +298 -5
- package/dist/cli/index.js +299 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BT5KR_K-.js → import-CArKOPG_.js} +2435 -504
- package/dist/import-CArKOPG_.js.map +1 -0
- package/dist/{import-Dyo6JaHg.cjs → import-DNfxS6QZ.cjs} +2448 -517
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BT5KR_K-.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as ErrorCodes, $t as RULESYNC_SKILLS_RELATIVE_DIR_PATH, A as RulesyncMcp, At as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, B as stringifyFrontmatter, Bt as RULESYNC_IGNORE_RELATIVE_FILE_PATH, C as RulesyncSubagent, Ct as writeFileContent, D as RulesyncRule, Dt as ToolTargetSchema, E as RulesyncSkillFrontmatterSchema, Et as PACKAGING_TOOL_TARGETS, F as parseJsonc, Ft as RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH, G as ConfigFileSchema, Gt as RULESYNC_MCP_SCHEMA_URL, H as SHARED_USER_MANAGED_CONFIG_PATHS, Ht as RULESYNC_MCP_FILE_NAME, I as RulesyncCommand, It as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, J as ConsoleLogger, Jt as RULESYNC_PERMISSIONS_LEGACY_FILE_NAME, K as SourceEntrySchema, Kt as RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH, L as RulesyncCommandFrontmatterSchema, Lt as RULESYNC_HOOKS_FILE_NAME, M as RulesyncHooks, Mt as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, N as getRulesyncSourceCandidates, Nt as RULESYNC_CONFIG_RELATIVE_FILE_PATH, O as RulesyncRuleFrontmatterSchema, Ot as MAX_FILE_SIZE, P as resolveRulesyncSourceWritePath, Pt as RULESYNC_CONFIG_SCHEMA_URL, Q as CLIError, Qt as RULESYNC_RULES_RELATIVE_DIR_PATH, R as RulesyncCheck, Rt as RULESYNC_HOOKS_LEGACY_FILE_NAME, S as getLocalSkillDirNames, St as toPosixPath, T as RulesyncSkill, Tt as ALL_TOOL_TARGETS_WITH_WILDCARD, U as SKILL_FILE_NAME$1, Ut as RULESYNC_MCP_LEGACY_FILE_NAME, V as loadYaml, Vt as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, W as ConfigResolver, Wt as RULESYNC_MCP_RELATIVE_FILE_PATH, X as fallbackLogger, Xt as RULESYNC_PERMISSIONS_SCHEMA_URL, Y as JsonLogger, Yt as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, Z as warnOnConflictingFlags, Zt as RULESYNC_RELATIVE_DIR_PATH, _ as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, _t as removeFile, a as convertFromTool, at as directoryExists, b as CODEXCLI_BASH_RULES_FILE_NAME, bt as resolvePath, c as SubagentsProcessor, ct as findFilesByGlobs, d as IgnoreProcessor, dt as isSymlink, en as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, et as assertDirectoryIfExists, f as HooksProcessor, ft as listDirectoryFiles, g as CLAUDECODE_MEMORIES_DIR_NAME, gt as removeDirectoryStrict, h as CLAUDECODE_LOCAL_RULE_FILE_NAME, ht as removeDirectory, i as getProcessorRegistryEntry, in as formatError, it as createTempDirectory, j as RulesyncIgnore, jt as RULESYNC_CHECKS_RELATIVE_DIR_PATH, k as RulesyncPermissions, kt as RULESYNC_AIIGNORE_FILE_NAME, l as SkillsProcessor, lt as getFileSize, m as CLAUDECODE_DIR, mt as readFileContentOrNull, n as checkRulesyncDirExists, nn as ALL_FEATURES, nt as assertWritablePathInsideRoot, o as isPackagingToolTarget, ot as ensureDir, p as CommandsProcessor, pt as readFileContent, q as findControlCharacter, qt as RULESYNC_PERMISSIONS_FILE_NAME, r as generate, rn as ALL_FEATURES_WITH_WILDCARD, rt as checkPathTraversal, s as RulesProcessor, st as fileExists, t as importFromTool, tn as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, tt as assertTreeContainsNoSymlinks, u as McpProcessor, ut as getHomeDirectory, v as CLAUDECODE_SKILLS_DIR_PATH, vt as removeFileStrict, w as RulesyncSubagentFrontmatterSchema, wt as ALL_TOOL_TARGETS, x as CODEXCLI_DIR, xt as runWithDirectoryRollback, y as ChecksProcessor, yt as removeTempDirectory, z as RulesyncCheckFrontmatterSchema, zt as RULESYNC_HOOKS_RELATIVE_FILE_PATH } from "../import-
|
|
2
|
+
import { $ as ErrorCodes, $t as RULESYNC_SKILLS_RELATIVE_DIR_PATH, A as RulesyncMcp, At as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, B as stringifyFrontmatter, Bt as RULESYNC_IGNORE_RELATIVE_FILE_PATH, C as RulesyncSubagent, Ct as writeFileContent, D as RulesyncRule, Dt as ToolTargetSchema, E as RulesyncSkillFrontmatterSchema, Et as PACKAGING_TOOL_TARGETS, F as parseJsonc, Ft as RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH, G as ConfigFileSchema, Gt as RULESYNC_MCP_SCHEMA_URL, H as SHARED_USER_MANAGED_CONFIG_PATHS, Ht as RULESYNC_MCP_FILE_NAME, I as RulesyncCommand, It as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, J as ConsoleLogger, Jt as RULESYNC_PERMISSIONS_LEGACY_FILE_NAME, K as SourceEntrySchema, Kt as RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH, L as RulesyncCommandFrontmatterSchema, Lt as RULESYNC_HOOKS_FILE_NAME, M as RulesyncHooks, Mt as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, N as getRulesyncSourceCandidates, Nt as RULESYNC_CONFIG_RELATIVE_FILE_PATH, O as RulesyncRuleFrontmatterSchema, Ot as MAX_FILE_SIZE, P as resolveRulesyncSourceWritePath, Pt as RULESYNC_CONFIG_SCHEMA_URL, Q as CLIError, Qt as RULESYNC_RULES_RELATIVE_DIR_PATH, R as RulesyncCheck, Rt as RULESYNC_HOOKS_LEGACY_FILE_NAME, S as getLocalSkillDirNames, St as toPosixPath, T as RulesyncSkill, Tt as ALL_TOOL_TARGETS_WITH_WILDCARD, U as SKILL_FILE_NAME$1, Ut as RULESYNC_MCP_LEGACY_FILE_NAME, V as loadYaml, Vt as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, W as ConfigResolver, Wt as RULESYNC_MCP_RELATIVE_FILE_PATH, X as fallbackLogger, Xt as RULESYNC_PERMISSIONS_SCHEMA_URL, Y as JsonLogger, Yt as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, Z as warnOnConflictingFlags, Zt as RULESYNC_RELATIVE_DIR_PATH, _ as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, _t as removeFile, a as convertFromTool, at as directoryExists, b as CODEXCLI_BASH_RULES_FILE_NAME, bt as resolvePath, c as SubagentsProcessor, ct as findFilesByGlobs, d as IgnoreProcessor, dt as isSymlink, en as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, et as assertDirectoryIfExists, f as HooksProcessor, ft as listDirectoryFiles, g as CLAUDECODE_MEMORIES_DIR_NAME, gt as removeDirectoryStrict, h as CLAUDECODE_LOCAL_RULE_FILE_NAME, ht as removeDirectory, i as getProcessorRegistryEntry, in as formatError, it as createTempDirectory, j as RulesyncIgnore, jt as RULESYNC_CHECKS_RELATIVE_DIR_PATH, k as RulesyncPermissions, kt as RULESYNC_AIIGNORE_FILE_NAME, l as SkillsProcessor, lt as getFileSize, m as CLAUDECODE_DIR, mt as readFileContentOrNull, n as checkRulesyncDirExists, nn as ALL_FEATURES, nt as assertWritablePathInsideRoot, o as isPackagingToolTarget, ot as ensureDir, p as CommandsProcessor, pt as readFileContent, q as findControlCharacter, qt as RULESYNC_PERMISSIONS_FILE_NAME, r as generate, rn as ALL_FEATURES_WITH_WILDCARD, rt as checkPathTraversal, s as RulesProcessor, st as fileExists, t as importFromTool, tn as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, tt as assertTreeContainsNoSymlinks, u as McpProcessor, ut as getHomeDirectory, v as CLAUDECODE_SKILLS_DIR_PATH, vt as removeFileStrict, w as RulesyncSubagentFrontmatterSchema, wt as ALL_TOOL_TARGETS, x as CODEXCLI_DIR, xt as runWithDirectoryRollback, y as ChecksProcessor, yt as removeTempDirectory, z as RulesyncCheckFrontmatterSchema, zt as RULESYNC_HOOKS_RELATIVE_FILE_PATH } from "../import-CArKOPG_.js";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { nonnegative, optional, refine, z } from "zod/mini";
|
|
5
5
|
import { cp, mkdtemp, realpath, rm } from "node:fs/promises";
|
|
@@ -17,8 +17,9 @@ import { promisify } from "node:util";
|
|
|
17
17
|
import { RequestError } from "@octokit/request-error";
|
|
18
18
|
import { Octokit } from "@octokit/rest";
|
|
19
19
|
import { gunzipSync } from "node:zlib";
|
|
20
|
-
import { FastMCP } from "fastmcp";
|
|
21
20
|
import * as fs from "node:fs";
|
|
21
|
+
import { existsSync, watch } from "node:fs";
|
|
22
|
+
import { FastMCP } from "fastmcp";
|
|
22
23
|
import { Readable, Transform } from "node:stream";
|
|
23
24
|
import { pipeline } from "node:stream/promises";
|
|
24
25
|
//#region src/utils/parse-comma-separated-list.ts
|
|
@@ -4374,6 +4375,216 @@ async function fetchCommand(logger, options) {
|
|
|
4374
4375
|
throw error;
|
|
4375
4376
|
}
|
|
4376
4377
|
}
|
|
4378
|
+
/**
|
|
4379
|
+
* Coalesces file-system change notifications into debounced, non-overlapping
|
|
4380
|
+
* runs.
|
|
4381
|
+
*
|
|
4382
|
+
* Guarantees:
|
|
4383
|
+
* - At most one `run` is in flight at any time.
|
|
4384
|
+
* - Every notified path is reported to exactly one `run` as a trigger.
|
|
4385
|
+
* - Notifications that arrive while a run is in flight schedule exactly one
|
|
4386
|
+
* follow-up run after it finishes, so a change is never lost and never
|
|
4387
|
+
* causes a run per event.
|
|
4388
|
+
*/
|
|
4389
|
+
var WatchScheduler = class {
|
|
4390
|
+
run;
|
|
4391
|
+
onError;
|
|
4392
|
+
debounceMs;
|
|
4393
|
+
pending = /* @__PURE__ */ new Set();
|
|
4394
|
+
timer;
|
|
4395
|
+
running;
|
|
4396
|
+
closed = false;
|
|
4397
|
+
constructor({ run, onError, debounceMs = 300 }) {
|
|
4398
|
+
this.run = run;
|
|
4399
|
+
this.onError = onError;
|
|
4400
|
+
this.debounceMs = debounceMs;
|
|
4401
|
+
}
|
|
4402
|
+
notify({ path }) {
|
|
4403
|
+
if (this.closed) return;
|
|
4404
|
+
this.pending.add(path);
|
|
4405
|
+
this.schedule();
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Stops accepting notifications and waits for an in-flight run to settle.
|
|
4409
|
+
* Pending (not yet started) changes are dropped.
|
|
4410
|
+
*/
|
|
4411
|
+
async close() {
|
|
4412
|
+
this.closed = true;
|
|
4413
|
+
this.clearTimer();
|
|
4414
|
+
this.pending.clear();
|
|
4415
|
+
await this.running;
|
|
4416
|
+
}
|
|
4417
|
+
clearTimer() {
|
|
4418
|
+
if (this.timer !== void 0) {
|
|
4419
|
+
clearTimeout(this.timer);
|
|
4420
|
+
this.timer = void 0;
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
schedule() {
|
|
4424
|
+
this.clearTimer();
|
|
4425
|
+
this.timer = setTimeout(() => {
|
|
4426
|
+
this.timer = void 0;
|
|
4427
|
+
this.flush();
|
|
4428
|
+
}, this.debounceMs);
|
|
4429
|
+
}
|
|
4430
|
+
async flush() {
|
|
4431
|
+
if (this.closed || this.running !== void 0 || this.pending.size === 0) return;
|
|
4432
|
+
const triggers = [...this.pending];
|
|
4433
|
+
this.pending.clear();
|
|
4434
|
+
const running = (async () => {
|
|
4435
|
+
try {
|
|
4436
|
+
await this.run({ triggers });
|
|
4437
|
+
} catch (error) {
|
|
4438
|
+
this.onError({
|
|
4439
|
+
error,
|
|
4440
|
+
triggers
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4443
|
+
})();
|
|
4444
|
+
this.running = running;
|
|
4445
|
+
await running;
|
|
4446
|
+
this.running = void 0;
|
|
4447
|
+
if (!this.closed && this.pending.size > 0) this.schedule();
|
|
4448
|
+
}
|
|
4449
|
+
};
|
|
4450
|
+
/**
|
|
4451
|
+
* Watches one directory, re-attaching the underlying `fs.watch` if the
|
|
4452
|
+
* directory is deleted and later recreated.
|
|
4453
|
+
*
|
|
4454
|
+
* Without this, a `git checkout` to a branch without `.rulesync/` (or any
|
|
4455
|
+
* tool that replaces the directory rather than its contents) would silently
|
|
4456
|
+
* kill the watcher: the deleted inode emits no further events and no error,
|
|
4457
|
+
* so watch mode would keep running while never regenerating again.
|
|
4458
|
+
*
|
|
4459
|
+
* The first attach is not guarded — a missing directory at startup is a real
|
|
4460
|
+
* configuration error and must surface to the caller.
|
|
4461
|
+
*/
|
|
4462
|
+
function watchTargetWithRearm({ target, onChange, onError, rearmIntervalMs }) {
|
|
4463
|
+
let watcher;
|
|
4464
|
+
let rearmTimer;
|
|
4465
|
+
let closed = false;
|
|
4466
|
+
const attach = () => {
|
|
4467
|
+
const created = watch(target.directory, {
|
|
4468
|
+
recursive: target.recursive,
|
|
4469
|
+
persistent: true
|
|
4470
|
+
}, (_eventType, filename) => {
|
|
4471
|
+
if (filename === null || filename === void 0) {
|
|
4472
|
+
onChange({ path: target.directory });
|
|
4473
|
+
verifyStillWatching();
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4476
|
+
const relativePath = filename.toString();
|
|
4477
|
+
if (target.include && !target.include(relativePath)) {
|
|
4478
|
+
verifyStillWatching();
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
onChange({ path: join(target.directory, relativePath) });
|
|
4482
|
+
verifyStillWatching();
|
|
4483
|
+
});
|
|
4484
|
+
created.on("error", (error) => {
|
|
4485
|
+
onError({
|
|
4486
|
+
error,
|
|
4487
|
+
directory: target.directory
|
|
4488
|
+
});
|
|
4489
|
+
verifyStillWatching();
|
|
4490
|
+
});
|
|
4491
|
+
watcher = created;
|
|
4492
|
+
};
|
|
4493
|
+
const scheduleRearm = () => {
|
|
4494
|
+
if (closed || rearmTimer !== void 0) return;
|
|
4495
|
+
rearmTimer = setInterval(() => {
|
|
4496
|
+
if (closed || !existsSync(target.directory)) return;
|
|
4497
|
+
clearInterval(rearmTimer);
|
|
4498
|
+
rearmTimer = void 0;
|
|
4499
|
+
try {
|
|
4500
|
+
attach();
|
|
4501
|
+
} catch (error) {
|
|
4502
|
+
onError({
|
|
4503
|
+
error,
|
|
4504
|
+
directory: target.directory
|
|
4505
|
+
});
|
|
4506
|
+
scheduleRearm();
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4509
|
+
onChange({ path: target.directory });
|
|
4510
|
+
}, rearmIntervalMs);
|
|
4511
|
+
};
|
|
4512
|
+
const verifyStillWatching = () => {
|
|
4513
|
+
if (closed || watcher === void 0 || existsSync(target.directory)) return;
|
|
4514
|
+
watcher.close();
|
|
4515
|
+
watcher = void 0;
|
|
4516
|
+
scheduleRearm();
|
|
4517
|
+
};
|
|
4518
|
+
attach();
|
|
4519
|
+
return { close: () => {
|
|
4520
|
+
closed = true;
|
|
4521
|
+
if (rearmTimer !== void 0) {
|
|
4522
|
+
clearInterval(rearmTimer);
|
|
4523
|
+
rearmTimer = void 0;
|
|
4524
|
+
}
|
|
4525
|
+
watcher?.close();
|
|
4526
|
+
watcher = void 0;
|
|
4527
|
+
} };
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* Starts one watcher per target and forwards matching events to `onChange` as
|
|
4531
|
+
* absolute paths. If any target fails to attach, the watchers started so far
|
|
4532
|
+
* are closed before the error propagates, so no descriptor is leaked.
|
|
4533
|
+
*/
|
|
4534
|
+
function watchTargets({ targets, onChange, onError, rearmIntervalMs = 500 }) {
|
|
4535
|
+
const handles = [];
|
|
4536
|
+
const closeAll = () => {
|
|
4537
|
+
for (const handle of handles) handle.close();
|
|
4538
|
+
};
|
|
4539
|
+
try {
|
|
4540
|
+
for (const target of targets) handles.push(watchTargetWithRearm({
|
|
4541
|
+
target,
|
|
4542
|
+
onChange,
|
|
4543
|
+
onError,
|
|
4544
|
+
rearmIntervalMs
|
|
4545
|
+
}));
|
|
4546
|
+
} catch (error) {
|
|
4547
|
+
closeAll();
|
|
4548
|
+
throw error;
|
|
4549
|
+
}
|
|
4550
|
+
return { close: closeAll };
|
|
4551
|
+
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Builds the set of directories watch mode observes: the `.rulesync/` source
|
|
4554
|
+
* tree (recursively) and, filtered down to the configuration files themselves,
|
|
4555
|
+
* the directory holding `rulesync.jsonc`.
|
|
4556
|
+
*
|
|
4557
|
+
* Only input paths are watched. Generated output lives outside `.rulesync/`, so
|
|
4558
|
+
* a regeneration cannot re-trigger the watcher.
|
|
4559
|
+
*/
|
|
4560
|
+
function buildWatchTargets({ inputRoot, configFilePath }) {
|
|
4561
|
+
const configFilePaths = buildConfigFilePaths({ configFilePath });
|
|
4562
|
+
return [{
|
|
4563
|
+
directory: join(inputRoot, RULESYNC_RELATIVE_DIR_PATH),
|
|
4564
|
+
recursive: true
|
|
4565
|
+
}, {
|
|
4566
|
+
directory: dirname(configFilePath),
|
|
4567
|
+
recursive: false,
|
|
4568
|
+
include: (relativePath) => configFilePaths.has(join(dirname(configFilePath), relativePath))
|
|
4569
|
+
}];
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* The absolute paths of the configuration files watch mode observes: the base
|
|
4573
|
+
* configuration file and the `rulesync.local.jsonc` sitting next to it, which
|
|
4574
|
+
* is exactly what `ConfigResolver` loads.
|
|
4575
|
+
*/
|
|
4576
|
+
function buildConfigFilePaths({ configFilePath }) {
|
|
4577
|
+
return /* @__PURE__ */ new Set([configFilePath, join(dirname(configFilePath), RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH)]);
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Renders trigger paths relative to `baseDir` for logging, truncating long
|
|
4581
|
+
* bursts so a `git checkout` does not flood the terminal.
|
|
4582
|
+
*/
|
|
4583
|
+
function formatTriggerPaths({ triggers, baseDir, max = 5 }) {
|
|
4584
|
+
const displayed = triggers.slice(0, max).map((trigger) => relative(baseDir, trigger) || trigger);
|
|
4585
|
+
const remaining = triggers.length - displayed.length;
|
|
4586
|
+
return remaining > 0 ? `${displayed.join(", ")} (+${remaining} more)` : displayed.join(", ");
|
|
4587
|
+
}
|
|
4377
4588
|
//#endregion
|
|
4378
4589
|
//#region src/cli/commands/generate.ts
|
|
4379
4590
|
/**
|
|
@@ -4463,7 +4674,20 @@ function buildSummaryParts(result) {
|
|
|
4463
4674
|
return parts;
|
|
4464
4675
|
}
|
|
4465
4676
|
async function generateCommand(logger, options) {
|
|
4466
|
-
|
|
4677
|
+
if (options.watch) {
|
|
4678
|
+
await generateWatchCommand(logger, options);
|
|
4679
|
+
return;
|
|
4680
|
+
}
|
|
4681
|
+
await generateOnce(logger, options);
|
|
4682
|
+
}
|
|
4683
|
+
/**
|
|
4684
|
+
* Runs one generation. `resolvedConfig` lets a caller that already resolved
|
|
4685
|
+
* the configuration (watch mode's startup validation) reuse it instead of
|
|
4686
|
+
* paying for a second resolution — and, more importantly, instead of emitting
|
|
4687
|
+
* the resolver's warnings twice.
|
|
4688
|
+
*/
|
|
4689
|
+
async function generateOnce(logger, options, { resolvedConfig } = {}) {
|
|
4690
|
+
const config = resolvedConfig ?? await ConfigResolver.resolve(options, { logger });
|
|
4467
4691
|
const check = config.getCheck();
|
|
4468
4692
|
const isPreview = config.isPreviewMode();
|
|
4469
4693
|
const modePrefix = isPreview ? "[DRY RUN]" : "";
|
|
@@ -4558,6 +4782,76 @@ async function generateCommand(logger, options) {
|
|
|
4558
4782
|
if (isPreview) logger.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
4559
4783
|
else logger.success(`🎉 All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
4560
4784
|
}
|
|
4785
|
+
/**
|
|
4786
|
+
* Rejects flag combinations that contradict a long-running watch: `--check`
|
|
4787
|
+
* and `--dry-run` are one-shot verification modes (the former is meant to exit
|
|
4788
|
+
* non-zero), and `--json` buffers a single result document until the command
|
|
4789
|
+
* returns, which never happens while watching.
|
|
4790
|
+
*/
|
|
4791
|
+
function assertWatchModeCompatible({ isCheck, isDryRun, isJsonMode }) {
|
|
4792
|
+
const conflicts = [
|
|
4793
|
+
isCheck ? "--check" : void 0,
|
|
4794
|
+
isDryRun ? "--dry-run" : void 0,
|
|
4795
|
+
isJsonMode ? "--json" : void 0
|
|
4796
|
+
].filter((flag) => flag !== void 0);
|
|
4797
|
+
if (conflicts.length > 0) throw new CLIError(`--watch cannot be combined with ${conflicts.join(", ")}.`, ErrorCodes.VALIDATION_FAILED);
|
|
4798
|
+
}
|
|
4799
|
+
async function generateWatchCommand(logger, options) {
|
|
4800
|
+
const config = await ConfigResolver.resolve(options, { logger });
|
|
4801
|
+
assertWatchModeCompatible({
|
|
4802
|
+
isCheck: config.getCheck(),
|
|
4803
|
+
isDryRun: config.getDryRun(),
|
|
4804
|
+
isJsonMode: logger.jsonMode
|
|
4805
|
+
});
|
|
4806
|
+
const inputRoot = config.getInputRoot();
|
|
4807
|
+
const configFilePath = config.getConfigFilePath();
|
|
4808
|
+
const configFilePaths = buildConfigFilePaths({ configFilePath });
|
|
4809
|
+
await generateOnce(logger, options, { resolvedConfig: config });
|
|
4810
|
+
const targets = buildWatchTargets({
|
|
4811
|
+
inputRoot,
|
|
4812
|
+
configFilePath
|
|
4813
|
+
});
|
|
4814
|
+
const scheduler = new WatchScheduler({
|
|
4815
|
+
run: async ({ triggers }) => {
|
|
4816
|
+
logger.info(`\nChange detected: ${formatTriggerPaths({
|
|
4817
|
+
triggers,
|
|
4818
|
+
baseDir: inputRoot
|
|
4819
|
+
})}`);
|
|
4820
|
+
if (triggers.some((trigger) => configFilePaths.has(trigger))) logger.warn("Configuration file changed. The set of watched paths is fixed at startup — restart 'rulesync generate --watch' if you changed 'inputRoot' or the configuration file location.");
|
|
4821
|
+
await generateOnce(logger, options);
|
|
4822
|
+
},
|
|
4823
|
+
onError: ({ error }) => {
|
|
4824
|
+
logger.error(`Generation failed: ${formatError(error)}`);
|
|
4825
|
+
logger.info("Still watching for changes...");
|
|
4826
|
+
}
|
|
4827
|
+
});
|
|
4828
|
+
const handle = watchTargets({
|
|
4829
|
+
targets,
|
|
4830
|
+
onChange: ({ path }) => {
|
|
4831
|
+
scheduler.notify({ path });
|
|
4832
|
+
},
|
|
4833
|
+
onError: ({ error, directory }) => {
|
|
4834
|
+
logger.error(`Watch error on ${directory}: ${formatError(error)}`);
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4837
|
+
logger.info(`\nWatching for changes in:\n${targets.map((target) => ` ${target.directory}`).join("\n")}`);
|
|
4838
|
+
logger.info("Press Ctrl+C to stop.");
|
|
4839
|
+
await new Promise((resolveShutdown) => {
|
|
4840
|
+
const shutdown = () => {
|
|
4841
|
+
process.off("SIGINT", shutdown);
|
|
4842
|
+
process.off("SIGTERM", shutdown);
|
|
4843
|
+
handle.close();
|
|
4844
|
+
scheduler.close().catch((error) => {
|
|
4845
|
+
logger.error(`Failed to stop the watcher cleanly: ${formatError(error)}`);
|
|
4846
|
+
}).finally(() => {
|
|
4847
|
+
logger.info("\nStopped watching.");
|
|
4848
|
+
resolveShutdown();
|
|
4849
|
+
});
|
|
4850
|
+
};
|
|
4851
|
+
process.once("SIGINT", shutdown);
|
|
4852
|
+
process.once("SIGTERM", shutdown);
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4561
4855
|
//#endregion
|
|
4562
4856
|
//#region src/cli/commands/gitignore-derive.ts
|
|
4563
4857
|
const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set([
|
|
@@ -9002,7 +9296,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
|
|
|
9002
9296
|
}
|
|
9003
9297
|
//#endregion
|
|
9004
9298
|
//#region src/cli/program.ts
|
|
9005
|
-
const getVersion = () => "16.
|
|
9299
|
+
const getVersion = () => "16.2.0";
|
|
9006
9300
|
const FEATURES_HELP = `${ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
|
|
9007
9301
|
function wrapCommand(name, errorCode, handler) {
|
|
9008
9302
|
return wrapCommand$1({
|
|
@@ -9071,7 +9365,7 @@ function createProgram() {
|
|
|
9071
9365
|
silent: options.silent
|
|
9072
9366
|
});
|
|
9073
9367
|
}));
|
|
9074
|
-
program.command("generate").description("Generate configuration files for AI tools").option("-t, --targets <tools>", "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to generate (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("--delete", "Delete all existing files in output directories before generating").option("-o, --output-roots <paths>", "Output root directories to generate files into (comma-separated for multiple paths)", parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-c, --config <path>", "Path to configuration file").option("-g, --global", "Generate for global(user scope) configuration files").option("--simulate-commands", "Generate simulated commands. This feature is only available for copilot, cursor and codexcli.").option("--simulate-subagents", "Generate simulated subagents. This feature is only available for copilot and codexcli.").option("--simulate-skills", "Generate simulated skills. This feature is only available for copilot, cursor and codexcli.").option("--input-root <path>", "Path to the directory containing .rulesync/ (parent of .rulesync/)").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(wrapCommand("generate", "GENERATION_FAILED", async (logger, options) => {
|
|
9368
|
+
program.command("generate").description("Generate configuration files for AI tools").option("-t, --targets <tools>", "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)", parseCommaSeparatedList).option("-f, --features <features>", `Comma-separated list of features to generate (${FEATURES_HELP}) or '*' for all`, parseCommaSeparatedList).option("--delete", "Delete all existing files in output directories before generating").option("-o, --output-roots <paths>", "Output root directories to generate files into (comma-separated for multiple paths)", parseCommaSeparatedList).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-c, --config <path>", "Path to configuration file").option("-g, --global", "Generate for global(user scope) configuration files").option("--simulate-commands", "Generate simulated commands. This feature is only available for copilot, cursor and codexcli.").option("--simulate-subagents", "Generate simulated subagents. This feature is only available for copilot and codexcli.").option("--simulate-skills", "Generate simulated skills. This feature is only available for copilot, cursor and codexcli.").option("--input-root <path>", "Path to the directory containing .rulesync/ (parent of .rulesync/)").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)").option("-w, --watch", "Keep running and regenerate whenever rulesync source files change (cannot be combined with --check, --dry-run or --json)").action(wrapCommand("generate", "GENERATION_FAILED", async (logger, options) => {
|
|
9075
9369
|
await generateCommand(logger, options);
|
|
9076
9370
|
}));
|
|
9077
9371
|
program.command("update").description("Update rulesync to the latest version").option("--check", "Check for updates without installing").option("--force", "Force update even if already at latest version").option("--token <token>", "GitHub token for API access").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(wrapCommand("update", "UPDATE_FAILED", async (logger, options) => {
|