diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js
index 8130f58c..4bac2864 100644
--- a/dist/cache-save/index.js
+++ b/dist/cache-save/index.js
@@ -3477,6 +3477,7 @@ const file_command_1 = __nccwpck_require__(717);
 const utils_1 = __nccwpck_require__(5278);
 const os = __importStar(__nccwpck_require__(2037));
 const path = __importStar(__nccwpck_require__(1017));
+const uuid_1 = __nccwpck_require__(8974);
 const oidc_utils_1 = __nccwpck_require__(8041);
 /**
  * The code to exit an action
@@ -3506,9 +3507,20 @@ function exportVariable(name, val) {
     process.env[name] = convertedVal;
     const filePath = process.env['GITHUB_ENV'] || '';
     if (filePath) {
-        return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
+        const delimiter = `ghadelimiter_${uuid_1.v4()}`;
+        // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.
+        if (name.includes(delimiter)) {
+            throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
+        }
+        if (convertedVal.includes(delimiter)) {
+            throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
+        }
+        const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
+        file_command_1.issueCommand('ENV', commandValue);
+    }
+    else {
+        command_1.issueCommand('set-env', { name }, convertedVal);
     }
-    command_1.issueCommand('set-env', { name }, convertedVal);
 }
 exports.exportVariable = exportVariable;
 /**
@@ -3526,7 +3538,7 @@ exports.setSecret = setSecret;
 function addPath(inputPath) {
     const filePath = process.env['GITHUB_PATH'] || '';
     if (filePath) {
-        file_command_1.issueFileCommand('PATH', inputPath);
+        file_command_1.issueCommand('PATH', inputPath);
     }
     else {
         command_1.issueCommand('add-path', {}, inputPath);
@@ -3566,10 +3578,7 @@ function getMultilineInput(name, options) {
     const inputs = getInput(name, options)
         .split('\n')
         .filter(x => x !== '');
-    if (options && options.trimWhitespace === false) {
-        return inputs;
-    }
-    return inputs.map(input => input.trim());
+    return inputs;
 }
 exports.getMultilineInput = getMultilineInput;
 /**
@@ -3602,12 +3611,8 @@ exports.getBooleanInput = getBooleanInput;
  */
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function setOutput(name, value) {
-    const filePath = process.env['GITHUB_OUTPUT'] || '';
-    if (filePath) {
-        return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
-    }
     process.stdout.write(os.EOL);
-    command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
+    command_1.issueCommand('set-output', { name }, value);
 }
 exports.setOutput = setOutput;
 /**
@@ -3736,11 +3741,7 @@ exports.group = group;
  */
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function saveState(name, value) {
-    const filePath = process.env['GITHUB_STATE'] || '';
-    if (filePath) {
-        return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
-    }
-    command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
+    command_1.issueCommand('save-state', { name }, value);
 }
 exports.saveState = saveState;
 /**
@@ -3806,14 +3807,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
     return result;
 };
 Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
+exports.issueCommand = void 0;
 // We use any as a valid input type
 /* eslint-disable @typescript-eslint/no-explicit-any */
 const fs = __importStar(__nccwpck_require__(7147));
 const os = __importStar(__nccwpck_require__(2037));
-const uuid_1 = __nccwpck_require__(8974);
 const utils_1 = __nccwpck_require__(5278);
-function issueFileCommand(command, message) {
+function issueCommand(command, message) {
     const filePath = process.env[`GITHUB_${command}`];
     if (!filePath) {
         throw new Error(`Unable to find environment variable for file command ${command}`);
@@ -3825,22 +3825,7 @@ function issueFileCommand(command, message) {
         encoding: 'utf8'
     });
 }
-exports.issueFileCommand = issueFileCommand;
-function prepareKeyValueMessage(key, value) {
-    const delimiter = `ghadelimiter_${uuid_1.v4()}`;
-    const convertedValue = utils_1.toCommandValue(value);
-    // These should realistically never happen, but just in case someone finds a
-    // way to exploit uuid generation let's not allow keys or values that contain
-    // the delimiter.
-    if (key.includes(delimiter)) {
-        throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
-    }
-    if (convertedValue.includes(delimiter)) {
-        throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
-    }
-    return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
-}
-exports.prepareKeyValueMessage = prepareKeyValueMessage;
+exports.issueCommand = issueCommand;
 //# sourceMappingURL=file-command.js.map
 
 /***/ }),
diff --git a/dist/setup/index.js b/dist/setup/index.js
index ce1cc756..cf999af4 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -3477,6 +3477,7 @@ const file_command_1 = __nccwpck_require__(717);
 const utils_1 = __nccwpck_require__(5278);
 const os = __importStar(__nccwpck_require__(2037));
 const path = __importStar(__nccwpck_require__(1017));
+const uuid_1 = __nccwpck_require__(8974);
 const oidc_utils_1 = __nccwpck_require__(8041);
 /**
  * The code to exit an action
@@ -3506,9 +3507,20 @@ function exportVariable(name, val) {
     process.env[name] = convertedVal;
     const filePath = process.env['GITHUB_ENV'] || '';
     if (filePath) {
-        return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
+        const delimiter = `ghadelimiter_${uuid_1.v4()}`;
+        // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.
+        if (name.includes(delimiter)) {
+            throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
+        }
+        if (convertedVal.includes(delimiter)) {
+            throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
+        }
+        const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
+        file_command_1.issueCommand('ENV', commandValue);
+    }
+    else {
+        command_1.issueCommand('set-env', { name }, convertedVal);
     }
-    command_1.issueCommand('set-env', { name }, convertedVal);
 }
 exports.exportVariable = exportVariable;
 /**
@@ -3526,7 +3538,7 @@ exports.setSecret = setSecret;
 function addPath(inputPath) {
     const filePath = process.env['GITHUB_PATH'] || '';
     if (filePath) {
-        file_command_1.issueFileCommand('PATH', inputPath);
+        file_command_1.issueCommand('PATH', inputPath);
     }
     else {
         command_1.issueCommand('add-path', {}, inputPath);
@@ -3566,10 +3578,7 @@ function getMultilineInput(name, options) {
     const inputs = getInput(name, options)
         .split('\n')
         .filter(x => x !== '');
-    if (options && options.trimWhitespace === false) {
-        return inputs;
-    }
-    return inputs.map(input => input.trim());
+    return inputs;
 }
 exports.getMultilineInput = getMultilineInput;
 /**
@@ -3602,12 +3611,8 @@ exports.getBooleanInput = getBooleanInput;
  */
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function setOutput(name, value) {
-    const filePath = process.env['GITHUB_OUTPUT'] || '';
-    if (filePath) {
-        return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
-    }
     process.stdout.write(os.EOL);
-    command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
+    command_1.issueCommand('set-output', { name }, value);
 }
 exports.setOutput = setOutput;
 /**
@@ -3736,11 +3741,7 @@ exports.group = group;
  */
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function saveState(name, value) {
-    const filePath = process.env['GITHUB_STATE'] || '';
-    if (filePath) {
-        return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
-    }
-    command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
+    command_1.issueCommand('save-state', { name }, value);
 }
 exports.saveState = saveState;
 /**
@@ -3806,14 +3807,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
     return result;
 };
 Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
+exports.issueCommand = void 0;
 // We use any as a valid input type
 /* eslint-disable @typescript-eslint/no-explicit-any */
 const fs = __importStar(__nccwpck_require__(7147));
 const os = __importStar(__nccwpck_require__(2037));
-const uuid_1 = __nccwpck_require__(8974);
 const utils_1 = __nccwpck_require__(5278);
-function issueFileCommand(command, message) {
+function issueCommand(command, message) {
     const filePath = process.env[`GITHUB_${command}`];
     if (!filePath) {
         throw new Error(`Unable to find environment variable for file command ${command}`);
@@ -3825,22 +3825,7 @@ function issueFileCommand(command, message) {
         encoding: 'utf8'
     });
 }
-exports.issueFileCommand = issueFileCommand;
-function prepareKeyValueMessage(key, value) {
-    const delimiter = `ghadelimiter_${uuid_1.v4()}`;
-    const convertedValue = utils_1.toCommandValue(value);
-    // These should realistically never happen, but just in case someone finds a
-    // way to exploit uuid generation let's not allow keys or values that contain
-    // the delimiter.
-    if (key.includes(delimiter)) {
-        throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
-    }
-    if (convertedValue.includes(delimiter)) {
-        throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
-    }
-    return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
-}
-exports.prepareKeyValueMessage = prepareKeyValueMessage;
+exports.issueCommand = issueCommand;
 //# sourceMappingURL=file-command.js.map
 
 /***/ }),
@@ -73181,6 +73166,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
 };
 Object.defineProperty(exports, "__esModule", ({ value: true }));
 const os = __nccwpck_require__(2037);
+const fs = __nccwpck_require__(7147);
 const assert = __importStar(__nccwpck_require__(9491));
 const core = __importStar(__nccwpck_require__(2186));
 const hc = __importStar(__nccwpck_require__(9925));
@@ -73188,15 +73174,19 @@ const io = __importStar(__nccwpck_require__(7436));
 const tc = __importStar(__nccwpck_require__(7784));
 const path = __importStar(__nccwpck_require__(1017));
 const semver = __importStar(__nccwpck_require__(5911));
-const fs = __nccwpck_require__(7147);
+// TODO: unify with other isNnn?
+const V8_CANARY = 'v8-canary';
+const isVersionCanary = (versionSpec) => versionSpec.includes(`-${V8_CANARY}`);
 function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) {
     return __awaiter(this, void 0, void 0, function* () {
         // Store manifest data to avoid multiple calls
         let manifest;
         let nodeVersions;
-        let isNightly = versionSpec.includes('nightly');
+        let isCanary = isVersionCanary(versionSpec);
+        let isNightly = versionSpec.includes('nightly') && !isCanary; // avoid both set by preceding isCanary
         let osPlat = os.platform();
         let osArch = translateArchToDistUrl(arch);
+        core.debug(`get node isLtsAlias=${isLtsAlias(versionSpec)} isCanary=${isCanary} isNightly=${isNightly}`);
         if (isLtsAlias(versionSpec)) {
             core.info('Attempt to resolve LTS alias from manifest...');
             // No try-catch since it's not possible to resolve LTS alias without manifest
@@ -73204,15 +73194,18 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) {
             versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest);
         }
         if (isLatestSyntax(versionSpec)) {
+            core.debug('will use latest node version from node repository because of latest syntax ...');
             nodeVersions = yield getVersionsFromDist(versionSpec);
             versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions);
-            core.info(`getting latest node version...`);
+            core.info(`getting latest node version ${versionSpec}...`);
         }
-        if (isNightly && checkLatest) {
+        if (isNightly && checkLatest || isCanary && checkLatest) {
+            core.debug(`will check latest node version from node repository because of check latest input with ${isNightly ? 'nightly' : 'canary'} set...`);
             nodeVersions = yield getVersionsFromDist(versionSpec);
             versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions);
+            core.info(`getting node version ${versionSpec}...`);
         }
-        if (checkLatest && !isNightly) {
+        if (checkLatest && !isNightly && !isCanary) {
             core.info('Attempt to resolve the latest version from manifest...');
             const resolvedVersion = yield resolveVersionFromManifest(versionSpec, stable, auth, osArch, manifest);
             if (resolvedVersion) {
@@ -73226,9 +73219,9 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) {
         // check cache
         core.debug('check toolcache');
         let toolPath;
-        if (isNightly) {
-            const nightlyVersion = findNightlyVersionInHostedToolcache(versionSpec, osArch);
-            toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch);
+        if (isNightly || isCanary) {
+            const nightlyOrCanaryVersion = findNightlyOrCanaryVersionInHostedToolcache(versionSpec, osArch);
+            toolPath = nightlyOrCanaryVersion && tc.find('node', nightlyOrCanaryVersion, osArch);
         }
         else {
             toolPath = tc.find('node', versionSpec, osArch);
@@ -73245,7 +73238,7 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) {
             // Try download from internal distribution (popular versions only)
             //
             try {
-                info = yield getInfoFromManifest(versionSpec, !isNightly, auth, osArch, manifest);
+                info = yield getInfoFromManifest(versionSpec, !isNightly && !isVersionCanary, auth, osArch, manifest);
                 if (info) {
                     core.info(`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`);
                     downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, auth);
@@ -73328,11 +73321,10 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) {
     });
 }
 exports.getNode = getNode;
-function findNightlyVersionInHostedToolcache(versionsSpec, osArch) {
+function findNightlyOrCanaryVersionInHostedToolcache(versionsSpec, osArch) {
     const foundAllVersions = tc.findAllVersions('node', osArch);
     core.debug(foundAllVersions.join('\n'));
-    const version = evaluateVersions(foundAllVersions, versionsSpec);
-    return version;
+    return evaluateVersions(foundAllVersions, versionsSpec);
 }
 function isLtsAlias(versionSpec) {
     return versionSpec.startsWith('lts/');
@@ -73388,7 +73380,14 @@ function getInfoFromDist(versionSpec, arch = os.arch(), nodeVersions) {
     return __awaiter(this, void 0, void 0, function* () {
         let osPlat = os.platform();
         let osArch = translateArchToDistUrl(arch);
+        core.debug(`getting the release version from index.json in node repository`);
         let version = yield queryDistForMatch(versionSpec, arch, nodeVersions);
+        if (version) {
+            core.debug(`got "${version}" release version from index.json in node repository`);
+        }
+        else {
+            core.debug(`release version from index.json in node repository not found`);
+        }
         if (!version) {
             return null;
         }
@@ -73460,6 +73459,48 @@ function evaluateNightlyVersions(versions, versionSpec) {
     }
     return version;
 }
+function evaluateCanaryVersions(versions, versionSpec) {
+    var _a;
+    let version = '';
+    let range;
+    const [raw, prerelease] = versionSpec.split(/-(.*)/s);
+    const isValidVersion = semver.valid(raw);
+    const rawVersion = isValidVersion ? raw : (_a = semver.coerce(raw)) === null || _a === void 0 ? void 0 : _a.version;
+    if (rawVersion) {
+        if (prerelease === V8_CANARY) {
+            range = semver.validRange(`^${rawVersion}`);
+        }
+        else {
+            range = `${rawVersion}+${prerelease.replace(V8_CANARY, V8_CANARY + '.')}`;
+        }
+    }
+    core.debug(`evaluate canary versions rawVersion="${rawVersion}" range="${range}"`);
+    if (range) {
+        const versionsReversed = versions.sort((a, b) => {
+            if (semver.gt(a, b)) {
+                return -1;
+            }
+            else if (semver.lt(a, b)) {
+                return 1;
+            }
+            return 0;
+        });
+        for (const potential of versionsReversed) {
+            const satisfied = semver.satisfies(potential.replace('-' + V8_CANARY, '+' + V8_CANARY + '.'), range);
+            if (satisfied) {
+                version = potential;
+                break;
+            }
+        }
+    }
+    if (version) {
+        core.debug(`matched: ${version}`);
+    }
+    else {
+        core.debug('match not found');
+    }
+    return version;
+}
 // TODO - should we just export this from @actions/tool-cache? Lifted directly from there
 function evaluateVersions(versions, versionSpec) {
     let version = '';
@@ -73468,6 +73509,9 @@ function evaluateVersions(versions, versionSpec) {
     if (versionSpec.includes('nightly')) {
         return evaluateNightlyVersions(versions, versionSpec);
     }
+    else if (isVersionCanary(versionSpec)) {
+        return evaluateCanaryVersions(versions, versionSpec);
+    }
     versions = versions.sort((a, b) => {
         if (semver.gt(a, b)) {
             return 1;
@@ -73493,12 +73537,18 @@ function evaluateVersions(versions, versionSpec) {
 function getNodejsDistUrl(version) {
     const prerelease = semver.prerelease(version);
     if (version.includes('nightly')) {
+        core.debug('requested nightly build');
         return 'https://nodejs.org/download/nightly';
     }
+    else if (isVersionCanary(version)) {
+        core.debug('requested v8 canary build');
+        return 'https://nodejs.org/download/v8-canary';
+    }
     else if (!prerelease) {
         return 'https://nodejs.org/dist';
     }
     else {
+        core.debug('requested RC build');
         return 'https://nodejs.org/download/rc';
     }
 }
@@ -73545,6 +73595,7 @@ function getVersionsFromDist(versionSpec) {
     return __awaiter(this, void 0, void 0, function* () {
         const initialUrl = getNodejsDistUrl(versionSpec);
         const dataUrl = `${initialUrl}/index.json`;
+        core.debug(`download ${dataUrl}`);
         let httpClient = new hc.HttpClient('setup-node', [], {
             allowRetries: true,
             maxRetries: 3
diff --git a/src/installer.ts b/src/installer.ts
index 9d7e017f..7aaf0f50 100644
--- a/src/installer.ts
+++ b/src/installer.ts
@@ -1,4 +1,5 @@
 import os = require('os');
+import fs = require('fs');
 import * as assert from 'assert';
 import * as core from '@actions/core';
 import * as hc from '@actions/http-client';
@@ -6,13 +7,13 @@ import * as io from '@actions/io';
 import * as tc from '@actions/tool-cache';
 import * as path from 'path';
 import * as semver from 'semver';
-import fs = require('fs');
 
 //
 // Node versions interface
 // see https://nodejs.org/dist/index.json
 // for nightly https://nodejs.org/download/nightly/index.json
-//
+// for canary https://nodejs.org/download/v8-canary/index.json
+
 export interface INodeVersion {
   version: string;
   files: string[];
@@ -29,6 +30,10 @@ interface INodeRelease extends tc.IToolRelease {
   lts?: string;
 }
 
+// TODO: unify with other isNnn?
+const V8_CANARY = 'v8-canary';
+const isVersionCanary = (versionSpec: string):boolean => versionSpec.includes(`-${V8_CANARY}`);
+
 export async function getNode(
   versionSpec: string,
   stable: boolean,
@@ -39,10 +44,13 @@ export async function getNode(
   // Store manifest data to avoid multiple calls
   let manifest: INodeRelease[] | undefined;
   let nodeVersions: INodeVersion[] | undefined;
-  let isNightly = versionSpec.includes('nightly');
+  let isCanary = isVersionCanary(versionSpec);
+  let isNightly = versionSpec.includes('nightly') && !isCanary; // avoid both set by preceding isCanary
   let osPlat: string = os.platform();
   let osArch: string = translateArchToDistUrl(arch);
 
+  core.debug(`get node isLtsAlias=${isLtsAlias(versionSpec)} isCanary=${isCanary} isNightly=${isNightly}`);
+
   if (isLtsAlias(versionSpec)) {
     core.info('Attempt to resolve LTS alias from manifest...');
 
@@ -53,17 +61,22 @@ export async function getNode(
   }
 
   if (isLatestSyntax(versionSpec)) {
+    core.debug('will use latest node version from node repository because of latest syntax ...');
     nodeVersions = await getVersionsFromDist(versionSpec);
     versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions);
-    core.info(`getting latest node version...`);
+    core.info(`getting latest node version ${versionSpec}...`);
   }
 
-  if (isNightly && checkLatest) {
+  if (isNightly && checkLatest || isCanary && checkLatest) {
+    core.debug(
+      `will check latest node version from node repository because of check latest input with ${isNightly ? 'nightly' : 'canary'} set...`
+    );
     nodeVersions = await getVersionsFromDist(versionSpec);
     versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions);
+    core.info(`getting node version ${versionSpec}...`);
   }
 
-  if (checkLatest && !isNightly) {
+  if (checkLatest && !isNightly && !isCanary) {
     core.info('Attempt to resolve the latest version from manifest...');
     const resolvedVersion = await resolveVersionFromManifest(
       versionSpec,
@@ -83,12 +96,12 @@ export async function getNode(
   // check cache
   core.debug('check toolcache');
   let toolPath: string;
-  if (isNightly) {
-    const nightlyVersion = findNightlyVersionInHostedToolcache(
+  if (isNightly || isCanary) {
+    const nightlyOrCanaryVersion = findNightlyOrCanaryVersionInHostedToolcache(
       versionSpec,
       osArch
     );
-    toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch);
+    toolPath = nightlyOrCanaryVersion && tc.find('node', nightlyOrCanaryVersion, osArch);
   } else {
     toolPath = tc.find('node', versionSpec, osArch);
   }
@@ -107,7 +120,7 @@ export async function getNode(
     try {
       info = await getInfoFromManifest(
         versionSpec,
-        !isNightly,
+        !isNightly && !isVersionCanary,
         auth,
         osArch,
         manifest
@@ -215,15 +228,13 @@ export async function getNode(
   core.addPath(toolPath);
 }
 
-function findNightlyVersionInHostedToolcache(
+function findNightlyOrCanaryVersionInHostedToolcache(
   versionsSpec: string,
   osArch: string
 ) {
   const foundAllVersions = tc.findAllVersions('node', osArch);
   core.debug(foundAllVersions.join('\n'));
-  const version = evaluateVersions(foundAllVersions, versionsSpec);
-
-  return version;
+  return evaluateVersions(foundAllVersions, versionsSpec);
 }
 
 function isLtsAlias(versionSpec: string): boolean {
@@ -313,11 +324,17 @@ async function getInfoFromDist(
   let osPlat: string = os.platform();
   let osArch: string = translateArchToDistUrl(arch);
 
+  core.debug(`getting the release version from index.json in node repository`);
   let version: string = await queryDistForMatch(
     versionSpec,
     arch,
     nodeVersions
   );
+  if (version) {
+    core.debug(`got "${version}" release version from index.json in node repository`);
+  } else {
+    core.debug(`release version from index.json in node repository not found`);
+  }
 
   if (!version) {
     return null;
@@ -412,6 +429,53 @@ function evaluateNightlyVersions(
   return version;
 }
 
+function evaluateCanaryVersions(
+  versions: string[],
+  versionSpec: string
+): string {
+  let version = '';
+  let range: string | null | undefined;
+  const [raw, prerelease] = versionSpec.split(/-(.*)/s);
+  const isValidVersion = semver.valid(raw);
+  const rawVersion = isValidVersion ? raw : semver.coerce(raw)?.version;
+  if (rawVersion) {
+    if (prerelease === V8_CANARY) {
+      range = semver.validRange(`^${rawVersion}`);
+    } else {
+      range = `${rawVersion}+${prerelease.replace(V8_CANARY, V8_CANARY + '.')}`;
+    }
+  }
+  core.debug(`evaluate canary versions rawVersion="${rawVersion}" range="${range}"`)
+
+  if (range) {
+    const versionsReversed = versions.sort((a, b) => {
+      if (semver.gt(a, b)) {
+        return -1;
+      } else if (semver.lt(a, b)) {
+        return 1;
+      }
+      return 0;
+    });
+    for (const potential of versionsReversed) {
+      const satisfied: boolean = semver.satisfies(
+        potential.replace('-' + V8_CANARY, '+' + V8_CANARY + '.'),
+        range);
+      if (satisfied) {
+        version = potential;
+        break;
+      }
+    }
+  }
+
+  if (version) {
+    core.debug(`matched: ${version}`);
+  } else {
+    core.debug('match not found');
+  }
+
+  return version;
+}
+
 // TODO - should we just export this from @actions/tool-cache? Lifted directly from there
 function evaluateVersions(versions: string[], versionSpec: string): string {
   let version = '';
@@ -420,6 +484,8 @@ function evaluateVersions(versions: string[], versionSpec: string): string {
 
   if (versionSpec.includes('nightly')) {
     return evaluateNightlyVersions(versions, versionSpec);
+  } else if (isVersionCanary(versionSpec)) {
+    return evaluateCanaryVersions(versions, versionSpec);
   }
 
   versions = versions.sort((a, b) => {
@@ -449,10 +515,15 @@ function evaluateVersions(versions: string[], versionSpec: string): string {
 function getNodejsDistUrl(version: string) {
   const prerelease = semver.prerelease(version);
   if (version.includes('nightly')) {
+    core.debug('requested nightly build');
     return 'https://nodejs.org/download/nightly';
+  } else if (isVersionCanary(version)) {
+    core.debug('requested v8 canary build');
+    return 'https://nodejs.org/download/v8-canary';
   } else if (!prerelease) {
     return 'https://nodejs.org/dist';
   } else {
+    core.debug('requested RC build');
     return 'https://nodejs.org/download/rc';
   }
 }
@@ -510,6 +581,7 @@ export async function getVersionsFromDist(
 ): Promise<INodeVersion[]> {
   const initialUrl = getNodejsDistUrl(versionSpec);
   const dataUrl = `${initialUrl}/index.json`;
+  core.debug(`download ${dataUrl}`);
   let httpClient = new hc.HttpClient('setup-node', [], {
     allowRetries: true,
     maxRetries: 3