@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,383 +0,0 @@
1
- import { join } from "node:path";
2
-
3
- import {
4
- findOwningSyncEntry,
5
- type ResolvedSyncConfigEntry,
6
- type ResolvedSyncOverride,
7
- readSyncConfig,
8
- resolveEntryRelativeRepoPath,
9
- resolveRelativeSyncMode,
10
- type SyncMode,
11
- } from "#app/config/sync.ts";
12
-
13
- import {
14
- createSyncConfigDocument,
15
- createSyncConfigDocumentEntry,
16
- sortSyncConfigEntries,
17
- writeValidatedSyncConfig,
18
- } from "./config-file.ts";
19
- import { DevsyncError } from "./error.ts";
20
- import { getPathStats } from "./filesystem.ts";
21
- import {
22
- buildRepoPathWithinRoot,
23
- isExplicitLocalPath,
24
- resolveCommandTargetPath,
25
- tryBuildRepoPathWithinRoot,
26
- tryNormalizeRepoPathInput,
27
- } from "./paths.ts";
28
- import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
29
-
30
- export type SyncSetRequest = Readonly<{
31
- recursive: boolean;
32
- state: SyncMode;
33
- target: string;
34
- }>;
35
-
36
- type SyncSetScope = "default" | "exact" | "subtree";
37
- type SyncSetAction = "added" | "removed" | "unchanged" | "updated";
38
-
39
- export type SyncSetResult = Readonly<{
40
- action: SyncSetAction;
41
- configPath: string;
42
- entryRepoPath: string;
43
- localPath: string;
44
- mode: SyncMode;
45
- repoPath: string;
46
- scope: SyncSetScope;
47
- syncDirectory: string;
48
- }>;
49
-
50
- const resolveTargetPath = async (
51
- target: string,
52
- entry: ResolvedSyncConfigEntry,
53
- context: Pick<SyncContext, "cwd" | "environment" | "paths">,
54
- ) => {
55
- if (isExplicitLocalPath(target)) {
56
- const localPath = resolveCommandTargetPath(
57
- target,
58
- context.environment,
59
- context.cwd,
60
- );
61
- const stats = await getPathStats(localPath);
62
-
63
- if (stats === undefined) {
64
- throw new DevsyncError(`Sync set target does not exist: ${localPath}`);
65
- }
66
-
67
- return {
68
- localPath,
69
- repoPath: buildRepoPathWithinRoot(
70
- localPath,
71
- context.paths.homeDirectory,
72
- "Sync set target",
73
- ),
74
- stats,
75
- };
76
- }
77
-
78
- const repoPath = tryNormalizeRepoPathInput(target);
79
-
80
- if (repoPath === undefined) {
81
- throw new DevsyncError(
82
- `Sync set target must be a local path or repository path: ${target}`,
83
- );
84
- }
85
-
86
- const relativePath = resolveEntryRelativeRepoPath(entry, repoPath);
87
- const localPath =
88
- relativePath === undefined || relativePath === ""
89
- ? entry.localPath
90
- : join(entry.localPath, ...relativePath.split("/"));
91
-
92
- return {
93
- localPath,
94
- repoPath,
95
- stats: await getPathStats(localPath),
96
- };
97
- };
98
-
99
- const resolveSetTarget = async (
100
- target: string,
101
- config: Awaited<ReturnType<typeof readSyncConfig>>,
102
- context: Pick<SyncContext, "cwd" | "environment" | "paths">,
103
- ) => {
104
- const trimmedTarget = target.trim();
105
-
106
- if (trimmedTarget.length === 0) {
107
- throw new DevsyncError("Target path is required.");
108
- }
109
-
110
- const homeDirectory = context.paths.homeDirectory;
111
- const explicitLocalPath = isExplicitLocalPath(trimmedTarget);
112
- const localTargetPath = resolveCommandTargetPath(
113
- trimmedTarget,
114
- context.environment,
115
- context.cwd,
116
- );
117
- const localRepoPath = explicitLocalPath
118
- ? buildRepoPathWithinRoot(localTargetPath, homeDirectory, "Sync set target")
119
- : tryBuildRepoPathWithinRoot(
120
- localTargetPath,
121
- homeDirectory,
122
- "Sync set target",
123
- );
124
-
125
- if (localRepoPath !== undefined) {
126
- const localStats = await getPathStats(localTargetPath);
127
-
128
- if (explicitLocalPath && localStats === undefined) {
129
- throw new DevsyncError(
130
- `Sync set target does not exist: ${localTargetPath}`,
131
- );
132
- }
133
-
134
- const entry = findOwningSyncEntry(config, localRepoPath);
135
-
136
- if (entry?.kind === "directory") {
137
- const relativePath = resolveEntryRelativeRepoPath(entry, localRepoPath);
138
-
139
- if (relativePath !== undefined) {
140
- return {
141
- entry,
142
- localPath: localTargetPath,
143
- relativePath,
144
- repoPath: localRepoPath,
145
- stats: localStats,
146
- };
147
- }
148
- }
149
-
150
- if (explicitLocalPath) {
151
- throw new DevsyncError(
152
- `Sync set target must be inside a tracked directory entry: ${trimmedTarget}`,
153
- );
154
- }
155
- }
156
-
157
- const repoPath = tryNormalizeRepoPathInput(trimmedTarget);
158
-
159
- if (repoPath === undefined) {
160
- throw new DevsyncError(
161
- `Sync set target must be a local path or repository path: ${trimmedTarget}`,
162
- );
163
- }
164
-
165
- const entry = findOwningSyncEntry(config, repoPath);
166
-
167
- if (entry === undefined || entry.kind !== "directory") {
168
- throw new DevsyncError(
169
- `Sync set target must be inside a tracked directory entry: ${trimmedTarget}`,
170
- );
171
- }
172
-
173
- const resolvedTarget = await resolveTargetPath(trimmedTarget, entry, context);
174
- const relativePath = resolveEntryRelativeRepoPath(
175
- entry,
176
- resolvedTarget.repoPath,
177
- );
178
-
179
- if (relativePath === undefined) {
180
- throw new DevsyncError(
181
- `Sync set target must be inside a tracked directory entry: ${trimmedTarget}`,
182
- );
183
- }
184
-
185
- return {
186
- entry,
187
- localPath: resolvedTarget.localPath,
188
- relativePath,
189
- repoPath: resolvedTarget.repoPath,
190
- stats: resolvedTarget.stats,
191
- };
192
- };
193
-
194
- const updateEntryMode = (
195
- entry: ResolvedSyncConfigEntry,
196
- mode: SyncMode,
197
- ): {
198
- action: SyncSetAction;
199
- entry: ResolvedSyncConfigEntry;
200
- } => {
201
- if (entry.mode === mode) {
202
- return {
203
- action: "unchanged",
204
- entry,
205
- };
206
- }
207
-
208
- return {
209
- action: "updated",
210
- entry: {
211
- ...entry,
212
- mode,
213
- },
214
- };
215
- };
216
-
217
- const updateChildOverride = (
218
- entry: ResolvedSyncConfigEntry,
219
- input: Readonly<{
220
- match: Extract<SyncSetScope, "exact" | "subtree">;
221
- mode: SyncMode;
222
- relativePath: string;
223
- }>,
224
- ): {
225
- action: SyncSetAction;
226
- entry: ResolvedSyncConfigEntry;
227
- } => {
228
- const existingOverride = entry.overrides.find((override) => {
229
- return (
230
- override.match === input.match && override.path === input.relativePath
231
- );
232
- });
233
- const remainingOverrides = entry.overrides.filter((override) => {
234
- return !(
235
- override.match === input.match && override.path === input.relativePath
236
- );
237
- });
238
- const inheritedMode = resolveRelativeSyncMode(
239
- entry.mode,
240
- remainingOverrides,
241
- input.relativePath,
242
- );
243
-
244
- if (input.mode === inheritedMode) {
245
- if (existingOverride === undefined) {
246
- return {
247
- action: "unchanged",
248
- entry,
249
- };
250
- }
251
-
252
- return {
253
- action: "removed",
254
- entry: {
255
- ...entry,
256
- overrides: remainingOverrides,
257
- },
258
- };
259
- }
260
-
261
- if (existingOverride?.mode === input.mode) {
262
- return {
263
- action: "unchanged",
264
- entry,
265
- };
266
- }
267
-
268
- const nextOverride = {
269
- match: input.match,
270
- mode: input.mode,
271
- path: input.relativePath,
272
- } satisfies ResolvedSyncOverride;
273
-
274
- return {
275
- action: existingOverride === undefined ? "added" : "updated",
276
- entry: {
277
- ...entry,
278
- overrides: [...remainingOverrides, nextOverride],
279
- },
280
- };
281
- };
282
-
283
- export const setSyncTargetMode = async (
284
- request: SyncSetRequest,
285
- context: SyncContext,
286
- ): Promise<SyncSetResult> => {
287
- await ensureSyncRepository(context);
288
-
289
- const config = await readSyncConfig(
290
- context.paths.syncDirectory,
291
- context.environment,
292
- );
293
- const target = await resolveSetTarget(request.target, config, context);
294
-
295
- if (target.relativePath === "") {
296
- if (!request.recursive) {
297
- throw new DevsyncError(
298
- "Tracked directory roots require --recursive to update the entry mode.",
299
- );
300
- }
301
-
302
- const update = updateEntryMode(target.entry, request.state);
303
- const nextConfig = createSyncConfigDocument(config);
304
-
305
- nextConfig.entries = sortSyncConfigEntries(
306
- nextConfig.entries.map((entry) => {
307
- if (entry.repoPath !== target.entry.repoPath) {
308
- return entry;
309
- }
310
-
311
- return createSyncConfigDocumentEntry(update.entry);
312
- }),
313
- );
314
-
315
- if (update.action !== "unchanged") {
316
- await writeValidatedSyncConfig(context.paths.syncDirectory, nextConfig, {
317
- environment: context.environment,
318
- });
319
- }
320
-
321
- return {
322
- action: update.action,
323
- configPath: context.paths.configPath,
324
- entryRepoPath: target.entry.repoPath,
325
- localPath: target.localPath,
326
- mode: request.state,
327
- repoPath: target.repoPath,
328
- scope: "default",
329
- syncDirectory: context.paths.syncDirectory,
330
- };
331
- }
332
-
333
- if (target.stats?.isDirectory() && !request.recursive) {
334
- throw new DevsyncError(
335
- "Directory targets require --recursive. Use a file path for exact overrides.",
336
- );
337
- }
338
-
339
- if (
340
- request.recursive &&
341
- target.stats !== undefined &&
342
- !target.stats.isDirectory()
343
- ) {
344
- throw new DevsyncError(
345
- "--recursive can only be used with directories or tracked directory roots.",
346
- );
347
- }
348
-
349
- const scope = request.recursive ? "subtree" : "exact";
350
- const update = updateChildOverride(target.entry, {
351
- match: scope,
352
- mode: request.state,
353
- relativePath: target.relativePath,
354
- });
355
- const nextConfig = createSyncConfigDocument(config);
356
-
357
- nextConfig.entries = sortSyncConfigEntries(
358
- nextConfig.entries.map((entry) => {
359
- if (entry.repoPath !== target.entry.repoPath) {
360
- return entry;
361
- }
362
-
363
- return createSyncConfigDocumentEntry(update.entry);
364
- }),
365
- );
366
-
367
- if (update.action !== "unchanged") {
368
- await writeValidatedSyncConfig(context.paths.syncDirectory, nextConfig, {
369
- environment: context.environment,
370
- });
371
- }
372
-
373
- return {
374
- action: update.action,
375
- configPath: context.paths.configPath,
376
- entryRepoPath: target.entry.repoPath,
377
- localPath: target.localPath,
378
- mode: request.state,
379
- repoPath: target.repoPath,
380
- scope,
381
- syncDirectory: context.paths.syncDirectory,
382
- };
383
- };
@@ -1,57 +0,0 @@
1
- import { readSyncConfig } from "#app/config/sync.ts";
2
-
3
- import { countConfiguredRules } from "./config-file.ts";
4
- import {
5
- buildPullPlan,
6
- buildPullPlanPreview,
7
- buildPullResultFromPlan,
8
- } from "./pull.ts";
9
- import {
10
- buildPushPlan,
11
- buildPushPlanPreview,
12
- buildPushResultFromPlan,
13
- } from "./push.ts";
14
- import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
15
-
16
- export type SyncStatusResult = Readonly<{
17
- configPath: string;
18
- entryCount: number;
19
- pull: ReturnType<typeof buildPullResultFromPlan> & {
20
- preview: readonly string[];
21
- };
22
- push: ReturnType<typeof buildPushResultFromPlan> & {
23
- preview: readonly string[];
24
- };
25
- recipientCount: number;
26
- ruleCount: number;
27
- syncDirectory: string;
28
- }>;
29
-
30
- export const getSyncStatus = async (
31
- context: SyncContext,
32
- ): Promise<SyncStatusResult> => {
33
- await ensureSyncRepository(context);
34
-
35
- const config = await readSyncConfig(
36
- context.paths.syncDirectory,
37
- context.environment,
38
- );
39
- const pushPlan = await buildPushPlan(config, context);
40
- const pullPlan = await buildPullPlan(config, context);
41
-
42
- return {
43
- configPath: context.paths.configPath,
44
- entryCount: config.entries.length,
45
- pull: {
46
- ...buildPullResultFromPlan(pullPlan, context, true),
47
- preview: buildPullPlanPreview(pullPlan),
48
- },
49
- push: {
50
- ...buildPushResultFromPlan(pushPlan, context, true),
51
- preview: buildPushPlanPreview(pushPlan),
52
- },
53
- recipientCount: config.age.recipients.length,
54
- ruleCount: countConfiguredRules(config),
55
- syncDirectory: context.paths.syncDirectory,
56
- };
57
- };
@@ -1,179 +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 { syncSecretArtifactSuffix } from "#app/config/sync.ts";
7
- import { addSyncTarget } from "#app/services/add.ts";
8
- import { initializeSync } from "#app/services/init.ts";
9
- import { pullSync } from "#app/services/pull.ts";
10
- import { pushSync } from "#app/services/push.ts";
11
- import { createSyncContext } from "#app/services/runtime.ts";
12
- import { setSyncTargetMode } from "#app/services/set.ts";
13
- import {
14
- createAgeKeyPair,
15
- createTemporaryDirectory,
16
- writeIdentityFile,
17
- } from "../test/helpers/sync-fixture.ts";
18
-
19
- const temporaryDirectories: string[] = [];
20
-
21
- const createWorkspace = async () => {
22
- const directory = await createTemporaryDirectory("devsync-dry-run-");
23
-
24
- temporaryDirectories.push(directory);
25
-
26
- return directory;
27
- };
28
-
29
- const createEnvironment = (
30
- homeDirectory: string,
31
- xdgConfigHome: string,
32
- ): NodeJS.ProcessEnv => {
33
- return {
34
- HOME: homeDirectory,
35
- XDG_CONFIG_HOME: xdgConfigHome,
36
- };
37
- };
38
-
39
- afterEach(async () => {
40
- while (temporaryDirectories.length > 0) {
41
- const directory = temporaryDirectories.pop();
42
-
43
- if (directory !== undefined) {
44
- await rm(directory, { force: true, recursive: true });
45
- }
46
- }
47
- });
48
-
49
- describe("sync dry runs", () => {
50
- it("reports push changes without mutating repository artifacts", async () => {
51
- const workspace = await createWorkspace();
52
- const homeDirectory = join(workspace, "home");
53
- const xdgConfigHome = join(workspace, "xdg");
54
- const bundleDirectory = join(homeDirectory, "bundle");
55
- const plainFile = join(bundleDirectory, "plain.txt");
56
- const secretFile = join(bundleDirectory, "token.txt");
57
- const ageKeys = await createAgeKeyPair();
58
- const context = createSyncContext({
59
- environment: createEnvironment(homeDirectory, xdgConfigHome),
60
- });
61
-
62
- await writeIdentityFile(xdgConfigHome, ageKeys.identity);
63
- await mkdir(bundleDirectory, { recursive: true });
64
- await writeFile(plainFile, "plain\n", "utf8");
65
- await writeFile(secretFile, "secret\n", "utf8");
66
-
67
- await initializeSync(
68
- {
69
- identityFile: "$XDG_CONFIG_HOME/devsync/age/keys.txt",
70
- recipients: [ageKeys.recipient],
71
- },
72
- context,
73
- );
74
- await addSyncTarget(
75
- {
76
- secret: false,
77
- target: bundleDirectory,
78
- },
79
- context,
80
- );
81
- await setSyncTargetMode(
82
- {
83
- recursive: false,
84
- state: "secret",
85
- target: secretFile,
86
- },
87
- context,
88
- );
89
-
90
- const result = await pushSync(
91
- {
92
- dryRun: true,
93
- },
94
- context,
95
- );
96
-
97
- expect(result.dryRun).toBe(true);
98
- expect(result.directoryCount).toBe(1);
99
- expect(result.plainFileCount).toBe(1);
100
- expect(result.encryptedFileCount).toBe(1);
101
- await expect(
102
- readFile(
103
- join(xdgConfigHome, "devsync", "sync", "files", "bundle", "plain.txt"),
104
- "utf8",
105
- ),
106
- ).rejects.toMatchObject({
107
- code: "ENOENT",
108
- });
109
- await expect(
110
- readFile(
111
- join(
112
- xdgConfigHome,
113
- "devsync",
114
- "sync",
115
- "files",
116
- "bundle",
117
- `token.txt${syncSecretArtifactSuffix}`,
118
- ),
119
- "utf8",
120
- ),
121
- ).rejects.toMatchObject({
122
- code: "ENOENT",
123
- });
124
- });
125
-
126
- it("reports pull changes without mutating local files", async () => {
127
- const workspace = await createWorkspace();
128
- const homeDirectory = join(workspace, "home");
129
- const xdgConfigHome = join(workspace, "xdg");
130
- const bundleDirectory = join(homeDirectory, "bundle");
131
- const plainFile = join(bundleDirectory, "plain.txt");
132
- const extraFile = join(bundleDirectory, "extra.txt");
133
- const ageKeys = await createAgeKeyPair();
134
- const context = createSyncContext({
135
- environment: createEnvironment(homeDirectory, xdgConfigHome),
136
- });
137
-
138
- await writeIdentityFile(xdgConfigHome, ageKeys.identity);
139
- await mkdir(bundleDirectory, { recursive: true });
140
- await writeFile(plainFile, "plain\n", "utf8");
141
-
142
- await initializeSync(
143
- {
144
- identityFile: "$XDG_CONFIG_HOME/devsync/age/keys.txt",
145
- recipients: [ageKeys.recipient],
146
- },
147
- context,
148
- );
149
- await addSyncTarget(
150
- {
151
- secret: false,
152
- target: bundleDirectory,
153
- },
154
- context,
155
- );
156
- await pushSync(
157
- {
158
- dryRun: false,
159
- },
160
- context,
161
- );
162
-
163
- await writeFile(plainFile, "changed locally\n", "utf8");
164
- await writeFile(extraFile, "leave me\n", "utf8");
165
-
166
- const result = await pullSync(
167
- {
168
- dryRun: true,
169
- },
170
- context,
171
- );
172
-
173
- expect(result.dryRun).toBe(true);
174
- expect(result.plainFileCount).toBe(1);
175
- expect(result.deletedLocalCount).toBeGreaterThanOrEqual(1);
176
- expect(await readFile(plainFile, "utf8")).toBe("changed locally\n");
177
- expect(await readFile(extraFile, "utf8")).toBe("leave me\n");
178
- });
179
- });