-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(run-if-supported): create a new cli package (#80)
* chore(run-if-supported): create directory for `@sounisi5011/run-if-supported` cli * chore(run-if-supported): add the required dependencies * chore(run-if-supported): create a type definition file for the `npm-install-checks` package * feat(run-if-supported): add script files for the CLI * chore(run-if-supported): remove unused dependencies * test(run-if-supported): add basic tests * fix(run-if-supported): write errors to stderr * test(run-if-supported): fix some tests * fix(run-if-supported): quote arguments that contain whitespace characters * test(run-if-supported): add tests for the parser of CLI arguments * test(run-if-supported): add tests for the `isNotSupported()` function * refactor(run-if-supported): reduce Cognitive Complexity in `getCliData()` function Code Climate reported: + Function `getCliData` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring. * refactor(run-if-supported): reduce Cognitive Complexity in `getBinName()` function Code Climate reported: + Function `getBinName` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring. * refactor(run-if-supported): reduce lines in `createHelpText()` function Code Climate reported: + Function `createHelpText` has 32 lines of code (exceeds 25 allowed). Consider refactoring. * refactor(run-if-supported): reduce lines and Cognitive Complexity in `main()` function Code Climate reported: + Function `main` has a Cognitive Complexity of 16 (exceeds 5 allowed). Consider refactoring. + Function `main` has 30 lines of code (exceeds 25 allowed). Consider refactoring. * refactor(run-if-supported): reduce Cognitive Complexity in `main()` function Code Climate reported: + Function `main` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring. * refactor(run-if-supported): reduce lines and Cognitive Complexity in `parseOptions()` function Code Climate reported: + Function `parseOptions` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring. + Function `parseOptions` has 36 lines of code (exceeds 25 allowed). Consider refactoring. * refactor(run-if-supported): reduce lines in `parseOptions()` function Code Climate reported: + Function `parseOptions` has 30 lines of code (exceeds 25 allowed). Consider refactoring. * refactor(run-if-supported): simplify the functions `parseOptions`, `processOption`, `parseLongOption`, and `parseShortOption` * refactor(run-if-supported): reduce Cognitive Complexity in `createRequiredPlatformText()` function Code Climate reported: + Function `createRequiredPlatformText` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring. * refactor(run-if-supported): reduce Cognitive Complexity in `isNotSupported()` function Code Climate reported: + Function `isNotSupported` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring. * docs(run-if-supported): add `README.md` * docs(run-if-supported): add keywords to `package.json` + `cpu` + `exec`, `execute` and `execution` + `*s` * build(run-if-supported): exclude `*.d.ts` files from the package This package is a CLI, so it should not require type definition files. * ci(run-if-supported): add custom publish scripts
- Loading branch information
1 parent
26bb403
commit 696bf0f
Showing
26 changed files
with
1,449 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
packages/cli/run-if-supported/.github/workflows/publish.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/bash | ||
|
||
# https://qiita.com/yudoufu/items/48cb6fb71e5b498b2532#comment-87e291b98f4cabf77138 | ||
readonly DIR_PATH="$(cd "$(dirname "${BASH_SOURCE:-${(%):-%N}}")"; pwd)" | ||
|
||
outputs_tag_name="${outputs_tag_name}" \ | ||
node "${DIR_PATH}/../../scripts/publish-convert-readme.js" | ||
|
||
# see https://stackoverflow.com/a/62675843/4907315 | ||
pnpm publish --access=public --no-git-checks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# @sounisi5011/run-if-supported | ||
|
||
<!-- [![Go to the latest release page on npm](https://img.shields.io/npm/v/@sounisi5011/run-if-supported.svg)](https://www.npmjs.com/package/@sounisi5011/run-if-supported) --> | ||
<!-- ![Supported Node.js version: ^12.17.x || 14.x || 15.x || 16.x](https://img.shields.io/node/v/@sounisi5011/run-if-supported) --> | ||
[![Tested with Jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest) | ||
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) | ||
<!-- [![Minified Bundle Size Details](https://img.shields.io/bundlephobia/min/@sounisi5011/run-if-supported)](https://bundlephobia.com/result?p=@sounisi5011/run-if-supported) --> | ||
<!-- [![Install Size Details](https://packagephobia.com/badge?p=@sounisi5011/run-if-supported)](https://packagephobia.com/result?p=@sounisi5011/run-if-supported) --> | ||
[![Dependencies Status](https://status.david-dm.org/gh/sounisi5011/npm-packages.svg?path=packages%2Fcli%2Frun-if-supported)](https://david-dm.org/sounisi5011/npm-packages?path=packages/cli/run-if-supported) | ||
[![Build Status](https://github.com/sounisi5011/npm-packages/actions/workflows/ci.yaml/badge.svg)](https://github.com/sounisi5011/npm-packages/actions/workflows/ci.yaml) | ||
[![Maintainability Status](https://api.codeclimate.com/v1/badges/26495b68302f7ff963c3/maintainability)](https://codeclimate.com/github/sounisi5011/npm-packages/maintainability) | ||
|
||
Execute the command only if you are running on a supported version of Node and platform. | ||
By using this CLI, you can run tests only on the supported node versions and platforms, for example, when testing in multiple environments with CI. | ||
|
||
## Installation | ||
|
||
```sh | ||
npm install --save-dev @sounisi5011/run-if-supported | ||
``` | ||
|
||
```sh | ||
yarn add @sounisi5011/run-if-supported --dev | ||
``` | ||
|
||
```sh | ||
pnpm add --save-dev @sounisi5011/run-if-supported | ||
``` | ||
|
||
## Usage | ||
|
||
For example, if you want to run the command `jest`: | ||
|
||
```console | ||
$ run-if-supported jest | ||
# ... | ||
# jest's result | ||
# ... | ||
``` | ||
|
||
Add the `--verbose` option if you want to display the executed command and the reason why it was skipped. | ||
|
||
```console | ||
$ run-if-supported --verbose jest | ||
> $ jest | ||
# ... | ||
# jest's result | ||
# ... | ||
``` | ||
|
||
```console | ||
$ run-if-supported --verbose jest | ||
Skipped command execution. ... | ||
``` | ||
|
||
If you want to show only the reason for skipping, add the `--print-skip-message` option. | ||
|
||
```console | ||
$ run-if-supported --print-skip-message jest | ||
# ... | ||
# jest's result | ||
# ... | ||
``` | ||
|
||
```console | ||
$ run-if-supported --print-skip-message jest | ||
Skipped command execution. ... | ||
``` | ||
|
||
For more information, use the `--help` option to see how to use it, or refer to the [`tests/cli.ts` file](./tests/cli.ts). | ||
|
||
## Define supported versions | ||
|
||
[npm-install-checks]: https://github.com/npm/npm-install-checks | ||
|
||
To define the supported Node.js versions, use the [`engines.node` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#engines) of `package.json`. | ||
|
||
```json | ||
{ | ||
"engines": { | ||
"node": "12.x || 14.x || 16.x" | ||
} | ||
} | ||
``` | ||
|
||
This CLI uses the [same logic as the npm CLI][npm-install-checks] to check the supported versions. | ||
|
||
## Define supported platforms | ||
|
||
To define the supported platforms, use the [`os` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#os) and [`cpu` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#cpu) of `package.json`. | ||
|
||
```json | ||
{ | ||
"os": [ | ||
"win32", | ||
"darwin", | ||
"linux" | ||
], | ||
"cpu": [ | ||
"any" | ||
] | ||
} | ||
``` | ||
|
||
This CLI uses the [same logic as the npm CLI][npm-install-checks] to check the supported platforms. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
coverageDirectory: 'coverage', | ||
globals: { | ||
'ts-jest': { | ||
tsconfig: '<rootDir>/tests/tsconfig.json', | ||
}, | ||
}, | ||
testEnvironment: 'node', | ||
testMatch: ['<rootDir>/tests/**/*.ts'], | ||
testPathIgnorePatterns: [ | ||
'<rootDir>/tests/fixtures/', | ||
'<rootDir>/tests/helpers/', | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"name": "@sounisi5011/run-if-supported", | ||
"version": "0.0.0", | ||
"description": "Execute the command only if you are running on a supported version of Node and platform", | ||
"keywords": [ | ||
"ci", | ||
"cli", | ||
"command", | ||
"commandline", | ||
"cpu", | ||
"exec", | ||
"execute", | ||
"execution", | ||
"node", | ||
"os", | ||
"platform", | ||
"platforms", | ||
"run", | ||
"support", | ||
"supported", | ||
"supports", | ||
"tool", | ||
"tools", | ||
"version", | ||
"versions" | ||
], | ||
"homepage": "https://github.com/sounisi5011/npm-packages/tree/main/packages/cli/run-if-supported#readme", | ||
"bugs": { | ||
"url": "https://github.com/sounisi5011/npm-packages/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/sounisi5011/npm-packages.git", | ||
"directory": "packages/cli/run-if-supported" | ||
}, | ||
"license": "MIT", | ||
"author": "sounisi5011", | ||
"type": "commonjs", | ||
"bin": { | ||
"run-if-supported": "./dist/index.js" | ||
}, | ||
"directories": { | ||
"lib": "./src/", | ||
"test": "./tests/" | ||
}, | ||
"files": [ | ||
"dist/", | ||
"!*.d.ts", | ||
"!*.tsbuildinfo" | ||
], | ||
"scripts": { | ||
"build": "tsc -p ./src/", | ||
"lint:tsc": "run-p lint:tsc:*", | ||
"lint:tsc:src": "tsc -p ./src/ --noEmit", | ||
"lint:tsc:test": "tsc -p ./tests/ --noEmit", | ||
"test": "jest" | ||
}, | ||
"dependencies": { | ||
"command-join": "^3.0.0", | ||
"cross-spawn": "^7.0.3", | ||
"npm-install-checks": "^4.0.0", | ||
"parse-json": "^5.2.0", | ||
"pkg-up": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"@types/cross-spawn": "6.0.2", | ||
"@types/node": "*", | ||
"@types/parse-json": "^4.0.0", | ||
"execa": "5.0.0", | ||
"ultra-runner": "3.10.5" | ||
}, | ||
"engines": { | ||
"node": "^12.17.x || 14.x || 15.x || 16.x" | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
packages/cli/run-if-supported/scripts/publish-convert-readme.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const getPath = pathname => path.resolve(__dirname, pathname); | ||
|
||
const pkgPath = getPath('../package.json'); | ||
const readmePath = getPath('../README.md'); | ||
|
||
const pkg = require(pkgPath); | ||
|
||
/** | ||
* @param {string} name | ||
* @returns {string|undefined} | ||
*/ | ||
function lookupEnv(name) { | ||
for (const [envName, value] of Object.entries(process.env)) { | ||
if (envName.toLowerCase() === name.toLowerCase()) { | ||
return value; | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
||
const repoURL = pkg.repository | ||
&& (typeof pkg.repository === 'string' ? pkg.repository : pkg.repository.url) | ||
.replace(/^git\+/, '') | ||
.replace(/\.git$/, ''); | ||
const directory = pkg.repository && typeof pkg.repository === 'object' | ||
? pkg.repository.directory.replace(/\/*$/, '/') | ||
: ''; | ||
const tagName = lookupEnv('outputs_tag_name'); | ||
const rootURL = repoURL ? `${repoURL}/tree/${tagName || 'main'}/${directory}` : ''; | ||
|
||
/** | ||
* @param {string} url | ||
* @returns {string} | ||
*/ | ||
function replaceURL(url) { | ||
if (pkg.version) { | ||
if (url.startsWith('https://img.shields.io/bundlephobia/') && !/\/\d+(?:\.\d+){2}$/.test(url)) { | ||
return `${url}/${pkg.version}`; | ||
} | ||
if ( | ||
url.startsWith('https://bundlephobia.com/result?p=') | ||
|| url.startsWith('https://packagephobia.com/badge?p=') | ||
|| url.startsWith('https://packagephobia.com/result?p=') | ||
) { | ||
return url.replace(/(?:@\d+(?:\.\d+){2})?$/, `@${pkg.version}`); | ||
} | ||
} | ||
if (rootURL) { | ||
if (url.startsWith('./')) { | ||
return rootURL + url.replace(/^\.\//, ''); | ||
} | ||
} | ||
if (pkg.license) { | ||
if (url.startsWith('https://img.shields.io/npm/l/')) { | ||
return `https://img.shields.io/static/v1?label=license&message=${encodeURIComponent(pkg.license)}&color=green`; | ||
} | ||
} | ||
if (pkg.engines && pkg.engines.node && url.startsWith('https://img.shields.io/node/v/')) { | ||
return `https://img.shields.io/static/v1?label=node&message=${ | ||
encodeURIComponent(pkg.engines.node) | ||
}&color=brightgreen`; | ||
} | ||
return url; | ||
} | ||
|
||
const readmeText = fs.readFileSync(readmePath, 'utf8'); | ||
|
||
const updatedReadmeText = readmeText | ||
.replace(/(?<=\()(?:https?:\/\/|\.{1,2}\/)[^)\s]+/g, url => { | ||
const newUrl = replaceURL(url); | ||
if (newUrl !== url) { | ||
console.log(`replace "${url}"\n to "${newUrl}"`); | ||
} | ||
return newUrl; | ||
}) | ||
.replace(/(?<=\]: *)(?:https?:\/\/|\.{1,2}\/)[^\s]+/g, url => { | ||
const newUrl = replaceURL(url); | ||
if (newUrl !== url) { | ||
console.log(`replace "${url}"\n to "${newUrl}"`); | ||
} | ||
return newUrl; | ||
}); | ||
|
||
fs.writeFileSync(readmePath, updatedReadmeText); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#!/usr/bin/env node | ||
|
||
import { argv, cwd as getCwd, versions } from 'process'; | ||
|
||
import { main } from './main'; | ||
import { spawnAsync } from './spawn'; | ||
|
||
main({ | ||
cwd: getCwd(), | ||
entryFilepath: __filename, | ||
argv: argv.slice(2), | ||
nodeVersion: versions.node, | ||
spawnAsync, | ||
}).catch(error => { | ||
process.exitCode = 1; | ||
console.error(error); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { checkEngine, checkPlatform } from 'npm-install-checks'; | ||
|
||
import { isRecordLike, isString } from './utils'; | ||
|
||
function readProp<T>( | ||
obj: unknown, | ||
prop: string, | ||
validate: (value: unknown) => value is T, | ||
): T | null { | ||
if (isRecordLike(obj)) { | ||
const value = obj[prop]; | ||
if (validate(value)) return value; | ||
} | ||
return null; | ||
} | ||
|
||
function validateError<T>(error: unknown, fn: (error: Error & Record<PropertyKey, unknown>) => T): T { | ||
if (error instanceof Error && isRecordLike(error)) return fn(error); | ||
throw error; | ||
} | ||
|
||
function createRequiredPlatformText(error: Error & Record<PropertyKey, unknown>): string { | ||
if (!isRecordLike(error['current']) || !isRecordLike(error['required'])) return ''; | ||
return Object.entries(error['current']) | ||
.flatMap(([prop, currentPlatform]): string[] => { | ||
if (!isString(currentPlatform)) return []; | ||
const requiredPlatformList = ( | ||
readProp(error['required'], prop, Array.isArray) | ||
?? [readProp(error['required'], prop, isString)] | ||
).filter(isString); | ||
if (requiredPlatformList.length < 1) return []; | ||
return [ | ||
`${prop}:`, | ||
` current: ${currentPlatform}`, | ||
` required:`, | ||
...requiredPlatformList.map(platform => ` - ${/\s|^$/.test(platform) ? `"${platform}"` : platform}`), | ||
]; | ||
}) | ||
.map(line => ` ${line}`) | ||
.join('\n'); | ||
} | ||
|
||
export function isNotSupported( | ||
pkg: Record<string, unknown>, | ||
nodeVersion: string, | ||
): string | false { | ||
try { | ||
checkEngine(pkg, null, nodeVersion); | ||
} catch (error: unknown) { | ||
return validateError(error, error => { | ||
if (error['code'] !== 'EBADENGINE') throw error; | ||
const nodeRange = readProp(error['required'], 'node', isString); | ||
return `Node ${nodeVersion} is not included in supported range${nodeRange ? `: ${nodeRange}` : ''}`; | ||
}); | ||
} | ||
try { | ||
checkPlatform(pkg); | ||
} catch (error: unknown) { | ||
return validateError(error, error => { | ||
if (error['code'] !== 'EBADPLATFORM') throw error; | ||
const requiredPlatform = createRequiredPlatformText(error); | ||
return `Current platform is not included in supported list${ | ||
requiredPlatform ? `:\n${requiredPlatform}` : '' | ||
}`; | ||
}); | ||
} | ||
return false; | ||
} |
Oops, something went wrong.