@tinyrack/devsync 1.1.0 → 1.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/README.md +230 -62
- package/dist/cli/base-command.d.ts +14 -0
- package/dist/cli/base-command.d.ts.map +1 -0
- package/dist/cli/base-command.js +22 -0
- package/dist/cli/base-command.js.map +1 -0
- package/dist/cli/commands/dir.d.ts +8 -0
- package/dist/cli/commands/dir.d.ts.map +1 -0
- package/dist/cli/commands/dir.js +16 -0
- package/dist/cli/commands/dir.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +8 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +18 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/index.d.ts +23 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +23 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +43 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/machine/list.d.ts +7 -0
- package/dist/cli/commands/machine/list.d.ts.map +1 -0
- package/dist/cli/commands/machine/list.js +12 -0
- package/dist/cli/commands/machine/list.js.map +1 -0
- package/dist/cli/commands/machine/use.d.ts +11 -0
- package/dist/cli/commands/machine/use.d.ts.map +1 -0
- package/dist/cli/commands/machine/use.js +28 -0
- package/dist/cli/commands/machine/use.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +12 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +34 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/push.d.ts +12 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +34 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/status.d.ts +11 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +27 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/track.d.ts +16 -0
- package/dist/cli/commands/track.d.ts.map +1 -0
- package/dist/cli/commands/track.js +82 -0
- package/dist/cli/commands/track.js.map +1 -0
- package/dist/cli/commands/untrack.d.ts +11 -0
- package/dist/cli/commands/untrack.d.ts.map +1 -0
- package/dist/cli/commands/untrack.js +28 -0
- package/dist/cli/commands/untrack.js.map +1 -0
- package/dist/config/global-config.d.ts +21 -0
- package/dist/config/global-config.d.ts.map +1 -0
- package/dist/config/global-config.js +106 -0
- package/dist/config/global-config.js.map +1 -0
- package/dist/config/platform.d.ts +11 -0
- package/dist/config/platform.d.ts.map +1 -0
- package/dist/config/platform.js +19 -0
- package/dist/config/platform.js.map +1 -0
- package/dist/config/sync.d.ts +107 -0
- package/dist/config/sync.d.ts.map +1 -0
- package/dist/config/sync.js +424 -0
- package/dist/config/sync.js.map +1 -0
- package/dist/config/xdg.d.ts +14 -0
- package/dist/config/xdg.d.ts.map +1 -0
- package/dist/config/xdg.js +102 -0
- package/dist/config/xdg.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/lib/file-mode.d.ts +3 -0
- package/dist/lib/file-mode.d.ts.map +1 -0
- package/dist/lib/file-mode.js +7 -0
- package/dist/lib/file-mode.js.map +1 -0
- package/dist/lib/output.d.ts +31 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +198 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/path.d.ts +5 -0
- package/dist/lib/path.d.ts.map +1 -0
- package/dist/lib/path.js +25 -0
- package/dist/lib/path.js.map +1 -0
- package/dist/lib/string.d.ts +2 -0
- package/dist/lib/string.d.ts.map +1 -0
- package/dist/lib/string.js +4 -0
- package/dist/lib/string.js.map +1 -0
- package/dist/lib/validation.d.ts +3 -0
- package/dist/lib/validation.d.ts.map +1 -0
- package/dist/lib/validation.js +9 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/services/add.d.ts +20 -0
- package/dist/services/add.d.ts.map +1 -0
- package/dist/services/add.js +161 -0
- package/dist/services/add.js.map +1 -0
- package/dist/services/config-file.d.ts +45 -0
- package/dist/services/config-file.d.ts.map +1 -0
- package/dist/services/config-file.js +35 -0
- package/dist/services/config-file.js.map +1 -0
- package/dist/services/crypto.d.ts +9 -0
- package/dist/services/crypto.d.ts.map +1 -0
- package/dist/services/crypto.js +75 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/doctor.d.ts +16 -0
- package/dist/services/doctor.d.ts.map +1 -0
- package/dist/services/doctor.js +84 -0
- package/dist/services/doctor.js.map +1 -0
- package/dist/services/error.d.ts +14 -0
- package/dist/services/error.d.ts.map +1 -0
- package/dist/services/error.js +38 -0
- package/dist/services/error.js.map +1 -0
- package/dist/services/filesystem.d.ts +15 -0
- package/dist/services/filesystem.d.ts.map +1 -0
- package/dist/services/filesystem.js +113 -0
- package/dist/services/filesystem.js.map +1 -0
- package/dist/services/forget.d.ts +14 -0
- package/dist/services/forget.d.ts.map +1 -0
- package/dist/services/forget.js +124 -0
- package/dist/services/forget.js.map +1 -0
- package/dist/services/git.d.ts +10 -0
- package/dist/services/git.d.ts.map +1 -0
- package/dist/services/git.js +57 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/init.d.ts +19 -0
- package/dist/services/init.d.ts.map +1 -0
- package/dist/services/init.js +203 -0
- package/dist/services/init.js.map +1 -0
- package/dist/services/local-materialization.d.ts +28 -0
- package/dist/services/local-materialization.d.ts.map +1 -0
- package/dist/services/local-materialization.js +262 -0
- package/dist/services/local-materialization.js.map +1 -0
- package/dist/services/local-snapshot.d.ts +25 -0
- package/dist/services/local-snapshot.d.ts.map +1 -0
- package/dist/services/local-snapshot.js +93 -0
- package/dist/services/local-snapshot.js.map +1 -0
- package/dist/services/machine.d.ts +40 -0
- package/dist/services/machine.d.ts.map +1 -0
- package/dist/services/machine.js +113 -0
- package/dist/services/machine.js.map +1 -0
- package/dist/services/paths.d.ts +13 -0
- package/dist/services/paths.d.ts.map +1 -0
- package/dist/services/paths.js +71 -0
- package/dist/services/paths.js.map +1 -0
- package/dist/services/pull.d.ts +28 -0
- package/dist/services/pull.d.ts.map +1 -0
- package/dist/services/pull.js +67 -0
- package/dist/services/pull.js.map +1 -0
- package/dist/services/push.d.ts +35 -0
- package/dist/services/push.d.ts.map +1 -0
- package/dist/services/push.js +96 -0
- package/dist/services/push.js.map +1 -0
- package/dist/services/repo-artifacts.d.ts +52 -0
- package/dist/services/repo-artifacts.d.ts.map +1 -0
- package/dist/services/repo-artifacts.js +251 -0
- package/dist/services/repo-artifacts.js.map +1 -0
- package/dist/services/repo-snapshot.d.ts +6 -0
- package/dist/services/repo-snapshot.d.ts.map +1 -0
- package/dist/services/repo-snapshot.js +163 -0
- package/dist/services/repo-snapshot.js.map +1 -0
- package/dist/services/runtime.d.ts +40 -0
- package/dist/services/runtime.d.ts.map +1 -0
- package/dist/services/runtime.js +71 -0
- package/dist/services/runtime.js.map +1 -0
- package/dist/services/set.d.ts +38 -0
- package/dist/services/set.d.ts.map +1 -0
- package/dist/services/set.js +184 -0
- package/dist/services/set.js.map +1 -0
- package/dist/services/status.d.ts +30 -0
- package/dist/services/status.d.ts.map +1 -0
- package/dist/services/status.js +35 -0
- package/dist/services/status.js.map +1 -0
- package/package.json +15 -7
- package/src/cli/commands/add.ts +0 -40
- package/src/cli/commands/cd.ts +0 -80
- package/src/cli/commands/doctor.ts +0 -20
- package/src/cli/commands/forget.ts +0 -32
- package/src/cli/commands/index.ts +0 -23
- package/src/cli/commands/init.ts +0 -43
- package/src/cli/commands/list.ts +0 -17
- package/src/cli/commands/pull.ts +0 -31
- package/src/cli/commands/push.ts +0 -31
- package/src/cli/commands/set.ts +0 -47
- package/src/cli/commands/status.ts +0 -18
- package/src/cli/sync-output.test.ts +0 -173
- package/src/cli/sync-output.ts +0 -200
- package/src/config/sync.test.ts +0 -609
- package/src/config/sync.ts +0 -572
- package/src/config/xdg.ts +0 -138
- package/src/lib/string.test.ts +0 -13
- package/src/lib/string.ts +0 -3
- package/src/lib/validation.test.ts +0 -32
- package/src/lib/validation.ts +0 -11
- package/src/services/add.ts +0 -178
- package/src/services/config-file.test.ts +0 -161
- package/src/services/config-file.ts +0 -101
- package/src/services/crypto.test.ts +0 -132
- package/src/services/crypto.ts +0 -83
- package/src/services/doctor.ts +0 -142
- package/src/services/error.ts +0 -6
- package/src/services/filesystem.test.ts +0 -171
- package/src/services/filesystem.ts +0 -183
- package/src/services/forget.ts +0 -261
- package/src/services/git.test.ts +0 -83
- package/src/services/git.ts +0 -74
- package/src/services/init.test.ts +0 -109
- package/src/services/init.ts +0 -244
- package/src/services/list.ts +0 -63
- package/src/services/local-materialization.ts +0 -421
- package/src/services/local-snapshot.ts +0 -173
- package/src/services/paths.test.ts +0 -74
- package/src/services/paths.ts +0 -98
- package/src/services/pull.ts +0 -144
- package/src/services/push.ts +0 -168
- package/src/services/repo-artifacts.ts +0 -262
- package/src/services/repo-snapshot.ts +0 -197
- package/src/services/runtime.ts +0 -57
- package/src/services/set.ts +0 -383
- package/src/services/status.ts +0 -57
- package/src/services/sync.dry-run.test.ts +0 -179
- package/src/services/sync.runtime.test.ts +0 -756
- package/src/services/sync.service.test.ts +0 -1169
- package/src/test/helpers/sync-fixture.ts +0 -47
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { lstat, readFile, readlink } from "node:fs/promises";
|
|
2
|
-
import { join, posix } from "node:path";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
type ResolvedSyncConfig,
|
|
6
|
-
resolveManagedSyncMode,
|
|
7
|
-
} from "#app/config/sync.ts";
|
|
8
|
-
import { DevsyncError } from "./error.ts";
|
|
9
|
-
import {
|
|
10
|
-
getPathStats,
|
|
11
|
-
isExecutableMode,
|
|
12
|
-
listDirectoryEntries,
|
|
13
|
-
} from "./filesystem.ts";
|
|
14
|
-
import { assertStorageSafeRepoPath } from "./repo-artifacts.ts";
|
|
15
|
-
|
|
16
|
-
export type SnapshotNode =
|
|
17
|
-
| Readonly<{
|
|
18
|
-
type: "directory";
|
|
19
|
-
}>
|
|
20
|
-
| Readonly<{
|
|
21
|
-
executable: boolean;
|
|
22
|
-
secret: boolean;
|
|
23
|
-
type: "file";
|
|
24
|
-
contents: Uint8Array;
|
|
25
|
-
}>
|
|
26
|
-
| Readonly<{
|
|
27
|
-
linkTarget: string;
|
|
28
|
-
type: "symlink";
|
|
29
|
-
}>;
|
|
30
|
-
|
|
31
|
-
export type FileSnapshotNode = Extract<
|
|
32
|
-
SnapshotNode,
|
|
33
|
-
Readonly<{ type: "file" }>
|
|
34
|
-
>;
|
|
35
|
-
|
|
36
|
-
export type FileLikeSnapshotNode = Extract<
|
|
37
|
-
SnapshotNode,
|
|
38
|
-
Readonly<{ type: "file" | "symlink" }>
|
|
39
|
-
>;
|
|
40
|
-
|
|
41
|
-
export const addSnapshotNode = (
|
|
42
|
-
snapshot: Map<string, SnapshotNode>,
|
|
43
|
-
repoPath: string,
|
|
44
|
-
node: SnapshotNode,
|
|
45
|
-
) => {
|
|
46
|
-
if (snapshot.has(repoPath)) {
|
|
47
|
-
throw new DevsyncError(`Duplicate sync path generated for ${repoPath}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
snapshot.set(repoPath, node);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const addLocalNode = async (
|
|
54
|
-
snapshot: Map<string, SnapshotNode>,
|
|
55
|
-
config: ResolvedSyncConfig,
|
|
56
|
-
repoPath: string,
|
|
57
|
-
path: string,
|
|
58
|
-
stats: Awaited<ReturnType<typeof lstat>>,
|
|
59
|
-
) => {
|
|
60
|
-
assertStorageSafeRepoPath(repoPath);
|
|
61
|
-
const mode = resolveManagedSyncMode(config, repoPath);
|
|
62
|
-
|
|
63
|
-
if (mode === "ignore") {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (stats.isDirectory()) {
|
|
68
|
-
throw new DevsyncError(
|
|
69
|
-
`Expected a file-like path but found a directory: ${path}`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (stats.isSymbolicLink()) {
|
|
74
|
-
if (mode === "secret") {
|
|
75
|
-
throw new DevsyncError(
|
|
76
|
-
`Secret sync paths must be regular files, not symlinks: ${repoPath}`,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
addSnapshotNode(snapshot, repoPath, {
|
|
81
|
-
linkTarget: await readlink(path),
|
|
82
|
-
type: "symlink",
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!stats.isFile()) {
|
|
89
|
-
throw new DevsyncError(`Unsupported filesystem entry: ${path}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
addSnapshotNode(snapshot, repoPath, {
|
|
93
|
-
contents: await readFile(path),
|
|
94
|
-
executable: isExecutableMode(stats.mode),
|
|
95
|
-
secret: mode === "secret",
|
|
96
|
-
type: "file",
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const walkLocalDirectory = async (
|
|
101
|
-
snapshot: Map<string, SnapshotNode>,
|
|
102
|
-
config: ResolvedSyncConfig,
|
|
103
|
-
localDirectory: string,
|
|
104
|
-
repoPathPrefix: string,
|
|
105
|
-
) => {
|
|
106
|
-
const entries = await listDirectoryEntries(localDirectory);
|
|
107
|
-
|
|
108
|
-
for (const entry of entries) {
|
|
109
|
-
const localPath = join(localDirectory, entry.name);
|
|
110
|
-
const repoPath = posix.join(repoPathPrefix, entry.name);
|
|
111
|
-
const stats = await lstat(localPath);
|
|
112
|
-
|
|
113
|
-
if (stats.isDirectory()) {
|
|
114
|
-
assertStorageSafeRepoPath(repoPath);
|
|
115
|
-
await walkLocalDirectory(snapshot, config, localPath, repoPath);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
await addLocalNode(snapshot, config, repoPath, localPath, stats);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export const buildLocalSnapshot = async (config: ResolvedSyncConfig) => {
|
|
124
|
-
const snapshot = new Map<string, SnapshotNode>();
|
|
125
|
-
|
|
126
|
-
for (const entry of config.entries) {
|
|
127
|
-
const stats = await getPathStats(entry.localPath);
|
|
128
|
-
|
|
129
|
-
if (stats === undefined) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const entryMode = resolveManagedSyncMode(config, entry.repoPath);
|
|
134
|
-
|
|
135
|
-
if (entry.kind === "file") {
|
|
136
|
-
if (entryMode === "ignore") {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (stats.isDirectory()) {
|
|
141
|
-
throw new DevsyncError(
|
|
142
|
-
`Sync entry ${entry.name} expects a file, but found a directory: ${entry.localPath}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
await addLocalNode(
|
|
147
|
-
snapshot,
|
|
148
|
-
config,
|
|
149
|
-
entry.repoPath,
|
|
150
|
-
entry.localPath,
|
|
151
|
-
stats,
|
|
152
|
-
);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!stats.isDirectory()) {
|
|
157
|
-
throw new DevsyncError(
|
|
158
|
-
`Sync entry ${entry.name} expects a directory: ${entry.localPath}`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const snapshotSizeBeforeWalk = snapshot.size;
|
|
163
|
-
await walkLocalDirectory(snapshot, config, entry.localPath, entry.repoPath);
|
|
164
|
-
|
|
165
|
-
if (entryMode !== "ignore" || snapshot.size > snapshotSizeBeforeWalk) {
|
|
166
|
-
addSnapshotNode(snapshot, entry.repoPath, {
|
|
167
|
-
type: "directory",
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return snapshot;
|
|
173
|
-
};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { DevsyncError } from "#app/services/error.ts";
|
|
4
|
-
import {
|
|
5
|
-
buildConfiguredHomeLocalPath,
|
|
6
|
-
buildDirectoryKey,
|
|
7
|
-
buildRepoPathWithinRoot,
|
|
8
|
-
doPathsOverlap,
|
|
9
|
-
isExplicitLocalPath,
|
|
10
|
-
isPathEqualOrNested,
|
|
11
|
-
resolveCommandTargetPath,
|
|
12
|
-
tryBuildRepoPathWithinRoot,
|
|
13
|
-
tryNormalizeRepoPathInput,
|
|
14
|
-
} from "#app/services/paths.ts";
|
|
15
|
-
|
|
16
|
-
describe("path helpers", () => {
|
|
17
|
-
it("builds repository directory keys", () => {
|
|
18
|
-
expect(buildDirectoryKey("bundle/cache")).toBe("bundle/cache/");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("detects nested and overlapping paths", () => {
|
|
22
|
-
expect(isPathEqualOrNested("/tmp/home/project/file.txt", "/tmp/home")).toBe(
|
|
23
|
-
true,
|
|
24
|
-
);
|
|
25
|
-
expect(isPathEqualOrNested("/tmp/elsewhere", "/tmp/home")).toBe(false);
|
|
26
|
-
expect(doPathsOverlap("/tmp/home/project", "/tmp/home")).toBe(true);
|
|
27
|
-
expect(doPathsOverlap("/tmp/home/one", "/tmp/home/two")).toBe(false);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("recognizes explicit local path inputs", () => {
|
|
31
|
-
expect(isExplicitLocalPath(".")).toBe(true);
|
|
32
|
-
expect(isExplicitLocalPath("~/bundle")).toBe(true);
|
|
33
|
-
expect(isExplicitLocalPath("../bundle")).toBe(true);
|
|
34
|
-
expect(isExplicitLocalPath("bundle/file.txt")).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("resolves command targets from cwd and home prefixes", () => {
|
|
38
|
-
expect(
|
|
39
|
-
resolveCommandTargetPath("~/bundle", { HOME: "/tmp/home" }, "/tmp/cwd"),
|
|
40
|
-
).toBe("/tmp/home/bundle");
|
|
41
|
-
expect(
|
|
42
|
-
resolveCommandTargetPath("./bundle", { HOME: "/tmp/home" }, "/tmp/cwd"),
|
|
43
|
-
).toBe("/tmp/cwd/bundle");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("builds repository paths within a root", () => {
|
|
47
|
-
expect(
|
|
48
|
-
buildRepoPathWithinRoot(
|
|
49
|
-
"/tmp/home/.config/tool/settings.json",
|
|
50
|
-
"/tmp/home",
|
|
51
|
-
"Sync target",
|
|
52
|
-
),
|
|
53
|
-
).toBe(".config/tool/settings.json");
|
|
54
|
-
expect(buildConfiguredHomeLocalPath(".config/tool/settings.json")).toBe(
|
|
55
|
-
"~/.config/tool/settings.json",
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("rejects root and out-of-root repository paths", () => {
|
|
60
|
-
expect(() => {
|
|
61
|
-
buildRepoPathWithinRoot("/tmp/home", "/tmp/home", "Sync target");
|
|
62
|
-
}).toThrowError(DevsyncError);
|
|
63
|
-
expect(() => {
|
|
64
|
-
buildRepoPathWithinRoot("/tmp/elsewhere", "/tmp/home", "Sync target");
|
|
65
|
-
}).toThrowError(DevsyncError);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("returns undefined from tolerant helpers for invalid inputs", () => {
|
|
69
|
-
expect(
|
|
70
|
-
tryBuildRepoPathWithinRoot("/tmp/elsewhere", "/tmp/home", "Sync target"),
|
|
71
|
-
).toBeUndefined();
|
|
72
|
-
expect(tryNormalizeRepoPathInput("../bundle")).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
package/src/services/paths.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
|
-
|
|
3
|
-
import { normalizeSyncRepoPath } from "#app/config/sync.ts";
|
|
4
|
-
import { expandHomePath } from "#app/config/xdg.ts";
|
|
5
|
-
|
|
6
|
-
import { DevsyncError } from "./error.ts";
|
|
7
|
-
|
|
8
|
-
export const buildDirectoryKey = (repoPath: string) => {
|
|
9
|
-
return `${repoPath}/`;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const isPathEqualOrNested = (path: string, rootPath: string) => {
|
|
13
|
-
const rootToPath = relative(rootPath, path);
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
rootToPath === "" ||
|
|
17
|
-
(!isAbsolute(rootToPath) &&
|
|
18
|
-
!rootToPath.startsWith("..") &&
|
|
19
|
-
rootToPath !== "..")
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const doPathsOverlap = (leftPath: string, rightPath: string) => {
|
|
24
|
-
return (
|
|
25
|
-
isPathEqualOrNested(leftPath, rightPath) ||
|
|
26
|
-
isPathEqualOrNested(rightPath, leftPath)
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const isExplicitLocalPath = (target: string) => {
|
|
31
|
-
return (
|
|
32
|
-
target === "." ||
|
|
33
|
-
target === ".." ||
|
|
34
|
-
target === "~" ||
|
|
35
|
-
target.startsWith("./") ||
|
|
36
|
-
target.startsWith("../") ||
|
|
37
|
-
target.startsWith("~/") ||
|
|
38
|
-
isAbsolute(target)
|
|
39
|
-
);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const resolveCommandTargetPath = (
|
|
43
|
-
target: string,
|
|
44
|
-
environment: NodeJS.ProcessEnv,
|
|
45
|
-
cwd: string,
|
|
46
|
-
) => {
|
|
47
|
-
return resolve(cwd, expandHomePath(target, environment));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const buildRepoPathWithinRoot = (
|
|
51
|
-
absolutePath: string,
|
|
52
|
-
rootPath: string,
|
|
53
|
-
description: string,
|
|
54
|
-
) => {
|
|
55
|
-
const relativePath = relative(rootPath, absolutePath);
|
|
56
|
-
|
|
57
|
-
if (relativePath === "") {
|
|
58
|
-
throw new DevsyncError(
|
|
59
|
-
`${description} must be inside ${rootPath}, not the root itself: ${absolutePath}`,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
isAbsolute(relativePath) ||
|
|
65
|
-
relativePath.startsWith("..") ||
|
|
66
|
-
relativePath === ".."
|
|
67
|
-
) {
|
|
68
|
-
throw new DevsyncError(
|
|
69
|
-
`${description} must be inside ${rootPath}: ${absolutePath}`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return normalizeSyncRepoPath(relativePath);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const buildConfiguredHomeLocalPath = (repoPath: string) => {
|
|
77
|
-
return `~/${repoPath}`;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const tryBuildRepoPathWithinRoot = (
|
|
81
|
-
absolutePath: string,
|
|
82
|
-
rootPath: string,
|
|
83
|
-
description: string,
|
|
84
|
-
) => {
|
|
85
|
-
try {
|
|
86
|
-
return buildRepoPathWithinRoot(absolutePath, rootPath, description);
|
|
87
|
-
} catch {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export const tryNormalizeRepoPathInput = (value: string) => {
|
|
93
|
-
try {
|
|
94
|
-
return normalizeSyncRepoPath(value);
|
|
95
|
-
} catch {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
};
|
package/src/services/pull.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { type ResolvedSyncConfig, readSyncConfig } from "#app/config/sync.ts";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
applyEntryMaterialization,
|
|
5
|
-
buildEntryMaterialization,
|
|
6
|
-
buildPullCounts,
|
|
7
|
-
countDeletedLocalNodes,
|
|
8
|
-
} from "./local-materialization.ts";
|
|
9
|
-
import { buildRepositorySnapshot } from "./repo-snapshot.ts";
|
|
10
|
-
import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
|
|
11
|
-
|
|
12
|
-
export type SyncPullRequest = Readonly<{
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
}>;
|
|
15
|
-
|
|
16
|
-
export type SyncPullResult = Readonly<{
|
|
17
|
-
configPath: string;
|
|
18
|
-
decryptedFileCount: number;
|
|
19
|
-
deletedLocalCount: number;
|
|
20
|
-
directoryCount: number;
|
|
21
|
-
dryRun: boolean;
|
|
22
|
-
plainFileCount: number;
|
|
23
|
-
symlinkCount: number;
|
|
24
|
-
syncDirectory: string;
|
|
25
|
-
}>;
|
|
26
|
-
|
|
27
|
-
export type PullPlan = Readonly<{
|
|
28
|
-
counts: ReturnType<typeof buildPullCounts>;
|
|
29
|
-
deletedLocalCount: number;
|
|
30
|
-
desiredKeys: ReadonlySet<string>;
|
|
31
|
-
existingKeys: ReadonlySet<string>;
|
|
32
|
-
materializations: readonly ReturnType<typeof buildEntryMaterialization>[];
|
|
33
|
-
}>;
|
|
34
|
-
|
|
35
|
-
const collectDesiredKeys = (
|
|
36
|
-
materializations: readonly ReturnType<typeof buildEntryMaterialization>[],
|
|
37
|
-
) => {
|
|
38
|
-
const keys = new Set<string>();
|
|
39
|
-
|
|
40
|
-
for (const materialization of materializations) {
|
|
41
|
-
for (const key of materialization.desiredKeys) {
|
|
42
|
-
keys.add(key);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return keys;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const buildPullPlan = async (
|
|
50
|
-
config: ResolvedSyncConfig,
|
|
51
|
-
context: SyncContext,
|
|
52
|
-
): Promise<PullPlan> => {
|
|
53
|
-
const snapshot = await buildRepositorySnapshot(
|
|
54
|
-
context.paths.syncDirectory,
|
|
55
|
-
config,
|
|
56
|
-
);
|
|
57
|
-
const materializations = config.entries.map((entry) => {
|
|
58
|
-
return buildEntryMaterialization(entry, snapshot);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
let deletedLocalCount = 0;
|
|
62
|
-
const existingKeys = new Set<string>();
|
|
63
|
-
|
|
64
|
-
for (let index = 0; index < config.entries.length; index += 1) {
|
|
65
|
-
const entry = config.entries[index];
|
|
66
|
-
const materialization = materializations[index];
|
|
67
|
-
|
|
68
|
-
if (entry === undefined || materialization === undefined) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
deletedLocalCount += await countDeletedLocalNodes(
|
|
73
|
-
entry,
|
|
74
|
-
materialization.desiredKeys,
|
|
75
|
-
config,
|
|
76
|
-
existingKeys,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
counts: buildPullCounts(materializations),
|
|
82
|
-
deletedLocalCount,
|
|
83
|
-
desiredKeys: collectDesiredKeys(materializations),
|
|
84
|
-
existingKeys,
|
|
85
|
-
materializations,
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const buildPullPlanPreview = (plan: PullPlan) => {
|
|
90
|
-
const desired = [...plan.desiredKeys].sort((left, right) => {
|
|
91
|
-
return left.localeCompare(right);
|
|
92
|
-
});
|
|
93
|
-
const deleted = [...plan.existingKeys]
|
|
94
|
-
.filter((key) => {
|
|
95
|
-
return !plan.desiredKeys.has(key);
|
|
96
|
-
})
|
|
97
|
-
.sort((left, right) => {
|
|
98
|
-
return left.localeCompare(right);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return [...desired.slice(0, 4), ...deleted.slice(0, 4)].slice(0, 6);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export const buildPullResultFromPlan = (
|
|
105
|
-
plan: PullPlan,
|
|
106
|
-
context: SyncContext,
|
|
107
|
-
dryRun: boolean,
|
|
108
|
-
): SyncPullResult => {
|
|
109
|
-
return {
|
|
110
|
-
configPath: context.paths.configPath,
|
|
111
|
-
deletedLocalCount: plan.deletedLocalCount,
|
|
112
|
-
dryRun,
|
|
113
|
-
syncDirectory: context.paths.syncDirectory,
|
|
114
|
-
...plan.counts,
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const pullSync = async (
|
|
119
|
-
request: SyncPullRequest,
|
|
120
|
-
context: SyncContext,
|
|
121
|
-
): Promise<SyncPullResult> => {
|
|
122
|
-
await ensureSyncRepository(context);
|
|
123
|
-
|
|
124
|
-
const config = await readSyncConfig(
|
|
125
|
-
context.paths.syncDirectory,
|
|
126
|
-
context.environment,
|
|
127
|
-
);
|
|
128
|
-
const plan = await buildPullPlan(config, context);
|
|
129
|
-
|
|
130
|
-
for (let index = 0; index < config.entries.length; index += 1) {
|
|
131
|
-
const entry = config.entries[index];
|
|
132
|
-
const materialization = plan.materializations[index];
|
|
133
|
-
|
|
134
|
-
if (entry === undefined || materialization === undefined) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!request.dryRun) {
|
|
139
|
-
await applyEntryMaterialization(entry, materialization, config);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return buildPullResultFromPlan(plan, context, request.dryRun);
|
|
144
|
-
};
|
package/src/services/push.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
type ResolvedSyncConfig,
|
|
6
|
-
readSyncConfig,
|
|
7
|
-
resolveSyncArtifactsDirectoryPath,
|
|
8
|
-
} from "#app/config/sync.ts";
|
|
9
|
-
|
|
10
|
-
import { replacePathAtomically } from "./filesystem.ts";
|
|
11
|
-
import { buildLocalSnapshot, type SnapshotNode } from "./local-snapshot.ts";
|
|
12
|
-
import {
|
|
13
|
-
buildArtifactKey,
|
|
14
|
-
buildRepoArtifacts,
|
|
15
|
-
collectExistingArtifactKeys,
|
|
16
|
-
writeArtifactsToDirectory,
|
|
17
|
-
} from "./repo-artifacts.ts";
|
|
18
|
-
import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
|
|
19
|
-
|
|
20
|
-
export type SyncPushRequest = Readonly<{
|
|
21
|
-
dryRun: boolean;
|
|
22
|
-
}>;
|
|
23
|
-
|
|
24
|
-
export type SyncPushResult = Readonly<{
|
|
25
|
-
configPath: string;
|
|
26
|
-
deletedArtifactCount: number;
|
|
27
|
-
directoryCount: number;
|
|
28
|
-
dryRun: boolean;
|
|
29
|
-
encryptedFileCount: number;
|
|
30
|
-
plainFileCount: number;
|
|
31
|
-
symlinkCount: number;
|
|
32
|
-
syncDirectory: string;
|
|
33
|
-
}>;
|
|
34
|
-
|
|
35
|
-
export type PushPlan = Readonly<{
|
|
36
|
-
counts: ReturnType<typeof buildPushCounts>;
|
|
37
|
-
deletedArtifactCount: number;
|
|
38
|
-
desiredArtifactKeys: ReadonlySet<string>;
|
|
39
|
-
existingArtifactKeys: ReadonlySet<string>;
|
|
40
|
-
snapshot: ReadonlyMap<string, SnapshotNode>;
|
|
41
|
-
}>;
|
|
42
|
-
|
|
43
|
-
const buildPushCounts = (snapshot: ReadonlyMap<string, SnapshotNode>) => {
|
|
44
|
-
let directoryCount = 0;
|
|
45
|
-
let encryptedFileCount = 0;
|
|
46
|
-
let plainFileCount = 0;
|
|
47
|
-
let symlinkCount = 0;
|
|
48
|
-
|
|
49
|
-
for (const node of snapshot.values()) {
|
|
50
|
-
if (node.type === "directory") {
|
|
51
|
-
directoryCount += 1;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (node.type === "symlink") {
|
|
56
|
-
symlinkCount += 1;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (node.secret) {
|
|
61
|
-
encryptedFileCount += 1;
|
|
62
|
-
} else {
|
|
63
|
-
plainFileCount += 1;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
directoryCount,
|
|
69
|
-
encryptedFileCount,
|
|
70
|
-
plainFileCount,
|
|
71
|
-
symlinkCount,
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export const buildPushPlan = async (
|
|
76
|
-
config: ResolvedSyncConfig,
|
|
77
|
-
context: SyncContext,
|
|
78
|
-
): Promise<PushPlan> => {
|
|
79
|
-
const snapshot = await buildLocalSnapshot(config);
|
|
80
|
-
const artifacts = await buildRepoArtifacts(snapshot, config);
|
|
81
|
-
const desiredArtifactKeys = new Set(
|
|
82
|
-
artifacts.map((artifact) => {
|
|
83
|
-
return buildArtifactKey(artifact);
|
|
84
|
-
}),
|
|
85
|
-
);
|
|
86
|
-
const existingArtifactKeys = await collectExistingArtifactKeys(
|
|
87
|
-
context.paths.syncDirectory,
|
|
88
|
-
config,
|
|
89
|
-
);
|
|
90
|
-
const deletedArtifactCount = [...existingArtifactKeys].filter((key) => {
|
|
91
|
-
return !desiredArtifactKeys.has(key);
|
|
92
|
-
}).length;
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
counts: buildPushCounts(snapshot),
|
|
96
|
-
deletedArtifactCount,
|
|
97
|
-
desiredArtifactKeys,
|
|
98
|
-
existingArtifactKeys,
|
|
99
|
-
snapshot,
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const buildPushPlanPreview = (plan: PushPlan) => {
|
|
104
|
-
const createdOrUpdated = [...plan.snapshot.keys()].sort((left, right) => {
|
|
105
|
-
return left.localeCompare(right);
|
|
106
|
-
});
|
|
107
|
-
const deleted = [...plan.existingArtifactKeys]
|
|
108
|
-
.filter((key) => {
|
|
109
|
-
return !plan.desiredArtifactKeys.has(key);
|
|
110
|
-
})
|
|
111
|
-
.sort((left, right) => {
|
|
112
|
-
return left.localeCompare(right);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return [...createdOrUpdated.slice(0, 4), ...deleted.slice(0, 4)].slice(0, 6);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const buildPushResultFromPlan = (
|
|
119
|
-
plan: PushPlan,
|
|
120
|
-
context: SyncContext,
|
|
121
|
-
dryRun: boolean,
|
|
122
|
-
): SyncPushResult => {
|
|
123
|
-
return {
|
|
124
|
-
configPath: context.paths.configPath,
|
|
125
|
-
deletedArtifactCount: plan.deletedArtifactCount,
|
|
126
|
-
dryRun,
|
|
127
|
-
syncDirectory: context.paths.syncDirectory,
|
|
128
|
-
...plan.counts,
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export const pushSync = async (
|
|
133
|
-
request: SyncPushRequest,
|
|
134
|
-
context: SyncContext,
|
|
135
|
-
): Promise<SyncPushResult> => {
|
|
136
|
-
await ensureSyncRepository(context);
|
|
137
|
-
|
|
138
|
-
const config = await readSyncConfig(
|
|
139
|
-
context.paths.syncDirectory,
|
|
140
|
-
context.environment,
|
|
141
|
-
);
|
|
142
|
-
const plan = await buildPushPlan(config, context);
|
|
143
|
-
|
|
144
|
-
if (!request.dryRun) {
|
|
145
|
-
const stagingRoot = await mkdtemp(
|
|
146
|
-
join(context.paths.syncDirectory, ".devsync-sync-push-"),
|
|
147
|
-
);
|
|
148
|
-
const nextArtifactsDirectory = join(stagingRoot, "files");
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const artifacts = await buildRepoArtifacts(plan.snapshot, config);
|
|
152
|
-
|
|
153
|
-
await writeArtifactsToDirectory(nextArtifactsDirectory, artifacts);
|
|
154
|
-
|
|
155
|
-
await replacePathAtomically(
|
|
156
|
-
resolveSyncArtifactsDirectoryPath(context.paths.syncDirectory),
|
|
157
|
-
nextArtifactsDirectory,
|
|
158
|
-
);
|
|
159
|
-
} finally {
|
|
160
|
-
await rm(stagingRoot, {
|
|
161
|
-
force: true,
|
|
162
|
-
recursive: true,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return buildPushResultFromPlan(plan, context, request.dryRun);
|
|
168
|
-
};
|