@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.
Files changed (220) 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/platform.d.ts +11 -0
  55. package/dist/config/platform.d.ts.map +1 -0
  56. package/dist/config/platform.js +19 -0
  57. package/dist/config/platform.js.map +1 -0
  58. package/dist/config/sync.d.ts +107 -0
  59. package/dist/config/sync.d.ts.map +1 -0
  60. package/dist/config/sync.js +424 -0
  61. package/dist/config/sync.js.map +1 -0
  62. package/dist/config/xdg.d.ts +14 -0
  63. package/dist/config/xdg.d.ts.map +1 -0
  64. package/dist/config/xdg.js +102 -0
  65. package/dist/config/xdg.js.map +1 -0
  66. package/dist/index.d.ts +3 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/{src/index.ts → dist/index.js} +1 -1
  69. package/dist/index.js.map +1 -0
  70. package/dist/lib/file-mode.d.ts +3 -0
  71. package/dist/lib/file-mode.d.ts.map +1 -0
  72. package/dist/lib/file-mode.js +7 -0
  73. package/dist/lib/file-mode.js.map +1 -0
  74. package/dist/lib/output.d.ts +31 -0
  75. package/dist/lib/output.d.ts.map +1 -0
  76. package/dist/lib/output.js +198 -0
  77. package/dist/lib/output.js.map +1 -0
  78. package/dist/lib/path.d.ts +5 -0
  79. package/dist/lib/path.d.ts.map +1 -0
  80. package/dist/lib/path.js +25 -0
  81. package/dist/lib/path.js.map +1 -0
  82. package/dist/lib/string.d.ts +2 -0
  83. package/dist/lib/string.d.ts.map +1 -0
  84. package/dist/lib/string.js +4 -0
  85. package/dist/lib/string.js.map +1 -0
  86. package/dist/lib/validation.d.ts +3 -0
  87. package/dist/lib/validation.d.ts.map +1 -0
  88. package/dist/lib/validation.js +9 -0
  89. package/dist/lib/validation.js.map +1 -0
  90. package/dist/services/add.d.ts +20 -0
  91. package/dist/services/add.d.ts.map +1 -0
  92. package/dist/services/add.js +161 -0
  93. package/dist/services/add.js.map +1 -0
  94. package/dist/services/config-file.d.ts +45 -0
  95. package/dist/services/config-file.d.ts.map +1 -0
  96. package/dist/services/config-file.js +35 -0
  97. package/dist/services/config-file.js.map +1 -0
  98. package/dist/services/crypto.d.ts +9 -0
  99. package/dist/services/crypto.d.ts.map +1 -0
  100. package/dist/services/crypto.js +75 -0
  101. package/dist/services/crypto.js.map +1 -0
  102. package/dist/services/doctor.d.ts +16 -0
  103. package/dist/services/doctor.d.ts.map +1 -0
  104. package/dist/services/doctor.js +84 -0
  105. package/dist/services/doctor.js.map +1 -0
  106. package/dist/services/error.d.ts +14 -0
  107. package/dist/services/error.d.ts.map +1 -0
  108. package/dist/services/error.js +38 -0
  109. package/dist/services/error.js.map +1 -0
  110. package/dist/services/filesystem.d.ts +15 -0
  111. package/dist/services/filesystem.d.ts.map +1 -0
  112. package/dist/services/filesystem.js +113 -0
  113. package/dist/services/filesystem.js.map +1 -0
  114. package/dist/services/forget.d.ts +14 -0
  115. package/dist/services/forget.d.ts.map +1 -0
  116. package/dist/services/forget.js +124 -0
  117. package/dist/services/forget.js.map +1 -0
  118. package/dist/services/git.d.ts +10 -0
  119. package/dist/services/git.d.ts.map +1 -0
  120. package/dist/services/git.js +57 -0
  121. package/dist/services/git.js.map +1 -0
  122. package/dist/services/init.d.ts +19 -0
  123. package/dist/services/init.d.ts.map +1 -0
  124. package/dist/services/init.js +203 -0
  125. package/dist/services/init.js.map +1 -0
  126. package/dist/services/local-materialization.d.ts +28 -0
  127. package/dist/services/local-materialization.d.ts.map +1 -0
  128. package/dist/services/local-materialization.js +262 -0
  129. package/dist/services/local-materialization.js.map +1 -0
  130. package/dist/services/local-snapshot.d.ts +25 -0
  131. package/dist/services/local-snapshot.d.ts.map +1 -0
  132. package/dist/services/local-snapshot.js +93 -0
  133. package/dist/services/local-snapshot.js.map +1 -0
  134. package/dist/services/machine.d.ts +40 -0
  135. package/dist/services/machine.d.ts.map +1 -0
  136. package/dist/services/machine.js +113 -0
  137. package/dist/services/machine.js.map +1 -0
  138. package/dist/services/paths.d.ts +13 -0
  139. package/dist/services/paths.d.ts.map +1 -0
  140. package/dist/services/paths.js +71 -0
  141. package/dist/services/paths.js.map +1 -0
  142. package/dist/services/pull.d.ts +28 -0
  143. package/dist/services/pull.d.ts.map +1 -0
  144. package/dist/services/pull.js +67 -0
  145. package/dist/services/pull.js.map +1 -0
  146. package/dist/services/push.d.ts +35 -0
  147. package/dist/services/push.d.ts.map +1 -0
  148. package/dist/services/push.js +96 -0
  149. package/dist/services/push.js.map +1 -0
  150. package/dist/services/repo-artifacts.d.ts +52 -0
  151. package/dist/services/repo-artifacts.d.ts.map +1 -0
  152. package/dist/services/repo-artifacts.js +251 -0
  153. package/dist/services/repo-artifacts.js.map +1 -0
  154. package/dist/services/repo-snapshot.d.ts +6 -0
  155. package/dist/services/repo-snapshot.d.ts.map +1 -0
  156. package/dist/services/repo-snapshot.js +163 -0
  157. package/dist/services/repo-snapshot.js.map +1 -0
  158. package/dist/services/runtime.d.ts +40 -0
  159. package/dist/services/runtime.d.ts.map +1 -0
  160. package/dist/services/runtime.js +71 -0
  161. package/dist/services/runtime.js.map +1 -0
  162. package/dist/services/set.d.ts +38 -0
  163. package/dist/services/set.d.ts.map +1 -0
  164. package/dist/services/set.js +184 -0
  165. package/dist/services/set.js.map +1 -0
  166. package/dist/services/status.d.ts +30 -0
  167. package/dist/services/status.d.ts.map +1 -0
  168. package/dist/services/status.js +35 -0
  169. package/dist/services/status.js.map +1 -0
  170. package/package.json +15 -7
  171. package/src/cli/commands/add.ts +0 -40
  172. package/src/cli/commands/cd.ts +0 -80
  173. package/src/cli/commands/doctor.ts +0 -20
  174. package/src/cli/commands/forget.ts +0 -32
  175. package/src/cli/commands/index.ts +0 -23
  176. package/src/cli/commands/init.ts +0 -43
  177. package/src/cli/commands/list.ts +0 -17
  178. package/src/cli/commands/pull.ts +0 -31
  179. package/src/cli/commands/push.ts +0 -31
  180. package/src/cli/commands/set.ts +0 -47
  181. package/src/cli/commands/status.ts +0 -18
  182. package/src/cli/sync-output.test.ts +0 -173
  183. package/src/cli/sync-output.ts +0 -200
  184. package/src/config/sync.test.ts +0 -609
  185. package/src/config/sync.ts +0 -572
  186. package/src/config/xdg.ts +0 -138
  187. package/src/lib/string.test.ts +0 -13
  188. package/src/lib/string.ts +0 -3
  189. package/src/lib/validation.test.ts +0 -32
  190. package/src/lib/validation.ts +0 -11
  191. package/src/services/add.ts +0 -178
  192. package/src/services/config-file.test.ts +0 -161
  193. package/src/services/config-file.ts +0 -101
  194. package/src/services/crypto.test.ts +0 -132
  195. package/src/services/crypto.ts +0 -83
  196. package/src/services/doctor.ts +0 -142
  197. package/src/services/error.ts +0 -6
  198. package/src/services/filesystem.test.ts +0 -171
  199. package/src/services/filesystem.ts +0 -183
  200. package/src/services/forget.ts +0 -261
  201. package/src/services/git.test.ts +0 -83
  202. package/src/services/git.ts +0 -74
  203. package/src/services/init.test.ts +0 -109
  204. package/src/services/init.ts +0 -244
  205. package/src/services/list.ts +0 -63
  206. package/src/services/local-materialization.ts +0 -421
  207. package/src/services/local-snapshot.ts +0 -173
  208. package/src/services/paths.test.ts +0 -74
  209. package/src/services/paths.ts +0 -98
  210. package/src/services/pull.ts +0 -144
  211. package/src/services/push.ts +0 -168
  212. package/src/services/repo-artifacts.ts +0 -262
  213. package/src/services/repo-snapshot.ts +0 -197
  214. package/src/services/runtime.ts +0 -57
  215. package/src/services/set.ts +0 -383
  216. package/src/services/status.ts +0 -57
  217. package/src/services/sync.dry-run.test.ts +0 -179
  218. package/src/services/sync.runtime.test.ts +0 -756
  219. package/src/services/sync.service.test.ts +0 -1169
  220. 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
- });