Originally the CLI was using Bun for a lot of the file reading and writing, and building the CLI was using the --compile flag to create a single binary. While it looked like this was working initially, the problem was the architecture was being defaulted to my personal machine rather than being cross platform. Bun has the ability to create cross platform binaries, but then the problem becomes distributing those binaries through install scripts and a whole bunch of other fun stuff. Since we originally launched through npm we will stick with a Node build for now, but will highly consider going back to bun and doing binaries in the future.
+38
-10
bun.lock
+38
-10
bun.lock
···
24
24
},
25
25
"packages/cli": {
26
26
"name": "sequoia-cli",
27
-
"version": "0.0.6",
27
+
"version": "0.1.0",
28
28
"bin": {
29
-
"sequoia": "dist/sequoia",
29
+
"sequoia": "dist/index.js",
30
30
},
31
31
"dependencies": {
32
32
"@atproto/api": "^0.18.17",
33
33
"@clack/prompts": "^1.0.0",
34
34
"cmd-ts": "^0.14.3",
35
+
"glob": "^13.0.0",
36
+
"mime-types": "^2.1.35",
37
+
"minimatch": "^10.1.1",
35
38
},
36
39
"devDependencies": {
37
-
"@types/bun": "latest",
40
+
"@types/mime-types": "^3.0.1",
41
+
"@types/node": "^20",
38
42
},
39
43
"peerDependencies": {
40
44
"typescript": "^5",
···
188
192
189
193
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
190
194
195
+
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
196
+
197
+
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
198
+
191
199
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
192
200
193
201
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
···
524
532
525
533
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
526
534
535
+
"@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="],
536
+
527
537
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
528
538
529
-
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
539
+
"@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="],
530
540
531
541
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
532
542
···
834
844
835
845
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
836
846
847
+
"glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
848
+
837
849
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
838
850
839
851
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
···
1094
1106
1095
1107
"mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
1096
1108
1097
-
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1109
+
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
1110
+
1111
+
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
1098
1112
1099
1113
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
1100
1114
1101
1115
"mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
1102
1116
1117
+
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
1118
+
1119
+
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
1120
+
1103
1121
"minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="],
1104
1122
1105
1123
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
···
1150
1168
1151
1169
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
1152
1170
1171
+
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
1172
+
1153
1173
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
1154
1174
1155
1175
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
···
1336
1356
1337
1357
"uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
1338
1358
1339
-
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1359
+
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
1340
1360
1341
1361
"unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="],
1342
1362
···
1442
1462
1443
1463
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
1444
1464
1465
+
"bun-types/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1466
+
1445
1467
"chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
1446
1468
1469
+
"compressible/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1470
+
1447
1471
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1448
1472
1449
1473
"create-vocs/@clack/prompts": ["@clack/prompts@0.7.0", "", { "dependencies": { "@clack/core": "^0.3.3", "is-unicode-supported": "*", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA=="],
···
1456
1480
1457
1481
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
1458
1482
1483
+
"eval/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1484
+
1459
1485
"hast-util-from-dom/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
1460
1486
1461
1487
"hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
···
1474
1500
1475
1501
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
1476
1502
1503
+
"path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
1504
+
1477
1505
"radix-ui/@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
1478
1506
1479
1507
"rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1480
1508
1481
1509
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1482
1510
1483
-
"sequoia-cli/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
1484
-
1485
1511
"vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1486
1512
1487
1513
"@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
1488
1514
1489
1515
"@shikijs/twoslash/twoslash/twoslash-protocol": ["twoslash-protocol@0.2.12", "", {}, "sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg=="],
1490
1516
1517
+
"bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1518
+
1491
1519
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1492
1520
1493
1521
"create-vocs/@clack/prompts/@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="],
···
1498
1526
1499
1527
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
1500
1528
1529
+
"eval/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1530
+
1501
1531
"hast-util-from-dom/hastscript/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
1502
1532
1503
1533
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
1504
1534
1505
1535
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1506
-
1507
-
"sequoia-cli/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
1508
1536
}
1509
1537
}
+10
-7
packages/cli/package.json
+10
-7
packages/cli/package.json
···
1
1
{
2
2
"name": "sequoia-cli",
3
3
"version": "0.1.0",
4
-
"module": "dist/index.js",
5
4
"type": "module",
6
5
"bin": {
7
-
"sequoia": "dist/sequoia"
6
+
"sequoia": "dist/index.js"
8
7
},
9
8
"files": [
10
9
"dist",
11
10
"README.md"
12
11
],
13
-
"main": "./dist/sequoia",
12
+
"main": "./dist/index.js",
14
13
"exports": {
15
-
".": "./dist/sequoia"
14
+
".": "./dist/index.js"
16
15
},
17
16
"scripts": {
18
-
"build": "bun build src/index.ts --compile --outfile dist/sequoia",
17
+
"build": "bun build src/index.ts --target node --outdir dist",
19
18
"dev": "bun run build && bun link",
20
19
"deploy": "bun run build && bun publish"
21
20
},
22
21
"devDependencies": {
23
-
"@types/bun": "latest"
22
+
"@types/mime-types": "^3.0.1",
23
+
"@types/node": "^20"
24
24
},
25
25
"peerDependencies": {
26
26
"typescript": "^5"
27
27
},
28
28
"dependencies": {
29
29
"@atproto/api": "^0.18.17",
30
+
"@clack/prompts": "^1.0.0",
30
31
"cmd-ts": "^0.14.3",
31
-
"@clack/prompts": "^1.0.0"
32
+
"glob": "^13.0.0",
33
+
"mime-types": "^2.1.35",
34
+
"minimatch": "^10.1.1"
32
35
}
33
36
}
+18
-8
packages/cli/src/commands/init.ts
+18
-8
packages/cli/src/commands/init.ts
···
1
+
import * as fs from "fs/promises";
1
2
import { command } from "cmd-ts";
2
3
import {
3
4
intro,
···
16
17
import { createAgent, createPublication } from "../lib/atproto";
17
18
import type { FrontmatterMapping } from "../lib/types";
18
19
20
+
async function fileExists(filePath: string): Promise<boolean> {
21
+
try {
22
+
await fs.access(filePath);
23
+
return true;
24
+
} catch {
25
+
return false;
26
+
}
27
+
}
28
+
19
29
const onCancel = () => {
20
30
outro("Setup cancelled");
21
31
process.exit(0);
···
270
280
});
271
281
272
282
const configPath = path.join(process.cwd(), "sequoia.json");
273
-
await Bun.write(configPath, configContent);
283
+
await fs.writeFile(configPath, configContent);
274
284
275
285
log.success(`Configuration saved to ${configPath}`);
276
286
···
283
293
const wellKnownPath = path.join(wellKnownDir, "site.standard.publication");
284
294
285
295
// Ensure .well-known directory exists
286
-
await Bun.write(path.join(wellKnownDir, ".gitkeep"), "");
287
-
await Bun.write(wellKnownPath, publicationUri);
296
+
await fs.mkdir(wellKnownDir, { recursive: true });
297
+
await fs.writeFile(path.join(wellKnownDir, ".gitkeep"), "");
298
+
await fs.writeFile(wellKnownPath, publicationUri);
288
299
289
300
log.success(`Created ${wellKnownPath}`);
290
301
291
302
// Update .gitignore
292
303
const gitignorePath = path.join(process.cwd(), ".gitignore");
293
-
const gitignoreFile = Bun.file(gitignorePath);
294
304
const stateFilename = ".sequoia-state.json";
295
305
296
-
if (await gitignoreFile.exists()) {
297
-
const gitignoreContent = await gitignoreFile.text();
306
+
if (await fileExists(gitignorePath)) {
307
+
const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
298
308
if (!gitignoreContent.includes(stateFilename)) {
299
-
await Bun.write(
309
+
await fs.writeFile(
300
310
gitignorePath,
301
311
gitignoreContent + `\n${stateFilename}\n`,
302
312
);
303
313
log.info(`Added ${stateFilename} to .gitignore`);
304
314
}
305
315
} else {
306
-
await Bun.write(gitignorePath, `${stateFilename}\n`);
316
+
await fs.writeFile(gitignorePath, `${stateFilename}\n`);
307
317
log.info(`Created .gitignore with ${stateFilename}`);
308
318
}
309
319
+11
-12
packages/cli/src/commands/inject.ts
+11
-12
packages/cli/src/commands/inject.ts
···
1
+
import * as fs from "fs/promises";
1
2
import { command, flag, option, optional, string } from "cmd-ts";
2
3
import { log } from "@clack/prompts";
3
4
import * as path from "path";
4
-
import { Glob } from "bun";
5
+
import { glob } from "glob";
5
6
import { loadConfig, loadState, findConfig } from "../lib/config";
6
7
7
8
export const injectCommand = command({
···
97
98
log.info(`Found ${pathToAtUri.size} published posts in state`);
98
99
99
100
// Scan for HTML files
100
-
const glob = new Glob("**/*.html");
101
-
const htmlFiles: string[] = [];
102
-
103
-
for await (const file of glob.scan(resolvedOutputDir)) {
104
-
htmlFiles.push(path.join(resolvedOutputDir, file));
105
-
}
101
+
const htmlFiles = await glob("**/*.html", {
102
+
cwd: resolvedOutputDir,
103
+
absolute: false,
104
+
});
106
105
107
106
if (htmlFiles.length === 0) {
108
107
log.warn(`No HTML files found in ${resolvedOutputDir}`);
···
115
114
let skippedCount = 0;
116
115
let alreadyHasCount = 0;
117
116
118
-
for (const htmlPath of htmlFiles) {
117
+
for (const file of htmlFiles) {
118
+
const htmlPath = path.join(resolvedOutputDir, file);
119
119
// Try to match this HTML file to a published post
120
-
const relativePath = path.relative(resolvedOutputDir, htmlPath);
120
+
const relativePath = file;
121
121
const htmlDir = path.dirname(relativePath);
122
122
const htmlBasename = path.basename(relativePath, ".html");
123
123
···
152
152
}
153
153
154
154
// Read the HTML file
155
-
const file = Bun.file(htmlPath);
156
-
let content = await file.text();
155
+
let content = await fs.readFile(htmlPath, "utf-8");
157
156
158
157
// Check if link tag already exists
159
158
const linkTag = `<link rel="site.standard.document" href="${atUri}">`;
···
184
183
`${indent}${linkTag}\n${indent}` +
185
184
content.slice(headCloseIndex);
186
185
187
-
await Bun.write(htmlPath, content);
186
+
await fs.writeFile(htmlPath, content);
188
187
log.success(` Injected into: ${relativePath}`);
189
188
injectedCount++;
190
189
}
+3
-2
packages/cli/src/commands/publish.ts
+3
-2
packages/cli/src/commands/publish.ts
···
1
+
import * as fs from "fs/promises";
1
2
import { command, flag } from "cmd-ts";
2
3
import { select, spinner, log } from "@clack/prompts";
3
4
import * as path from "path";
···
164
165
// Handle cover image upload
165
166
let coverImage: BlobObject | undefined;
166
167
if (post.frontmatter.ogImage) {
167
-
const imagePath = resolveImagePath(
168
+
const imagePath = await resolveImagePath(
168
169
post.frontmatter.ogImage,
169
170
imagesDir,
170
171
contentDir
···
191
192
192
193
// Update frontmatter with atUri
193
194
const updatedContent = updateFrontmatterWithAtUri(post.rawContent, atUri);
194
-
await Bun.write(post.filePath, updatedContent);
195
+
await fs.writeFile(post.filePath, updatedContent);
195
196
log.info(` Updated frontmatter in ${path.basename(post.filePath)}`);
196
197
197
198
// Use updated content (with atUri) for hash so next run sees matching hash
+3
-3
packages/cli/src/commands/sync.ts
+3
-3
packages/cli/src/commands/sync.ts
···
1
+
import * as fs from "fs/promises";
1
2
import { command, flag } from "cmd-ts";
2
3
import { select, spinner, log } from "@clack/prompts";
3
4
import * as path from "path";
···
169
170
if (frontmatterUpdates.length > 0) {
170
171
s.start(`Updating frontmatter in ${frontmatterUpdates.length} files...`);
171
172
for (const { filePath, atUri } of frontmatterUpdates) {
172
-
const file = Bun.file(filePath);
173
-
const content = await file.text();
173
+
const content = await fs.readFile(filePath, "utf-8");
174
174
const updated = updateFrontmatterWithAtUri(content, atUri);
175
-
await Bun.write(filePath, updated);
175
+
await fs.writeFile(filePath, updated);
176
176
log.message(` Updated: ${path.basename(filePath)}`);
177
177
}
178
178
s.stop("Frontmatter updated");
+1
-1
packages/cli/src/index.ts
+1
-1
packages/cli/src/index.ts
+20
-15
packages/cli/src/lib/atproto.ts
+20
-15
packages/cli/src/lib/atproto.ts
···
1
1
import { AtpAgent } from "@atproto/api";
2
+
import * as fs from "fs/promises";
2
3
import * as path from "path";
4
+
import * as mimeTypes from "mime-types";
3
5
import type { Credentials, BlogPost, BlobObject, PublisherConfig } from "./types";
4
6
import { stripMarkdownForText } from "./markdown";
5
7
8
+
async function fileExists(filePath: string): Promise<boolean> {
9
+
try {
10
+
await fs.access(filePath);
11
+
return true;
12
+
} catch {
13
+
return false;
14
+
}
15
+
}
16
+
6
17
export async function resolveHandleToPDS(handle: string): Promise<string> {
7
18
// First, resolve the handle to a DID
8
19
let did: string;
···
87
98
agent: AtpAgent,
88
99
imagePath: string
89
100
): Promise<BlobObject | undefined> {
90
-
const file = Bun.file(imagePath);
91
-
92
-
if (!(await file.exists())) {
101
+
if (!(await fileExists(imagePath))) {
93
102
return undefined;
94
103
}
95
104
96
105
try {
97
-
const imageBuffer = await file.arrayBuffer();
98
-
const mimeType = file.type || "application/octet-stream";
106
+
const imageBuffer = await fs.readFile(imagePath);
107
+
const mimeType = mimeTypes.lookup(imagePath) || "application/octet-stream";
99
108
100
109
const response = await agent.com.atproto.repo.uploadBlob(
101
110
new Uint8Array(imageBuffer),
···
118
127
}
119
128
}
120
129
121
-
export function resolveImagePath(
130
+
export async function resolveImagePath(
122
131
ogImage: string,
123
132
imagesDir: string | undefined,
124
133
contentDir: string
125
-
): string | null {
134
+
): Promise<string | null> {
126
135
// Try multiple resolution strategies
127
136
const filename = path.basename(ogImage);
128
137
129
138
// 1. If imagesDir is specified, look there
130
139
if (imagesDir) {
131
140
const imagePath = path.join(imagesDir, filename);
132
-
try {
133
-
const stat = Bun.file(imagePath);
141
+
if (await fileExists(imagePath)) {
142
+
const stat = await fs.stat(imagePath);
134
143
if (stat.size > 0) {
135
144
return imagePath;
136
145
}
137
-
} catch {
138
-
// File doesn't exist, continue
139
146
}
140
147
}
141
148
···
146
153
147
154
// 3. Try relative to content directory
148
155
const contentRelative = path.join(contentDir, ogImage);
149
-
try {
150
-
const stat = Bun.file(contentRelative);
156
+
if (await fileExists(contentRelative)) {
157
+
const stat = await fs.stat(contentRelative);
151
158
if (stat.size > 0) {
152
159
return contentRelative;
153
160
}
154
-
} catch {
155
-
// File doesn't exist
156
161
}
157
162
158
163
return null;
+15
-8
packages/cli/src/lib/config.ts
+15
-8
packages/cli/src/lib/config.ts
···
1
+
import * as fs from "fs/promises";
1
2
import * as path from "path";
2
3
import type { PublisherConfig, PublisherState, FrontmatterMapping } from "./types";
3
4
4
5
const CONFIG_FILENAME = "sequoia.json";
5
6
const STATE_FILENAME = ".sequoia-state.json";
6
7
8
+
async function fileExists(filePath: string): Promise<boolean> {
9
+
try {
10
+
await fs.access(filePath);
11
+
return true;
12
+
} catch {
13
+
return false;
14
+
}
15
+
}
16
+
7
17
export async function findConfig(
8
18
startDir: string = process.cwd(),
9
19
): Promise<string | null> {
···
11
21
12
22
while (true) {
13
23
const configPath = path.join(currentDir, CONFIG_FILENAME);
14
-
const file = Bun.file(configPath);
15
24
16
-
if (await file.exists()) {
25
+
if (await fileExists(configPath)) {
17
26
return configPath;
18
27
}
19
28
···
38
47
}
39
48
40
49
try {
41
-
const file = Bun.file(resolvedPath);
42
-
const content = await file.text();
50
+
const content = await fs.readFile(resolvedPath, "utf-8");
43
51
const config = JSON.parse(content) as PublisherConfig;
44
52
45
53
// Validate required fields
···
109
117
110
118
export async function loadState(configDir: string): Promise<PublisherState> {
111
119
const statePath = path.join(configDir, STATE_FILENAME);
112
-
const file = Bun.file(statePath);
113
120
114
-
if (!(await file.exists())) {
121
+
if (!(await fileExists(statePath))) {
115
122
return { posts: {} };
116
123
}
117
124
118
125
try {
119
-
const content = await file.text();
126
+
const content = await fs.readFile(statePath, "utf-8");
120
127
return JSON.parse(content) as PublisherState;
121
128
} catch {
122
129
return { posts: {} };
···
128
135
state: PublisherState,
129
136
): Promise<void> {
130
137
const statePath = path.join(configDir, STATE_FILENAME);
131
-
await Bun.write(statePath, JSON.stringify(state, null, 2));
138
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
132
139
}
133
140
134
141
export function getStatePath(configDir: string): string {
+15
-6
packages/cli/src/lib/credentials.ts
+15
-6
packages/cli/src/lib/credentials.ts
···
1
+
import * as fs from "fs/promises";
1
2
import * as path from "path";
2
3
import * as os from "os";
3
4
import type { Credentials } from "./types";
···
8
9
// Stored credentials keyed by identifier
9
10
type CredentialsStore = Record<string, Credentials>;
10
11
12
+
async function fileExists(filePath: string): Promise<boolean> {
13
+
try {
14
+
await fs.access(filePath);
15
+
return true;
16
+
} catch {
17
+
return false;
18
+
}
19
+
}
20
+
11
21
/**
12
22
* Load all stored credentials
13
23
*/
14
24
async function loadCredentialsStore(): Promise<CredentialsStore> {
15
-
const file = Bun.file(CREDENTIALS_FILE);
16
-
if (!(await file.exists())) {
25
+
if (!(await fileExists(CREDENTIALS_FILE))) {
17
26
return {};
18
27
}
19
28
20
29
try {
21
-
const content = await file.text();
30
+
const content = await fs.readFile(CREDENTIALS_FILE, "utf-8");
22
31
const parsed = JSON.parse(content);
23
32
24
33
// Handle legacy single-credential format (migrate on read)
···
37
46
* Save the entire credentials store
38
47
*/
39
48
async function saveCredentialsStore(store: CredentialsStore): Promise<void> {
40
-
await Bun.$`mkdir -p ${CONFIG_DIR}`;
41
-
await Bun.write(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
42
-
await Bun.$`chmod 600 ${CREDENTIALS_FILE}`;
49
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
50
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
51
+
await fs.chmod(CREDENTIALS_FILE, 0o600);
43
52
}
44
53
45
54
/**
+9
-9
packages/cli/src/lib/markdown.ts
+9
-9
packages/cli/src/lib/markdown.ts
···
1
+
import * as fs from "fs/promises";
1
2
import * as path from "path";
2
-
import { Glob } from "bun";
3
+
import { glob } from "glob";
4
+
import { minimatch } from "minimatch";
3
5
import type { PostFrontmatter, BlogPost, FrontmatterMapping } from "./types";
4
6
5
7
export function parseFrontmatter(content: string, mapping?: FrontmatterMapping): {
···
120
122
121
123
function shouldIgnore(relativePath: string, ignorePatterns: string[]): boolean {
122
124
for (const pattern of ignorePatterns) {
123
-
const glob = new Glob(pattern);
124
-
if (glob.match(relativePath)) {
125
+
if (minimatch(relativePath, pattern)) {
125
126
return true;
126
127
}
127
128
}
···
137
138
const posts: BlogPost[] = [];
138
139
139
140
for (const pattern of patterns) {
140
-
const glob = new Glob(pattern);
141
-
142
-
for await (const relativePath of glob.scan({
141
+
const files = await glob(pattern, {
143
142
cwd: contentDir,
144
143
absolute: false,
145
-
})) {
144
+
});
145
+
146
+
for (const relativePath of files) {
146
147
// Skip files matching ignore patterns
147
148
if (shouldIgnore(relativePath, ignorePatterns)) {
148
149
continue;
149
150
}
150
151
151
152
const filePath = path.join(contentDir, relativePath);
152
-
const file = Bun.file(filePath);
153
-
const rawContent = await file.text();
153
+
const rawContent = await fs.readFile(filePath, "utf-8");
154
154
155
155
try {
156
156
const { frontmatter, body } = parseFrontmatter(rawContent, frontmatterMapping);
+11
-20
packages/cli/tsconfig.json
+11
-20
packages/cli/tsconfig.json
···
1
1
{
2
2
"compilerOptions": {
3
-
// Environment setup & latest features
4
-
"lib": ["ESNext"],
5
-
"target": "ESNext",
6
-
"module": "Preserve",
7
-
"moduleDetection": "force",
8
-
"jsx": "react-jsx",
9
-
"allowJs": true,
10
-
11
-
// Bundler mode
3
+
"lib": ["ES2022"],
4
+
"target": "ES2022",
5
+
"module": "ESNext",
12
6
"moduleResolution": "bundler",
13
-
"allowImportingTsExtensions": true,
14
-
"verbatimModuleSyntax": true,
15
-
"noEmit": true,
16
-
17
-
// Best practices
7
+
"outDir": "./dist",
8
+
"rootDir": "./src",
9
+
"declaration": true,
10
+
"sourceMap": true,
18
11
"strict": true,
19
12
"skipLibCheck": true,
13
+
"esModuleInterop": true,
14
+
"resolveJsonModule": true,
15
+
"forceConsistentCasingInFileNames": true,
20
16
"noFallthroughCasesInSwitch": true,
21
17
"noUncheckedIndexedAccess": true,
22
-
"noImplicitOverride": true,
23
-
24
-
// Some stricter flags (disabled by default)
25
18
"noUnusedLocals": false,
26
-
"noUnusedParameters": false,
27
-
"noPropertyAccessFromIndexSignature": false,
28
-
"composite": true
19
+
"noUnusedParameters": false
29
20
},
30
21
"include": ["src"]
31
22
}
History
1 round
0 comments
stevedylan.dev
submitted
#0
1 commit
expand
collapse
chore: refactored codebase to use node and fs instead of bun
expand 0 comments
pull request successfully merged