1
1
mirror of https://github.com/orhun/git-cliff.git synced 2024-09-11 06:55:38 +03:00

feat(npm): add programmatic API for TypeScript (#523)

* feat: add programmatic api

* fix: return the execa result from the programmatic API instead of exiting the process

* feat: make it possible to customize execa when using the programmatic api

* fix: fixed dev script

* refactor: remove unnecessary await

* chore: update lockfile and let yarn resort dependencies

* style: formatting in tsup config

* chore(npm): log the error in case of exe path is not found

---------

Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
Jeroen Claassens 2024-03-03 20:29:42 +01:00 committed by GitHub
parent 292723109f
commit 8b33267967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 3908 additions and 810 deletions

View File

@ -125,7 +125,7 @@ dist
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
# yarn berry
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml

893
npm/git-cliff/.yarn/releases/yarn-4.1.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
enableGlobalCache: true
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.1.0.cjs

View File

@ -2,13 +2,32 @@
"name": "git-cliff",
"version": "2.1.0-rc.0",
"description": "A highly customizable Changelog Generator that follows Conventional Commit specifications ⛰️",
"bin": "lib/index.js",
"type": "module",
"main": "lib/cjs/index.d.cts",
"module": "lib/esm/index.d.ts",
"types": "lib/cjs/index.d.cts",
"bin": "lib/cli/cli.js",
"exports": {
"./cli": {
"import": "./lib/cli/cli.js"
},
".": {
"import": {
"types": "./lib/esm/index.d.ts",
"default": "./lib/esm/index.js"
},
"require": {
"types": "./lib/cjs/index.d.cts",
"default": "./lib/cjs/index.cjs"
}
}
},
"scripts": {
"typecheck": "tsc --noEmit",
"typecheck": "tsc",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"build": "tsc",
"dev": "yarn build && node lib/index.js"
"build": "tsup",
"dev": "yarn build && node lib/cli/cli.js"
},
"repository": {
"type": "git",
@ -54,20 +73,24 @@
"url": "https://github.com/orhun/git-cliff/issues"
},
"homepage": "https://github.com/orhun/git-cliff#readme",
"dependencies": {
"execa": "^8.0.1"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint": "^8.31.0",
"typescript": "^4.9.4"
"@types/node": "^20.11.22",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"eslint": "^8.57.0",
"tsup": "^8.0.2",
"typescript": "^5.3.3"
},
"optionalDependencies": {
"git-cliff-linux-x64": "2.1.0-rc.0",
"git-cliff-linux-arm64": "2.1.0-rc.0",
"git-cliff-darwin-x64": "2.1.0-rc.0",
"git-cliff-darwin-arm64": "2.1.0-rc.0",
"git-cliff-windows-x64": "2.1.0-rc.0",
"git-cliff-windows-arm64": "2.1.0-rc.0"
"git-cliff-darwin-x64": "2.1.0-rc.0",
"git-cliff-linux-arm64": "2.1.0-rc.0",
"git-cliff-linux-x64": "2.1.0-rc.0",
"git-cliff-windows-arm64": "2.1.0-rc.0",
"git-cliff-windows-x64": "2.1.0-rc.0"
},
"eslintConfig": {
"extends": [
@ -82,5 +105,6 @@
"lib/*"
],
"root": true
}
},
"packageManager": "yarn@4.1.0"
}

12
npm/git-cliff/src/cli.ts Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
import { runGitCliff } from "./index.js";
async function run() {
const args = process.argv.slice(2);
const processResult = await runGitCliff(args);
process.exit(processResult.exitCode ?? 0);
}
void run();

View File

@ -0,0 +1,33 @@
import { arch as getArch, platform as getPlatform } from "os";
/**
* Returns the executable path for git-cliff located inside node_modules
* The naming convention is git-cliff-${os}-${arch}
* If the platform is `win32` or `cygwin`, executable will include a `.exe` extension
* @see https://nodejs.org/api/os.html#osarch
* @see https://nodejs.org/api/os.html#osplatform
* @example "x/xx/node_modules/git-cliff-darwin-arm64"
*/
export async function getExePath() {
const platform = getPlatform();
const arch = getArch();
let os = platform as string;
let extension = "";
if (platform === "win32" || platform === "cygwin") {
os = "windows";
extension = ".exe";
}
try {
// Since the bin will be located inside `node_modules`, we can simply call import.meta.resolve
return import.meta.resolve(
`git-cliff-${os}-${arch}/bin/git-cliff${extension}`,
);
} catch (e) {
throw new Error(
`Couldn't find git-cliff binary inside node_modules for ${os}-${arch} (${e})`,
);
}
}

View File

@ -1,39 +1,61 @@
#!/usr/bin/env node
import { execa, type Options as ExecaOptions, type ExecaReturnValue, } from "execa";
import { fileURLToPath } from "node:url";
import { getExePath } from "./getExePath.js";
import type { Options } from "./options.js";
import { optionsToStringArgs } from "./optionsToStringArgs.js";
import { spawnSync } from "child_process"
export type { Options } from "./options.js";
/**
* Returns the executable path for git-cliff located inside node_modules
* The naming convention is git-cliff-${os}-${arch}
* If the platform is `win32` or `cygwin`, executable will include a `.exe` extension
* @see https://nodejs.org/api/os.html#osarch
* @see https://nodejs.org/api/os.html#osplatform
* @example "x/xx/node_modules/git-cliff-darwin-arm64"
*/
function getExePath() {
const arch = process.arch;
let os = process.platform as string;
let extension = '';
if (['win32', 'cygwin'].includes(process.platform)) {
os = 'windows';
extension = '.exe';
}
try {
// Since the bin will be located inside `node_modules`, we can simply call require.resolve
return require.resolve(`git-cliff-${os}-${arch}/bin/git-cliff${extension}`)
} catch (e) {
throw new Error(`Couldn't find git-cliff binary inside node_modules for ${os}-${arch}`)
}
}
* Runs `git-cliff` with the provided options as a JavaScript object.
*
* @param options - The options to pass to `git-cliff`.
* These get transformed into an array strings.
* - Values that are `true` will be passed as flags (`--flag`).
* - Values that are `false` or `null` will be ignored.
* - All other values will be passed as options (`--option value`).
*
* @param execaOptions - Options to pass to {@link execa}.
*/
export async function runGitCliff(options: Options, execaOptions?: ExecaOptions): Promise<ExecaReturnValue<string>>;
/**
* Runs `git-cliff` with args using nodejs spawn
*/
function runGitCliff() {
const args = process.argv.slice(2)
const processResult = spawnSync(getExePath(), args, { stdio: "inherit" })
process.exit(processResult.status ?? 0)
}
* Runs the `git-cliff` with the provided arguments.
*
* @param args - The arguments to pass to `git-cliff`.
* These should be in an array of string format.
* Every option and their value should be its own entry in the array.
*
* @param execaOptions - Options to pass to {@link execa}.
*
* @returns A promise that resolves when the `git-cliff` has finished running.
*
* @example
* Options with values
* ```typescript
* await runGitCliff(["--tag", "1.0.0", "--config", "github"]);
* ```
*
* @example
* Boolean flags
* ```typescript
* await runGitCliff(["--unreleased", "--topo-order"]);
* ```
*
* @example
* Combining options and flags
* ```typescript
* await runGitCliff(["--tag", "1.0.0", "--config", "github", "--topo-order"]);
* ```
*/
export async function runGitCliff(args: string[], execaOptions?: ExecaOptions): Promise<ExecaReturnValue<string>>;
export async function runGitCliff(argsOrOptions: Options | string[], execaOptions?: ExecaOptions): Promise<ExecaReturnValue<string>> {
const exePath = await getExePath();
const args = Array.isArray(argsOrOptions)
? argsOrOptions
: optionsToStringArgs(argsOrOptions);
runGitCliff()
return execa(fileURLToPath(exePath), args, {
stdio: "inherit",
...execaOptions,
});
}

View File

@ -0,0 +1,29 @@
export type Options = Partial<{
help: boolean;
version: boolean;
verbose: boolean;
init: boolean | string;
config: string;
workdir: string;
repository: string;
includePath: string;
excludePath: string;
withCommit: string;
skipCommit: string;
prepend: string;
output: string;
tag: string;
bump: boolean;
bumpedVersion: boolean;
body: string;
latest: boolean;
current: boolean;
unreleased: boolean;
topoOrder: boolean;
noExec: boolean;
context: boolean;
strip: "header" | "footer" | "all";
sort: "oldest" | "newest";
githubToken: string;
githubRepo: string;
}>;

View File

@ -0,0 +1,26 @@
import type { Options } from "./options.js";
/**
* Transforms a JavaScript object of options into an array
* of strings that can be passed to {@link execa} for calling `git-cliff`
*
* @param options The options to transform
* @returns The options as an array of strings
*/
export function optionsToStringArgs(options: Options): string[] {
const args: string[] = [];
for (const [key, value] of Object.entries(options)) {
const hyphenCaseKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
if (value === true) {
args.push(`--${hyphenCaseKey}`);
} else if (value === false || value === null) {
continue;
} else {
args.push(`--${hyphenCaseKey}`, value);
}
}
return args;
}

View File

@ -1,12 +1,12 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"module": "node16",
"moduleResolution": "node16",
"esModuleInterop": true,
"baseUrl": "./",
"outDir": "lib",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
"resolveJsonModule": true,
"noEmit": true
}
}

View File

@ -0,0 +1,36 @@
import { defineConfig, type Options } from "tsup";
import { dependencies } from "./package.json";
const baseOptions: Options = {
clean: true,
dts: true,
entry: ["src/index.ts"],
minify: false,
external: Object.keys(dependencies),
sourcemap: true,
target: "es2020",
tsconfig: "tsconfig.json",
keepNames: true,
treeshake: true,
};
export default [
defineConfig({
...baseOptions,
outDir: "lib/cjs",
format: "cjs",
}),
defineConfig({
...baseOptions,
outDir: "lib/esm",
format: "esm",
}),
defineConfig({
...baseOptions,
outDir: "lib/cli",
entry: ["src/cli.ts"],
dts: false,
sourcemap: false,
format: "esm",
}),
];

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,19 @@ npm install git-cliff --save-dev
```
Afterwards, you can run **git-cliff** via `npm exec git-cliff` or `npx git-cliff@latest`.
## Programmatic API
**git-cliff** also provides a fully typed programmatic API. You can use it to integrate **git-cliff** into your own tooling.
```typescript
import { runGitCliff, type Options } from 'git-cliff';
const options: Options = {
// ...
};
runGitCliff(options);
```
Under the hood this will parse the options, set up the environment and call the **git-cliff** binary for you.