freestyle-sync 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,46 +1,12 @@
1
1
  # Freestyle Sync
2
2
 
3
- Sync your current directory, it's dependencies, and your agent context into a remote runtime. Freestyle VMs support snapshots so only changes are uploaded on later runs.
3
+ Offload any development environment to a VM with 1 command. Authentication, agent context, and dependencies are automatically handled.
4
4
 
5
5
  ```
6
6
  npx freestyle-sync
7
7
  ```
8
8
 
9
- Freestyle VMs are the default provider. You can sync into a local Apple container on macOS with the Apple `container` CLI installed:
10
-
11
- ```sh
12
- npx freestyle-sync --provider apple-container --apple-container-image ubuntu:24.04
13
- ```
14
-
15
- Use `--vm-id <container-name-or-id>` to reuse an existing Apple container. Snapshot caching is only available with the Freestyle provider.
16
-
17
- Use the SDK when embedding sync inside another CLI/app (no required `freestyle-sync.config.ts` or cache file):
18
-
19
- ```ts
20
- import { defineConfig, sync, type SyncCache } from "freestyle-sync";
21
- import { gitAuthPlugin } from "@freestyle-sync/auth-git";
22
- import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
23
-
24
- let cache: SyncCache | undefined;
25
-
26
- const result = await sync({
27
- config: defineConfig({
28
- plugins: [gitAuthPlugin(), nodeNpmPlugin()],
29
- }),
30
- options: {
31
- projectRoot: process.cwd(),
32
- autoSsh: false,
33
- },
34
- cache,
35
- onCacheUpdate(nextCache) {
36
- cache = nextCache;
37
- },
38
- });
39
-
40
- console.log("synced runtime", result.vmId);
41
- ```
42
-
43
- freestyle-sync creates `freestyle-sync.config.mjs` on first run and installs the config dependencies into your project as dev dependencies. Remove the generated config or dependencies if you want to reset it later.
9
+ On it's first run, freestyle-sync creates `freestyle-sync.config.mjs` and configures a large set of default plugins. Plugins help sync parts of your development environment that require more complex logic than just copying the current directory's files.
44
10
 
45
11
  ```js
46
12
  import { defineConfig } from "freestyle-sync";
@@ -57,8 +23,11 @@ import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
57
23
  import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
58
24
  import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
59
25
  import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
26
+ import { nextjsPlugin } from "@freestyle-sync/nextjs";
60
27
  import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
28
+ import { rustPlugin } from "@freestyle-sync/rust";
61
29
  import { shellHistoryPlugin } from "@freestyle-sync/shell-history";
30
+ import { vitePlugin } from "@freestyle-sync/vite";
62
31
 
63
32
  export default defineConfig({
64
33
  plugins: [
@@ -73,6 +42,9 @@ export default defineConfig({
73
42
  azureAuthPlugin(),
74
43
  gcloudAuthPlugin(),
75
44
  nodeNpmPlugin(),
45
+ nextjsPlugin(),
46
+ vitePlugin(),
47
+ rustPlugin(),
76
48
  claudeAgentPlugin(),
77
49
  codexAgentPlugin(),
78
50
  copilotAgentPlugin(),
@@ -81,7 +53,7 @@ export default defineConfig({
81
53
  });
82
54
  ```
83
55
 
84
- Project sync can exclude folders and include additional local paths. Include paths may point outside the project directory; they are copied into the remote project at the configured relative target.
56
+ You can exclude folders and include additional local paths. Include paths may point outside the project directory; they are copied into the remote project at the configured relative target.
85
57
 
86
58
  ```js
87
59
  export default defineConfig({
@@ -90,15 +62,50 @@ export default defineConfig({
90
62
  include: [
91
63
  { source: "../shared-config", target: "shared-config" },
92
64
  ],
93
- },
94
- plugins: [
95
- nodeNpmPlugin(),
96
- ],
65
+ }
97
66
  });
98
67
  ```
99
68
 
100
- `nodeNpmPlugin()` excludes `node_modules` from upload by default and installs dependencies on the remote runtime using the project lockfile: `pnpm install --frozen-lockfile`, Yarn `--frozen-lockfile`/`--immutable`, or `npm ci`. To keep the previous behavior of syncing `node_modules` and only repairing native/workspace packages remotely, use:
69
+ The default Node/framework/runtime plugins avoid uploading generated dependency and build output folders:
70
+
71
+ ```js
72
+ nodeNpmPlugin() // excludes node_modules and installs frozen dependencies remotely
73
+ nextjsPlugin() // excludes .next
74
+ vitePlugin() // excludes dist, build, and .vite
75
+ rustPlugin() // excludes target, installs Rust/Cargo, and runs cargo fetch
76
+ ```
77
+
78
+ You can keep uploading those generated folders when needed:
101
79
 
102
80
  ```js
103
81
  nodeNpmPlugin({ syncNodeModules: true })
82
+ nextjsPlugin({ syncNextDir: true })
83
+ vitePlugin({ syncBuildOutput: true })
84
+ rustPlugin({ syncTargetDir: true })
85
+ ```
86
+
87
+ If you're integrating freestyle-sync into your own toolchain, you can use it as an sdk and take full control over how configuration and cache is stored.
88
+
89
+ ```ts
90
+ import { defineConfig, sync, type SyncCache } from "freestyle-sync";
91
+ import { gitAuthPlugin } from "@freestyle-sync/auth-git";
92
+ import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
93
+
94
+ let cache: SyncCache | undefined;
95
+
96
+ const result = await sync({
97
+ config: defineConfig({
98
+ plugins: [gitAuthPlugin(), nodeNpmPlugin()],
99
+ }),
100
+ options: {
101
+ projectRoot: process.cwd(),
102
+ autoSsh: false,
103
+ },
104
+ cache,
105
+ onCacheUpdate(nextCache) {
106
+ cache = nextCache;
107
+ },
108
+ });
109
+
110
+ console.log("synced runtime", result.vmId);
104
111
  ```
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import type { CliOptions, PushvmConfig, VmProviderName } from "./plugin-api.ts";
4
+ export * from "./plugin-api.ts";
5
+ type FileKind = "file" | "symlink";
6
+ type FileDigest = {
7
+ hash: string;
8
+ kind: FileKind;
9
+ mode: number;
10
+ size: number;
11
+ };
12
+ type CacheFile = {
13
+ version: number;
14
+ projectRoot: string;
15
+ remoteProjectDir: string;
16
+ provider?: VmProviderName;
17
+ vmId?: string;
18
+ snapshotId?: string;
19
+ projectFiles: Record<string, FileDigest>;
20
+ contextFiles: Record<string, FileDigest>;
21
+ snapshotProjectFiles?: Record<string, FileDigest>;
22
+ snapshotContextFiles?: Record<string, FileDigest>;
23
+ envHash?: string;
24
+ snapshotEnvHash?: string;
25
+ updatedAt?: string;
26
+ };
27
+ type PluginPreferences = {
28
+ version: number;
29
+ disabledPlugins: string[];
30
+ updatedAt?: string;
31
+ };
32
+ export type SyncCache = CacheFile;
33
+ export type SyncPluginPreferences = PluginPreferences;
34
+ export type SyncSdkOptions = {
35
+ config: PushvmConfig;
36
+ options?: Partial<CliOptions>;
37
+ cache?: SyncCache;
38
+ pluginPreferences?: SyncPluginPreferences;
39
+ onCacheUpdate?(cache: SyncCache): Promise<void> | void;
40
+ };
41
+ export type SyncSdkResult = {
42
+ vmId?: string;
43
+ cache: SyncCache;
44
+ snapshotId?: string;
45
+ };
46
+ export declare function sync(sdkOptions: SyncSdkOptions): Promise<SyncSdkResult>;
package/dist/src/main.js CHANGED
@@ -30,6 +30,8 @@ const DEFAULT_STACK_API_URL = "https://api.stack-auth.com";
30
30
  const DEFAULT_STACK_PROJECT_ID = "0edf478c-f123-46fb-818f-34c0024a9f35";
31
31
  const DEFAULT_STACK_PUBLISHABLE_CLIENT_KEY = "pck_h2aft7g9pqjzrkdnzs199h1may5wjtdtdxeex7m2wzp1r";
32
32
  const STACK_REFRESH_TOKEN_ENV_KEY = "FREESTYLE_STACK_REFRESH_TOKEN";
33
+ const GENERATED_UPLOAD_DIRECTORY_NAMES = new Set([".cache", ".freestyle-sync", ".git", ".next", ".turbo", ".vercel", ".vite", "build", "coverage", "dist", "node_modules", "target"]);
34
+ const DEFAULT_PROJECT_EXCLUDES = [".freestyle-sync"];
33
35
  const USE_UNICODE_OUTPUT = process.stdout.isTTY && (process.env.TERM !== "dumb" || Boolean(process.env.TERM_PROGRAM));
34
36
  const USE_STYLED_OUTPUT = process.stdout.isTTY && process.env.NO_COLOR !== "1";
35
37
  const DEFAULT_CONFIG_DEPENDENCIES = [
@@ -47,8 +49,11 @@ const DEFAULT_CONFIG_DEPENDENCIES = [
47
49
  { name: "@freestyle-sync/auth-npm", spec: "@freestyle-sync/auth-npm@latest" },
48
50
  { name: "@freestyle-sync/auth-ssh", spec: "@freestyle-sync/auth-ssh@latest" },
49
51
  { name: "@freestyle-sync/auth-yarn", spec: "@freestyle-sync/auth-yarn@latest" },
52
+ { name: "@freestyle-sync/nextjs", spec: "@freestyle-sync/nextjs@latest" },
50
53
  { name: "@freestyle-sync/node-npm", spec: "@freestyle-sync/node-npm@latest" },
54
+ { name: "@freestyle-sync/rust", spec: "@freestyle-sync/rust@latest" },
51
55
  { name: "@freestyle-sync/shell-history", spec: "@freestyle-sync/shell-history@latest" },
56
+ { name: "@freestyle-sync/vite", spec: "@freestyle-sync/vite@latest" },
52
57
  ];
53
58
  const pluginUtils = {
54
59
  checkedExec,
@@ -107,7 +112,6 @@ async function main() {
107
112
  }
108
113
  export async function sync(sdkOptions) {
109
114
  const options = await resolveCliOptions(sdkOptions.options);
110
- printHeading(CLI_NAME);
111
115
  config = sdkOptions.config;
112
116
  plugins = config.plugins;
113
117
  const pluginPreferences = await updatePluginPreferences(options, sdkOptions.pluginPreferences);
@@ -375,8 +379,11 @@ import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
375
379
  import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
376
380
  import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
377
381
  import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
382
+ import { nextjsPlugin } from "@freestyle-sync/nextjs";
378
383
  import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
384
+ import { rustPlugin } from "@freestyle-sync/rust";
379
385
  import { shellHistoryPlugin } from "@freestyle-sync/shell-history";
386
+ import { vitePlugin } from "@freestyle-sync/vite";
380
387
  import { defineConfig } from "freestyle-sync";
381
388
 
382
389
  export default defineConfig({
@@ -392,6 +399,9 @@ export default defineConfig({
392
399
  azureAuthPlugin(),
393
400
  gcloudAuthPlugin(),
394
401
  nodeNpmPlugin(),
402
+ nextjsPlugin(),
403
+ vitePlugin(),
404
+ rustPlugin(),
395
405
  claudeAgentPlugin(),
396
406
  codexAgentPlugin(),
397
407
  copilotAgentPlugin(),
@@ -414,8 +424,11 @@ import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
414
424
  import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
415
425
  import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
416
426
  import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
427
+ import { nextjsPlugin } from "@freestyle-sync/nextjs";
417
428
  import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
429
+ import { rustPlugin } from "@freestyle-sync/rust";
418
430
  import { shellHistoryPlugin } from "@freestyle-sync/shell-history";
431
+ import { vitePlugin } from "@freestyle-sync/vite";
419
432
  import { defineConfig } from "freestyle-sync";
420
433
 
421
434
  export default defineConfig({
@@ -431,6 +444,9 @@ export default defineConfig({
431
444
  azureAuthPlugin(),
432
445
  gcloudAuthPlugin(),
433
446
  nodeNpmPlugin(),
447
+ nextjsPlugin(),
448
+ vitePlugin(),
449
+ rustPlugin(),
434
450
  claudeAgentPlugin(),
435
451
  codexAgentPlugin(),
436
452
  copilotAgentPlugin(),
@@ -730,7 +746,7 @@ async function resolveProjectSyncConfig(options) {
730
746
  configs.push(pluginConfig);
731
747
  }
732
748
  return {
733
- exclude: configs.flatMap((projectConfig) => projectConfig.exclude ?? []).map(normalizeProjectPattern),
749
+ exclude: [...DEFAULT_PROJECT_EXCLUDES, ...configs.flatMap((projectConfig) => projectConfig.exclude ?? [])].map(normalizeProjectPattern),
734
750
  include: configs.flatMap((projectConfig) => normalizeProjectIncludes(options.projectRoot, projectConfig.include ?? [])),
735
751
  };
736
752
  }
@@ -1066,6 +1082,8 @@ function printPlan(options, projectChanges, contextChanges, envExports, cache) {
1066
1082
  console.log(`${dim(" Project files:")} ${projectChanges.changed.length} changed, ${projectChanges.removed.length} removed, ${projectChanges.unchanged} unchanged`);
1067
1083
  console.log(`${dim(" Context files:")} ${contextChanges.changed.length} changed, ${contextChanges.removed.length} removed, ${contextChanges.unchanged} unchanged`);
1068
1084
  console.log(`${dim(" Estimated upload:")} ${formatBytes(totalEntrySize(projectChanges.changed))} project, ${formatBytes(totalEntrySize(contextChanges.changed))} context`);
1085
+ printUploadBreakdown("Project upload contributors", projectUploadContributors(projectChanges.changed), "consider sync.exclude for generated folders");
1086
+ printUploadBreakdown("Context upload contributors", contextUploadContributors(contextChanges.changed), "disable unneeded context plugins to skip");
1069
1087
  if (Object.keys(envExports).length > 0) {
1070
1088
  console.log(`${dim(" Environment exports:")} ${Object.keys(envExports).length}`);
1071
1089
  }
@@ -1074,6 +1092,40 @@ function printPlan(options, projectChanges, contextChanges, envExports, cache) {
1074
1092
  function totalEntrySize(entries) {
1075
1093
  return entries.reduce((total, entry) => total + entry.size, 0);
1076
1094
  }
1095
+ function projectUploadContributors(entries) {
1096
+ return largestContributors(entries, (entry) => contributionPath(entry.relativePath));
1097
+ }
1098
+ function contextUploadContributors(entries) {
1099
+ return largestContributors(entries, (entry) => entry.label);
1100
+ }
1101
+ function contributionPath(relativePath) {
1102
+ const parts = relativePath.split("/").filter(Boolean);
1103
+ if (parts.length <= 1)
1104
+ return relativePath;
1105
+ const generatedDirectoryIndex = parts.findIndex((part) => GENERATED_UPLOAD_DIRECTORY_NAMES.has(part));
1106
+ if (generatedDirectoryIndex >= 0)
1107
+ return parts.slice(0, generatedDirectoryIndex + 1).join("/");
1108
+ return parts.slice(0, Math.min(parts.length - 1, 3)).join("/");
1109
+ }
1110
+ function largestContributors(entries, bucketForEntry) {
1111
+ const buckets = new Map();
1112
+ for (const entry of entries) {
1113
+ const bucketName = bucketForEntry(entry);
1114
+ const bucket = buckets.get(bucketName) ?? { path: bucketName, size: 0, files: 0 };
1115
+ bucket.size += entry.size;
1116
+ bucket.files += 1;
1117
+ buckets.set(bucketName, bucket);
1118
+ }
1119
+ return Array.from(buckets.values()).sort((left, right) => right.size - left.size).slice(0, 6);
1120
+ }
1121
+ function printUploadBreakdown(title, contributors, hint) {
1122
+ if (contributors.length === 0)
1123
+ return;
1124
+ console.log(`${dim(` ${title}:`)} ${hint}`);
1125
+ for (const contributor of contributors) {
1126
+ console.log(`${dim(" -")} ${formatBytes(contributor.size).padStart(9)} ${contributor.path} ${dim(`(${contributor.files} ${contributor.files === 1 ? "file" : "files"})`)}`);
1127
+ }
1128
+ }
1077
1129
  function printHeading(name) {
1078
1130
  console.log(`${bold(name)} ${dim(`${symbol("→", "-")} Freestyle sync`)}`);
1079
1131
  console.log("");
@@ -1581,6 +1633,8 @@ async function runInstall(vm, projectRoot, remoteProjectDir) {
1581
1633
  await checkedExec(vm, `cd ${shellQuote(remoteProjectDir)} && ${installCommand}`, 20 * 60 * 1000);
1582
1634
  }
1583
1635
  async function detectInstallCommand(projectRoot) {
1636
+ if (await exists(path.join(projectRoot, "bun.lock")) || await exists(path.join(projectRoot, "bun.lockb")))
1637
+ return "bun install --frozen-lockfile";
1584
1638
  if (await exists(path.join(projectRoot, "pnpm-lock.yaml")))
1585
1639
  return "corepack enable && pnpm install --frozen-lockfile";
1586
1640
  if (await exists(path.join(projectRoot, "yarn.lock")))
@@ -1648,11 +1702,13 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
1648
1702
  const width = String(chunkCount - 1).length;
1649
1703
  const canRenderInlineProgress = process.stdout.isTTY;
1650
1704
  const logEvery = Math.max(1, Math.ceil(chunkCount / 4));
1705
+ const chunkManifest = [];
1651
1706
  let index = 0;
1652
1707
  for await (const chunk of createReadStream(archivePath, { highWaterMark: ARCHIVE_CHUNK_BYTES })) {
1653
- const chunkName = `${String(index).padStart(width, "0")}.chunk.b64`;
1708
+ const chunkName = `${String(index).padStart(width, "0")}.chunk`;
1654
1709
  const content = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1655
- await vm.fs.writeTextFile(`${chunkDir}/${chunkName}`, content.toString("base64"));
1710
+ chunkManifest.push(`${chunkName} ${content.length}`);
1711
+ await vm.fs.writeFile(`${chunkDir}/${chunkName}`, content);
1656
1712
  const uploadedChunks = index + 1;
1657
1713
  if (chunkCount > 1) {
1658
1714
  const progressMessage = `VM ${vmId}: uploaded ${uploadedChunks}/${chunkCount} ${label} archive chunks`;
@@ -1668,14 +1724,63 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
1668
1724
  }
1669
1725
  index += 1;
1670
1726
  }
1727
+ if (archiveSize > 0) {
1728
+ await vm.fs.writeTextFile(`${chunkDir}/manifest`, `${chunkManifest.join("\n")}\n`);
1729
+ }
1671
1730
  await checkedExec(vm, archiveSize === 0
1672
1731
  ? `: > ${shellQuote(remoteArchivePath)} && rm -rf ${shellQuote(chunkDir)}`
1673
1732
  : [
1674
1733
  "set -e",
1675
- `rm -f ${shellQuote(remoteArchivePath)}`,
1676
- `for chunk in ${shellQuote(chunkDir)}/*.chunk.b64; do`,
1677
- ` base64 -d "$chunk" >> ${shellQuote(remoteArchivePath)}`,
1734
+ `chunk_dir=${shellQuote(chunkDir)}`,
1735
+ `remote_archive=${shellQuote(remoteArchivePath)}`,
1736
+ `manifest="$chunk_dir/manifest"`,
1737
+ "deadline=$(( $(date +%s) + 60 ))",
1738
+ "while :; do",
1739
+ " validation_error=",
1740
+ " validated_chunks=0",
1741
+ " if [ ! -f \"$manifest\" ]; then",
1742
+ " validation_error='missing chunk manifest'",
1743
+ " else",
1744
+ " actual_chunks=$(find \"$chunk_dir\" -maxdepth 1 -type f -name '*.chunk' | wc -l | tr -d '[:space:]')",
1745
+ ` if [ "$actual_chunks" != ${shellQuote(String(chunkCount))} ]; then`,
1746
+ ` validation_error="expected ${chunkCount} chunks, found $actual_chunks"`,
1747
+ " else",
1748
+ " while IFS=' ' read -r chunk_name expected_size; do",
1749
+ " [ -n \"$chunk_name\" ] || continue",
1750
+ " chunk_path=\"$chunk_dir/$chunk_name\"",
1751
+ " if [ ! -f \"$chunk_path\" ]; then",
1752
+ " validation_error=\"missing chunk $chunk_name\"",
1753
+ " break",
1754
+ " fi",
1755
+ " actual_chunk_size=$(wc -c < \"$chunk_path\" | tr -d '[:space:]')",
1756
+ " if [ \"$actual_chunk_size\" != \"$expected_size\" ]; then",
1757
+ " validation_error=\"chunk $chunk_name expected $expected_size bytes, got $actual_chunk_size\"",
1758
+ " break",
1759
+ " fi",
1760
+ " validated_chunks=$((validated_chunks + 1))",
1761
+ " done < \"$manifest\"",
1762
+ ` if [ "$validated_chunks" != ${shellQuote(String(chunkCount))} ] && [ -z "$validation_error" ]; then`,
1763
+ ` validation_error="expected ${chunkCount} manifest entries, found $validated_chunks"`,
1764
+ " fi",
1765
+ " fi",
1766
+ " fi",
1767
+ " if [ -z \"$validation_error\" ]; then",
1768
+ " break",
1769
+ " fi",
1770
+ " if [ \"$(date +%s)\" -ge \"$deadline\" ]; then",
1771
+ ` rm -f ${shellQuote(remoteArchivePath)}`,
1772
+ ` rm -rf ${shellQuote(chunkDir)}`,
1773
+ ` echo ${shellQuote(`archive chunk validation failed for ${label}`)} >&2`,
1774
+ " echo \"$validation_error\" >&2",
1775
+ " exit 1",
1776
+ " fi",
1777
+ " sleep 1",
1678
1778
  "done",
1779
+ `rm -f ${shellQuote(remoteArchivePath)}`,
1780
+ "while IFS=' ' read -r chunk_name expected_size; do",
1781
+ " [ -n \"$chunk_name\" ] || continue",
1782
+ " cat \"$chunk_dir/$chunk_name\" >> \"$remote_archive\"",
1783
+ "done < \"$manifest\"",
1679
1784
  `actual_size=$(wc -c < ${shellQuote(remoteArchivePath)} | tr -d '[:space:]')`,
1680
1785
  `actual_hash=$(sha256sum ${shellQuote(remoteArchivePath)} | awk '{print $1}')`,
1681
1786
  `if [ "$actual_size" != ${shellQuote(String(archiveSize))} ] || [ "$actual_hash" != ${shellQuote(archiveHash)} ]; then`,
@@ -1686,7 +1791,7 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
1686
1791
  " exit 1",
1687
1792
  "fi",
1688
1793
  `rm -rf ${shellQuote(chunkDir)}`,
1689
- ].join("\n"));
1794
+ ].join("\n"), 5 * 60 * MS_PER_SECOND);
1690
1795
  }
1691
1796
  async function mkdirRemote(vm, directories) {
1692
1797
  for (const chunk of chunkArray(directories, 50)) {
@@ -0,0 +1,127 @@
1
+ export type VmProviderName = "freestyle" | "apple-container";
2
+ export type CliOptions = {
3
+ projectRoot: string;
4
+ cachePath: string;
5
+ remoteProjectDir: string;
6
+ provider: VmProviderName;
7
+ name: string;
8
+ vmId?: string;
9
+ appleContainerImage: string;
10
+ idleTimeoutSeconds?: number;
11
+ yes: boolean;
12
+ dryRun: boolean;
13
+ disablePlugins: string[];
14
+ enablePlugins: string[];
15
+ resetPluginPrefs: boolean;
16
+ listPlugins: boolean;
17
+ includeAuth: boolean;
18
+ includeAgentContext: boolean;
19
+ includeGitDir: boolean;
20
+ includeAllCopilotWorkspaces: boolean;
21
+ snapshot: boolean;
22
+ skipSync: boolean;
23
+ install: boolean;
24
+ autoSsh: boolean;
25
+ envKeys: string[];
26
+ };
27
+ export type ContextCandidate = {
28
+ source: string;
29
+ remoteRoot: string;
30
+ label: string;
31
+ sensitive: boolean;
32
+ preferenceKey: string;
33
+ promptLabel: string;
34
+ };
35
+ export type RemoteVm = {
36
+ exec(args: {
37
+ command: string;
38
+ timeoutMs?: number;
39
+ }): Promise<{
40
+ stdout?: string | null;
41
+ stderr?: string | null;
42
+ statusCode?: number | null;
43
+ }>;
44
+ fs: {
45
+ writeFile(path: string, content: Buffer): Promise<void>;
46
+ writeTextFile(path: string, content: string): Promise<void>;
47
+ };
48
+ };
49
+ export type UploadArchiveInChunks = (vm: RemoteVm, vmId: string, archivePath: string, remoteArchivePath: string, label: string) => Promise<void>;
50
+ export type ExecFileAsync = (file: string, args: string[]) => Promise<{
51
+ stdout: string;
52
+ stderr: string;
53
+ }>;
54
+ export type PushvmPluginUtils = {
55
+ checkedExec(vm: RemoteVm, command: string, timeoutMs?: number): Promise<{
56
+ stdout?: string | null;
57
+ stderr?: string | null;
58
+ statusCode?: number | null;
59
+ }>;
60
+ createTar(args: string[]): Promise<void>;
61
+ uploadArchiveInChunks: UploadArchiveInChunks;
62
+ execFileAsync: ExecFileAsync;
63
+ shellQuote(value: string): string;
64
+ md5(value: string): string;
65
+ delay(ms: number): Promise<void>;
66
+ };
67
+ export type PushvmPluginContext = {
68
+ options: CliOptions;
69
+ utils: PushvmPluginUtils;
70
+ };
71
+ export type RemoteHookContext = PushvmPluginContext & {
72
+ vm: RemoteVm;
73
+ vmId: string;
74
+ };
75
+ export type EditorScheme = string;
76
+ export type EditorHookContext = RemoteHookContext & {
77
+ scheme: EditorScheme;
78
+ remoteWorkspaceUri: string;
79
+ };
80
+ export type ConnectHookContext = RemoteHookContext & {
81
+ contextCandidates: ContextCandidate[];
82
+ runBeforeOpenRemoteEditor(args: {
83
+ scheme: EditorScheme;
84
+ remoteWorkspaceUri: string;
85
+ }): Promise<string[]>;
86
+ runAfterOpenRemoteEditor(args: {
87
+ scheme: EditorScheme;
88
+ remoteWorkspaceUri: string;
89
+ }): Promise<string[]>;
90
+ };
91
+ export type ProjectSyncInclude = string | {
92
+ source: string;
93
+ target?: string;
94
+ };
95
+ export type ProjectSyncConfig = {
96
+ exclude?: string[];
97
+ include?: ProjectSyncInclude[];
98
+ };
99
+ export type PushvmPlugin = {
100
+ name: string;
101
+ collectEnvironment?(context: PushvmPluginContext): Record<string, string>;
102
+ discoverContextCandidates?(context: PushvmPluginContext): Promise<ContextCandidate[]> | ContextCandidate[];
103
+ configureProjectSync?(context: PushvmPluginContext): Promise<ProjectSyncConfig | void> | ProjectSyncConfig | void;
104
+ shouldSkipContextDirectory?(relativePath: string, name: string): boolean;
105
+ isProtectedRemotePath?(remotePath: string): boolean;
106
+ afterProjectSync?(context: RemoteHookContext): Promise<void> | void;
107
+ afterContextSync?(context: RemoteHookContext & {
108
+ changedRemotePaths: string[];
109
+ }): Promise<void> | void;
110
+ beforeSnapshot?(context: RemoteHookContext): Promise<void> | void;
111
+ afterSync?(context: RemoteHookContext & {
112
+ contextCandidates: ContextCandidate[];
113
+ }): Promise<string[] | void> | string[] | void;
114
+ connect?(context: ConnectHookContext): Promise<boolean | void> | boolean | void;
115
+ beforeOpenRemoteEditor?(context: EditorHookContext & {
116
+ contextCandidates: ContextCandidate[];
117
+ }): Promise<string[] | void> | string[] | void;
118
+ afterOpenRemoteEditor?(context: EditorHookContext & {
119
+ contextCandidates: ContextCandidate[];
120
+ }): Promise<string[] | void> | string[] | void;
121
+ };
122
+ export type PushvmConfig = {
123
+ plugins: PushvmPlugin[];
124
+ sync?: ProjectSyncConfig;
125
+ };
126
+ export declare function definePlugin(plugin: PushvmPlugin): PushvmPlugin;
127
+ export declare function defineConfig(config: PushvmConfig): PushvmConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freestyle-sync",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "main": "dist/src/main.js",
6
6
  "exports": {
@@ -25,7 +25,9 @@
25
25
  "publish": "npm run check && npm run build && npm run publish:workspaces && npm run publish:root",
26
26
  "publish:root": "node scripts/publish-root.mjs",
27
27
  "publish:workspaces": "node scripts/publish-workspaces.mjs",
28
- "start": "node src/main.ts"
28
+ "start": "node src/main.ts",
29
+ "version:patch": "node scripts/version-workspaces.mjs patch",
30
+ "version:workspaces": "node scripts/version-workspaces.mjs"
29
31
  },
30
32
  "dependencies": {
31
33
  "dotenv": "^17.4.2",