agentln 0.1.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/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/args.d.ts +4 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +110 -0
- package/dist/args.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/executor.d.ts +11 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +163 -0
- package/dist/executor.js.map +1 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +29 -0
- package/dist/logger.js.map +1 -0
- package/dist/planner.d.ts +22 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +136 -0
- package/dist/planner.js.map +1 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +106 -0
- package/dist/prompts.js.map +1 -0
- package/dist/scanner.d.ts +25 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +147 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 abaktiar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# agentln
|
|
2
|
+
|
|
3
|
+
Cross-platform CLI that keeps `CLAUDE.md` and `AGENTS.md` in sync across an
|
|
4
|
+
entire repository by managing **relative symlinks** between them.
|
|
5
|
+
|
|
6
|
+
You make **one decision at the repository root** — which filename is the source
|
|
7
|
+
of truth — and `agentln` applies that decision recursively to every directory
|
|
8
|
+
in the repo.
|
|
9
|
+
|
|
10
|
+
- Works on macOS, Linux, and Windows (PowerShell, Git Bash, WSL).
|
|
11
|
+
- Uses Node's native `fs` symlink APIs. Never shells out to `ln`, `bash`, or
|
|
12
|
+
PowerShell.
|
|
13
|
+
- Designed for monorepos: Nx, Turborepo, pnpm workspaces, and nested apps.
|
|
14
|
+
|
|
15
|
+
## Install / Run
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# One-shot
|
|
19
|
+
npx agentln
|
|
20
|
+
pnpm dlx agentln
|
|
21
|
+
|
|
22
|
+
# Or install globally
|
|
23
|
+
npm i -g agentln
|
|
24
|
+
agentln
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Node.js 18+.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
At the repository root, `agentln` looks for `CLAUDE.md` and `AGENTS.md` and
|
|
32
|
+
asks you a single question based on what it finds:
|
|
33
|
+
|
|
34
|
+
| Root state | Question |
|
|
35
|
+
| --------------------------- | ------------------------------------------------------------------------------ |
|
|
36
|
+
| Only `CLAUDE.md` | Use `CLAUDE.md` as the source of truth across the repository? |
|
|
37
|
+
| Only `AGENTS.md` | Use `AGENTS.md` as the source of truth across the repository? |
|
|
38
|
+
| Both exist | Which one should be the source of truth? `CLAUDE.md` / `AGENTS.md` / Cancel. |
|
|
39
|
+
| Neither exists | Which file should become the repository standard? `CLAUDE.md` / `AGENTS.md`. |
|
|
40
|
+
|
|
41
|
+
Once you pick a source of truth (call it `SRC`), every directory in the repo
|
|
42
|
+
that contains either file is reconciled to the same convention:
|
|
43
|
+
|
|
44
|
+
| Directory contents | Result |
|
|
45
|
+
| ---------------------- | --------------------------------------------------------------------- |
|
|
46
|
+
| `SRC` only | Create `OTHER` as a symlink → `SRC`. |
|
|
47
|
+
| `OTHER` only | Rename `OTHER` → `SRC` (preserve content), then link `OTHER` → `SRC`. |
|
|
48
|
+
| Both exist | Preserve `SRC`. Replace `OTHER` with a symlink → `SRC`. |
|
|
49
|
+
| Neither | Do nothing. |
|
|
50
|
+
|
|
51
|
+
`agentln` is idempotent. Running it twice in a row will say "already in sync."
|
|
52
|
+
|
|
53
|
+
### Symlink format
|
|
54
|
+
|
|
55
|
+
Symlinks are always **relative** (`AGENTS.md → CLAUDE.md`), never absolute.
|
|
56
|
+
This keeps the repo portable across machines and clones.
|
|
57
|
+
|
|
58
|
+
### Ignored directories
|
|
59
|
+
|
|
60
|
+
The scanner skips common build / vendor folders by default:
|
|
61
|
+
|
|
62
|
+
`node_modules`, `.git`, `dist`, `build`, `.next`, `coverage`, `vendor`,
|
|
63
|
+
`.turbo`, `.cache`, `.parcel-cache`, `.svelte-kit`, `.nuxt`, `.output`,
|
|
64
|
+
`.vercel`, `.idea`, `.vscode`, and any other dotfile-prefixed directory.
|
|
65
|
+
|
|
66
|
+
## CLI flags
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
agentln [options]
|
|
70
|
+
|
|
71
|
+
--root <path> Repository root (default: current working directory).
|
|
72
|
+
--source <name> Use CLAUDE.md or AGENTS.md as source of truth (skips prompt).
|
|
73
|
+
-y, --yes Non-interactive mode. Accepts defaults for every prompt.
|
|
74
|
+
--dry-run Show planned changes without writing anything.
|
|
75
|
+
--force Overwrite divergent regular files when replacing with a symlink.
|
|
76
|
+
--copy-fallback On Windows, copy the file instead of failing when symlink
|
|
77
|
+
creation is denied.
|
|
78
|
+
--no-copy-fallback Disable the copy fallback (default).
|
|
79
|
+
--verbose Print debug-level information.
|
|
80
|
+
-h, --help Show help.
|
|
81
|
+
-v, --version Show version.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Examples
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Preview what would happen
|
|
88
|
+
npx agentln --dry-run
|
|
89
|
+
|
|
90
|
+
# Non-interactive, default to CLAUDE.md when both exist
|
|
91
|
+
npx agentln --yes --source CLAUDE.md
|
|
92
|
+
|
|
93
|
+
# Target a different repo
|
|
94
|
+
npx agentln --root ~/code/my-monorepo
|
|
95
|
+
|
|
96
|
+
# Replace existing AGENTS.md regular files (not just symlinks)
|
|
97
|
+
npx agentln --force
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Windows notes
|
|
101
|
+
|
|
102
|
+
Creating symbolic links on Windows requires either:
|
|
103
|
+
|
|
104
|
+
- **Developer Mode** enabled (Settings → Privacy & security → For developers), or
|
|
105
|
+
- an elevated shell, or
|
|
106
|
+
- the `SeCreateSymbolicLinkPrivilege` granted to your user.
|
|
107
|
+
|
|
108
|
+
If `agentln` cannot create a symlink, you have two options:
|
|
109
|
+
|
|
110
|
+
1. Re-run after enabling one of the above (recommended — symlinks keep both
|
|
111
|
+
filenames in sync automatically).
|
|
112
|
+
2. Pass `--copy-fallback` to write a regular file copy instead. The two files
|
|
113
|
+
will then diverge on subsequent edits until you reconcile them.
|
|
114
|
+
|
|
115
|
+
The fallback is **off by default** so you never silently lose the "single
|
|
116
|
+
source of truth" guarantee.
|
|
117
|
+
|
|
118
|
+
## Use cases
|
|
119
|
+
|
|
120
|
+
- Tools like Claude Code expect `CLAUDE.md`; other tools expect `AGENTS.md`.
|
|
121
|
+
Symlink them so both worlds read the same file.
|
|
122
|
+
- Monorepos with multiple workspaces, each having their own per-package
|
|
123
|
+
instructions: `agentln` reconciles every workspace in one pass.
|
|
124
|
+
|
|
125
|
+
## Publishing
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm run build
|
|
129
|
+
npm publish --access public
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The `prepublishOnly` script rebuilds `dist/` and ships only `dist/` plus the
|
|
133
|
+
README and license.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,YAAY,CAAC;AAqBpB,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAoEpD;AAsBD,wBAAgB,QAAQ,IAAI,MAAM,CAEjC"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AGENTS_FILE, CLAUDE_FILE, } from "./types.js";
|
|
3
|
+
const HELP_TEXT = `agentln — manage CLAUDE.md ⇄ AGENTS.md symlinks across a repo
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
npx agentln [options]
|
|
7
|
+
pnpm dlx agentln [options]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--root <path> Repository root (default: current working directory).
|
|
11
|
+
--source <name> Use CLAUDE.md or AGENTS.md as source of truth (skips prompt).
|
|
12
|
+
-y, --yes Non-interactive mode (assume defaults for every prompt).
|
|
13
|
+
--dry-run Show planned changes without writing anything.
|
|
14
|
+
--force Overwrite divergent regular files when replacing with a symlink.
|
|
15
|
+
--copy-fallback On Windows, fall back to a file copy if symlink creation is denied.
|
|
16
|
+
--no-copy-fallback Disable the copy fallback (default).
|
|
17
|
+
--verbose Print debug-level information.
|
|
18
|
+
-h, --help Show this help.
|
|
19
|
+
-v, --version Show version.
|
|
20
|
+
`;
|
|
21
|
+
export function parseArgs(argv) {
|
|
22
|
+
const opts = {
|
|
23
|
+
root: process.cwd(),
|
|
24
|
+
yes: false,
|
|
25
|
+
dryRun: false,
|
|
26
|
+
force: false,
|
|
27
|
+
verbose: false,
|
|
28
|
+
help: false,
|
|
29
|
+
version: false,
|
|
30
|
+
copyFallback: false,
|
|
31
|
+
noCopyFallback: false,
|
|
32
|
+
};
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
const arg = argv[i];
|
|
35
|
+
switch (arg) {
|
|
36
|
+
case "-h":
|
|
37
|
+
case "--help":
|
|
38
|
+
opts.help = true;
|
|
39
|
+
break;
|
|
40
|
+
case "-v":
|
|
41
|
+
case "--version":
|
|
42
|
+
opts.version = true;
|
|
43
|
+
break;
|
|
44
|
+
case "-y":
|
|
45
|
+
case "--yes":
|
|
46
|
+
opts.yes = true;
|
|
47
|
+
break;
|
|
48
|
+
case "--dry-run":
|
|
49
|
+
opts.dryRun = true;
|
|
50
|
+
break;
|
|
51
|
+
case "--force":
|
|
52
|
+
opts.force = true;
|
|
53
|
+
break;
|
|
54
|
+
case "--verbose":
|
|
55
|
+
opts.verbose = true;
|
|
56
|
+
break;
|
|
57
|
+
case "--copy-fallback":
|
|
58
|
+
opts.copyFallback = true;
|
|
59
|
+
break;
|
|
60
|
+
case "--no-copy-fallback":
|
|
61
|
+
opts.noCopyFallback = true;
|
|
62
|
+
opts.copyFallback = false;
|
|
63
|
+
break;
|
|
64
|
+
case "--root": {
|
|
65
|
+
const next = argv[++i];
|
|
66
|
+
if (!next)
|
|
67
|
+
throw new Error("--root requires a path argument");
|
|
68
|
+
opts.root = path.resolve(next);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "--source": {
|
|
72
|
+
const next = argv[++i];
|
|
73
|
+
opts.source = parseSource(next);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
default: {
|
|
77
|
+
if (arg.startsWith("--root=")) {
|
|
78
|
+
opts.root = path.resolve(arg.slice("--root=".length));
|
|
79
|
+
}
|
|
80
|
+
else if (arg.startsWith("--source=")) {
|
|
81
|
+
opts.source = parseSource(arg.slice("--source=".length));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return opts;
|
|
90
|
+
}
|
|
91
|
+
function parseSource(value) {
|
|
92
|
+
if (!value)
|
|
93
|
+
throw new Error("--source requires a value (CLAUDE.md or AGENTS.md)");
|
|
94
|
+
const normalised = value.trim();
|
|
95
|
+
if (normalised === CLAUDE_FILE ||
|
|
96
|
+
normalised.toLowerCase() === "claude" ||
|
|
97
|
+
normalised.toLowerCase() === "claude.md") {
|
|
98
|
+
return CLAUDE_FILE;
|
|
99
|
+
}
|
|
100
|
+
if (normalised === AGENTS_FILE ||
|
|
101
|
+
normalised.toLowerCase() === "agents" ||
|
|
102
|
+
normalised.toLowerCase() === "agents.md") {
|
|
103
|
+
return AGENTS_FILE;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Invalid --source value: ${value}. Expected CLAUDE.md or AGENTS.md.`);
|
|
106
|
+
}
|
|
107
|
+
export function helpText() {
|
|
108
|
+
return HELP_TEXT;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,GAAG,EAAE,CAAC,IAAI,CACR,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA+B,GAAa,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA0BA,wBAAsB,GAAG,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAuIjF"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { helpText, parseArgs } from "./args.js";
|
|
6
|
+
import { executeAction, isWindowsHost, platformLabel } from "./executor.js";
|
|
7
|
+
import { createLogger } from "./logger.js";
|
|
8
|
+
import { buildPlan, describeAction } from "./planner.js";
|
|
9
|
+
import { classifyRoot, decideRoot } from "./prompts.js";
|
|
10
|
+
import { describeEntry, scanRepository } from "./scanner.js";
|
|
11
|
+
async function readPackageVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
14
|
+
const pkgPath = path.join(here, "..", "package.json");
|
|
15
|
+
const raw = await fs.readFile(pkgPath, "utf8");
|
|
16
|
+
const pkg = JSON.parse(raw);
|
|
17
|
+
return pkg.version ?? "0.0.0";
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return "0.0.0";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
24
|
+
let options;
|
|
25
|
+
try {
|
|
26
|
+
options = parseArgs(argv);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
process.stderr.write(`${pc.red("✗")} ${err.message}\n`);
|
|
30
|
+
process.stderr.write(helpText());
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
if (options.help) {
|
|
34
|
+
process.stdout.write(helpText());
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (options.version) {
|
|
38
|
+
process.stdout.write(`${await readPackageVersion()}\n`);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
const logger = createLogger(options.verbose);
|
|
42
|
+
const root = path.resolve(options.root);
|
|
43
|
+
logger.step("agentln", `${root}`);
|
|
44
|
+
logger.debug(`Platform: ${platformLabel()}`);
|
|
45
|
+
// Verify the root directory exists before doing anything else.
|
|
46
|
+
try {
|
|
47
|
+
const stat = await fs.stat(root);
|
|
48
|
+
if (!stat.isDirectory()) {
|
|
49
|
+
logger.error(`${root} is not a directory.`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
logger.error(`Cannot read ${root}. Does the path exist?`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
// First pass: just read the root state so we can ask the right question.
|
|
58
|
+
// We do this with an arbitrary source (CLAUDE) — the answer doesn't depend
|
|
59
|
+
// on which file the user later picks, only on which files are present.
|
|
60
|
+
const initialScan = await scanRepository(root, "CLAUDE.md");
|
|
61
|
+
const rootState = initialScan.find((d) => d.dir === root);
|
|
62
|
+
if (!rootState) {
|
|
63
|
+
logger.error(`Failed to scan repository root at ${root}.`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
logger.debug(`Root CLAUDE.md: ${describeEntry(rootState.claude)}`);
|
|
67
|
+
logger.debug(`Root AGENTS.md: ${describeEntry(rootState.agents)}`);
|
|
68
|
+
const rootCase = classifyRoot(rootState);
|
|
69
|
+
logger.debug(`Root case: ${rootCase}`);
|
|
70
|
+
const decision = await decideRoot(rootState, {
|
|
71
|
+
yes: options.yes,
|
|
72
|
+
sourceFlag: options.source,
|
|
73
|
+
});
|
|
74
|
+
logger.info(`Source of truth: ${pc.bold(decision.source)}${decision.bootstrapRoot ? pc.dim(" (will be created at root)") : ""}`);
|
|
75
|
+
// Re-scan now that we know the actual source, so entry classification can
|
|
76
|
+
// judge whether existing symlinks point at the right sibling.
|
|
77
|
+
const dirs = await scanRepository(root, decision.source);
|
|
78
|
+
logger.debug(`Scanned ${dirs.length} relevant director${dirs.length === 1 ? "y" : "ies"}.`);
|
|
79
|
+
const plan = buildPlan({
|
|
80
|
+
root,
|
|
81
|
+
source: decision.source,
|
|
82
|
+
dirs,
|
|
83
|
+
bootstrapRoot: decision.bootstrapRoot,
|
|
84
|
+
force: options.force,
|
|
85
|
+
});
|
|
86
|
+
if (plan.actions.length === 0) {
|
|
87
|
+
logger.success(`Nothing to do. ${plan.cleanCount} director${plan.cleanCount === 1 ? "y is" : "ies are"} already in sync.`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
logger.step(options.dryRun ? "Planned changes (dry run)" : "Planned changes", `${plan.actions.length} action${plan.actions.length === 1 ? "" : "s"}`);
|
|
91
|
+
for (const action of plan.actions) {
|
|
92
|
+
logger.raw(` ${pc.dim("•")} ${describeAction(action)}`);
|
|
93
|
+
}
|
|
94
|
+
if (options.dryRun) {
|
|
95
|
+
logger.info("Dry run complete. No changes were written.");
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
if (isWindowsHost() && !options.copyFallback) {
|
|
99
|
+
logger.debug("Tip: pass --copy-fallback to write a regular file copy if symlink creation is denied on Windows.");
|
|
100
|
+
}
|
|
101
|
+
let failures = 0;
|
|
102
|
+
let copyFallbacks = 0;
|
|
103
|
+
for (const action of plan.actions) {
|
|
104
|
+
const result = await executeAction(action, {
|
|
105
|
+
dryRun: false,
|
|
106
|
+
force: options.force,
|
|
107
|
+
copyFallback: options.copyFallback,
|
|
108
|
+
});
|
|
109
|
+
if (result.fellBackToCopy)
|
|
110
|
+
copyFallbacks++;
|
|
111
|
+
if (!result.ok) {
|
|
112
|
+
failures++;
|
|
113
|
+
logger.error(`${describeAction(action)} — ${result.message}`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
logger.success(`${describeAction(action)} — ${result.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (copyFallbacks > 0) {
|
|
120
|
+
logger.warn(`${copyFallbacks} file${copyFallbacks === 1 ? "" : "s"} written as a copy instead of a symlink. ` +
|
|
121
|
+
"Edits to one file will not propagate to the other until real symlinks are restored.");
|
|
122
|
+
}
|
|
123
|
+
if (failures > 0) {
|
|
124
|
+
logger.error(`${failures} action${failures === 1 ? "" : "s"} failed.`);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
logger.success(`Done. ${plan.actions.length} action${plan.actions.length === 1 ? "" : "s"} applied.`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ActionResult, PlannedAction } from "./types.js";
|
|
2
|
+
export interface ExecuteOptions {
|
|
3
|
+
dryRun: boolean;
|
|
4
|
+
force: boolean;
|
|
5
|
+
/** If true, fall back to copying the file when symlink creation fails. */
|
|
6
|
+
copyFallback: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function executeAction(action: PlannedAction, options: ExecuteOptions): Promise<ActionResult>;
|
|
9
|
+
export declare function isWindowsHost(): boolean;
|
|
10
|
+
export declare function platformLabel(): string;
|
|
11
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,YAAY,EAAE,OAAO,CAAC;CACvB;AAgCD,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,YAAY,CAAC,CAsHvB;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
5
|
+
/**
|
|
6
|
+
* On Windows, `fs.symlink` needs an explicit type hint. We always use `file`
|
|
7
|
+
* for our use case (linking a single file). On POSIX systems the type arg is
|
|
8
|
+
* ignored.
|
|
9
|
+
*/
|
|
10
|
+
async function createRelativeSymlink(linkPath, sourcePath) {
|
|
11
|
+
const target = path.relative(path.dirname(linkPath), sourcePath);
|
|
12
|
+
await fs.symlink(target, linkPath, IS_WINDOWS ? "file" : undefined);
|
|
13
|
+
}
|
|
14
|
+
async function safeUnlink(p) {
|
|
15
|
+
try {
|
|
16
|
+
await fs.unlink(p);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const code = err.code;
|
|
20
|
+
if (code !== "ENOENT")
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function copyFile(sourcePath, destPath) {
|
|
25
|
+
await fs.copyFile(sourcePath, destPath);
|
|
26
|
+
}
|
|
27
|
+
function isSymlinkPermissionError(err) {
|
|
28
|
+
const code = err?.code;
|
|
29
|
+
return code === "EPERM" || code === "EACCES" || code === "ENOSYS";
|
|
30
|
+
}
|
|
31
|
+
export async function executeAction(action, options) {
|
|
32
|
+
switch (action.type) {
|
|
33
|
+
case "skip":
|
|
34
|
+
return { action, ok: true, message: `skipped (${action.reason})` };
|
|
35
|
+
case "create-root-source": {
|
|
36
|
+
if (options.dryRun) {
|
|
37
|
+
return { action, ok: true, message: "would create empty source file" };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const handle = await fs.open(action.sourcePath, "wx");
|
|
41
|
+
await handle.close();
|
|
42
|
+
return { action, ok: true, message: "created empty source file" };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const code = err.code;
|
|
46
|
+
if (code === "EEXIST") {
|
|
47
|
+
return { action, ok: true, message: "source file already exists" };
|
|
48
|
+
}
|
|
49
|
+
return { action, ok: false, message: errorMessage(err) };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
case "create-symlink": {
|
|
53
|
+
if (options.dryRun) {
|
|
54
|
+
const verb = action.replacesExisting ? "replace" : "create";
|
|
55
|
+
return { action, ok: true, message: `would ${verb} symlink` };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
if (action.replacesExisting)
|
|
59
|
+
await safeUnlink(action.linkPath);
|
|
60
|
+
await createRelativeSymlink(action.linkPath, action.sourcePath);
|
|
61
|
+
return { action, ok: true, message: "symlink created" };
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (IS_WINDOWS && isSymlinkPermissionError(err) && options.copyFallback) {
|
|
65
|
+
try {
|
|
66
|
+
await safeUnlink(action.linkPath);
|
|
67
|
+
await copyFile(action.sourcePath, action.linkPath);
|
|
68
|
+
return {
|
|
69
|
+
action,
|
|
70
|
+
ok: true,
|
|
71
|
+
message: "symlink not permitted on this system; wrote a file copy instead. " +
|
|
72
|
+
"Re-run after enabling Developer Mode or with admin privileges " +
|
|
73
|
+
"to use real symlinks.",
|
|
74
|
+
fellBackToCopy: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (copyErr) {
|
|
78
|
+
return { action, ok: false, message: errorMessage(copyErr) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (IS_WINDOWS && isSymlinkPermissionError(err)) {
|
|
82
|
+
return {
|
|
83
|
+
action,
|
|
84
|
+
ok: false,
|
|
85
|
+
message: "creating a symlink was denied by the OS. On Windows, enable " +
|
|
86
|
+
"Developer Mode or run an elevated shell, or re-run with " +
|
|
87
|
+
"--copy-fallback to write a regular file copy instead.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { action, ok: false, message: errorMessage(err) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
case "promote-to-source": {
|
|
94
|
+
if (options.dryRun) {
|
|
95
|
+
return {
|
|
96
|
+
action,
|
|
97
|
+
ok: true,
|
|
98
|
+
message: `would rename ${path.basename(action.fromPath)} → ${path.basename(action.toPath)} and link back`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
// If the target source path already exists (race or stale state) and
|
|
103
|
+
// we'd otherwise overwrite, bail out unless --force was requested.
|
|
104
|
+
let targetExists = false;
|
|
105
|
+
try {
|
|
106
|
+
await fs.lstat(action.toPath);
|
|
107
|
+
targetExists = true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
targetExists = false;
|
|
111
|
+
}
|
|
112
|
+
if (targetExists && !options.force) {
|
|
113
|
+
return {
|
|
114
|
+
action,
|
|
115
|
+
ok: false,
|
|
116
|
+
message: `${action.toPath} already exists; refusing to overwrite without --force.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (targetExists)
|
|
120
|
+
await safeUnlink(action.toPath);
|
|
121
|
+
await fs.rename(action.fromPath, action.toPath);
|
|
122
|
+
await createRelativeSymlink(action.fromPath, action.toPath);
|
|
123
|
+
return {
|
|
124
|
+
action,
|
|
125
|
+
ok: true,
|
|
126
|
+
message: "promoted file to source and linked back",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (IS_WINDOWS && isSymlinkPermissionError(err) && options.copyFallback) {
|
|
131
|
+
try {
|
|
132
|
+
await copyFile(action.toPath, action.fromPath);
|
|
133
|
+
return {
|
|
134
|
+
action,
|
|
135
|
+
ok: true,
|
|
136
|
+
message: "symlink not permitted; wrote a file copy as fallback. The " +
|
|
137
|
+
"two files will diverge on subsequent edits.",
|
|
138
|
+
fellBackToCopy: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (copyErr) {
|
|
142
|
+
return { action, ok: false, message: errorMessage(copyErr) };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { action, ok: false, message: errorMessage(err) };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function isWindowsHost() {
|
|
151
|
+
return IS_WINDOWS;
|
|
152
|
+
}
|
|
153
|
+
export function platformLabel() {
|
|
154
|
+
return `${process.platform} (${os.release()})`;
|
|
155
|
+
}
|
|
156
|
+
function errorMessage(err) {
|
|
157
|
+
if (err instanceof Error) {
|
|
158
|
+
const code = err.code;
|
|
159
|
+
return code ? `${code}: ${err.message}` : err.message;
|
|
160
|
+
}
|
|
161
|
+
return String(err);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
info(msg: string): void;
|
|
3
|
+
success(msg: string): void;
|
|
4
|
+
warn(msg: string): void;
|
|
5
|
+
error(msg: string): void;
|
|
6
|
+
debug(msg: string): void;
|
|
7
|
+
raw(msg: string): void;
|
|
8
|
+
step(label: string, detail?: string): void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createLogger(verbose: boolean): Logger;
|
|
11
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAyBrD"}
|