drop2run 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # drop2run
2
2
 
3
- Publish a static site to [Drop2Run](https://dropto.run) from the command line.
3
+ Publish a page, a folder of documents or a built site to [Drop2Run](https://dropto.run) from the
4
+ command line — by hand, from CI, or from a coding agent.
4
5
 
5
6
  ```bash
6
7
  npm i -g drop2run
package/dist/index.js CHANGED
@@ -439,6 +439,485 @@ async function problemToError(response) {
439
439
  const message = typeof problem.detail === "string" ? problem.detail : typeof problem.title === "string" ? problem.title : `The server responded with ${response.status}.`;
440
440
  return new DeployError(code, message, problem);
441
441
  }
442
+ const FILE_TYPES = [
443
+ // Documents. The three extensions that make a folder a documents site.
444
+ {
445
+ ext: ".md",
446
+ mime: "text/markdown; charset=utf-8",
447
+ kind: "markdown",
448
+ document: true,
449
+ reader: true,
450
+ editable: true
451
+ },
452
+ {
453
+ ext: ".markdown",
454
+ mime: "text/markdown; charset=utf-8",
455
+ kind: "markdown",
456
+ document: true,
457
+ reader: true,
458
+ editable: true
459
+ },
460
+ {
461
+ ext: ".pdf",
462
+ mime: "application/pdf",
463
+ kind: "pdf",
464
+ document: true,
465
+ reader: true,
466
+ editable: false
467
+ },
468
+ // Office documents and ebooks. Each has its own kind because each needs its own renderer, loaded
469
+ // only when one is opened — `.docx` through mammoth, `.xlsx` through read-excel-file, `.epub`
470
+ // through foliate-js.
471
+ {
472
+ ext: ".docx",
473
+ mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
474
+ kind: "docx",
475
+ document: true,
476
+ reader: true,
477
+ editable: false
478
+ },
479
+ {
480
+ ext: ".xlsx",
481
+ mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
482
+ kind: "xlsx",
483
+ document: true,
484
+ reader: true,
485
+ editable: false
486
+ },
487
+ {
488
+ ext: ".epub",
489
+ mime: "application/epub+zip",
490
+ kind: "epub",
491
+ document: true,
492
+ reader: true,
493
+ editable: false
494
+ },
495
+ // Prose the reader shows preformatted.
496
+ {
497
+ ext: ".txt",
498
+ mime: "text/plain; charset=utf-8",
499
+ kind: "text",
500
+ document: false,
501
+ reader: true,
502
+ editable: true
503
+ },
504
+ {
505
+ ext: ".log",
506
+ mime: "text/plain; charset=utf-8",
507
+ kind: "text",
508
+ document: false,
509
+ reader: true,
510
+ editable: false
511
+ },
512
+ {
513
+ ext: ".csv",
514
+ mime: "text/csv; charset=utf-8",
515
+ kind: "text",
516
+ document: false,
517
+ reader: true,
518
+ editable: true
519
+ },
520
+ // Structured text. Read through the reader inside a folder of notes, which is why these carry
521
+ // `reader: true` while the source formats below do not.
522
+ {
523
+ ext: ".json",
524
+ mime: "application/json",
525
+ kind: "code",
526
+ document: false,
527
+ reader: true,
528
+ editable: true
529
+ },
530
+ {
531
+ ext: ".yaml",
532
+ mime: "text/yaml; charset=utf-8",
533
+ kind: "code",
534
+ document: false,
535
+ reader: true,
536
+ editable: true
537
+ },
538
+ {
539
+ ext: ".yml",
540
+ mime: "text/yaml; charset=utf-8",
541
+ kind: "code",
542
+ document: false,
543
+ reader: true,
544
+ editable: true
545
+ },
546
+ {
547
+ // No registered media type, so it takes text/plain rather than an invented one. The point is
548
+ // only that a browser shows it instead of downloading it.
549
+ ext: ".toml",
550
+ mime: "text/plain; charset=utf-8",
551
+ kind: "code",
552
+ document: false,
553
+ reader: true,
554
+ editable: true
555
+ },
556
+ // Source the viewer highlights. A link straight to one of these hands over the file.
557
+ {
558
+ ext: ".css",
559
+ mime: "text/css; charset=utf-8",
560
+ kind: "code",
561
+ document: false,
562
+ reader: false,
563
+ editable: true
564
+ },
565
+ {
566
+ ext: ".js",
567
+ mime: "text/javascript; charset=utf-8",
568
+ kind: "code",
569
+ document: false,
570
+ reader: false,
571
+ editable: true
572
+ },
573
+ {
574
+ ext: ".ts",
575
+ mime: null,
576
+ kind: "code",
577
+ document: false,
578
+ reader: false,
579
+ editable: false
580
+ },
581
+ {
582
+ ext: ".tsx",
583
+ mime: null,
584
+ kind: "code",
585
+ document: false,
586
+ reader: false,
587
+ editable: false
588
+ },
589
+ {
590
+ ext: ".jsx",
591
+ mime: null,
592
+ kind: "code",
593
+ document: false,
594
+ reader: false,
595
+ editable: false
596
+ },
597
+ {
598
+ ext: ".sh",
599
+ mime: null,
600
+ kind: "code",
601
+ document: false,
602
+ reader: false,
603
+ editable: false
604
+ },
605
+ // The languages an assistant writes most, beyond the web's own. Same row as `.ts`: shown as source
606
+ // when somebody opens one in the reader or publishes it on its own, handed over as a file when a
607
+ // link names it directly, and no media type — adding one changes what a browser does with a
608
+ // published file, which I10 makes a decision of its own rather than a side effect of this list.
609
+ {
610
+ ext: ".py",
611
+ mime: null,
612
+ kind: "code",
613
+ document: false,
614
+ reader: false,
615
+ editable: false
616
+ },
617
+ {
618
+ ext: ".sql",
619
+ mime: null,
620
+ kind: "code",
621
+ document: false,
622
+ reader: false,
623
+ editable: false
624
+ },
625
+ {
626
+ ext: ".go",
627
+ mime: null,
628
+ kind: "code",
629
+ document: false,
630
+ reader: false,
631
+ editable: false
632
+ },
633
+ {
634
+ ext: ".rs",
635
+ mime: null,
636
+ kind: "code",
637
+ document: false,
638
+ reader: false,
639
+ editable: false
640
+ },
641
+ {
642
+ ext: ".java",
643
+ mime: null,
644
+ kind: "code",
645
+ document: false,
646
+ reader: false,
647
+ editable: false
648
+ },
649
+ {
650
+ ext: ".kt",
651
+ mime: null,
652
+ kind: "code",
653
+ document: false,
654
+ reader: false,
655
+ editable: false
656
+ },
657
+ {
658
+ ext: ".swift",
659
+ mime: null,
660
+ kind: "code",
661
+ document: false,
662
+ reader: false,
663
+ editable: false
664
+ },
665
+ {
666
+ ext: ".rb",
667
+ mime: null,
668
+ kind: "code",
669
+ document: false,
670
+ reader: false,
671
+ editable: false
672
+ },
673
+ {
674
+ ext: ".php",
675
+ mime: null,
676
+ kind: "code",
677
+ document: false,
678
+ reader: false,
679
+ editable: false
680
+ },
681
+ {
682
+ ext: ".c",
683
+ mime: null,
684
+ kind: "code",
685
+ document: false,
686
+ reader: false,
687
+ editable: false
688
+ },
689
+ {
690
+ ext: ".h",
691
+ mime: null,
692
+ kind: "code",
693
+ document: false,
694
+ reader: false,
695
+ editable: false
696
+ },
697
+ {
698
+ ext: ".cpp",
699
+ mime: null,
700
+ kind: "code",
701
+ document: false,
702
+ reader: false,
703
+ editable: false
704
+ },
705
+ {
706
+ ext: ".cs",
707
+ mime: null,
708
+ kind: "code",
709
+ document: false,
710
+ reader: false,
711
+ editable: false
712
+ },
713
+ {
714
+ ext: ".r",
715
+ mime: null,
716
+ kind: "code",
717
+ document: false,
718
+ reader: false,
719
+ editable: false
720
+ },
721
+ {
722
+ ext: ".xml",
723
+ mime: "application/xml",
724
+ kind: "code",
725
+ document: false,
726
+ reader: false,
727
+ editable: true
728
+ },
729
+ // Pictures, shown with an img element.
730
+ {
731
+ ext: ".png",
732
+ mime: "image/png",
733
+ kind: "image",
734
+ document: false,
735
+ reader: false,
736
+ editable: false
737
+ },
738
+ {
739
+ ext: ".jpg",
740
+ mime: "image/jpeg",
741
+ kind: "image",
742
+ document: false,
743
+ reader: false,
744
+ editable: false
745
+ },
746
+ {
747
+ ext: ".jpeg",
748
+ mime: "image/jpeg",
749
+ kind: "image",
750
+ document: false,
751
+ reader: false,
752
+ editable: false
753
+ },
754
+ {
755
+ ext: ".gif",
756
+ mime: "image/gif",
757
+ kind: "image",
758
+ document: false,
759
+ reader: false,
760
+ editable: false
761
+ },
762
+ {
763
+ ext: ".webp",
764
+ mime: "image/webp",
765
+ kind: "image",
766
+ document: false,
767
+ reader: false,
768
+ editable: false
769
+ },
770
+ {
771
+ ext: ".avif",
772
+ mime: "image/avif",
773
+ kind: "image",
774
+ document: false,
775
+ reader: false,
776
+ editable: false
777
+ },
778
+ {
779
+ // Text, hand-edited in practice, and script-carrying. Editable anyway: the Worker derives the
780
+ // type at serve time (I10) and already serves whatever was uploaded, so withholding the edit
781
+ // would withhold nothing else.
782
+ ext: ".svg",
783
+ mime: "image/svg+xml",
784
+ kind: "image",
785
+ document: false,
786
+ reader: false,
787
+ editable: true
788
+ },
789
+ {
790
+ ext: ".ico",
791
+ mime: "image/x-icon",
792
+ kind: "image",
793
+ document: false,
794
+ reader: false,
795
+ editable: false
796
+ },
797
+ // A customer's own pages. Never rendered inside the reader.
798
+ {
799
+ ext: ".html",
800
+ mime: "text/html; charset=utf-8",
801
+ kind: "html",
802
+ document: false,
803
+ reader: false,
804
+ editable: true
805
+ },
806
+ {
807
+ // ⚠️ No media type, and that is the state of the code rather than a decision: `TYPES` in
808
+ // `headers.ts` has never held `htm`, so a published `.htm` is served as octet-stream and
809
+ // downloads instead of rendering. Recorded here rather than fixed, because this table was
810
+ // introduced as a refactor and a refactor that changes what a URL answers is not one.
811
+ ext: ".htm",
812
+ mime: null,
813
+ kind: "html",
814
+ document: false,
815
+ reader: false,
816
+ editable: true
817
+ },
818
+ // Everything below renders as nothing — the viewer offers a download — but still needs a media
819
+ // type, because these are the files a static site is built out of.
820
+ {
821
+ ext: ".mjs",
822
+ mime: "text/javascript; charset=utf-8",
823
+ kind: "binary",
824
+ document: false,
825
+ reader: false,
826
+ editable: true
827
+ },
828
+ {
829
+ ext: ".cjs",
830
+ mime: null,
831
+ kind: "binary",
832
+ document: false,
833
+ reader: false,
834
+ editable: true
835
+ },
836
+ {
837
+ ext: ".map",
838
+ mime: "application/json",
839
+ kind: "binary",
840
+ document: false,
841
+ reader: false,
842
+ editable: false
843
+ },
844
+ {
845
+ ext: ".wasm",
846
+ mime: "application/wasm",
847
+ kind: "binary",
848
+ document: false,
849
+ reader: false,
850
+ editable: false
851
+ },
852
+ {
853
+ ext: ".woff2",
854
+ mime: "font/woff2",
855
+ kind: "binary",
856
+ document: false,
857
+ reader: false,
858
+ editable: false
859
+ },
860
+ {
861
+ ext: ".woff",
862
+ mime: "font/woff",
863
+ kind: "binary",
864
+ document: false,
865
+ reader: false,
866
+ editable: false
867
+ },
868
+ {
869
+ ext: ".ttf",
870
+ mime: "font/ttf",
871
+ kind: "binary",
872
+ document: false,
873
+ reader: false,
874
+ editable: false
875
+ },
876
+ {
877
+ ext: ".mp4",
878
+ mime: "video/mp4",
879
+ kind: "binary",
880
+ document: false,
881
+ reader: false,
882
+ editable: false
883
+ },
884
+ {
885
+ ext: ".webm",
886
+ mime: "video/webm",
887
+ kind: "binary",
888
+ document: false,
889
+ reader: false,
890
+ editable: false
891
+ }
892
+ ];
893
+ function extensionsWhere(include) {
894
+ return FILE_TYPES.filter(include).map((type) => type.ext);
895
+ }
896
+ const DOCUMENT_EXTENSIONS$1 = extensionsWhere((type) => type.document);
897
+ const VIEWABLE_EXTENSIONS$1 = extensionsWhere(
898
+ (type) => type.kind !== "binary"
899
+ );
900
+ extensionsWhere((type) => type.reader);
901
+ extensionsWhere((type) => type.editable);
902
+ const WEB_ASSET_EXTENSIONS = [
903
+ ".html",
904
+ ".htm",
905
+ ".js",
906
+ ".mjs",
907
+ ".cjs",
908
+ ".css"
909
+ ];
910
+ const GALLERY_EXTENSIONS = extensionsWhere(
911
+ (type) => type.kind === "image"
912
+ );
913
+ Object.fromEntries(
914
+ FILE_TYPES.filter((type) => type.mime !== null).map((type) => [type.ext, type.mime])
915
+ );
916
+ function extensionOf(path) {
917
+ const name = path.slice(path.lastIndexOf("/") + 1);
918
+ const dot = name.lastIndexOf(".");
919
+ return dot <= 0 ? "" : name.slice(dot).toLowerCase();
920
+ }
442
921
  async function sha256Hex(bytes) {
443
922
  const digest = await crypto.subtle.digest("SHA-256", bytes.slice().buffer);
444
923
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -470,52 +949,29 @@ function shouldIgnore(path) {
470
949
  return segments.some((segment) => IGNORED_DIRECTORIES.includes(segment));
471
950
  }
472
951
  const REQUIRED_INDEX = "index.html";
473
- const DOCUMENT_EXTENSIONS = [".md", ".markdown", ".pdf"];
952
+ const DOCUMENT_EXTENSIONS = DOCUMENT_EXTENSIONS$1;
474
953
  function isDocumentPath(path) {
475
954
  const lower = path.toLowerCase();
476
955
  return DOCUMENT_EXTENSIONS.some((extension) => lower.endsWith(extension));
477
956
  }
478
- const VIEWABLE_EXTENSIONS = [
479
- // Documents — these also count at any depth, via DOCUMENT_EXTENSIONS.
480
- ".md",
481
- ".markdown",
482
- ".pdf",
483
- // Prose the reader shows preformatted.
484
- ".txt",
485
- ".log",
486
- ".csv",
487
- // Source the reader shows with highlighting.
488
- ".json",
489
- ".yaml",
490
- ".yml",
491
- ".toml",
492
- ".css",
493
- ".js",
494
- ".ts",
495
- ".tsx",
496
- ".jsx",
497
- ".sh",
498
- ".xml",
499
- // Pictures the reader shows with an img element.
500
- ".png",
501
- ".jpg",
502
- ".jpeg",
503
- ".gif",
504
- ".webp",
505
- ".avif",
506
- ".svg",
507
- ".ico",
508
- // See the note above: a floor for a caller that did not rename it.
509
- ".html",
510
- ".htm"
511
- ];
957
+ const VIEWABLE_EXTENSIONS = VIEWABLE_EXTENSIONS$1;
512
958
  function isViewablePath(path) {
513
959
  const lower = path.toLowerCase();
514
960
  return VIEWABLE_EXTENSIONS.some((extension) => lower.endsWith(extension));
515
961
  }
962
+ function isImageFolder(paths) {
963
+ let pictures = false;
964
+ for (const path of paths) {
965
+ const ext = extensionOf(path);
966
+ if (WEB_ASSET_EXTENSIONS.includes(ext)) return false;
967
+ if (GALLERY_EXTENSIONS.includes(ext)) pictures = true;
968
+ }
969
+ return pictures;
970
+ }
516
971
  function canPublish(paths) {
517
972
  if (paths.some((path) => path === REQUIRED_INDEX)) return true;
518
973
  if (paths.some(isDocumentPath)) return true;
974
+ if (isImageFolder(paths)) return true;
519
975
  return paths.length === 1 && paths[0] !== void 0 && isViewablePath(paths[0]);
520
976
  }
521
977
  function renameOfLoneHtmlPage(paths) {
@@ -544,37 +1000,42 @@ function checkLimits(files, limits) {
544
1000
  `A site needs an ${REQUIRED_INDEX} at its top level, or at least one .md or .pdf file to publish as a documents site — or be a single file that can be displayed on its own, such as a note, a PDF, a page or an image. Drop the folder that contains it, not the folder above.`
545
1001
  );
546
1002
  }
547
- if (!limits) return;
1003
+ const refusal = sizeRefusal(
1004
+ files.map((file) => ({ path: file.path, size: file.bytes.length })),
1005
+ limits
1006
+ );
1007
+ if (refusal) throw refusal;
1008
+ }
1009
+ function sizeRefusal(files, limits) {
1010
+ if (!limits) return null;
548
1011
  if (files.length > limits.maxFiles) {
549
- throw new DeployError(
1012
+ return new DeployError(
550
1013
  ClientErrorCode.TooManyFiles,
551
1014
  `That is ${files.length} files, and this plan allows ${limits.maxFiles}.`,
552
1015
  { limit: limits.maxFiles, actual: files.length }
553
1016
  );
554
1017
  }
555
- const oversized = files.find((file) => file.bytes.length > limits.maxFileBytes);
1018
+ const oversized = files.find((file) => file.size > limits.maxFileBytes);
556
1019
  if (oversized) {
557
- throw new DeployError(
1020
+ return new DeployError(
558
1021
  ClientErrorCode.FileTooLarge,
559
- `${oversized.path} is ${formatBytes$1(oversized.bytes.length)}, and this plan allows ${formatBytes$1(limits.maxFileBytes)} per file.`,
1022
+ `${oversized.path} is ${formatBytes$1(oversized.size)}, and this plan allows ${formatBytes$1(limits.maxFileBytes)} per file.`,
560
1023
  {
561
1024
  path: oversized.path,
562
1025
  limit: limits.maxFileBytes,
563
- actual: oversized.bytes.length
1026
+ actual: oversized.size
564
1027
  }
565
1028
  );
566
1029
  }
567
- const total = totalBytes(files);
1030
+ const total = files.reduce((sum, file) => sum + file.size, 0);
568
1031
  if (limits.maxSiteBytes !== null && total > limits.maxSiteBytes) {
569
- throw new DeployError(
1032
+ return new DeployError(
570
1033
  ClientErrorCode.TooLarge,
571
1034
  `That drop is ${formatBytes$1(total)}, and this plan allows ${formatBytes$1(limits.maxSiteBytes)}.`,
572
1035
  { limit: limits.maxSiteBytes, actual: total }
573
1036
  );
574
1037
  }
575
- }
576
- function totalBytes(files) {
577
- return files.reduce((sum, file) => sum + file.bytes.length, 0);
1038
+ return null;
578
1039
  }
579
1040
  const MAX_NAME_LENGTH = 60;
580
1041
  const HEAD_BYTES = 64 * 1024;
@@ -1388,7 +1849,7 @@ async function run(argv) {
1388
1849
  const positional = positionals(argv);
1389
1850
  const [command, ...rest] = positional;
1390
1851
  if (argv.includes("--version")) {
1391
- const { version } = await import("./package-sLisZctX.js").then(
1852
+ const { version } = await import("./package-DXmy47q4.js").then(
1392
1853
  (module) => module.default
1393
1854
  );
1394
1855
  return { text: version, json: { version }, code: 0 };
@@ -1,6 +1,6 @@
1
1
  const name = "drop2run";
2
- const version = "0.4.1";
3
- const description = "Publish a static site to Drop2Run from the command line.";
2
+ const version = "0.5.0";
3
+ const description = "Publish a page, a folder of documents or a built site to Drop2Run from the command line.";
4
4
  const license = "MIT";
5
5
  const type = "module";
6
6
  const bin = { "drop2run": "bin/drop2run.mjs" };
@@ -10,7 +10,7 @@ const publishConfig = { "access": "public" };
10
10
  const homepage = "https://dropto.run";
11
11
  const repository = { "type": "git", "url": "git+https://github.com/Drop-to-run/drop2run-cli.git" };
12
12
  const bugs = { "url": "https://github.com/Drop-to-run/drop2run-cli/issues" };
13
- const keywords = ["static-site", "deploy", "hosting", "cli", "ai", "claude", "agent", "llm"];
13
+ const keywords = ["static-site", "markdown", "pdf", "deploy", "hosting", "cli", "ai", "claude", "agent", "llm"];
14
14
  const scripts = { "prepublishOnly": "npm run build && npm test", "build": "vite build", "typecheck": "tsc --noEmit", "test": "vitest run", "lint": "biome check .", "format": "biome check --write ." };
15
15
  const devDependencies = { "@biomejs/biome": "^2.5.11", "@types/node": "^26.4.0", "typescript": "^5.9.3", "vite": "^7.2.1", "vitest": "^4.1.11" };
16
16
  const _package = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "drop2run",
3
- "version": "0.4.1",
4
- "description": "Publish a static site to Drop2Run from the command line.",
3
+ "version": "0.5.0",
4
+ "description": "Publish a page, a folder of documents or a built site to Drop2Run from the command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -27,6 +27,8 @@
27
27
  },
28
28
  "keywords": [
29
29
  "static-site",
30
+ "markdown",
31
+ "pdf",
30
32
  "deploy",
31
33
  "hosting",
32
34
  "cli",