Skip to content

Add N-API subproject to run node-semver test eventually #13

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ concurrency:
cancel-in-progress: true

jobs:
ubuntu-build:
macos-latest-build:
runs-on: macos-latest
steps:
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
Expand All @@ -33,3 +33,27 @@ jobs:
run: ctest --output-on-failure --test-dir build
- name: Run default benchmark
run: ./build/benchmarks/benchmark

- uses: actions/setup-node@v4
with:
node-version: lts/*

- name: Get the Yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(cd napi && corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
shell: bash

- uses: actions/cache@v4
with:
path: ${{steps.yarn-cache-dir-path.outputs.dir}}
key: ${{runner.os}}-yarn-${{hashFiles('**/yarn.lock')}}
restore-keys: |
${{runner.os}}-yarn-

- run: cd napi && corepack yarn install --immutable

- name: Build N-API node-semver port
run: cd napi && corepack yarn build

- name: Run node-semver tests
run: cd napi && corepack yarn test
3 changes: 3 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
YARN_ENABLE_GLOBAL_CACHE: false

jobs:
ubuntu-build:
runs-on: ubuntu-24.04
Expand Down
8 changes: 8 additions & 0 deletions napi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build/
node_modules/
tmp/
src/
.nyc_output/

.yarn/
!.yarn/patches
3 changes: 3 additions & 0 deletions napi/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
compressionLevel: mixed

nodeLinker: node-modules
69 changes: 69 additions & 0 deletions napi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.10)
cmake_policy(SET CMP0042 NEW)
set (CMAKE_CXX_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)

include(ExternalProject)

project (node-version-weaver)

include_directories(${CMAKE_SOURCE_DIR}/node_modules/node-addon-api)
include_directories(${CMAKE_JS_INC})

file(GLOB SOURCE_FILES "node-semver.cc")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})

# Define NAPI_VERSION
add_definitions(-DNAPI_VERSION=9)

ExternalProject_Add(version_weaver
SOURCE_DIR ${CMAKE_SOURCE_DIR}/../
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/version_weaver
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
add_dependencies(${PROJECT_NAME} version_weaver)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/version_weaver/include)
target_link_libraries(${PROJECT_NAME} ${CMAKE_CURRENT_BINARY_DIR}/version_weaver/lib/libversion_weaver.a)

function (add_custom_command_with_target _targetName _output firstDep)
add_custom_command(
OUTPUT ${_output}
COMMAND sed "s/__FUNCTION__/${FUNCTION}/" ${firstDep} > ${_output}
DEPENDS ${firstDep} ${ARGN}
)
add_custom_target(${_targetName} ALL DEPENDS ${_output})
endfunction()

set(FUNCTIONS coerce parse valid)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/functions
COMMAND mkdir ${CMAKE_BINARY_DIR}/functions
VERBATIM
)
foreach(FUNCTION ${FUNCTIONS})
add_custom_command_with_target(${FUNCTION} ${CMAKE_BINARY_DIR}/functions/${FUNCTION}.js ${CMAKE_SOURCE_DIR}/templates/functions.template ${CMAKE_BINARY_DIR}/functions)
endforeach()

set(NODE_SEMVER_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/node-semver)
ExternalProject_Add(node-semver
GIT_REPOSITORY https://github.com/npm/node-semver.git
GIT_TAG v7.6.3
GIT_SHALLOW true
PREFIX ${NODE_SEMVER_PREFIX}
CONFIGURE_COMMAND true
BUILD_COMMAND true
INSTALL_COMMAND true
)

add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/test
COMMAND rm -rf ${CMAKE_BINARY_DIR}/test && mv ${NODE_SEMVER_PREFIX}/src/node-semver/test ${CMAKE_BINARY_DIR}/test
DEPENDS node-semver
COMMENT "Copying node-semver test directory"
)

add_custom_target(copy_node_semver_test ALL DEPENDS ${CMAKE_BINARY_DIR}/test)
add_dependencies(${PROJECT_NAME} copy_node_semver_test)

48 changes: 48 additions & 0 deletions napi/compare_test_status.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
import { describe, it } from "node:test";
import { open, opendir, mkdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const shouldOverwrite = process.argv[2] === '--overwrite';

const actualDir = fileURLToPath(
new URL("./tap_output/build/test/", import.meta.url),
);
const expectedDir = fileURLToPath(
new URL("./tap_output/test/", import.meta.url),
);
// Remove test duration and line numbers that might not be stable across versions of Node.js.
const okNotOKPattern = /^\s*(not )?ok \d+ - /;
const lineEnding = /(# time=.+s)?\r?\n/

describe("compare test results", { concurrency: true }, async () => {
for await (const dirent of await opendir(actualDir, { recursive: true })) {
if (dirent.isDirectory() || !dirent.name.endsWith(".tap")) continue;
const actualResultPath = path.join(dirent.parentPath, dirent.name);
const testDirSubpath = dirent.parentPath.slice(actualDir.length);
const expectedResultDir = path.join(expectedDir, testDirSubpath);
const expectedResultPath = path.join(expectedResultDir, dirent.name);
it(testDirSubpath + path.sep + dirent.name.slice(0, -4), async (t) => {
await mkdir(expectedResultDir, { recursive: true });
const files = await Promise.all([
open(actualResultPath, "r"),
open(expectedResultPath, shouldOverwrite ? "w" : "r"),
]);
try {
const [actualRawResult, expectedResult] =
shouldOverwrite ?
[await files[0].readFile("utf-8")]
: await Promise.all(files.map(async (f) => f.readFile("utf-8")));
const actualResult = actualRawResult.split(lineEnding).filter(l=>okNotOKPattern.test(l)).join('\n')
if (shouldOverwrite) {
await files[1].writeFile(actualResult);
} else {
t.assert.strictEqual(actualResult, expectedResult, 'use --overwrite to update the snapshot');
}
} finally {
await Promise.all(files.map((f) => f.close()));
}
});
}
});
24 changes: 24 additions & 0 deletions napi/node-semver.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <napi.h>
#include "version_weaver.h"

Napi::Value CoerceWrapped(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

if (info.Length() < 1 || !info[0].IsString()) {
return env.Null();
}

std::string arg0 = info[0].As<Napi::String>().Utf8Value();
std::optional<std::string> result = version_weaver::coerce(arg0);

return result.has_value() ? Napi::String::New(env, result.value())
: env.Null();
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "coerce"),
Napi::Function::New(env, CoerceWrapped));
return exports;
}

NODE_API_MODULE(myaddon, Init)
17 changes: 17 additions & 0 deletions napi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add package-lock and use npm? Yarn might be hard to maintain in the long term.

"name": "node-version-weaver",
"description": "N-API port of node-semver",
"dependencies": {
"bindings": "~1.5.0",
"node-addon-api": "^8.3.0"
},
"scripts": {
"build": "cmake-js --prefer-make compile",
"test": "tap build/test -d./tap_output --no-coverage || true && node compare_test_status.mjs"
},
"devDependencies": {
"cmake-js": "^7.3.0",
"tap": "^16.0.0"
},
"packageManager": "[email protected]+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728"
}
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
18 changes: 18 additions & 0 deletions napi/tap_output/test/functions/coerce.js.tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ok 1 - coerce(null) should be null
ok 2 - coerce([object Object]) should be null
ok 3 - coerce(function () { return '1.2.3' }) should be null
ok 4 - coerce() should be null
ok 5 - coerce(.) should be null
ok 6 - coerce(version one) should be null
not ok 7 - coerce(9999999999999999) should be null
not ok 8 - coerce(11111111111111111) should be null
not ok 9 - coerce(a9999999999999999) should be null
not ok 10 - coerce(a11111111111111111) should be null
not ok 11 - coerce(9999999999999999a) should be null
not ok 12 - coerce(11111111111111111a) should be null
not ok 13 - coerce(9999999999999999.4.7.4) should be null
not ok 14 - coerce(9999999999999999.2222222222222222.3333333333333333) should be null
not ok 15 - coerce(1111111111111111.9999999999999999.3333333333333333) should be null
not ok 16 - coerce(1111111111111111.2222222222222222.9999999999999999) should be null
not ok 17 - parse is not a function
not ok 1 - coerce tests
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
8 changes: 8 additions & 0 deletions napi/tap_output/test/internal/debug.js.tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
not ok 1 - success exit status
ok 2 - no signal
not ok 3 - got no output
not ok 1 - without env set
not ok 1 - success exit status
ok 2 - no signal
not ok 3 - got expected output
not ok 2 - with env set
Empty file.
Empty file.
Empty file.
Empty file.
Empty file added napi/tap_output/test/map.js.tap
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions napi/templates/functions.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
'use strict';
module.exports = require("bindings")("node-version-weaver").__FUNCTION__;
Loading