@syncat-dev/cli 0.0.0 → 0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # syncat
2
2
 
3
- For installation, configuration, commands, and usage details, see the [repository README](../../README.md).
3
+ For installation, configuration, commands, and usage details, see the [repository README](https://github.com/bingtsingw/syncat-dev#readme).
4
4
 
5
5
  ## License
6
6
 
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as loadSyncatConfig, n as buildSyncPlan, r as createUnifiedDiff, t as applySyncPlan } from "./dist-Bn-gPTzR.js";
2
+ import { a as loadSyncatConfig, n as buildSyncPlan, r as createUnifiedDiff, t as applySyncPlan } from "./dist-C5ZWFAKV.js";
3
3
  import { cac } from "cac";
4
4
  //#region package.json
5
- var version = "0.0.0";
5
+ var version = "0.2.0";
6
6
  //#endregion
7
7
  //#region src/commands.ts
8
8
  async function runCheck(options) {
@@ -26,6 +26,7 @@ const syncatConfigSchema = z.object({
26
26
  target: z.string().min(1),
27
27
  files: z.array(z.object({
28
28
  path: z.string().min(1),
29
+ exclude: z.array(z.string().min(1)).optional(),
29
30
  strategy: strategySchema.optional()
30
31
  }).strict()).min(1, "At least one file rule is required.")
31
32
  }).strict();
@@ -38,7 +39,10 @@ function resolveSyncatConfig(rawConfig, configFile) {
38
39
  const configDir = dirname(configFile);
39
40
  const sourceDir = resolveConfigPath(parsed.data.source, configDir);
40
41
  const targetDir = resolveConfigPath(parsed.data.target, configDir);
41
- for (const rule of parsed.data.files) assertSafeRulePath(rule.path);
42
+ for (const rule of parsed.data.files) {
43
+ assertSafeRulePath(rule.path);
44
+ for (const pattern of rule.exclude ?? []) assertSafeRulePath(pattern);
45
+ }
42
46
  return {
43
47
  config: parsed.data,
44
48
  configFile,
@@ -200,7 +204,7 @@ async function buildSyncPlan(config) {
200
204
  const entries = [];
201
205
  const seenPaths = /* @__PURE__ */ new Set();
202
206
  for (const rule of config.config.files) {
203
- const paths = await resolveRulePaths(config.sourceDir, rule.path);
207
+ const paths = await resolveRulePaths(config.sourceDir, rule.path, rule.exclude);
204
208
  if (paths.length === 0) throw new SyncatError(`File rule matched no source files: ${rule.path}`);
205
209
  for (const path of paths) {
206
210
  if (seenPaths.has(path)) throw new SyncatError(`A source file is managed by more than one rule: ${path}`);
@@ -233,13 +237,14 @@ async function buildSyncPlan(config) {
233
237
  drift: entries.filter((entry) => entry.status !== "synced")
234
238
  };
235
239
  }
236
- async function resolveRulePaths(sourceDir, rulePath) {
237
- if (!globSyntax.test(rulePath)) return [rulePath];
240
+ async function resolveRulePaths(sourceDir, rulePath, exclude) {
241
+ if (!globSyntax.test(rulePath) && (!exclude || exclude.length === 0)) return [rulePath];
238
242
  return fastGlob(rulePath, {
239
243
  cwd: sourceDir,
240
244
  onlyFiles: true,
241
245
  dot: true,
242
246
  followSymbolicLinks: false,
247
+ ignore: exclude,
243
248
  unique: true
244
249
  });
245
250
  }
@@ -291,4 +296,4 @@ async function loadSyncatConfig(options = {}) {
291
296
  //#endregion
292
297
  export { loadSyncatConfig as a, defineConfig as i, buildSyncPlan as n, createUnifiedDiff as r, applySyncPlan as t };
293
298
 
294
- //# sourceMappingURL=dist-Bn-gPTzR.js.map
299
+ //# sourceMappingURL=dist-C5ZWFAKV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dist-C5ZWFAKV.js","names":[],"sources":["../../core/dist/index.js"],"sourcesContent":["import { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { loadConfig } from \"c12\";\nimport { chmod, lstat, mkdir, readFile, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { randomUUID } from \"node:crypto\";\nimport { TextDecoder } from \"node:util\";\nimport fastGlob from \"fast-glob\";\n//#region src/errors.ts\nvar SyncatError = class extends Error {\n\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.name = \"SyncatError\";\n\t}\n};\n//#endregion\n//#region src/config.ts\nconst replaceRuleSchema = z.object({\n\tfrom: z.string().min(1, \"Replacement \\\"from\\\" must not be empty.\"),\n\tto: z.string(),\n\tall: z.boolean().optional()\n}).strict();\nconst strategySchema = z.discriminatedUnion(\"type\", [z.object({ type: z.literal(\"copy\") }).strict(), z.object({\n\ttype: z.literal(\"text-replace\"),\n\treplacements: z.array(replaceRuleSchema).min(1, \"A text-replace strategy needs at least one replacement.\")\n}).strict()]);\nconst syncatConfigSchema = z.object({\n\tsource: z.string().min(1),\n\ttarget: z.string().min(1),\n\tfiles: z.array(z.object({\n\t\tpath: z.string().min(1),\n\t\texclude: z.array(z.string().min(1)).optional(),\n\t\tstrategy: strategySchema.optional()\n\t}).strict()).min(1, \"At least one file rule is required.\")\n}).strict();\nfunction defineConfig(config) {\n\treturn config;\n}\nfunction resolveSyncatConfig(rawConfig, configFile) {\n\tconst parsed = syncatConfigSchema.safeParse(rawConfig);\n\tif (!parsed.success) throw new SyncatError(`Invalid syncat config:\\n${z.prettifyError(parsed.error)}`);\n\tconst configDir = dirname(configFile);\n\tconst sourceDir = resolveConfigPath(parsed.data.source, configDir);\n\tconst targetDir = resolveConfigPath(parsed.data.target, configDir);\n\tfor (const rule of parsed.data.files) {\n\t\tassertSafeRulePath(rule.path);\n\t\tfor (const pattern of rule.exclude ?? []) assertSafeRulePath(pattern);\n\t}\n\treturn {\n\t\tconfig: parsed.data,\n\t\tconfigFile,\n\t\tsourceDir,\n\t\ttargetDir\n\t};\n}\nfunction resolveConfigPath(value, cwd) {\n\tconst home = process.env[\"HOME\"] ?? process.env[\"USERPROFILE\"];\n\tconst expanded = value === \"~\" && home ? home : value.startsWith(\"~/\") && home ? resolve(home, value.slice(2)) : value;\n\treturn resolve(cwd, expanded);\n}\nfunction assertSafeRulePath(value) {\n\tif (isAbsolute(value) || value.includes(\"\\\\\")) throw new SyncatError(`File rule path must be a relative POSIX path: ${value}`);\n\tif (value.split(\"/\").some((segment) => segment === \"..\") || value === \".\" || value.startsWith(\"../\")) throw new SyncatError(`File rule path must not escape its project root: ${value}`);\n}\nfunction assertPathInside(root, candidate) {\n\tconst pathRelative = relative(root, candidate);\n\tif (pathRelative === \"\" || !pathRelative.startsWith(`..${sep}`) && pathRelative !== \"..\" && !isAbsolute(pathRelative)) return;\n\tthrow new SyncatError(`Resolved path escapes its project root: ${candidate}`);\n}\nasync function assertRealPathInside(root, candidate) {\n\tconst [realRoot, realCandidate] = await Promise.all([realpath(root), realpath(candidate)]);\n\tassertPathInside(realRoot, realCandidate);\n}\nasync function assertExistingAncestorInside(root, candidate) {\n\tconst realRoot = await realpath(root);\n\tlet ancestor = candidate;\n\twhile (true) try {\n\t\tassertPathInside(realRoot, await realpath(ancestor));\n\t\treturn;\n\t} catch (error) {\n\t\tif (!isNotFound$2(error)) throw error;\n\t\tconst parent = dirname(ancestor);\n\t\tif (parent === ancestor) throw new SyncatError(`Could not find an existing ancestor for path: ${candidate}`);\n\t\tancestor = parent;\n\t}\n}\nfunction isNotFound$2(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/apply.ts\nasync function applySyncPlan(plan, options) {\n\tconst written = plan.drift;\n\tif (options.dryRun) return {\n\t\tdryRun: true,\n\t\twritten\n\t};\n\tfor (const entry of written) await writeEntry(plan.config.targetDir, entry.sourcePath, entry.targetPath, entry.desiredContent);\n\treturn {\n\t\tdryRun: false,\n\t\twritten\n\t};\n}\nasync function writeEntry(targetRoot, sourcePath, targetPath, contents) {\n\tawait mkdir(dirname(targetPath), { recursive: true });\n\tawait assertRealPathInside(targetRoot, dirname(targetPath));\n\tawait rejectTargetSymlink(targetPath);\n\tconst sourceStat = await stat(sourcePath);\n\tconst temporaryPath = join(dirname(targetPath), `.${basename(targetPath)}.syncat-${randomUUID()}.tmp`);\n\ttry {\n\t\tawait writeFile(temporaryPath, contents, { mode: sourceStat.mode });\n\t\tawait chmod(temporaryPath, sourceStat.mode);\n\t\tawait rename(temporaryPath, targetPath);\n\t} catch (error) {\n\t\tawait rm(temporaryPath, { force: true });\n\t\tthrow new SyncatError(`Failed to write ${targetPath}: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n}\nasync function rejectTargetSymlink(path) {\n\ttry {\n\t\tif ((await lstat(path)).isSymbolicLink()) throw new SyncatError(`Refusing to overwrite symbolic link target: ${path}`);\n\t} catch (error) {\n\t\tif (isNotFound$1(error)) return;\n\t\tthrow error;\n\t}\n}\nfunction isNotFound$1(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/diff.ts\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\nconst maximumComparisonCells = 1e6;\nfunction createUnifiedDiff(path, actual, expected) {\n\tlet actualText;\n\tlet expectedText;\n\ttry {\n\t\tactualText = decoder.decode(actual);\n\t\texpectedText = decoder.decode(expected);\n\t} catch {\n\t\treturn `Binary files differ: ${path}\\n`;\n\t}\n\tconst before = actualText.split(\"\\n\");\n\tconst after = expectedText.split(\"\\n\");\n\tif (before.length * after.length > maximumComparisonCells) return `Diff omitted for ${path}: files are too large for a line-by-line comparison.\\n`;\n\tconst table = Array.from({ length: before.length + 1 }, () => new Uint32Array(after.length + 1));\n\tfor (let beforeIndex = before.length - 1; beforeIndex >= 0; beforeIndex -= 1) for (let afterIndex = after.length - 1; afterIndex >= 0; afterIndex -= 1) {\n\t\tconst beforeLine = getLine(before, beforeIndex);\n\t\tconst afterLine = getLine(after, afterIndex);\n\t\tgetRow(table, beforeIndex)[afterIndex] = beforeLine === afterLine ? getCell(table, beforeIndex + 1, afterIndex + 1) + 1 : Math.max(getCell(table, beforeIndex + 1, afterIndex), getCell(table, beforeIndex, afterIndex + 1));\n\t}\n\tconst output = [\n\t\t`--- target/${path}`,\n\t\t`+++ expected/${path}`,\n\t\t`@@ -1,${before.length} +1,${after.length} @@`\n\t];\n\tlet beforeIndex = 0;\n\tlet afterIndex = 0;\n\twhile (beforeIndex < before.length || afterIndex < after.length) {\n\t\tconst beforeLine = beforeIndex < before.length ? getLine(before, beforeIndex) : void 0;\n\t\tconst afterLine = afterIndex < after.length ? getLine(after, afterIndex) : void 0;\n\t\tif (beforeLine !== void 0 && afterLine !== void 0 && beforeLine === afterLine) {\n\t\t\toutput.push(` ${beforeLine}`);\n\t\t\tbeforeIndex += 1;\n\t\t\tafterIndex += 1;\n\t\t} else if (afterLine !== void 0 && (beforeLine === void 0 || getCell(table, beforeIndex, afterIndex + 1) >= getCell(table, beforeIndex + 1, afterIndex))) {\n\t\t\toutput.push(`+${afterLine}`);\n\t\t\tafterIndex += 1;\n\t\t} else {\n\t\t\tif (beforeLine === void 0) throw new Error(\"Diff generation reached an invalid state.\");\n\t\t\toutput.push(`-${beforeLine}`);\n\t\t\tbeforeIndex += 1;\n\t\t}\n\t}\n\treturn `${output.join(\"\\n\")}\\n`;\n}\nfunction getCell(table, rowIndex, columnIndex) {\n\treturn getRow(table, rowIndex)[columnIndex] ?? 0;\n}\nfunction getLine(lines, index) {\n\tconst line = lines[index];\n\tif (line === void 0) throw new Error(\"Diff generation reached an invalid line index.\");\n\treturn line;\n}\nfunction getRow(table, rowIndex) {\n\tconst row = table[rowIndex];\n\tif (!row) throw new Error(\"Diff generation reached an invalid table row.\");\n\treturn row;\n}\n//#endregion\n//#region src/strategy.ts\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: true });\nfunction renderDesiredContent(source, strategy, path) {\n\tif (!strategy || strategy.type === \"copy\") return source;\n\tlet text;\n\ttry {\n\t\ttext = utf8Decoder.decode(source);\n\t} catch {\n\t\tthrow new SyncatError(`Text strategy cannot process non-UTF-8 file: ${path}`);\n\t}\n\tfor (const replacement of strategy.replacements) {\n\t\tif (text.split(replacement.from).length - 1 === 0) throw new SyncatError(`Replacement source was not found in ${path}: ${JSON.stringify(replacement.from)}`);\n\t\ttext = replacement.all === false ? text.replace(replacement.from, replacement.to) : text.split(replacement.from).join(replacement.to);\n\t}\n\treturn Buffer.from(text, \"utf8\");\n}\n//#endregion\n//#region src/plan.ts\nconst globSyntax = /[*?[\\]{}]/;\nasync function buildSyncPlan(config) {\n\tawait assertDirectory(config.sourceDir, \"source\");\n\tawait assertDirectory(config.targetDir, \"target\");\n\tconst [realSourceDir, realTargetDir] = await Promise.all([realpath(config.sourceDir), realpath(config.targetDir)]);\n\tif (realSourceDir === realTargetDir) throw new SyncatError(\"Configured source and target directories must be different.\");\n\tconst entries = [];\n\tconst seenPaths = /* @__PURE__ */ new Set();\n\tfor (const rule of config.config.files) {\n\t\tconst paths = await resolveRulePaths(config.sourceDir, rule.path, rule.exclude);\n\t\tif (paths.length === 0) throw new SyncatError(`File rule matched no source files: ${rule.path}`);\n\t\tfor (const path of paths) {\n\t\t\tif (seenPaths.has(path)) throw new SyncatError(`A source file is managed by more than one rule: ${path}`);\n\t\t\tseenPaths.add(path);\n\t\t\tconst sourcePath = resolve(config.sourceDir, path);\n\t\t\tconst targetPath = resolve(config.targetDir, path);\n\t\t\tassertPathInside(config.sourceDir, sourcePath);\n\t\t\tassertPathInside(config.targetDir, targetPath);\n\t\t\tawait assertRealPathInside(config.sourceDir, sourcePath);\n\t\t\tawait assertRegularFile(sourcePath, \"source\");\n\t\t\tconst sourceContent = await readFile(sourcePath);\n\t\t\tconst desiredContent = renderDesiredContent(sourceContent, rule.strategy, path);\n\t\t\tconst targetContent = await readOptionalRegularFile(config.targetDir, targetPath);\n\t\t\tconst status = !targetContent ? \"missing\" : targetContent.equals(desiredContent) ? \"synced\" : \"changed\";\n\t\t\tentries.push({\n\t\t\t\tpath,\n\t\t\t\tsourcePath,\n\t\t\t\ttargetPath,\n\t\t\t\tsourceContent,\n\t\t\t\ttargetContent,\n\t\t\t\tdesiredContent,\n\t\t\t\tstatus\n\t\t\t});\n\t\t}\n\t}\n\tentries.sort((left, right) => left.path.localeCompare(right.path));\n\treturn {\n\t\tconfig,\n\t\tentries,\n\t\tdrift: entries.filter((entry) => entry.status !== \"synced\")\n\t};\n}\nasync function resolveRulePaths(sourceDir, rulePath, exclude) {\n\tif (!globSyntax.test(rulePath) && (!exclude || exclude.length === 0)) return [rulePath];\n\treturn fastGlob(rulePath, {\n\t\tcwd: sourceDir,\n\t\tonlyFiles: true,\n\t\tdot: true,\n\t\tfollowSymbolicLinks: false,\n\t\tignore: exclude,\n\t\tunique: true\n\t});\n}\nasync function assertDirectory(path, label) {\n\tlet fileStat;\n\ttry {\n\t\tfileStat = await stat(path);\n\t} catch {\n\t\tthrow new SyncatError(`Configured ${label} directory does not exist: ${path}`);\n\t}\n\tif (!fileStat.isDirectory()) throw new SyncatError(`Configured ${label} path is not a directory: ${path}`);\n}\nasync function assertRegularFile(path, label) {\n\tif (!(await lstat(path)).isFile()) throw new SyncatError(`Managed ${label} path is not a regular file: ${path}`);\n}\nasync function readOptionalRegularFile(targetRoot, path) {\n\ttry {\n\t\tawait assertExistingAncestorInside(targetRoot, path);\n\t\tawait assertRegularFile(path, \"target\");\n\t\tawait assertRealPathInside(targetRoot, path);\n\t\treturn await readFile(path);\n\t} catch (error) {\n\t\tif (isNotFound(error)) return;\n\t\tthrow error;\n\t}\n}\nfunction isNotFound(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/index.ts\nasync function loadSyncatConfig(options = {}) {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst configPath = resolve(cwd, options.configPath ?? \"syncat.config.ts\");\n\tconst loaded = await loadConfig({\n\t\tname: \"syncat\",\n\t\tcwd,\n\t\tconfigFile: configPath,\n\t\tconfigFileRequired: true,\n\t\tdotenv: false,\n\t\tenvName: false,\n\t\textend: false,\n\t\tgiget: false,\n\t\tglobalRc: false,\n\t\tpackageJson: false,\n\t\trcFile: false\n\t});\n\tif (!loaded.configFile) throw new Error(\"Could not determine the loaded syncat config file.\");\n\treturn resolveSyncatConfig(loaded.config, loaded.configFile);\n}\n//#endregion\nexport { SyncatError, applySyncPlan, buildSyncPlan, createUnifiedDiff, defineConfig, loadSyncatConfig, resolveSyncatConfig };\n\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ interface TextReplaceStrategy {
16
16
  type StrategyConfig = CopyStrategy | TextReplaceStrategy;
17
17
  interface FileRule {
18
18
  path: string;
19
+ exclude?: string[];
19
20
  strategy?: StrategyConfig;
20
21
  }
21
22
  interface SyncatConfig {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as defineConfig } from "./dist-Bn-gPTzR.js";
1
+ import { i as defineConfig } from "./dist-C5ZWFAKV.js";
2
2
  //#region ../strategy/dist/index.js
3
3
  const text = { replace(replacements) {
4
4
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncat-dev/cli",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "Synchronize selected files from a source directory to a target directory with configurable strategies.",
5
5
  "keywords": [
6
6
  "file-copy",
@@ -8,7 +8,16 @@
8
8
  "strategy",
9
9
  "synchronization"
10
10
  ],
11
+ "homepage": "https://github.com/bingtsingw/syncat-dev#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/bingtsingw/syncat-dev/issues"
14
+ },
11
15
  "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/bingtsingw/syncat-dev",
19
+ "directory": "packages/cli"
20
+ },
12
21
  "bin": {
13
22
  "syncat": "./dist/cli.js"
14
23
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"dist-Bn-gPTzR.js","names":[],"sources":["../../core/dist/index.js"],"sourcesContent":["import { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { loadConfig } from \"c12\";\nimport { chmod, lstat, mkdir, readFile, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { randomUUID } from \"node:crypto\";\nimport { TextDecoder } from \"node:util\";\nimport fastGlob from \"fast-glob\";\n//#region src/errors.ts\nvar SyncatError = class extends Error {\n\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.name = \"SyncatError\";\n\t}\n};\n//#endregion\n//#region src/config.ts\nconst replaceRuleSchema = z.object({\n\tfrom: z.string().min(1, \"Replacement \\\"from\\\" must not be empty.\"),\n\tto: z.string(),\n\tall: z.boolean().optional()\n}).strict();\nconst strategySchema = z.discriminatedUnion(\"type\", [z.object({ type: z.literal(\"copy\") }).strict(), z.object({\n\ttype: z.literal(\"text-replace\"),\n\treplacements: z.array(replaceRuleSchema).min(1, \"A text-replace strategy needs at least one replacement.\")\n}).strict()]);\nconst syncatConfigSchema = z.object({\n\tsource: z.string().min(1),\n\ttarget: z.string().min(1),\n\tfiles: z.array(z.object({\n\t\tpath: z.string().min(1),\n\t\tstrategy: strategySchema.optional()\n\t}).strict()).min(1, \"At least one file rule is required.\")\n}).strict();\nfunction defineConfig(config) {\n\treturn config;\n}\nfunction resolveSyncatConfig(rawConfig, configFile) {\n\tconst parsed = syncatConfigSchema.safeParse(rawConfig);\n\tif (!parsed.success) throw new SyncatError(`Invalid syncat config:\\n${z.prettifyError(parsed.error)}`);\n\tconst configDir = dirname(configFile);\n\tconst sourceDir = resolveConfigPath(parsed.data.source, configDir);\n\tconst targetDir = resolveConfigPath(parsed.data.target, configDir);\n\tfor (const rule of parsed.data.files) assertSafeRulePath(rule.path);\n\treturn {\n\t\tconfig: parsed.data,\n\t\tconfigFile,\n\t\tsourceDir,\n\t\ttargetDir\n\t};\n}\nfunction resolveConfigPath(value, cwd) {\n\tconst home = process.env[\"HOME\"] ?? process.env[\"USERPROFILE\"];\n\tconst expanded = value === \"~\" && home ? home : value.startsWith(\"~/\") && home ? resolve(home, value.slice(2)) : value;\n\treturn resolve(cwd, expanded);\n}\nfunction assertSafeRulePath(value) {\n\tif (isAbsolute(value) || value.includes(\"\\\\\")) throw new SyncatError(`File rule path must be a relative POSIX path: ${value}`);\n\tif (value.split(\"/\").some((segment) => segment === \"..\") || value === \".\" || value.startsWith(\"../\")) throw new SyncatError(`File rule path must not escape its project root: ${value}`);\n}\nfunction assertPathInside(root, candidate) {\n\tconst pathRelative = relative(root, candidate);\n\tif (pathRelative === \"\" || !pathRelative.startsWith(`..${sep}`) && pathRelative !== \"..\" && !isAbsolute(pathRelative)) return;\n\tthrow new SyncatError(`Resolved path escapes its project root: ${candidate}`);\n}\nasync function assertRealPathInside(root, candidate) {\n\tconst [realRoot, realCandidate] = await Promise.all([realpath(root), realpath(candidate)]);\n\tassertPathInside(realRoot, realCandidate);\n}\nasync function assertExistingAncestorInside(root, candidate) {\n\tconst realRoot = await realpath(root);\n\tlet ancestor = candidate;\n\twhile (true) try {\n\t\tassertPathInside(realRoot, await realpath(ancestor));\n\t\treturn;\n\t} catch (error) {\n\t\tif (!isNotFound$2(error)) throw error;\n\t\tconst parent = dirname(ancestor);\n\t\tif (parent === ancestor) throw new SyncatError(`Could not find an existing ancestor for path: ${candidate}`);\n\t\tancestor = parent;\n\t}\n}\nfunction isNotFound$2(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/apply.ts\nasync function applySyncPlan(plan, options) {\n\tconst written = plan.drift;\n\tif (options.dryRun) return {\n\t\tdryRun: true,\n\t\twritten\n\t};\n\tfor (const entry of written) await writeEntry(plan.config.targetDir, entry.sourcePath, entry.targetPath, entry.desiredContent);\n\treturn {\n\t\tdryRun: false,\n\t\twritten\n\t};\n}\nasync function writeEntry(targetRoot, sourcePath, targetPath, contents) {\n\tawait mkdir(dirname(targetPath), { recursive: true });\n\tawait assertRealPathInside(targetRoot, dirname(targetPath));\n\tawait rejectTargetSymlink(targetPath);\n\tconst sourceStat = await stat(sourcePath);\n\tconst temporaryPath = join(dirname(targetPath), `.${basename(targetPath)}.syncat-${randomUUID()}.tmp`);\n\ttry {\n\t\tawait writeFile(temporaryPath, contents, { mode: sourceStat.mode });\n\t\tawait chmod(temporaryPath, sourceStat.mode);\n\t\tawait rename(temporaryPath, targetPath);\n\t} catch (error) {\n\t\tawait rm(temporaryPath, { force: true });\n\t\tthrow new SyncatError(`Failed to write ${targetPath}: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n}\nasync function rejectTargetSymlink(path) {\n\ttry {\n\t\tif ((await lstat(path)).isSymbolicLink()) throw new SyncatError(`Refusing to overwrite symbolic link target: ${path}`);\n\t} catch (error) {\n\t\tif (isNotFound$1(error)) return;\n\t\tthrow error;\n\t}\n}\nfunction isNotFound$1(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/diff.ts\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\nconst maximumComparisonCells = 1e6;\nfunction createUnifiedDiff(path, actual, expected) {\n\tlet actualText;\n\tlet expectedText;\n\ttry {\n\t\tactualText = decoder.decode(actual);\n\t\texpectedText = decoder.decode(expected);\n\t} catch {\n\t\treturn `Binary files differ: ${path}\\n`;\n\t}\n\tconst before = actualText.split(\"\\n\");\n\tconst after = expectedText.split(\"\\n\");\n\tif (before.length * after.length > maximumComparisonCells) return `Diff omitted for ${path}: files are too large for a line-by-line comparison.\\n`;\n\tconst table = Array.from({ length: before.length + 1 }, () => new Uint32Array(after.length + 1));\n\tfor (let beforeIndex = before.length - 1; beforeIndex >= 0; beforeIndex -= 1) for (let afterIndex = after.length - 1; afterIndex >= 0; afterIndex -= 1) {\n\t\tconst beforeLine = getLine(before, beforeIndex);\n\t\tconst afterLine = getLine(after, afterIndex);\n\t\tgetRow(table, beforeIndex)[afterIndex] = beforeLine === afterLine ? getCell(table, beforeIndex + 1, afterIndex + 1) + 1 : Math.max(getCell(table, beforeIndex + 1, afterIndex), getCell(table, beforeIndex, afterIndex + 1));\n\t}\n\tconst output = [\n\t\t`--- target/${path}`,\n\t\t`+++ expected/${path}`,\n\t\t`@@ -1,${before.length} +1,${after.length} @@`\n\t];\n\tlet beforeIndex = 0;\n\tlet afterIndex = 0;\n\twhile (beforeIndex < before.length || afterIndex < after.length) {\n\t\tconst beforeLine = beforeIndex < before.length ? getLine(before, beforeIndex) : void 0;\n\t\tconst afterLine = afterIndex < after.length ? getLine(after, afterIndex) : void 0;\n\t\tif (beforeLine !== void 0 && afterLine !== void 0 && beforeLine === afterLine) {\n\t\t\toutput.push(` ${beforeLine}`);\n\t\t\tbeforeIndex += 1;\n\t\t\tafterIndex += 1;\n\t\t} else if (afterLine !== void 0 && (beforeLine === void 0 || getCell(table, beforeIndex, afterIndex + 1) >= getCell(table, beforeIndex + 1, afterIndex))) {\n\t\t\toutput.push(`+${afterLine}`);\n\t\t\tafterIndex += 1;\n\t\t} else {\n\t\t\tif (beforeLine === void 0) throw new Error(\"Diff generation reached an invalid state.\");\n\t\t\toutput.push(`-${beforeLine}`);\n\t\t\tbeforeIndex += 1;\n\t\t}\n\t}\n\treturn `${output.join(\"\\n\")}\\n`;\n}\nfunction getCell(table, rowIndex, columnIndex) {\n\treturn getRow(table, rowIndex)[columnIndex] ?? 0;\n}\nfunction getLine(lines, index) {\n\tconst line = lines[index];\n\tif (line === void 0) throw new Error(\"Diff generation reached an invalid line index.\");\n\treturn line;\n}\nfunction getRow(table, rowIndex) {\n\tconst row = table[rowIndex];\n\tif (!row) throw new Error(\"Diff generation reached an invalid table row.\");\n\treturn row;\n}\n//#endregion\n//#region src/strategy.ts\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: true });\nfunction renderDesiredContent(source, strategy, path) {\n\tif (!strategy || strategy.type === \"copy\") return source;\n\tlet text;\n\ttry {\n\t\ttext = utf8Decoder.decode(source);\n\t} catch {\n\t\tthrow new SyncatError(`Text strategy cannot process non-UTF-8 file: ${path}`);\n\t}\n\tfor (const replacement of strategy.replacements) {\n\t\tif (text.split(replacement.from).length - 1 === 0) throw new SyncatError(`Replacement source was not found in ${path}: ${JSON.stringify(replacement.from)}`);\n\t\ttext = replacement.all === false ? text.replace(replacement.from, replacement.to) : text.split(replacement.from).join(replacement.to);\n\t}\n\treturn Buffer.from(text, \"utf8\");\n}\n//#endregion\n//#region src/plan.ts\nconst globSyntax = /[*?[\\]{}]/;\nasync function buildSyncPlan(config) {\n\tawait assertDirectory(config.sourceDir, \"source\");\n\tawait assertDirectory(config.targetDir, \"target\");\n\tconst [realSourceDir, realTargetDir] = await Promise.all([realpath(config.sourceDir), realpath(config.targetDir)]);\n\tif (realSourceDir === realTargetDir) throw new SyncatError(\"Configured source and target directories must be different.\");\n\tconst entries = [];\n\tconst seenPaths = /* @__PURE__ */ new Set();\n\tfor (const rule of config.config.files) {\n\t\tconst paths = await resolveRulePaths(config.sourceDir, rule.path);\n\t\tif (paths.length === 0) throw new SyncatError(`File rule matched no source files: ${rule.path}`);\n\t\tfor (const path of paths) {\n\t\t\tif (seenPaths.has(path)) throw new SyncatError(`A source file is managed by more than one rule: ${path}`);\n\t\t\tseenPaths.add(path);\n\t\t\tconst sourcePath = resolve(config.sourceDir, path);\n\t\t\tconst targetPath = resolve(config.targetDir, path);\n\t\t\tassertPathInside(config.sourceDir, sourcePath);\n\t\t\tassertPathInside(config.targetDir, targetPath);\n\t\t\tawait assertRealPathInside(config.sourceDir, sourcePath);\n\t\t\tawait assertRegularFile(sourcePath, \"source\");\n\t\t\tconst sourceContent = await readFile(sourcePath);\n\t\t\tconst desiredContent = renderDesiredContent(sourceContent, rule.strategy, path);\n\t\t\tconst targetContent = await readOptionalRegularFile(config.targetDir, targetPath);\n\t\t\tconst status = !targetContent ? \"missing\" : targetContent.equals(desiredContent) ? \"synced\" : \"changed\";\n\t\t\tentries.push({\n\t\t\t\tpath,\n\t\t\t\tsourcePath,\n\t\t\t\ttargetPath,\n\t\t\t\tsourceContent,\n\t\t\t\ttargetContent,\n\t\t\t\tdesiredContent,\n\t\t\t\tstatus\n\t\t\t});\n\t\t}\n\t}\n\tentries.sort((left, right) => left.path.localeCompare(right.path));\n\treturn {\n\t\tconfig,\n\t\tentries,\n\t\tdrift: entries.filter((entry) => entry.status !== \"synced\")\n\t};\n}\nasync function resolveRulePaths(sourceDir, rulePath) {\n\tif (!globSyntax.test(rulePath)) return [rulePath];\n\treturn fastGlob(rulePath, {\n\t\tcwd: sourceDir,\n\t\tonlyFiles: true,\n\t\tdot: true,\n\t\tfollowSymbolicLinks: false,\n\t\tunique: true\n\t});\n}\nasync function assertDirectory(path, label) {\n\tlet fileStat;\n\ttry {\n\t\tfileStat = await stat(path);\n\t} catch {\n\t\tthrow new SyncatError(`Configured ${label} directory does not exist: ${path}`);\n\t}\n\tif (!fileStat.isDirectory()) throw new SyncatError(`Configured ${label} path is not a directory: ${path}`);\n}\nasync function assertRegularFile(path, label) {\n\tif (!(await lstat(path)).isFile()) throw new SyncatError(`Managed ${label} path is not a regular file: ${path}`);\n}\nasync function readOptionalRegularFile(targetRoot, path) {\n\ttry {\n\t\tawait assertExistingAncestorInside(targetRoot, path);\n\t\tawait assertRegularFile(path, \"target\");\n\t\tawait assertRealPathInside(targetRoot, path);\n\t\treturn await readFile(path);\n\t} catch (error) {\n\t\tif (isNotFound(error)) return;\n\t\tthrow error;\n\t}\n}\nfunction isNotFound(error) {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\";\n}\n//#endregion\n//#region src/index.ts\nasync function loadSyncatConfig(options = {}) {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst configPath = resolve(cwd, options.configPath ?? \"syncat.config.ts\");\n\tconst loaded = await loadConfig({\n\t\tname: \"syncat\",\n\t\tcwd,\n\t\tconfigFile: configPath,\n\t\tconfigFileRequired: true,\n\t\tdotenv: false,\n\t\tenvName: false,\n\t\textend: false,\n\t\tgiget: false,\n\t\tglobalRc: false,\n\t\tpackageJson: false,\n\t\trcFile: false\n\t});\n\tif (!loaded.configFile) throw new Error(\"Could not determine the loaded syncat config file.\");\n\treturn resolveSyncatConfig(loaded.config, loaded.configFile);\n}\n//#endregion\nexport { SyncatError, applySyncPlan, buildSyncPlan, createUnifiedDiff, defineConfig, loadSyncatConfig, resolveSyncatConfig };\n\n//# sourceMappingURL=index.js.map"],"mappings":"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"}