Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
Mar 3, 2024

Conversation

favna
Copy link
Contributor

@favna favna commented Feb 29, 2024

Description

Working on cliff-jumper it irked me that I had to call git-cliff through npx. Looking at the code I figured I could easily add a small programmatic API on top of (or technically rather below 😂) the CLI one currently provided so here we are.

Other than the actual changes in the source code I also took the liberty to:

  1. Update dev dependencies
  2. Switch out regular child_process with execa, primarily for its promise-based API but also refer to the other advantages it offers (FWIW it's one of many Sindre's packages so it can be trusted to be stable and good)
  3. Added tsup as bundler so separate CJS, ESM and CLI bundles can easily be generated
    • The whole split of CJS and ESM is something I could go into a lot more detail for but for brevity's sake I'll just keep it at that the NodeJS ecosystem is currently shattered between the two and for proper TypeScript support 2 bundles have to be provided, related for more reading: https://github.com/arethetypeswrong/arethetypeswrong.github.io
  4. Updated from Yarn v1 to Yarn v4
  5. Marked the package as being an ES module, thanks to the added export mapping and the split CJS/ESM bundles from point 3 this is now possible. It also allows the source code to use modern ESM code like import.meta.resolve instead of the old require.resolve, yay!
  6. Added the export mapping, it's the modern way in a package.json to define all the entrypoints
  7. Modernized the tsconfig, more yay for modern code.

Important: This sets the minimum NodeJS requirement to Node 18 as it is the lowest version that supports import.meta.resolve without an experimental flag (docs v16, docs v18, docs v20). In my opinion this is acceptable because Node 16 has been End of Life since 2023-09-11 and people should as minimum update to Node 18.

Motivation and Context

Having a programmatic API is always a nice-to-have for the more advanced users of the git-cliff npm package as it allows far easier scripting integration. The primary motivation here is my own library cliff-jumper which is one such "more advanced use".

How Has This Been Tested?

I have tested this by yarn linking the local repository to my cliff-jumper repository where I then refactored my code to use the programmatic API. Where before I had to parse out the options provided to cliff-jumper to proper CLI options and call npx I can now call the programmatic API. I have ran this code and it works perfectly:

const gitCliffOptions: GitCliffOptions = {
  tag: options.tagTemplate,
  prepend: './CHANGELOG.md',
  unreleased: true,
  config: './cliff.toml'
};

if (!isNullishOrEmpty(repositoryRootDirectory)) {
  gitCliffOptions.repository = repositoryRootDirectory;
  gitCliffOptions.includePath = `${options.packagePath}/*`;
}

const githubToken = getGitHubToken(options);
const githubRepo = getGitHubRepo(options);
if (!isNullishOrEmpty(githubRepo) && !isNullishOrEmpty(githubToken)) {
  const resolvedGitHubRepo = githubRepo === 'auto' ? `${options.org}/${options.name}` : `${githubRepo}`;

  gitCliffOptions.githubRepo = resolvedGitHubRepo;
  gitCliffOptions.githubToken = githubToken;
}

await runGitCliff(gitCliffOptions);

Screenshots / Logs (if applicable)

N.A.

Types of Changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (no code change)
  • Refactor (refactoring production code)
  • Other

Checklist:

  • My code follows the code style of this project.
    (I hope so anyway, I had VSCode auto format and it should have picked up editorconfig)
  • I have updated the documentation accordingly.
  • I have formatted the code with rustfmt. N.A. I haven't touched rust code
  • I checked the lints with clippy ESLint.
  • I have added tests to cover my changes. N.A.
  • All new and existing tests passed.

@favna favna requested a review from orhun as a code owner February 29, 2024 02:27
@codecov-commenter
Copy link

codecov-commenter commented Feb 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 41.14%. Comparing base (2927231) to head (2ee7d10).

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #523   +/-   ##
=======================================
  Coverage   41.14%   41.14%           
=======================================
  Files          15       15           
  Lines        1038     1038           
=======================================
  Hits          427      427           
  Misses        611      611           
Flag Coverage Δ
unit-tests 41.14% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@favna
Copy link
Contributor Author

favna commented Feb 29, 2024

Tested it on both MacOS and Windows now. Also with a not 3 AM brain which let me to make the last 2 commits lol

@orhun
Copy link
Owner

orhun commented Mar 1, 2024

Hey this looks pretty good! I will test it locally soon.

I can also create a RC release just to test this functionality and see if everything works with NPM.

@orhun
Copy link
Owner

orhun commented Mar 2, 2024

I tried running yarn && yarn dev inside npm/git-cliff directory and got this error:

file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/index.js:3
Object.defineProperty(exports, "__esModule", { value: true });
                      ^

ReferenceError: exports is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/home/orhun/gh/git-cliff/npm/git-cliff/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/index.js:3:23
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

Node.js v18.17.0

Whereas this was working before like this:

$ yarn dev
yarn run v1.22.21
$ yarn build && node lib/index.js
$ tsc
 WARN  git_cliff > "cliff.toml" is not found, using the default configuration.

@favna any idea what's going on?

@favna
Copy link
Contributor Author

favna commented Mar 2, 2024

@orhun Fixed it. I overlooked the dev script calling the cli so I just had to adjust the path.

@favna
Copy link
Contributor Author

favna commented Mar 2, 2024

I do still get an error but I don't think that's related to the programmatic change.

 WARN  git_cliff > "cliff.toml" is not found, using the default configuration.
 ERROR git_cliff > Git error: `could not find repository at 'F:\favware\git-cliff\npm\git-cliff'; class=Repository (6); code=NotFound (-3)`
file:///F:/favware/git-cliff/npm/git-cliff/node_modules/execa/lib/error.js:60
                error = new Error(message);
                        ^

Error: Command failed with exit code 1: F:\favware\git-cliff\npm\git-cliff\node_modules\git-cliff-windows-x64\bin\git-cliff.exe
    at makeError (file:///F:/favware/git-cliff/npm/git-cliff/node_modules/execa/lib/error.js:60:11)
    at handlePromise (file:///F:/favware/git-cliff/npm/git-cliff/node_modules/execa/index.js:124:26)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async run (file:///F:/favware/git-cliff/npm/git-cliff/lib/cli/cli.js:60:25) {
  shortMessage: 'Command failed with exit code 1: F:\\favware\\git-cliff\\npm\\git-cliff\\node_modules\\git-cliff-windows-x64\\bin\\git-cliff.exe',
  command: 'F:\\favware\\git-cliff\\npm\\git-cliff\\node_modules\\git-cliff-windows-x64\\bin\\git-cliff.exe',
  escapedCommand: '"F:\\favware\\git-cliff\\npm\\git-cliff\\node_modules\\git-cliff-windows-x64\\bin\\git-cliff.exe"',
  exitCode: 1,
  signal: undefined,
  signalDescription: undefined,
  stdout: undefined,
  stderr: undefined,
  cwd: 'F:\\favware\\git-cliff\\npm\\git-cliff',
  failed: true,
  timedOut: false,
  isCanceled: false,
  killed: false
}

Node.js v20.11.0

In fact I switched back to main and I got this still, essentially the same error just less verbose (which relates to what Execa prints and I'd say in this case the higher verbosity is better)

yarn run v1.22.21
$ yarn build && node lib/index.js
$ tsc
 WARN  git_cliff > "cliff.toml" is not found, using the default configuration.
 ERROR git_cliff > Git error: `could not find repository at 'F:\favware\git-cliff\npm\git-cliff'; class=Repository (6); code=NotFound (-3)`
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

@orhun
Copy link
Owner

orhun commented Mar 2, 2024

I'm getting a different error:

file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/cli/cli.js:22
    throw new Error(
          ^

Error: Couldn't find git-cliff binary inside node_modules for linux-x64
    at getExePath (file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/cli/cli.js:22:11)
    at runGitCliff (file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/cli/cli.js:48:25)
    at run (file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/cli/cli.js:60:31)
    at file:///home/orhun/gh/git-cliff/npm/git-cliff/lib/cli/cli.js:64:6
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

Node.js v18.17.0

However, I can confirm that the binary exists:

$ file node_modules/git-cliff-linux-x64/bin/git-cliff
node_modules/git-cliff-linux-x64/bin/git-cliff: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7bc79cc471a2b79104849701c87fcc2ebcb76add, stripped

@orhun
Copy link
Owner

orhun commented Mar 2, 2024

I applied this patch:

diff --git a/npm/git-cliff/src/getExePath.ts b/npm/git-cliff/src/getExePath.ts
index f1bbbd6..341087f 100644
--- a/npm/git-cliff/src/getExePath.ts
+++ b/npm/git-cliff/src/getExePath.ts
@@ -28,7 +28,7 @@ export async function getExePath() {
     );
   } catch (e) {
     throw new Error(
-      `Couldn't find git-cliff binary inside node_modules for ${os}-${arch}`
+      `Couldn't find git-cliff binary inside node_modules for ${os}-${arch} (${e})`,
     );
   }
 }

The error is:

Error: Couldn't find git-cliff binary inside node_modules for linux-x64 (TypeError: (intermediate value).resolve is not a function)

My IDE also complains about the await import.meta.resolve line:

typescript: 'await' has no effect on the type of this expression.

@favna favna force-pushed the feat/npm-programmatic-api branch from 5254d1c to 7b01b05 Compare March 3, 2024 16:33
@favna
Copy link
Contributor Author

favna commented Mar 3, 2024

My IDE also complains about the await import.meta.resolve line:

typescript: 'await' has no effect on the type of this expression.

I was under the impression that import.meta.resolve returns a Promise but apparently it does not so the IDE was correct here. I corrected this in eb3b90e

I'll see if I can reproduce your issue. So far I've only tried with MacOS and Windows on Node 20. I'll try with Node 18.17.0 (your version as seen above) on both and leverage one of my VPSs for Linux x64 as well. I'll report back.

@favna
Copy link
Contributor Author

favna commented Mar 3, 2024

OS Arch Node version Result of import.meta.resolve Result
Windows 11 Pro x64 v18.17.0 TypeError: (intermediate value).resolve is not a function
Windows 11 Pro x64 v18.19.1 file:///F:/favware/git-cliff/npm/git-cliff/node_modules/git-cliff-windows-x64/bin/git-cliff.exe
Windows 11 Pro x64 v20.11.1 file:///F:/favware/git-cliff/npm/git-cliff/node_modules/git-cliff-windows-x64/bin/git-cliff.exe
MacOS (darwin) arm64 v18.17.0 TypeError: (intermediate value).resolve is not a function
MacOS (darwin) arm64 v18.19.1 file:///Users/favna/workspace/favware/git-cliff/npm/git-cliff/node_modules/git-cliff-darwin-arm64/bin/git-cliff
MacOS (darwin) arm64 v20.11.1 file:///Users/favna/workspace/favware/git-cliff/npm/git-cliff/node_modules/git-cliff-darwin-arm64/bin/git-cliff
Debian GNU/Linux 11 x64 v18.17.0 TypeError: (intermediate value).resolve is not a function
Debian GNU/Linux 11 x64 v18.19.1 file:///root/workspace/git-cliff/npm/git-cliff/node_modules/git-cliff-linux-x64/bin/git-cliff
Debian GNU/Linux 11 x64 v20.11.1 file:///root/workspace/git-cliff/npm/git-cliff/node_modules/git-cliff-linux-x64/bin/git-cliff

I think the pattern is fairly obvious. Looks like there is a bug in Node 18.17.0 which is fixed by 18.19.1. 18.19.1 is the current latest release within the v18 major.

Edit: I was curious which exactly version fixed it and it was v18.19.0. The version history was:

Version Date Time
v18.7.0 26-Jul-2022 22:21
v18.8.0 24-Aug-2022 15:59
v18.9.0 08-Sep-2022 14:44
v18.9.1 23-Sep-2022 16:01

(as pulled from https://nodejs.org/download/release/)

@orhun
Copy link
Owner

orhun commented Mar 3, 2024

Thanks for debugging this - everything works as expected now after upgrading node!

@orhun orhun changed the title feat: add programmatic api feat(npm): add programmatic API for TypeScript Mar 3, 2024
@orhun orhun merged commit 8b33267 into orhun:main Mar 3, 2024
44 checks passed
@favna favna deleted the feat/npm-programmatic-api branch March 3, 2024 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants