diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 2f3cd19d3b..4b73ad2ef9 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -75,14 +75,15 @@ jobs: run: | cat > Dockerfile << 'EOF' FROM --platform=${BUILDPLATFORM} alpine as builder - ADD restate-cli-aarch64-unknown-linux-musl.tar.xz /restate-arm64 - ADD restate-cli-x86_64-unknown-linux-musl.tar.xz /restate-amd64 - # keep output image small by removing the server binary - RUN rm /restate-*/restate-server + ADD restate-cli-aarch64-unknown-linux-musl.tar.xz /restate-cli-arm64 + ADD restatectl-aarch64-unknown-linux-musl.tar.xz /restatectl-arm64 + ADD restate-cli-x86_64-unknown-linux-musl.tar.xz /restate-cli-amd64 + ADD restatectl-x86_64-unknown-linux-musl.tar.xz /restatectl-amd64 FROM alpine ARG TARGETARCH - COPY --from=builder /restate-${TARGETARCH} / + COPY --from=builder /restate-cli-${TARGETARCH} / + COPY --from=builder /restatectl-${TARGETARCH}/restatectl / ENTRYPOINT [ "/restate" ] EOF diff --git a/.github/workflows/notarize.yml b/.github/workflows/notarize.yml new file mode 100644 index 0000000000..6a1cf90b6c --- /dev/null +++ b/.github/workflows/notarize.yml @@ -0,0 +1,54 @@ +name: Notarize darwin binaries + +on: + workflow_call: + inputs: + # comes from cargo-dist workflow call + plan: + required: true + type: string + +env: + PLAN: ${{ inputs.plan }} + +jobs: + notarize: + runs-on: warp-macos-latest-arm64-6x + strategy: + matrix: + app_name: + - restate-cli + - restatectl + - restate-server + target: + - aarch64-apple-darwin + - x86_64-apple-darwin + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v4 + with: + pattern: artifacts-*-apple-darwin + merge-multiple: true + + - name: Extract binaries + run: tar -xvzf ${{ matrix.app_name }}-${{ matrix.target }}.tar.xz + + - name: Add notary credentials + run: xcrun notarytool store-credentials --apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_APP_SPECIFIC_PASSWORD" --team-id "$CODESIGN_IDENTITY" "Notarization" + env: + NOTARY_APPLE_ID: ${{ secrets.NOTARY_APPLE_ID }} + NOTARY_APP_SPECIFIC_PASSWORD: ${{ secrets.NOTARY_APP_SPECIFIC_PASSWORD }} + CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} + + - name: Notarize + shell: bash + run: | + bin="$(echo "$PLAN" | jq -r '.artifacts["${{ matrix.app_name }}-${{ matrix.target }}.tar.xz"].assets[] | select(.kind == "executable").name')" + + zip ${{ matrix.app_name }}-${{ matrix.target }}.zip "${{ matrix.app_name }}-${{ matrix.target }}/${bin}" + + xcrun notarytool submit ${{ matrix.app_name }}-${{ matrix.target }}.zip --keychain-profile "Notarization" diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index c56dbeabea..91ec5006ec 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -21,6 +21,7 @@ jobs: matrix: app_name: - restate-cli + - restatectl - restate-server build: - target: aarch64-apple-darwin @@ -57,7 +58,7 @@ jobs: - name: Publish to NPM shell: bash run: | - bin="$(echo "$PLAN" | jq -r '.artifacts["${{ matrix.app_name }}-${{ matrix.build.target }}.tar.xz"].assets[] | select(.kind == "executable").name') + bin="$(echo "$PLAN" | jq -r '.artifacts["${{ matrix.app_name }}-${{ matrix.build.target }}.tar.xz"].assets[] | select(.kind == "executable").name')" cd npm node_os="${{ matrix.build.node_os }}" @@ -78,10 +79,11 @@ jobs: # generate package.json from the template envsubst < package.json.tmpl > "${node_pkg}/package.json" # copy the binary into the package - cp "../${bin}" "${node_pkg}/bin" - cp ../NOTICE "${node_pkg}" - cp ../LICENSE "${node_pkg}" - cp ../README.md "${node_pkg}" + dir="../${{ matrix.app_name }}-${{ matrix.target }}" + cp "${dir}/${bin}" "${node_pkg}/bin" + cp "${dir}/NOTICE" "${node_pkg}" + cp "${dir}/LICENSE" "${node_pkg}" + cp "${dir}/README.md" "${node_pkg}" # publish the package pushd "${node_pkg}" npm publish --access public @@ -96,6 +98,7 @@ jobs: matrix: app_name: - restate-cli + - restatectl - restate-server steps: - name: Checkout @@ -110,7 +113,7 @@ jobs: shell: bash run: | node_version=$(echo "$PLAN" | jq -r '.releases[] | select(.app_name == "${{ matrix.app_name }}").app_version')" - bin="$(echo "$PLAN" | jq -r '.artifacts["${{ matrix.package.artifact }}-${{ matrix.build.target }}.tar.xz"].assets[] | select(.kind == "executable").name') + bin="$(echo "$PLAN" | jq -r '.artifacts["${{ matrix.package.artifact }}-${{ matrix.build.target }}.tar.xz"].assets[] | select(.kind == "executable").name')" cd npm if npm view "@restatedev/${bin}@${node_version}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e77d13f266..b4657460c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -235,14 +235,24 @@ jobs: path: | ${{ steps.cargo-dist.outputs.paths }} ${{ env.BUILD_MANIFEST_NAME }} + + custom-notarize: + needs: + - plan + - build-local-artifacts + uses: ./.github/workflows/notarize.yml + with: + plan: ${{ needs.plan.outputs.val }} + secrets: inherit # Determines if we should publish/announce host: needs: - plan - build-local-artifacts - build-global-artifacts + - custom-notarize # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) - if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-notarize.result == 'skipped' || needs.custom-notarize.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} runs-on: "ubuntu-20.04" @@ -306,6 +316,7 @@ jobs: - plan - build-local-artifacts - build-global-artifacts + - custom-notarize uses: ./.github/workflows/ci.yml with: plan: ${{ needs.plan.outputs.val }} diff --git a/dist-workspace.toml b/dist-workspace.toml index 3302ff4df3..b00bb18012 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -22,6 +22,7 @@ precise-builds = true cache-builds = false host-jobs = ["./ci"] +global-artifacts-jobs = ["./notarize"] publish-jobs = ["./docker-release", "homebrew", "./npm"] post-announce-jobs = ["./helm"] diff --git a/npm/restate-server/src/index.ts b/npm/restate-server/src/index.ts index c79581fd5d..957cf5c7f2 100644 --- a/npm/restate-server/src/index.ts +++ b/npm/restate-server/src/index.ts @@ -1,17 +1,31 @@ #!/usr/bin/env node +/* + * Copyright (c) 2023 - 2025 Restate Software, Inc., Restate GmbH. + * All rights reserved. + * + * Use of this software is governed by the Business Source License + * included in the LICENSE file. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0. + */ + import { spawnSync } from "child_process"; -import os from 'node:os'; +import os from "node:os"; function getExePath() { const arch = os.arch(); const op = os.platform(); try { - return require.resolve(`@restatedev/restate-server-${op}-${arch}/bin/restate-server`); + return require.resolve( + `@restatedev/restate-server-${op}-${arch}/bin/restate-server`, + ); } catch (e) { throw new Error( - `Couldn't find application binary inside node_modules for ${op}-${arch}` + `Couldn't find application binary inside node_modules for ${op}-${arch}`, ); } } diff --git a/npm/restate/src/index.ts b/npm/restate/src/index.ts index 6d451f1b62..eabe11a811 100644 --- a/npm/restate/src/index.ts +++ b/npm/restate/src/index.ts @@ -1,3 +1,5 @@ +#!/usr/bin/env node + /* * Copyright (c) 2023 - 2025 Restate Software, Inc., Restate GmbH. * All rights reserved. @@ -10,10 +12,8 @@ * by the Apache License, Version 2.0. */ -#!/usr/bin/env node - import { spawnSync } from "child_process"; -import os from 'node:os'; +import os from "node:os"; function getExePath() { const arch = os.arch(); @@ -23,7 +23,7 @@ function getExePath() { return require.resolve(`@restatedev/restate-${op}-${arch}/bin/restate`); } catch (e) { throw new Error( - `Couldn't find application binary inside node_modules for ${op}-${arch}` + `Couldn't find application binary inside node_modules for ${op}-${arch}`, ); } } diff --git a/npm/restatectl/package.json b/npm/restatectl/package.json new file mode 100644 index 0000000000..f84b10978b --- /dev/null +++ b/npm/restatectl/package.json @@ -0,0 +1,37 @@ +{ + "name": "@restatedev/restatectl", + "description": "Restate administration tools", + "version": "0.5.1", + "bin": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/restatedev/restate.git" + }, + "publishConfig": { + "@restatedev:registry": "https://registry.npmjs.org" + }, + "author": "Restate Developers", + "license": "BSL", + "email": "code@restate.dev", + "homepage": "https://github.com/restatedev/restate#readme", + "scripts": { + "typecheck": "tsc --noEmit", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "build": "tsc", + "dev": "npm run build && node lib/index.js" + }, + "devDependencies": { + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "@typescript-eslint/parser": "^5.48.0", + "eslint": "^8.31.0", + "typescript": "^4.9.4" + }, + "optionalDependencies": { + "@restatedev/restatectl-linux-x64": "0.5.1", + "@restatedev/restatectl-linux-arm64": "0.5.1", + "@restatedev/restatectl-darwin-x64": "0.5.1", + "@restatedev/restatectl-darwin-arm64": "0.5.1" + } +} diff --git a/npm/restatectl/src/index.ts b/npm/restatectl/src/index.ts new file mode 100644 index 0000000000..ffb94cf889 --- /dev/null +++ b/npm/restatectl/src/index.ts @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +/* + * Copyright (c) 2023 - 2025 Restate Software, Inc., Restate GmbH. + * All rights reserved. + * + * Use of this software is governed by the Business Source License + * included in the LICENSE file. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0. + */ + +import { spawnSync } from "child_process"; +import os from "node:os"; + +function getExePath() { + const arch = os.arch(); + const op = os.platform(); + + try { + return require.resolve( + `@restatedev/restatectl-${op}-${arch}/bin/restatectl`, + ); + } catch (e) { + throw new Error( + `Couldn't find application binary inside node_modules for ${op}-${arch}`, + ); + } +} + +function run() { + const args = process.argv.slice(2); + const processResult = spawnSync(getExePath(), args, { stdio: "inherit" }); + process.exit(processResult.status ?? 0); +} + +run(); diff --git a/npm/restatectl/tsconfig.json b/npm/restatectl/tsconfig.json new file mode 100644 index 0000000000..5ed5d484c0 --- /dev/null +++ b/npm/restatectl/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "baseUrl": "./", + "outDir": "lib", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/tools/restatectl/Cargo.toml b/tools/restatectl/Cargo.toml index 75753adfe1..957a3209c6 100644 --- a/tools/restatectl/Cargo.toml +++ b/tools/restatectl/Cargo.toml @@ -2,11 +2,18 @@ name = "restatectl" version.workspace = true authors.workspace = true +description = "Restate administration tools" edition.workspace = true rust-version.workspace = true license.workspace = true +repository.workspace = true +homepage.workspace = true publish = false +[package.metadata.dist] +dist = true +formula = "restatectl" + [features] default = ["replicated-loglet", "memory-loglet", "no-trace-logging"] replicated-loglet = [