@tinyrack/devsync 1.0.3 → 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 (210) hide show
  1. package/README.md +248 -56
  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/forget.ts +0 -32
  170. package/src/cli/commands/index.ts +0 -17
  171. package/src/cli/commands/init.ts +0 -43
  172. package/src/cli/commands/pull.ts +0 -31
  173. package/src/cli/commands/push.ts +0 -31
  174. package/src/cli/commands/set.ts +0 -47
  175. package/src/cli/sync-output.test.ts +0 -105
  176. package/src/cli/sync-output.ts +0 -129
  177. package/src/config/sync.test.ts +0 -609
  178. package/src/config/sync.ts +0 -572
  179. package/src/config/xdg.ts +0 -138
  180. package/src/lib/string.test.ts +0 -13
  181. package/src/lib/string.ts +0 -3
  182. package/src/lib/validation.test.ts +0 -32
  183. package/src/lib/validation.ts +0 -11
  184. package/src/services/add.ts +0 -178
  185. package/src/services/config-file.test.ts +0 -161
  186. package/src/services/config-file.ts +0 -101
  187. package/src/services/crypto.test.ts +0 -132
  188. package/src/services/crypto.ts +0 -83
  189. package/src/services/error.ts +0 -6
  190. package/src/services/filesystem.test.ts +0 -171
  191. package/src/services/filesystem.ts +0 -183
  192. package/src/services/forget.ts +0 -261
  193. package/src/services/git.test.ts +0 -83
  194. package/src/services/git.ts +0 -74
  195. package/src/services/init.test.ts +0 -109
  196. package/src/services/init.ts +0 -244
  197. package/src/services/local-materialization.ts +0 -421
  198. package/src/services/local-snapshot.ts +0 -173
  199. package/src/services/paths.test.ts +0 -74
  200. package/src/services/paths.ts +0 -98
  201. package/src/services/pull.ts +0 -75
  202. package/src/services/push.ts +0 -121
  203. package/src/services/repo-artifacts.ts +0 -262
  204. package/src/services/repo-snapshot.ts +0 -197
  205. package/src/services/runtime.ts +0 -57
  206. package/src/services/set.ts +0 -383
  207. package/src/services/sync.dry-run.test.ts +0 -179
  208. package/src/services/sync.runtime.test.ts +0 -756
  209. package/src/services/sync.service.test.ts +0 -1025
  210. package/src/test/helpers/sync-fixture.ts +0 -47
@@ -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
- };
@@ -1,75 +0,0 @@
1
- import { 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 const pullSync = async (
28
- request: SyncPullRequest,
29
- context: SyncContext,
30
- ): Promise<SyncPullResult> => {
31
- await ensureSyncRepository(context);
32
-
33
- const config = await readSyncConfig(
34
- context.paths.syncDirectory,
35
- context.environment,
36
- );
37
- const snapshot = await buildRepositorySnapshot(
38
- context.paths.syncDirectory,
39
- config,
40
- );
41
- const materializations = config.entries.map((entry) => {
42
- return buildEntryMaterialization(entry, snapshot);
43
- });
44
-
45
- let deletedLocalCount = 0;
46
-
47
- for (let index = 0; index < config.entries.length; index += 1) {
48
- const entry = config.entries[index];
49
- const materialization = materializations[index];
50
-
51
- if (entry === undefined || materialization === undefined) {
52
- continue;
53
- }
54
-
55
- deletedLocalCount += await countDeletedLocalNodes(
56
- entry,
57
- materialization.desiredKeys,
58
- config,
59
- );
60
-
61
- if (!request.dryRun) {
62
- await applyEntryMaterialization(entry, materialization, config);
63
- }
64
- }
65
-
66
- const counts = buildPullCounts(materializations);
67
-
68
- return {
69
- configPath: context.paths.configPath,
70
- deletedLocalCount,
71
- dryRun: request.dryRun,
72
- syncDirectory: context.paths.syncDirectory,
73
- ...counts,
74
- };
75
- };
@@ -1,121 +0,0 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
2
- import { join } from "node:path";
3
-
4
- import {
5
- readSyncConfig,
6
- resolveSyncArtifactsDirectoryPath,
7
- } from "#app/config/sync.ts";
8
-
9
- import { replacePathAtomically } from "./filesystem.ts";
10
- import { buildLocalSnapshot, type SnapshotNode } from "./local-snapshot.ts";
11
- import {
12
- buildArtifactKey,
13
- buildRepoArtifacts,
14
- collectExistingArtifactKeys,
15
- writeArtifactsToDirectory,
16
- } from "./repo-artifacts.ts";
17
- import { ensureSyncRepository, type SyncContext } from "./runtime.ts";
18
-
19
- export type SyncPushRequest = Readonly<{
20
- dryRun: boolean;
21
- }>;
22
-
23
- export type SyncPushResult = Readonly<{
24
- configPath: string;
25
- deletedArtifactCount: number;
26
- directoryCount: number;
27
- dryRun: boolean;
28
- encryptedFileCount: number;
29
- plainFileCount: number;
30
- symlinkCount: number;
31
- syncDirectory: string;
32
- }>;
33
-
34
- const buildPushCounts = (snapshot: ReadonlyMap<string, SnapshotNode>) => {
35
- let directoryCount = 0;
36
- let encryptedFileCount = 0;
37
- let plainFileCount = 0;
38
- let symlinkCount = 0;
39
-
40
- for (const node of snapshot.values()) {
41
- if (node.type === "directory") {
42
- directoryCount += 1;
43
- continue;
44
- }
45
-
46
- if (node.type === "symlink") {
47
- symlinkCount += 1;
48
- continue;
49
- }
50
-
51
- if (node.secret) {
52
- encryptedFileCount += 1;
53
- } else {
54
- plainFileCount += 1;
55
- }
56
- }
57
-
58
- return {
59
- directoryCount,
60
- encryptedFileCount,
61
- plainFileCount,
62
- symlinkCount,
63
- };
64
- };
65
-
66
- export const pushSync = async (
67
- request: SyncPushRequest,
68
- context: SyncContext,
69
- ): Promise<SyncPushResult> => {
70
- await ensureSyncRepository(context);
71
-
72
- const config = await readSyncConfig(
73
- context.paths.syncDirectory,
74
- context.environment,
75
- );
76
- const snapshot = await buildLocalSnapshot(config);
77
- const artifacts = await buildRepoArtifacts(snapshot, config);
78
- const desiredArtifactKeys = new Set(
79
- artifacts.map((artifact) => {
80
- return buildArtifactKey(artifact);
81
- }),
82
- );
83
- const existingArtifactKeys = await collectExistingArtifactKeys(
84
- context.paths.syncDirectory,
85
- config,
86
- );
87
- const deletedArtifactCount = [...existingArtifactKeys].filter((key) => {
88
- return !desiredArtifactKeys.has(key);
89
- }).length;
90
-
91
- if (!request.dryRun) {
92
- const stagingRoot = await mkdtemp(
93
- join(context.paths.syncDirectory, ".devsync-sync-push-"),
94
- );
95
- const nextArtifactsDirectory = join(stagingRoot, "files");
96
-
97
- try {
98
- await writeArtifactsToDirectory(nextArtifactsDirectory, artifacts);
99
-
100
- await replacePathAtomically(
101
- resolveSyncArtifactsDirectoryPath(context.paths.syncDirectory),
102
- nextArtifactsDirectory,
103
- );
104
- } finally {
105
- await rm(stagingRoot, {
106
- force: true,
107
- recursive: true,
108
- });
109
- }
110
- }
111
-
112
- const counts = buildPushCounts(snapshot);
113
-
114
- return {
115
- configPath: context.paths.configPath,
116
- deletedArtifactCount,
117
- dryRun: request.dryRun,
118
- syncDirectory: context.paths.syncDirectory,
119
- ...counts,
120
- };
121
- };
@@ -1,262 +0,0 @@
1
- import { lstat, mkdir } from "node:fs/promises";
2
- import { join } from "node:path";
3
-
4
- import {
5
- hasReservedSyncArtifactSuffixSegment,
6
- type ResolvedSyncConfig,
7
- resolveSyncArtifactsDirectoryPath,
8
- syncSecretArtifactSuffix,
9
- } from "#app/config/sync.ts";
10
-
11
- import { encryptSecretFile } from "./crypto.ts";
12
- import { DevsyncError } from "./error.ts";
13
- import {
14
- getPathStats,
15
- listDirectoryEntries,
16
- pathExists,
17
- writeFileNode,
18
- writeSymlinkNode,
19
- } from "./filesystem.ts";
20
- import type { SnapshotNode } from "./local-snapshot.ts";
21
- import { buildDirectoryKey } from "./paths.ts";
22
-
23
- export type RepoArtifact =
24
- | Readonly<{
25
- category: "plain";
26
- kind: "directory";
27
- repoPath: string;
28
- }>
29
- | Readonly<{
30
- category: "plain";
31
- kind: "file";
32
- repoPath: string;
33
- contents: Uint8Array;
34
- executable: boolean;
35
- }>
36
- | Readonly<{
37
- category: "plain";
38
- kind: "symlink";
39
- repoPath: string;
40
- linkTarget: string;
41
- }>
42
- | Readonly<{
43
- category: "secret";
44
- kind: "file";
45
- repoPath: string;
46
- contents: string;
47
- executable: boolean;
48
- }>;
49
-
50
- export const buildArtifactKey = (artifact: RepoArtifact) => {
51
- const relativePath = resolveArtifactRelativePath(artifact);
52
-
53
- return artifact.kind === "directory"
54
- ? buildDirectoryKey(relativePath)
55
- : relativePath;
56
- };
57
-
58
- export const isSecretArtifactPath = (relativePath: string) => {
59
- return relativePath.endsWith(syncSecretArtifactSuffix);
60
- };
61
-
62
- export const stripSecretArtifactSuffix = (relativePath: string) => {
63
- if (!isSecretArtifactPath(relativePath)) {
64
- return undefined;
65
- }
66
-
67
- return relativePath.slice(0, -syncSecretArtifactSuffix.length);
68
- };
69
-
70
- export const assertStorageSafeRepoPath = (repoPath: string) => {
71
- if (!hasReservedSyncArtifactSuffixSegment(repoPath)) {
72
- return;
73
- }
74
-
75
- throw new DevsyncError(
76
- `Tracked sync paths must not use the reserved suffix ${syncSecretArtifactSuffix}: ${repoPath}`,
77
- );
78
- };
79
-
80
- export const resolveArtifactRelativePath = (
81
- artifact: Pick<RepoArtifact, "category" | "repoPath">,
82
- ) => {
83
- return artifact.category === "secret"
84
- ? `${artifact.repoPath}${syncSecretArtifactSuffix}`
85
- : artifact.repoPath;
86
- };
87
-
88
- export const buildRepoArtifacts = async (
89
- snapshot: ReadonlyMap<string, SnapshotNode>,
90
- config: ResolvedSyncConfig,
91
- ) => {
92
- const artifacts: RepoArtifact[] = [];
93
- const seenArtifactKeys = new Set<string>();
94
-
95
- for (const repoPath of [...snapshot.keys()].sort((left, right) => {
96
- return left.localeCompare(right);
97
- })) {
98
- assertStorageSafeRepoPath(repoPath);
99
- const node = snapshot.get(repoPath);
100
-
101
- if (node === undefined) {
102
- continue;
103
- }
104
-
105
- if (node.type === "directory") {
106
- const artifact = {
107
- category: "plain",
108
- kind: "directory",
109
- repoPath,
110
- } satisfies RepoArtifact;
111
- const key = buildArtifactKey(artifact);
112
-
113
- if (seenArtifactKeys.has(key)) {
114
- throw new DevsyncError(
115
- `Duplicate repository artifact generated for ${key}`,
116
- );
117
- }
118
-
119
- seenArtifactKeys.add(key);
120
- artifacts.push(artifact);
121
- continue;
122
- }
123
-
124
- if (node.type === "symlink") {
125
- const artifact = {
126
- category: "plain",
127
- kind: "symlink",
128
- linkTarget: node.linkTarget,
129
- repoPath,
130
- } satisfies RepoArtifact;
131
- const key = buildArtifactKey(artifact);
132
-
133
- if (seenArtifactKeys.has(key)) {
134
- throw new DevsyncError(
135
- `Duplicate repository artifact generated for ${key}`,
136
- );
137
- }
138
-
139
- seenArtifactKeys.add(key);
140
- artifacts.push(artifact);
141
- continue;
142
- }
143
-
144
- if (!node.secret) {
145
- const artifact = {
146
- category: "plain",
147
- contents: node.contents,
148
- executable: node.executable,
149
- kind: "file",
150
- repoPath,
151
- } satisfies RepoArtifact;
152
- const key = buildArtifactKey(artifact);
153
-
154
- if (seenArtifactKeys.has(key)) {
155
- throw new DevsyncError(
156
- `Duplicate repository artifact generated for ${key}`,
157
- );
158
- }
159
-
160
- seenArtifactKeys.add(key);
161
- artifacts.push(artifact);
162
- continue;
163
- }
164
-
165
- const artifact = {
166
- category: "secret",
167
- contents: await encryptSecretFile(node.contents, config.age.recipients),
168
- executable: node.executable,
169
- kind: "file",
170
- repoPath,
171
- } satisfies RepoArtifact;
172
- const key = buildArtifactKey(artifact);
173
-
174
- if (seenArtifactKeys.has(key)) {
175
- throw new DevsyncError(
176
- `Duplicate repository artifact generated for ${key}`,
177
- );
178
- }
179
-
180
- seenArtifactKeys.add(key);
181
- artifacts.push(artifact);
182
- }
183
-
184
- return artifacts;
185
- };
186
-
187
- const collectArtifactLeafKeys = async (
188
- rootDirectory: string,
189
- keys: Set<string>,
190
- prefix?: string,
191
- ) => {
192
- if (!(await pathExists(rootDirectory))) {
193
- return;
194
- }
195
-
196
- const entries = await listDirectoryEntries(rootDirectory);
197
-
198
- for (const entry of entries) {
199
- const absolutePath = join(rootDirectory, entry.name);
200
- const relativePath =
201
- prefix === undefined ? entry.name : `${prefix}/${entry.name}`;
202
- const stats = await lstat(absolutePath);
203
-
204
- if (stats?.isDirectory()) {
205
- await collectArtifactLeafKeys(absolutePath, keys, relativePath);
206
- continue;
207
- }
208
-
209
- keys.add(relativePath);
210
- }
211
- };
212
-
213
- export const collectExistingArtifactKeys = async (
214
- syncDirectory: string,
215
- config: ResolvedSyncConfig,
216
- ) => {
217
- const keys = new Set<string>();
218
- const artifactsDirectory = resolveSyncArtifactsDirectoryPath(syncDirectory);
219
-
220
- await collectArtifactLeafKeys(artifactsDirectory, keys);
221
-
222
- for (const entry of config.entries) {
223
- if (entry.kind !== "directory") {
224
- continue;
225
- }
226
-
227
- const path = join(artifactsDirectory, ...entry.repoPath.split("/"));
228
- const stats = await getPathStats(path);
229
-
230
- if (stats?.isDirectory()) {
231
- keys.add(buildDirectoryKey(entry.repoPath));
232
- }
233
- }
234
-
235
- return keys;
236
- };
237
-
238
- export const writeArtifactsToDirectory = async (
239
- rootDirectory: string,
240
- artifacts: readonly RepoArtifact[],
241
- ) => {
242
- await mkdir(rootDirectory, { recursive: true });
243
-
244
- for (const artifact of artifacts) {
245
- const artifactPath = join(
246
- rootDirectory,
247
- ...resolveArtifactRelativePath(artifact).split("/"),
248
- );
249
-
250
- if (artifact.kind === "directory") {
251
- await mkdir(artifactPath, { recursive: true });
252
- continue;
253
- }
254
-
255
- if (artifact.kind === "symlink") {
256
- await writeSymlinkNode(artifactPath, artifact.linkTarget);
257
- continue;
258
- }
259
-
260
- await writeFileNode(artifactPath, artifact);
261
- }
262
- };