@maydotinc/s3-sync 0.1.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -107,7 +107,11 @@ function defaultConfig() {
107
107
  // src/workflow/generator.ts
108
108
  function generateWorkflow(config, packageSpecifier) {
109
109
  const envBlock = buildEnvBlock(config);
110
- const pathFilters = config.targets.map((t) => ` - '${t.directory}/**'`).join("\n");
110
+ const pathFilters = [
111
+ ...config.targets.map((t) => `${t.directory}/**`),
112
+ "s3-sync.json",
113
+ ".github/workflows/s3-sync.yml"
114
+ ].map((pattern) => ` - '${pattern}'`).join("\n");
111
115
  return `name: S3 Sync
112
116
 
113
117
  on:
@@ -115,6 +119,7 @@ on:
115
119
  branches: [${config.branch}]
116
120
  paths:
117
121
  ${pathFilters}
122
+ workflow_dispatch:
118
123
 
119
124
  jobs:
120
125
  sync:
@@ -519,26 +524,118 @@ function computeDiff(localFiles, remoteObjects, prefix, shouldDelete) {
519
524
  }
520
525
 
521
526
  // src/notifications/slack.ts
522
- async function sendSlackNotification(webhookUrl, message) {
527
+ var SLACK_GREEN = "#2EB67D";
528
+ var SLACK_ATTACHMENT_TEXT_LIMIT = 7e3;
529
+ var SLACK_ATTACHMENT_TITLE_LIMIT = 256;
530
+ var SLACK_FIELD_TITLE_LIMIT = 200;
531
+ var SLACK_FIELD_VALUE_LIMIT = 2e3;
532
+ function truncate(input, maxLength) {
533
+ if (input.length <= maxLength) return input;
534
+ return `${input.slice(0, maxLength - 13)}
535
+
536
+ ...[truncated]`;
537
+ }
538
+ function normalizePayload(payload) {
539
+ if (typeof payload === "string") {
540
+ return { message: payload };
541
+ }
542
+ return payload;
543
+ }
544
+ async function sendSlackNotification(webhookUrl, payload) {
545
+ const normalized = normalizePayload(payload);
546
+ const title = truncate(
547
+ normalized.title?.trim() || "S3 Sync Completed",
548
+ SLACK_ATTACHMENT_TITLE_LIMIT
549
+ );
550
+ const text = normalized.message ? truncate(normalized.message, SLACK_ATTACHMENT_TEXT_LIMIT) : void 0;
551
+ const fields = (normalized.fields ?? []).filter((field) => field.name.trim() && field.value.trim()).map((field) => ({
552
+ title: truncate(field.name.trim(), SLACK_FIELD_TITLE_LIMIT),
553
+ value: truncate(field.value.trim(), SLACK_FIELD_VALUE_LIMIT),
554
+ short: field.inline ?? false
555
+ }));
556
+ const attachment = {
557
+ color: SLACK_GREEN,
558
+ title,
559
+ mrkdwn_in: ["text", "fields"],
560
+ footer: "@maydotinc/s3-sync"
561
+ };
562
+ if (text) {
563
+ attachment.text = text;
564
+ }
565
+ if (fields.length > 0) {
566
+ attachment.fields = fields;
567
+ }
523
568
  const response = await fetch(webhookUrl, {
524
569
  method: "POST",
525
570
  headers: { "Content-Type": "application/json" },
526
- body: JSON.stringify({ text: message })
571
+ body: JSON.stringify({
572
+ text: normalized.title?.trim() || "S3 sync completed",
573
+ attachments: [attachment]
574
+ })
527
575
  });
528
576
  if (!response.ok) {
529
- throw new Error(`Slack notification failed: ${response.statusText}`);
577
+ const body = await response.text();
578
+ throw new Error(
579
+ `Slack notification failed (${response.status}): ${body || response.statusText}`
580
+ );
530
581
  }
531
582
  }
532
583
 
533
584
  // src/notifications/discord.ts
534
- async function sendDiscordNotification(webhookUrl, message) {
585
+ var DISCORD_GREEN = 3061373;
586
+ var DISCORD_EMBED_DESCRIPTION_LIMIT = 4096;
587
+ var DISCORD_EMBED_TITLE_LIMIT = 256;
588
+ var DISCORD_EMBED_FIELD_NAME_LIMIT = 256;
589
+ var DISCORD_EMBED_FIELD_VALUE_LIMIT = 1024;
590
+ var DISCORD_EMBED_FIELD_MAX = 25;
591
+ function truncate2(input, maxLength) {
592
+ if (input.length <= maxLength) return input;
593
+ return `${input.slice(0, maxLength - 13)}
594
+
595
+ ...[truncated]`;
596
+ }
597
+ function normalizePayload2(payload) {
598
+ if (typeof payload === "string") {
599
+ return { message: payload };
600
+ }
601
+ return payload;
602
+ }
603
+ async function sendDiscordNotification(webhookUrl, payload) {
604
+ const normalized = normalizePayload2(payload);
605
+ const title = truncate2(
606
+ normalized.title?.trim() || "S3 Sync Completed",
607
+ DISCORD_EMBED_TITLE_LIMIT
608
+ );
609
+ const description = normalized.message ? truncate2(normalized.message, DISCORD_EMBED_DESCRIPTION_LIMIT) : void 0;
610
+ const fields = (normalized.fields ?? []).filter((field) => field.name.trim() && field.value.trim()).slice(0, DISCORD_EMBED_FIELD_MAX).map((field) => ({
611
+ name: truncate2(field.name.trim(), DISCORD_EMBED_FIELD_NAME_LIMIT),
612
+ value: truncate2(field.value.trim(), DISCORD_EMBED_FIELD_VALUE_LIMIT),
613
+ inline: field.inline ?? false
614
+ }));
615
+ const embed = {
616
+ title,
617
+ color: DISCORD_GREEN,
618
+ footer: { text: "@maydotinc/s3-sync" },
619
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
620
+ };
621
+ if (description) {
622
+ embed.description = description;
623
+ }
624
+ if (fields.length > 0) {
625
+ embed.fields = fields;
626
+ }
535
627
  const response = await fetch(webhookUrl, {
536
628
  method: "POST",
537
629
  headers: { "Content-Type": "application/json" },
538
- body: JSON.stringify({ content: message })
630
+ body: JSON.stringify({
631
+ embeds: [embed]
632
+ })
539
633
  });
540
634
  if (!response.ok) {
541
- throw new Error(`Discord notification failed: ${response.statusText}`);
635
+ const body = await response.text();
636
+ throw new Error(
637
+ `Discord notification failed (${response.status}): ${body || response.statusText}`
638
+ );
542
639
  }
543
640
  }
544
641
 
@@ -558,7 +655,11 @@ async function syncCommand() {
558
655
  log.error("No sync targets configured. Run setup first.");
559
656
  process.exit(1);
560
657
  }
561
- const summaries = [];
658
+ let syncedTargets = 0;
659
+ const syncedTargetMappings = [];
660
+ let totalUploaded = 0;
661
+ let totalDeleted = 0;
662
+ let totalUnchanged = 0;
562
663
  let hasErrors = false;
563
664
  const globalEndpoint = process.env.S3_ENDPOINT?.trim() || void 0;
564
665
  const hasTargetSpecificEndpoints = config.targets.some(
@@ -577,15 +678,32 @@ async function syncCommand() {
577
678
  secretAccessKey,
578
679
  endpoint
579
680
  );
580
- summaries.push(buildTargetSummary(target, diff));
681
+ syncedTargets += 1;
682
+ syncedTargetMappings.push(buildTargetMapping(target));
683
+ totalUploaded += diff.toUpload.length;
684
+ totalDeleted += diff.toDelete.length;
685
+ totalUnchanged += diff.unchanged.length;
581
686
  } catch (err) {
582
687
  log.error(`[${target.directory}] ${err.message}`);
583
688
  hasErrors = true;
584
689
  }
585
690
  }
586
- if (summaries.length > 0) {
587
- const fullSummary = summaries.join("\n\n---\n\n");
588
- await notify(config, fullSummary);
691
+ if (syncedTargets > 0) {
692
+ const targetLabel = syncedTargets === 1 ? "target" : "targets";
693
+ const message = hasErrors ? `Synced ${syncedTargets} ${targetLabel}. Some targets failed.` : `Synced ${syncedTargets} ${targetLabel}.`;
694
+ await notify(config, {
695
+ title: hasErrors ? "S3 Sync Completed with Errors" : "S3 Sync Completed",
696
+ message,
697
+ fields: [
698
+ {
699
+ name: syncedTargetMappings.length === 1 ? "Target" : "Targets",
700
+ value: syncedTargetMappings.map((mapping) => `- ${mapping}`).join("\n")
701
+ },
702
+ { name: "Uploaded", value: String(totalUploaded), inline: true },
703
+ { name: "Deleted", value: String(totalDeleted), inline: true },
704
+ { name: "Unchanged", value: String(totalUnchanged), inline: true }
705
+ ]
706
+ });
589
707
  }
590
708
  if (hasErrors) {
591
709
  process.exit(1);
@@ -651,30 +769,16 @@ function resolveEndpoint(targetEndpoint, globalEndpoint, hasTargetSpecificEndpoi
651
769
  if (!hasTargetSpecificEndpoints) return globalEndpoint;
652
770
  return void 0;
653
771
  }
654
- function buildTargetSummary(target, diff) {
655
- const lines = [
656
- `*${target.directory}/ \u2192 s3://${target.bucket}/${target.prefix}*`,
657
- `Uploaded: ${diff.toUpload.length}`,
658
- `Deleted: ${diff.toDelete.length}`,
659
- `Unchanged: ${diff.unchanged.length}`
660
- ];
661
- if (diff.toUpload.length > 0) {
662
- const fileList = diff.toUpload.slice(0, 10).map((f) => ` \u2022 ${f.relativePath}`).join("\n");
663
- lines.push(`
664
- Uploaded files:
665
- ${fileList}`);
666
- if (diff.toUpload.length > 10) {
667
- lines.push(` ...and ${diff.toUpload.length - 10} more`);
668
- }
669
- }
670
- return lines.join("\n");
772
+ function buildTargetMapping(target) {
773
+ const remotePath = target.prefix.trim() ? `${target.bucket}/${target.prefix}` : `${target.bucket}/`;
774
+ return `${target.directory}/ -> ${remotePath}`;
671
775
  }
672
- async function notify(config, message) {
776
+ async function notify(config, payload) {
673
777
  if (config.notifications?.slack) {
674
778
  const webhookUrl = process.env.SLACK_WEBHOOK_URL;
675
779
  if (webhookUrl) {
676
780
  try {
677
- await sendSlackNotification(webhookUrl, message);
781
+ await sendSlackNotification(webhookUrl, payload);
678
782
  log.success("Slack notification sent");
679
783
  } catch (err) {
680
784
  log.warn(`Slack notification failed: ${err.message}`);
@@ -685,7 +789,7 @@ async function notify(config, message) {
685
789
  const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
686
790
  if (webhookUrl) {
687
791
  try {
688
- await sendDiscordNotification(webhookUrl, message);
792
+ await sendDiscordNotification(webhookUrl, payload);
689
793
  log.success("Discord notification sent");
690
794
  } catch (err) {
691
795
  log.warn(`Discord notification failed: ${err.message}`);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/setup.ts","../src/utils/config.ts","../src/workflow/generator.ts","../src/utils/logger.ts","../src/utils/package-meta.ts","../src/commands/sync.ts","../src/core/fingerprint.ts","../src/core/s3-client.ts","../src/core/diff.ts","../src/notifications/slack.ts","../src/notifications/discord.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { setupCommand } from \"./commands/setup.js\";\nimport { syncCommand } from \"./commands/sync.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"s3-sync\")\n .description(\"Sync local directories to S3-compatible buckets via GitHub Actions\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"sync\")\n .description(\"Execute sync for all configured targets\")\n .action(async () => {\n try {\n await syncCommand();\n } catch (err: any) {\n console.error(err.message);\n process.exit(1);\n }\n });\n\nprogram\n .argument(\"<directory>\", \"Local directory to sync (e.g. public, apps/web/dist)\")\n .argument(\"sync\", \"Generate workflow and config for syncing this directory\")\n .option(\"--bucket <name>\", \"S3 bucket name\")\n .option(\"--region <region>\", \"AWS region\")\n .option(\"--endpoint <url>\", \"Custom S3-compatible endpoint\")\n .option(\"--prefix <prefix>\", \"Path prefix in bucket\")\n .option(\"--branch <branch>\", \"Git branch to trigger on\")\n .option(\"--delete\", \"Delete remote files not present locally\")\n .option(\"--no-delete\", \"Keep remote files not present locally\")\n .option(\"--slack\", \"Enable Slack notifications\")\n .option(\"--discord\", \"Enable Discord notifications\")\n .action(async (directory: string, _syncArg: string, options: any) => {\n try {\n await setupCommand(directory, {\n bucket: options.bucket,\n region: options.region,\n endpoint: options.endpoint,\n prefix: options.prefix,\n branch: options.branch,\n delete: options.delete === true ? true : options.delete === false ? false : undefined,\n slack: options.slack,\n discord: options.discord,\n });\n } catch (err: any) {\n console.error(err.message);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport inquirer from \"inquirer\";\nimport {\n defaultConfig,\n defaultTarget,\n writeConfig,\n readConfig,\n configExists,\n type S3SyncConfig,\n type SyncTarget,\n} from \"../utils/config.js\";\nimport { generateWorkflow } from \"../workflow/generator.js\";\nimport { log } from \"../utils/logger.js\";\nimport { getCurrentPackageSpecifier } from \"../utils/package-meta.js\";\n\ninterface SetupFlags {\n bucket?: string;\n region?: string;\n endpoint?: string;\n prefix?: string;\n branch?: string;\n delete?: boolean;\n slack?: boolean;\n discord?: boolean;\n}\n\nexport async function setupCommand(\n directory: string,\n flags: SetupFlags\n): Promise<void> {\n log.heading(\"s3-sync setup\");\n\n let config: S3SyncConfig;\n let isAdding = false;\n\n if (configExists()) {\n const existing = await readConfig();\n const alreadyHasDir = existing.targets.some(\n (t) => t.directory === directory\n );\n\n if (alreadyHasDir) {\n const { overwrite } = await inquirer.prompt([\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `Target \"${directory}\" already exists. Overwrite it?`,\n default: false,\n },\n ]);\n if (!overwrite) {\n log.info(\"Setup cancelled.\");\n return;\n }\n existing.targets = existing.targets.filter(\n (t) => t.directory !== directory\n );\n } else {\n log.info(\n `Existing config found with ${existing.targets.length} target(s). Adding \"${directory}\".`\n );\n isAdding = true;\n }\n config = existing;\n } else {\n config = defaultConfig();\n }\n\n const dirPath = path.resolve(directory);\n if (!existsSync(dirPath)) {\n log.warn(\n `Directory \"${directory}\" doesn't exist yet — that's okay, it will be created later.`\n );\n }\n\n const target = await gatherTarget(directory, flags);\n config.targets.push(target);\n\n if (!isAdding) {\n await gatherSharedConfig(config, flags);\n }\n\n await writeConfig(config);\n log.success(\"Updated s3-sync.json\");\n\n const workflowDir = path.join(process.cwd(), \".github\", \"workflows\");\n await mkdir(workflowDir, { recursive: true });\n\n const workflowPath = path.join(workflowDir, \"s3-sync.yml\");\n const packageSpecifier = getCurrentPackageSpecifier();\n const workflowContent = generateWorkflow(config, packageSpecifier);\n await writeFile(workflowPath, workflowContent, \"utf-8\");\n log.success(\"Updated .github/workflows/s3-sync.yml\");\n\n printSecretInstructions(config);\n}\n\nasync function gatherTarget(\n directory: string,\n flags: SetupFlags\n): Promise<SyncTarget> {\n const target = defaultTarget();\n target.directory = directory;\n\n const questions: any[] = [];\n\n if (flags.bucket === undefined) {\n questions.push({\n type: \"input\",\n name: \"bucket\",\n message: `[${directory}] S3 bucket name:`,\n validate: (v: string) => (v.trim() ? true : \"Bucket name is required\"),\n });\n } else {\n target.bucket = flags.bucket;\n }\n\n if (flags.region === undefined) {\n questions.push({\n type: \"input\",\n name: \"region\",\n message: `[${directory}] AWS region:`,\n default: \"us-east-1\",\n });\n } else {\n target.region = flags.region;\n }\n\n if (flags.endpoint === undefined) {\n questions.push({\n type: \"input\",\n name: \"endpoint\",\n message: `[${directory}] Custom S3 endpoint (leave blank for AWS):`,\n default: \"\",\n });\n } else {\n target.endpoint = flags.endpoint;\n }\n\n if (flags.prefix === undefined) {\n questions.push({\n type: \"input\",\n name: \"prefix\",\n message: `[${directory}] Path prefix (leave blank for root):`,\n default: \"\",\n });\n } else {\n target.prefix = flags.prefix;\n }\n\n if (flags.delete === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"delete\",\n message: `[${directory}] Delete files from S3 that no longer exist locally?`,\n default: false,\n });\n } else {\n target.delete = flags.delete;\n }\n\n if (questions.length > 0) {\n const answers = await inquirer.prompt(questions);\n if (answers.bucket !== undefined) target.bucket = answers.bucket;\n if (answers.region !== undefined) target.region = answers.region;\n if (answers.endpoint !== undefined) target.endpoint = answers.endpoint;\n if (answers.prefix !== undefined) target.prefix = answers.prefix;\n if (answers.delete !== undefined) target.delete = answers.delete;\n }\n\n return target;\n}\n\nasync function gatherSharedConfig(\n config: S3SyncConfig,\n flags: SetupFlags\n): Promise<void> {\n const questions: any[] = [];\n\n if (flags.branch === undefined) {\n questions.push({\n type: \"input\",\n name: \"branch\",\n message: \"Branch to trigger sync on:\",\n default: \"main\",\n });\n } else {\n config.branch = flags.branch;\n }\n\n if (flags.slack === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"slack\",\n message: \"Enable Slack notifications?\",\n default: false,\n });\n } else {\n config.notifications.slack = flags.slack;\n }\n\n if (flags.discord === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"discord\",\n message: \"Enable Discord notifications?\",\n default: false,\n });\n } else {\n config.notifications.discord = flags.discord;\n }\n\n if (questions.length > 0) {\n const answers = await inquirer.prompt(questions);\n if (answers.branch !== undefined) config.branch = answers.branch;\n if (answers.slack !== undefined) config.notifications.slack = answers.slack;\n if (answers.discord !== undefined)\n config.notifications.discord = answers.discord;\n }\n}\n\nfunction printSecretInstructions(config: S3SyncConfig): void {\n log.heading(\"Next steps\");\n log.info(\"Add these secrets to your GitHub repository:\");\n log.dim(\n \" Settings → Secrets and variables → Actions → New repository secret\\n\"\n );\n\n const secrets = [\n [\"S3_ACCESS_KEY_ID\", \"Your S3 access key\"],\n [\"S3_SECRET_ACCESS_KEY\", \"Your S3 secret key\"],\n ];\n\n if (config.notifications.slack) {\n secrets.push([\"SLACK_WEBHOOK_URL\", \"Slack incoming webhook URL\"]);\n }\n\n if (config.notifications.discord) {\n secrets.push([\"DISCORD_WEBHOOK_URL\", \"Discord webhook URL\"]);\n }\n\n for (const [name, desc] of secrets) {\n log.dim(` • ${name} — ${desc}`);\n }\n\n console.log();\n log.heading(\"Configured targets\");\n for (const t of config.targets) {\n log.info(` ${t.directory}/ → s3://${t.bucket}/${t.prefix}`);\n }\n\n console.log();\n log.success(\n `Push to \"${config.branch}\" to trigger a sync.`\n );\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nexport interface SyncTarget {\n directory: string;\n bucket: string;\n region: string;\n endpoint: string;\n prefix: string;\n delete: boolean;\n}\n\nexport interface S3SyncConfig {\n targets: SyncTarget[];\n branch: string;\n notifications: {\n slack: boolean;\n discord: boolean;\n };\n}\n\nconst CONFIG_FILE = \"s3-sync.json\";\n\nconst syncTargetSchema: z.ZodType<SyncTarget> = z.object({\n directory: z.string().min(1),\n bucket: z.string().min(1),\n region: z.string().min(1),\n endpoint: z.string().default(\"\"),\n prefix: z.string().default(\"\"),\n delete: z.boolean().default(false),\n});\n\nconst notificationsSchema: z.ZodType<S3SyncConfig[\"notifications\"]> = z.object({\n slack: z.boolean().default(false),\n discord: z.boolean().default(false),\n});\n\nconst configSchema: z.ZodType<S3SyncConfig> = z.object({\n targets: z.array(syncTargetSchema).default([]),\n branch: z.string().min(1).default(\"main\"),\n notifications: notificationsSchema.default({ slack: false, discord: false }),\n});\n\nconst legacyConfigSchema = z.object({\n directory: z.string().min(1),\n bucket: z.string().min(1),\n region: z.string().min(1),\n endpoint: z.string().default(\"\"),\n prefix: z.string().default(\"\"),\n branch: z.string().min(1).default(\"main\"),\n delete: z.boolean().default(false),\n notifications: notificationsSchema.default({ slack: false, discord: false }),\n});\n\nexport function getConfigPath(cwd: string = process.cwd()): string {\n return path.join(cwd, CONFIG_FILE);\n}\n\nexport function configExists(cwd: string = process.cwd()): boolean {\n return existsSync(getConfigPath(cwd));\n}\n\nexport async function readConfig(\n cwd: string = process.cwd()\n): Promise<S3SyncConfig> {\n const configPath = getConfigPath(cwd);\n const raw = JSON.parse(await readFile(configPath, \"utf-8\"));\n const parsedConfig = configSchema.safeParse(raw);\n if (parsedConfig.success) return parsedConfig.data;\n\n const parsedLegacyConfig = legacyConfigSchema.safeParse(raw);\n if (!parsedLegacyConfig.success) {\n const issues = parsedConfig.error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"root\";\n return `${path}: ${issue.message}`;\n })\n .join(\"; \");\n throw new Error(`Invalid ${CONFIG_FILE}: ${issues}`);\n }\n\n const legacy = parsedLegacyConfig.data;\n return {\n targets: [\n {\n directory: legacy.directory,\n bucket: legacy.bucket,\n region: legacy.region,\n endpoint: legacy.endpoint,\n prefix: legacy.prefix,\n delete: legacy.delete,\n },\n ],\n branch: legacy.branch,\n notifications: legacy.notifications,\n };\n}\n\nexport async function writeConfig(\n config: S3SyncConfig,\n cwd: string = process.cwd()\n): Promise<void> {\n const configPath = getConfigPath(cwd);\n const validatedConfig = configSchema.parse(config);\n await writeFile(\n configPath,\n JSON.stringify(validatedConfig, null, 2) + \"\\n\",\n \"utf-8\"\n );\n}\n\nexport function defaultTarget(): SyncTarget {\n return {\n directory: \"public\",\n bucket: \"\",\n region: \"us-east-1\",\n endpoint: \"\",\n prefix: \"\",\n delete: false,\n };\n}\n\nexport function defaultConfig(): S3SyncConfig {\n return {\n targets: [],\n branch: \"main\",\n notifications: { slack: false, discord: false },\n };\n}\n","import type { S3SyncConfig } from \"../utils/config.js\";\n\nexport function generateWorkflow(\n config: S3SyncConfig,\n packageSpecifier: string\n): string {\n const envBlock = buildEnvBlock(config);\n const pathFilters = config.targets\n .map((t) => ` - '${t.directory}/**'`)\n .join(\"\\n\");\n\n return `name: S3 Sync\n\non:\n push:\n branches: [${config.branch}]\n paths:\n${pathFilters}\n\njobs:\n sync:\n name: Sync to S3\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n\n - name: Sync files\n run: npx --yes ${packageSpecifier} sync\n${envBlock}\n`;\n}\n\nfunction buildEnvBlock(config: S3SyncConfig): string {\n const lines = [\n \" env:\",\n \" S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}\",\n \" S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}\",\n ];\n\n if (config.notifications.slack) {\n lines.push(\n \" SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\"\n );\n }\n\n if (config.notifications.discord) {\n lines.push(\n \" DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}\"\n );\n }\n\n return lines.join(\"\\n\");\n}\n","import chalk from \"chalk\";\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue(\"ℹ\"), msg),\n success: (msg: string) => console.log(chalk.green(\"✓\"), msg),\n warn: (msg: string) => console.log(chalk.yellow(\"⚠\"), msg),\n error: (msg: string) => console.error(chalk.red(\"✗\"), msg),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n heading: (msg: string) => console.log(chalk.bold.cyan(`\\n${msg}\\n`)),\n};\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\ninterface PackageMeta {\n name?: string;\n version?: string;\n}\n\nfunction findPackageJson(startDirectory: string): string | undefined {\n let currentDirectory = startDirectory;\n while (true) {\n const candidate = path.join(currentDirectory, \"package.json\");\n if (existsSync(candidate)) return candidate;\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) return undefined;\n currentDirectory = parentDirectory;\n }\n}\n\nexport function getCurrentPackageSpecifier(\n fallbackName = \"@maydotinc/s3-sync\"\n): string {\n const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageJsonPath = findPackageJson(currentFileDirectory);\n if (!packageJsonPath) return fallbackName;\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as PackageMeta;\n const packageName = parsed.name?.trim() || fallbackName;\n const packageVersion = parsed.version?.trim();\n return packageVersion ? `${packageName}@${packageVersion}` : packageName;\n } catch {\n return fallbackName;\n }\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { readConfig, type SyncTarget } from \"../utils/config.js\";\nimport { log } from \"../utils/logger.js\";\nimport { fingerprintDirectory } from \"../core/fingerprint.js\";\nimport {\n createS3,\n listAllObjects,\n uploadFile,\n deleteObject,\n} from \"../core/s3-client.js\";\nimport { computeDiff, type DiffResult } from \"../core/diff.js\";\nimport { sendSlackNotification } from \"../notifications/slack.js\";\nimport { sendDiscordNotification } from \"../notifications/discord.js\";\n\nexport async function syncCommand(): Promise<void> {\n log.heading(\"s3-sync\");\n\n const config = await readConfig();\n\n const accessKeyId = process.env.S3_ACCESS_KEY_ID;\n const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY;\n\n if (!accessKeyId || !secretAccessKey) {\n log.error(\n \"Missing S3_ACCESS_KEY_ID or S3_SECRET_ACCESS_KEY environment variables.\"\n );\n process.exit(1);\n }\n\n if (config.targets.length === 0) {\n log.error(\"No sync targets configured. Run setup first.\");\n process.exit(1);\n }\n\n const summaries: string[] = [];\n let hasErrors = false;\n const globalEndpoint = process.env.S3_ENDPOINT?.trim() || undefined;\n const hasTargetSpecificEndpoints = config.targets.some(\n (target) => target.endpoint.trim().length > 0\n );\n\n for (const target of config.targets) {\n try {\n const endpoint = resolveEndpoint(\n target.endpoint,\n globalEndpoint,\n hasTargetSpecificEndpoints\n );\n const diff = await syncTarget(\n target,\n accessKeyId,\n secretAccessKey,\n endpoint\n );\n summaries.push(buildTargetSummary(target, diff));\n } catch (err: any) {\n log.error(`[${target.directory}] ${err.message}`);\n hasErrors = true;\n }\n }\n\n if (summaries.length > 0) {\n const fullSummary = summaries.join(\"\\n\\n---\\n\\n\");\n await notify(config, fullSummary);\n }\n\n if (hasErrors) {\n process.exit(1);\n }\n\n log.success(\"All targets synced.\");\n}\n\nasync function syncTarget(\n target: SyncTarget,\n accessKeyId: string,\n secretAccessKey: string,\n endpoint: string | undefined\n): Promise<DiffResult> {\n log.heading(`Syncing ${target.directory}/ → s3://${target.bucket}/${target.prefix}`);\n\n const directory = path.resolve(target.directory);\n if (!existsSync(directory)) {\n throw new Error(`Directory \"${target.directory}\" does not exist.`);\n }\n\n const client = createS3({\n bucket: target.bucket,\n region: target.region,\n endpoint,\n accessKeyId,\n secretAccessKey,\n });\n\n log.info(`Scanning ${target.directory}/...`);\n const localFiles = await fingerprintDirectory(directory);\n log.info(`Found ${localFiles.length} local files`);\n\n log.info(`Listing objects in s3://${target.bucket}/${target.prefix}...`);\n const remoteObjects = await listAllObjects(\n client,\n target.bucket,\n target.prefix\n );\n log.info(`Found ${remoteObjects.length} remote objects`);\n\n const diff = computeDiff(\n localFiles,\n remoteObjects,\n target.prefix,\n target.delete\n );\n\n log.info(\n `${diff.toUpload.length} to upload, ${diff.toDelete.length} to delete, ${diff.unchanged.length} unchanged`\n );\n\n if (diff.toUpload.length === 0 && diff.toDelete.length === 0) {\n log.success(`[${target.directory}] Already in sync.`);\n return diff;\n }\n\n for (const file of diff.toUpload) {\n const key = target.prefix\n ? `${target.prefix}/${file.relativePath}`\n : file.relativePath;\n log.dim(` uploading ${key}`);\n await uploadFile(client, target.bucket, key, file.absolutePath);\n }\n\n if (diff.toUpload.length > 0) {\n log.success(`[${target.directory}] Uploaded ${diff.toUpload.length} files`);\n }\n\n for (const obj of diff.toDelete) {\n log.dim(` deleting ${obj.key}`);\n await deleteObject(client, target.bucket, obj.key);\n }\n\n if (diff.toDelete.length > 0) {\n log.success(`[${target.directory}] Deleted ${diff.toDelete.length} files`);\n }\n\n return diff;\n}\n\nfunction resolveEndpoint(\n targetEndpoint: string,\n globalEndpoint: string | undefined,\n hasTargetSpecificEndpoints: boolean\n): string | undefined {\n const trimmedTargetEndpoint = targetEndpoint.trim();\n if (trimmedTargetEndpoint) return trimmedTargetEndpoint;\n if (!hasTargetSpecificEndpoints) return globalEndpoint;\n return undefined;\n}\n\nfunction buildTargetSummary(target: SyncTarget, diff: DiffResult): string {\n const lines = [\n `*${target.directory}/ → s3://${target.bucket}/${target.prefix}*`,\n `Uploaded: ${diff.toUpload.length}`,\n `Deleted: ${diff.toDelete.length}`,\n `Unchanged: ${diff.unchanged.length}`,\n ];\n\n if (diff.toUpload.length > 0) {\n const fileList = diff.toUpload\n .slice(0, 10)\n .map((f) => ` • ${f.relativePath}`)\n .join(\"\\n\");\n lines.push(`\\nUploaded files:\\n${fileList}`);\n if (diff.toUpload.length > 10) {\n lines.push(` ...and ${diff.toUpload.length - 10} more`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nasync function notify(config: any, message: string): Promise<void> {\n if (config.notifications?.slack) {\n const webhookUrl = process.env.SLACK_WEBHOOK_URL;\n if (webhookUrl) {\n try {\n await sendSlackNotification(webhookUrl, message);\n log.success(\"Slack notification sent\");\n } catch (err: any) {\n log.warn(`Slack notification failed: ${err.message}`);\n }\n }\n }\n\n if (config.notifications?.discord) {\n const webhookUrl = process.env.DISCORD_WEBHOOK_URL;\n if (webhookUrl) {\n try {\n await sendDiscordNotification(webhookUrl, message);\n log.success(\"Discord notification sent\");\n } catch (err: any) {\n log.warn(`Discord notification failed: ${err.message}`);\n }\n }\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { glob } from \"glob\";\n\nexport interface LocalFile {\n relativePath: string;\n absolutePath: string;\n md5: string;\n size: number;\n}\n\nexport async function fingerprintDirectory(\n directory: string\n): Promise<LocalFile[]> {\n const absoluteDir = path.resolve(directory);\n const files = await glob(\"**/*\", {\n cwd: absoluteDir,\n nodir: true,\n dot: false,\n });\n\n const results: LocalFile[] = [];\n\n await Promise.all(\n files.map(async (relativePath) => {\n const absolutePath = path.join(absoluteDir, relativePath);\n const content = await readFile(absolutePath);\n const md5 = createHash(\"md5\").update(content).digest(\"hex\");\n\n results.push({\n relativePath: relativePath.replace(/\\\\/g, \"/\"),\n absolutePath,\n md5,\n size: content.length,\n });\n })\n );\n\n return results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));\n}\n","import {\n S3Client,\n ListObjectsV2Command,\n type ListObjectsV2CommandOutput,\n PutObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { readFile } from \"node:fs/promises\";\nimport mime from \"mime-types\";\n\nexport interface S3Object {\n key: string;\n etag: string;\n size: number;\n}\n\nexport interface S3Config {\n bucket: string;\n region: string;\n endpoint?: string;\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport function createS3(config: S3Config): S3Client {\n return new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n ...(config.endpoint && {\n endpoint: config.endpoint,\n forcePathStyle: true,\n }),\n });\n}\n\nexport async function listAllObjects(\n client: S3Client,\n bucket: string,\n prefix: string\n): Promise<S3Object[]> {\n const objects: S3Object[] = [];\n let continuationToken: string | undefined;\n\n do {\n const command = new ListObjectsV2Command({\n Bucket: bucket,\n Prefix: prefix || undefined,\n ContinuationToken: continuationToken,\n });\n\n const response: ListObjectsV2CommandOutput = await client.send(command);\n\n if (response.Contents) {\n for (const obj of response.Contents) {\n if (obj.Key && obj.ETag && obj.Size !== undefined) {\n objects.push({\n key: obj.Key,\n etag: obj.ETag.replace(/\"/g, \"\"),\n size: obj.Size,\n });\n }\n }\n }\n\n continuationToken = response.IsTruncated\n ? response.NextContinuationToken\n : undefined;\n } while (continuationToken);\n\n return objects;\n}\n\nexport async function uploadFile(\n client: S3Client,\n bucket: string,\n key: string,\n filePath: string\n): Promise<void> {\n const body = await readFile(filePath);\n const contentType = mime.lookup(filePath) || \"application/octet-stream\";\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n })\n );\n}\n\nexport async function deleteObject(\n client: S3Client,\n bucket: string,\n key: string\n): Promise<void> {\n await client.send(\n new DeleteObjectCommand({\n Bucket: bucket,\n Key: key,\n })\n );\n}\n","import type { LocalFile } from \"./fingerprint.js\";\nimport type { S3Object } from \"./s3-client.js\";\n\nexport interface DiffResult {\n toUpload: LocalFile[];\n toDelete: S3Object[];\n unchanged: LocalFile[];\n}\n\nexport function computeDiff(\n localFiles: LocalFile[],\n remoteObjects: S3Object[],\n prefix: string,\n shouldDelete: boolean\n): DiffResult {\n const remoteByKey = new Map<string, S3Object>();\n for (const obj of remoteObjects) {\n remoteByKey.set(obj.key, obj);\n }\n\n const toUpload: LocalFile[] = [];\n const unchanged: LocalFile[] = [];\n const localKeys = new Set<string>();\n\n for (const file of localFiles) {\n const key = prefix ? `${prefix}/${file.relativePath}` : file.relativePath;\n localKeys.add(key);\n\n const remote = remoteByKey.get(key);\n\n if (!remote || remote.etag !== file.md5) {\n toUpload.push(file);\n } else {\n unchanged.push(file);\n }\n }\n\n const toDelete: S3Object[] = [];\n if (shouldDelete) {\n for (const obj of remoteObjects) {\n if (!localKeys.has(obj.key)) {\n toDelete.push(obj);\n }\n }\n }\n\n return { toUpload, toDelete, unchanged };\n}\n","export async function sendSlackNotification(\n webhookUrl: string,\n message: string\n): Promise<void> {\n const response = await fetch(webhookUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text: message }),\n });\n\n if (!response.ok) {\n throw new Error(`Slack notification failed: ${response.statusText}`);\n }\n}\n","export async function sendDiscordNotification(\n webhookUrl: string,\n message: string\n): Promise<void> {\n const response = await fetch(webhookUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ content: message }),\n });\n\n if (!response.ok) {\n throw new Error(`Discord notification failed: ${response.statusText}`);\n }\n}\n"],"mappings":"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","names":["writeFile","existsSync","path","path","existsSync","path","path","existsSync","writeFile","existsSync","path","readFile","path","readFile","path","existsSync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/setup.ts","../src/utils/config.ts","../src/workflow/generator.ts","../src/utils/logger.ts","../src/utils/package-meta.ts","../src/commands/sync.ts","../src/core/fingerprint.ts","../src/core/s3-client.ts","../src/core/diff.ts","../src/notifications/slack.ts","../src/notifications/discord.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { setupCommand } from \"./commands/setup.js\";\nimport { syncCommand } from \"./commands/sync.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"s3-sync\")\n .description(\"Sync local directories to S3-compatible buckets via GitHub Actions\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"sync\")\n .description(\"Execute sync for all configured targets\")\n .action(async () => {\n try {\n await syncCommand();\n } catch (err: any) {\n console.error(err.message);\n process.exit(1);\n }\n });\n\nprogram\n .argument(\"<directory>\", \"Local directory to sync (e.g. public, apps/web/dist)\")\n .argument(\"sync\", \"Generate workflow and config for syncing this directory\")\n .option(\"--bucket <name>\", \"S3 bucket name\")\n .option(\"--region <region>\", \"AWS region\")\n .option(\"--endpoint <url>\", \"Custom S3-compatible endpoint\")\n .option(\"--prefix <prefix>\", \"Path prefix in bucket\")\n .option(\"--branch <branch>\", \"Git branch to trigger on\")\n .option(\"--delete\", \"Delete remote files not present locally\")\n .option(\"--no-delete\", \"Keep remote files not present locally\")\n .option(\"--slack\", \"Enable Slack notifications\")\n .option(\"--discord\", \"Enable Discord notifications\")\n .action(async (directory: string, _syncArg: string, options: any) => {\n try {\n await setupCommand(directory, {\n bucket: options.bucket,\n region: options.region,\n endpoint: options.endpoint,\n prefix: options.prefix,\n branch: options.branch,\n delete: options.delete === true ? true : options.delete === false ? false : undefined,\n slack: options.slack,\n discord: options.discord,\n });\n } catch (err: any) {\n console.error(err.message);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport inquirer from \"inquirer\";\nimport {\n defaultConfig,\n defaultTarget,\n writeConfig,\n readConfig,\n configExists,\n type S3SyncConfig,\n type SyncTarget,\n} from \"../utils/config.js\";\nimport { generateWorkflow } from \"../workflow/generator.js\";\nimport { log } from \"../utils/logger.js\";\nimport { getCurrentPackageSpecifier } from \"../utils/package-meta.js\";\n\ninterface SetupFlags {\n bucket?: string;\n region?: string;\n endpoint?: string;\n prefix?: string;\n branch?: string;\n delete?: boolean;\n slack?: boolean;\n discord?: boolean;\n}\n\nexport async function setupCommand(\n directory: string,\n flags: SetupFlags\n): Promise<void> {\n log.heading(\"s3-sync setup\");\n\n let config: S3SyncConfig;\n let isAdding = false;\n\n if (configExists()) {\n const existing = await readConfig();\n const alreadyHasDir = existing.targets.some(\n (t) => t.directory === directory\n );\n\n if (alreadyHasDir) {\n const { overwrite } = await inquirer.prompt([\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `Target \"${directory}\" already exists. Overwrite it?`,\n default: false,\n },\n ]);\n if (!overwrite) {\n log.info(\"Setup cancelled.\");\n return;\n }\n existing.targets = existing.targets.filter(\n (t) => t.directory !== directory\n );\n } else {\n log.info(\n `Existing config found with ${existing.targets.length} target(s). Adding \"${directory}\".`\n );\n isAdding = true;\n }\n config = existing;\n } else {\n config = defaultConfig();\n }\n\n const dirPath = path.resolve(directory);\n if (!existsSync(dirPath)) {\n log.warn(\n `Directory \"${directory}\" doesn't exist yet — that's okay, it will be created later.`\n );\n }\n\n const target = await gatherTarget(directory, flags);\n config.targets.push(target);\n\n if (!isAdding) {\n await gatherSharedConfig(config, flags);\n }\n\n await writeConfig(config);\n log.success(\"Updated s3-sync.json\");\n\n const workflowDir = path.join(process.cwd(), \".github\", \"workflows\");\n await mkdir(workflowDir, { recursive: true });\n\n const workflowPath = path.join(workflowDir, \"s3-sync.yml\");\n const packageSpecifier = getCurrentPackageSpecifier();\n const workflowContent = generateWorkflow(config, packageSpecifier);\n await writeFile(workflowPath, workflowContent, \"utf-8\");\n log.success(\"Updated .github/workflows/s3-sync.yml\");\n\n printSecretInstructions(config);\n}\n\nasync function gatherTarget(\n directory: string,\n flags: SetupFlags\n): Promise<SyncTarget> {\n const target = defaultTarget();\n target.directory = directory;\n\n const questions: any[] = [];\n\n if (flags.bucket === undefined) {\n questions.push({\n type: \"input\",\n name: \"bucket\",\n message: `[${directory}] S3 bucket name:`,\n validate: (v: string) => (v.trim() ? true : \"Bucket name is required\"),\n });\n } else {\n target.bucket = flags.bucket;\n }\n\n if (flags.region === undefined) {\n questions.push({\n type: \"input\",\n name: \"region\",\n message: `[${directory}] AWS region:`,\n default: \"us-east-1\",\n });\n } else {\n target.region = flags.region;\n }\n\n if (flags.endpoint === undefined) {\n questions.push({\n type: \"input\",\n name: \"endpoint\",\n message: `[${directory}] Custom S3 endpoint (leave blank for AWS):`,\n default: \"\",\n });\n } else {\n target.endpoint = flags.endpoint;\n }\n\n if (flags.prefix === undefined) {\n questions.push({\n type: \"input\",\n name: \"prefix\",\n message: `[${directory}] Path prefix (leave blank for root):`,\n default: \"\",\n });\n } else {\n target.prefix = flags.prefix;\n }\n\n if (flags.delete === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"delete\",\n message: `[${directory}] Delete files from S3 that no longer exist locally?`,\n default: false,\n });\n } else {\n target.delete = flags.delete;\n }\n\n if (questions.length > 0) {\n const answers = await inquirer.prompt(questions);\n if (answers.bucket !== undefined) target.bucket = answers.bucket;\n if (answers.region !== undefined) target.region = answers.region;\n if (answers.endpoint !== undefined) target.endpoint = answers.endpoint;\n if (answers.prefix !== undefined) target.prefix = answers.prefix;\n if (answers.delete !== undefined) target.delete = answers.delete;\n }\n\n return target;\n}\n\nasync function gatherSharedConfig(\n config: S3SyncConfig,\n flags: SetupFlags\n): Promise<void> {\n const questions: any[] = [];\n\n if (flags.branch === undefined) {\n questions.push({\n type: \"input\",\n name: \"branch\",\n message: \"Branch to trigger sync on:\",\n default: \"main\",\n });\n } else {\n config.branch = flags.branch;\n }\n\n if (flags.slack === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"slack\",\n message: \"Enable Slack notifications?\",\n default: false,\n });\n } else {\n config.notifications.slack = flags.slack;\n }\n\n if (flags.discord === undefined) {\n questions.push({\n type: \"confirm\",\n name: \"discord\",\n message: \"Enable Discord notifications?\",\n default: false,\n });\n } else {\n config.notifications.discord = flags.discord;\n }\n\n if (questions.length > 0) {\n const answers = await inquirer.prompt(questions);\n if (answers.branch !== undefined) config.branch = answers.branch;\n if (answers.slack !== undefined) config.notifications.slack = answers.slack;\n if (answers.discord !== undefined)\n config.notifications.discord = answers.discord;\n }\n}\n\nfunction printSecretInstructions(config: S3SyncConfig): void {\n log.heading(\"Next steps\");\n log.info(\"Add these secrets to your GitHub repository:\");\n log.dim(\n \" Settings → Secrets and variables → Actions → New repository secret\\n\"\n );\n\n const secrets = [\n [\"S3_ACCESS_KEY_ID\", \"Your S3 access key\"],\n [\"S3_SECRET_ACCESS_KEY\", \"Your S3 secret key\"],\n ];\n\n if (config.notifications.slack) {\n secrets.push([\"SLACK_WEBHOOK_URL\", \"Slack incoming webhook URL\"]);\n }\n\n if (config.notifications.discord) {\n secrets.push([\"DISCORD_WEBHOOK_URL\", \"Discord webhook URL\"]);\n }\n\n for (const [name, desc] of secrets) {\n log.dim(` • ${name} — ${desc}`);\n }\n\n console.log();\n log.heading(\"Configured targets\");\n for (const t of config.targets) {\n log.info(` ${t.directory}/ → s3://${t.bucket}/${t.prefix}`);\n }\n\n console.log();\n log.success(\n `Push to \"${config.branch}\" to trigger a sync.`\n );\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nexport interface SyncTarget {\n directory: string;\n bucket: string;\n region: string;\n endpoint: string;\n prefix: string;\n delete: boolean;\n}\n\nexport interface S3SyncConfig {\n targets: SyncTarget[];\n branch: string;\n notifications: {\n slack: boolean;\n discord: boolean;\n };\n}\n\nconst CONFIG_FILE = \"s3-sync.json\";\n\nconst syncTargetSchema: z.ZodType<SyncTarget> = z.object({\n directory: z.string().min(1),\n bucket: z.string().min(1),\n region: z.string().min(1),\n endpoint: z.string().default(\"\"),\n prefix: z.string().default(\"\"),\n delete: z.boolean().default(false),\n});\n\nconst notificationsSchema: z.ZodType<S3SyncConfig[\"notifications\"]> = z.object({\n slack: z.boolean().default(false),\n discord: z.boolean().default(false),\n});\n\nconst configSchema: z.ZodType<S3SyncConfig> = z.object({\n targets: z.array(syncTargetSchema).default([]),\n branch: z.string().min(1).default(\"main\"),\n notifications: notificationsSchema.default({ slack: false, discord: false }),\n});\n\nconst legacyConfigSchema = z.object({\n directory: z.string().min(1),\n bucket: z.string().min(1),\n region: z.string().min(1),\n endpoint: z.string().default(\"\"),\n prefix: z.string().default(\"\"),\n branch: z.string().min(1).default(\"main\"),\n delete: z.boolean().default(false),\n notifications: notificationsSchema.default({ slack: false, discord: false }),\n});\n\nexport function getConfigPath(cwd: string = process.cwd()): string {\n return path.join(cwd, CONFIG_FILE);\n}\n\nexport function configExists(cwd: string = process.cwd()): boolean {\n return existsSync(getConfigPath(cwd));\n}\n\nexport async function readConfig(\n cwd: string = process.cwd()\n): Promise<S3SyncConfig> {\n const configPath = getConfigPath(cwd);\n const raw = JSON.parse(await readFile(configPath, \"utf-8\"));\n const parsedConfig = configSchema.safeParse(raw);\n if (parsedConfig.success) return parsedConfig.data;\n\n const parsedLegacyConfig = legacyConfigSchema.safeParse(raw);\n if (!parsedLegacyConfig.success) {\n const issues = parsedConfig.error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"root\";\n return `${path}: ${issue.message}`;\n })\n .join(\"; \");\n throw new Error(`Invalid ${CONFIG_FILE}: ${issues}`);\n }\n\n const legacy = parsedLegacyConfig.data;\n return {\n targets: [\n {\n directory: legacy.directory,\n bucket: legacy.bucket,\n region: legacy.region,\n endpoint: legacy.endpoint,\n prefix: legacy.prefix,\n delete: legacy.delete,\n },\n ],\n branch: legacy.branch,\n notifications: legacy.notifications,\n };\n}\n\nexport async function writeConfig(\n config: S3SyncConfig,\n cwd: string = process.cwd()\n): Promise<void> {\n const configPath = getConfigPath(cwd);\n const validatedConfig = configSchema.parse(config);\n await writeFile(\n configPath,\n JSON.stringify(validatedConfig, null, 2) + \"\\n\",\n \"utf-8\"\n );\n}\n\nexport function defaultTarget(): SyncTarget {\n return {\n directory: \"public\",\n bucket: \"\",\n region: \"us-east-1\",\n endpoint: \"\",\n prefix: \"\",\n delete: false,\n };\n}\n\nexport function defaultConfig(): S3SyncConfig {\n return {\n targets: [],\n branch: \"main\",\n notifications: { slack: false, discord: false },\n };\n}\n","import type { S3SyncConfig } from \"../utils/config.js\";\n\nexport function generateWorkflow(\n config: S3SyncConfig,\n packageSpecifier: string\n): string {\n const envBlock = buildEnvBlock(config);\n const pathFilters = [\n ...config.targets.map((t) => `${t.directory}/**`),\n \"s3-sync.json\",\n \".github/workflows/s3-sync.yml\",\n ]\n .map((pattern) => ` - '${pattern}'`)\n .join(\"\\n\");\n\n return `name: S3 Sync\n\non:\n push:\n branches: [${config.branch}]\n paths:\n${pathFilters}\n workflow_dispatch:\n\njobs:\n sync:\n name: Sync to S3\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n\n - name: Sync files\n run: npx --yes ${packageSpecifier} sync\n${envBlock}\n`;\n}\n\nfunction buildEnvBlock(config: S3SyncConfig): string {\n const lines = [\n \" env:\",\n \" S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}\",\n \" S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}\",\n ];\n\n if (config.notifications.slack) {\n lines.push(\n \" SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\"\n );\n }\n\n if (config.notifications.discord) {\n lines.push(\n \" DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}\"\n );\n }\n\n return lines.join(\"\\n\");\n}\n","import chalk from \"chalk\";\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue(\"ℹ\"), msg),\n success: (msg: string) => console.log(chalk.green(\"✓\"), msg),\n warn: (msg: string) => console.log(chalk.yellow(\"⚠\"), msg),\n error: (msg: string) => console.error(chalk.red(\"✗\"), msg),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n heading: (msg: string) => console.log(chalk.bold.cyan(`\\n${msg}\\n`)),\n};\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\ninterface PackageMeta {\n name?: string;\n version?: string;\n}\n\nfunction findPackageJson(startDirectory: string): string | undefined {\n let currentDirectory = startDirectory;\n while (true) {\n const candidate = path.join(currentDirectory, \"package.json\");\n if (existsSync(candidate)) return candidate;\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) return undefined;\n currentDirectory = parentDirectory;\n }\n}\n\nexport function getCurrentPackageSpecifier(\n fallbackName = \"@maydotinc/s3-sync\"\n): string {\n const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageJsonPath = findPackageJson(currentFileDirectory);\n if (!packageJsonPath) return fallbackName;\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as PackageMeta;\n const packageName = parsed.name?.trim() || fallbackName;\n const packageVersion = parsed.version?.trim();\n return packageVersion ? `${packageName}@${packageVersion}` : packageName;\n } catch {\n return fallbackName;\n }\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { readConfig, type S3SyncConfig, type SyncTarget } from \"../utils/config.js\";\nimport { log } from \"../utils/logger.js\";\nimport { fingerprintDirectory } from \"../core/fingerprint.js\";\nimport {\n createS3,\n listAllObjects,\n uploadFile,\n deleteObject,\n} from \"../core/s3-client.js\";\nimport { computeDiff, type DiffResult } from \"../core/diff.js\";\nimport { sendSlackNotification } from \"../notifications/slack.js\";\nimport { sendDiscordNotification } from \"../notifications/discord.js\";\nimport type { NotificationPayload } from \"../notifications/types.js\";\n\nexport async function syncCommand(): Promise<void> {\n log.heading(\"s3-sync\");\n\n const config = await readConfig();\n\n const accessKeyId = process.env.S3_ACCESS_KEY_ID;\n const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY;\n\n if (!accessKeyId || !secretAccessKey) {\n log.error(\n \"Missing S3_ACCESS_KEY_ID or S3_SECRET_ACCESS_KEY environment variables.\"\n );\n process.exit(1);\n }\n\n if (config.targets.length === 0) {\n log.error(\"No sync targets configured. Run setup first.\");\n process.exit(1);\n }\n\n let syncedTargets = 0;\n const syncedTargetMappings: string[] = [];\n let totalUploaded = 0;\n let totalDeleted = 0;\n let totalUnchanged = 0;\n let hasErrors = false;\n const globalEndpoint = process.env.S3_ENDPOINT?.trim() || undefined;\n const hasTargetSpecificEndpoints = config.targets.some(\n (target) => target.endpoint.trim().length > 0\n );\n\n for (const target of config.targets) {\n try {\n const endpoint = resolveEndpoint(\n target.endpoint,\n globalEndpoint,\n hasTargetSpecificEndpoints\n );\n const diff = await syncTarget(\n target,\n accessKeyId,\n secretAccessKey,\n endpoint\n );\n syncedTargets += 1;\n syncedTargetMappings.push(buildTargetMapping(target));\n totalUploaded += diff.toUpload.length;\n totalDeleted += diff.toDelete.length;\n totalUnchanged += diff.unchanged.length;\n } catch (err: any) {\n log.error(`[${target.directory}] ${err.message}`);\n hasErrors = true;\n }\n }\n\n if (syncedTargets > 0) {\n const targetLabel = syncedTargets === 1 ? \"target\" : \"targets\";\n const message = hasErrors\n ? `Synced ${syncedTargets} ${targetLabel}. Some targets failed.`\n : `Synced ${syncedTargets} ${targetLabel}.`;\n await notify(config, {\n title: hasErrors ? \"S3 Sync Completed with Errors\" : \"S3 Sync Completed\",\n message,\n fields: [\n {\n name: syncedTargetMappings.length === 1 ? \"Target\" : \"Targets\",\n value: syncedTargetMappings\n .map((mapping) => `- ${mapping}`)\n .join(\"\\n\"),\n },\n { name: \"Uploaded\", value: String(totalUploaded), inline: true },\n { name: \"Deleted\", value: String(totalDeleted), inline: true },\n { name: \"Unchanged\", value: String(totalUnchanged), inline: true },\n ],\n });\n }\n\n if (hasErrors) {\n process.exit(1);\n }\n\n log.success(\"All targets synced.\");\n}\n\nasync function syncTarget(\n target: SyncTarget,\n accessKeyId: string,\n secretAccessKey: string,\n endpoint: string | undefined\n): Promise<DiffResult> {\n log.heading(`Syncing ${target.directory}/ → s3://${target.bucket}/${target.prefix}`);\n\n const directory = path.resolve(target.directory);\n if (!existsSync(directory)) {\n throw new Error(`Directory \"${target.directory}\" does not exist.`);\n }\n\n const client = createS3({\n bucket: target.bucket,\n region: target.region,\n endpoint,\n accessKeyId,\n secretAccessKey,\n });\n\n log.info(`Scanning ${target.directory}/...`);\n const localFiles = await fingerprintDirectory(directory);\n log.info(`Found ${localFiles.length} local files`);\n\n log.info(`Listing objects in s3://${target.bucket}/${target.prefix}...`);\n const remoteObjects = await listAllObjects(\n client,\n target.bucket,\n target.prefix\n );\n log.info(`Found ${remoteObjects.length} remote objects`);\n\n const diff = computeDiff(\n localFiles,\n remoteObjects,\n target.prefix,\n target.delete\n );\n\n log.info(\n `${diff.toUpload.length} to upload, ${diff.toDelete.length} to delete, ${diff.unchanged.length} unchanged`\n );\n\n if (diff.toUpload.length === 0 && diff.toDelete.length === 0) {\n log.success(`[${target.directory}] Already in sync.`);\n return diff;\n }\n\n for (const file of diff.toUpload) {\n const key = target.prefix\n ? `${target.prefix}/${file.relativePath}`\n : file.relativePath;\n log.dim(` uploading ${key}`);\n await uploadFile(client, target.bucket, key, file.absolutePath);\n }\n\n if (diff.toUpload.length > 0) {\n log.success(`[${target.directory}] Uploaded ${diff.toUpload.length} files`);\n }\n\n for (const obj of diff.toDelete) {\n log.dim(` deleting ${obj.key}`);\n await deleteObject(client, target.bucket, obj.key);\n }\n\n if (diff.toDelete.length > 0) {\n log.success(`[${target.directory}] Deleted ${diff.toDelete.length} files`);\n }\n\n return diff;\n}\n\nfunction resolveEndpoint(\n targetEndpoint: string,\n globalEndpoint: string | undefined,\n hasTargetSpecificEndpoints: boolean\n): string | undefined {\n const trimmedTargetEndpoint = targetEndpoint.trim();\n if (trimmedTargetEndpoint) return trimmedTargetEndpoint;\n if (!hasTargetSpecificEndpoints) return globalEndpoint;\n return undefined;\n}\n\nfunction buildTargetMapping(target: SyncTarget): string {\n const remotePath = target.prefix.trim()\n ? `${target.bucket}/${target.prefix}`\n : `${target.bucket}/`;\n return `${target.directory}/ -> ${remotePath}`;\n}\n\nasync function notify(\n config: S3SyncConfig,\n payload: string | NotificationPayload\n): Promise<void> {\n if (config.notifications?.slack) {\n const webhookUrl = process.env.SLACK_WEBHOOK_URL;\n if (webhookUrl) {\n try {\n await sendSlackNotification(webhookUrl, payload);\n log.success(\"Slack notification sent\");\n } catch (err: any) {\n log.warn(`Slack notification failed: ${err.message}`);\n }\n }\n }\n\n if (config.notifications?.discord) {\n const webhookUrl = process.env.DISCORD_WEBHOOK_URL;\n if (webhookUrl) {\n try {\n await sendDiscordNotification(webhookUrl, payload);\n log.success(\"Discord notification sent\");\n } catch (err: any) {\n log.warn(`Discord notification failed: ${err.message}`);\n }\n }\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { glob } from \"glob\";\n\nexport interface LocalFile {\n relativePath: string;\n absolutePath: string;\n md5: string;\n size: number;\n}\n\nexport async function fingerprintDirectory(\n directory: string\n): Promise<LocalFile[]> {\n const absoluteDir = path.resolve(directory);\n const files = await glob(\"**/*\", {\n cwd: absoluteDir,\n nodir: true,\n dot: false,\n });\n\n const results: LocalFile[] = [];\n\n await Promise.all(\n files.map(async (relativePath) => {\n const absolutePath = path.join(absoluteDir, relativePath);\n const content = await readFile(absolutePath);\n const md5 = createHash(\"md5\").update(content).digest(\"hex\");\n\n results.push({\n relativePath: relativePath.replace(/\\\\/g, \"/\"),\n absolutePath,\n md5,\n size: content.length,\n });\n })\n );\n\n return results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));\n}\n","import {\n S3Client,\n ListObjectsV2Command,\n type ListObjectsV2CommandOutput,\n PutObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { readFile } from \"node:fs/promises\";\nimport mime from \"mime-types\";\n\nexport interface S3Object {\n key: string;\n etag: string;\n size: number;\n}\n\nexport interface S3Config {\n bucket: string;\n region: string;\n endpoint?: string;\n accessKeyId: string;\n secretAccessKey: string;\n}\n\nexport function createS3(config: S3Config): S3Client {\n return new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n ...(config.endpoint && {\n endpoint: config.endpoint,\n forcePathStyle: true,\n }),\n });\n}\n\nexport async function listAllObjects(\n client: S3Client,\n bucket: string,\n prefix: string\n): Promise<S3Object[]> {\n const objects: S3Object[] = [];\n let continuationToken: string | undefined;\n\n do {\n const command = new ListObjectsV2Command({\n Bucket: bucket,\n Prefix: prefix || undefined,\n ContinuationToken: continuationToken,\n });\n\n const response: ListObjectsV2CommandOutput = await client.send(command);\n\n if (response.Contents) {\n for (const obj of response.Contents) {\n if (obj.Key && obj.ETag && obj.Size !== undefined) {\n objects.push({\n key: obj.Key,\n etag: obj.ETag.replace(/\"/g, \"\"),\n size: obj.Size,\n });\n }\n }\n }\n\n continuationToken = response.IsTruncated\n ? response.NextContinuationToken\n : undefined;\n } while (continuationToken);\n\n return objects;\n}\n\nexport async function uploadFile(\n client: S3Client,\n bucket: string,\n key: string,\n filePath: string\n): Promise<void> {\n const body = await readFile(filePath);\n const contentType = mime.lookup(filePath) || \"application/octet-stream\";\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n })\n );\n}\n\nexport async function deleteObject(\n client: S3Client,\n bucket: string,\n key: string\n): Promise<void> {\n await client.send(\n new DeleteObjectCommand({\n Bucket: bucket,\n Key: key,\n })\n );\n}\n","import type { LocalFile } from \"./fingerprint.js\";\nimport type { S3Object } from \"./s3-client.js\";\n\nexport interface DiffResult {\n toUpload: LocalFile[];\n toDelete: S3Object[];\n unchanged: LocalFile[];\n}\n\nexport function computeDiff(\n localFiles: LocalFile[],\n remoteObjects: S3Object[],\n prefix: string,\n shouldDelete: boolean\n): DiffResult {\n const remoteByKey = new Map<string, S3Object>();\n for (const obj of remoteObjects) {\n remoteByKey.set(obj.key, obj);\n }\n\n const toUpload: LocalFile[] = [];\n const unchanged: LocalFile[] = [];\n const localKeys = new Set<string>();\n\n for (const file of localFiles) {\n const key = prefix ? `${prefix}/${file.relativePath}` : file.relativePath;\n localKeys.add(key);\n\n const remote = remoteByKey.get(key);\n\n if (!remote || remote.etag !== file.md5) {\n toUpload.push(file);\n } else {\n unchanged.push(file);\n }\n }\n\n const toDelete: S3Object[] = [];\n if (shouldDelete) {\n for (const obj of remoteObjects) {\n if (!localKeys.has(obj.key)) {\n toDelete.push(obj);\n }\n }\n }\n\n return { toUpload, toDelete, unchanged };\n}\n","import type { NotificationPayload } from \"./types.js\";\n\nconst SLACK_GREEN = \"#2EB67D\";\nconst SLACK_ATTACHMENT_TEXT_LIMIT = 7000;\nconst SLACK_ATTACHMENT_TITLE_LIMIT = 256;\nconst SLACK_FIELD_TITLE_LIMIT = 200;\nconst SLACK_FIELD_VALUE_LIMIT = 2000;\n\nfunction truncate(input: string, maxLength: number): string {\n if (input.length <= maxLength) return input;\n return `${input.slice(0, maxLength - 13)}\\n\\n...[truncated]`;\n}\n\nfunction normalizePayload(\n payload: string | NotificationPayload\n): NotificationPayload {\n if (typeof payload === \"string\") {\n return { message: payload };\n }\n return payload;\n}\n\nexport async function sendSlackNotification(\n webhookUrl: string,\n payload: string | NotificationPayload\n): Promise<void> {\n const normalized = normalizePayload(payload);\n const title = truncate(\n normalized.title?.trim() || \"S3 Sync Completed\",\n SLACK_ATTACHMENT_TITLE_LIMIT\n );\n const text = normalized.message\n ? truncate(normalized.message, SLACK_ATTACHMENT_TEXT_LIMIT)\n : undefined;\n const fields = (normalized.fields ?? [])\n .filter((field) => field.name.trim() && field.value.trim())\n .map((field) => ({\n title: truncate(field.name.trim(), SLACK_FIELD_TITLE_LIMIT),\n value: truncate(field.value.trim(), SLACK_FIELD_VALUE_LIMIT),\n short: field.inline ?? false,\n }));\n\n const attachment: {\n color: string;\n title: string;\n mrkdwn_in: string[];\n footer: string;\n text?: string;\n fields?: Array<{ title: string; value: string; short: boolean }>;\n } = {\n color: SLACK_GREEN,\n title,\n mrkdwn_in: [\"text\", \"fields\"],\n footer: \"@maydotinc/s3-sync\",\n };\n\n if (text) {\n attachment.text = text;\n }\n\n if (fields.length > 0) {\n attachment.fields = fields;\n }\n\n const response = await fetch(webhookUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n text: normalized.title?.trim() || \"S3 sync completed\",\n attachments: [attachment],\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(\n `Slack notification failed (${response.status}): ${body || response.statusText}`\n );\n }\n}\n","import type { NotificationPayload } from \"./types.js\";\n\nconst DISCORD_GREEN = 0x2eb67d;\nconst DISCORD_EMBED_DESCRIPTION_LIMIT = 4096;\nconst DISCORD_EMBED_TITLE_LIMIT = 256;\nconst DISCORD_EMBED_FIELD_NAME_LIMIT = 256;\nconst DISCORD_EMBED_FIELD_VALUE_LIMIT = 1024;\nconst DISCORD_EMBED_FIELD_MAX = 25;\n\nfunction truncate(input: string, maxLength: number): string {\n if (input.length <= maxLength) return input;\n return `${input.slice(0, maxLength - 13)}\\n\\n...[truncated]`;\n}\n\nfunction normalizePayload(\n payload: string | NotificationPayload\n): NotificationPayload {\n if (typeof payload === \"string\") {\n return { message: payload };\n }\n return payload;\n}\n\nexport async function sendDiscordNotification(\n webhookUrl: string,\n payload: string | NotificationPayload\n): Promise<void> {\n const normalized = normalizePayload(payload);\n const title = truncate(\n normalized.title?.trim() || \"S3 Sync Completed\",\n DISCORD_EMBED_TITLE_LIMIT\n );\n const description = normalized.message\n ? truncate(normalized.message, DISCORD_EMBED_DESCRIPTION_LIMIT)\n : undefined;\n const fields = (normalized.fields ?? [])\n .filter((field) => field.name.trim() && field.value.trim())\n .slice(0, DISCORD_EMBED_FIELD_MAX)\n .map((field) => ({\n name: truncate(field.name.trim(), DISCORD_EMBED_FIELD_NAME_LIMIT),\n value: truncate(field.value.trim(), DISCORD_EMBED_FIELD_VALUE_LIMIT),\n inline: field.inline ?? false,\n }));\n\n const embed: {\n title: string;\n color: number;\n footer: { text: string };\n timestamp: string;\n description?: string;\n fields?: Array<{ name: string; value: string; inline: boolean }>;\n } = {\n title,\n color: DISCORD_GREEN,\n footer: { text: \"@maydotinc/s3-sync\" },\n timestamp: new Date().toISOString(),\n };\n\n if (description) {\n embed.description = description;\n }\n\n if (fields.length > 0) {\n embed.fields = fields;\n }\n\n const response = await fetch(webhookUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n embeds: [embed],\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(\n `Discord notification failed (${response.status}): ${body || response.statusText}`\n );\n }\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,OAAO,aAAAA,kBAAiB;AACjC,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,WAAU;AACjB,OAAO,cAAc;;;ACHrB,SAAS,UAAU,iBAAiB;AACpC,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,SAAS,SAAS;AAoBlB,IAAM,cAAc;AAEpB,IAAM,mBAA0C,EAAE,OAAO;AAAA,EACvD,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC/B,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC7B,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK;AACnC,CAAC;AAED,IAAM,sBAAgE,EAAE,OAAO;AAAA,EAC7E,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EAChC,SAAS,EAAE,QAAQ,EAAE,QAAQ,KAAK;AACpC,CAAC;AAED,IAAM,eAAwC,EAAE,OAAO;AAAA,EACrD,SAAS,EAAE,MAAM,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC7C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,MAAM;AAAA,EACxC,eAAe,oBAAoB,QAAQ,EAAE,OAAO,OAAO,SAAS,MAAM,CAAC;AAC7E,CAAC;AAED,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC/B,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC7B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,MAAM;AAAA,EACxC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACjC,eAAe,oBAAoB,QAAQ,EAAE,OAAO,OAAO,SAAS,MAAM,CAAC;AAC7E,CAAC;AAEM,SAAS,cAAc,MAAc,QAAQ,IAAI,GAAW;AACjE,SAAO,KAAK,KAAK,KAAK,WAAW;AACnC;AAEO,SAAS,aAAa,MAAc,QAAQ,IAAI,GAAY;AACjE,SAAO,WAAW,cAAc,GAAG,CAAC;AACtC;AAEA,eAAsB,WACpB,MAAc,QAAQ,IAAI,GACH;AACvB,QAAM,aAAa,cAAc,GAAG;AACpC,QAAM,MAAM,KAAK,MAAM,MAAM,SAAS,YAAY,OAAO,CAAC;AAC1D,QAAM,eAAe,aAAa,UAAU,GAAG;AAC/C,MAAI,aAAa,QAAS,QAAO,aAAa;AAE9C,QAAM,qBAAqB,mBAAmB,UAAU,GAAG;AAC3D,MAAI,CAAC,mBAAmB,SAAS;AAC/B,UAAM,SAAS,aAAa,MAAM,OAC/B,IAAI,CAAC,UAAU;AACd,YAAMC,QAAO,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,GAAG,IAAI;AAC5D,aAAO,GAAGA,KAAI,KAAK,MAAM,OAAO;AAAA,IAClC,CAAC,EACA,KAAK,IAAI;AACZ,UAAM,IAAI,MAAM,WAAW,WAAW,KAAK,MAAM,EAAE;AAAA,EACrD;AAEA,QAAM,SAAS,mBAAmB;AAClC,SAAO;AAAA,IACL,SAAS;AAAA,MACP;AAAA,QACE,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,QACjB,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,IACA,QAAQ,OAAO;AAAA,IACf,eAAe,OAAO;AAAA,EACxB;AACF;AAEA,eAAsB,YACpB,QACA,MAAc,QAAQ,IAAI,GACX;AACf,QAAM,aAAa,cAAc,GAAG;AACpC,QAAM,kBAAkB,aAAa,MAAM,MAAM;AACjD,QAAM;AAAA,IACJ;AAAA,IACA,KAAK,UAAU,iBAAiB,MAAM,CAAC,IAAI;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,gBAA4B;AAC1C,SAAO;AAAA,IACL,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAEO,SAAS,gBAA8B;AAC5C,SAAO;AAAA,IACL,SAAS,CAAC;AAAA,IACV,QAAQ;AAAA,IACR,eAAe,EAAE,OAAO,OAAO,SAAS,MAAM;AAAA,EAChD;AACF;;;AChIO,SAAS,iBACd,QACA,kBACQ;AACR,QAAM,WAAW,cAAc,MAAM;AACrC,QAAM,cAAc;AAAA,IAClB,GAAG,OAAO,QAAQ,IAAI,CAAC,MAAM,GAAG,EAAE,SAAS,KAAK;AAAA,IAChD;AAAA,IACA;AAAA,EACF,EACG,IAAI,CAAC,YAAY,YAAY,OAAO,GAAG,EACvC,KAAK,IAAI;AAEZ,SAAO;AAAA;AAAA;AAAA;AAAA,iBAIQ,OAAO,MAAM;AAAA;AAAA,EAE5B,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAkBY,gBAAgB;AAAA,EACvC,QAAQ;AAAA;AAEV;AAEA,SAAS,cAAc,QAA8B;AACnD,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,OAAO;AAC9B,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,cAAc,SAAS;AAChC,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;AChEA,OAAO,WAAW;AAEX,IAAM,MAAM;AAAA,EACjB,MAAM,CAAC,QAAgB,QAAQ,IAAI,MAAM,KAAK,QAAG,GAAG,GAAG;AAAA,EACvD,SAAS,CAAC,QAAgB,QAAQ,IAAI,MAAM,MAAM,QAAG,GAAG,GAAG;AAAA,EAC3D,MAAM,CAAC,QAAgB,QAAQ,IAAI,MAAM,OAAO,QAAG,GAAG,GAAG;AAAA,EACzD,OAAO,CAAC,QAAgB,QAAQ,MAAM,MAAM,IAAI,QAAG,GAAG,GAAG;AAAA,EACzD,KAAK,CAAC,QAAgB,QAAQ,IAAI,MAAM,IAAI,GAAG,CAAC;AAAA,EAChD,SAAS,CAAC,QAAgB,QAAQ,IAAI,MAAM,KAAK,KAAK;AAAA,EAAK,GAAG;AAAA,CAAI,CAAC;AACrE;;;ACTA,SAAS,cAAAC,aAAY,oBAAoB;AACzC,OAAOC,WAAU;AACjB,SAAS,qBAAqB;AAO9B,SAAS,gBAAgB,gBAA4C;AACnE,MAAI,mBAAmB;AACvB,SAAO,MAAM;AACX,UAAM,YAAYA,MAAK,KAAK,kBAAkB,cAAc;AAC5D,QAAID,YAAW,SAAS,EAAG,QAAO;AAClC,UAAM,kBAAkBC,MAAK,QAAQ,gBAAgB;AACrD,QAAI,oBAAoB,iBAAkB,QAAO;AACjD,uBAAmB;AAAA,EACrB;AACF;AAEO,SAAS,2BACd,eAAe,sBACP;AACR,QAAM,uBAAuBA,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxE,QAAM,kBAAkB,gBAAgB,oBAAoB;AAC5D,MAAI,CAAC,gBAAiB,QAAO;AAE7B,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AAChE,UAAM,cAAc,OAAO,MAAM,KAAK,KAAK;AAC3C,UAAM,iBAAiB,OAAO,SAAS,KAAK;AAC5C,WAAO,iBAAiB,GAAG,WAAW,IAAI,cAAc,KAAK;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AJPA,eAAsB,aACpB,WACA,OACe;AACf,MAAI,QAAQ,eAAe;AAE3B,MAAI;AACJ,MAAI,WAAW;AAEf,MAAI,aAAa,GAAG;AAClB,UAAM,WAAW,MAAM,WAAW;AAClC,UAAM,gBAAgB,SAAS,QAAQ;AAAA,MACrC,CAAC,MAAM,EAAE,cAAc;AAAA,IACzB;AAEA,QAAI,eAAe;AACjB,YAAM,EAAE,UAAU,IAAI,MAAM,SAAS,OAAO;AAAA,QAC1C;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS,WAAW,SAAS;AAAA,UAC7B,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AACD,UAAI,CAAC,WAAW;AACd,YAAI,KAAK,kBAAkB;AAC3B;AAAA,MACF;AACA,eAAS,UAAU,SAAS,QAAQ;AAAA,QAClC,CAAC,MAAM,EAAE,cAAc;AAAA,MACzB;AAAA,IACF,OAAO;AACL,UAAI;AAAA,QACF,8BAA8B,SAAS,QAAQ,MAAM,uBAAuB,SAAS;AAAA,MACvF;AACA,iBAAW;AAAA,IACb;AACA,aAAS;AAAA,EACX,OAAO;AACL,aAAS,cAAc;AAAA,EACzB;AAEA,QAAM,UAAUC,MAAK,QAAQ,SAAS;AACtC,MAAI,CAACC,YAAW,OAAO,GAAG;AACxB,QAAI;AAAA,MACF,cAAc,SAAS;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,aAAa,WAAW,KAAK;AAClD,SAAO,QAAQ,KAAK,MAAM;AAE1B,MAAI,CAAC,UAAU;AACb,UAAM,mBAAmB,QAAQ,KAAK;AAAA,EACxC;AAEA,QAAM,YAAY,MAAM;AACxB,MAAI,QAAQ,sBAAsB;AAElC,QAAM,cAAcD,MAAK,KAAK,QAAQ,IAAI,GAAG,WAAW,WAAW;AACnE,QAAM,MAAM,aAAa,EAAE,WAAW,KAAK,CAAC;AAE5C,QAAM,eAAeA,MAAK,KAAK,aAAa,aAAa;AACzD,QAAM,mBAAmB,2BAA2B;AACpD,QAAM,kBAAkB,iBAAiB,QAAQ,gBAAgB;AACjE,QAAME,WAAU,cAAc,iBAAiB,OAAO;AACtD,MAAI,QAAQ,uCAAuC;AAEnD,0BAAwB,MAAM;AAChC;AAEA,eAAe,aACb,WACA,OACqB;AACrB,QAAM,SAAS,cAAc;AAC7B,SAAO,YAAY;AAEnB,QAAM,YAAmB,CAAC;AAE1B,MAAI,MAAM,WAAW,QAAW;AAC9B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,IAAI,SAAS;AAAA,MACtB,UAAU,CAAC,MAAe,EAAE,KAAK,IAAI,OAAO;AAAA,IAC9C,CAAC;AAAA,EACH,OAAO;AACL,WAAO,SAAS,MAAM;AAAA,EACxB;AAEA,MAAI,MAAM,WAAW,QAAW;AAC9B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,IAAI,SAAS;AAAA,MACtB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,SAAS,MAAM;AAAA,EACxB;AAEA,MAAI,MAAM,aAAa,QAAW;AAChC,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,IAAI,SAAS;AAAA,MACtB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,WAAW,MAAM;AAAA,EAC1B;AAEA,MAAI,MAAM,WAAW,QAAW;AAC9B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,IAAI,SAAS;AAAA,MACtB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,SAAS,MAAM;AAAA,EACxB;AAEA,MAAI,MAAM,WAAW,QAAW;AAC9B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,IAAI,SAAS;AAAA,MACtB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,SAAS,MAAM;AAAA,EACxB;AAEA,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,UAAU,MAAM,SAAS,OAAO,SAAS;AAC/C,QAAI,QAAQ,WAAW,OAAW,QAAO,SAAS,QAAQ;AAC1D,QAAI,QAAQ,WAAW,OAAW,QAAO,SAAS,QAAQ;AAC1D,QAAI,QAAQ,aAAa,OAAW,QAAO,WAAW,QAAQ;AAC9D,QAAI,QAAQ,WAAW,OAAW,QAAO,SAAS,QAAQ;AAC1D,QAAI,QAAQ,WAAW,OAAW,QAAO,SAAS,QAAQ;AAAA,EAC5D;AAEA,SAAO;AACT;AAEA,eAAe,mBACb,QACA,OACe;AACf,QAAM,YAAmB,CAAC;AAE1B,MAAI,MAAM,WAAW,QAAW;AAC9B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,SAAS,MAAM;AAAA,EACxB;AAEA,MAAI,MAAM,UAAU,QAAW;AAC7B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC;AAEA,MAAI,MAAM,YAAY,QAAW;AAC/B,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH,OAAO;AACL,WAAO,cAAc,UAAU,MAAM;AAAA,EACvC;AAEA,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,UAAU,MAAM,SAAS,OAAO,SAAS;AAC/C,QAAI,QAAQ,WAAW,OAAW,QAAO,SAAS,QAAQ;AAC1D,QAAI,QAAQ,UAAU,OAAW,QAAO,cAAc,QAAQ,QAAQ;AACtE,QAAI,QAAQ,YAAY;AACtB,aAAO,cAAc,UAAU,QAAQ;AAAA,EAC3C;AACF;AAEA,SAAS,wBAAwB,QAA4B;AAC3D,MAAI,QAAQ,YAAY;AACxB,MAAI,KAAK,8CAA8C;AACvD,MAAI;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU;AAAA,IACd,CAAC,oBAAoB,oBAAoB;AAAA,IACzC,CAAC,wBAAwB,oBAAoB;AAAA,EAC/C;AAEA,MAAI,OAAO,cAAc,OAAO;AAC9B,YAAQ,KAAK,CAAC,qBAAqB,4BAA4B,CAAC;AAAA,EAClE;AAEA,MAAI,OAAO,cAAc,SAAS;AAChC,YAAQ,KAAK,CAAC,uBAAuB,qBAAqB,CAAC;AAAA,EAC7D;AAEA,aAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,QAAI,IAAI,YAAO,IAAI,WAAM,IAAI,EAAE;AAAA,EACjC;AAEA,UAAQ,IAAI;AACZ,MAAI,QAAQ,oBAAoB;AAChC,aAAW,KAAK,OAAO,SAAS;AAC9B,QAAI,KAAK,KAAK,EAAE,SAAS,iBAAY,EAAE,MAAM,IAAI,EAAE,MAAM,EAAE;AAAA,EAC7D;AAEA,UAAQ,IAAI;AACZ,MAAI;AAAA,IACF,YAAY,OAAO,MAAM;AAAA,EAC3B;AACF;;;AKjQA,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,WAAU;;;ACDjB,SAAS,kBAAkB;AAC3B,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;AACjB,SAAS,YAAY;AASrB,eAAsB,qBACpB,WACsB;AACtB,QAAM,cAAcA,MAAK,QAAQ,SAAS;AAC1C,QAAM,QAAQ,MAAM,KAAK,QAAQ;AAAA,IAC/B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC;AAED,QAAM,UAAuB,CAAC;AAE9B,QAAM,QAAQ;AAAA,IACZ,MAAM,IAAI,OAAO,iBAAiB;AAChC,YAAM,eAAeA,MAAK,KAAK,aAAa,YAAY;AACxD,YAAM,UAAU,MAAMD,UAAS,YAAY;AAC3C,YAAM,MAAM,WAAW,KAAK,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAE1D,cAAQ,KAAK;AAAA,QACX,cAAc,aAAa,QAAQ,OAAO,GAAG;AAAA,QAC7C;AAAA,QACA;AAAA,QACA,MAAM,QAAQ;AAAA,MAChB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,cAAc,EAAE,YAAY,CAAC;AAC5E;;;ACxCA;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAAE,iBAAgB;AACzB,OAAO,UAAU;AAgBV,SAAS,SAAS,QAA4B;AACnD,SAAO,IAAI,SAAS;AAAA,IAClB,QAAQ,OAAO;AAAA,IACf,aAAa;AAAA,MACX,aAAa,OAAO;AAAA,MACpB,iBAAiB,OAAO;AAAA,IAC1B;AAAA,IACA,GAAI,OAAO,YAAY;AAAA,MACrB,UAAU,OAAO;AAAA,MACjB,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,eACpB,QACA,QACA,QACqB;AACrB,QAAM,UAAsB,CAAC;AAC7B,MAAI;AAEJ,KAAG;AACD,UAAM,UAAU,IAAI,qBAAqB;AAAA,MACvC,QAAQ;AAAA,MACR,QAAQ,UAAU;AAAA,MAClB,mBAAmB;AAAA,IACrB,CAAC;AAED,UAAM,WAAuC,MAAM,OAAO,KAAK,OAAO;AAEtE,QAAI,SAAS,UAAU;AACrB,iBAAW,OAAO,SAAS,UAAU;AACnC,YAAI,IAAI,OAAO,IAAI,QAAQ,IAAI,SAAS,QAAW;AACjD,kBAAQ,KAAK;AAAA,YACX,KAAK,IAAI;AAAA,YACT,MAAM,IAAI,KAAK,QAAQ,MAAM,EAAE;AAAA,YAC/B,MAAM,IAAI;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,wBAAoB,SAAS,cACzB,SAAS,wBACT;AAAA,EACN,SAAS;AAET,SAAO;AACT;AAEA,eAAsB,WACpB,QACA,QACA,KACA,UACe;AACf,QAAM,OAAO,MAAMA,UAAS,QAAQ;AACpC,QAAM,cAAc,KAAK,OAAO,QAAQ,KAAK;AAE7C,QAAM,OAAO;AAAA,IACX,IAAI,iBAAiB;AAAA,MACnB,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,aACpB,QACA,QACA,KACe;AACf,QAAM,OAAO;AAAA,IACX,IAAI,oBAAoB;AAAA,MACtB,QAAQ;AAAA,MACR,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AACF;;;AChGO,SAAS,YACd,YACA,eACA,QACA,cACY;AACZ,QAAM,cAAc,oBAAI,IAAsB;AAC9C,aAAW,OAAO,eAAe;AAC/B,gBAAY,IAAI,IAAI,KAAK,GAAG;AAAA,EAC9B;AAEA,QAAM,WAAwB,CAAC;AAC/B,QAAM,YAAyB,CAAC;AAChC,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,QAAQ,YAAY;AAC7B,UAAM,MAAM,SAAS,GAAG,MAAM,IAAI,KAAK,YAAY,KAAK,KAAK;AAC7D,cAAU,IAAI,GAAG;AAEjB,UAAM,SAAS,YAAY,IAAI,GAAG;AAElC,QAAI,CAAC,UAAU,OAAO,SAAS,KAAK,KAAK;AACvC,eAAS,KAAK,IAAI;AAAA,IACpB,OAAO;AACL,gBAAU,KAAK,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,WAAuB,CAAC;AAC9B,MAAI,cAAc;AAChB,eAAW,OAAO,eAAe;AAC/B,UAAI,CAAC,UAAU,IAAI,IAAI,GAAG,GAAG;AAC3B,iBAAS,KAAK,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,UAAU,UAAU;AACzC;;;AC7CA,IAAM,cAAc;AACpB,IAAM,8BAA8B;AACpC,IAAM,+BAA+B;AACrC,IAAM,0BAA0B;AAChC,IAAM,0BAA0B;AAEhC,SAAS,SAAS,OAAe,WAA2B;AAC1D,MAAI,MAAM,UAAU,UAAW,QAAO;AACtC,SAAO,GAAG,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAAA;AAAA;AAC1C;AAEA,SAAS,iBACP,SACqB;AACrB,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,SAAS,QAAQ;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,eAAsB,sBACpB,YACA,SACe;AACf,QAAM,aAAa,iBAAiB,OAAO;AAC3C,QAAM,QAAQ;AAAA,IACZ,WAAW,OAAO,KAAK,KAAK;AAAA,IAC5B;AAAA,EACF;AACA,QAAM,OAAO,WAAW,UACpB,SAAS,WAAW,SAAS,2BAA2B,IACxD;AACJ,QAAM,UAAU,WAAW,UAAU,CAAC,GACnC,OAAO,CAAC,UAAU,MAAM,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,CAAC,EACzD,IAAI,CAAC,WAAW;AAAA,IACf,OAAO,SAAS,MAAM,KAAK,KAAK,GAAG,uBAAuB;AAAA,IAC1D,OAAO,SAAS,MAAM,MAAM,KAAK,GAAG,uBAAuB;AAAA,IAC3D,OAAO,MAAM,UAAU;AAAA,EACzB,EAAE;AAEJ,QAAM,aAOF;AAAA,IACF,OAAO;AAAA,IACP;AAAA,IACA,WAAW,CAAC,QAAQ,QAAQ;AAAA,IAC5B,QAAQ;AAAA,EACV;AAEA,MAAI,MAAM;AACR,eAAW,OAAO;AAAA,EACpB;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,eAAW,SAAS;AAAA,EACtB;AAEA,QAAM,WAAW,MAAM,MAAM,YAAY;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,MAAM,WAAW,OAAO,KAAK,KAAK;AAAA,MAClC,aAAa,CAAC,UAAU;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,IAAI;AAAA,MACR,8BAA8B,SAAS,MAAM,MAAM,QAAQ,SAAS,UAAU;AAAA,IAChF;AAAA,EACF;AACF;;;AC7EA,IAAM,gBAAgB;AACtB,IAAM,kCAAkC;AACxC,IAAM,4BAA4B;AAClC,IAAM,iCAAiC;AACvC,IAAM,kCAAkC;AACxC,IAAM,0BAA0B;AAEhC,SAASC,UAAS,OAAe,WAA2B;AAC1D,MAAI,MAAM,UAAU,UAAW,QAAO;AACtC,SAAO,GAAG,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAAA;AAAA;AAC1C;AAEA,SAASC,kBACP,SACqB;AACrB,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,SAAS,QAAQ;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,eAAsB,wBACpB,YACA,SACe;AACf,QAAM,aAAaA,kBAAiB,OAAO;AAC3C,QAAM,QAAQD;AAAA,IACZ,WAAW,OAAO,KAAK,KAAK;AAAA,IAC5B;AAAA,EACF;AACA,QAAM,cAAc,WAAW,UAC3BA,UAAS,WAAW,SAAS,+BAA+B,IAC5D;AACJ,QAAM,UAAU,WAAW,UAAU,CAAC,GACnC,OAAO,CAAC,UAAU,MAAM,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,CAAC,EACzD,MAAM,GAAG,uBAAuB,EAChC,IAAI,CAAC,WAAW;AAAA,IACf,MAAMA,UAAS,MAAM,KAAK,KAAK,GAAG,8BAA8B;AAAA,IAChE,OAAOA,UAAS,MAAM,MAAM,KAAK,GAAG,+BAA+B;AAAA,IACnE,QAAQ,MAAM,UAAU;AAAA,EAC1B,EAAE;AAEJ,QAAM,QAOF;AAAA,IACF;AAAA,IACA,OAAO;AAAA,IACP,QAAQ,EAAE,MAAM,qBAAqB;AAAA,IACrC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,MAAI,aAAa;AACf,UAAM,cAAc;AAAA,EACtB;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,SAAS;AAAA,EACjB;AAEA,QAAM,WAAW,MAAM,MAAM,YAAY;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,QAAQ,CAAC,KAAK;AAAA,IAChB,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,IAAI;AAAA,MACR,gCAAgC,SAAS,MAAM,MAAM,QAAQ,SAAS,UAAU;AAAA,IAClF;AAAA,EACF;AACF;;;ALhEA,eAAsB,cAA6B;AACjD,MAAI,QAAQ,SAAS;AAErB,QAAM,SAAS,MAAM,WAAW;AAEhC,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,kBAAkB,QAAQ,IAAI;AAEpC,MAAI,CAAC,eAAe,CAAC,iBAAiB;AACpC,QAAI;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,OAAO,QAAQ,WAAW,GAAG;AAC/B,QAAI,MAAM,8CAA8C;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,gBAAgB;AACpB,QAAM,uBAAiC,CAAC;AACxC,MAAI,gBAAgB;AACpB,MAAI,eAAe;AACnB,MAAI,iBAAiB;AACrB,MAAI,YAAY;AAChB,QAAM,iBAAiB,QAAQ,IAAI,aAAa,KAAK,KAAK;AAC1D,QAAM,6BAA6B,OAAO,QAAQ;AAAA,IAChD,CAAC,WAAW,OAAO,SAAS,KAAK,EAAE,SAAS;AAAA,EAC9C;AAEA,aAAW,UAAU,OAAO,SAAS;AACnC,QAAI;AACF,YAAM,WAAW;AAAA,QACf,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACF;AACA,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,uBAAiB;AACjB,2BAAqB,KAAK,mBAAmB,MAAM,CAAC;AACpD,uBAAiB,KAAK,SAAS;AAC/B,sBAAgB,KAAK,SAAS;AAC9B,wBAAkB,KAAK,UAAU;AAAA,IACnC,SAAS,KAAU;AACjB,UAAI,MAAM,IAAI,OAAO,SAAS,KAAK,IAAI,OAAO,EAAE;AAChD,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,MAAI,gBAAgB,GAAG;AACrB,UAAM,cAAc,kBAAkB,IAAI,WAAW;AACrD,UAAM,UAAU,YACZ,UAAU,aAAa,IAAI,WAAW,2BACtC,UAAU,aAAa,IAAI,WAAW;AAC1C,UAAM,OAAO,QAAQ;AAAA,MACnB,OAAO,YAAY,kCAAkC;AAAA,MACrD;AAAA,MACA,QAAQ;AAAA,QACN;AAAA,UACE,MAAM,qBAAqB,WAAW,IAAI,WAAW;AAAA,UACrD,OAAO,qBACJ,IAAI,CAAC,YAAY,KAAK,OAAO,EAAE,EAC/B,KAAK,IAAI;AAAA,QACd;AAAA,QACA,EAAE,MAAM,YAAY,OAAO,OAAO,aAAa,GAAG,QAAQ,KAAK;AAAA,QAC/D,EAAE,MAAM,WAAW,OAAO,OAAO,YAAY,GAAG,QAAQ,KAAK;AAAA,QAC7D,EAAE,MAAM,aAAa,OAAO,OAAO,cAAc,GAAG,QAAQ,KAAK;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,WAAW;AACb,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,QAAQ,qBAAqB;AACnC;AAEA,eAAe,WACb,QACA,aACA,iBACA,UACqB;AACrB,MAAI,QAAQ,WAAW,OAAO,SAAS,iBAAY,OAAO,MAAM,IAAI,OAAO,MAAM,EAAE;AAEnF,QAAM,YAAYE,MAAK,QAAQ,OAAO,SAAS;AAC/C,MAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,UAAM,IAAI,MAAM,cAAc,OAAO,SAAS,mBAAmB;AAAA,EACnE;AAEA,QAAM,SAAS,SAAS;AAAA,IACtB,QAAQ,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,KAAK,YAAY,OAAO,SAAS,MAAM;AAC3C,QAAM,aAAa,MAAM,qBAAqB,SAAS;AACvD,MAAI,KAAK,SAAS,WAAW,MAAM,cAAc;AAEjD,MAAI,KAAK,2BAA2B,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK;AACvE,QAAM,gBAAgB,MAAM;AAAA,IAC1B;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,cAAc,MAAM,iBAAiB;AAEvD,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAEA,MAAI;AAAA,IACF,GAAG,KAAK,SAAS,MAAM,eAAe,KAAK,SAAS,MAAM,eAAe,KAAK,UAAU,MAAM;AAAA,EAChG;AAEA,MAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,WAAW,GAAG;AAC5D,QAAI,QAAQ,IAAI,OAAO,SAAS,oBAAoB;AACpD,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,KAAK,UAAU;AAChC,UAAM,MAAM,OAAO,SACf,GAAG,OAAO,MAAM,IAAI,KAAK,YAAY,KACrC,KAAK;AACT,QAAI,IAAI,eAAe,GAAG,EAAE;AAC5B,UAAM,WAAW,QAAQ,OAAO,QAAQ,KAAK,KAAK,YAAY;AAAA,EAChE;AAEA,MAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,QAAI,QAAQ,IAAI,OAAO,SAAS,cAAc,KAAK,SAAS,MAAM,QAAQ;AAAA,EAC5E;AAEA,aAAW,OAAO,KAAK,UAAU;AAC/B,QAAI,IAAI,cAAc,IAAI,GAAG,EAAE;AAC/B,UAAM,aAAa,QAAQ,OAAO,QAAQ,IAAI,GAAG;AAAA,EACnD;AAEA,MAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,QAAI,QAAQ,IAAI,OAAO,SAAS,aAAa,KAAK,SAAS,MAAM,QAAQ;AAAA,EAC3E;AAEA,SAAO;AACT;AAEA,SAAS,gBACP,gBACA,gBACA,4BACoB;AACpB,QAAM,wBAAwB,eAAe,KAAK;AAClD,MAAI,sBAAuB,QAAO;AAClC,MAAI,CAAC,2BAA4B,QAAO;AACxC,SAAO;AACT;AAEA,SAAS,mBAAmB,QAA4B;AACtD,QAAM,aAAa,OAAO,OAAO,KAAK,IAClC,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,KACjC,GAAG,OAAO,MAAM;AACpB,SAAO,GAAG,OAAO,SAAS,QAAQ,UAAU;AAC9C;AAEA,eAAe,OACb,QACA,SACe;AACf,MAAI,OAAO,eAAe,OAAO;AAC/B,UAAM,aAAa,QAAQ,IAAI;AAC/B,QAAI,YAAY;AACd,UAAI;AACF,cAAM,sBAAsB,YAAY,OAAO;AAC/C,YAAI,QAAQ,yBAAyB;AAAA,MACvC,SAAS,KAAU;AACjB,YAAI,KAAK,8BAA8B,IAAI,OAAO,EAAE;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,eAAe,SAAS;AACjC,UAAM,aAAa,QAAQ,IAAI;AAC/B,QAAI,YAAY;AACd,UAAI;AACF,cAAM,wBAAwB,YAAY,OAAO;AACjD,YAAI,QAAQ,2BAA2B;AAAA,MACzC,SAAS,KAAU;AACjB,YAAI,KAAK,gCAAgC,IAAI,OAAO,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;;;ANtNA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,SAAS,EACd,YAAY,oEAAoE,EAChF,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,yCAAyC,EACrD,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,YAAY;AAAA,EACpB,SAAS,KAAU;AACjB,YAAQ,MAAM,IAAI,OAAO;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,SAAS,eAAe,sDAAsD,EAC9E,SAAS,QAAQ,yDAAyD,EAC1E,OAAO,mBAAmB,gBAAgB,EAC1C,OAAO,qBAAqB,YAAY,EACxC,OAAO,oBAAoB,+BAA+B,EAC1D,OAAO,qBAAqB,uBAAuB,EACnD,OAAO,qBAAqB,0BAA0B,EACtD,OAAO,YAAY,yCAAyC,EAC5D,OAAO,eAAe,uCAAuC,EAC7D,OAAO,WAAW,4BAA4B,EAC9C,OAAO,aAAa,8BAA8B,EAClD,OAAO,OAAO,WAAmB,UAAkB,YAAiB;AACnE,MAAI;AACF,UAAM,aAAa,WAAW;AAAA,MAC5B,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ,WAAW,OAAO,OAAO,QAAQ,WAAW,QAAQ,QAAQ;AAAA,MAC5E,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH,SAAS,KAAU;AACjB,YAAQ,MAAM,IAAI,OAAO;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":["writeFile","existsSync","path","path","existsSync","path","path","existsSync","writeFile","existsSync","path","readFile","path","readFile","truncate","normalizePayload","path","existsSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maydotinc/s3-sync",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI to sync local directories to S3-compatible buckets via GitHub Actions",
5
5
  "type": "module",
6
6
  "bin": {