|
| 1 | +#!/usr/bin/env node |
| 2 | +// Copyright © 2023 Ory Corp |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +// Publishes the Ory CLI to npm for a tagged release. Not part of the npm |
| 6 | +// package itself — run by the npm-publish job in .github/workflows/ci.yaml. |
| 7 | +// |
| 8 | +// For every supported platform this downloads the release archive from GitHub, |
| 9 | +// extracts the ory binary into a minimal per-platform package |
| 10 | +// (e.g. @ory/cli-linux-x64) and publishes it. It then publishes @ory/cli |
| 11 | +// itself, which contains only the npm/run.js launcher plus exact-version |
| 12 | +// optionalDependencies on the platform packages, so that npm downloads just |
| 13 | +// the binary matching the consumer's platform. |
| 14 | +// |
| 15 | +// Usage: node npm/publish.js <version> [--dry-run] |
| 16 | +// version the release tag, with or without the leading "v" |
| 17 | +// --dry-run build and pack everything, but do not upload to npm |
| 18 | + |
| 19 | +"use strict" |
| 20 | + |
| 21 | +const { execFileSync } = require("child_process") |
| 22 | +const fs = require("fs") |
| 23 | +const path = require("path") |
| 24 | + |
| 25 | +const platforms = [ |
| 26 | + { os: "darwin", cpu: "arm64", assetSuffix: "macOS_arm64.tar.gz" }, |
| 27 | + { os: "darwin", cpu: "x64", assetSuffix: "macOS_64bit.tar.gz" }, |
| 28 | + { os: "linux", cpu: "arm64", assetSuffix: "linux_arm64.tar.gz" }, |
| 29 | + { os: "linux", cpu: "x64", assetSuffix: "linux_64bit.tar.gz" }, |
| 30 | + { os: "win32", cpu: "arm64", assetSuffix: "windows_arm64.zip" }, |
| 31 | + { os: "win32", cpu: "x64", assetSuffix: "windows_64bit.zip" }, |
| 32 | +] |
| 33 | + |
| 34 | +const rootDir = path.join(__dirname, "..") |
| 35 | +const distDir = path.join(rootDir, "dist", "npm") |
| 36 | + |
| 37 | +function run(cmd, args, opts) { |
| 38 | + console.log("+ " + cmd + " " + args.join(" ")) |
| 39 | + return execFileSync(cmd, args, Object.assign({ stdio: "inherit" }, opts)) |
| 40 | +} |
| 41 | + |
| 42 | +function buildPlatformPackage(platform, version) { |
| 43 | + const pkgName = "@ory/cli-" + platform.os + "-" + platform.cpu |
| 44 | + const asset = "ory_" + version + "-" + platform.assetSuffix |
| 45 | + const url = |
| 46 | + "https://github.com/ory/cli/releases/download/v" + version + "/" + asset |
| 47 | + const pkgDir = path.join(distDir, platform.os + "-" + platform.cpu) |
| 48 | + const binDir = path.join(pkgDir, "bin") |
| 49 | + const binName = platform.os === "win32" ? "ory.exe" : "ory" |
| 50 | + const archive = path.join(distDir, asset) |
| 51 | + |
| 52 | + fs.mkdirSync(binDir, { recursive: true }) |
| 53 | + run("curl", ["-fsSL", "--retry", "3", "-o", archive, url]) |
| 54 | + if (asset.endsWith(".zip")) { |
| 55 | + run("unzip", ["-oq", archive, binName, "-d", binDir]) |
| 56 | + } else { |
| 57 | + run("tar", ["-xzf", archive, "-C", binDir, binName]) |
| 58 | + } |
| 59 | + fs.chmodSync(path.join(binDir, binName), 0o755) |
| 60 | + fs.rmSync(archive) |
| 61 | + fs.copyFileSync(path.join(rootDir, "LICENSE"), path.join(pkgDir, "LICENSE")) |
| 62 | + fs.writeFileSync( |
| 63 | + path.join(pkgDir, "package.json"), |
| 64 | + JSON.stringify( |
| 65 | + { |
| 66 | + name: pkgName, |
| 67 | + version: version, |
| 68 | + description: |
| 69 | + "The Ory CLI binary for " + platform.os + " " + platform.cpu + ".", |
| 70 | + repository: { type: "git", url: "git+https://github.com/ory/cli.git" }, |
| 71 | + homepage: "https://ory.com/cli", |
| 72 | + license: "Apache-2.0", |
| 73 | + os: [platform.os], |
| 74 | + cpu: [platform.cpu], |
| 75 | + files: ["bin"], |
| 76 | + preferUnplugged: true, |
| 77 | + }, |
| 78 | + null, |
| 79 | + 2, |
| 80 | + ) + "\n", |
| 81 | + ) |
| 82 | + return { name: pkgName, dir: pkgDir, bin: path.join(binDir, binName) } |
| 83 | +} |
| 84 | + |
| 85 | +function smokeTest(pkg, platform, version) { |
| 86 | + if (platform.os !== process.platform || platform.cpu !== process.arch) { |
| 87 | + return |
| 88 | + } |
| 89 | + const out = execFileSync(pkg.bin, ["version"], { encoding: "utf8" }) |
| 90 | + console.log(out) |
| 91 | + if (!out.includes(version)) { |
| 92 | + throw new Error( |
| 93 | + "smoke test failed: `ory version` does not mention " + version, |
| 94 | + ) |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +function publish(dir, distTag, dryRun) { |
| 99 | + const args = ["publish", "--access", "public", "--tag", distTag] |
| 100 | + if (dryRun) { |
| 101 | + args.push("--dry-run") |
| 102 | + } |
| 103 | + run("npm", args, { cwd: dir }) |
| 104 | +} |
| 105 | + |
| 106 | +function main() { |
| 107 | + const args = process.argv.slice(2) |
| 108 | + const dryRun = args.includes("--dry-run") |
| 109 | + const version = (args.find((a) => !a.startsWith("--")) || "").replace( |
| 110 | + /^v/, |
| 111 | + "", |
| 112 | + ) |
| 113 | + if (!/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(version)) { |
| 114 | + console.error("Usage: node npm/publish.js <version> [--dry-run]") |
| 115 | + process.exit(1) |
| 116 | + } |
| 117 | + |
| 118 | + // Prereleases must not become the version a plain `npm install @ory/cli` |
| 119 | + // resolves to, so publish them under the "next" dist-tag instead of "latest". |
| 120 | + const distTag = version.includes("-") ? "next" : "latest" |
| 121 | + |
| 122 | + fs.rmSync(distDir, { recursive: true, force: true }) |
| 123 | + const built = platforms.map((platform) => { |
| 124 | + const pkg = buildPlatformPackage(platform, version) |
| 125 | + smokeTest(pkg, platform, version) |
| 126 | + return pkg |
| 127 | + }) |
| 128 | + |
| 129 | + // Publish the platform packages before @ory/cli itself so a failure cannot |
| 130 | + // leave @ory/cli pointing at binary packages that do not exist. |
| 131 | + for (const pkg of built) { |
| 132 | + publish(pkg.dir, distTag, dryRun) |
| 133 | + } |
| 134 | + |
| 135 | + const rootPkgPath = path.join(rootDir, "package.json") |
| 136 | + const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf8")) |
| 137 | + rootPkg.version = version |
| 138 | + rootPkg.optionalDependencies = {} |
| 139 | + for (const pkg of built) { |
| 140 | + rootPkg.optionalDependencies[pkg.name] = version |
| 141 | + } |
| 142 | + fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + "\n") |
| 143 | + publish(rootDir, distTag, dryRun) |
| 144 | +} |
| 145 | + |
| 146 | +main() |
0 commit comments