├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── __tests__ └── meson-build.test.ts ├── action.yml ├── dist └── index.js ├── jest.config.js ├── meson.build ├── package-lock.json ├── package.json ├── src ├── main.ts └── meson-build.ts └── tsconfig.json /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | functionality: 5 | name: Functionality 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, windows-latest, macos-latest] 10 | python-version: [3.6, 3.7, 3.8, pypy3] 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Setup python 15 | uses: actions/setup-python@v1 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Run Meson 19 | uses: ./ 20 | with: 21 | action: build 22 | - name: Run Meson 23 | uses: ./ 24 | with: 25 | action: test 26 | code: 27 | name: Code 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v2 32 | - name: Install node 33 | uses: actions/setup-node@v1 34 | with: 35 | node-version: '10.x' 36 | - name: Install dependencies 37 | run: npm install 38 | - name: Run tests 39 | run: npm test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | lib/ 4 | build/ 5 | 6 | logs 7 | *.log 8 | 9 | build/Release 10 | 11 | *.tsbuildinfo 12 | 13 | .npm 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 BSFishy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meson Build action 2 | [![Test](https://github.com/BSFishy/meson-build/workflows/Test/badge.svg)](https://github.com/BSFishy/meson-build/actions) 3 | 4 | Run a Meson task during your Actions workflow 5 | 6 | ## Usage 7 | See [action.yml](action.yml) 8 | 9 | Basic: 10 | ```yaml 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v1 14 | - uses: BSFishy/meson-build@v1.0.3 15 | with: 16 | action: test 17 | ``` 18 | 19 | All options: 20 | ```yaml 21 | - uses: BSFishy/meson-build@v1.0.3 22 | with: 23 | action: test 24 | directory: build 25 | setup-options: -Db_coverage=true 26 | options: --verbose 27 | meson-version: 0.53.2 28 | ninja-version: 1.9.0.post1 29 | gcovr-version: 4.2 30 | ``` 31 | 32 | ### Options 33 | Here is a list of options that can be used with this action. 34 | 35 | #### `action` 36 | The action to perform. 37 | This specifies what the action should actually be doing. 38 | It defines what command is run when it comes time. 39 | 40 | Note that the project will always be setup before an action is run. 41 | Before the action is run, `meson setup [directory]` will be run to setup the project. 42 | If the directory already exists and there is a file name `build.ninja` in it, this step is skipped. 43 | 44 | It should be one of the following values: 45 | - `build` 46 | Simply build the project. This will use Ninja to automatically build the project. Apart from that, no additional tasks are run. 47 | - `install` 48 | Install the project. This will use Meson to install the project to the system. 49 | - `test` 50 | Run unit tests. This will run any unit tests defined in the Meson project, using Meson. 51 | - `coverage` 52 | Run [Gcovr](https://gcovr.com/en/stable/). This will use Meson to find the code coverage of the project. 53 | Note that this required Gcovr. If it is not already installed, it will be installed automatically. 54 | - `tidy` 55 | Run [Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/) on the project. This will use Meson to lint the project. 56 | Note that this requires Clang-Tidy. If it is not installed, an error will occur. 57 | 58 | #### `directory` 59 | The directory to build the project in. 60 | This will automatically cause all of the commands to run the commands on whatever directory is specified here. 61 | By default, the build directory will simply be `build`. 62 | 63 | The build directory can already exist. 64 | If the build directory exists before the action is run, it is checked for a `build.ninja` file. 65 | If the directory and the `build.ninja` file already exist, nothing is done to setup the project. 66 | If either of them are missing, the project is setup using `meson setup`. 67 | 68 | #### `setup-options` 69 | Options to pass when setting up the project. 70 | These are command line arguments that are passed to Meson when setting up the project. 71 | This happens when either the `directory` specified or the `build.ninja` inside of the directory does not already exist. 72 | This allows you to specify specific options to Meson to build with. 73 | The options are passed directly to `meson setup`. 74 | 75 | #### `options` 76 | Options to pass to the Meson command. 77 | These are command line arguments that are passed to whatever Meson command is to be called. 78 | This allows you to run the command with specific options. 79 | The options are passed directly to whatever command is run without formatting. 80 | 81 | #### `meson-version` 82 | The version of Meson to install. 83 | Whenever the action is initially run, it checks if Meson is installed. 84 | If Meson is already installed, this part is skipped. 85 | Note that this means that it won't garuantee that a specific version of Meson is installed. 86 | If Meson is not installed when the action is run, it will install it using pip and this version. 87 | 88 | #### `ninja-version` 89 | The version of Ninja to install. 90 | Whenever the action is initially run, it checks if Ninja is installed. 91 | If Ninja is already installed, this part is skipped. 92 | Note that this means that it won't garuantee that a specific version of Ninja is installed. 93 | If Ninja is not installed when the action is run, it will be installed using pip and this version. 94 | 95 | #### `gcovr-version` 96 | The version of Gcovr to install. 97 | Whenever the `coverage` action is run, Gcovr is checked if it is installed. 98 | If not, pip will be used to install this version of Gcovr. 99 | 100 | ### Additional info 101 | Here is just a little bit of additional information to explain some things that may not be trivial. 102 | 103 | #### Lifecycle 104 | The lifecycle of the action is fairly important. 105 | Luckily, it is fairly simple. 106 | Here is what it looks like: 107 | 108 | 1. Process the inputs. 109 | First, we need to get all of our inputs and figure out what exactly they are. The main part of this step is to format the `action` input. We just need to make sure it is a valid value. 110 | 2. Check for Ninja. 111 | Next, we need to make sure Ninja is installed. Ninja is a dependency of Meson, so we do a simple check if it is on the current system. If not, it is installed through pip. 112 | 3. Check for Meson. 113 | We also need to make sure Meson is installed. If Meson is not already installed, it is installed through pip. 114 | 4. Check if the project is setup. 115 | We need to check if the project is setup. This will check if the build directory exists and if the `build.ninja` file exists in that directory. If either of those are missing, a `meson setup` command will be called to setup the project. If either of those are missing even after running the setup command, it is assumed that the project has an error, and it exits. 116 | 5. Build the command. 117 | Next, we build the command that is to be run. Based on the action to run, an executable and basic set of arguments are selected that correspond to that command. Any dependencies (i.e. Gcovr or Clang-Tidy) are also installed if their respective actions are used. Finally, the `options` input is appended to the arguments. 118 | 6. Run the command. 119 | Finally, we run the command that was built in the previous step. 120 | 121 | ## License 122 | 123 | The scripts and documentation in this project are released under the [MIT License](LICENSE) 124 | -------------------------------------------------------------------------------- /__tests__/meson-build.test.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as exec from '@actions/exec'; 3 | import * as io from '@actions/io'; 4 | import * as path from 'path'; 5 | import * as fs from 'fs'; 6 | 7 | import main = require('../src/main'); 8 | 9 | describe('meson-build', () => { 10 | it('', () => {}); 11 | }); 12 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Meson Build 2 | author: BSFishy 3 | description: Run a Meson task during your Actions workflow 4 | runs: 5 | using: 'node12' 6 | main: 'dist/index.js' 7 | branding: 8 | icon: 'arrow-down' 9 | color: 'green' 10 | inputs: 11 | action: 12 | description: The action to run 13 | required: true 14 | directory: 15 | description: The directory to build the project to 16 | required: true 17 | default: 'build' 18 | setup-options: 19 | description: Additional options to pass to Meson during setup 20 | required: false 21 | default: '' 22 | options: 23 | description: Additional options to pass to Meson 24 | required: false 25 | default: '' 26 | meson-version: 27 | description: The version of Meson to use 28 | required: true 29 | default: '0.53.2' 30 | ninja-version: 31 | description: The version of Ninja to use 32 | required: true 33 | default: '1.9.0.post1' 34 | gcovr-version: 35 | description: The version of gcovr to use (If doing coverage) 36 | required: true 37 | default: '4.2' 38 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules, runtime) { // webpackBootstrap 3 | /******/ "use strict"; 4 | /******/ // The module cache 5 | /******/ var installedModules = {}; 6 | /******/ 7 | /******/ // The require function 8 | /******/ function __webpack_require__(moduleId) { 9 | /******/ 10 | /******/ // Check if module is in cache 11 | /******/ if(installedModules[moduleId]) { 12 | /******/ return installedModules[moduleId].exports; 13 | /******/ } 14 | /******/ // Create a new module (and put it into the cache) 15 | /******/ var module = installedModules[moduleId] = { 16 | /******/ i: moduleId, 17 | /******/ l: false, 18 | /******/ exports: {} 19 | /******/ }; 20 | /******/ 21 | /******/ // Execute the module function 22 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 23 | /******/ 24 | /******/ // Flag the module as loaded 25 | /******/ module.l = true; 26 | /******/ 27 | /******/ // Return the exports of the module 28 | /******/ return module.exports; 29 | /******/ } 30 | /******/ 31 | /******/ 32 | /******/ __webpack_require__.ab = __dirname + "/"; 33 | /******/ 34 | /******/ // the startup function 35 | /******/ function startup() { 36 | /******/ // Load entry module and return exports 37 | /******/ return __webpack_require__(995); 38 | /******/ }; 39 | /******/ 40 | /******/ // run startup 41 | /******/ return startup(); 42 | /******/ }) 43 | /************************************************************************/ 44 | /******/ ({ 45 | 46 | /***/ 1: 47 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 48 | 49 | "use strict"; 50 | 51 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 52 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 53 | return new (P || (P = Promise))(function (resolve, reject) { 54 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 55 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 56 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 57 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 58 | }); 59 | }; 60 | Object.defineProperty(exports, "__esModule", { value: true }); 61 | const childProcess = __webpack_require__(129); 62 | const path = __webpack_require__(622); 63 | const util_1 = __webpack_require__(669); 64 | const ioUtil = __webpack_require__(672); 65 | const exec = util_1.promisify(childProcess.exec); 66 | /** 67 | * Copies a file or folder. 68 | * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js 69 | * 70 | * @param source source path 71 | * @param dest destination path 72 | * @param options optional. See CopyOptions. 73 | */ 74 | function cp(source, dest, options = {}) { 75 | return __awaiter(this, void 0, void 0, function* () { 76 | const { force, recursive } = readCopyOptions(options); 77 | const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; 78 | // Dest is an existing file, but not forcing 79 | if (destStat && destStat.isFile() && !force) { 80 | return; 81 | } 82 | // If dest is an existing directory, should copy inside. 83 | const newDest = destStat && destStat.isDirectory() 84 | ? path.join(dest, path.basename(source)) 85 | : dest; 86 | if (!(yield ioUtil.exists(source))) { 87 | throw new Error(`no such file or directory: ${source}`); 88 | } 89 | const sourceStat = yield ioUtil.stat(source); 90 | if (sourceStat.isDirectory()) { 91 | if (!recursive) { 92 | throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`); 93 | } 94 | else { 95 | yield cpDirRecursive(source, newDest, 0, force); 96 | } 97 | } 98 | else { 99 | if (path.relative(source, newDest) === '') { 100 | // a file cannot be copied to itself 101 | throw new Error(`'${newDest}' and '${source}' are the same file`); 102 | } 103 | yield copyFile(source, newDest, force); 104 | } 105 | }); 106 | } 107 | exports.cp = cp; 108 | /** 109 | * Moves a path. 110 | * 111 | * @param source source path 112 | * @param dest destination path 113 | * @param options optional. See MoveOptions. 114 | */ 115 | function mv(source, dest, options = {}) { 116 | return __awaiter(this, void 0, void 0, function* () { 117 | if (yield ioUtil.exists(dest)) { 118 | let destExists = true; 119 | if (yield ioUtil.isDirectory(dest)) { 120 | // If dest is directory copy src into dest 121 | dest = path.join(dest, path.basename(source)); 122 | destExists = yield ioUtil.exists(dest); 123 | } 124 | if (destExists) { 125 | if (options.force == null || options.force) { 126 | yield rmRF(dest); 127 | } 128 | else { 129 | throw new Error('Destination already exists'); 130 | } 131 | } 132 | } 133 | yield mkdirP(path.dirname(dest)); 134 | yield ioUtil.rename(source, dest); 135 | }); 136 | } 137 | exports.mv = mv; 138 | /** 139 | * Remove a path recursively with force 140 | * 141 | * @param inputPath path to remove 142 | */ 143 | function rmRF(inputPath) { 144 | return __awaiter(this, void 0, void 0, function* () { 145 | if (ioUtil.IS_WINDOWS) { 146 | // Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another 147 | // program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del. 148 | try { 149 | if (yield ioUtil.isDirectory(inputPath, true)) { 150 | yield exec(`rd /s /q "${inputPath}"`); 151 | } 152 | else { 153 | yield exec(`del /f /a "${inputPath}"`); 154 | } 155 | } 156 | catch (err) { 157 | // if you try to delete a file that doesn't exist, desired result is achieved 158 | // other errors are valid 159 | if (err.code !== 'ENOENT') 160 | throw err; 161 | } 162 | // Shelling out fails to remove a symlink folder with missing source, this unlink catches that 163 | try { 164 | yield ioUtil.unlink(inputPath); 165 | } 166 | catch (err) { 167 | // if you try to delete a file that doesn't exist, desired result is achieved 168 | // other errors are valid 169 | if (err.code !== 'ENOENT') 170 | throw err; 171 | } 172 | } 173 | else { 174 | let isDir = false; 175 | try { 176 | isDir = yield ioUtil.isDirectory(inputPath); 177 | } 178 | catch (err) { 179 | // if you try to delete a file that doesn't exist, desired result is achieved 180 | // other errors are valid 181 | if (err.code !== 'ENOENT') 182 | throw err; 183 | return; 184 | } 185 | if (isDir) { 186 | yield exec(`rm -rf "${inputPath}"`); 187 | } 188 | else { 189 | yield ioUtil.unlink(inputPath); 190 | } 191 | } 192 | }); 193 | } 194 | exports.rmRF = rmRF; 195 | /** 196 | * Make a directory. Creates the full path with folders in between 197 | * Will throw if it fails 198 | * 199 | * @param fsPath path to create 200 | * @returns Promise 201 | */ 202 | function mkdirP(fsPath) { 203 | return __awaiter(this, void 0, void 0, function* () { 204 | yield ioUtil.mkdirP(fsPath); 205 | }); 206 | } 207 | exports.mkdirP = mkdirP; 208 | /** 209 | * Returns path of a tool had the tool actually been invoked. Resolves via paths. 210 | * If you check and the tool does not exist, it will throw. 211 | * 212 | * @param tool name of the tool 213 | * @param check whether to check if tool exists 214 | * @returns Promise path to tool 215 | */ 216 | function which(tool, check) { 217 | return __awaiter(this, void 0, void 0, function* () { 218 | if (!tool) { 219 | throw new Error("parameter 'tool' is required"); 220 | } 221 | // recursive when check=true 222 | if (check) { 223 | const result = yield which(tool, false); 224 | if (!result) { 225 | if (ioUtil.IS_WINDOWS) { 226 | throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); 227 | } 228 | else { 229 | throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); 230 | } 231 | } 232 | } 233 | try { 234 | // build the list of extensions to try 235 | const extensions = []; 236 | if (ioUtil.IS_WINDOWS && process.env.PATHEXT) { 237 | for (const extension of process.env.PATHEXT.split(path.delimiter)) { 238 | if (extension) { 239 | extensions.push(extension); 240 | } 241 | } 242 | } 243 | // if it's rooted, return it if exists. otherwise return empty. 244 | if (ioUtil.isRooted(tool)) { 245 | const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); 246 | if (filePath) { 247 | return filePath; 248 | } 249 | return ''; 250 | } 251 | // if any path separators, return empty 252 | if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) { 253 | return ''; 254 | } 255 | // build the list of directories 256 | // 257 | // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, 258 | // it feels like we should not do this. Checking the current directory seems like more of a use 259 | // case of a shell, and the which() function exposed by the toolkit should strive for consistency 260 | // across platforms. 261 | const directories = []; 262 | if (process.env.PATH) { 263 | for (const p of process.env.PATH.split(path.delimiter)) { 264 | if (p) { 265 | directories.push(p); 266 | } 267 | } 268 | } 269 | // return the first match 270 | for (const directory of directories) { 271 | const filePath = yield ioUtil.tryGetExecutablePath(directory + path.sep + tool, extensions); 272 | if (filePath) { 273 | return filePath; 274 | } 275 | } 276 | return ''; 277 | } 278 | catch (err) { 279 | throw new Error(`which failed with message ${err.message}`); 280 | } 281 | }); 282 | } 283 | exports.which = which; 284 | function readCopyOptions(options) { 285 | const force = options.force == null ? true : options.force; 286 | const recursive = Boolean(options.recursive); 287 | return { force, recursive }; 288 | } 289 | function cpDirRecursive(sourceDir, destDir, currentDepth, force) { 290 | return __awaiter(this, void 0, void 0, function* () { 291 | // Ensure there is not a run away recursive copy 292 | if (currentDepth >= 255) 293 | return; 294 | currentDepth++; 295 | yield mkdirP(destDir); 296 | const files = yield ioUtil.readdir(sourceDir); 297 | for (const fileName of files) { 298 | const srcFile = `${sourceDir}/${fileName}`; 299 | const destFile = `${destDir}/${fileName}`; 300 | const srcFileStat = yield ioUtil.lstat(srcFile); 301 | if (srcFileStat.isDirectory()) { 302 | // Recurse 303 | yield cpDirRecursive(srcFile, destFile, currentDepth, force); 304 | } 305 | else { 306 | yield copyFile(srcFile, destFile, force); 307 | } 308 | } 309 | // Change the mode for the newly created directory 310 | yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode); 311 | }); 312 | } 313 | // Buffered file copy 314 | function copyFile(srcFile, destFile, force) { 315 | return __awaiter(this, void 0, void 0, function* () { 316 | if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) { 317 | // unlink/re-link it 318 | try { 319 | yield ioUtil.lstat(destFile); 320 | yield ioUtil.unlink(destFile); 321 | } 322 | catch (e) { 323 | // Try to override file permission 324 | if (e.code === 'EPERM') { 325 | yield ioUtil.chmod(destFile, '0666'); 326 | yield ioUtil.unlink(destFile); 327 | } 328 | // other errors = it doesn't exist, no work to do 329 | } 330 | // Copy over symlink 331 | const symlinkFull = yield ioUtil.readlink(srcFile); 332 | yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null); 333 | } 334 | else if (!(yield ioUtil.exists(destFile)) || force) { 335 | yield ioUtil.copyFile(srcFile, destFile); 336 | } 337 | }); 338 | } 339 | //# sourceMappingURL=io.js.map 340 | 341 | /***/ }), 342 | 343 | /***/ 9: 344 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 345 | 346 | "use strict"; 347 | 348 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 349 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 350 | return new (P || (P = Promise))(function (resolve, reject) { 351 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 352 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 353 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 354 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 355 | }); 356 | }; 357 | Object.defineProperty(exports, "__esModule", { value: true }); 358 | const os = __webpack_require__(87); 359 | const events = __webpack_require__(614); 360 | const child = __webpack_require__(129); 361 | const path = __webpack_require__(622); 362 | const io = __webpack_require__(1); 363 | const ioUtil = __webpack_require__(672); 364 | /* eslint-disable @typescript-eslint/unbound-method */ 365 | const IS_WINDOWS = process.platform === 'win32'; 366 | /* 367 | * Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way. 368 | */ 369 | class ToolRunner extends events.EventEmitter { 370 | constructor(toolPath, args, options) { 371 | super(); 372 | if (!toolPath) { 373 | throw new Error("Parameter 'toolPath' cannot be null or empty."); 374 | } 375 | this.toolPath = toolPath; 376 | this.args = args || []; 377 | this.options = options || {}; 378 | } 379 | _debug(message) { 380 | if (this.options.listeners && this.options.listeners.debug) { 381 | this.options.listeners.debug(message); 382 | } 383 | } 384 | _getCommandString(options, noPrefix) { 385 | const toolPath = this._getSpawnFileName(); 386 | const args = this._getSpawnArgs(options); 387 | let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool 388 | if (IS_WINDOWS) { 389 | // Windows + cmd file 390 | if (this._isCmdFile()) { 391 | cmd += toolPath; 392 | for (const a of args) { 393 | cmd += ` ${a}`; 394 | } 395 | } 396 | // Windows + verbatim 397 | else if (options.windowsVerbatimArguments) { 398 | cmd += `"${toolPath}"`; 399 | for (const a of args) { 400 | cmd += ` ${a}`; 401 | } 402 | } 403 | // Windows (regular) 404 | else { 405 | cmd += this._windowsQuoteCmdArg(toolPath); 406 | for (const a of args) { 407 | cmd += ` ${this._windowsQuoteCmdArg(a)}`; 408 | } 409 | } 410 | } 411 | else { 412 | // OSX/Linux - this can likely be improved with some form of quoting. 413 | // creating processes on Unix is fundamentally different than Windows. 414 | // on Unix, execvp() takes an arg array. 415 | cmd += toolPath; 416 | for (const a of args) { 417 | cmd += ` ${a}`; 418 | } 419 | } 420 | return cmd; 421 | } 422 | _processLineBuffer(data, strBuffer, onLine) { 423 | try { 424 | let s = strBuffer + data.toString(); 425 | let n = s.indexOf(os.EOL); 426 | while (n > -1) { 427 | const line = s.substring(0, n); 428 | onLine(line); 429 | // the rest of the string ... 430 | s = s.substring(n + os.EOL.length); 431 | n = s.indexOf(os.EOL); 432 | } 433 | strBuffer = s; 434 | } 435 | catch (err) { 436 | // streaming lines to console is best effort. Don't fail a build. 437 | this._debug(`error processing line. Failed with error ${err}`); 438 | } 439 | } 440 | _getSpawnFileName() { 441 | if (IS_WINDOWS) { 442 | if (this._isCmdFile()) { 443 | return process.env['COMSPEC'] || 'cmd.exe'; 444 | } 445 | } 446 | return this.toolPath; 447 | } 448 | _getSpawnArgs(options) { 449 | if (IS_WINDOWS) { 450 | if (this._isCmdFile()) { 451 | let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; 452 | for (const a of this.args) { 453 | argline += ' '; 454 | argline += options.windowsVerbatimArguments 455 | ? a 456 | : this._windowsQuoteCmdArg(a); 457 | } 458 | argline += '"'; 459 | return [argline]; 460 | } 461 | } 462 | return this.args; 463 | } 464 | _endsWith(str, end) { 465 | return str.endsWith(end); 466 | } 467 | _isCmdFile() { 468 | const upperToolPath = this.toolPath.toUpperCase(); 469 | return (this._endsWith(upperToolPath, '.CMD') || 470 | this._endsWith(upperToolPath, '.BAT')); 471 | } 472 | _windowsQuoteCmdArg(arg) { 473 | // for .exe, apply the normal quoting rules that libuv applies 474 | if (!this._isCmdFile()) { 475 | return this._uvQuoteCmdArg(arg); 476 | } 477 | // otherwise apply quoting rules specific to the cmd.exe command line parser. 478 | // the libuv rules are generic and are not designed specifically for cmd.exe 479 | // command line parser. 480 | // 481 | // for a detailed description of the cmd.exe command line parser, refer to 482 | // http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912 483 | // need quotes for empty arg 484 | if (!arg) { 485 | return '""'; 486 | } 487 | // determine whether the arg needs to be quoted 488 | const cmdSpecialChars = [ 489 | ' ', 490 | '\t', 491 | '&', 492 | '(', 493 | ')', 494 | '[', 495 | ']', 496 | '{', 497 | '}', 498 | '^', 499 | '=', 500 | ';', 501 | '!', 502 | "'", 503 | '+', 504 | ',', 505 | '`', 506 | '~', 507 | '|', 508 | '<', 509 | '>', 510 | '"' 511 | ]; 512 | let needsQuotes = false; 513 | for (const char of arg) { 514 | if (cmdSpecialChars.some(x => x === char)) { 515 | needsQuotes = true; 516 | break; 517 | } 518 | } 519 | // short-circuit if quotes not needed 520 | if (!needsQuotes) { 521 | return arg; 522 | } 523 | // the following quoting rules are very similar to the rules that by libuv applies. 524 | // 525 | // 1) wrap the string in quotes 526 | // 527 | // 2) double-up quotes - i.e. " => "" 528 | // 529 | // this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately 530 | // doesn't work well with a cmd.exe command line. 531 | // 532 | // note, replacing " with "" also works well if the arg is passed to a downstream .NET console app. 533 | // for example, the command line: 534 | // foo.exe "myarg:""my val""" 535 | // is parsed by a .NET console app into an arg array: 536 | // [ "myarg:\"my val\"" ] 537 | // which is the same end result when applying libuv quoting rules. although the actual 538 | // command line from libuv quoting rules would look like: 539 | // foo.exe "myarg:\"my val\"" 540 | // 541 | // 3) double-up slashes that precede a quote, 542 | // e.g. hello \world => "hello \world" 543 | // hello\"world => "hello\\""world" 544 | // hello\\"world => "hello\\\\""world" 545 | // hello world\ => "hello world\\" 546 | // 547 | // technically this is not required for a cmd.exe command line, or the batch argument parser. 548 | // the reasons for including this as a .cmd quoting rule are: 549 | // 550 | // a) this is optimized for the scenario where the argument is passed from the .cmd file to an 551 | // external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule. 552 | // 553 | // b) it's what we've been doing previously (by deferring to node default behavior) and we 554 | // haven't heard any complaints about that aspect. 555 | // 556 | // note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be 557 | // escaped when used on the command line directly - even though within a .cmd file % can be escaped 558 | // by using %%. 559 | // 560 | // the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts 561 | // the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing. 562 | // 563 | // one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would 564 | // often work, since it is unlikely that var^ would exist, and the ^ character is removed when the 565 | // variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args 566 | // to an external program. 567 | // 568 | // an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file. 569 | // % can be escaped within a .cmd file. 570 | let reverse = '"'; 571 | let quoteHit = true; 572 | for (let i = arg.length; i > 0; i--) { 573 | // walk the string in reverse 574 | reverse += arg[i - 1]; 575 | if (quoteHit && arg[i - 1] === '\\') { 576 | reverse += '\\'; // double the slash 577 | } 578 | else if (arg[i - 1] === '"') { 579 | quoteHit = true; 580 | reverse += '"'; // double the quote 581 | } 582 | else { 583 | quoteHit = false; 584 | } 585 | } 586 | reverse += '"'; 587 | return reverse 588 | .split('') 589 | .reverse() 590 | .join(''); 591 | } 592 | _uvQuoteCmdArg(arg) { 593 | // Tool runner wraps child_process.spawn() and needs to apply the same quoting as 594 | // Node in certain cases where the undocumented spawn option windowsVerbatimArguments 595 | // is used. 596 | // 597 | // Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV, 598 | // see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details), 599 | // pasting copyright notice from Node within this function: 600 | // 601 | // Copyright Joyent, Inc. and other Node contributors. All rights reserved. 602 | // 603 | // Permission is hereby granted, free of charge, to any person obtaining a copy 604 | // of this software and associated documentation files (the "Software"), to 605 | // deal in the Software without restriction, including without limitation the 606 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 607 | // sell copies of the Software, and to permit persons to whom the Software is 608 | // furnished to do so, subject to the following conditions: 609 | // 610 | // The above copyright notice and this permission notice shall be included in 611 | // all copies or substantial portions of the Software. 612 | // 613 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 614 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 615 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 616 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 617 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 618 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 619 | // IN THE SOFTWARE. 620 | if (!arg) { 621 | // Need double quotation for empty argument 622 | return '""'; 623 | } 624 | if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) { 625 | // No quotation needed 626 | return arg; 627 | } 628 | if (!arg.includes('"') && !arg.includes('\\')) { 629 | // No embedded double quotes or backslashes, so I can just wrap 630 | // quote marks around the whole thing. 631 | return `"${arg}"`; 632 | } 633 | // Expected input/output: 634 | // input : hello"world 635 | // output: "hello\"world" 636 | // input : hello""world 637 | // output: "hello\"\"world" 638 | // input : hello\world 639 | // output: hello\world 640 | // input : hello\\world 641 | // output: hello\\world 642 | // input : hello\"world 643 | // output: "hello\\\"world" 644 | // input : hello\\"world 645 | // output: "hello\\\\\"world" 646 | // input : hello world\ 647 | // output: "hello world\\" - note the comment in libuv actually reads "hello world\" 648 | // but it appears the comment is wrong, it should be "hello world\\" 649 | let reverse = '"'; 650 | let quoteHit = true; 651 | for (let i = arg.length; i > 0; i--) { 652 | // walk the string in reverse 653 | reverse += arg[i - 1]; 654 | if (quoteHit && arg[i - 1] === '\\') { 655 | reverse += '\\'; 656 | } 657 | else if (arg[i - 1] === '"') { 658 | quoteHit = true; 659 | reverse += '\\'; 660 | } 661 | else { 662 | quoteHit = false; 663 | } 664 | } 665 | reverse += '"'; 666 | return reverse 667 | .split('') 668 | .reverse() 669 | .join(''); 670 | } 671 | _cloneExecOptions(options) { 672 | options = options || {}; 673 | const result = { 674 | cwd: options.cwd || process.cwd(), 675 | env: options.env || process.env, 676 | silent: options.silent || false, 677 | windowsVerbatimArguments: options.windowsVerbatimArguments || false, 678 | failOnStdErr: options.failOnStdErr || false, 679 | ignoreReturnCode: options.ignoreReturnCode || false, 680 | delay: options.delay || 10000 681 | }; 682 | result.outStream = options.outStream || process.stdout; 683 | result.errStream = options.errStream || process.stderr; 684 | return result; 685 | } 686 | _getSpawnOptions(options, toolPath) { 687 | options = options || {}; 688 | const result = {}; 689 | result.cwd = options.cwd; 690 | result.env = options.env; 691 | result['windowsVerbatimArguments'] = 692 | options.windowsVerbatimArguments || this._isCmdFile(); 693 | if (options.windowsVerbatimArguments) { 694 | result.argv0 = `"${toolPath}"`; 695 | } 696 | return result; 697 | } 698 | /** 699 | * Exec a tool. 700 | * Output will be streamed to the live console. 701 | * Returns promise with return code 702 | * 703 | * @param tool path to tool to exec 704 | * @param options optional exec options. See ExecOptions 705 | * @returns number 706 | */ 707 | exec() { 708 | return __awaiter(this, void 0, void 0, function* () { 709 | // root the tool path if it is unrooted and contains relative pathing 710 | if (!ioUtil.isRooted(this.toolPath) && 711 | (this.toolPath.includes('/') || 712 | (IS_WINDOWS && this.toolPath.includes('\\')))) { 713 | // prefer options.cwd if it is specified, however options.cwd may also need to be rooted 714 | this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); 715 | } 716 | // if the tool is only a file name, then resolve it from the PATH 717 | // otherwise verify it exists (add extension on Windows if necessary) 718 | this.toolPath = yield io.which(this.toolPath, true); 719 | return new Promise((resolve, reject) => { 720 | this._debug(`exec tool: ${this.toolPath}`); 721 | this._debug('arguments:'); 722 | for (const arg of this.args) { 723 | this._debug(` ${arg}`); 724 | } 725 | const optionsNonNull = this._cloneExecOptions(this.options); 726 | if (!optionsNonNull.silent && optionsNonNull.outStream) { 727 | optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); 728 | } 729 | const state = new ExecState(optionsNonNull, this.toolPath); 730 | state.on('debug', (message) => { 731 | this._debug(message); 732 | }); 733 | const fileName = this._getSpawnFileName(); 734 | const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); 735 | const stdbuffer = ''; 736 | if (cp.stdout) { 737 | cp.stdout.on('data', (data) => { 738 | if (this.options.listeners && this.options.listeners.stdout) { 739 | this.options.listeners.stdout(data); 740 | } 741 | if (!optionsNonNull.silent && optionsNonNull.outStream) { 742 | optionsNonNull.outStream.write(data); 743 | } 744 | this._processLineBuffer(data, stdbuffer, (line) => { 745 | if (this.options.listeners && this.options.listeners.stdline) { 746 | this.options.listeners.stdline(line); 747 | } 748 | }); 749 | }); 750 | } 751 | const errbuffer = ''; 752 | if (cp.stderr) { 753 | cp.stderr.on('data', (data) => { 754 | state.processStderr = true; 755 | if (this.options.listeners && this.options.listeners.stderr) { 756 | this.options.listeners.stderr(data); 757 | } 758 | if (!optionsNonNull.silent && 759 | optionsNonNull.errStream && 760 | optionsNonNull.outStream) { 761 | const s = optionsNonNull.failOnStdErr 762 | ? optionsNonNull.errStream 763 | : optionsNonNull.outStream; 764 | s.write(data); 765 | } 766 | this._processLineBuffer(data, errbuffer, (line) => { 767 | if (this.options.listeners && this.options.listeners.errline) { 768 | this.options.listeners.errline(line); 769 | } 770 | }); 771 | }); 772 | } 773 | cp.on('error', (err) => { 774 | state.processError = err.message; 775 | state.processExited = true; 776 | state.processClosed = true; 777 | state.CheckComplete(); 778 | }); 779 | cp.on('exit', (code) => { 780 | state.processExitCode = code; 781 | state.processExited = true; 782 | this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); 783 | state.CheckComplete(); 784 | }); 785 | cp.on('close', (code) => { 786 | state.processExitCode = code; 787 | state.processExited = true; 788 | state.processClosed = true; 789 | this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); 790 | state.CheckComplete(); 791 | }); 792 | state.on('done', (error, exitCode) => { 793 | if (stdbuffer.length > 0) { 794 | this.emit('stdline', stdbuffer); 795 | } 796 | if (errbuffer.length > 0) { 797 | this.emit('errline', errbuffer); 798 | } 799 | cp.removeAllListeners(); 800 | if (error) { 801 | reject(error); 802 | } 803 | else { 804 | resolve(exitCode); 805 | } 806 | }); 807 | }); 808 | }); 809 | } 810 | } 811 | exports.ToolRunner = ToolRunner; 812 | /** 813 | * Convert an arg string to an array of args. Handles escaping 814 | * 815 | * @param argString string of arguments 816 | * @returns string[] array of arguments 817 | */ 818 | function argStringToArray(argString) { 819 | const args = []; 820 | let inQuotes = false; 821 | let escaped = false; 822 | let arg = ''; 823 | function append(c) { 824 | // we only escape double quotes. 825 | if (escaped && c !== '"') { 826 | arg += '\\'; 827 | } 828 | arg += c; 829 | escaped = false; 830 | } 831 | for (let i = 0; i < argString.length; i++) { 832 | const c = argString.charAt(i); 833 | if (c === '"') { 834 | if (!escaped) { 835 | inQuotes = !inQuotes; 836 | } 837 | else { 838 | append(c); 839 | } 840 | continue; 841 | } 842 | if (c === '\\' && escaped) { 843 | append(c); 844 | continue; 845 | } 846 | if (c === '\\' && inQuotes) { 847 | escaped = true; 848 | continue; 849 | } 850 | if (c === ' ' && !inQuotes) { 851 | if (arg.length > 0) { 852 | args.push(arg); 853 | arg = ''; 854 | } 855 | continue; 856 | } 857 | append(c); 858 | } 859 | if (arg.length > 0) { 860 | args.push(arg.trim()); 861 | } 862 | return args; 863 | } 864 | exports.argStringToArray = argStringToArray; 865 | class ExecState extends events.EventEmitter { 866 | constructor(options, toolPath) { 867 | super(); 868 | this.processClosed = false; // tracks whether the process has exited and stdio is closed 869 | this.processError = ''; 870 | this.processExitCode = 0; 871 | this.processExited = false; // tracks whether the process has exited 872 | this.processStderr = false; // tracks whether stderr was written to 873 | this.delay = 10000; // 10 seconds 874 | this.done = false; 875 | this.timeout = null; 876 | if (!toolPath) { 877 | throw new Error('toolPath must not be empty'); 878 | } 879 | this.options = options; 880 | this.toolPath = toolPath; 881 | if (options.delay) { 882 | this.delay = options.delay; 883 | } 884 | } 885 | CheckComplete() { 886 | if (this.done) { 887 | return; 888 | } 889 | if (this.processClosed) { 890 | this._setResult(); 891 | } 892 | else if (this.processExited) { 893 | this.timeout = setTimeout(ExecState.HandleTimeout, this.delay, this); 894 | } 895 | } 896 | _debug(message) { 897 | this.emit('debug', message); 898 | } 899 | _setResult() { 900 | // determine whether there is an error 901 | let error; 902 | if (this.processExited) { 903 | if (this.processError) { 904 | error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); 905 | } 906 | else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { 907 | error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); 908 | } 909 | else if (this.processStderr && this.options.failOnStdErr) { 910 | error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); 911 | } 912 | } 913 | // clear the timeout 914 | if (this.timeout) { 915 | clearTimeout(this.timeout); 916 | this.timeout = null; 917 | } 918 | this.done = true; 919 | this.emit('done', error, this.processExitCode); 920 | } 921 | static HandleTimeout(state) { 922 | if (state.done) { 923 | return; 924 | } 925 | if (!state.processClosed && state.processExited) { 926 | const message = `The STDIO streams did not close within ${state.delay / 927 | 1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; 928 | state._debug(message); 929 | } 930 | state._setResult(); 931 | } 932 | } 933 | //# sourceMappingURL=toolrunner.js.map 934 | 935 | /***/ }), 936 | 937 | /***/ 82: 938 | /***/ (function(__unusedmodule, exports) { 939 | 940 | "use strict"; 941 | 942 | // We use any as a valid input type 943 | /* eslint-disable @typescript-eslint/no-explicit-any */ 944 | Object.defineProperty(exports, "__esModule", { value: true }); 945 | /** 946 | * Sanitizes an input into a string so it can be passed into issueCommand safely 947 | * @param input input to sanitize into a string 948 | */ 949 | function toCommandValue(input) { 950 | if (input === null || input === undefined) { 951 | return ''; 952 | } 953 | else if (typeof input === 'string' || input instanceof String) { 954 | return input; 955 | } 956 | return JSON.stringify(input); 957 | } 958 | exports.toCommandValue = toCommandValue; 959 | //# sourceMappingURL=utils.js.map 960 | 961 | /***/ }), 962 | 963 | /***/ 87: 964 | /***/ (function(module) { 965 | 966 | module.exports = require("os"); 967 | 968 | /***/ }), 969 | 970 | /***/ 102: 971 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 972 | 973 | "use strict"; 974 | 975 | // For internal use, subject to change. 976 | var __importStar = (this && this.__importStar) || function (mod) { 977 | if (mod && mod.__esModule) return mod; 978 | var result = {}; 979 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 980 | result["default"] = mod; 981 | return result; 982 | }; 983 | Object.defineProperty(exports, "__esModule", { value: true }); 984 | // We use any as a valid input type 985 | /* eslint-disable @typescript-eslint/no-explicit-any */ 986 | const fs = __importStar(__webpack_require__(747)); 987 | const os = __importStar(__webpack_require__(87)); 988 | const utils_1 = __webpack_require__(82); 989 | function issueCommand(command, message) { 990 | const filePath = process.env[`GITHUB_${command}`]; 991 | if (!filePath) { 992 | throw new Error(`Unable to find environment variable for file command ${command}`); 993 | } 994 | if (!fs.existsSync(filePath)) { 995 | throw new Error(`Missing file at path: ${filePath}`); 996 | } 997 | fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { 998 | encoding: 'utf8' 999 | }); 1000 | } 1001 | exports.issueCommand = issueCommand; 1002 | //# sourceMappingURL=file-command.js.map 1003 | 1004 | /***/ }), 1005 | 1006 | /***/ 129: 1007 | /***/ (function(module) { 1008 | 1009 | module.exports = require("child_process"); 1010 | 1011 | /***/ }), 1012 | 1013 | /***/ 198: 1014 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1015 | 1016 | "use strict"; 1017 | 1018 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 1019 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 1020 | return new (P || (P = Promise))(function (resolve, reject) { 1021 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 1022 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 1023 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 1024 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 1025 | }); 1026 | }; 1027 | var __importStar = (this && this.__importStar) || function (mod) { 1028 | if (mod && mod.__esModule) return mod; 1029 | var result = {}; 1030 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 1031 | result["default"] = mod; 1032 | return result; 1033 | }; 1034 | Object.defineProperty(exports, "__esModule", { value: true }); 1035 | const core = __importStar(__webpack_require__(470)); 1036 | const exec = __importStar(__webpack_require__(986)); 1037 | const io = __importStar(__webpack_require__(1)); 1038 | const path = __importStar(__webpack_require__(622)); 1039 | const fs = __importStar(__webpack_require__(747)); 1040 | const NINJA = 'ninja'; 1041 | const MESON = 'meson'; 1042 | const PYTHON = 'python'; 1043 | const CLANG_TIDY = 'clang-tidy'; 1044 | const GCOVR = 'gcovr'; 1045 | const NINJA_FILE = 'build.ninja'; 1046 | const ACTION = 'action'; 1047 | const DIRECTORY = 'directory'; 1048 | const SETUP_OPTIONS = 'setup-options'; 1049 | const OPTIONS = 'options'; 1050 | const NINJA_VERSION = 'ninja-version'; 1051 | const MESON_VERSION = 'meson-version'; 1052 | const GCOVR_VERSION = 'gcovr-version'; 1053 | var MesonAction; 1054 | (function (MesonAction) { 1055 | MesonAction["Build"] = "build"; 1056 | MesonAction["Install"] = "install"; 1057 | MesonAction["Test"] = "test"; 1058 | MesonAction["Coverage"] = "coverage"; 1059 | MesonAction["Tidy"] = "tidy"; 1060 | })(MesonAction || (MesonAction = {})); 1061 | const BUILD = MesonAction.Build; 1062 | const INSTALL = MesonAction.Install; 1063 | const TEST = MesonAction.Test; 1064 | const COVERAGE = MesonAction.Coverage; 1065 | const TIDY = MesonAction.Tidy; 1066 | var action; 1067 | var directory; 1068 | var setupOptions; 1069 | var options; 1070 | var ninjaVersion; 1071 | var mesonVersion; 1072 | var gcovrVersion; 1073 | function processActionArg() { 1074 | const actionTmp = core.getInput(ACTION); 1075 | core.debug(`Processing action argument: ${actionTmp}`); 1076 | switch (actionTmp.toLowerCase()) { 1077 | case BUILD: 1078 | return MesonAction.Build; 1079 | case INSTALL: 1080 | return MesonAction.Install; 1081 | case TEST: 1082 | return MesonAction.Test; 1083 | case COVERAGE: 1084 | return MesonAction.Coverage; 1085 | case TIDY: 1086 | return MesonAction.Tidy; 1087 | default: 1088 | throw new Error(`Unknown Meson action: ${actionTmp}`); 1089 | } 1090 | } 1091 | function processArgs() { 1092 | core.debug('Processing args...'); 1093 | action = processActionArg(); 1094 | directory = core.getInput(DIRECTORY); 1095 | core.debug(`Processing directory argument: ${directory}`); 1096 | if (directory.length < 1) { 1097 | throw new Error('Meson must build to a directory'); 1098 | } 1099 | const setupOptionsTmp = core.getInput(SETUP_OPTIONS); 1100 | core.debug(`Processing setup options argument: ${setupOptionsTmp}`); 1101 | if (setupOptionsTmp.length > 0) { 1102 | setupOptions = setupOptionsTmp.split(" "); 1103 | } 1104 | else { 1105 | setupOptions = undefined; 1106 | } 1107 | const optionsTmp = core.getInput(OPTIONS); 1108 | core.debug(`Processing options argument: ${optionsTmp}`); 1109 | if (optionsTmp.length > 0) { 1110 | options = optionsTmp.split(" "); 1111 | } 1112 | else { 1113 | options = undefined; 1114 | } 1115 | ninjaVersion = core.getInput(NINJA_VERSION); 1116 | core.debug(`Processing ninja version argument: ${ninjaVersion}`); 1117 | if (ninjaVersion.length < 1) { 1118 | throw new Error('No Ninja version specified'); 1119 | } 1120 | mesonVersion = core.getInput(MESON_VERSION); 1121 | core.debug(`Processing meson version argument: ${mesonVersion}`); 1122 | if (mesonVersion.length < 1) { 1123 | throw new Error('No Meson version specified'); 1124 | } 1125 | gcovrVersion = core.getInput(GCOVR_VERSION); 1126 | core.debug(`Processing gcovr version argument: ${gcovrVersion}`); 1127 | if (gcovrVersion.length < 1) { 1128 | throw new Error('No gcovr version specified'); 1129 | } 1130 | } 1131 | var pythonCache = undefined; 1132 | function findPython() { 1133 | return __awaiter(this, void 0, void 0, function* () { 1134 | core.debug('Searching for Python...'); 1135 | if (pythonCache) { 1136 | core.debug('Using Python from cache'); 1137 | return pythonCache; 1138 | } 1139 | let python; 1140 | let envLocation = process.env.pythonLocation; 1141 | if (envLocation) { 1142 | core.debug('Found Python from setup-python action'); 1143 | python = path.join(envLocation, PYTHON); 1144 | } 1145 | else { 1146 | python = yield io.which(PYTHON); 1147 | if (python.length < 1) 1148 | throw new Error('Python could not be found'); 1149 | core.debug('Found Python using which'); 1150 | } 1151 | pythonCache = python; 1152 | return python; 1153 | }); 1154 | } 1155 | function findNinja() { 1156 | return __awaiter(this, void 0, void 0, function* () { 1157 | core.debug('Checking for Ninja...'); 1158 | try { 1159 | const ninja = yield io.which(NINJA); 1160 | if (ninja.length < 1) 1161 | throw new Error(); 1162 | core.debug('Found Ninja using which'); 1163 | return ninja; 1164 | } 1165 | catch (_a) { 1166 | core.info(`Installing Ninja version ${ninjaVersion}`); 1167 | const python = yield findPython(); 1168 | yield exec.exec(python, ['-m', 'pip', 'install', `ninja==${ninjaVersion}`]); 1169 | const ninja = yield io.which(NINJA); 1170 | if (ninja.length < 1) 1171 | throw new Error('Ninja could not be found after installing'); 1172 | return ninja; 1173 | } 1174 | }); 1175 | } 1176 | function findMeson() { 1177 | return __awaiter(this, void 0, void 0, function* () { 1178 | core.debug('Checking for Meson...'); 1179 | try { 1180 | const meson = yield io.which(MESON); 1181 | if (meson.length < 1) 1182 | throw new Error(); 1183 | core.debug('Found Meson using which'); 1184 | return meson; 1185 | } 1186 | catch (_a) { 1187 | core.info(`Installing Meson version ${mesonVersion}`); 1188 | const python = yield findPython(); 1189 | yield exec.exec(python, ['-m', 'pip', 'install', `meson==${mesonVersion}`]); 1190 | const meson = yield io.which(MESON); 1191 | if (meson.length < 1) 1192 | throw new Error('Meson could not be found after installing'); 1193 | return meson; 1194 | } 1195 | }); 1196 | } 1197 | function findCoverage() { 1198 | return __awaiter(this, void 0, void 0, function* () { 1199 | core.debug(`Checking for ${COVERAGE}`); 1200 | try { 1201 | const gcovr = yield io.which(GCOVR); 1202 | if (gcovr.length < 1) 1203 | throw new Error(); 1204 | core.debug('Found gcovr using which'); 1205 | return gcovr; 1206 | } 1207 | catch (_a) { 1208 | core.info(`Installing gcovr version ${gcovrVersion}`); 1209 | const python = yield findPython(); 1210 | yield exec.exec(python, ['-m', 'pip', 'install', `gcovr==${gcovrVersion}`]); 1211 | const gcovr = yield io.which(GCOVR); 1212 | if (gcovr.length < 1) 1213 | throw new Error('gcovr could not be found after installing'); 1214 | return gcovr; 1215 | } 1216 | }); 1217 | } 1218 | function findTidy() { 1219 | return __awaiter(this, void 0, void 0, function* () { 1220 | core.debug(`Checking for ${CLANG_TIDY}`); 1221 | const tidy = yield io.which(CLANG_TIDY); 1222 | if (tidy.length < 1) 1223 | throw new Error('Clang-tidy must be installed to run it'); 1224 | return tidy; 1225 | }); 1226 | } 1227 | function run() { 1228 | return __awaiter(this, void 0, void 0, function* () { 1229 | try { 1230 | processArgs(); 1231 | const ninja = yield findNinja(); 1232 | const meson = yield findMeson(); 1233 | if (!fs.existsSync(directory) || !fs.existsSync(path.join(directory, NINJA_FILE))) { 1234 | core.info('Project isn\'t setup yet. Setting it up.'); 1235 | let setupArgs = ['setup', directory]; 1236 | if (action == MesonAction.Coverage) 1237 | setupArgs = setupArgs.concat('-Db_coverage=true'); 1238 | if (setupOptions) 1239 | setupArgs = setupArgs.concat(setupOptions); 1240 | core.debug(`Running Meson setup: ${meson} ${setupArgs.join(' ')}`); 1241 | yield exec.exec(meson, setupArgs); 1242 | } 1243 | if (!fs.existsSync(path.join(directory, NINJA_FILE))) { 1244 | throw new Error('Project was not setup successfully'); 1245 | } 1246 | var command = ''; 1247 | var args = []; 1248 | core.debug(`Building arguments array for ${action}`); 1249 | switch (action) { 1250 | case MesonAction.Build: 1251 | command = ninja; 1252 | args = ['-C', directory]; 1253 | break; 1254 | case MesonAction.Install: 1255 | command = meson; 1256 | args = [INSTALL, '-C', directory]; 1257 | break; 1258 | case MesonAction.Test: 1259 | command = meson; 1260 | args = [TEST, '-C', directory]; 1261 | break; 1262 | case MesonAction.Coverage: 1263 | command = yield findCoverage(); 1264 | args = ['-C', directory, COVERAGE]; 1265 | break; 1266 | case MesonAction.Tidy: 1267 | command = yield findTidy(); 1268 | args = ['-C', directory, CLANG_TIDY]; 1269 | break; 1270 | } 1271 | if (options) 1272 | args = args.concat(options); 1273 | core.debug(`Running: ${command} ${args.join(' ')}`); 1274 | yield exec.exec(`"${command}"`, args); 1275 | } 1276 | catch (err) { 1277 | core.setFailed(err.message); 1278 | } 1279 | }); 1280 | } 1281 | exports.run = run; 1282 | 1283 | 1284 | /***/ }), 1285 | 1286 | /***/ 357: 1287 | /***/ (function(module) { 1288 | 1289 | module.exports = require("assert"); 1290 | 1291 | /***/ }), 1292 | 1293 | /***/ 431: 1294 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1295 | 1296 | "use strict"; 1297 | 1298 | var __importStar = (this && this.__importStar) || function (mod) { 1299 | if (mod && mod.__esModule) return mod; 1300 | var result = {}; 1301 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 1302 | result["default"] = mod; 1303 | return result; 1304 | }; 1305 | Object.defineProperty(exports, "__esModule", { value: true }); 1306 | const os = __importStar(__webpack_require__(87)); 1307 | const utils_1 = __webpack_require__(82); 1308 | /** 1309 | * Commands 1310 | * 1311 | * Command Format: 1312 | * ::name key=value,key=value::message 1313 | * 1314 | * Examples: 1315 | * ::warning::This is the message 1316 | * ::set-env name=MY_VAR::some value 1317 | */ 1318 | function issueCommand(command, properties, message) { 1319 | const cmd = new Command(command, properties, message); 1320 | process.stdout.write(cmd.toString() + os.EOL); 1321 | } 1322 | exports.issueCommand = issueCommand; 1323 | function issue(name, message = '') { 1324 | issueCommand(name, {}, message); 1325 | } 1326 | exports.issue = issue; 1327 | const CMD_STRING = '::'; 1328 | class Command { 1329 | constructor(command, properties, message) { 1330 | if (!command) { 1331 | command = 'missing.command'; 1332 | } 1333 | this.command = command; 1334 | this.properties = properties; 1335 | this.message = message; 1336 | } 1337 | toString() { 1338 | let cmdStr = CMD_STRING + this.command; 1339 | if (this.properties && Object.keys(this.properties).length > 0) { 1340 | cmdStr += ' '; 1341 | let first = true; 1342 | for (const key in this.properties) { 1343 | if (this.properties.hasOwnProperty(key)) { 1344 | const val = this.properties[key]; 1345 | if (val) { 1346 | if (first) { 1347 | first = false; 1348 | } 1349 | else { 1350 | cmdStr += ','; 1351 | } 1352 | cmdStr += `${key}=${escapeProperty(val)}`; 1353 | } 1354 | } 1355 | } 1356 | } 1357 | cmdStr += `${CMD_STRING}${escapeData(this.message)}`; 1358 | return cmdStr; 1359 | } 1360 | } 1361 | function escapeData(s) { 1362 | return utils_1.toCommandValue(s) 1363 | .replace(/%/g, '%25') 1364 | .replace(/\r/g, '%0D') 1365 | .replace(/\n/g, '%0A'); 1366 | } 1367 | function escapeProperty(s) { 1368 | return utils_1.toCommandValue(s) 1369 | .replace(/%/g, '%25') 1370 | .replace(/\r/g, '%0D') 1371 | .replace(/\n/g, '%0A') 1372 | .replace(/:/g, '%3A') 1373 | .replace(/,/g, '%2C'); 1374 | } 1375 | //# sourceMappingURL=command.js.map 1376 | 1377 | /***/ }), 1378 | 1379 | /***/ 470: 1380 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1381 | 1382 | "use strict"; 1383 | 1384 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 1385 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 1386 | return new (P || (P = Promise))(function (resolve, reject) { 1387 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 1388 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 1389 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 1390 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 1391 | }); 1392 | }; 1393 | var __importStar = (this && this.__importStar) || function (mod) { 1394 | if (mod && mod.__esModule) return mod; 1395 | var result = {}; 1396 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 1397 | result["default"] = mod; 1398 | return result; 1399 | }; 1400 | Object.defineProperty(exports, "__esModule", { value: true }); 1401 | const command_1 = __webpack_require__(431); 1402 | const file_command_1 = __webpack_require__(102); 1403 | const utils_1 = __webpack_require__(82); 1404 | const os = __importStar(__webpack_require__(87)); 1405 | const path = __importStar(__webpack_require__(622)); 1406 | /** 1407 | * The code to exit an action 1408 | */ 1409 | var ExitCode; 1410 | (function (ExitCode) { 1411 | /** 1412 | * A code indicating that the action was successful 1413 | */ 1414 | ExitCode[ExitCode["Success"] = 0] = "Success"; 1415 | /** 1416 | * A code indicating that the action was a failure 1417 | */ 1418 | ExitCode[ExitCode["Failure"] = 1] = "Failure"; 1419 | })(ExitCode = exports.ExitCode || (exports.ExitCode = {})); 1420 | //----------------------------------------------------------------------- 1421 | // Variables 1422 | //----------------------------------------------------------------------- 1423 | /** 1424 | * Sets env variable for this action and future actions in the job 1425 | * @param name the name of the variable to set 1426 | * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify 1427 | */ 1428 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 1429 | function exportVariable(name, val) { 1430 | const convertedVal = utils_1.toCommandValue(val); 1431 | process.env[name] = convertedVal; 1432 | const filePath = process.env['GITHUB_ENV'] || ''; 1433 | if (filePath) { 1434 | const delimiter = '_GitHubActionsFileCommandDelimeter_'; 1435 | const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; 1436 | file_command_1.issueCommand('ENV', commandValue); 1437 | } 1438 | else { 1439 | command_1.issueCommand('set-env', { name }, convertedVal); 1440 | } 1441 | } 1442 | exports.exportVariable = exportVariable; 1443 | /** 1444 | * Registers a secret which will get masked from logs 1445 | * @param secret value of the secret 1446 | */ 1447 | function setSecret(secret) { 1448 | command_1.issueCommand('add-mask', {}, secret); 1449 | } 1450 | exports.setSecret = setSecret; 1451 | /** 1452 | * Prepends inputPath to the PATH (for this action and future actions) 1453 | * @param inputPath 1454 | */ 1455 | function addPath(inputPath) { 1456 | const filePath = process.env['GITHUB_PATH'] || ''; 1457 | if (filePath) { 1458 | file_command_1.issueCommand('PATH', inputPath); 1459 | } 1460 | else { 1461 | command_1.issueCommand('add-path', {}, inputPath); 1462 | } 1463 | process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; 1464 | } 1465 | exports.addPath = addPath; 1466 | /** 1467 | * Gets the value of an input. The value is also trimmed. 1468 | * 1469 | * @param name name of the input to get 1470 | * @param options optional. See InputOptions. 1471 | * @returns string 1472 | */ 1473 | function getInput(name, options) { 1474 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; 1475 | if (options && options.required && !val) { 1476 | throw new Error(`Input required and not supplied: ${name}`); 1477 | } 1478 | return val.trim(); 1479 | } 1480 | exports.getInput = getInput; 1481 | /** 1482 | * Sets the value of an output. 1483 | * 1484 | * @param name name of the output to set 1485 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 1486 | */ 1487 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 1488 | function setOutput(name, value) { 1489 | command_1.issueCommand('set-output', { name }, value); 1490 | } 1491 | exports.setOutput = setOutput; 1492 | /** 1493 | * Enables or disables the echoing of commands into stdout for the rest of the step. 1494 | * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. 1495 | * 1496 | */ 1497 | function setCommandEcho(enabled) { 1498 | command_1.issue('echo', enabled ? 'on' : 'off'); 1499 | } 1500 | exports.setCommandEcho = setCommandEcho; 1501 | //----------------------------------------------------------------------- 1502 | // Results 1503 | //----------------------------------------------------------------------- 1504 | /** 1505 | * Sets the action status to failed. 1506 | * When the action exits it will be with an exit code of 1 1507 | * @param message add error issue message 1508 | */ 1509 | function setFailed(message) { 1510 | process.exitCode = ExitCode.Failure; 1511 | error(message); 1512 | } 1513 | exports.setFailed = setFailed; 1514 | //----------------------------------------------------------------------- 1515 | // Logging Commands 1516 | //----------------------------------------------------------------------- 1517 | /** 1518 | * Gets whether Actions Step Debug is on or not 1519 | */ 1520 | function isDebug() { 1521 | return process.env['RUNNER_DEBUG'] === '1'; 1522 | } 1523 | exports.isDebug = isDebug; 1524 | /** 1525 | * Writes debug message to user log 1526 | * @param message debug message 1527 | */ 1528 | function debug(message) { 1529 | command_1.issueCommand('debug', {}, message); 1530 | } 1531 | exports.debug = debug; 1532 | /** 1533 | * Adds an error issue 1534 | * @param message error issue message. Errors will be converted to string via toString() 1535 | */ 1536 | function error(message) { 1537 | command_1.issue('error', message instanceof Error ? message.toString() : message); 1538 | } 1539 | exports.error = error; 1540 | /** 1541 | * Adds an warning issue 1542 | * @param message warning issue message. Errors will be converted to string via toString() 1543 | */ 1544 | function warning(message) { 1545 | command_1.issue('warning', message instanceof Error ? message.toString() : message); 1546 | } 1547 | exports.warning = warning; 1548 | /** 1549 | * Writes info to log with console.log. 1550 | * @param message info message 1551 | */ 1552 | function info(message) { 1553 | process.stdout.write(message + os.EOL); 1554 | } 1555 | exports.info = info; 1556 | /** 1557 | * Begin an output group. 1558 | * 1559 | * Output until the next `groupEnd` will be foldable in this group 1560 | * 1561 | * @param name The name of the output group 1562 | */ 1563 | function startGroup(name) { 1564 | command_1.issue('group', name); 1565 | } 1566 | exports.startGroup = startGroup; 1567 | /** 1568 | * End an output group. 1569 | */ 1570 | function endGroup() { 1571 | command_1.issue('endgroup'); 1572 | } 1573 | exports.endGroup = endGroup; 1574 | /** 1575 | * Wrap an asynchronous function call in a group. 1576 | * 1577 | * Returns the same type as the function itself. 1578 | * 1579 | * @param name The name of the group 1580 | * @param fn The function to wrap in the group 1581 | */ 1582 | function group(name, fn) { 1583 | return __awaiter(this, void 0, void 0, function* () { 1584 | startGroup(name); 1585 | let result; 1586 | try { 1587 | result = yield fn(); 1588 | } 1589 | finally { 1590 | endGroup(); 1591 | } 1592 | return result; 1593 | }); 1594 | } 1595 | exports.group = group; 1596 | //----------------------------------------------------------------------- 1597 | // Wrapper action state 1598 | //----------------------------------------------------------------------- 1599 | /** 1600 | * Saves state for current action, the state can only be retrieved by this action's post job execution. 1601 | * 1602 | * @param name name of the state to store 1603 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 1604 | */ 1605 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 1606 | function saveState(name, value) { 1607 | command_1.issueCommand('save-state', { name }, value); 1608 | } 1609 | exports.saveState = saveState; 1610 | /** 1611 | * Gets the value of an state set by this action's main execution. 1612 | * 1613 | * @param name name of the state to get 1614 | * @returns string 1615 | */ 1616 | function getState(name) { 1617 | return process.env[`STATE_${name}`] || ''; 1618 | } 1619 | exports.getState = getState; 1620 | //# sourceMappingURL=core.js.map 1621 | 1622 | /***/ }), 1623 | 1624 | /***/ 614: 1625 | /***/ (function(module) { 1626 | 1627 | module.exports = require("events"); 1628 | 1629 | /***/ }), 1630 | 1631 | /***/ 622: 1632 | /***/ (function(module) { 1633 | 1634 | module.exports = require("path"); 1635 | 1636 | /***/ }), 1637 | 1638 | /***/ 669: 1639 | /***/ (function(module) { 1640 | 1641 | module.exports = require("util"); 1642 | 1643 | /***/ }), 1644 | 1645 | /***/ 672: 1646 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1647 | 1648 | "use strict"; 1649 | 1650 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 1651 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 1652 | return new (P || (P = Promise))(function (resolve, reject) { 1653 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 1654 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 1655 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 1656 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 1657 | }); 1658 | }; 1659 | var _a; 1660 | Object.defineProperty(exports, "__esModule", { value: true }); 1661 | const assert_1 = __webpack_require__(357); 1662 | const fs = __webpack_require__(747); 1663 | const path = __webpack_require__(622); 1664 | _a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; 1665 | exports.IS_WINDOWS = process.platform === 'win32'; 1666 | function exists(fsPath) { 1667 | return __awaiter(this, void 0, void 0, function* () { 1668 | try { 1669 | yield exports.stat(fsPath); 1670 | } 1671 | catch (err) { 1672 | if (err.code === 'ENOENT') { 1673 | return false; 1674 | } 1675 | throw err; 1676 | } 1677 | return true; 1678 | }); 1679 | } 1680 | exports.exists = exists; 1681 | function isDirectory(fsPath, useStat = false) { 1682 | return __awaiter(this, void 0, void 0, function* () { 1683 | const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); 1684 | return stats.isDirectory(); 1685 | }); 1686 | } 1687 | exports.isDirectory = isDirectory; 1688 | /** 1689 | * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: 1690 | * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). 1691 | */ 1692 | function isRooted(p) { 1693 | p = normalizeSeparators(p); 1694 | if (!p) { 1695 | throw new Error('isRooted() parameter "p" cannot be empty'); 1696 | } 1697 | if (exports.IS_WINDOWS) { 1698 | return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello 1699 | ); // e.g. C: or C:\hello 1700 | } 1701 | return p.startsWith('/'); 1702 | } 1703 | exports.isRooted = isRooted; 1704 | /** 1705 | * Recursively create a directory at `fsPath`. 1706 | * 1707 | * This implementation is optimistic, meaning it attempts to create the full 1708 | * path first, and backs up the path stack from there. 1709 | * 1710 | * @param fsPath The path to create 1711 | * @param maxDepth The maximum recursion depth 1712 | * @param depth The current recursion depth 1713 | */ 1714 | function mkdirP(fsPath, maxDepth = 1000, depth = 1) { 1715 | return __awaiter(this, void 0, void 0, function* () { 1716 | assert_1.ok(fsPath, 'a path argument must be provided'); 1717 | fsPath = path.resolve(fsPath); 1718 | if (depth >= maxDepth) 1719 | return exports.mkdir(fsPath); 1720 | try { 1721 | yield exports.mkdir(fsPath); 1722 | return; 1723 | } 1724 | catch (err) { 1725 | switch (err.code) { 1726 | case 'ENOENT': { 1727 | yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1); 1728 | yield exports.mkdir(fsPath); 1729 | return; 1730 | } 1731 | default: { 1732 | let stats; 1733 | try { 1734 | stats = yield exports.stat(fsPath); 1735 | } 1736 | catch (err2) { 1737 | throw err; 1738 | } 1739 | if (!stats.isDirectory()) 1740 | throw err; 1741 | } 1742 | } 1743 | } 1744 | }); 1745 | } 1746 | exports.mkdirP = mkdirP; 1747 | /** 1748 | * Best effort attempt to determine whether a file exists and is executable. 1749 | * @param filePath file path to check 1750 | * @param extensions additional file extensions to try 1751 | * @return if file exists and is executable, returns the file path. otherwise empty string. 1752 | */ 1753 | function tryGetExecutablePath(filePath, extensions) { 1754 | return __awaiter(this, void 0, void 0, function* () { 1755 | let stats = undefined; 1756 | try { 1757 | // test file exists 1758 | stats = yield exports.stat(filePath); 1759 | } 1760 | catch (err) { 1761 | if (err.code !== 'ENOENT') { 1762 | // eslint-disable-next-line no-console 1763 | console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); 1764 | } 1765 | } 1766 | if (stats && stats.isFile()) { 1767 | if (exports.IS_WINDOWS) { 1768 | // on Windows, test for valid extension 1769 | const upperExt = path.extname(filePath).toUpperCase(); 1770 | if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { 1771 | return filePath; 1772 | } 1773 | } 1774 | else { 1775 | if (isUnixExecutable(stats)) { 1776 | return filePath; 1777 | } 1778 | } 1779 | } 1780 | // try each extension 1781 | const originalFilePath = filePath; 1782 | for (const extension of extensions) { 1783 | filePath = originalFilePath + extension; 1784 | stats = undefined; 1785 | try { 1786 | stats = yield exports.stat(filePath); 1787 | } 1788 | catch (err) { 1789 | if (err.code !== 'ENOENT') { 1790 | // eslint-disable-next-line no-console 1791 | console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); 1792 | } 1793 | } 1794 | if (stats && stats.isFile()) { 1795 | if (exports.IS_WINDOWS) { 1796 | // preserve the case of the actual file (since an extension was appended) 1797 | try { 1798 | const directory = path.dirname(filePath); 1799 | const upperName = path.basename(filePath).toUpperCase(); 1800 | for (const actualName of yield exports.readdir(directory)) { 1801 | if (upperName === actualName.toUpperCase()) { 1802 | filePath = path.join(directory, actualName); 1803 | break; 1804 | } 1805 | } 1806 | } 1807 | catch (err) { 1808 | // eslint-disable-next-line no-console 1809 | console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); 1810 | } 1811 | return filePath; 1812 | } 1813 | else { 1814 | if (isUnixExecutable(stats)) { 1815 | return filePath; 1816 | } 1817 | } 1818 | } 1819 | } 1820 | return ''; 1821 | }); 1822 | } 1823 | exports.tryGetExecutablePath = tryGetExecutablePath; 1824 | function normalizeSeparators(p) { 1825 | p = p || ''; 1826 | if (exports.IS_WINDOWS) { 1827 | // convert slashes on Windows 1828 | p = p.replace(/\//g, '\\'); 1829 | // remove redundant slashes 1830 | return p.replace(/\\\\+/g, '\\'); 1831 | } 1832 | // remove redundant slashes 1833 | return p.replace(/\/\/+/g, '/'); 1834 | } 1835 | // on Mac/Linux, test the execute bit 1836 | // R W X R W X R W X 1837 | // 256 128 64 32 16 8 4 2 1 1838 | function isUnixExecutable(stats) { 1839 | return ((stats.mode & 1) > 0 || 1840 | ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || 1841 | ((stats.mode & 64) > 0 && stats.uid === process.getuid())); 1842 | } 1843 | //# sourceMappingURL=io-util.js.map 1844 | 1845 | /***/ }), 1846 | 1847 | /***/ 747: 1848 | /***/ (function(module) { 1849 | 1850 | module.exports = require("fs"); 1851 | 1852 | /***/ }), 1853 | 1854 | /***/ 986: 1855 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1856 | 1857 | "use strict"; 1858 | 1859 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 1860 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 1861 | return new (P || (P = Promise))(function (resolve, reject) { 1862 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 1863 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 1864 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 1865 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 1866 | }); 1867 | }; 1868 | Object.defineProperty(exports, "__esModule", { value: true }); 1869 | const tr = __webpack_require__(9); 1870 | /** 1871 | * Exec a command. 1872 | * Output will be streamed to the live console. 1873 | * Returns promise with return code 1874 | * 1875 | * @param commandLine command to execute (can include additional args). Must be correctly escaped. 1876 | * @param args optional arguments for tool. Escaping is handled by the lib. 1877 | * @param options optional exec options. See ExecOptions 1878 | * @returns Promise exit code 1879 | */ 1880 | function exec(commandLine, args, options) { 1881 | return __awaiter(this, void 0, void 0, function* () { 1882 | const commandArgs = tr.argStringToArray(commandLine); 1883 | if (commandArgs.length === 0) { 1884 | throw new Error(`Parameter 'commandLine' cannot be null or empty.`); 1885 | } 1886 | // Path to tool to execute should be first arg 1887 | const toolPath = commandArgs[0]; 1888 | args = commandArgs.slice(1).concat(args || []); 1889 | const runner = new tr.ToolRunner(toolPath, args, options); 1890 | return runner.exec(); 1891 | }); 1892 | } 1893 | exports.exec = exec; 1894 | //# sourceMappingURL=exec.js.map 1895 | 1896 | /***/ }), 1897 | 1898 | /***/ 995: 1899 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1900 | 1901 | "use strict"; 1902 | 1903 | Object.defineProperty(exports, "__esModule", { value: true }); 1904 | const main_1 = __webpack_require__(198); 1905 | main_1.run(); 1906 | 1907 | 1908 | /***/ }) 1909 | 1910 | /******/ }); -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } 12 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('meson-build') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meson-build", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Run a Meson task during your Actions workflow", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "jest", 10 | "release": "ncc build lib/meson-build.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/BSFishy/meson-build.git" 15 | }, 16 | "keywords": [ 17 | "actions", 18 | "node", 19 | "setup" 20 | ], 21 | "author": "BSFishy", 22 | "license": "MIT", 23 | "dependencies": { 24 | "@actions/core": "^1.2.6", 25 | "@actions/exec": "^1.0.0", 26 | "@actions/io": "^1.0.0" 27 | }, 28 | "devDependencies": { 29 | "@types/jest": "^24.0.13", 30 | "@types/node": "^12.0.4", 31 | "@zeit/ncc": "^0.20.5", 32 | "jest": "^24.8.0", 33 | "jest-circus": "^24.7.1", 34 | "ts-jest": "^24.0.2", 35 | "typescript": "^3.5.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as exec from '@actions/exec'; 3 | import * as io from '@actions/io'; 4 | import * as path from 'path'; 5 | import * as fs from 'fs'; 6 | 7 | const NINJA: string = 'ninja'; 8 | const MESON: string = 'meson'; 9 | const PYTHON: string = 'python'; 10 | const CLANG_TIDY: string = 'clang-tidy'; 11 | const GCOVR: string = 'gcovr'; 12 | 13 | const NINJA_FILE: string = 'build.ninja'; 14 | 15 | const ACTION: string = 'action'; 16 | const DIRECTORY: string = 'directory'; 17 | const SETUP_OPTIONS: string = 'setup-options'; 18 | const OPTIONS: string = 'options'; 19 | const NINJA_VERSION: string = 'ninja-version'; 20 | const MESON_VERSION: string = 'meson-version'; 21 | const GCOVR_VERSION: string = 'gcovr-version'; 22 | 23 | enum MesonAction { 24 | Build = 'build', 25 | Install = 'install', 26 | Test = 'test', 27 | Coverage = 'coverage', 28 | Tidy = 'tidy' 29 | } 30 | 31 | const BUILD: string = MesonAction.Build; 32 | const INSTALL: string = MesonAction.Install; 33 | const TEST: string = MesonAction.Test; 34 | const COVERAGE: string = MesonAction.Coverage; 35 | const TIDY: string = MesonAction.Tidy; 36 | 37 | var action: MesonAction; 38 | var directory: string; 39 | var setupOptions: string[] | undefined; 40 | var options: string[] | undefined; 41 | var ninjaVersion: string; 42 | var mesonVersion: string; 43 | var gcovrVersion: string; 44 | 45 | function processActionArg(): MesonAction { 46 | const actionTmp: string = core.getInput(ACTION); 47 | core.debug(`Processing action argument: ${actionTmp}`); 48 | 49 | switch (actionTmp.toLowerCase()) { 50 | case BUILD: 51 | return MesonAction.Build; 52 | case INSTALL: 53 | return MesonAction.Install; 54 | case TEST: 55 | return MesonAction.Test; 56 | case COVERAGE: 57 | return MesonAction.Coverage; 58 | case TIDY: 59 | return MesonAction.Tidy; 60 | default: 61 | throw new Error(`Unknown Meson action: ${actionTmp}`); 62 | } 63 | } 64 | 65 | function processArgs() { 66 | core.debug('Processing args...'); 67 | 68 | action = processActionArg(); 69 | 70 | directory = core.getInput(DIRECTORY); 71 | core.debug(`Processing directory argument: ${directory}`); 72 | if (directory.length < 1) { 73 | throw new Error('Meson must build to a directory'); 74 | } 75 | 76 | const setupOptionsTmp: string = core.getInput(SETUP_OPTIONS); 77 | core.debug(`Processing setup options argument: ${setupOptionsTmp}`); 78 | if (setupOptionsTmp.length > 0) { 79 | setupOptions = setupOptionsTmp.split(" "); 80 | } else { 81 | setupOptions = undefined; 82 | } 83 | 84 | const optionsTmp: string = core.getInput(OPTIONS); 85 | core.debug(`Processing options argument: ${optionsTmp}`); 86 | if (optionsTmp.length > 0) { 87 | options = optionsTmp.split(" "); 88 | } else { 89 | options = undefined; 90 | } 91 | 92 | ninjaVersion = core.getInput(NINJA_VERSION); 93 | core.debug(`Processing ninja version argument: ${ninjaVersion}`); 94 | if (ninjaVersion.length < 1) { 95 | throw new Error('No Ninja version specified'); 96 | } 97 | 98 | mesonVersion = core.getInput(MESON_VERSION); 99 | core.debug(`Processing meson version argument: ${mesonVersion}`); 100 | if (mesonVersion.length < 1) { 101 | throw new Error('No Meson version specified'); 102 | } 103 | 104 | gcovrVersion = core.getInput(GCOVR_VERSION); 105 | core.debug(`Processing gcovr version argument: ${gcovrVersion}`); 106 | if (gcovrVersion.length < 1) { 107 | throw new Error('No gcovr version specified'); 108 | } 109 | } 110 | 111 | var pythonCache: string | undefined = undefined; 112 | async function findPython(): Promise { 113 | core.debug('Searching for Python...'); 114 | 115 | if (pythonCache) { 116 | core.debug('Using Python from cache'); 117 | return pythonCache; 118 | } 119 | 120 | let python: string; 121 | let envLocation: string | undefined = process.env.pythonLocation; 122 | 123 | if (envLocation) { 124 | core.debug('Found Python from setup-python action'); 125 | python = path.join(envLocation, PYTHON); 126 | } else { 127 | python = await io.which(PYTHON); 128 | if (python.length < 1) 129 | throw new Error('Python could not be found'); 130 | 131 | core.debug('Found Python using which'); 132 | } 133 | 134 | pythonCache = python; 135 | return python; 136 | } 137 | 138 | async function findNinja(): Promise { 139 | core.debug('Checking for Ninja...'); 140 | 141 | try { 142 | const ninja: string = await io.which(NINJA); 143 | if (ninja.length < 1) 144 | throw new Error(); 145 | 146 | core.debug('Found Ninja using which'); 147 | return ninja; 148 | } catch { 149 | core.info(`Installing Ninja version ${ninjaVersion}`); 150 | const python: string = await findPython(); 151 | await exec.exec(python, ['-m', 'pip', 'install', `ninja==${ninjaVersion}`]); 152 | 153 | const ninja: string = await io.which(NINJA); 154 | if (ninja.length < 1) 155 | throw new Error('Ninja could not be found after installing'); 156 | 157 | return ninja; 158 | } 159 | } 160 | 161 | async function findMeson(): Promise { 162 | core.debug('Checking for Meson...'); 163 | 164 | try { 165 | const meson: string = await io.which(MESON); 166 | if (meson.length < 1) 167 | throw new Error(); 168 | 169 | core.debug('Found Meson using which'); 170 | return meson; 171 | } catch { 172 | core.info(`Installing Meson version ${mesonVersion}`); 173 | const python: string = await findPython(); 174 | await exec.exec(python, ['-m', 'pip', 'install', `meson==${mesonVersion}`]); 175 | 176 | const meson: string = await io.which(MESON); 177 | if (meson.length < 1) 178 | throw new Error('Meson could not be found after installing'); 179 | 180 | return meson; 181 | } 182 | } 183 | 184 | async function findCoverage(): Promise { 185 | core.debug(`Checking for ${COVERAGE}`); 186 | 187 | try { 188 | const gcovr: string = await io.which(GCOVR); 189 | if (gcovr.length < 1) 190 | throw new Error(); 191 | 192 | core.debug('Found gcovr using which'); 193 | return gcovr; 194 | } catch { 195 | core.info(`Installing gcovr version ${gcovrVersion}`); 196 | const python: string = await findPython(); 197 | await exec.exec(python, ['-m', 'pip', 'install', `gcovr==${gcovrVersion}`]); 198 | 199 | const gcovr: string = await io.which(GCOVR); 200 | if (gcovr.length < 1) 201 | throw new Error('gcovr could not be found after installing'); 202 | 203 | return gcovr; 204 | } 205 | } 206 | 207 | async function findTidy(): Promise { 208 | core.debug(`Checking for ${CLANG_TIDY}`); 209 | 210 | const tidy: string = await io.which(CLANG_TIDY); 211 | if (tidy.length < 1) 212 | throw new Error('Clang-tidy must be installed to run it'); 213 | 214 | return tidy; 215 | } 216 | 217 | export async function run() { 218 | try { 219 | processArgs(); 220 | 221 | const ninja: string = await findNinja(); 222 | const meson: string = await findMeson(); 223 | 224 | if (!fs.existsSync(directory) || !fs.existsSync(path.join(directory, NINJA_FILE))) { 225 | core.info('Project isn\'t setup yet. Setting it up.'); 226 | 227 | let setupArgs: string[] = ['setup', directory]; 228 | if (action == MesonAction.Coverage) 229 | setupArgs = setupArgs.concat('-Db_coverage=true'); 230 | if (setupOptions) 231 | setupArgs = setupArgs.concat(setupOptions); 232 | 233 | core.debug(`Running Meson setup: ${meson} ${setupArgs.join(' ')}`); 234 | await exec.exec(meson, setupArgs); 235 | } 236 | 237 | if (!fs.existsSync(path.join(directory, NINJA_FILE))) { 238 | throw new Error('Project was not setup successfully'); 239 | } 240 | 241 | var command: string = ''; 242 | var args: string[] = []; 243 | 244 | core.debug(`Building arguments array for ${action}`); 245 | switch (action) { 246 | case MesonAction.Build: 247 | command = ninja; 248 | args = ['-C', directory]; 249 | break; 250 | case MesonAction.Install: 251 | command = meson; 252 | args = [INSTALL, '-C', directory]; 253 | break; 254 | case MesonAction.Test: 255 | command = meson; 256 | args = [TEST, '-C', directory]; 257 | break; 258 | case MesonAction.Coverage: 259 | command = await findCoverage(); 260 | args = ['-C', directory, COVERAGE]; 261 | break; 262 | case MesonAction.Tidy: 263 | command = await findTidy(); 264 | args = ['-C', directory, CLANG_TIDY]; 265 | break; 266 | } 267 | 268 | if (options) 269 | args = args.concat(options); 270 | 271 | core.debug(`Running: ${command} ${args.join(' ')}`); 272 | await exec.exec(`"${command}"`, args); 273 | } catch (err) { 274 | core.setFailed(err.message); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/meson-build.ts: -------------------------------------------------------------------------------- 1 | import {run} from './main'; 2 | 3 | run(); 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | "exclude": ["node_modules", "**/*.test.ts"] 63 | } 64 | --------------------------------------------------------------------------------