@tinyrack/devsync 1.1.0 → 1.2.2

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.
Files changed (216) hide show
  1. package/README.md +230 -62
  2. package/dist/cli/base-command.d.ts +14 -0
  3. package/dist/cli/base-command.d.ts.map +1 -0
  4. package/dist/cli/base-command.js +22 -0
  5. package/dist/cli/base-command.js.map +1 -0
  6. package/dist/cli/commands/dir.d.ts +8 -0
  7. package/dist/cli/commands/dir.d.ts.map +1 -0
  8. package/dist/cli/commands/dir.js +16 -0
  9. package/dist/cli/commands/dir.js.map +1 -0
  10. package/dist/cli/commands/doctor.d.ts +8 -0
  11. package/dist/cli/commands/doctor.d.ts.map +1 -0
  12. package/dist/cli/commands/doctor.js +18 -0
  13. package/dist/cli/commands/doctor.js.map +1 -0
  14. package/dist/cli/commands/index.d.ts +23 -0
  15. package/dist/cli/commands/index.d.ts.map +1 -0
  16. package/dist/cli/commands/index.js +23 -0
  17. package/dist/cli/commands/index.js.map +1 -0
  18. package/dist/cli/commands/init.d.ts +15 -0
  19. package/dist/cli/commands/init.d.ts.map +1 -0
  20. package/dist/cli/commands/init.js +43 -0
  21. package/dist/cli/commands/init.js.map +1 -0
  22. package/dist/cli/commands/machine/list.d.ts +7 -0
  23. package/dist/cli/commands/machine/list.d.ts.map +1 -0
  24. package/dist/cli/commands/machine/list.js +12 -0
  25. package/dist/cli/commands/machine/list.js.map +1 -0
  26. package/dist/cli/commands/machine/use.d.ts +11 -0
  27. package/dist/cli/commands/machine/use.d.ts.map +1 -0
  28. package/dist/cli/commands/machine/use.js +28 -0
  29. package/dist/cli/commands/machine/use.js.map +1 -0
  30. package/dist/cli/commands/pull.d.ts +12 -0
  31. package/dist/cli/commands/pull.d.ts.map +1 -0
  32. package/dist/cli/commands/pull.js +34 -0
  33. package/dist/cli/commands/pull.js.map +1 -0
  34. package/dist/cli/commands/push.d.ts +12 -0
  35. package/dist/cli/commands/push.d.ts.map +1 -0
  36. package/dist/cli/commands/push.js +34 -0
  37. package/dist/cli/commands/push.js.map +1 -0
  38. package/dist/cli/commands/status.d.ts +11 -0
  39. package/dist/cli/commands/status.d.ts.map +1 -0
  40. package/dist/cli/commands/status.js +27 -0
  41. package/dist/cli/commands/status.js.map +1 -0
  42. package/dist/cli/commands/track.d.ts +16 -0
  43. package/dist/cli/commands/track.d.ts.map +1 -0
  44. package/dist/cli/commands/track.js +82 -0
  45. package/dist/cli/commands/track.js.map +1 -0
  46. package/dist/cli/commands/untrack.d.ts +11 -0
  47. package/dist/cli/commands/untrack.d.ts.map +1 -0
  48. package/dist/cli/commands/untrack.js +28 -0
  49. package/dist/cli/commands/untrack.js.map +1 -0
  50. package/dist/config/global-config.d.ts +21 -0
  51. package/dist/config/global-config.d.ts.map +1 -0
  52. package/dist/config/global-config.js +106 -0
  53. package/dist/config/global-config.js.map +1 -0
  54. package/dist/config/sync.d.ts +96 -0
  55. package/dist/config/sync.d.ts.map +1 -0
  56. package/dist/config/sync.js +412 -0
  57. package/dist/config/sync.js.map +1 -0
  58. package/dist/config/xdg.d.ts +11 -0
  59. package/dist/config/xdg.d.ts.map +1 -0
  60. package/dist/config/xdg.js +79 -0
  61. package/dist/config/xdg.js.map +1 -0
  62. package/dist/index.d.ts +3 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/{src/index.ts → dist/index.js} +1 -1
  65. package/dist/index.js.map +1 -0
  66. package/dist/lib/file-mode.d.ts +3 -0
  67. package/dist/lib/file-mode.d.ts.map +1 -0
  68. package/dist/lib/file-mode.js +7 -0
  69. package/dist/lib/file-mode.js.map +1 -0
  70. package/dist/lib/output.d.ts +31 -0
  71. package/dist/lib/output.d.ts.map +1 -0
  72. package/dist/lib/output.js +198 -0
  73. package/dist/lib/output.js.map +1 -0
  74. package/dist/lib/path.d.ts +5 -0
  75. package/dist/lib/path.d.ts.map +1 -0
  76. package/dist/lib/path.js +25 -0
  77. package/dist/lib/path.js.map +1 -0
  78. package/dist/lib/string.d.ts +2 -0
  79. package/dist/lib/string.d.ts.map +1 -0
  80. package/dist/lib/string.js +4 -0
  81. package/dist/lib/string.js.map +1 -0
  82. package/dist/lib/validation.d.ts +3 -0
  83. package/dist/lib/validation.d.ts.map +1 -0
  84. package/dist/lib/validation.js +9 -0
  85. package/dist/lib/validation.js.map +1 -0
  86. package/dist/services/add.d.ts +20 -0
  87. package/dist/services/add.d.ts.map +1 -0
  88. package/dist/services/add.js +161 -0
  89. package/dist/services/add.js.map +1 -0
  90. package/dist/services/config-file.d.ts +30 -0
  91. package/dist/services/config-file.d.ts.map +1 -0
  92. package/dist/services/config-file.js +34 -0
  93. package/dist/services/config-file.js.map +1 -0
  94. package/dist/services/crypto.d.ts +9 -0
  95. package/dist/services/crypto.d.ts.map +1 -0
  96. package/dist/services/crypto.js +75 -0
  97. package/dist/services/crypto.js.map +1 -0
  98. package/dist/services/doctor.d.ts +16 -0
  99. package/dist/services/doctor.d.ts.map +1 -0
  100. package/dist/services/doctor.js +84 -0
  101. package/dist/services/doctor.js.map +1 -0
  102. package/dist/services/error.d.ts +14 -0
  103. package/dist/services/error.d.ts.map +1 -0
  104. package/dist/services/error.js +38 -0
  105. package/dist/services/error.js.map +1 -0
  106. package/dist/services/filesystem.d.ts +15 -0
  107. package/dist/services/filesystem.d.ts.map +1 -0
  108. package/dist/services/filesystem.js +113 -0
  109. package/dist/services/filesystem.js.map +1 -0
  110. package/dist/services/forget.d.ts +14 -0
  111. package/dist/services/forget.d.ts.map +1 -0
  112. package/dist/services/forget.js +124 -0
  113. package/dist/services/forget.js.map +1 -0
  114. package/dist/services/git.d.ts +10 -0
  115. package/dist/services/git.d.ts.map +1 -0
  116. package/dist/services/git.js +57 -0
  117. package/dist/services/git.js.map +1 -0
  118. package/dist/services/init.d.ts +19 -0
  119. package/dist/services/init.d.ts.map +1 -0
  120. package/dist/services/init.js +203 -0
  121. package/dist/services/init.js.map +1 -0
  122. package/dist/services/local-materialization.d.ts +28 -0
  123. package/dist/services/local-materialization.d.ts.map +1 -0
  124. package/dist/services/local-materialization.js +262 -0
  125. package/dist/services/local-materialization.js.map +1 -0
  126. package/dist/services/local-snapshot.d.ts +25 -0
  127. package/dist/services/local-snapshot.d.ts.map +1 -0
  128. package/dist/services/local-snapshot.js +93 -0
  129. package/dist/services/local-snapshot.js.map +1 -0
  130. package/dist/services/machine.d.ts +40 -0
  131. package/dist/services/machine.d.ts.map +1 -0
  132. package/dist/services/machine.js +113 -0
  133. package/dist/services/machine.js.map +1 -0
  134. package/dist/services/paths.d.ts +12 -0
  135. package/dist/services/paths.d.ts.map +1 -0
  136. package/dist/services/paths.js +71 -0
  137. package/dist/services/paths.js.map +1 -0
  138. package/dist/services/pull.d.ts +28 -0
  139. package/dist/services/pull.d.ts.map +1 -0
  140. package/dist/services/pull.js +67 -0
  141. package/dist/services/pull.js.map +1 -0
  142. package/dist/services/push.d.ts +35 -0
  143. package/dist/services/push.d.ts.map +1 -0
  144. package/dist/services/push.js +96 -0
  145. package/dist/services/push.js.map +1 -0
  146. package/dist/services/repo-artifacts.d.ts +52 -0
  147. package/dist/services/repo-artifacts.d.ts.map +1 -0
  148. package/dist/services/repo-artifacts.js +251 -0
  149. package/dist/services/repo-artifacts.js.map +1 -0
  150. package/dist/services/repo-snapshot.d.ts +6 -0
  151. package/dist/services/repo-snapshot.d.ts.map +1 -0
  152. package/dist/services/repo-snapshot.js +163 -0
  153. package/dist/services/repo-snapshot.js.map +1 -0
  154. package/dist/services/runtime.d.ts +40 -0
  155. package/dist/services/runtime.d.ts.map +1 -0
  156. package/dist/services/runtime.js +71 -0
  157. package/dist/services/runtime.js.map +1 -0
  158. package/dist/services/set.d.ts +38 -0
  159. package/dist/services/set.d.ts.map +1 -0
  160. package/dist/services/set.js +184 -0
  161. package/dist/services/set.js.map +1 -0
  162. package/dist/services/status.d.ts +30 -0
  163. package/dist/services/status.d.ts.map +1 -0
  164. package/dist/services/status.js +35 -0
  165. package/dist/services/status.js.map +1 -0
  166. package/package.json +15 -7
  167. package/src/cli/commands/add.ts +0 -40
  168. package/src/cli/commands/cd.ts +0 -80
  169. package/src/cli/commands/doctor.ts +0 -20
  170. package/src/cli/commands/forget.ts +0 -32
  171. package/src/cli/commands/index.ts +0 -23
  172. package/src/cli/commands/init.ts +0 -43
  173. package/src/cli/commands/list.ts +0 -17
  174. package/src/cli/commands/pull.ts +0 -31
  175. package/src/cli/commands/push.ts +0 -31
  176. package/src/cli/commands/set.ts +0 -47
  177. package/src/cli/commands/status.ts +0 -18
  178. package/src/cli/sync-output.test.ts +0 -173
  179. package/src/cli/sync-output.ts +0 -200
  180. package/src/config/sync.test.ts +0 -609
  181. package/src/config/sync.ts +0 -572
  182. package/src/config/xdg.ts +0 -138
  183. package/src/lib/string.test.ts +0 -13
  184. package/src/lib/string.ts +0 -3
  185. package/src/lib/validation.test.ts +0 -32
  186. package/src/lib/validation.ts +0 -11
  187. package/src/services/add.ts +0 -178
  188. package/src/services/config-file.test.ts +0 -161
  189. package/src/services/config-file.ts +0 -101
  190. package/src/services/crypto.test.ts +0 -132
  191. package/src/services/crypto.ts +0 -83
  192. package/src/services/doctor.ts +0 -142
  193. package/src/services/error.ts +0 -6
  194. package/src/services/filesystem.test.ts +0 -171
  195. package/src/services/filesystem.ts +0 -183
  196. package/src/services/forget.ts +0 -261
  197. package/src/services/git.test.ts +0 -83
  198. package/src/services/git.ts +0 -74
  199. package/src/services/init.test.ts +0 -109
  200. package/src/services/init.ts +0 -244
  201. package/src/services/list.ts +0 -63
  202. package/src/services/local-materialization.ts +0 -421
  203. package/src/services/local-snapshot.ts +0 -173
  204. package/src/services/paths.test.ts +0 -74
  205. package/src/services/paths.ts +0 -98
  206. package/src/services/pull.ts +0 -144
  207. package/src/services/push.ts +0 -168
  208. package/src/services/repo-artifacts.ts +0 -262
  209. package/src/services/repo-snapshot.ts +0 -197
  210. package/src/services/runtime.ts +0 -57
  211. package/src/services/set.ts +0 -383
  212. package/src/services/status.ts +0 -57
  213. package/src/services/sync.dry-run.test.ts +0 -179
  214. package/src/services/sync.runtime.test.ts +0 -756
  215. package/src/services/sync.service.test.ts +0 -1169
  216. package/src/test/helpers/sync-fixture.ts +0 -47
@@ -1,261 +0,0 @@
1
- import { readdir, rm } from "node:fs/promises";
2
- import { dirname, join, posix } from "node:path";
3
-
4
- import {
5
- type ResolvedSyncConfig,
6
- type ResolvedSyncConfigEntry,
7
- readSyncConfig,
8
- resolveSyncArtifactsDirectoryPath,
9
- } from "#app/config/sync.ts";
10
-
11
- import {
12
- createSyncConfigDocument,
13
- sortSyncConfigEntries,
14
- writeValidatedSyncConfig,
15
- } from "./config-file.ts";
16
- import { DevsyncError } from "./error.ts";
17
- import {
18
- getPathStats,
19
- listDirectoryEntries,
20
- removePathAtomically,
21
- } from "./filesystem.ts";
22
- import {
23
- isExplicitLocalPath,
24
- isPathEqualOrNested,
25
- resolveCommandTargetPath,
26
- tryNormalizeRepoPathInput,
27
- } from "./paths.ts";
28
- import {
29
- isSecretArtifactPath,
30
- resolveArtifactRelativePath,
31
- } from "./repo-artifacts.ts";
32
- import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
33
-
34
- export type SyncForgetRequest = Readonly<{
35
- target: string;
36
- }>;
37
-
38
- export type SyncForgetResult = Readonly<{
39
- configPath: string;
40
- localPath: string;
41
- plainArtifactCount: number;
42
- repoPath: string;
43
- secretArtifactCount: number;
44
- syncDirectory: string;
45
- }>;
46
-
47
- const findMatchingTrackedEntry = (
48
- config: ResolvedSyncConfig,
49
- target: string,
50
- context: Pick<SyncContext, "cwd" | "environment">,
51
- ) => {
52
- const trimmedTarget = target.trim();
53
- const resolvedTargetPath = resolveCommandTargetPath(
54
- trimmedTarget,
55
- context.environment,
56
- context.cwd,
57
- );
58
- const byLocalPath = config.entries.find((entry) => {
59
- return entry.localPath === resolvedTargetPath;
60
- });
61
-
62
- if (byLocalPath !== undefined || isExplicitLocalPath(trimmedTarget)) {
63
- return byLocalPath;
64
- }
65
-
66
- const normalizedRepoPath = tryNormalizeRepoPathInput(trimmedTarget);
67
-
68
- if (normalizedRepoPath === undefined) {
69
- return undefined;
70
- }
71
-
72
- return config.entries.find((entry) => {
73
- return entry.repoPath === normalizedRepoPath;
74
- });
75
- };
76
-
77
- const collectRepoArtifactCounts = async (
78
- targetPath: string,
79
- counts: {
80
- plain: number;
81
- secret: number;
82
- },
83
- relativePath: string,
84
- ) => {
85
- const stats = await getPathStats(targetPath);
86
-
87
- if (stats === undefined) {
88
- return;
89
- }
90
-
91
- if (stats.isDirectory()) {
92
- counts.plain += 1;
93
-
94
- const entries = await listDirectoryEntries(targetPath);
95
-
96
- for (const entry of entries) {
97
- await collectRepoArtifactCounts(
98
- join(targetPath, entry.name),
99
- counts,
100
- posix.join(relativePath, entry.name),
101
- );
102
- }
103
-
104
- return;
105
- }
106
-
107
- if (isSecretArtifactPath(relativePath)) {
108
- counts.secret += 1;
109
- } else {
110
- counts.plain += 1;
111
- }
112
- };
113
-
114
- const collectEntryArtifactCounts = async (
115
- syncDirectory: string,
116
- entry: ResolvedSyncConfigEntry,
117
- ) => {
118
- const artifactsRoot = resolveSyncArtifactsDirectoryPath(syncDirectory);
119
- const counts = {
120
- plain: 0,
121
- secret: 0,
122
- };
123
-
124
- if (entry.kind === "directory") {
125
- await collectRepoArtifactCounts(
126
- join(artifactsRoot, ...entry.repoPath.split("/")),
127
- counts,
128
- entry.repoPath,
129
- );
130
- } else {
131
- await collectRepoArtifactCounts(
132
- join(artifactsRoot, ...entry.repoPath.split("/")),
133
- counts,
134
- entry.repoPath,
135
- );
136
- await collectRepoArtifactCounts(
137
- join(
138
- artifactsRoot,
139
- ...resolveArtifactRelativePath({
140
- category: "secret",
141
- repoPath: entry.repoPath,
142
- }).split("/"),
143
- ),
144
- counts,
145
- resolveArtifactRelativePath({
146
- category: "secret",
147
- repoPath: entry.repoPath,
148
- }),
149
- );
150
- }
151
-
152
- return {
153
- plainArtifactCount: counts.plain,
154
- secretArtifactCount: counts.secret,
155
- };
156
- };
157
-
158
- const pruneEmptyParentDirectories = async (
159
- startPath: string,
160
- rootPath: string,
161
- ) => {
162
- let currentPath = startPath;
163
-
164
- while (
165
- isPathEqualOrNested(currentPath, rootPath) &&
166
- currentPath !== rootPath
167
- ) {
168
- const stats = await getPathStats(currentPath);
169
-
170
- if (stats === undefined) {
171
- currentPath = dirname(currentPath);
172
- continue;
173
- }
174
-
175
- if (!stats.isDirectory()) {
176
- break;
177
- }
178
-
179
- const entries = await readdir(currentPath);
180
-
181
- if (entries.length > 0) {
182
- break;
183
- }
184
-
185
- await rm(currentPath, { force: true, recursive: true });
186
- currentPath = dirname(currentPath);
187
- }
188
- };
189
-
190
- const removeTrackedEntryArtifacts = async (
191
- syncDirectory: string,
192
- entry: ResolvedSyncConfigEntry,
193
- ) => {
194
- const artifactsRoot = resolveSyncArtifactsDirectoryPath(syncDirectory);
195
- const plainPath = join(artifactsRoot, ...entry.repoPath.split("/"));
196
-
197
- await removePathAtomically(plainPath);
198
- await pruneEmptyParentDirectories(dirname(plainPath), artifactsRoot);
199
-
200
- if (entry.kind === "directory") {
201
- return;
202
- }
203
-
204
- const secretPath = join(
205
- artifactsRoot,
206
- ...resolveArtifactRelativePath({
207
- category: "secret",
208
- repoPath: entry.repoPath,
209
- }).split("/"),
210
- );
211
-
212
- await removePathAtomically(secretPath);
213
- await pruneEmptyParentDirectories(dirname(secretPath), artifactsRoot);
214
- };
215
-
216
- export const forgetSyncTarget = async (
217
- request: SyncForgetRequest,
218
- context: SyncContext,
219
- ): Promise<SyncForgetResult> => {
220
- const target = request.target.trim();
221
-
222
- if (target.length === 0) {
223
- throw new DevsyncError("Target path is required.");
224
- }
225
-
226
- await ensureSyncRepository(context);
227
-
228
- const config = await readSyncConfig(
229
- context.paths.syncDirectory,
230
- context.environment,
231
- );
232
- const entry = findMatchingTrackedEntry(config, target, context);
233
-
234
- if (entry === undefined) {
235
- throw new DevsyncError(`No tracked sync entry matches: ${target}`);
236
- }
237
-
238
- const { plainArtifactCount, secretArtifactCount } =
239
- await collectEntryArtifactCounts(context.paths.syncDirectory, entry);
240
- const nextConfig = createSyncConfigDocument(config);
241
-
242
- nextConfig.entries = sortSyncConfigEntries(
243
- nextConfig.entries.filter((configEntry) => {
244
- return configEntry.repoPath !== entry.repoPath;
245
- }),
246
- );
247
-
248
- await writeValidatedSyncConfig(context.paths.syncDirectory, nextConfig, {
249
- environment: context.environment,
250
- });
251
- await removeTrackedEntryArtifacts(context.paths.syncDirectory, entry);
252
-
253
- return {
254
- configPath: context.paths.configPath,
255
- localPath: entry.localPath,
256
- plainArtifactCount,
257
- repoPath: entry.repoPath,
258
- secretArtifactCount,
259
- syncDirectory: context.paths.syncDirectory,
260
- };
261
- };
@@ -1,83 +0,0 @@
1
- import { rm } from "node:fs/promises";
2
- import { join } from "node:path";
3
-
4
- import { afterEach, describe, expect, it } from "vitest";
5
-
6
- import { DevsyncError } from "#app/services/error.ts";
7
- import {
8
- ensureGitRepository,
9
- ensureRepository,
10
- initializeRepository,
11
- } from "#app/services/git.ts";
12
- import {
13
- createTemporaryDirectory,
14
- runGit,
15
- } from "../test/helpers/sync-fixture.ts";
16
-
17
- const temporaryDirectories: string[] = [];
18
-
19
- const createWorkspace = async () => {
20
- const directory = await createTemporaryDirectory("devsync-git-");
21
-
22
- temporaryDirectories.push(directory);
23
-
24
- return directory;
25
- };
26
-
27
- afterEach(async () => {
28
- while (temporaryDirectories.length > 0) {
29
- const directory = temporaryDirectories.pop();
30
-
31
- if (directory !== undefined) {
32
- await rm(directory, { force: true, recursive: true });
33
- }
34
- }
35
- });
36
-
37
- describe("git helpers", () => {
38
- it("initializes a repository with a main branch", async () => {
39
- const workspace = await createWorkspace();
40
- const repositoryPath = join(workspace, "sync");
41
-
42
- await expect(initializeRepository(repositoryPath)).resolves.toEqual({
43
- action: "initialized",
44
- });
45
- await expect(ensureRepository(repositoryPath)).resolves.toBeUndefined();
46
- await expect(
47
- runGit(["-C", repositoryPath, "symbolic-ref", "--short", "HEAD"]),
48
- ).resolves.toMatchObject({
49
- stdout: "main\n",
50
- });
51
- });
52
-
53
- it("clones an existing repository and reports the source", async () => {
54
- const workspace = await createWorkspace();
55
- const sourcePath = join(workspace, "source");
56
- const targetPath = join(workspace, "clone");
57
-
58
- await runGit(["init", "-b", "main", sourcePath], workspace);
59
-
60
- await expect(initializeRepository(targetPath, sourcePath)).resolves.toEqual(
61
- {
62
- action: "cloned",
63
- source: sourcePath,
64
- },
65
- );
66
- await expect(ensureRepository(targetPath)).resolves.toBeUndefined();
67
- });
68
-
69
- it("wraps missing git repositories in a DevsyncError", async () => {
70
- const workspace = await createWorkspace();
71
- const missingRepositoryPath = join(workspace, "not-a-repo");
72
-
73
- await expect(
74
- ensureRepository(missingRepositoryPath),
75
- ).rejects.toThrowError();
76
- await expect(
77
- ensureGitRepository(missingRepositoryPath),
78
- ).rejects.toThrowError(DevsyncError);
79
- await expect(
80
- ensureGitRepository(missingRepositoryPath),
81
- ).rejects.toThrowError(/Sync directory is not a git repository/u);
82
- });
83
- });
@@ -1,74 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
-
4
- import { DevsyncError } from "./error.ts";
5
-
6
- const execFileAsync = promisify(execFile);
7
-
8
- const runGitCommand = async (
9
- args: readonly string[],
10
- options?: Readonly<{ cwd?: string }>,
11
- ) => {
12
- try {
13
- const result = await execFileAsync("git", [...args], {
14
- cwd: options?.cwd,
15
- encoding: "utf8",
16
- maxBuffer: 10_000_000,
17
- });
18
-
19
- return {
20
- stderr: result.stderr,
21
- stdout: result.stdout,
22
- };
23
- } catch (error: unknown) {
24
- if (error instanceof Error && "stderr" in error) {
25
- const stderr =
26
- typeof error.stderr === "string" ? error.stderr.trim() : undefined;
27
- const stdout =
28
- "stdout" in error && typeof error.stdout === "string"
29
- ? error.stdout.trim()
30
- : undefined;
31
- const message = stderr || stdout || error.message;
32
-
33
- throw new Error(message);
34
- }
35
-
36
- throw new Error(error instanceof Error ? error.message : "git failed.");
37
- }
38
- };
39
-
40
- export const ensureRepository = async (directory: string) => {
41
- await runGitCommand(["-C", directory, "rev-parse", "--is-inside-work-tree"]);
42
- };
43
-
44
- export const initializeRepository = async (
45
- directory: string,
46
- source?: string,
47
- ) => {
48
- if (source === undefined) {
49
- await runGitCommand(["init", "-b", "main", directory]);
50
-
51
- return {
52
- action: "initialized" as const,
53
- };
54
- }
55
-
56
- await runGitCommand(["clone", source, directory]);
57
-
58
- return {
59
- action: "cloned" as const,
60
- source,
61
- };
62
- };
63
-
64
- export const ensureGitRepository = async (syncDirectory: string) => {
65
- try {
66
- await ensureRepository(syncDirectory);
67
- } catch (error: unknown) {
68
- throw new DevsyncError(
69
- error instanceof Error
70
- ? `Sync directory is not a git repository: ${error.message}`
71
- : "Sync directory is not a git repository.",
72
- );
73
- }
74
- };
@@ -1,109 +0,0 @@
1
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
-
4
- import { afterEach, describe, expect, it } from "vitest";
5
-
6
- import { DevsyncError } from "#app/services/error.ts";
7
- import { initializeSync } from "#app/services/init.ts";
8
- import { createSyncContext } from "#app/services/runtime.ts";
9
- import {
10
- createAgeKeyPair,
11
- createTemporaryDirectory,
12
- runGit,
13
- writeIdentityFile,
14
- } from "../test/helpers/sync-fixture.ts";
15
-
16
- const temporaryDirectories: string[] = [];
17
-
18
- const createWorkspace = async () => {
19
- const directory = await createTemporaryDirectory("devsync-init-");
20
-
21
- temporaryDirectories.push(directory);
22
-
23
- return directory;
24
- };
25
-
26
- const createEnvironment = (
27
- homeDirectory: string,
28
- xdgConfigHome: string,
29
- ): NodeJS.ProcessEnv => {
30
- return {
31
- HOME: homeDirectory,
32
- XDG_CONFIG_HOME: xdgConfigHome,
33
- };
34
- };
35
-
36
- afterEach(async () => {
37
- while (temporaryDirectories.length > 0) {
38
- const directory = temporaryDirectories.pop();
39
-
40
- if (directory !== undefined) {
41
- await rm(directory, { force: true, recursive: true });
42
- }
43
- }
44
- });
45
-
46
- describe("init service", () => {
47
- it("clones a configured repository source during initialization", async () => {
48
- const workspace = await createWorkspace();
49
- const homeDirectory = join(workspace, "home");
50
- const xdgConfigHome = join(workspace, "xdg");
51
- const sourceRepository = join(workspace, "remote-sync");
52
- const ageKeys = await createAgeKeyPair();
53
-
54
- await writeIdentityFile(xdgConfigHome, ageKeys.identity);
55
- await runGit(["init", "-b", "main", sourceRepository], workspace);
56
-
57
- const result = await initializeSync(
58
- {
59
- identityFile: "$XDG_CONFIG_HOME/devsync/age/keys.txt",
60
- recipients: [ageKeys.recipient],
61
- repository: sourceRepository,
62
- },
63
- createSyncContext({
64
- environment: createEnvironment(homeDirectory, xdgConfigHome),
65
- }),
66
- );
67
-
68
- expect(result.gitAction).toBe("cloned");
69
- expect(result.gitSource).toBe(sourceRepository);
70
- expect(
71
- await readFile(join(result.syncDirectory, "config.json"), "utf8"),
72
- ).toContain("$XDG_CONFIG_HOME/devsync/age/keys.txt");
73
- });
74
-
75
- it("rejects non-empty sync directories that are not git repositories", async () => {
76
- const workspace = await createWorkspace();
77
- const homeDirectory = join(workspace, "home");
78
- const xdgConfigHome = join(workspace, "xdg");
79
- const syncDirectory = join(xdgConfigHome, "devsync", "sync");
80
- const ageKeys = await createAgeKeyPair();
81
-
82
- await writeIdentityFile(xdgConfigHome, ageKeys.identity);
83
- await mkdir(syncDirectory, { recursive: true });
84
- await writeFile(join(syncDirectory, "placeholder.txt"), "keep\n", "utf8");
85
-
86
- await expect(
87
- initializeSync(
88
- {
89
- identityFile: "$XDG_CONFIG_HOME/devsync/age/keys.txt",
90
- recipients: [ageKeys.recipient],
91
- },
92
- createSyncContext({
93
- environment: createEnvironment(homeDirectory, xdgConfigHome),
94
- }),
95
- ),
96
- ).rejects.toThrowError(DevsyncError);
97
- await expect(
98
- initializeSync(
99
- {
100
- identityFile: "$XDG_CONFIG_HOME/devsync/age/keys.txt",
101
- recipients: [ageKeys.recipient],
102
- },
103
- createSyncContext({
104
- environment: createEnvironment(homeDirectory, xdgConfigHome),
105
- }),
106
- ),
107
- ).rejects.toThrowError(/already exists and is not empty/u);
108
- });
109
- });