From 3ae303bcb43b8ff89571a44c3cbc20036143dee2 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Mon, 9 Feb 2026 11:45:58 -0600 Subject: [PATCH 1/4] Initial setup of project files and directory structure. --- .github/workflows/early-access.yaml | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/early-access.yaml diff --git a/.github/workflows/early-access.yaml b/.github/workflows/early-access.yaml new file mode 100644 index 0000000..6fbcade --- /dev/null +++ b/.github/workflows/early-access.yaml @@ -0,0 +1,42 @@ +name: Publish Early Access + +on: + workflow_dispatch: + inputs: + ref: + description: "Git ref to build (branch, tag, or SHA)" + required: false + default: "main" + channel: + description: "Early access channel path under /releases///" + required: false + default: "early" + +env: + GITHUB_TOKEN: ${{ secrets.ROAMJS_RELEASE_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ vars.AWS_REGION }} + ROAMJS_PROXY: ${{ vars.ROAMJS_PROXY }} + GITHUB_APP_ID: 312167 + +jobs: + publish-early-access: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Install dependencies + run: npm install + + - name: Build and publish to early-access channel + run: npx samepage build + env: + GITHUB_REF_NAME: ${{ inputs.channel }} + + - name: Print release URL + run: | + echo "Early-access base URL:" + echo "https://roamjs.com/releases/${{ github.repository }}/${{ inputs.channel }}/" From 6dacf041675dca10389d8112ab92284f265c652c Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Mon, 9 Feb 2026 17:57:09 -0600 Subject: [PATCH 2/4] Update package dependencies and scripts; include new devDependencies and adjust TypeScript configuration to include scripts directory. --- .github/workflows/vercel-release-manual.yaml | 37 + package-lock.json | 733 ++++++++++++++++++- package.json | 12 +- scripts/deploy-vercel.ts | 85 +++ scripts/publish-one-off.ts | 341 +++++++++ tsconfig.json | 2 +- 6 files changed, 1206 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/vercel-release-manual.yaml create mode 100644 scripts/deploy-vercel.ts create mode 100644 scripts/publish-one-off.ts diff --git a/.github/workflows/vercel-release-manual.yaml b/.github/workflows/vercel-release-manual.yaml new file mode 100644 index 0000000..2d5fd2b --- /dev/null +++ b/.github/workflows/vercel-release-manual.yaml @@ -0,0 +1,37 @@ +name: Manual - Breadcrumbs Vercel Release Publish + +on: + workflow_dispatch: + inputs: + ref: + description: "Git ref to deploy (branch, tag, or SHA)" + required: false + default: "main" + +env: + BLOB_READ_WRITE_TOKEN: ${{ secrets.ROAMJS_VERCEL_BLOB_READ_WRITE_TOKEN }} + ROAMJS_RELEASES_BASE_URL: https://roamJS.com + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm install + + - name: Build + run: npx samepage build --dry + + - name: Deploy + run: npm run deploy:vercel -- --no-compile diff --git a/package-lock.json b/package-lock.json index 5f72716..6b38014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,13 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@vercel/blob": "^1.1.1", "roamjs-components": "^0.86.4" + }, + "devDependencies": { + "prettier": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.6.11", + "tsx": "^4.20.5" } }, "node_modules/@alloc/quick-lru": { @@ -1564,6 +1570,23 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.14.tgz", @@ -1836,6 +1859,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz", @@ -1853,6 +1893,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz", @@ -1870,6 +1927,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz", @@ -1938,6 +2012,15 @@ "node": ">=12" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@hypnosphi/create-react-context": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", @@ -3510,6 +3593,22 @@ "license": "MIT", "peer": true }, + "node_modules/@vercel/blob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vercel/blob/-/blob-1.1.1.tgz", + "integrity": "sha512-heiJGj2qt5qTv6yiShH9f6KRAoZGj+lz61GQ+lBRL4lhvUmKI9A51KYlQTnsUd9ymdFlKHBlvmPeG+yGz2Qsbg==", + "license": "Apache-2.0", + "dependencies": { + "async-retry": "^1.3.3", + "is-buffer": "^2.0.5", + "is-node-process": "^1.2.0", + "throttleit": "^2.1.0", + "undici": "^5.28.4" + }, + "engines": { + "node": ">=16.14" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -3737,6 +3836,15 @@ "license": "MIT", "peer": true }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5202,7 +5310,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -5297,6 +5404,19 @@ "node": ">=6" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5700,6 +5820,29 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", @@ -5815,6 +5958,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6991,6 +7140,109 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -7391,6 +7643,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -7906,6 +8177,18 @@ "node": ">=0.8" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -8084,6 +8367,442 @@ "license": "0BSD", "peer": true }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/typed-styles": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", @@ -8105,6 +8824,18 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", diff --git a/package.json b/package.json index 3ee5f29..01b1d3e 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,24 @@ "start": "samepage dev", "prebuild:roam": "npm install", "build:roam": "samepage build --dry", + "deploy:vercel": "tsx scripts/deploy-vercel.ts", + "publish:one-off": "tsx scripts/publish-one-off.ts", "test": "samepage test" }, - "keywords": ["roam", "roam-research", "roamjs"], + "keywords": [ + "roam", + "roam-research", + "roamjs" + ], "license": "MIT", "dependencies": { + "@vercel/blob": "^1.1.1", "roamjs-components": "^0.86.4" }, "devDependencies": { "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.6.11" + "prettier-plugin-tailwindcss": "^0.6.11", + "tsx": "^4.20.5" }, "samepage": { "extends": "node_modules/roamjs-components/package.json" diff --git a/scripts/deploy-vercel.ts b/scripts/deploy-vercel.ts new file mode 100644 index 0000000..5c03a96 --- /dev/null +++ b/scripts/deploy-vercel.ts @@ -0,0 +1,85 @@ +import { put } from "@vercel/blob"; +import fs from "fs"; +import { execSync } from "child_process"; +import path from "path"; + +const build = (): void => { + execSync("npx samepage build --dry", { stdio: "inherit" }); +}; + +const resolveBranch = (): string => + process.env.GITHUB_HEAD_REF || + process.env.GITHUB_REF_NAME || + execSync("git rev-parse --abbrev-ref HEAD").toString().trim() || + "main"; + +const deploy = async (): Promise => { + process.env.NODE_ENV = process.env.NODE_ENV || "production"; + + console.log("Deploying ..."); + if (!process.argv.includes("--no-compile")) { + try { + console.log("Building"); + build(); + } catch (error) { + console.error("Deployment failed on compile:", error); + process.exit(1); + } + } + + try { + const resolvedWorkspace = "breadcrumbs"; + const token = process.env.BLOB_READ_WRITE_TOKEN; + if (!token) { + throw new Error( + "BLOB_READ_WRITE_TOKEN is required but not found in environment variables", + ); + } + + const resolvedBranch = resolveBranch(); + const distPath = path.join(process.cwd(), "dist"); + const files = [ + "extension.js", + "extension.css", + "package.json", + "README.md", + "CHANGELOG.md", + ]; + + for (const file of files) { + const filePath = path.join(distPath, file); + if (!fs.existsSync(filePath)) { + console.log(`Skipping ${file} - file does not exist`); + continue; + } + + const content = fs.readFileSync(filePath); + const pathname = + resolvedBranch === "main" + ? `releases/${resolvedWorkspace}/${file}` + : `releases/${resolvedWorkspace}/${resolvedBranch}/${file}`; + + console.log(`Uploading ${file}...`); + const blob = await put(pathname, content, { + access: "public", + addRandomSuffix: false, + allowOverwrite: true, + token, + }); + console.log(`Uploaded to ${blob.url}`); + } + + console.log("Deploy completed successfully!"); + const urlBase = process.env.ROMEJS_RELEASES_BASE_URL || "https://RomeJS.com"; + const url = `${urlBase}/releases/${resolvedWorkspace}/${resolvedBranch}`; + console.log(url); + } catch (error) { + console.error("Deploy failed:", error); + process.exit(1); + } +}; + +deploy().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/publish-one-off.ts b/scripts/publish-one-off.ts new file mode 100644 index 0000000..b1a4b2a --- /dev/null +++ b/scripts/publish-one-off.ts @@ -0,0 +1,341 @@ +import fs from "fs"; +import path from "path"; +import { execSync } from "child_process"; +import { S3 } from "@aws-sdk/client-s3"; +import mime from "mime-types"; + +const DIST_DIR = path.join(process.cwd(), "dist"); +const ASSETS_DIR = path.join(process.cwd(), "assets"); + +type ParsedArgs = { + skipGithub: boolean; + skipS3: boolean; + skipBuild: boolean; + tag?: string; + bucket?: string; + branch?: string; +}; + +const parseArgs = (): ParsedArgs => { + const args = process.argv.slice(2); + return args.reduce( + (acc, arg) => { + if (arg === "--skip-github") return { ...acc, skipGithub: true }; + if (arg === "--skip-s3") return { ...acc, skipS3: true }; + if (arg === "--skip-build") return { ...acc, skipBuild: true }; + if (arg.startsWith("--tag=")) return { ...acc, tag: arg.slice(6) }; + if (arg.startsWith("--bucket=")) return { ...acc, bucket: arg.slice(9) }; + if (arg.startsWith("--branch=")) return { ...acc, branch: arg.slice(9) }; + return acc; + }, + { skipGithub: false, skipS3: false, skipBuild: false }, + ); +}; + +const ensureDistExists = (): void => { + if (!fs.existsSync(DIST_DIR)) { + throw new Error("dist/ does not exist. Run build first."); + } +}; + +const getRepo = (): string => { + if (process.env.GITHUB_REPOSITORY) return process.env.GITHUB_REPOSITORY; + try { + const remote = execSync("git config --get remote.origin.url") + .toString() + .trim(); + const match = /[:/]([^/]+\/[^/.]+)(?:\.git)?$/.exec(remote); + return match ? match[1] : "samepage.network"; + } catch { + return "samepage.network"; + } +}; + +const getBranch = (branchArg?: string): string => { + if (branchArg) return branchArg; + if (process.env.GITHUB_HEAD_REF) return process.env.GITHUB_HEAD_REF; + if (process.env.GITHUB_REF_NAME) return process.env.GITHUB_REF_NAME; + try { + return execSync("git rev-parse --abbrev-ref HEAD").toString().trim(); + } catch { + return "main"; + } +}; + +const getTag = (tagArg?: string): string => { + if (tagArg) return tagArg; + const now = new Date(); + const y = now.getUTCFullYear(); + const m = String(now.getUTCMonth() + 1).padStart(2, "0"); + const d = String(now.getUTCDate()).padStart(2, "0"); + const h = String(now.getUTCHours()).padStart(2, "0"); + const min = String(now.getUTCMinutes()).padStart(2, "0"); + return `${y}.${m}.${d}.${h}${min}`; +}; + +const runBuild = (): void => { + execSync("npx samepage build --dry", { stdio: "inherit" }); +}; + +const runZip = (zipName: string): void => { + const cmd = + process.platform === "win32" + ? `powershell -NoProfile -Command "Compress-Archive -Path * -DestinationPath ${zipName} -Force"` + : `zip -qr ${zipName} .`; + const cwd = process.cwd(); + process.chdir(DIST_DIR); + try { + execSync(cmd, { stdio: "inherit" }); + } finally { + process.chdir(cwd); + } +}; + +type GithubRequestArgs = { + endpoint: string; + method?: string; + token: string; + body?: unknown; + host: string; +}; + +type GithubError = Error & { response?: unknown }; + +const githubRequest = async ({ + endpoint, + method = "GET", + token, + body, + host, +}: GithubRequestArgs): Promise => { + const response = await fetch(`${host}${endpoint}`, { + method, + headers: { + Authorization: `token ${token}`, + Accept: "application/vnd.github+json", + ...(body ? { "Content-Type": "application/json" } : {}), + }, + body: body ? JSON.stringify(body) : undefined, + }); + + const text = await response.text(); + let data: any = {}; + try { + data = text ? JSON.parse(text) : {}; + } catch { + data = { message: text }; + } + + if (!response.ok) { + const err: GithubError = new Error(`GitHub API failed (${response.status})`); + err.response = data; + throw err; + } + return data; +}; + +const uploadGithubRelease = async ({ + repo, + tag, + token, + branch, +}: { + repo: string; + tag: string; + token?: string; + branch: string; +}): Promise => { + if (branch !== "main") { + console.warn("Not on main branch, skipping GitHub release."); + return; + } + if (!token) { + console.warn("No GITHUB_TOKEN set, skipping GitHub release."); + return; + } + + const sha = + process.env.GITHUB_SHA || execSync("git rev-parse HEAD").toString().trim(); + const message = await githubRequest({ + endpoint: `/repos/${repo}/commits/${sha}`, + token, + host: "https://api.github.com", + }).then((r) => r.commit?.message || `Release ${tag}`); + + let release: any; + try { + release = await githubRequest({ + endpoint: `/repos/${repo}/releases`, + method: "POST", + token, + host: "https://api.github.com", + body: { + tag_name: tag, + name: message.length > 50 ? `${message.slice(0, 47)}...` : message, + body: message.length > 50 ? `...${message.slice(47)}` : "", + }, + }); + } catch (error) { + const err = error as GithubError & { + response?: { errors?: { code?: string }[] }; + }; + const alreadyExists = + Array.isArray(err.response?.errors) && + err.response.errors[0] && + err.response.errors[0].code === "already_exists"; + if (!alreadyExists) throw error; + release = await githubRequest({ + endpoint: `/repos/${repo}/releases/tags/${tag}`, + token, + host: "https://api.github.com", + }); + } + + const distFiles = fs.readdirSync(DIST_DIR).filter((f) => f !== "package.json"); + await Promise.all( + distFiles.map(async (fileName) => { + const filePath = path.join(DIST_DIR, fileName); + const content = fs.readFileSync(filePath); + const response = await fetch( + `https://uploads.github.com/repos/${repo}/releases/${release.id}/assets?name=${encodeURIComponent( + fileName, + )}`, + { + method: "POST", + headers: { + Authorization: `token ${token}`, + Accept: "application/vnd.github+json", + "Content-Type": mime.lookup(filePath) || "application/octet-stream", + }, + body: content, + }, + ); + if (!response.ok) { + const text = await response.text(); + let parsed: any = {}; + try { + parsed = text ? JSON.parse(text) : {}; + } catch { + parsed = { message: text }; + } + const alreadyExists = + Array.isArray(parsed.errors) && + parsed.errors[0] && + parsed.errors[0].code === "already_exists"; + if (alreadyExists) { + console.warn(`Release asset ${fileName} already exists`); + return; + } + throw new Error(`Failed to upload ${fileName}: ${JSON.stringify(parsed)}`); + } + }), + ); + + console.log(`GitHub release published for tag ${release.tag_name}`); +}; + +const uploadS3Artifacts = async ({ + repo, + branch, + bucket, +}: { + repo: string; + branch: string; + bucket: string; +}): Promise => { + if (!process.env.AWS_ACCESS_KEY_ID) { + console.warn("No AWS_ACCESS_KEY_ID set, skipping S3 uploads."); + return; + } + if (bucket === "none") { + console.warn("Bucket set to none, skipping S3 uploads."); + return; + } + + const s3 = new S3({}); + const artifacts = fs.existsSync(DIST_DIR) ? fs.readdirSync(DIST_DIR) : []; + const assets = fs.existsSync(ASSETS_DIR) ? fs.readdirSync(ASSETS_DIR) : []; + const repoName = repo + .split("/") + .slice(-1)[0] + .replace(/-samepage$/, ""); + + const files = artifacts + .flatMap((name) => { + const key = `releases/${repo}/${branch === "main" ? "" : `${branch}/`}${name}`; + const localPath = path.join(DIST_DIR, name); + const lowerKey = key.toLowerCase(); + if (key === lowerKey) return [{ key, localPath }]; + return [ + { key, localPath }, + { key: lowerKey, localPath }, + ]; + }) + .concat( + branch === "main" + ? assets.map((name) => ({ + key: `assets/${repoName}/${name}`, + localPath: path.join(ASSETS_DIR, name), + })) + : [], + ); + + await Promise.all( + files.map(({ key, localPath }) => + s3 + .putObject({ + Bucket: bucket, + Key: key, + Body: fs.createReadStream(localPath), + ContentType: mime.lookup(localPath) || "application/octet-stream", + }) + .then(() => console.log(`Uploaded ${localPath} to s3://${bucket}/${key}`)), + ), + ); +}; + +const main = async (): Promise => { + const { skipGithub, skipS3, skipBuild, tag, bucket, branch: branchArg } = + parseArgs(); + + const repo = getRepo(); + const branch = getBranch(branchArg); + const releaseTag = getTag(tag); + const bucketName = bucket || "samepage.network"; + const repoBaseName = repo.split("/").slice(-1)[0]; + + if (!skipBuild) { + console.log(`Building extension for ${repo} on branch ${branch}...`); + runBuild(); + } else { + console.log(`Skipping build for ${repo} on branch ${branch}...`); + } + ensureDistExists(); + + const zipName = `${repoBaseName}.zip`; + runZip(zipName); + + if (!skipGithub) { + await uploadGithubRelease({ + repo, + tag: releaseTag, + token: process.env.GITHUB_TOKEN, + branch, + }); + } + + if (!skipS3) { + await uploadS3Artifacts({ + repo, + branch, + bucket: bucketName, + }); + } + + console.log("One-off publish complete."); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/tsconfig.json b/tsconfig.json index aa5fe56..ad6853b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,6 @@ "~/*": ["./src/*"] } }, - "include": ["src"], + "include": ["src", "scripts"], "exclude": ["node_modules"] } From 2e22d99e9c793abfe6d742aa292800bce7e729b0 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Mon, 9 Feb 2026 17:59:09 -0600 Subject: [PATCH 3/4] Fix URL base in deploy script to use correct environment variable for RoamJS releases. --- scripts/deploy-vercel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/deploy-vercel.ts b/scripts/deploy-vercel.ts index 5c03a96..fd815f4 100644 --- a/scripts/deploy-vercel.ts +++ b/scripts/deploy-vercel.ts @@ -70,7 +70,8 @@ const deploy = async (): Promise => { } console.log("Deploy completed successfully!"); - const urlBase = process.env.ROMEJS_RELEASES_BASE_URL || "https://RomeJS.com"; + const urlBase = + process.env.ROAMJS_RELEASES_BASE_URL || "https://roamjs.com"; const url = `${urlBase}/releases/${resolvedWorkspace}/${resolvedBranch}`; console.log(url); } catch (error) { From 2b5c9051f328e7a807664f75013afe45e6a920ad Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Mon, 9 Feb 2026 18:02:16 -0600 Subject: [PATCH 4/4] Remove unused publish script and early access workflow from the project. --- .github/workflows/early-access.yaml | 42 ---- package.json | 1 - scripts/publish-one-off.ts | 341 ---------------------------- 3 files changed, 384 deletions(-) delete mode 100644 .github/workflows/early-access.yaml delete mode 100644 scripts/publish-one-off.ts diff --git a/.github/workflows/early-access.yaml b/.github/workflows/early-access.yaml deleted file mode 100644 index 6fbcade..0000000 --- a/.github/workflows/early-access.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Publish Early Access - -on: - workflow_dispatch: - inputs: - ref: - description: "Git ref to build (branch, tag, or SHA)" - required: false - default: "main" - channel: - description: "Early access channel path under /releases///" - required: false - default: "early" - -env: - GITHUB_TOKEN: ${{ secrets.ROAMJS_RELEASE_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ vars.AWS_REGION }} - ROAMJS_PROXY: ${{ vars.ROAMJS_PROXY }} - GITHUB_APP_ID: 312167 - -jobs: - publish-early-access: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Install dependencies - run: npm install - - - name: Build and publish to early-access channel - run: npx samepage build - env: - GITHUB_REF_NAME: ${{ inputs.channel }} - - - name: Print release URL - run: | - echo "Early-access base URL:" - echo "https://roamjs.com/releases/${{ github.repository }}/${{ inputs.channel }}/" diff --git a/package.json b/package.json index 01b1d3e..d0a5bec 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "prebuild:roam": "npm install", "build:roam": "samepage build --dry", "deploy:vercel": "tsx scripts/deploy-vercel.ts", - "publish:one-off": "tsx scripts/publish-one-off.ts", "test": "samepage test" }, "keywords": [ diff --git a/scripts/publish-one-off.ts b/scripts/publish-one-off.ts deleted file mode 100644 index b1a4b2a..0000000 --- a/scripts/publish-one-off.ts +++ /dev/null @@ -1,341 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { execSync } from "child_process"; -import { S3 } from "@aws-sdk/client-s3"; -import mime from "mime-types"; - -const DIST_DIR = path.join(process.cwd(), "dist"); -const ASSETS_DIR = path.join(process.cwd(), "assets"); - -type ParsedArgs = { - skipGithub: boolean; - skipS3: boolean; - skipBuild: boolean; - tag?: string; - bucket?: string; - branch?: string; -}; - -const parseArgs = (): ParsedArgs => { - const args = process.argv.slice(2); - return args.reduce( - (acc, arg) => { - if (arg === "--skip-github") return { ...acc, skipGithub: true }; - if (arg === "--skip-s3") return { ...acc, skipS3: true }; - if (arg === "--skip-build") return { ...acc, skipBuild: true }; - if (arg.startsWith("--tag=")) return { ...acc, tag: arg.slice(6) }; - if (arg.startsWith("--bucket=")) return { ...acc, bucket: arg.slice(9) }; - if (arg.startsWith("--branch=")) return { ...acc, branch: arg.slice(9) }; - return acc; - }, - { skipGithub: false, skipS3: false, skipBuild: false }, - ); -}; - -const ensureDistExists = (): void => { - if (!fs.existsSync(DIST_DIR)) { - throw new Error("dist/ does not exist. Run build first."); - } -}; - -const getRepo = (): string => { - if (process.env.GITHUB_REPOSITORY) return process.env.GITHUB_REPOSITORY; - try { - const remote = execSync("git config --get remote.origin.url") - .toString() - .trim(); - const match = /[:/]([^/]+\/[^/.]+)(?:\.git)?$/.exec(remote); - return match ? match[1] : "samepage.network"; - } catch { - return "samepage.network"; - } -}; - -const getBranch = (branchArg?: string): string => { - if (branchArg) return branchArg; - if (process.env.GITHUB_HEAD_REF) return process.env.GITHUB_HEAD_REF; - if (process.env.GITHUB_REF_NAME) return process.env.GITHUB_REF_NAME; - try { - return execSync("git rev-parse --abbrev-ref HEAD").toString().trim(); - } catch { - return "main"; - } -}; - -const getTag = (tagArg?: string): string => { - if (tagArg) return tagArg; - const now = new Date(); - const y = now.getUTCFullYear(); - const m = String(now.getUTCMonth() + 1).padStart(2, "0"); - const d = String(now.getUTCDate()).padStart(2, "0"); - const h = String(now.getUTCHours()).padStart(2, "0"); - const min = String(now.getUTCMinutes()).padStart(2, "0"); - return `${y}.${m}.${d}.${h}${min}`; -}; - -const runBuild = (): void => { - execSync("npx samepage build --dry", { stdio: "inherit" }); -}; - -const runZip = (zipName: string): void => { - const cmd = - process.platform === "win32" - ? `powershell -NoProfile -Command "Compress-Archive -Path * -DestinationPath ${zipName} -Force"` - : `zip -qr ${zipName} .`; - const cwd = process.cwd(); - process.chdir(DIST_DIR); - try { - execSync(cmd, { stdio: "inherit" }); - } finally { - process.chdir(cwd); - } -}; - -type GithubRequestArgs = { - endpoint: string; - method?: string; - token: string; - body?: unknown; - host: string; -}; - -type GithubError = Error & { response?: unknown }; - -const githubRequest = async ({ - endpoint, - method = "GET", - token, - body, - host, -}: GithubRequestArgs): Promise => { - const response = await fetch(`${host}${endpoint}`, { - method, - headers: { - Authorization: `token ${token}`, - Accept: "application/vnd.github+json", - ...(body ? { "Content-Type": "application/json" } : {}), - }, - body: body ? JSON.stringify(body) : undefined, - }); - - const text = await response.text(); - let data: any = {}; - try { - data = text ? JSON.parse(text) : {}; - } catch { - data = { message: text }; - } - - if (!response.ok) { - const err: GithubError = new Error(`GitHub API failed (${response.status})`); - err.response = data; - throw err; - } - return data; -}; - -const uploadGithubRelease = async ({ - repo, - tag, - token, - branch, -}: { - repo: string; - tag: string; - token?: string; - branch: string; -}): Promise => { - if (branch !== "main") { - console.warn("Not on main branch, skipping GitHub release."); - return; - } - if (!token) { - console.warn("No GITHUB_TOKEN set, skipping GitHub release."); - return; - } - - const sha = - process.env.GITHUB_SHA || execSync("git rev-parse HEAD").toString().trim(); - const message = await githubRequest({ - endpoint: `/repos/${repo}/commits/${sha}`, - token, - host: "https://api.github.com", - }).then((r) => r.commit?.message || `Release ${tag}`); - - let release: any; - try { - release = await githubRequest({ - endpoint: `/repos/${repo}/releases`, - method: "POST", - token, - host: "https://api.github.com", - body: { - tag_name: tag, - name: message.length > 50 ? `${message.slice(0, 47)}...` : message, - body: message.length > 50 ? `...${message.slice(47)}` : "", - }, - }); - } catch (error) { - const err = error as GithubError & { - response?: { errors?: { code?: string }[] }; - }; - const alreadyExists = - Array.isArray(err.response?.errors) && - err.response.errors[0] && - err.response.errors[0].code === "already_exists"; - if (!alreadyExists) throw error; - release = await githubRequest({ - endpoint: `/repos/${repo}/releases/tags/${tag}`, - token, - host: "https://api.github.com", - }); - } - - const distFiles = fs.readdirSync(DIST_DIR).filter((f) => f !== "package.json"); - await Promise.all( - distFiles.map(async (fileName) => { - const filePath = path.join(DIST_DIR, fileName); - const content = fs.readFileSync(filePath); - const response = await fetch( - `https://uploads.github.com/repos/${repo}/releases/${release.id}/assets?name=${encodeURIComponent( - fileName, - )}`, - { - method: "POST", - headers: { - Authorization: `token ${token}`, - Accept: "application/vnd.github+json", - "Content-Type": mime.lookup(filePath) || "application/octet-stream", - }, - body: content, - }, - ); - if (!response.ok) { - const text = await response.text(); - let parsed: any = {}; - try { - parsed = text ? JSON.parse(text) : {}; - } catch { - parsed = { message: text }; - } - const alreadyExists = - Array.isArray(parsed.errors) && - parsed.errors[0] && - parsed.errors[0].code === "already_exists"; - if (alreadyExists) { - console.warn(`Release asset ${fileName} already exists`); - return; - } - throw new Error(`Failed to upload ${fileName}: ${JSON.stringify(parsed)}`); - } - }), - ); - - console.log(`GitHub release published for tag ${release.tag_name}`); -}; - -const uploadS3Artifacts = async ({ - repo, - branch, - bucket, -}: { - repo: string; - branch: string; - bucket: string; -}): Promise => { - if (!process.env.AWS_ACCESS_KEY_ID) { - console.warn("No AWS_ACCESS_KEY_ID set, skipping S3 uploads."); - return; - } - if (bucket === "none") { - console.warn("Bucket set to none, skipping S3 uploads."); - return; - } - - const s3 = new S3({}); - const artifacts = fs.existsSync(DIST_DIR) ? fs.readdirSync(DIST_DIR) : []; - const assets = fs.existsSync(ASSETS_DIR) ? fs.readdirSync(ASSETS_DIR) : []; - const repoName = repo - .split("/") - .slice(-1)[0] - .replace(/-samepage$/, ""); - - const files = artifacts - .flatMap((name) => { - const key = `releases/${repo}/${branch === "main" ? "" : `${branch}/`}${name}`; - const localPath = path.join(DIST_DIR, name); - const lowerKey = key.toLowerCase(); - if (key === lowerKey) return [{ key, localPath }]; - return [ - { key, localPath }, - { key: lowerKey, localPath }, - ]; - }) - .concat( - branch === "main" - ? assets.map((name) => ({ - key: `assets/${repoName}/${name}`, - localPath: path.join(ASSETS_DIR, name), - })) - : [], - ); - - await Promise.all( - files.map(({ key, localPath }) => - s3 - .putObject({ - Bucket: bucket, - Key: key, - Body: fs.createReadStream(localPath), - ContentType: mime.lookup(localPath) || "application/octet-stream", - }) - .then(() => console.log(`Uploaded ${localPath} to s3://${bucket}/${key}`)), - ), - ); -}; - -const main = async (): Promise => { - const { skipGithub, skipS3, skipBuild, tag, bucket, branch: branchArg } = - parseArgs(); - - const repo = getRepo(); - const branch = getBranch(branchArg); - const releaseTag = getTag(tag); - const bucketName = bucket || "samepage.network"; - const repoBaseName = repo.split("/").slice(-1)[0]; - - if (!skipBuild) { - console.log(`Building extension for ${repo} on branch ${branch}...`); - runBuild(); - } else { - console.log(`Skipping build for ${repo} on branch ${branch}...`); - } - ensureDistExists(); - - const zipName = `${repoBaseName}.zip`; - runZip(zipName); - - if (!skipGithub) { - await uploadGithubRelease({ - repo, - tag: releaseTag, - token: process.env.GITHUB_TOKEN, - branch, - }); - } - - if (!skipS3) { - await uploadS3Artifacts({ - repo, - branch, - bucket: bucketName, - }); - } - - console.log("One-off publish complete."); -}; - -main().catch((error) => { - console.error(error); - process.exit(1); -});