@syncat-dev/cli 0.1.0 → 0.3.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/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-
|
|
2
|
+
import { a as loadSyncatConfig, n as buildSyncPlan, r as createUnifiedDiff, t as applySyncPlan } from "./dist-2BXlu0sn.js";
|
|
3
3
|
import { cac } from "cac";
|
|
4
4
|
//#region package.json
|
|
5
|
-
var version = "0.
|
|
5
|
+
var version = "0.3.0";
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region src/commands.ts
|
|
8
8
|
async function runCheck(options) {
|
|
@@ -24,8 +24,10 @@ const strategySchema = z.discriminatedUnion("type", [z.object({ type: z.literal(
|
|
|
24
24
|
const syncatConfigSchema = z.object({
|
|
25
25
|
source: z.string().min(1),
|
|
26
26
|
target: z.string().min(1),
|
|
27
|
+
exclude: z.array(z.string().min(1)).optional(),
|
|
27
28
|
files: z.array(z.object({
|
|
28
29
|
path: z.string().min(1),
|
|
30
|
+
exclude: z.array(z.string().min(1)).optional(),
|
|
29
31
|
strategy: strategySchema.optional()
|
|
30
32
|
}).strict()).min(1, "At least one file rule is required.")
|
|
31
33
|
}).strict();
|
|
@@ -38,7 +40,11 @@ function resolveSyncatConfig(rawConfig, configFile) {
|
|
|
38
40
|
const configDir = dirname(configFile);
|
|
39
41
|
const sourceDir = resolveConfigPath(parsed.data.source, configDir);
|
|
40
42
|
const targetDir = resolveConfigPath(parsed.data.target, configDir);
|
|
41
|
-
for (const
|
|
43
|
+
for (const pattern of parsed.data.exclude ?? []) assertSafeRulePath(pattern);
|
|
44
|
+
for (const rule of parsed.data.files) {
|
|
45
|
+
assertSafeRulePath(rule.path);
|
|
46
|
+
for (const pattern of rule.exclude ?? []) assertSafeRulePath(pattern);
|
|
47
|
+
}
|
|
42
48
|
return {
|
|
43
49
|
config: parsed.data,
|
|
44
50
|
configFile,
|
|
@@ -200,7 +206,8 @@ async function buildSyncPlan(config) {
|
|
|
200
206
|
const entries = [];
|
|
201
207
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
202
208
|
for (const rule of config.config.files) {
|
|
203
|
-
const
|
|
209
|
+
const exclude = [...config.config.exclude ?? [], ...rule.exclude ?? []];
|
|
210
|
+
const paths = await resolveRulePaths(config.sourceDir, rule.path, exclude);
|
|
204
211
|
if (paths.length === 0) throw new SyncatError(`File rule matched no source files: ${rule.path}`);
|
|
205
212
|
for (const path of paths) {
|
|
206
213
|
if (seenPaths.has(path)) throw new SyncatError(`A source file is managed by more than one rule: ${path}`);
|
|
@@ -233,13 +240,14 @@ async function buildSyncPlan(config) {
|
|
|
233
240
|
drift: entries.filter((entry) => entry.status !== "synced")
|
|
234
241
|
};
|
|
235
242
|
}
|
|
236
|
-
async function resolveRulePaths(sourceDir, rulePath) {
|
|
237
|
-
if (!globSyntax.test(rulePath)) return [rulePath];
|
|
243
|
+
async function resolveRulePaths(sourceDir, rulePath, exclude) {
|
|
244
|
+
if (!globSyntax.test(rulePath) && (!exclude || exclude.length === 0)) return [rulePath];
|
|
238
245
|
return fastGlob(rulePath, {
|
|
239
246
|
cwd: sourceDir,
|
|
240
247
|
onlyFiles: true,
|
|
241
248
|
dot: true,
|
|
242
249
|
followSymbolicLinks: false,
|
|
250
|
+
ignore: exclude,
|
|
243
251
|
unique: true
|
|
244
252
|
});
|
|
245
253
|
}
|
|
@@ -291,4 +299,4 @@ async function loadSyncatConfig(options = {}) {
|
|
|
291
299
|
//#endregion
|
|
292
300
|
export { loadSyncatConfig as a, defineConfig as i, buildSyncPlan as n, createUnifiedDiff as r, applySyncPlan as t };
|
|
293
301
|
|
|
294
|
-
//# sourceMappingURL=dist-
|
|
302
|
+
//# sourceMappingURL=dist-2BXlu0sn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist-2BXlu0sn.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\texclude: z.array(z.string().min(1)).optional(),\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 pattern of parsed.data.exclude ?? []) assertSafeRulePath(pattern);\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 exclude = [...config.config.exclude ?? [], ...rule.exclude ?? []];\n\t\tconst paths = await resolveRulePaths(config.sourceDir, rule.path, 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,11 +16,13 @@ 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 {
|
|
22
23
|
source: string;
|
|
23
24
|
target: string;
|
|
25
|
+
exclude?: string[];
|
|
24
26
|
files: FileRule[];
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -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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"}
|