@krislavten/env-sync 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 krislavten
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,233 @@
1
+ # env-sync
2
+
3
+ > Local-first env file synchronization for people who live across worktrees, repos, and project variants.
4
+
5
+ `env-sync` is a small CLI for keeping local `.env` files aligned without turning secrets into a shared service, a git artifact, or a Slack message.
6
+
7
+ It is built around a simple idea: env files are local operational state, but modern development is rarely done in one checkout. You may have multiple worktrees for the same repo, a web app and worker split across directories, test-only env files, and project-specific variants that should never bleed into each other. `env-sync` gives that workflow a boring, explicit, auditable local mechanism.
8
+
9
+ ```sh
10
+ env-sync push pilot
11
+ env-sync pull pilot
12
+ env-sync status pilot
13
+ env-sync diff pilot
14
+ ```
15
+
16
+ By default, it syncs `.env.local` in the current directory to:
17
+
18
+ ```text
19
+ ~/.env-sync/<project>/files/.env.local
20
+ ```
21
+
22
+ No cloud account. No secret values in status output. No implicit overwrite.
23
+
24
+ ## Why
25
+
26
+ Most env-file workflows fail in one of three ways:
27
+
28
+ - **Copy-paste drift**: one worktree gets the new key, another keeps the stale value.
29
+ - **Unsafe visibility**: debugging commands print secret values into logs, terminals, or shared tickets.
30
+ - **Namespace accidents**: two projects both have `.env.local`, but the contents are not interchangeable.
31
+
32
+ `env-sync` treats those as product constraints, not documentation warnings.
33
+
34
+ ## Design Principles
35
+
36
+ - **Local-first**: the source of truth is on your machine, under `~/.env-sync`.
37
+ - **Project-explicit**: every sync operation belongs to a namespace such as `pilot`, `rush-app`, or `side-project`.
38
+ - **No silent clobbering**: `pull` refuses to overwrite different local content unless you pass `--force`.
39
+ - **Secret-aware output**: `status` and `diff` show key names and short digests, never raw values.
40
+ - **Repo-agnostic**: the CLI does not know or care whether the current directory is Pilot, a worktree, a monorepo package, or a standalone app.
41
+ - **Small surface area**: first make local synchronization dependable; add remote or team workflows only when the local contract is solid.
42
+
43
+ ## Install
44
+
45
+ Requirements:
46
+
47
+ - Node.js 20 or newer
48
+ - pnpm 10 or newer
49
+
50
+ From npm:
51
+
52
+ ```sh
53
+ npm install -g @krislavten/env-sync
54
+ ```
55
+
56
+ From source:
57
+
58
+ ```sh
59
+ git clone https://github.com/krislavten/env-sync.git
60
+ cd env-sync
61
+ pnpm install
62
+ pnpm build
63
+ pnpm link --global
64
+ ```
65
+
66
+ Then:
67
+
68
+ ```sh
69
+ env-sync --help
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ Save the current directory's `.env.local` under the `pilot` namespace:
75
+
76
+ ```sh
77
+ env-sync push pilot
78
+ ```
79
+
80
+ Restore it in another worktree:
81
+
82
+ ```sh
83
+ env-sync pull pilot
84
+ ```
85
+
86
+ If the destination already has different content, the pull is blocked:
87
+
88
+ ```text
89
+ .env.local: blocked-different
90
+ ```
91
+
92
+ Overwrite intentionally:
93
+
94
+ ```sh
95
+ env-sync pull pilot --force
96
+ ```
97
+
98
+ `--force` updates the local file in place. It does not create a backup or audit trail, so use it only when the stored snapshot is the version you intend to keep.
99
+
100
+ Check whether local and stored files match:
101
+
102
+ ```sh
103
+ env-sync status pilot
104
+ env-sync diff pilot
105
+ ```
106
+
107
+ `diff` output is intentionally redacted:
108
+
109
+ ```text
110
+ .env.local: different
111
+ ~ API_TOKEN local:f44e64e75f39 stored:8ed3f6ad685b
112
+ + NEW_FEATURE_FLAG stored:7f021a1415b8
113
+ ```
114
+
115
+ The key names are visible. The values are not.
116
+
117
+ ## Multiple Env Files
118
+
119
+ Pass extra files explicitly:
120
+
121
+ ```sh
122
+ env-sync push pilot \
123
+ --file .env.local \
124
+ --file .env.test.local \
125
+ --file apps/agent-hub/.env.local
126
+ ```
127
+
128
+ Or create a project config:
129
+
130
+ ```sh
131
+ env-sync init pilot \
132
+ --file .env.local \
133
+ --file .env.test.local \
134
+ --file apps/agent-hub/.env.local
135
+ ```
136
+
137
+ This writes:
138
+
139
+ ```json
140
+ {
141
+ "project": "pilot",
142
+ "files": [
143
+ ".env.local",
144
+ ".env.test.local",
145
+ "apps/agent-hub/.env.local"
146
+ ]
147
+ }
148
+ ```
149
+
150
+ `.env-sync.json` is safe to commit because it contains only the namespace and file paths, not secret values.
151
+
152
+ After `init`, the project argument becomes optional for read-only checks:
153
+
154
+ ```sh
155
+ env-sync status
156
+ env-sync diff
157
+ ```
158
+
159
+ ## Command Reference
160
+
161
+ ```text
162
+ env-sync init <project> [--file <path>...]
163
+ env-sync push <project> [--file <path>...]
164
+ env-sync pull <project> [--file <path>...] [--force]
165
+ env-sync status [project] [--file <path>...]
166
+ env-sync diff [project] [--file <path>...]
167
+ ```
168
+
169
+ If no `--file` is provided, `env-sync` uses files from `.env-sync.json`. If no config exists, it falls back to `.env.local`.
170
+
171
+ ## Storage Model
172
+
173
+ Each project namespace gets its own directory:
174
+
175
+ ```text
176
+ ~/.env-sync/
177
+ pilot/
178
+ files/
179
+ .env.local
180
+ .env.test.local
181
+ apps/agent-hub/.env.local
182
+ rush-app/
183
+ files/
184
+ .env.local
185
+ ```
186
+
187
+ This is deliberately plain. You can inspect it, back it up, remove it, or copy it between machines using tools you already trust.
188
+
189
+ The CLI creates project store directories with owner-only directory permissions and writes synced files with owner-only file permissions where the platform supports POSIX modes. The contents are still plaintext on disk.
190
+
191
+ ## Safety Model
192
+
193
+ `env-sync` is not a secret manager. It is a local file synchronization tool with conservative defaults.
194
+
195
+ What it does:
196
+
197
+ - Keeps project namespaces separated.
198
+ - Refuses accidental overwrites.
199
+ - Avoids printing secret values in `status` and `diff`.
200
+ - Stores synced files with owner-only file permissions where the platform supports it.
201
+ - Rejects env file paths outside the current project directory.
202
+
203
+ What it does not do:
204
+
205
+ - Encrypt stored env files.
206
+ - Manage cloud secret rotation.
207
+ - Share secrets with teammates.
208
+ - Decide which values are production-safe.
209
+
210
+ If you need centralized access control, audit logs, or encryption at rest, use a real secret manager. `env-sync` is for the local development gap before that system should be involved.
211
+
212
+ ## Development
213
+
214
+ ```sh
215
+ pnpm install
216
+ pnpm check
217
+ ```
218
+
219
+ `pnpm check` runs TypeScript build and tests.
220
+
221
+ ## Roadmap
222
+
223
+ - Safer `init` flows for existing repos and worktrees.
224
+ - Optional named profiles per project, for example `pilot/testing` and `pilot/dogfood`.
225
+ - Better human-readable summaries for added, removed, and changed keys.
226
+ - Optional encrypted local store.
227
+ - Optional import/export bundles for machine migration.
228
+
229
+ The project will stay local-first unless there is a clear reason to expand the trust boundary.
230
+
231
+ ## License
232
+
233
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import { CONFIG_FILE, diffText, initConfig, installHook, pull, push, resolveScope, status, uninstallHook } from "./index.js";
3
+ async function main(argv) {
4
+ const args = parseArgs(argv);
5
+ if (args.help || !args.command) {
6
+ printHelp();
7
+ return args.command ? 0 : 1;
8
+ }
9
+ if (args.command === "init") {
10
+ const result = await initConfig({ cwd: process.cwd(), project: args.project, files: args.files });
11
+ console.log(`created ${CONFIG_FILE}`);
12
+ console.log(`project: ${result.config.project}`);
13
+ console.log(`files: ${result.config.files.join(", ")}`);
14
+ return 0;
15
+ }
16
+ if (args.command === "hook") {
17
+ if (args.subcommand === "install") {
18
+ const project = requireArg(args.project, "hook install requires a project");
19
+ const result = await installHook({ cwd: process.cwd(), project, bin: args.bin });
20
+ console.log(`installed post-checkout hook: ${result.path}`);
21
+ return 0;
22
+ }
23
+ if (args.subcommand === "uninstall") {
24
+ const result = await uninstallHook(process.cwd());
25
+ console.log(`uninstalled post-checkout hook: ${result.path}`);
26
+ return 0;
27
+ }
28
+ throw new Error("usage: env-sync hook install <project> | env-sync hook uninstall");
29
+ }
30
+ const scope = await resolveScope({ cwd: process.cwd(), project: args.project, files: args.files });
31
+ if (args.command === "push") {
32
+ const results = await push(scope);
33
+ for (const result of results) {
34
+ console.log(`${result.file}: ${result.action}`);
35
+ }
36
+ return results.some((result) => result.action === "missing-local") ? 2 : 0;
37
+ }
38
+ if (args.command === "pull") {
39
+ const results = await pull(scope, args.force);
40
+ for (const result of results) {
41
+ console.log(`${result.file}: ${result.action}`);
42
+ }
43
+ return results.some((result) => result.action === "blocked-different" || result.action === "missing-store") ? 2 : 0;
44
+ }
45
+ if (args.command === "status" || args.command === "diff") {
46
+ const entries = await status(scope);
47
+ for (const entry of entries) {
48
+ console.log(`${entry.file}: ${entry.state}`);
49
+ if (args.command === "diff") {
50
+ const text = diffText(entry.localKeys, entry.storedKeys);
51
+ console.log(text ? indent(text) : " no key changes");
52
+ }
53
+ }
54
+ return entries.some((entry) => entry.state === "different" || entry.state.startsWith("missing")) ? 1 : 0;
55
+ }
56
+ throw new Error(`unknown command: ${args.command}`);
57
+ }
58
+ function parseArgs(argv) {
59
+ const parsed = { files: [], force: false, help: false };
60
+ for (let index = 0; index < argv.length; index += 1) {
61
+ const arg = argv[index];
62
+ if (arg === "--help" || arg === "-h") {
63
+ parsed.help = true;
64
+ continue;
65
+ }
66
+ if (arg === "--force") {
67
+ parsed.force = true;
68
+ continue;
69
+ }
70
+ if (arg === "--file" || arg === "-f") {
71
+ const file = argv[index + 1];
72
+ if (!file) {
73
+ throw new Error(`${arg} requires a path`);
74
+ }
75
+ parsed.files.push(file);
76
+ index += 1;
77
+ continue;
78
+ }
79
+ if (arg === "--bin") {
80
+ if (parsed.command !== "hook") {
81
+ throw new Error("--bin is only supported by env-sync hook install");
82
+ }
83
+ const bin = argv[index + 1];
84
+ if (!bin) {
85
+ throw new Error("--bin requires a path");
86
+ }
87
+ parsed.bin = bin;
88
+ index += 1;
89
+ continue;
90
+ }
91
+ if (arg.startsWith("-")) {
92
+ throw new Error(`unknown option: ${arg}`);
93
+ }
94
+ if (!parsed.command) {
95
+ parsed.command = arg;
96
+ continue;
97
+ }
98
+ if (parsed.command === "hook" && !parsed.subcommand) {
99
+ parsed.subcommand = arg;
100
+ continue;
101
+ }
102
+ if (!parsed.project) {
103
+ parsed.project = arg;
104
+ continue;
105
+ }
106
+ throw new Error(`unexpected argument: ${arg}`);
107
+ }
108
+ return parsed;
109
+ }
110
+ function printHelp() {
111
+ console.log(`env-sync
112
+
113
+ Usage:
114
+ env-sync init <project> [--file <path>...]
115
+ env-sync push <project> [--file <path>...]
116
+ env-sync pull <project> [--file <path>...] [--force]
117
+ env-sync status [project] [--file <path>...]
118
+ env-sync diff [project] [--file <path>...]
119
+ env-sync hook install <project>
120
+ env-sync hook uninstall
121
+ `);
122
+ }
123
+ function requireArg(value, message) {
124
+ if (!value) {
125
+ throw new Error(message);
126
+ }
127
+ return value;
128
+ }
129
+ function indent(text) {
130
+ return text
131
+ .split("\n")
132
+ .map((line) => ` ${line}`)
133
+ .join("\n");
134
+ }
135
+ main(process.argv.slice(2))
136
+ .then((code) => {
137
+ process.exitCode = code;
138
+ })
139
+ .catch((error) => {
140
+ console.error(error instanceof Error ? error.message : String(error));
141
+ process.exitCode = 1;
142
+ });
143
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ export declare const CONFIG_FILE = ".env-sync.json";
2
+ export declare const DEFAULT_ENV_FILE = ".env.local";
3
+ export type EnvSyncConfig = {
4
+ project?: string;
5
+ files?: string[];
6
+ };
7
+ export type ResolveOptions = {
8
+ cwd: string;
9
+ project?: string;
10
+ files?: string[];
11
+ };
12
+ export type ResolvedScope = {
13
+ cwd: string;
14
+ project: string;
15
+ files: string[];
16
+ storeRoot: string;
17
+ };
18
+ export type FileState = "missing-both" | "missing-local" | "missing-store" | "same" | "different";
19
+ export type StatusEntry = {
20
+ file: string;
21
+ state: FileState;
22
+ localKeys: RedactedEnvEntry[];
23
+ storedKeys: RedactedEnvEntry[];
24
+ };
25
+ export type RedactedEnvEntry = {
26
+ key: string;
27
+ digest: string;
28
+ };
29
+ export type PullResult = {
30
+ file: string;
31
+ action: "created" | "updated" | "skipped-same" | "blocked-different" | "missing-store";
32
+ };
33
+ export type PushResult = {
34
+ file: string;
35
+ action: "stored" | "missing-local";
36
+ };
37
+ export type InitResult = {
38
+ path: string;
39
+ config: Required<EnvSyncConfig>;
40
+ };
41
+ export declare function initConfig(options: ResolveOptions): Promise<InitResult>;
42
+ export declare function push(scope: ResolvedScope): Promise<PushResult[]>;
43
+ export declare function pull(scope: ResolvedScope, force?: boolean): Promise<PullResult[]>;
44
+ export declare function status(scope: ResolvedScope): Promise<StatusEntry[]>;
45
+ export declare function resolveScope(options: ResolveOptions): Promise<ResolvedScope>;
46
+ export declare function readConfig(cwd: string): Promise<EnvSyncConfig>;
47
+ export declare function projectStoreRoot(project: string): string;
48
+ export declare function storedFilePath(scope: ResolvedScope, file: string): string;
49
+ export declare function localFilePath(cwd: string, file: string): string;
50
+ export declare function redactEnv(content: string | undefined): RedactedEnvEntry[];
package/dist/core.js ADDED
@@ -0,0 +1,199 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ export const CONFIG_FILE = ".env-sync.json";
6
+ export const DEFAULT_ENV_FILE = ".env.local";
7
+ export async function initConfig(options) {
8
+ const project = requireProject(options.project);
9
+ const files = uniqueFiles(options.files?.length ? options.files : [DEFAULT_ENV_FILE]);
10
+ const config = { project, files };
11
+ const configPath = path.join(options.cwd, CONFIG_FILE);
12
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
13
+ return { path: configPath, config };
14
+ }
15
+ export async function push(scope) {
16
+ const results = [];
17
+ for (const file of scope.files) {
18
+ const localPath = localFilePath(scope.cwd, file);
19
+ const local = await readTextIfExists(localPath);
20
+ if (local === undefined) {
21
+ results.push({ file, action: "missing-local" });
22
+ continue;
23
+ }
24
+ const storedPath = storedFilePath(scope, file);
25
+ await mkdir(path.dirname(storedPath), { recursive: true, mode: 0o700 });
26
+ await writeFile(storedPath, local, { encoding: "utf8", mode: 0o600 });
27
+ results.push({ file, action: "stored" });
28
+ }
29
+ return results;
30
+ }
31
+ export async function pull(scope, force = false) {
32
+ const results = [];
33
+ for (const file of scope.files) {
34
+ const stored = await readTextIfExists(storedFilePath(scope, file));
35
+ if (stored === undefined) {
36
+ results.push({ file, action: "missing-store" });
37
+ continue;
38
+ }
39
+ const localPath = localFilePath(scope.cwd, file);
40
+ const local = await readTextIfExists(localPath);
41
+ if (local === stored) {
42
+ results.push({ file, action: "skipped-same" });
43
+ continue;
44
+ }
45
+ if (local !== undefined && !force) {
46
+ results.push({ file, action: "blocked-different" });
47
+ continue;
48
+ }
49
+ await mkdir(path.dirname(localPath), { recursive: true });
50
+ await writeFile(localPath, stored, { encoding: "utf8", mode: 0o600 });
51
+ results.push({ file, action: local === undefined ? "created" : "updated" });
52
+ }
53
+ return results;
54
+ }
55
+ export async function status(scope) {
56
+ const results = [];
57
+ for (const file of scope.files) {
58
+ const local = await readTextIfExists(localFilePath(scope.cwd, file));
59
+ const stored = await readTextIfExists(storedFilePath(scope, file));
60
+ results.push({
61
+ file,
62
+ state: fileState(local, stored),
63
+ localKeys: redactEnv(local),
64
+ storedKeys: redactEnv(stored),
65
+ });
66
+ }
67
+ return results;
68
+ }
69
+ export async function resolveScope(options) {
70
+ const config = await readConfig(options.cwd);
71
+ const project = options.project ?? config.project;
72
+ const files = options.files?.length ? options.files : config.files?.length ? config.files : [DEFAULT_ENV_FILE];
73
+ return {
74
+ cwd: options.cwd,
75
+ project: requireProject(project),
76
+ files: uniqueFiles(files),
77
+ storeRoot: projectStoreRoot(requireProject(project)),
78
+ };
79
+ }
80
+ export async function readConfig(cwd) {
81
+ const configPath = path.join(cwd, CONFIG_FILE);
82
+ const raw = await readTextIfExists(configPath);
83
+ if (raw === undefined) {
84
+ return {};
85
+ }
86
+ const parsed = JSON.parse(raw);
87
+ if (parsed.project !== undefined && typeof parsed.project !== "string") {
88
+ throw new Error(`${CONFIG_FILE}: project must be a string`);
89
+ }
90
+ if (parsed.files !== undefined && !Array.isArray(parsed.files)) {
91
+ throw new Error(`${CONFIG_FILE}: files must be an array`);
92
+ }
93
+ return parsed;
94
+ }
95
+ export function projectStoreRoot(project) {
96
+ return path.join(homedir(), ".env-sync", safeProjectName(project));
97
+ }
98
+ export function storedFilePath(scope, file) {
99
+ return path.join(scope.storeRoot, "files", safeRelativePath(file));
100
+ }
101
+ export function localFilePath(cwd, file) {
102
+ const resolved = path.resolve(cwd, file);
103
+ const relative = path.relative(cwd, resolved);
104
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
105
+ throw new Error(`file path must stay inside the current directory: ${file}`);
106
+ }
107
+ return resolved;
108
+ }
109
+ export function redactEnv(content) {
110
+ if (content === undefined) {
111
+ return [];
112
+ }
113
+ const entries = new Map();
114
+ for (const rawLine of content.split(/\r?\n/)) {
115
+ const line = rawLine.trim();
116
+ if (!line || line.startsWith("#")) {
117
+ continue;
118
+ }
119
+ const normalized = line.startsWith("export ") ? line.slice("export ".length).trimStart() : line;
120
+ const equalsIndex = normalized.indexOf("=");
121
+ if (equalsIndex <= 0) {
122
+ continue;
123
+ }
124
+ const key = normalized.slice(0, equalsIndex).trim();
125
+ const value = normalized.slice(equalsIndex + 1);
126
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
127
+ continue;
128
+ }
129
+ entries.set(key, shortDigest(value));
130
+ }
131
+ return [...entries.entries()]
132
+ .sort(([left], [right]) => left.localeCompare(right))
133
+ .map(([key, digest]) => ({ key, digest }));
134
+ }
135
+ function fileState(local, stored) {
136
+ if (local === undefined && stored === undefined) {
137
+ return "missing-both";
138
+ }
139
+ if (local === undefined) {
140
+ return "missing-local";
141
+ }
142
+ if (stored === undefined) {
143
+ return "missing-store";
144
+ }
145
+ return local === stored ? "same" : "different";
146
+ }
147
+ function safeProjectName(project) {
148
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(project)) {
149
+ throw new Error("project must contain only letters, numbers, dot, underscore, or dash");
150
+ }
151
+ return project;
152
+ }
153
+ function safeRelativePath(file) {
154
+ if (!file || path.isAbsolute(file)) {
155
+ throw new Error(`file path must be relative: ${file}`);
156
+ }
157
+ const normalized = path.normalize(file);
158
+ if (normalized.startsWith("..") || path.isAbsolute(normalized)) {
159
+ throw new Error(`file path must stay inside the current directory: ${file}`);
160
+ }
161
+ return normalized;
162
+ }
163
+ function requireProject(project) {
164
+ if (!project) {
165
+ throw new Error(`project is required. Pass it explicitly or run env-sync init <project>`);
166
+ }
167
+ return safeProjectName(project);
168
+ }
169
+ function uniqueFiles(files) {
170
+ const seen = new Set();
171
+ const unique = [];
172
+ for (const file of files) {
173
+ const safe = safeRelativePath(file);
174
+ if (!seen.has(safe)) {
175
+ seen.add(safe);
176
+ unique.push(safe);
177
+ }
178
+ }
179
+ return unique;
180
+ }
181
+ async function readTextIfExists(file) {
182
+ try {
183
+ await stat(file);
184
+ return await readFile(file, "utf8");
185
+ }
186
+ catch (error) {
187
+ if (isNodeError(error) && error.code === "ENOENT") {
188
+ return undefined;
189
+ }
190
+ throw error;
191
+ }
192
+ }
193
+ function shortDigest(value) {
194
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
195
+ }
196
+ function isNodeError(error) {
197
+ return error instanceof Error && "code" in error;
198
+ }
199
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
package/dist/hook.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type HookInstallOptions = {
2
+ cwd: string;
3
+ project: string;
4
+ bin?: string;
5
+ };
6
+ export type HookInstallResult = {
7
+ path: string;
8
+ };
9
+ export declare function installHook(options: HookInstallOptions): Promise<HookInstallResult>;
10
+ export declare function uninstallHook(cwd: string): Promise<HookInstallResult>;
11
+ export declare function renderHookBlock(project: string, bin: string): string;
package/dist/hook.js ADDED
@@ -0,0 +1,84 @@
1
+ import { execFile } from "node:child_process";
2
+ import { chmod, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ const execFileAsync = promisify(execFile);
6
+ const START_MARKER = "# >>> env-sync";
7
+ const END_MARKER = "# <<< env-sync";
8
+ const ZERO_HEAD = "0000000000000000000000000000000000000000";
9
+ export async function installHook(options) {
10
+ validateProject(options.project);
11
+ const hookPath = await gitPath(options.cwd, "hooks/post-checkout");
12
+ const existing = await readTextIfExists(hookPath);
13
+ const block = renderHookBlock(options.project, options.bin ?? "env-sync");
14
+ const next = upsertManagedBlock(existing ?? "#!/bin/sh\n", block);
15
+ await writeFile(hookPath, next, "utf8");
16
+ await chmod(hookPath, 0o755);
17
+ return { path: hookPath };
18
+ }
19
+ export async function uninstallHook(cwd) {
20
+ const hookPath = await gitPath(cwd, "hooks/post-checkout");
21
+ const existing = await readTextIfExists(hookPath);
22
+ if (existing === undefined) {
23
+ return { path: hookPath };
24
+ }
25
+ await writeFile(hookPath, removeManagedBlock(existing), "utf8");
26
+ return { path: hookPath };
27
+ }
28
+ export function renderHookBlock(project, bin) {
29
+ return `${START_MARKER}
30
+ # Managed by env-sync. Runs only for the initial checkout of a new worktree.
31
+ old_head="$1"
32
+ is_branch_checkout="$3"
33
+ zero="${ZERO_HEAD}"
34
+
35
+ if [ "$old_head" = "$zero" ] && [ "$is_branch_checkout" = "1" ]; then
36
+ ENV_SYNC_BIN=${quoteShell(bin)}
37
+ if [ -x "$ENV_SYNC_BIN" ] || command -v "$ENV_SYNC_BIN" >/dev/null 2>&1; then
38
+ "$ENV_SYNC_BIN" pull ${quoteShell(project)} || {
39
+ echo "env-sync: pull skipped or blocked; run env-sync status ${project}" >&2
40
+ }
41
+ else
42
+ echo "env-sync: command not found, skip env pull" >&2
43
+ fi
44
+ fi
45
+ ${END_MARKER}
46
+ `;
47
+ }
48
+ function upsertManagedBlock(existing, block) {
49
+ const without = removeManagedBlock(existing).trimEnd();
50
+ return `${without}\n\n${block}`;
51
+ }
52
+ function removeManagedBlock(existing) {
53
+ const pattern = new RegExp(`\\n?${escapeRegExp(START_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}\\n?`, "g");
54
+ const next = existing.replace(pattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd();
55
+ return next ? `${next}\n` : "";
56
+ }
57
+ async function gitPath(cwd, relativePath) {
58
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", relativePath], { cwd });
59
+ const gitPathOutput = stdout.trim();
60
+ return path.isAbsolute(gitPathOutput) ? gitPathOutput : path.resolve(cwd, gitPathOutput);
61
+ }
62
+ async function readTextIfExists(file) {
63
+ try {
64
+ return await readFile(file, "utf8");
65
+ }
66
+ catch (error) {
67
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
68
+ return undefined;
69
+ }
70
+ throw error;
71
+ }
72
+ }
73
+ function quoteShell(value) {
74
+ return `'${value.replaceAll("'", "'\"'\"'")}'`;
75
+ }
76
+ function validateProject(project) {
77
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(project)) {
78
+ throw new Error("project must contain only letters, numbers, dot, underscore, or dash");
79
+ }
80
+ }
81
+ function escapeRegExp(value) {
82
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
+ }
84
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from "./core.js";
2
+ export * from "./hook.js";
3
+ export * from "./render.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./core.js";
2
+ export * from "./hook.js";
3
+ export * from "./render.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { RedactedEnvEntry } from "./core.js";
2
+ export declare function diffText(localKeys: RedactedEnvEntry[], storedKeys: RedactedEnvEntry[]): string;
package/dist/render.js ADDED
@@ -0,0 +1,21 @@
1
+ export function diffText(localKeys, storedKeys) {
2
+ const local = new Map(localKeys.map((entry) => [entry.key, entry.digest]));
3
+ const stored = new Map(storedKeys.map((entry) => [entry.key, entry.digest]));
4
+ const keys = [...new Set([...local.keys(), ...stored.keys()])].sort();
5
+ const lines = [];
6
+ for (const key of keys) {
7
+ const localDigest = local.get(key);
8
+ const storedDigest = stored.get(key);
9
+ if (localDigest === undefined) {
10
+ lines.push(`+ ${key} stored:${storedDigest}`);
11
+ }
12
+ else if (storedDigest === undefined) {
13
+ lines.push(`- ${key} local:${localDigest}`);
14
+ }
15
+ else if (localDigest !== storedDigest) {
16
+ lines.push(`~ ${key} local:${localDigest} stored:${storedDigest}`);
17
+ }
18
+ }
19
+ return lines.join("\n");
20
+ }
21
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,QAAQ,CAAC,SAA6B,EAAE,UAA8B;IACpF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,WAAW,YAAY,EAAE,CAAC,CAAC;QAChD,CAAC;aAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,WAAW,EAAE,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,WAAW,WAAW,YAAY,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@krislavten/env-sync",
3
+ "version": "0.1.0",
4
+ "description": "A small local CLI for syncing env files across worktrees and projects.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/krislavten/env-sync.git"
8
+ },
9
+ "homepage": "https://github.com/krislavten/env-sync#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/krislavten/env-sync/issues"
12
+ },
13
+ "type": "module",
14
+ "bin": {
15
+ "env-sync": "dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "test": "vitest run",
25
+ "check": "pnpm build && pnpm test"
26
+ },
27
+ "keywords": [
28
+ "env",
29
+ "dotenv",
30
+ "cli",
31
+ "worktree"
32
+ ],
33
+ "author": "krislavten",
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "@types/node": "^22.10.0",
37
+ "typescript": "^5.7.2",
38
+ "vitest": "^2.1.8"
39
+ },
40
+ "engines": {
41
+ "node": ">=20"
42
+ }
43
+ }