freestyle-sync 0.1.12 → 0.1.14
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 +2 -0
- package/dist/src/main.js +159 -13
- package/dist/src/plugin-api.d.ts +2 -0
- package/dist/src/plugin-api.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ import { gitAuthPlugin } from "@freestyle-sync/auth-git";
|
|
|
22
22
|
import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
|
|
23
23
|
import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
|
|
24
24
|
import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
|
|
25
|
+
import { wranglerAuthPlugin } from "@freestyle-sync/auth-wrangler";
|
|
25
26
|
import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
|
|
26
27
|
import { nextjsPlugin } from "@freestyle-sync/nextjs";
|
|
27
28
|
import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
|
|
@@ -37,6 +38,7 @@ export default defineConfig({
|
|
|
37
38
|
githubCliAuthPlugin(),
|
|
38
39
|
npmAuthPlugin(),
|
|
39
40
|
yarnAuthPlugin(),
|
|
41
|
+
wranglerAuthPlugin(),
|
|
40
42
|
dockerAuthPlugin(),
|
|
41
43
|
awsAuthPlugin(),
|
|
42
44
|
azureAuthPlugin(),
|
package/dist/src/main.js
CHANGED
|
@@ -23,6 +23,11 @@ const CLI_NAME = "freestyle-sync";
|
|
|
23
23
|
const CACHE_VERSION = 1;
|
|
24
24
|
const PLUGIN_PREFERENCES_VERSION = 1;
|
|
25
25
|
const ARCHIVE_CHUNK_BYTES = 512 * 1024;
|
|
26
|
+
const ARCHIVE_UPLOAD_MIN_CONCURRENCY = 1;
|
|
27
|
+
const ARCHIVE_UPLOAD_INITIAL_CONCURRENCY = 4;
|
|
28
|
+
const ARCHIVE_UPLOAD_MAX_CONCURRENCY = 16;
|
|
29
|
+
const ARCHIVE_UPLOAD_WRITE_ATTEMPTS = 3;
|
|
30
|
+
const ARCHIVE_UPLOAD_RETRY_BASE_DELAY_MS = 200;
|
|
26
31
|
const MS_PER_SECOND = 1000;
|
|
27
32
|
const DEFAULT_APPLE_CONTAINER_IMAGE = "ubuntu:24.04";
|
|
28
33
|
const DEFAULT_FREESTYLE_API_URL = "https://api.freestyle.sh";
|
|
@@ -48,6 +53,7 @@ const DEFAULT_CONFIG_DEPENDENCIES = [
|
|
|
48
53
|
{ name: "@freestyle-sync/auth-github-cli", spec: "@freestyle-sync/auth-github-cli@latest" },
|
|
49
54
|
{ name: "@freestyle-sync/auth-npm", spec: "@freestyle-sync/auth-npm@latest" },
|
|
50
55
|
{ name: "@freestyle-sync/auth-ssh", spec: "@freestyle-sync/auth-ssh@latest" },
|
|
56
|
+
{ name: "@freestyle-sync/auth-wrangler", spec: "@freestyle-sync/auth-wrangler@latest" },
|
|
51
57
|
{ name: "@freestyle-sync/auth-yarn", spec: "@freestyle-sync/auth-yarn@latest" },
|
|
52
58
|
{ name: "@freestyle-sync/nextjs", spec: "@freestyle-sync/nextjs@latest" },
|
|
53
59
|
{ name: "@freestyle-sync/node-npm", spec: "@freestyle-sync/node-npm@latest" },
|
|
@@ -103,7 +109,12 @@ if (isDirectCliExecution()) {
|
|
|
103
109
|
});
|
|
104
110
|
}
|
|
105
111
|
async function main() {
|
|
106
|
-
const
|
|
112
|
+
const args = process.argv.slice(2);
|
|
113
|
+
if (args[0] === "update") {
|
|
114
|
+
await runUpdateCommand(args.slice(1));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const options = await parseArgs(args);
|
|
107
118
|
const loadedConfig = await loadConfig(options.projectRoot);
|
|
108
119
|
await sync({
|
|
109
120
|
config: loadedConfig,
|
|
@@ -245,13 +256,12 @@ export async function sync(sdkOptions) {
|
|
|
245
256
|
progress.step("Saving local sync cache");
|
|
246
257
|
await saveCache(buildSyncCache());
|
|
247
258
|
let snapshotPromise = null;
|
|
248
|
-
|
|
249
|
-
if (options.snapshot && createSnapshot) {
|
|
259
|
+
if (options.snapshot && vm.snapshot) {
|
|
250
260
|
progress.step(`Creating snapshot cache for ${vmId} in background`);
|
|
251
261
|
snapshotPromise = (async () => {
|
|
252
262
|
await runBeforeSnapshotPlugins(vm, vmId, options);
|
|
253
263
|
try {
|
|
254
|
-
const snapshot = await
|
|
264
|
+
const snapshot = await vm.snapshot({ name: `${CLI_NAME}-${path.basename(options.projectRoot)}-${Date.now()}` });
|
|
255
265
|
return snapshot.snapshotId;
|
|
256
266
|
}
|
|
257
267
|
catch (error) {
|
|
@@ -378,6 +388,7 @@ import { gitAuthPlugin } from "@freestyle-sync/auth-git";
|
|
|
378
388
|
import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
|
|
379
389
|
import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
|
|
380
390
|
import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
|
|
391
|
+
import { wranglerAuthPlugin } from "@freestyle-sync/auth-wrangler";
|
|
381
392
|
import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
|
|
382
393
|
import { nextjsPlugin } from "@freestyle-sync/nextjs";
|
|
383
394
|
import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
|
|
@@ -394,6 +405,7 @@ export default defineConfig({
|
|
|
394
405
|
githubCliAuthPlugin(),
|
|
395
406
|
npmAuthPlugin(),
|
|
396
407
|
yarnAuthPlugin(),
|
|
408
|
+
wranglerAuthPlugin(),
|
|
397
409
|
dockerAuthPlugin(),
|
|
398
410
|
awsAuthPlugin(),
|
|
399
411
|
azureAuthPlugin(),
|
|
@@ -423,6 +435,7 @@ import { gitAuthPlugin } from "@freestyle-sync/auth-git";
|
|
|
423
435
|
import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
|
|
424
436
|
import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
|
|
425
437
|
import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
|
|
438
|
+
import { wranglerAuthPlugin } from "@freestyle-sync/auth-wrangler";
|
|
426
439
|
import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
|
|
427
440
|
import { nextjsPlugin } from "@freestyle-sync/nextjs";
|
|
428
441
|
import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
|
|
@@ -439,6 +452,7 @@ export default defineConfig({
|
|
|
439
452
|
githubCliAuthPlugin(),
|
|
440
453
|
npmAuthPlugin(),
|
|
441
454
|
yarnAuthPlugin(),
|
|
455
|
+
wranglerAuthPlugin(),
|
|
442
456
|
dockerAuthPlugin(),
|
|
443
457
|
awsAuthPlugin(),
|
|
444
458
|
azureAuthPlugin(),
|
|
@@ -480,16 +494,81 @@ async function ensureDefaultConfigDependencies(projectRoot) {
|
|
|
480
494
|
throw new Error(`failed to install freestyle-sync config dependencies: ${details.trim() || String(error)}`);
|
|
481
495
|
}
|
|
482
496
|
}
|
|
497
|
+
async function updateDefaultConfigDependencies(projectRoot) {
|
|
498
|
+
const specs = DEFAULT_CONFIG_DEPENDENCIES.map((dependency) => dependency.spec);
|
|
499
|
+
const { command, args } = await dependencyInstallCommand(projectRoot, specs);
|
|
500
|
+
console.log(`Updating freestyle-sync packages with ${command}...`);
|
|
501
|
+
try {
|
|
502
|
+
await execFileAsync(command, args, { cwd: projectRoot });
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
const details = error && typeof error === "object" && "stderr" in error ? String(error.stderr ?? "") : String(error);
|
|
506
|
+
throw new Error(`failed to update freestyle-sync packages: ${details.trim() || String(error)}`);
|
|
507
|
+
}
|
|
508
|
+
console.log("Updated freestyle-sync and default plugins.");
|
|
509
|
+
}
|
|
483
510
|
async function isDependencyInstalled(projectRoot, name) {
|
|
484
511
|
return exists(path.join(projectRoot, "node_modules", ...name.split("/"), "package.json"));
|
|
485
512
|
}
|
|
486
513
|
async function dependencyInstallCommand(projectRoot, specs) {
|
|
487
|
-
|
|
514
|
+
const packageManager = await detectPackageManager(projectRoot);
|
|
515
|
+
if (packageManager === "bun")
|
|
516
|
+
return { command: "bun", args: ["add", "-d", ...specs] };
|
|
517
|
+
if (packageManager === "pnpm")
|
|
488
518
|
return { command: "pnpm", args: ["add", "-D", ...specs] };
|
|
489
|
-
if (
|
|
519
|
+
if (packageManager === "yarn")
|
|
490
520
|
return { command: "yarn", args: ["add", "-D", ...specs] };
|
|
491
521
|
return { command: "npm", args: ["install", "--save-dev", ...specs] };
|
|
492
522
|
}
|
|
523
|
+
async function detectPackageManager(projectRoot) {
|
|
524
|
+
if (await exists(path.join(projectRoot, "bun.lock")) || await exists(path.join(projectRoot, "bun.lockb")))
|
|
525
|
+
return "bun";
|
|
526
|
+
if (await exists(path.join(projectRoot, "pnpm-lock.yaml")))
|
|
527
|
+
return "pnpm";
|
|
528
|
+
if (await exists(path.join(projectRoot, "yarn.lock")))
|
|
529
|
+
return "yarn";
|
|
530
|
+
if (await exists(path.join(projectRoot, "package-lock.json")) || await exists(path.join(projectRoot, "npm-shrinkwrap.json")))
|
|
531
|
+
return "npm";
|
|
532
|
+
const packageManager = await readPackageManagerField(projectRoot);
|
|
533
|
+
if (packageManager?.startsWith("bun@"))
|
|
534
|
+
return "bun";
|
|
535
|
+
if (packageManager?.startsWith("pnpm@"))
|
|
536
|
+
return "pnpm";
|
|
537
|
+
if (packageManager?.startsWith("yarn@"))
|
|
538
|
+
return "yarn";
|
|
539
|
+
return "npm";
|
|
540
|
+
}
|
|
541
|
+
async function readPackageManagerField(projectRoot) {
|
|
542
|
+
try {
|
|
543
|
+
const parsed = JSON.parse(await readFile(path.join(projectRoot, "package.json"), "utf8"));
|
|
544
|
+
return typeof parsed.packageManager === "string" ? parsed.packageManager : undefined;
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function runUpdateCommand(args) {
|
|
551
|
+
const positional = [];
|
|
552
|
+
for (const arg of args) {
|
|
553
|
+
if (arg === "--help" || arg === "-h") {
|
|
554
|
+
printUpdateHelp();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (arg.startsWith("--")) {
|
|
558
|
+
throw new Error(`unknown update option: ${arg}`);
|
|
559
|
+
}
|
|
560
|
+
positional.push(arg);
|
|
561
|
+
}
|
|
562
|
+
if (positional.length > 1) {
|
|
563
|
+
throw new Error("expected at most one project path for update");
|
|
564
|
+
}
|
|
565
|
+
const projectRoot = path.resolve(positional[0] ?? process.cwd());
|
|
566
|
+
const projectStats = await stat(projectRoot).catch(() => null);
|
|
567
|
+
if (!projectStats?.isDirectory()) {
|
|
568
|
+
throw new Error(`project path is not a directory: ${projectRoot}`);
|
|
569
|
+
}
|
|
570
|
+
await updateDefaultConfigDependencies(projectRoot);
|
|
571
|
+
}
|
|
493
572
|
async function parseArgs(args) {
|
|
494
573
|
const options = defaultCliOptions();
|
|
495
574
|
const positional = [];
|
|
@@ -636,6 +715,10 @@ function printHelp() {
|
|
|
636
715
|
|
|
637
716
|
Usage:
|
|
638
717
|
${CLI_NAME} [project-dir] [options]
|
|
718
|
+
${CLI_NAME} update [project-dir]
|
|
719
|
+
|
|
720
|
+
Commands:
|
|
721
|
+
update Update freestyle-sync and the default plugin packages.
|
|
639
722
|
|
|
640
723
|
Options:
|
|
641
724
|
--provider <name> Runtime provider: freestyle or apple-container. Defaults to freestyle.
|
|
@@ -666,6 +749,16 @@ Options:
|
|
|
666
749
|
-h, --help Show this help.
|
|
667
750
|
`);
|
|
668
751
|
}
|
|
752
|
+
function printUpdateHelp() {
|
|
753
|
+
console.log(`${CLI_NAME} update refreshes freestyle-sync and the default plugin packages.
|
|
754
|
+
|
|
755
|
+
Usage:
|
|
756
|
+
${CLI_NAME} update [project-dir]
|
|
757
|
+
|
|
758
|
+
The update command uses the project's package manager, including Bun when bun.lock,
|
|
759
|
+
bun.lockb, or packageManager: "bun@..." is present.
|
|
760
|
+
`);
|
|
761
|
+
}
|
|
669
762
|
function readOptionValue(args, index, option) {
|
|
670
763
|
const value = args[index];
|
|
671
764
|
if (!value || value.startsWith("--")) {
|
|
@@ -1703,13 +1796,20 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
|
|
|
1703
1796
|
const canRenderInlineProgress = process.stdout.isTTY;
|
|
1704
1797
|
const logEvery = Math.max(1, Math.ceil(chunkCount / 4));
|
|
1705
1798
|
const chunkManifest = [];
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1799
|
+
const maxUploadConcurrency = Math.max(ARCHIVE_UPLOAD_MIN_CONCURRENCY, Math.min(ARCHIVE_UPLOAD_MAX_CONCURRENCY, chunkCount));
|
|
1800
|
+
let targetUploadConcurrency = Math.min(ARCHIVE_UPLOAD_INITIAL_CONCURRENCY, maxUploadConcurrency);
|
|
1801
|
+
let successfulUploadsSinceIncrease = 0;
|
|
1802
|
+
let uploadedChunks = 0;
|
|
1803
|
+
const inFlightUploads = new Set();
|
|
1804
|
+
const reportUploadedChunk = () => {
|
|
1805
|
+
uploadedChunks += 1;
|
|
1806
|
+
if (targetUploadConcurrency < maxUploadConcurrency) {
|
|
1807
|
+
successfulUploadsSinceIncrease += 1;
|
|
1808
|
+
if (successfulUploadsSinceIncrease >= targetUploadConcurrency) {
|
|
1809
|
+
targetUploadConcurrency += 1;
|
|
1810
|
+
successfulUploadsSinceIncrease = 0;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1713
1813
|
if (chunkCount > 1) {
|
|
1714
1814
|
const progressMessage = `VM ${vmId}: uploaded ${uploadedChunks}/${chunkCount} ${label} archive chunks`;
|
|
1715
1815
|
if (canRenderInlineProgress) {
|
|
@@ -1722,7 +1822,38 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
|
|
|
1722
1822
|
console.log(progressMessage);
|
|
1723
1823
|
}
|
|
1724
1824
|
}
|
|
1825
|
+
};
|
|
1826
|
+
const waitForUploadedChunk = async () => {
|
|
1827
|
+
const result = await Promise.race(inFlightUploads);
|
|
1828
|
+
inFlightUploads.delete(result.upload);
|
|
1829
|
+
if (!result.ok) {
|
|
1830
|
+
throw result.error;
|
|
1831
|
+
}
|
|
1832
|
+
reportUploadedChunk();
|
|
1833
|
+
};
|
|
1834
|
+
const queueChunkUpload = (remotePath, content) => {
|
|
1835
|
+
let upload;
|
|
1836
|
+
upload = writeRemoteFileWithRetries(vm, remotePath, content, () => {
|
|
1837
|
+
targetUploadConcurrency = Math.max(ARCHIVE_UPLOAD_MIN_CONCURRENCY, Math.floor(targetUploadConcurrency / 2));
|
|
1838
|
+
successfulUploadsSinceIncrease = 0;
|
|
1839
|
+
})
|
|
1840
|
+
.then(() => ({ ok: true, upload }))
|
|
1841
|
+
.catch((error) => ({ ok: false, upload, error }));
|
|
1842
|
+
inFlightUploads.add(upload);
|
|
1843
|
+
};
|
|
1844
|
+
let index = 0;
|
|
1845
|
+
for await (const chunk of createReadStream(archivePath, { highWaterMark: ARCHIVE_CHUNK_BYTES })) {
|
|
1846
|
+
const chunkName = `${String(index).padStart(width, "0")}.chunk`;
|
|
1847
|
+
const content = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1848
|
+
chunkManifest.push(`${chunkName} ${content.length}`);
|
|
1849
|
+
queueChunkUpload(`${chunkDir}/${chunkName}`, content);
|
|
1725
1850
|
index += 1;
|
|
1851
|
+
while (inFlightUploads.size >= targetUploadConcurrency) {
|
|
1852
|
+
await waitForUploadedChunk();
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
while (inFlightUploads.size > 0) {
|
|
1856
|
+
await waitForUploadedChunk();
|
|
1726
1857
|
}
|
|
1727
1858
|
if (archiveSize > 0) {
|
|
1728
1859
|
await vm.fs.writeTextFile(`${chunkDir}/manifest`, `${chunkManifest.join("\n")}\n`);
|
|
@@ -1793,6 +1924,21 @@ async function uploadArchiveInChunks(vm, vmId, archivePath, remoteArchivePath, l
|
|
|
1793
1924
|
`rm -rf ${shellQuote(chunkDir)}`,
|
|
1794
1925
|
].join("\n"), 5 * 60 * MS_PER_SECOND);
|
|
1795
1926
|
}
|
|
1927
|
+
async function writeRemoteFileWithRetries(vm, remotePath, content, onRetry) {
|
|
1928
|
+
for (let attempt = 1; attempt <= ARCHIVE_UPLOAD_WRITE_ATTEMPTS; attempt += 1) {
|
|
1929
|
+
try {
|
|
1930
|
+
await vm.fs.writeFile(remotePath, content);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
catch (error) {
|
|
1934
|
+
if (attempt >= ARCHIVE_UPLOAD_WRITE_ATTEMPTS) {
|
|
1935
|
+
throw error;
|
|
1936
|
+
}
|
|
1937
|
+
onRetry();
|
|
1938
|
+
await delay(ARCHIVE_UPLOAD_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1796
1942
|
async function mkdirRemote(vm, directories) {
|
|
1797
1943
|
for (const chunk of chunkArray(directories, 50)) {
|
|
1798
1944
|
await checkedExec(vm, `mkdir -p ${chunk.map(shellQuote).join(" ")}`);
|
package/dist/src/plugin-api.d.ts
CHANGED
|
@@ -125,3 +125,5 @@ export type PushvmConfig = {
|
|
|
125
125
|
};
|
|
126
126
|
export declare function definePlugin(plugin: PushvmPlugin): PushvmPlugin;
|
|
127
127
|
export declare function defineConfig(config: PushvmConfig): PushvmConfig;
|
|
128
|
+
export declare function localCommandOutput(execFileAsync: ExecFileAsync, file: string, args: string[]): Promise<string | undefined>;
|
|
129
|
+
export declare function runRemoteScript(vm: RemoteVm, utils: PushvmPluginUtils, name: string, script: string, timeoutMs?: number): Promise<void>;
|
package/dist/src/plugin-api.js
CHANGED
|
@@ -4,3 +4,26 @@ export function definePlugin(plugin) {
|
|
|
4
4
|
export function defineConfig(config) {
|
|
5
5
|
return config;
|
|
6
6
|
}
|
|
7
|
+
export async function localCommandOutput(execFileAsync, file, args) {
|
|
8
|
+
try {
|
|
9
|
+
const { stdout, stderr } = await execFileAsync(file, args);
|
|
10
|
+
return `${stdout}\n${stderr}`.trim();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function runRemoteScript(vm, utils, name, script, timeoutMs = 20 * 60 * 1000) {
|
|
17
|
+
const safeName = name.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
18
|
+
const scriptPath = `/tmp/freestyle-sync-${safeName}-${Date.now()}.sh`;
|
|
19
|
+
await vm.fs.writeTextFile(scriptPath, script);
|
|
20
|
+
const result = await vm.exec({
|
|
21
|
+
command: `bash ${utils.shellQuote(scriptPath)}\nexit_code=$?\nrm -f ${utils.shellQuote(scriptPath)}\nexit "$exit_code"`,
|
|
22
|
+
timeoutMs,
|
|
23
|
+
});
|
|
24
|
+
if (result.statusCode && result.statusCode !== 0) {
|
|
25
|
+
throw new Error(result.stderr ?? result.stdout ?? `${name} setup failed with status ${result.statusCode}`);
|
|
26
|
+
}
|
|
27
|
+
if (result.stdout?.trim())
|
|
28
|
+
console.log(result.stdout.trim());
|
|
29
|
+
}
|