diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d9566040..3680fc463 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,11 +104,17 @@ executors: environment: XTASK_TARGET: "x86_64-unknown-linux-gnu" - node_js: + node_js_nix: docker: - - image: node:lts + - image: node:20.16.0 resource_class: medium + node_js_windows: + machine: + image: "windows-server-2019-vs2019:2022.08.1" + shell: powershell.exe -ExecutionPolicy Bypass + resource_class: windows.medium + tag_matches_prerelease: &tag_matches_prerelease matches: @@ -171,10 +177,11 @@ workflows: rust_channel: [stable] command: [integration-test] - install_js: - name: Test installation for Javascript Package Managers (<< matrix.package_manager >>) + name: Test installation for Javascript Package Managers (<< matrix.package_manager >> on << matrix.platform >>) matrix: parameters: package_manager: [npm, npm_global, pnpm] + platform: [windows, nix] - node/test: name: Test NPM Installer Scripts app-dir: "~/project/installers/npm" @@ -216,10 +223,11 @@ workflows: <<: *run_release - install_js: - name: Test installation for Javascript Package Managers (<< matrix.package_manager >>) + name: Test installation for Javascript Package Managers (<< matrix.package_manager >> on << matrix.platform >>) matrix: parameters: package_manager: [ npm, npm_global, pnpm ] + platform: [windows, nix] <<: *run_release - node/test: @@ -244,9 +252,12 @@ workflows: - "Run cargo tests + studio integration tests (stable rust on amd_windows)" - "Run studio integration tests in GitHub Actions (amd_macos)" - "Run supergraph-demo tests (stable rust on amd_ubuntu)" - - "Test installation for Javascript Package Managers (npm)" - - "Test installation for Javascript Package Managers (npm_global)" - - "Test installation for Javascript Package Managers (pnpm)" + - "Test installation for Javascript Package Managers (npm on nix)" + - "Test installation for Javascript Package Managers (npm_global on nix)" + - "Test installation for Javascript Package Managers (pnpm on nix)" + - "Test installation for Javascript Package Managers (npm on windows)" + - "Test installation for Javascript Package Managers (npm_global on windows)" + - "Test installation for Javascript Package Managers (pnpm on windows)" - "Test NPM Installer Scripts" <<: *run_release @@ -348,15 +359,42 @@ jobs: package_manager: type: enum enum: ["npm", "npm_global", "pnpm"] - executor: node_js + platform: + type: enum + enum: ["nix", "windows"] + executor: node_js_<> steps: - checkout: path: "rover" - - run: - name: "Invoke Install Scripts" - command: | - cd rover/.circleci/scripts - ./install_<< parameters.package_manager >>.sh + - when: + condition: + equal: ["nix", <>] + steps: + - run: + name: "Invoke Install Scripts (Unix)" + command: | + cd rover/.circleci/scripts/<> + ./install_<< parameters.package_manager >>.sh + - when: + condition: + equal: ["windows", <>] + steps: + - run: + name: "Invoke Install Scripts (Windows)" + command: | + Write-Output "Installing Volta" + choco install volta + refreshenv + Write-Output "Installing Node & NPM" + volta install node@20.16.0 + Write-Output "Checking Node & NPM version" + node --version + npm --version + + $script_location=Join-Path rover\.circleci\scripts << parameters.platform >> + Set-Location $script_location + .\install_<< parameters.package_manager >>.ps1 + # reusable command snippets can be referred to in any `steps` object commands: diff --git a/.circleci/scripts/install_npm.sh b/.circleci/scripts/nix/install_npm.sh similarity index 93% rename from .circleci/scripts/install_npm.sh rename to .circleci/scripts/nix/install_npm.sh index 5c7c3b669..52f3f19c8 100755 --- a/.circleci/scripts/install_npm.sh +++ b/.circleci/scripts/nix/install_npm.sh @@ -2,7 +2,7 @@ set -euo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -INSTALLERS_DIR="$SCRIPT_DIR/../../installers/npm" +INSTALLERS_DIR="$SCRIPT_DIR/../../../installers/npm" cd "$(mktemp -d)" echo "Created test directory" diff --git a/.circleci/scripts/install_npm_global.sh b/.circleci/scripts/nix/install_npm_global.sh similarity index 93% rename from .circleci/scripts/install_npm_global.sh rename to .circleci/scripts/nix/install_npm_global.sh index 907c3eaff..aa8122ce1 100755 --- a/.circleci/scripts/install_npm_global.sh +++ b/.circleci/scripts/nix/install_npm_global.sh @@ -2,7 +2,7 @@ set -euo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -INSTALLERS_DIR="$SCRIPT_DIR/../../installers/npm" +INSTALLERS_DIR="$SCRIPT_DIR/../../../installers/npm" cd "$(mktemp -d)" echo "Created test directory" diff --git a/.circleci/scripts/install_pnpm.sh b/.circleci/scripts/nix/install_pnpm.sh similarity index 88% rename from .circleci/scripts/install_pnpm.sh rename to .circleci/scripts/nix/install_pnpm.sh index 49aee8e20..cb6f0e06e 100755 --- a/.circleci/scripts/install_pnpm.sh +++ b/.circleci/scripts/nix/install_pnpm.sh @@ -2,7 +2,7 @@ set -euo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -INSTALLERS_DIR="$SCRIPT_DIR/../../installers/npm" +INSTALLERS_DIR="$SCRIPT_DIR/../../../installers/npm" cd "$(mktemp -d)" echo "Created test directory" @@ -14,7 +14,7 @@ echo "Installed pnpm" npm --prefix "$INSTALLERS_DIR" version --allow-same-version 0.23.0 echo "Temporarily patched package.json to fixed stable binary" pnpm init -pnpm add "file:$SCRIPT_DIR/../../installers/npm" +pnpm add "file:$INSTALLERS_DIR" echo "Installed rover as pnpm package" cd node_modules/.bin/ echo "Checking version" diff --git a/.circleci/scripts/windows/install_npm.ps1 b/.circleci/scripts/windows/install_npm.ps1 new file mode 100644 index 000000000..e0c60a96a --- /dev/null +++ b/.circleci/scripts/windows/install_npm.ps1 @@ -0,0 +1,41 @@ +$ErrorActionPreference = "Stop" + +Function New-TemporaryFolder { + # Make a new folder based upon a TempFileName + $TEMP_PATH=[System.IO.Path]::GetTempPath() + $T= Join-Path $TEMP_PATH tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')) + New-Item -ItemType Directory -Path $T +} + +# Find the installers directory +$installer_dir = [System.IO.Path]::Combine($PSScriptRoot, "..", "..", "..", "installers", "npm") +Write-Output "Found installers directory at $installer_dir" + +# Create a temporary folder for the test +$test_dir = New-TemporaryFolder +Write-Output "Created test directory at $test_dir" +Set-Location $test_dir +# Initialise an empty NPM package +npm init -y +Write-Output "Initialised new npm package" + +# The choice of version here is arbitrary (we just need something we know exists) so that we can test if the +# installer works, given an existing version. This way we're not at the mercy of whether the binary that corresponds +# to the latest commit exists. +npm --prefix "$installer_dir" version --allow-same-version 0.23.0 +Write-Output "Temporarily patched package.json to fixed stable binary" + +# Install all the dependencies, including `rover` +npm install --install-links=true "$installer_dir" +Write-Output "Installed rover as local npm package" + +# Move to the installed location +$node_modules_path=[System.IO.Path]::Combine($test_dir, "node_modules", ".bin") +Set-Location $node_modules_path + +# Check the version +Write-Output "Checking version" +$dir_sep=[IO.Path]::DirectorySeparatorChar +$rover_command=".${dir_sep}rover --version" +Invoke-Expression $rover_command +Write-Output "Checked version, all ok!" diff --git a/.circleci/scripts/windows/install_npm_global.ps1 b/.circleci/scripts/windows/install_npm_global.ps1 new file mode 100644 index 000000000..f80b81d9e --- /dev/null +++ b/.circleci/scripts/windows/install_npm_global.ps1 @@ -0,0 +1,32 @@ +$ErrorActionPreference = "Stop" + +Function New-TemporaryFolder { + # Make a new folder based upon a TempFileName + $TEMP_PATH=[System.IO.Path]::GetTempPath() + $T= Join-Path $TEMP_PATH tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')) + New-Item -ItemType Directory -Path $T +} + +# Find the installers directory +$installer_dir = [System.IO.Path]::Combine($PSScriptRoot, "..", "..", "..", "installers", "npm") +Write-Output "Found installers directory at $installer_dir" + +# Create a temporary folder for the test +$test_dir = New-TemporaryFolder +Write-Output "Created test directory at $test_dir" +Set-Location $test_dir + +# The choice of version here is arbitrary (we just need something we know exists) so that we can test if the +# installer works, given an existing version. This way we're not at the mercy of whether the binary that corresponds +# to the latest commit exists. +npm --prefix "$installer_dir" version --allow-same-version 0.23.0 +Write-Output "Temporarily patched package.json to fixed stable binary" + +# Install all the dependencies, including `rover` +npm install --install-links=true -g "$installer_dir" +Write-Output "Installed rover as global npm package" + +# Check the version +Write-Output "Checking version" +rover --version +Write-Output "Checked version, all ok!" diff --git a/.circleci/scripts/windows/install_pnpm.ps1 b/.circleci/scripts/windows/install_pnpm.ps1 new file mode 100644 index 000000000..6f9f98415 --- /dev/null +++ b/.circleci/scripts/windows/install_pnpm.ps1 @@ -0,0 +1,41 @@ +$ErrorActionPreference = "Stop" + +Function New-TemporaryFolder { + # Make a new folder based upon a TempFileName + $TEMP_PATH=[System.IO.Path]::GetTempPath() + $T= Join-Path $TEMP_PATH tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')) + New-Item -ItemType Directory -Path $T +} + +# Find the installers directory +$installer_dir = [System.IO.Path]::Combine($PSScriptRoot, "..", "..", "..", "installers", "npm") +Write-Output "Found installers directory at $installer_dir" + +# Create a temporary folder for the test +$test_dir = New-TemporaryFolder +Write-Output "Created test directory at $test_dir" +Set-Location $test_dir +# Install pnpm +npm install -g pnpm@v9.3.0 + +# The choice of version here is arbitrary (we just need something we know exists) so that we can test if the +# installer works, given an existing version. This way we're not at the mercy of whether the binary that corresponds +# to the latest commit exists. +npm --prefix "$installer_dir" version --allow-same-version 0.23.0 +Write-Output "Temporarily patched package.json to fixed stable binary" + +# Install all the dependencies, including `rover` +pnpm init +pnpm add "file:$installer_dir" +Write-Output "Installed rover as local npm package" + +# Move to the installed location +$node_modules_path=[System.IO.Path]::Combine($test_dir, "node_modules", ".bin") +Set-Location $node_modules_path + +# Check the version +Write-Output "Checking version" +$dir_sep=[IO.Path]::DirectorySeparatorChar +$rover_command=".${dir_sep}rover --version" +Invoke-Expression $rover_command +Write-Output "Checked version, all ok!" diff --git a/installers/npm/binary.js b/installers/npm/binary.js index d8198efbb..230955e19 100644 --- a/installers/npm/binary.js +++ b/installers/npm/binary.js @@ -25,37 +25,39 @@ const supportedPlatforms = [ ARCHITECTURE: "x64", RUST_TARGET: "x86_64-pc-windows-msvc", BINARY_NAME: `${name}-${version}.exe`, + RAW_NAME: `${name}.exe` }, { TYPE: "Linux", ARCHITECTURE: "x64", RUST_TARGET: "x86_64-unknown-linux-gnu", BINARY_NAME: `${name}-${version}`, + RAW_NAME: `${name}` }, { TYPE: "Linux", ARCHITECTURE: "arm64", RUST_TARGET: "aarch64-unknown-linux-gnu", BINARY_NAME: `${name}-${version}`, + RAW_NAME: `${name}` }, { TYPE: "Darwin", ARCHITECTURE: "x64", RUST_TARGET: "x86_64-apple-darwin", BINARY_NAME: `${name}-${version}`, + RAW_NAME: `${name}` }, { TYPE: "Darwin", ARCHITECTURE: "arm64", RUST_TARGET: "aarch64-apple-darwin", BINARY_NAME: `${name}-${version}`, + RAW_NAME: `${name}` }, ]; -const getPlatform = () => { - const type = os.type(); - const architecture = os.arch(); - +const getPlatform = (type = os.type(), architecture = os.arch()) => { for (let supportedPlatform of supportedPlatforms) { if ( type === supportedPlatform.TYPE && @@ -102,7 +104,7 @@ const getPlatform = () => { /*! Copyright (c) 2019 Avery Harnish - MIT License */ class Binary { - constructor(name, url, installDirectory) { + constructor(name, raw_name, url, installDirectory) { let errors = []; if (typeof url !== "string") { errors.push("url must be a string"); @@ -132,6 +134,7 @@ class Binary { } this.url = url; this.name = name; + this.raw_name = raw_name; this.installDirectory = installDirectory; if (!existsSync(this.installDirectory)) { @@ -176,7 +179,7 @@ class Binary { }); }) .then(() => { - fs.renameSync(join(this.installDirectory, name), this.binaryPath); + fs.renameSync(join(this.installDirectory, this.raw_name), this.binaryPath); if (!suppressLogs) { console.error(`${this.name} has been installed!`); } @@ -212,8 +215,7 @@ class Binary { } } -const getBinary = (overrideInstallDirectory) => { - const platform = getPlatform(); +const getBinary = (overrideInstallDirectory, platform = getPlatform()) => { const download_host = process.env.npm_config_apollo_rover_download_host || process.env.APOLLO_ROVER_DOWNLOAD_HOST || 'https://rover.apollo.dev' // the url for this binary is constructed from values in `package.json` // https://rover.apollo.dev/tar/rover/x86_64-unknown-linux-gnu/v0.4.8 @@ -224,7 +226,7 @@ const getBinary = (overrideInstallDirectory) => { if (overrideInstallDirectory != null && overrideInstallDirectory !== "") { installDirectory = overrideInstallDirectory } - let binary = new Binary(platform.BINARY_NAME, url, installDirectory); + let binary = new Binary(platform.BINARY_NAME, platform.RAW_NAME, url, installDirectory); // setting this allows us to extract supergraph plugins to the proper directory // the variable itself is read in Rust code diff --git a/installers/npm/tests/binary.test.js b/installers/npm/tests/binary.test.js index d48b0a025..00d8b617c 100644 --- a/installers/npm/tests/binary.test.js +++ b/installers/npm/tests/binary.test.js @@ -6,9 +6,18 @@ const fs = require("node:fs"); const crypto = require("node:crypto"); const MockAdapter = require("axios-mock-adapter"); const axios = require("axios"); +const {getPlatform} = require("../binary"); var mock = new MockAdapter(axios); -mock.onGet(new RegExp("https://rover\.apollo\.dev.*")).reply(function (_) { +mock.onGet(new RegExp("https://rover\.apollo\.dev/tar/rover/x86_64-pc-windows-msvc/.*")).reply(function (_) { + return [ + 200, + fs.createReadStream( + path.join(__dirname, "fake_tarballs", "rover-fake-windows.tar.gz"), + ), + ]; +}); +mock.onGet(new RegExp("https://rover\.apollo\.dev/tar/rover/.*")).reply(function (_) { return [ 200, fs.createReadStream( @@ -17,6 +26,7 @@ mock.onGet(new RegExp("https://rover\.apollo\.dev.*")).reply(function (_) { ]; }); + test("getBinary should be created with correct name and URL", () => { fs.mkdtempSync(path.join(os.tmpdir(), "rover-tests-")); const bin = binary.getBinary(os.tmpdir()); @@ -129,6 +139,21 @@ test("install renames binary properly", async () => { ).toHaveLength(1); }); +test("install renames binary properly (Windows)", async () => { + // Establish temporary directory + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "rover-tests-")); + // Create a Binary object + const bin = binary.getBinary(directory, getPlatform("Windows_NT", "x64")); + const directory_entries = await bin.install({}, true).then(async () => { + return fs.readdirSync(directory, { withFileTypes: true }); + }); + expect( + directory_entries.filter( + (d) => d.isFile() && d.name === `rover-${pjson.version}.exe`, + ), + ).toHaveLength(1); +}); + test("install adds a new binary if another version exists", async () => { // Create the temporary directory const directory = fs.mkdtempSync(path.join(os.tmpdir(), "rover-tests-")); diff --git a/installers/npm/tests/fake_tarballs/rover-fake-windows.tar.gz b/installers/npm/tests/fake_tarballs/rover-fake-windows.tar.gz new file mode 100644 index 000000000..854656031 Binary files /dev/null and b/installers/npm/tests/fake_tarballs/rover-fake-windows.tar.gz differ