Skip to content

Commit

Permalink
feat(npm): add programmatic API for TypeScript (#523)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
favna and orhun committed Mar 3, 2024
1 parent 2927231 commit 8b33267
Show file tree
Hide file tree
Showing 13 changed files with 4,053 additions and 955 deletions.
2 changes: 1 addition & 1 deletion npm/git-cliff/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
893 changes: 893 additions & 0 deletions npm/git-cliff/.yarn/releases/yarn-4.1.0.cjs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions npm/git-cliff/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enableGlobalCache: true

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.1.0.cjs
54 changes: 39 additions & 15 deletions npm/git-cliff/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand All @@ -82,5 +105,6 @@
"lib/*"
],
"root": true
}
},
"packageManager": "[email protected]"
}
12 changes: 12 additions & 0 deletions npm/git-cliff/src/cli.ts
Original file line number Diff line number Diff line change
@@ -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();
33 changes: 33 additions & 0 deletions npm/git-cliff/src/getExePath.ts
Original file line number Diff line number Diff line change
@@ -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})`,
);
}
}
90 changes: 56 additions & 34 deletions npm/git-cliff/src/index.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
29 changes: 29 additions & 0 deletions npm/git-cliff/src/options.ts
Original file line number Diff line number Diff line change
@@ -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;
}>;
26 changes: 26 additions & 0 deletions npm/git-cliff/src/optionsToStringArgs.ts
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 4 additions & 4 deletions npm/git-cliff/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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
}
}
36 changes: 36 additions & 0 deletions npm/git-cliff/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -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",
}),
];
Loading

0 comments on commit 8b33267

Please sign in to comment.