diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..0ce578dc3 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Releases of the public npm packages (`@maple-dev/effect-sdk`, `@maple-dev/browser`, +`@maple-dev/alchemy`) are driven by changesets. Run `bunx changeset` in a PR that changes one +of them, pick the bump, and describe the change for the CHANGELOG. + +On `main`, `.github/workflows/release.yml` keeps a "Version Packages" PR open. Merging it +publishes every bumped package to npm (`scripts/publish-packages.ts`) and tags the release. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..baa5d56de --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@4.0.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["@maple/landing", "@maple/clickhouse-cli", "@effect-router/core"], + "privatePackages": { "version": false, "tag": false } +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..889ba07f8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +# Keeps a "Version Packages" PR open on main while changesets are pending. Merging it bumps +# the public npm packages, so the next run finds unpublished versions and publishes them +# (scripts/publish-packages.ts), then pushes the tags and cuts GitHub releases. + +on: + push: + branches: [main] + paths: + - ".changeset/**" + - "packages/effect-sdk/package.json" + - "packages/browser/package.json" + - "packages/alchemy-maple/package.json" + - "scripts/publish-packages.ts" + - ".github/workflows/release.yml" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + release: + name: Version or publish + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: npm-publish + permissions: + contents: write # version commit, tags, GitHub releases + pull-requests: write # the Version Packages PR + id-token: write # npm trusted publishing + provenance + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 + - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + - run: bun install --frozen-lockfile + - name: Configure npm token fallback + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -n "$NODE_AUTH_TOKEN" ]; then + echo '//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}' > "$HOME/.npmrc" + fi + + - uses: changesets/action@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2 + with: + version-script: bun run changeset:version + publish-script: bun run changeset:publish + commit-message: "chore(release): version packages" + pr-title: "chore(release): version packages" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Fallback until each package has npm trusted publishing configured + # (a brand-new package needs its first publish before that is possible). + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: "true" diff --git a/bun.lock b/bun.lock index 33bd44592..b04b490e7 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "pg": "^8.23.1", }, "devDependencies": { + "@changesets/cli": "3.0.3", "@cloudflare/workers-types": "catalog:alchemy", "@effect/platform-node": "catalog:effect", "@effect/tsgo": "0.48.1", @@ -1148,6 +1149,36 @@ "@capsizecss/unpack": ["@capsizecss/unpack@4.0.1", "", { "dependencies": { "fontkitten": "^1.0.3" } }, "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ=="], + "@changesets/apply-release-plan": ["@changesets/apply-release-plan@8.1.1", "", { "dependencies": { "@changesets/config": "^4.0.1", "@changesets/format": "^0.1.2", "@changesets/git": "^4.0.1", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "import-meta-resolve": "^4.2.0", "jsonc-parser": "^3.3.1", "semver": "^7.8.1" } }, "sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q=="], + + "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@7.0.0", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/get-dependents-graph": "^3.0.0", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "semver": "^7.8.1" } }, "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg=="], + + "@changesets/changelog-git": ["@changesets/changelog-git@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0" } }, "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA=="], + + "@changesets/cli": ["@changesets/cli@3.0.3", "", { "dependencies": { "@changesets/apply-release-plan": "^8.1.1", "@changesets/assemble-release-plan": "^7.0.0", "@changesets/changelog-git": "^1.0.0", "@changesets/config": "^4.0.1", "@changesets/errors": "^1.0.0", "@changesets/get-dependents-graph": "^3.0.0", "@changesets/git": "^4.0.1", "@changesets/pre": "^3.0.0", "@changesets/read": "^1.0.1", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "@changesets/write": "^1.0.1", "@clack/prompts": "^1.7.0", "@manypkg/get-packages": "^3.1.0", "@pnpm/deps.graph-sequencer": "^1100.0.1", "cac": "^7.0.0", "import-meta-resolve": "^4.2.0", "launch-editor": "^2.14.1", "package-manager-detector": "^1.6.0", "semver": "^7.8.1", "tinyexec": "^1.3.1" }, "bin": { "changeset": "bin.js" } }, "sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q=="], + + "@changesets/config": ["@changesets/config@4.0.1", "", { "dependencies": { "@changesets/get-dependents-graph": "^3.0.0", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0", "picomatch": "^4.0.7" } }, "sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg=="], + + "@changesets/errors": ["@changesets/errors@1.0.0", "", {}, "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g=="], + + "@changesets/format": ["@changesets/format@0.1.2", "", { "dependencies": { "package-manager-detector": "^1.8.0", "tinyexec": "^1.3.0" } }, "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA=="], + + "@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@3.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0", "semver": "^7.8.1" } }, "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg=="], + + "@changesets/git": ["@changesets/git@4.0.1", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0", "picomatch": "^4.0.4", "tinyexec": "^1.3.0" } }, "sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA=="], + + "@changesets/parse": ["@changesets/parse@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0", "yaml": "^2.9.0" } }, "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg=="], + + "@changesets/pre": ["@changesets/pre@3.0.0", "", { "dependencies": { "@changesets/errors": "^1.0.0", "@changesets/types": "^7.0.0", "@manypkg/get-packages": "^3.1.0" } }, "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q=="], + + "@changesets/read": ["@changesets/read@1.0.1", "", { "dependencies": { "@changesets/git": "^4.0.1", "@changesets/parse": "^1.0.0", "@changesets/types": "^7.0.0" } }, "sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ=="], + + "@changesets/should-skip-package": ["@changesets/should-skip-package@1.0.0", "", { "dependencies": { "@changesets/types": "^7.0.0" } }, "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg=="], + + "@changesets/types": ["@changesets/types@7.0.0", "", {}, "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w=="], + + "@changesets/write": ["@changesets/write@1.0.1", "", { "dependencies": { "@changesets/format": "^0.1.1", "@changesets/types": "^7.0.0", "human-id": "^4.2.0" } }, "sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA=="], + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="], "@chevrotain/gast": ["@chevrotain/gast@10.5.0", "", { "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A=="], @@ -1508,6 +1539,12 @@ "@maizzle/tailwindcss": ["@maizzle/tailwindcss@1.5.6", "", {}, "sha512-JBJJLV3x1SpG7zqgdpjMGhGzANcZ/T6qxKzxMe0TzfJJ+12a2UV4N55dWzos9xLgcdt91NySPqQRMNKWZexMUg=="], + "@manypkg/find-root": ["@manypkg/find-root@3.1.0", "", { "dependencies": { "@manypkg/tools": "^2.1.0" } }, "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA=="], + + "@manypkg/get-packages": ["@manypkg/get-packages@3.1.0", "", { "dependencies": { "@manypkg/find-root": "^3.1.0", "@manypkg/tools": "^2.1.0" } }, "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg=="], + + "@manypkg/tools": ["@manypkg/tools@2.1.2", "", { "dependencies": { "jju": "^1.4.0", "tinyglobby": "^0.2.13", "yaml": "^2.9.0" } }, "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ=="], + "@maple-dev/alchemy": ["@maple-dev/alchemy@workspace:packages/alchemy-maple"], "@maple-dev/browser": ["@maple-dev/browser@workspace:packages/browser"], @@ -1906,6 +1943,8 @@ "@playwright/test": ["@playwright/test@1.63.0", "", { "dependencies": { "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ=="], + "@pnpm/deps.graph-sequencer": ["@pnpm/deps.graph-sequencer@1100.0.1", "", {}, "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], @@ -3028,6 +3067,8 @@ "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "human-id": ["human-id@4.2.2", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-Z+8unD6em/MJwyrkWQ2M3wygRGI1+MztbutiNwk1DCuEPXOZRWGjtcMPZYP1exY9iNZMhd7ZOSMQSo9WYtiQRw=="], + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], @@ -3124,6 +3165,8 @@ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + "js-base64": ["js-base64@3.9.4", "", {}, "sha512-PtOMXpEGuP0RRiRXsjzHzl44dMHxSu2CPvAhinupR1tBa88me+1DPqsobl7eupn5UukjLkln1ZnAOAOXOrYG0Q=="], "js-cookie": ["js-cookie@3.0.8", "", {}, "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw=="], diff --git a/package.json b/package.json index d54c41262..14db6251e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ "alchemy:deploy:pr": "bun run alchemy:build-deps && alchemy deploy --yes --adopt --stage pr-${PR_NUMBER}", "alchemy:destroy:pr": "bun run alchemy:build-deps && alchemy destroy --yes --stage pr-${PR_NUMBER}", "test": "turbo test", + "changeset": "changeset", + "changeset:version": "changeset version && bun install --lockfile-only", + "changeset:publish": "bun run ./scripts/publish-packages.ts", "tinybird:manifest": "bun run ./scripts/generate-tinybird-project-manifest.ts", "tinybird:manifest:check": "bun run ./scripts/generate-tinybird-project-manifest.ts --check", "tinybird:datafiles": "bun run ./scripts/write-tinybird-datafiles.ts", @@ -51,6 +54,7 @@ "pg": "^8.23.1" }, "devDependencies": { + "@changesets/cli": "3.0.3", "@cloudflare/workers-types": "catalog:alchemy", "@effect/platform-node": "catalog:effect", "@effect/tsgo": "0.48.1", diff --git a/packages/alchemy-maple/CHANGELOG.md b/packages/alchemy-maple/CHANGELOG.md new file mode 100644 index 000000000..c69152b05 --- /dev/null +++ b/packages/alchemy-maple/CHANGELOG.md @@ -0,0 +1,7 @@ +# @maple-dev/alchemy + +## 0.2.0 + +### Minor Changes + +- Initial release. diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 000000000..e4eb5992a --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,137 @@ +/** + * Publish step of `.github/workflows/release.yml` (the changesets/action `publish-script`). + * + * `changeset publish` shells out to `npm publish` in a Bun workspace, which ships `workspace:*` + * and `catalog:` ranges verbatim. So this packs each package with `bun pm pack` (which rewrites + * them) and publishes the tarball with npm, which owns OIDC trusted publishing and provenance. + * + * Publishes every public workspace package whose current version is not on npm yet, then tags it + * and reports the tag to the action through `CHANGESETS_OUTPUT` so it can push the tag and cut a + * GitHub release. `--dry-run` packs and runs `npm publish --dry-run` without tagging. + */ +import { appendFileSync, mkdtempSync, readFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join, resolve } from "node:path" +import { Glob } from "bun" + +interface Manifest { + readonly name: string + readonly version: string + readonly private?: boolean + readonly publishConfig?: { readonly access?: string } + readonly dependencies?: Readonly> + readonly peerDependencies?: Readonly> +} + +interface Candidate { + readonly dir: string + readonly manifest: Manifest +} + +const root = resolve(import.meta.dir, "..") +const dryRun = process.argv.includes("--dry-run") + +const run = (cmd: ReadonlyArray, cwd = root, quiet = false) => { + const result = Bun.spawnSync([...cmd], { + cwd, + stdout: quiet ? "pipe" : "inherit", + stderr: quiet ? "pipe" : "inherit", + }) + return { ok: result.exitCode === 0, stdout: result.stdout?.toString() ?? "" } +} + +const fail = (message: string): never => { + console.error(`::error::${message}`) + process.exit(1) +} + +const changesetConfig: { readonly ignore?: ReadonlyArray } = JSON.parse( + readFileSync(join(root, ".changeset/config.json"), "utf8"), +) +const ignored = new Set(changesetConfig.ignore ?? []) +const rootManifest: { readonly workspaces: ReadonlyArray } = JSON.parse( + readFileSync(join(root, "package.json"), "utf8"), +) + +const workspaces: ReadonlyArray = rootManifest.workspaces.flatMap((pattern) => + Array.from(new Glob(`${pattern}/package.json`).scanSync({ cwd: root })).map((file) => ({ + dir: join(root, file, ".."), + manifest: JSON.parse(readFileSync(join(root, file), "utf8")), + })), +) + +const isOnNpm = ({ name, version }: Manifest) => + run(["npm", "view", `${name}@${version}`, "version"], root, true).stdout.trim() === version + +const isTagged = (tag: string) => + run(["git", "ls-remote", "--tags", "origin", `refs/tags/${tag}`], root, true).stdout.trim() !== "" + +const outputFile = process.env.CHANGESETS_OUTPUT + +// Tags the release locally and reports it so the action pushes the tag and cuts the GitHub release. +const reportRelease = ({ name, version }: Manifest) => { + const tag = `${name}@${version}` + if (!run(["git", "tag", "-a", tag, "-m", tag]).ok) fail(`git tag failed for ${tag}`) + console.log(`New tag: ${tag}`) + if (outputFile) { + appendFileSync(outputFile, `${JSON.stringify({ type: "git-tag", tag, packageName: name })}\n`) + } +} + +const publishable = workspaces.filter( + ({ manifest }) => + manifest.private !== true && + manifest.publishConfig?.access === "public" && + !ignored.has(manifest.name), +) +const [published, pending] = publishable.reduce<[Array, Array]>( + ([onNpm, notOnNpm], candidate) => + isOnNpm(candidate.manifest) ? [[...onNpm, candidate], notOnNpm] : [onNpm, [...notOnNpm, candidate]], + [[], []], +) + +// A rerun after a publish whose tag push or release failed: npm already has the version, so only +// the tag and release are still owed. +if (!dryRun) { + for (const { manifest } of published) { + if (!isTagged(`${manifest.name}@${manifest.version}`)) reportRelease(manifest) + } +} + +if (pending.length === 0) { + console.log("Nothing to publish: every public package version is already on npm.") + process.exit(0) +} + +// A package goes after any pending package it depends on, so `workspace:*` resolves to a +// version that already exists on npm by the time its dependent is published. +const pendingNames = new Set(pending.map(({ manifest }) => manifest.name)) +const internalDeps = ({ manifest }: Candidate) => + Object.keys({ ...manifest.dependencies, ...manifest.peerDependencies }).filter((dep) => + pendingNames.has(dep), + ).length +const ordered = pending.toSorted((a, b) => internalDeps(a) - internalDeps(b)) + +console.log(`Publishing: ${ordered.map(({ manifest }) => `${manifest.name}@${manifest.version}`).join(", ")}`) + +if ( + !run(["bunx", "turbo", "run", "build", ...ordered.map(({ manifest }) => `--filter=${manifest.name}`)]).ok +) { + fail("build failed") +} + +const packDir = mkdtempSync(join(tmpdir(), "maple-publish-")) + +for (const { dir, manifest } of ordered) { + const tag = `${manifest.name}@${manifest.version}` + const tarball = join( + packDir, + `${manifest.name.replace("@", "").replace("/", "-")}-${manifest.version}.tgz`, + ) + if (!run(["bun", "pm", "pack", "--filename", tarball], dir).ok) fail(`bun pm pack failed for ${tag}`) + const publishArgs = ["npm", "publish", tarball, "--access", "public"] + if (!run(dryRun ? [...publishArgs, "--dry-run"] : [...publishArgs, "--provenance"]).ok) { + fail(`npm publish failed for ${tag}`) + } + if (!dryRun) reportRelease(manifest) +}