Compare commits

...

2 Commits

Author SHA1 Message Date
Henry Mercer
14567d9b12 Log disk info throughout for debugging 2023-08-01 18:36:46 +01:00
Henry Mercer
5edc73a9a0 Update workflow for testing 2023-08-01 18:24:05 +01:00
13 changed files with 743 additions and 124 deletions

View File

@@ -1,80 +1,15 @@
name: "CodeQL action"
on:
push:
branches: [main, releases/v2]
pull_request:
branches: [main, releases/v2]
# Run checks on reopened draft PRs to support triggering PR checks on draft PRs that were opened
# by other workflows.
types: [opened, synchronize, reopened, ready_for_review]
schedule:
# Weekly on Sunday.
- cron: '30 1 * * 0'
on: push
env:
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
jobs:
# Identify the CodeQL tool versions to use in the analysis job.
check-codeql-versions:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.compare.outputs.versions }}
permissions:
security-events: write
steps:
- uses: actions/checkout@v3
- name: Init with default CodeQL bundle from the VM image
id: init-default
uses: ./init
with:
languages: javascript
- name: Remove empty database
# allows us to run init a second time
run: |
rm -rf "$RUNNER_TEMP/codeql_databases"
- name: Init with latest CodeQL bundle
id: init-latest
uses: ./init
with:
tools: latest
languages: javascript
- name: Compare default and latest CodeQL bundle versions
id: compare
env:
CODEQL_DEFAULT: ${{ steps.init-default.outputs.codeql-path }}
CODEQL_LATEST: ${{ steps.init-latest.outputs.codeql-path }}
run: |
CODEQL_VERSION_DEFAULT="$("$CODEQL_DEFAULT" version --format terse)"
CODEQL_VERSION_LATEST="$("$CODEQL_LATEST" version --format terse)"
echo "Default CodeQL bundle version is $CODEQL_VERSION_DEFAULT"
echo "Latest CodeQL bundle version is $CODEQL_VERSION_LATEST"
# If we're running on a pull request, run with both bundles, even if `tools: latest` would
# be the same as `tools: null`. This allows us to make the job for each of the bundles a
# required status check.
#
# If we're running on push or schedule, then we can skip running with `tools: latest` when it would be
# the same as running with `tools: null`.
if [[ "$GITHUB_EVENT_NAME" != "pull_request" && "$CODEQL_VERSION_DEFAULT" == "$CODEQL_VERSION_LATEST" ]]; then
VERSIONS_JSON='[null]'
else
VERSIONS_JSON='[null, "latest"]'
fi
# Output a JSON-encoded list with the distinct versions to test against.
echo "Suggested matrix config for analysis job: $VERSIONS_JSON"
echo "versions=${VERSIONS_JSON}" >> $GITHUB_OUTPUT
build:
needs: [check-codeql-versions]
strategy:
matrix:
os: [ubuntu-latest,windows-latest,macos-latest]
tools: ${{ fromJson(needs.check-codeql-versions.outputs.versions) }}
runs-on: ${{ matrix.os }}
permissions:
@@ -89,7 +24,7 @@ jobs:
with:
languages: javascript
config-file: ./.github/codeql/codeql-config.yml
tools: ${{ matrix.tools }}
tools: https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/codeql-bundle-20230801/codeql-bundle-linux64.tar.gz
# confirm steps.init.outputs.codeql-path points to the codeql binary
- name: Print CodeQL Version
run: ${{steps.init.outputs.codeql-path}} version --format=json

65
lib/setup-codeql.js generated
View File

@@ -31,6 +31,7 @@ const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const perf_hooks_1 = require("perf_hooks");
const toolcache = __importStar(require("@actions/tool-cache"));
const check_disk_space_1 = __importDefault(require("check-disk-space"));
const del_1 = __importDefault(require("del"));
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
const semver = __importStar(require("semver"));
@@ -88,14 +89,14 @@ function tryGetCodeQLCliVersionForRelease(release, logger) {
return undefined;
}
else if (cliVersionsFromMarkerFiles.length === 0) {
logger.debug(`Failed to find the CodeQL CLI version for release ${release.tag_name}.`);
logger.info(`Failed to find the CodeQL CLI version for release ${release.tag_name}.`);
return undefined;
}
return cliVersionsFromMarkerFiles[0];
}
async function tryFindCliVersionDotcomOnly(tagName, logger) {
try {
logger.debug(`Fetching the GitHub Release for the CodeQL bundle tagged ${tagName}.`);
logger.info(`Fetching the GitHub Release for the CodeQL bundle tagged ${tagName}.`);
const apiClient = api.getApiClient();
const codeQLActionRepository = getCodeQLActionRepository(logger);
const release = await apiClient.rest.repos.getReleaseByTag({
@@ -106,7 +107,7 @@ async function tryFindCliVersionDotcomOnly(tagName, logger) {
return tryGetCodeQLCliVersionForRelease(release.data, logger);
}
catch (e) {
logger.debug(`Failed to find the CLI version for the CodeQL bundle tagged ${tagName}. ${(0, util_1.wrapError)(e).message}`);
logger.info(`Failed to find the CLI version for the CodeQL bundle tagged ${tagName}. ${(0, util_1.wrapError)(e).message}`);
return undefined;
}
}
@@ -181,7 +182,7 @@ async function getCodeQLBundleDownloadURL(tagName, apiDetails, variant, logger)
function tryGetBundleVersionFromTagName(tagName, logger) {
const match = tagName.match(/^codeql-bundle-(.*)$/);
if (match === null || match.length < 2) {
logger.debug(`Could not determine bundle version from tag ${tagName}.`);
logger.info(`Could not determine bundle version from tag ${tagName}.`);
return undefined;
}
return match[1];
@@ -189,7 +190,7 @@ function tryGetBundleVersionFromTagName(tagName, logger) {
function tryGetTagNameFromUrl(url, logger) {
const match = url.match(/\/(codeql-bundle-.*)\//);
if (match === null || match.length < 2) {
logger.debug(`Could not determine tag name for URL ${url}.`);
logger.info(`Could not determine tag name for URL ${url}.`);
return undefined;
}
return match[1];
@@ -204,7 +205,7 @@ function tryGetBundleVersionFromUrl(url, logger) {
exports.tryGetBundleVersionFromUrl = tryGetBundleVersionFromUrl;
function convertToSemVer(version, logger) {
if (!semver.valid(version)) {
logger.debug(`Bundle version ${version} is not in SemVer format. Will treat it as pre-release 0.0.0-${version}.`);
logger.info(`Bundle version ${version} is not in SemVer format. Will treat it as pre-release 0.0.0-${version}.`);
version = `0.0.0-${version}`;
}
const s = semver.clean(version);
@@ -228,7 +229,7 @@ async function findOverridingToolsInCache(humanReadableVersion, logger) {
.filter(({ folder }) => fs.existsSync(path.join(folder, "pinned-version")));
if (candidates.length === 1) {
const candidate = candidates[0];
logger.debug(`CodeQL tools version ${candidate.version} in toolcache overriding version ${humanReadableVersion}.`);
logger.info(`CodeQL tools version ${candidate.version} in toolcache overriding version ${humanReadableVersion}.`);
return {
codeqlFolder: candidate.folder,
sourceType: "toolcache",
@@ -236,10 +237,10 @@ async function findOverridingToolsInCache(humanReadableVersion, logger) {
};
}
else if (candidates.length === 0) {
logger.debug("Did not find any candidate pinned versions of the CodeQL tools in the toolcache.");
logger.info("Did not find any candidate pinned versions of the CodeQL tools in the toolcache.");
}
else {
logger.debug("Could not use CodeQL tools from the toolcache since more than one candidate pinned " +
logger.info("Could not use CodeQL tools from the toolcache since more than one candidate pinned " +
"version was found in the toolcache.");
}
return undefined;
@@ -301,7 +302,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian
tagName ??
url ??
"unknown";
logger.debug("Attempting to obtain CodeQL tools. " +
logger.info("Attempting to obtain CodeQL tools. " +
`CLI version: ${cliVersion ?? "unknown"}, ` +
`bundle tag name: ${tagName ?? "unknown"}, ` +
`URL: ${url ?? "unspecified"}.`);
@@ -311,26 +312,26 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian
codeqlFolder = toolcache.find("CodeQL", cliVersion);
// Fall back to matching `x.y.z-<tagName>`.
if (!codeqlFolder) {
logger.debug("Didn't find a version of the CodeQL tools in the toolcache with a version number " +
logger.info("Didn't find a version of the CodeQL tools in the toolcache with a version number " +
`exactly matching ${cliVersion}.`);
const allVersions = toolcache.findAllVersions("CodeQL");
logger.debug(`Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify(allVersions)}.`);
logger.info(`Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify(allVersions)}.`);
// If there is exactly one version of the CodeQL tools in the toolcache, and that version is
// the form `x.y.z-<tagName>`, then use it.
const candidateVersions = allVersions.filter((version) => version.startsWith(`${cliVersion}-`));
if (candidateVersions.length === 1) {
logger.debug(`Exactly one version of the CodeQL tools starting with ${cliVersion} found in the ` +
logger.info(`Exactly one version of the CodeQL tools starting with ${cliVersion} found in the ` +
"toolcache, using that.");
codeqlFolder = toolcache.find("CodeQL", candidateVersions[0]);
}
else if (candidateVersions.length === 0) {
logger.debug(`Didn't find any versions of the CodeQL tools starting with ${cliVersion} ` +
logger.info(`Didn't find any versions of the CodeQL tools starting with ${cliVersion} ` +
`in the toolcache. Trying next fallback method.`);
}
else {
logger.warning(`Found ${candidateVersions.length} versions of the CodeQL tools starting with ` +
`${cliVersion} in the toolcache, but at most one was expected.`);
logger.debug("Trying next fallback method.");
logger.info("Trying next fallback method.");
}
}
}
@@ -341,7 +342,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian
codeqlFolder = toolcache.find("CodeQL", fallbackVersion);
}
else {
logger.debug("Could not determine a fallback toolcache version number for CodeQL tools version " +
logger.info("Could not determine a fallback toolcache version number for CodeQL tools version " +
`${humanReadableVersion}.`);
}
}
@@ -391,7 +392,7 @@ async function tryGetFallbackToolcacheVersion(cliVersion, tagName, logger) {
return undefined;
}
const fallbackVersion = convertToSemVer(bundleVersion, logger);
logger.debug(`Computed a fallback toolcache version number of ${fallbackVersion} for CodeQL version ` +
logger.info(`Computed a fallback toolcache version number of ${fallbackVersion} for CodeQL version ` +
`${cliVersion ?? tagName}.`);
return fallbackVersion;
}
@@ -408,34 +409,38 @@ async function downloadCodeQL(codeqlURL, maybeBundleVersion, maybeCliVersion, ap
// We also don't want to send an authorization header if there's already a token provided in the URL.
let authorization = undefined;
if (searchParams.has("token")) {
logger.debug("CodeQL tools URL contains an authorization token.");
logger.info("CodeQL tools URL contains an authorization token.");
}
else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
logger.debug("Providing an authorization token to download CodeQL tools.");
logger.info("Providing an authorization token to download CodeQL tools.");
authorization = `token ${apiDetails.auth}`;
}
else {
logger.debug("Downloading CodeQL tools without an authorization token.");
logger.info("Downloading CodeQL tools without an authorization token.");
}
await printDiskSpace(logger);
logger.info(`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`);
const dest = path.join(tempDir, (0, uuid_1.v4)());
const finalHeaders = Object.assign({ "User-Agent": "CodeQL Action" }, headers);
const toolsDownloadStart = perf_hooks_1.performance.now();
const codeqlPath = await toolcache.downloadTool(codeqlURL, dest, authorization, finalHeaders);
const toolsDownloadDurationMs = Math.round(perf_hooks_1.performance.now() - toolsDownloadStart);
logger.debug(`Finished downloading CodeQL bundle to ${codeqlPath} (${toolsDownloadDurationMs} ms).`);
logger.info(`Finished downloading CodeQL bundle to ${codeqlPath} (${toolsDownloadDurationMs} ms).`);
await printDiskSpace(logger);
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
logger.debug(`Finished extracting CodeQL bundle to ${codeqlExtracted}.`);
logger.info(`Finished extracting CodeQL bundle to ${codeqlExtracted}.`);
await printDiskSpace(logger);
try {
await (0, del_1.default)(codeqlPath, { force: true });
logger.debug("Deleted CodeQL bundle archive.");
logger.info("Deleted CodeQL bundle archive.");
}
catch (e) {
logger.warning("Failed to delete CodeQL bundle archive.");
}
await printDiskSpace(logger);
const bundleVersion = maybeBundleVersion ?? tryGetBundleVersionFromUrl(codeqlURL, logger);
if (bundleVersion === undefined) {
logger.debug("Could not cache CodeQL tools because we could not determine the bundle version from the " +
logger.info("Could not cache CodeQL tools because we could not determine the bundle version from the " +
`URL ${codeqlURL}.`);
return {
toolsVersion: maybeCliVersion ?? "unknown",
@@ -461,9 +466,13 @@ async function downloadCodeQL(codeqlURL, maybeBundleVersion, maybeCliVersion, ap
const toolcacheVersion = maybeCliVersion?.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)
? `${maybeCliVersion}-${bundleVersion}`
: convertToSemVer(bundleVersion, logger);
const codeqlFolder = await toolcache.cacheDir(codeqlExtracted, "CodeQL", toolcacheVersion);
await printDiskSpace(logger);
await (0, del_1.default)(codeqlExtracted, { force: true });
await printDiskSpace(logger);
return {
toolsVersion: maybeCliVersion ?? toolcacheVersion,
codeqlFolder: await toolcache.cacheDir(codeqlExtracted, "CodeQL", toolcacheVersion),
codeqlFolder,
toolsDownloadDurationMs,
};
}
@@ -502,7 +511,7 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
break;
case "toolcache":
codeqlFolder = source.codeqlFolder;
logger.debug(`CodeQL found in cache ${codeqlFolder}`);
logger.info(`CodeQL found in cache ${codeqlFolder}`);
toolsSource = ToolsSource.Toolcache;
break;
case "download": {
@@ -519,4 +528,8 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
return { codeqlFolder, toolsDownloadDurationMs, toolsSource, toolsVersion };
}
exports.setupCodeQLBundle = setupCodeQLBundle;
async function printDiskSpace(logger) {
const diskSpaceInfo = await (0, check_disk_space_1.default)(process.platform === "win32" ? path.parse(process.cwd()).root : "/");
logger.info(`Disk space info: ${diskSpaceInfo.free / (1024 * 1024 * 1024)} GB free of ${diskSpaceInfo.size / (1024 * 1024 * 1024)} GB`);
}
//# sourceMappingURL=setup-codeql.js.map

File diff suppressed because one or more lines are too long

8
node_modules/.package-lock.json generated vendored
View File

@@ -1751,6 +1751,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/check-disk-space": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz",
"integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==",
"engines": {
"node": ">=16"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",

21
node_modules/check-disk-space/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2019 Alexandre Demode
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

53
node_modules/check-disk-space/README.md generated vendored Normal file
View File

@@ -0,0 +1,53 @@
# Check disk space
[![Continue Integration](https://img.shields.io/github/workflow/status/Alex-D/check-disk-space/Continuous%20Integration?style=for-the-badge)](https://github.com/Alex-D/check-disk-space/actions/workflows/ci.yml)
[![check-disk-space on npm](https://img.shields.io/npm/v/check-disk-space?style=for-the-badge)](https://www.npmjs.com/package/check-disk-space)
[![License MIT](https://img.shields.io/github/license/Alex-D/check-disk-space.svg?style=for-the-badge)](LICENSE)
## Introduction
Light multi-platform disk space checker without third party for Node.js.
- Works on Linux, macOS and Windows
- Take care of mounting points on unix-like systems
- No dependencies
- TypeScript support
## Install
`npm install check-disk-space`
## Usage
```js
// ES
import checkDiskSpace from 'check-disk-space'
// CommonJS
const checkDiskSpace = require('check-disk-space').default
// On Windows
checkDiskSpace('C:/blabla/bla').then((diskSpace) => {
console.log(diskSpace)
// {
// diskPath: 'C:',
// free: 12345678,
// size: 98756432
// }
// Note: `free` and `size` are in bytes
})
// On Linux or macOS
checkDiskSpace('/mnt/mygames').then((diskSpace) => {
console.log(diskSpace)
// {
// diskPath: '/',
// free: 12345678,
// size: 98756432
// }
// Note: `free` and `size` are in bytes
})
```

210
node_modules/check-disk-space/dist/check-disk-space.cjs generated vendored Normal file
View File

@@ -0,0 +1,210 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_child_process = require('node:child_process');
var promises = require('node:fs/promises');
var node_os = require('node:os');
var node_path = require('node:path');
var node_process = require('node:process');
var node_util = require('node:util');
class InvalidPathError extends Error {
constructor(message) {
super(message);
this.name = 'InvalidPathError';
// Set the prototype explicitly.
Object.setPrototypeOf(this, InvalidPathError.prototype);
}
}
class NoMatchError extends Error {
constructor(message) {
super(message);
this.name = 'NoMatchError';
// Set the prototype explicitly.
Object.setPrototypeOf(this, NoMatchError.prototype);
}
}
/**
* Tells if directory exists
*
* @param directoryPath - The file/folder path
* @param dependencies - Dependencies container
*/
async function isDirectoryExisting(directoryPath, dependencies) {
try {
await dependencies.fsAccess(directoryPath);
return Promise.resolve(true);
}
catch (error) {
return Promise.resolve(false);
}
}
/**
* Get the first existing parent path
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
async function getFirstExistingParentPath(directoryPath, dependencies) {
let parentDirectoryPath = directoryPath;
let parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies);
while (!parentDirectoryFound) {
parentDirectoryPath = dependencies.pathNormalize(parentDirectoryPath + '/..');
parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies);
}
return parentDirectoryPath;
}
/**
* Tell if PowerShell 3 is available based on Windows version
*
* Note: 6.* is Windows 7
* Note: PowerShell 3 is natively available since Windows 8
*
* @param dependencies - Dependencies Injection Container
*/
async function hasPowerShell3(dependencies) {
const major = parseInt(dependencies.release.split('.')[0], 10);
if (major <= 6) {
return false;
}
try {
await dependencies.cpExecFile('where', ['powershell'], { windowsHide: true });
return true;
}
catch (error) {
return false;
}
}
/**
* Check disk space
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
function checkDiskSpace(directoryPath, dependencies = {
platform: node_process.platform,
release: node_os.release(),
fsAccess: promises.access,
pathNormalize: node_path.normalize,
pathSep: node_path.sep,
cpExecFile: node_util.promisify(node_child_process.execFile),
}) {
// Note: This function contains other functions in order
// to wrap them in a common context and make unit tests easier
/**
* Maps command output to a normalized object {diskPath, free, size}
*
* @param stdout - The command output
* @param filter - To filter drives (only used for win32)
* @param mapping - Map between column index and normalized column name
* @param coefficient - The size coefficient to get bytes instead of kB
*/
function mapOutput(stdout, filter, mapping, coefficient) {
const parsed = stdout
.split('\n') // Split lines
.map(line => line.trim()) // Trim all lines
.filter(line => line.length !== 0) // Remove empty lines
.slice(1) // Remove header
.map(line => line.split(/\s+(?=[\d/])/)); // Split on spaces to get columns
const filtered = parsed.filter(filter);
if (filtered.length === 0) {
throw new NoMatchError();
}
const diskData = filtered[0];
return {
diskPath: diskData[mapping.diskPath],
free: parseInt(diskData[mapping.free], 10) * coefficient,
size: parseInt(diskData[mapping.size], 10) * coefficient,
};
}
/**
* Run the command and do common things between win32 and unix
*
* @param cmd - The command to execute
* @param filter - To filter drives (only used for win32)
* @param mapping - Map between column index and normalized column name
* @param coefficient - The size coefficient to get bytes instead of kB
*/
async function check(cmd, filter, mapping, coefficient = 1) {
const [file, ...args] = cmd;
/* istanbul ignore if */
if (file === undefined) {
return Promise.reject(new Error('cmd must contain at least one item'));
}
try {
const { stdout } = await dependencies.cpExecFile(file, args, { windowsHide: true });
return mapOutput(stdout, filter, mapping, coefficient);
}
catch (error) {
return Promise.reject(error);
}
}
/**
* Build the check call for win32
*
* @param directoryPath - The file/folder path from where we want to know disk space
*/
async function checkWin32(directoryPath) {
if (directoryPath.charAt(1) !== ':') {
return Promise.reject(new InvalidPathError(`The following path is invalid (should be X:\\...): ${directoryPath}`));
}
const powershellCmd = [
'powershell',
'Get-CimInstance -ClassName Win32_LogicalDisk | Select-Object Caption, FreeSpace, Size',
];
const wmicCmd = [
'wmic',
'logicaldisk',
'get',
'size,freespace,caption',
];
const cmd = await hasPowerShell3(dependencies) ? powershellCmd : wmicCmd;
return check(cmd, driveData => {
// Only get the drive which match the path
const driveLetter = driveData[0];
return directoryPath.toUpperCase().startsWith(driveLetter.toUpperCase());
}, {
diskPath: 0,
free: 1,
size: 2,
});
}
/**
* Build the check call for unix
*
* @param directoryPath - The file/folder path from where we want to know disk space
*/
async function checkUnix(directoryPath) {
if (!dependencies.pathNormalize(directoryPath).startsWith(dependencies.pathSep)) {
return Promise.reject(new InvalidPathError(`The following path is invalid (should start by ${dependencies.pathSep}): ${directoryPath}`));
}
const pathToCheck = await getFirstExistingParentPath(directoryPath, dependencies);
return check([
'df',
'-Pk',
'--',
pathToCheck,
], () => true, // We should only get one line, so we did not need to filter
{
diskPath: 5,
free: 3,
size: 1,
}, 1024);
}
// Call the right check depending on the OS
if (dependencies.platform === 'win32') {
return checkWin32(directoryPath);
}
return checkUnix(directoryPath);
}
exports.InvalidPathError = InvalidPathError;
exports.NoMatchError = NoMatchError;
exports.default = checkDiskSpace;
exports.getFirstExistingParentPath = getFirstExistingParentPath;

View File

@@ -0,0 +1,53 @@
import { access } from 'node:fs/promises';
import { normalize, sep } from 'node:path';
declare class InvalidPathError extends Error {
name: string;
constructor(message?: string);
}
declare class NoMatchError extends Error {
name: string;
constructor(message?: string);
}
type Dependencies = {
platform: NodeJS.Platform;
release: string;
fsAccess: typeof access;
pathNormalize: typeof normalize;
pathSep: typeof sep;
cpExecFile: (file: string, args: ReadonlyArray<string> | undefined | null, options: {
windowsHide: true;
}) => Promise<{
stdout: string;
stderr: string;
}>;
};
/**
* Get the first existing parent path
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
declare function getFirstExistingParentPath(directoryPath: string, dependencies: Dependencies): Promise<string>;
/**
* `free` and `size` are in bytes
*/
type DiskSpace = {
diskPath: string;
free: number;
size: number;
};
/**
* Check disk space
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
declare function checkDiskSpace(directoryPath: string, dependencies?: Dependencies): Promise<DiskSpace>;
export { Dependencies, DiskSpace, InvalidPathError, NoMatchError, checkDiskSpace as default, getFirstExistingParentPath };

203
node_modules/check-disk-space/dist/check-disk-space.mjs generated vendored Normal file
View File

@@ -0,0 +1,203 @@
import { execFile } from 'node:child_process';
import { access } from 'node:fs/promises';
import { release } from 'node:os';
import { normalize, sep } from 'node:path';
import { platform } from 'node:process';
import { promisify } from 'node:util';
class InvalidPathError extends Error {
constructor(message) {
super(message);
this.name = 'InvalidPathError';
// Set the prototype explicitly.
Object.setPrototypeOf(this, InvalidPathError.prototype);
}
}
class NoMatchError extends Error {
constructor(message) {
super(message);
this.name = 'NoMatchError';
// Set the prototype explicitly.
Object.setPrototypeOf(this, NoMatchError.prototype);
}
}
/**
* Tells if directory exists
*
* @param directoryPath - The file/folder path
* @param dependencies - Dependencies container
*/
async function isDirectoryExisting(directoryPath, dependencies) {
try {
await dependencies.fsAccess(directoryPath);
return Promise.resolve(true);
}
catch (error) {
return Promise.resolve(false);
}
}
/**
* Get the first existing parent path
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
async function getFirstExistingParentPath(directoryPath, dependencies) {
let parentDirectoryPath = directoryPath;
let parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies);
while (!parentDirectoryFound) {
parentDirectoryPath = dependencies.pathNormalize(parentDirectoryPath + '/..');
parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies);
}
return parentDirectoryPath;
}
/**
* Tell if PowerShell 3 is available based on Windows version
*
* Note: 6.* is Windows 7
* Note: PowerShell 3 is natively available since Windows 8
*
* @param dependencies - Dependencies Injection Container
*/
async function hasPowerShell3(dependencies) {
const major = parseInt(dependencies.release.split('.')[0], 10);
if (major <= 6) {
return false;
}
try {
await dependencies.cpExecFile('where', ['powershell'], { windowsHide: true });
return true;
}
catch (error) {
return false;
}
}
/**
* Check disk space
*
* @param directoryPath - The file/folder path from where we want to know disk space
* @param dependencies - Dependencies container
*/
function checkDiskSpace(directoryPath, dependencies = {
platform,
release: release(),
fsAccess: access,
pathNormalize: normalize,
pathSep: sep,
cpExecFile: promisify(execFile),
}) {
// Note: This function contains other functions in order
// to wrap them in a common context and make unit tests easier
/**
* Maps command output to a normalized object {diskPath, free, size}
*
* @param stdout - The command output
* @param filter - To filter drives (only used for win32)
* @param mapping - Map between column index and normalized column name
* @param coefficient - The size coefficient to get bytes instead of kB
*/
function mapOutput(stdout, filter, mapping, coefficient) {
const parsed = stdout
.split('\n') // Split lines
.map(line => line.trim()) // Trim all lines
.filter(line => line.length !== 0) // Remove empty lines
.slice(1) // Remove header
.map(line => line.split(/\s+(?=[\d/])/)); // Split on spaces to get columns
const filtered = parsed.filter(filter);
if (filtered.length === 0) {
throw new NoMatchError();
}
const diskData = filtered[0];
return {
diskPath: diskData[mapping.diskPath],
free: parseInt(diskData[mapping.free], 10) * coefficient,
size: parseInt(diskData[mapping.size], 10) * coefficient,
};
}
/**
* Run the command and do common things between win32 and unix
*
* @param cmd - The command to execute
* @param filter - To filter drives (only used for win32)
* @param mapping - Map between column index and normalized column name
* @param coefficient - The size coefficient to get bytes instead of kB
*/
async function check(cmd, filter, mapping, coefficient = 1) {
const [file, ...args] = cmd;
/* istanbul ignore if */
if (file === undefined) {
return Promise.reject(new Error('cmd must contain at least one item'));
}
try {
const { stdout } = await dependencies.cpExecFile(file, args, { windowsHide: true });
return mapOutput(stdout, filter, mapping, coefficient);
}
catch (error) {
return Promise.reject(error);
}
}
/**
* Build the check call for win32
*
* @param directoryPath - The file/folder path from where we want to know disk space
*/
async function checkWin32(directoryPath) {
if (directoryPath.charAt(1) !== ':') {
return Promise.reject(new InvalidPathError(`The following path is invalid (should be X:\\...): ${directoryPath}`));
}
const powershellCmd = [
'powershell',
'Get-CimInstance -ClassName Win32_LogicalDisk | Select-Object Caption, FreeSpace, Size',
];
const wmicCmd = [
'wmic',
'logicaldisk',
'get',
'size,freespace,caption',
];
const cmd = await hasPowerShell3(dependencies) ? powershellCmd : wmicCmd;
return check(cmd, driveData => {
// Only get the drive which match the path
const driveLetter = driveData[0];
return directoryPath.toUpperCase().startsWith(driveLetter.toUpperCase());
}, {
diskPath: 0,
free: 1,
size: 2,
});
}
/**
* Build the check call for unix
*
* @param directoryPath - The file/folder path from where we want to know disk space
*/
async function checkUnix(directoryPath) {
if (!dependencies.pathNormalize(directoryPath).startsWith(dependencies.pathSep)) {
return Promise.reject(new InvalidPathError(`The following path is invalid (should start by ${dependencies.pathSep}): ${directoryPath}`));
}
const pathToCheck = await getFirstExistingParentPath(directoryPath, dependencies);
return check([
'df',
'-Pk',
'--',
pathToCheck,
], () => true, // We should only get one line, so we did not need to filter
{
diskPath: 5,
free: 3,
size: 1,
}, 1024);
}
// Call the right check depending on the OS
if (dependencies.platform === 'win32') {
return checkWin32(directoryPath);
}
return checkUnix(directoryPath);
}
export { InvalidPathError, NoMatchError, checkDiskSpace as default, getFirstExistingParentPath };

83
node_modules/check-disk-space/package.json generated vendored Normal file
View File

@@ -0,0 +1,83 @@
{
"name": "check-disk-space",
"version": "3.4.0",
"description": "Light multi-platform disk space checker without third party for Node.js",
"files": [
"dist",
"README.md"
],
"main": "./dist/check-disk-space.cjs",
"module": "./dist/check-disk-space.mjs",
"types": "./dist/check-disk-space.d.ts",
"exports": {
"import": "./dist/check-disk-space.mjs",
"require": "./dist/check-disk-space.cjs",
"types": "./dist/check-disk-space.d.ts"
},
"scripts": {
"build:lib": "rollup --config",
"build:dts": "rollup --config rollup.dts.config.mjs",
"build": "npm-run-all build:lib build:dts",
"lint": "eslint . --cache",
"lint:fix": "eslint --fix .",
"typecheck": "tsc --noEmit",
"test": "npm-run-all test:coverage lint typecheck",
"test:unit": "NODE_ENV=test TS_NODE_PROJECT='tsconfig.test.json' ava",
"test:coverage": "nyc --reporter=lcov --reporter=text npm run test:unit --silent"
},
"ava": {
"files": [
"test/**",
"!test/__helpers__/**"
],
"extensions": [
"ts"
],
"require": [
"tsconfig-paths/register",
"ts-node/register",
"source-map-support/register"
]
},
"engines": {
"node": ">=16"
},
"devDependencies": {
"@alex-d/eslint-config": "2.2.0",
"@istanbuljs/nyc-config-typescript": "1.0.2",
"@rollup/plugin-typescript": "11.1.1",
"@types/node": "16.11.7",
"@typescript-eslint/eslint-plugin": "5.59.6",
"@typescript-eslint/parser": "5.59.6",
"ava": "5.2.0",
"eslint": "8.41.0",
"eslint-plugin-ava": "14.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-simple-import-sort": "10.0.0",
"npm-run-all": "4.1.5",
"nyc": "15.1.0",
"rollup": "3.22.0",
"rollup-plugin-dts": "5.3.0",
"source-map-support": "0.5.21",
"ts-node": "10.9.1",
"tsconfig-paths": "4.2.0",
"tslib": "2.5.2",
"typescript": "5.0.4"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Alex-D/check-disk-space.git"
},
"keywords": [
"disk",
"space",
"hdd",
"free"
],
"author": "Alex-D <contact@alex-d.fr> (https://alex-d.fr)",
"license": "MIT",
"bugs": {
"url": "https://github.com/Alex-D/check-disk-space/issues"
},
"homepage": "https://github.com/Alex-D/check-disk-space#readme"
}

9
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"@schemastore/package": "0.0.10",
"@types/uuid": "^9.0.2",
"adm-zip": "^0.5.10",
"check-disk-space": "^3.4.0",
"console-log-level": "^1.4.1",
"del": "^6.1.1",
"fast-deep-equal": "^3.1.3",
@@ -1807,6 +1808,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/check-disk-space": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz",
"integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==",
"engines": {
"node": ">=16"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",

View File

@@ -34,6 +34,7 @@
"@schemastore/package": "0.0.10",
"@types/uuid": "^9.0.2",
"adm-zip": "^0.5.10",
"check-disk-space": "^3.4.0",
"console-log-level": "^1.4.1",
"del": "^6.1.1",
"fast-deep-equal": "^3.1.3",

View File

@@ -4,6 +4,7 @@ import * as path from "path";
import { performance } from "perf_hooks";
import * as toolcache from "@actions/tool-cache";
import checkDiskSpace from "check-disk-space";
import del from "del";
import { default as deepEqual } from "fast-deep-equal";
import * as semver from "semver";
@@ -71,7 +72,7 @@ function tryGetCodeQLCliVersionForRelease(
);
return undefined;
} else if (cliVersionsFromMarkerFiles.length === 0) {
logger.debug(
logger.info(
`Failed to find the CodeQL CLI version for release ${release.tag_name}.`,
);
return undefined;
@@ -84,7 +85,7 @@ export async function tryFindCliVersionDotcomOnly(
logger: Logger,
): Promise<string | undefined> {
try {
logger.debug(
logger.info(
`Fetching the GitHub Release for the CodeQL bundle tagged ${tagName}.`,
);
const apiClient = api.getApiClient();
@@ -96,7 +97,7 @@ export async function tryFindCliVersionDotcomOnly(
});
return tryGetCodeQLCliVersionForRelease(release.data, logger);
} catch (e) {
logger.debug(
logger.info(
`Failed to find the CLI version for the CodeQL bundle tagged ${tagName}. ${
wrapError(e).message
}`,
@@ -200,7 +201,7 @@ function tryGetBundleVersionFromTagName(
): string | undefined {
const match = tagName.match(/^codeql-bundle-(.*)$/);
if (match === null || match.length < 2) {
logger.debug(`Could not determine bundle version from tag ${tagName}.`);
logger.info(`Could not determine bundle version from tag ${tagName}.`);
return undefined;
}
return match[1];
@@ -209,7 +210,7 @@ function tryGetBundleVersionFromTagName(
function tryGetTagNameFromUrl(url: string, logger: Logger): string | undefined {
const match = url.match(/\/(codeql-bundle-.*)\//);
if (match === null || match.length < 2) {
logger.debug(`Could not determine tag name for URL ${url}.`);
logger.info(`Could not determine tag name for URL ${url}.`);
return undefined;
}
return match[1];
@@ -228,7 +229,7 @@ export function tryGetBundleVersionFromUrl(
export function convertToSemVer(version: string, logger: Logger): string {
if (!semver.valid(version)) {
logger.debug(
logger.info(
`Bundle version ${version} is not in SemVer format. Will treat it as pre-release 0.0.0-${version}.`,
);
version = `0.0.0-${version}`;
@@ -284,7 +285,7 @@ async function findOverridingToolsInCache(
if (candidates.length === 1) {
const candidate = candidates[0];
logger.debug(
logger.info(
`CodeQL tools version ${candidate.version} in toolcache overriding version ${humanReadableVersion}.`,
);
return {
@@ -293,11 +294,11 @@ async function findOverridingToolsInCache(
toolsVersion: candidate.version,
};
} else if (candidates.length === 0) {
logger.debug(
logger.info(
"Did not find any candidate pinned versions of the CodeQL tools in the toolcache.",
);
} else {
logger.debug(
logger.info(
"Could not use CodeQL tools from the toolcache since more than one candidate pinned " +
"version was found in the toolcache.",
);
@@ -376,7 +377,7 @@ export async function getCodeQLSource(
url ??
"unknown";
logger.debug(
logger.info(
"Attempting to obtain CodeQL tools. " +
`CLI version: ${cliVersion ?? "unknown"}, ` +
`bundle tag name: ${tagName ?? "unknown"}, ` +
@@ -391,12 +392,12 @@ export async function getCodeQLSource(
// Fall back to matching `x.y.z-<tagName>`.
if (!codeqlFolder) {
logger.debug(
logger.info(
"Didn't find a version of the CodeQL tools in the toolcache with a version number " +
`exactly matching ${cliVersion}.`,
);
const allVersions = toolcache.findAllVersions("CodeQL");
logger.debug(
logger.info(
`Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify(
allVersions,
)}.`,
@@ -407,13 +408,13 @@ export async function getCodeQLSource(
version.startsWith(`${cliVersion}-`),
);
if (candidateVersions.length === 1) {
logger.debug(
logger.info(
`Exactly one version of the CodeQL tools starting with ${cliVersion} found in the ` +
"toolcache, using that.",
);
codeqlFolder = toolcache.find("CodeQL", candidateVersions[0]);
} else if (candidateVersions.length === 0) {
logger.debug(
logger.info(
`Didn't find any versions of the CodeQL tools starting with ${cliVersion} ` +
`in the toolcache. Trying next fallback method.`,
);
@@ -422,7 +423,7 @@ export async function getCodeQLSource(
`Found ${candidateVersions.length} versions of the CodeQL tools starting with ` +
`${cliVersion} in the toolcache, but at most one was expected.`,
);
logger.debug("Trying next fallback method.");
logger.info("Trying next fallback method.");
}
}
}
@@ -437,7 +438,7 @@ export async function getCodeQLSource(
if (fallbackVersion) {
codeqlFolder = toolcache.find("CodeQL", fallbackVersion);
} else {
logger.debug(
logger.info(
"Could not determine a fallback toolcache version number for CodeQL tools version " +
`${humanReadableVersion}.`,
);
@@ -511,7 +512,7 @@ export async function tryGetFallbackToolcacheVersion(
return undefined;
}
const fallbackVersion = convertToSemVer(bundleVersion, logger);
logger.debug(
logger.info(
`Computed a fallback toolcache version number of ${fallbackVersion} for CodeQL version ` +
`${cliVersion ?? tagName}.`,
);
@@ -542,13 +543,16 @@ export async function downloadCodeQL(
// We also don't want to send an authorization header if there's already a token provided in the URL.
let authorization: string | undefined = undefined;
if (searchParams.has("token")) {
logger.debug("CodeQL tools URL contains an authorization token.");
logger.info("CodeQL tools URL contains an authorization token.");
} else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
logger.debug("Providing an authorization token to download CodeQL tools.");
logger.info("Providing an authorization token to download CodeQL tools.");
authorization = `token ${apiDetails.auth}`;
} else {
logger.debug("Downloading CodeQL tools without an authorization token.");
logger.info("Downloading CodeQL tools without an authorization token.");
}
await printDiskSpace(logger);
logger.info(
`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`,
);
@@ -570,26 +574,32 @@ export async function downloadCodeQL(
performance.now() - toolsDownloadStart,
);
logger.debug(
logger.info(
`Finished downloading CodeQL bundle to ${codeqlPath} (${toolsDownloadDurationMs} ms).`,
);
await printDiskSpace(logger);
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
logger.debug(`Finished extracting CodeQL bundle to ${codeqlExtracted}.`);
logger.info(`Finished extracting CodeQL bundle to ${codeqlExtracted}.`);
await printDiskSpace(logger);
try {
await del(codeqlPath, { force: true });
logger.debug("Deleted CodeQL bundle archive.");
logger.info("Deleted CodeQL bundle archive.");
} catch (e) {
logger.warning("Failed to delete CodeQL bundle archive.");
}
await printDiskSpace(logger);
const bundleVersion =
maybeBundleVersion ?? tryGetBundleVersionFromUrl(codeqlURL, logger);
if (bundleVersion === undefined) {
logger.debug(
logger.info(
"Could not cache CodeQL tools because we could not determine the bundle version from the " +
`URL ${codeqlURL}.`,
);
@@ -624,13 +634,22 @@ export async function downloadCodeQL(
const toolcacheVersion = maybeCliVersion?.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)
? `${maybeCliVersion}-${bundleVersion}`
: convertToSemVer(bundleVersion, logger);
const codeqlFolder = await toolcache.cacheDir(
codeqlExtracted,
"CodeQL",
toolcacheVersion,
);
await printDiskSpace(logger);
await del(codeqlExtracted, { force: true });
await printDiskSpace(logger);
return {
toolsVersion: maybeCliVersion ?? toolcacheVersion,
codeqlFolder: await toolcache.cacheDir(
codeqlExtracted,
"CodeQL",
toolcacheVersion,
),
codeqlFolder,
toolsDownloadDurationMs,
};
}
@@ -690,7 +709,7 @@ export async function setupCodeQLBundle(
break;
case "toolcache":
codeqlFolder = source.codeqlFolder;
logger.debug(`CodeQL found in cache ${codeqlFolder}`);
logger.info(`CodeQL found in cache ${codeqlFolder}`);
toolsSource = ToolsSource.Toolcache;
break;
case "download": {
@@ -714,3 +733,14 @@ export async function setupCodeQLBundle(
}
return { codeqlFolder, toolsDownloadDurationMs, toolsSource, toolsVersion };
}
async function printDiskSpace(logger: Logger) {
const diskSpaceInfo = await checkDiskSpace(
process.platform === "win32" ? path.parse(process.cwd()).root : "/",
);
logger.info(
`Disk space info: ${diskSpaceInfo.free / (1024 * 1024 * 1024)} GB free of ${
diskSpaceInfo.size / (1024 * 1024 * 1024)
} GB`,
);
}