From 7c5dd5600415a29165ef5218fff01f8e5702467f Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Mon, 23 Sep 2024 23:13:33 -0500 Subject: [PATCH 01/12] feat: init node SDK --- js/node-api/Cargo.toml | 43 ++++++++++++++++++++++++++++++++++++++++ js/node-api/package.json | 22 ++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 js/node-api/Cargo.toml create mode 100644 js/node-api/package.json diff --git a/js/node-api/Cargo.toml b/js/node-api/Cargo.toml new file mode 100644 index 000000000..9f871d728 --- /dev/null +++ b/js/node-api/Cargo.toml @@ -0,0 +1,43 @@ +[package] +edition = "2021" +name = "gritql_js" +version = "0.0.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix +napi = { version = "2.16.4", default-features = false, features = [ + "napi8", + "async", +] } +napi-derive = "2.12.2" +anyhow = "1.0.70" +serde_json = "1.0.113" + + +marzano-gritmodule = { path = "../../crates/gritmodule" } +marzano-core = { path = "../../vendor/gritql/crates/core", features = [ + "non_wasm", + "napi", +], default-features = false } +grit-util = { path = "../../vendor/gritql/crates/grit-util", features = [] } +grit-pattern-matcher = { path = "../../vendor/gritql/crates/grit-pattern-matcher", features = [ +] } +marzano-language = { path = "../../vendor/gritql/crates/language", features = [ + "finder", +] } +marzano-util = { path = "../../vendor/gritql/crates/util", features = ["napi"] } +marzano-cli = { path = "../../vendor/gritql/crates/cli", features = [ +], default-features = false } +grit_cloud_client = { path = "../../apps/marzano/grit_cloud_client", features = [ +] } +marzano-auth = { path = "../../vendor/gritql/crates/auth", features = [ +], default-features = true } +marzano_messenger = { path = "../../vendor/gritql/crates/marzano_messenger", features = [ +] } +ai_builtins = { path = "../../apps/marzano/ai_builtins", features = [] } + +[build-dependencies] +napi-build = "2.0.1" diff --git a/js/node-api/package.json b/js/node-api/package.json new file mode 100644 index 000000000..b4d750c9e --- /dev/null +++ b/js/node-api/package.json @@ -0,0 +1,22 @@ +{ + "name": "@getgrit/gritql", + "version": "0.0.0", + "main": "__generated__/index.js", + "types": "__generated__/index.d.ts", + "napi": { + "name": "gritql", + "triples": {} + }, + "license": "UNLICENSED", + "devDependencies": { + "@napi-rs/cli": "^2.18.3" + }, + "engines": { + "node": ">= 10" + }, + "scripts": { + "build": "if [ -z \"$SKIP_BRIDGE\" ]; then RUSTFLAGS=\"-A warnings\" napi build --no-const-enum --platform --release --dts __generated__/index.d.ts --js __generated__/index.js && mv *.node __generated__; fi", + "test": "bun test", + "test:ci": "bun test --run" + } +} From b93f3fde356e3f459bc1aa773689b0bf1f2e0e41 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Mon, 23 Sep 2024 23:17:22 -0500 Subject: [PATCH 02/12] [skip ci] yolo --- js/.gitignore | 182 +++++++++++++++++++++++++++++++++++++++++ js/node-api/build.rs | 5 ++ js/node-api/src/lib.rs | 20 +++++ 3 files changed, 207 insertions(+) create mode 100644 js/.gitignore create mode 100644 js/node-api/build.rs create mode 100644 js/node-api/src/lib.rs diff --git a/js/.gitignore b/js/.gitignore new file mode 100644 index 000000000..1f68e8c58 --- /dev/null +++ b/js/.gitignore @@ -0,0 +1,182 @@ +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +#Added by cargo + +/target +Cargo.lock + +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +*.node diff --git a/js/node-api/build.rs b/js/node-api/build.rs new file mode 100644 index 000000000..9fc236788 --- /dev/null +++ b/js/node-api/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/js/node-api/src/lib.rs b/js/node-api/src/lib.rs new file mode 100644 index 000000000..d6112c8b9 --- /dev/null +++ b/js/node-api/src/lib.rs @@ -0,0 +1,20 @@ +#[macro_use] +extern crate napi_derive; + +use marzano_gritmodule::{ + config::{get_stdlib_modules, DefinitionSource, ResolvedGritDefinition}, + fetcher::ModuleRepo, + resolver::resolve_patterns, + searcher::find_grit_dir_from, +}; +use napi::bindgen_prelude::*; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use anyhow::Context; +use api::SharedGritConfig; + +use marzano_gritmodule::api::{parse_grit_config, read_grit_config}; + +pub use marzano_core::UncompiledPatternBuilder; +pub use search::QueryBuilder; From 72de5ffda67d398b7b0f45d6f47cc8cb762ad250 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Mon, 23 Sep 2024 23:24:54 -0500 Subject: [PATCH 03/12] [skip ci] yolo --- Cargo.lock | 19 ++ Cargo.toml | 1 + js/node-api/Cargo.toml | 22 +- js/node-api/__generated__/index.d.ts | 65 ++++++ js/node-api/__generated__/index.js | 316 +++++++++++++++++++++++++++ js/node-api/src/lib.rs | 11 +- 6 files changed, 405 insertions(+), 29 deletions(-) create mode 100644 js/node-api/__generated__/index.d.ts create mode 100644 js/node-api/__generated__/index.js diff --git a/Cargo.lock b/Cargo.lock index c52c8c5a4..baa81eab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1495,6 +1495,19 @@ name = "grit_cloud_client" version = "0.1.0" source = "git+https://github.com/getgrit/grit_cloud_client.git#7221ad63b697fd2c14e14a1872de602a7b3c135a" +[[package]] +name = "gritql_js" +version = "0.0.0" +dependencies = [ + "anyhow", + "marzano-core", + "marzano-util", + "napi", + "napi-build", + "napi-derive", + "serde_json", +] + [[package]] name = "h2" version = "0.3.24" @@ -2595,6 +2608,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "napi-build" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" + [[package]] name = "napi-derive" version = "2.16.3" diff --git a/Cargo.toml b/Cargo.toml index 8810ff029..6e3fb731d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/wasm-bindings", "crates/marzano_messenger", "crates/cli_bin", + "js/node-api", ] exclude = [ "resources", diff --git a/js/node-api/Cargo.toml b/js/node-api/Cargo.toml index 9f871d728..668442d7d 100644 --- a/js/node-api/Cargo.toml +++ b/js/node-api/Cargo.toml @@ -16,28 +16,12 @@ napi-derive = "2.12.2" anyhow = "1.0.70" serde_json = "1.0.113" - -marzano-gritmodule = { path = "../../crates/gritmodule" } -marzano-core = { path = "../../vendor/gritql/crates/core", features = [ +marzano-util = { path = "../../crates/util", features = ["napi"] } +marzano-core = { path = "../../crates/core", features = [ + "network_requests", "non_wasm", "napi", ], default-features = false } -grit-util = { path = "../../vendor/gritql/crates/grit-util", features = [] } -grit-pattern-matcher = { path = "../../vendor/gritql/crates/grit-pattern-matcher", features = [ -] } -marzano-language = { path = "../../vendor/gritql/crates/language", features = [ - "finder", -] } -marzano-util = { path = "../../vendor/gritql/crates/util", features = ["napi"] } -marzano-cli = { path = "../../vendor/gritql/crates/cli", features = [ -], default-features = false } -grit_cloud_client = { path = "../../apps/marzano/grit_cloud_client", features = [ -] } -marzano-auth = { path = "../../vendor/gritql/crates/auth", features = [ -], default-features = true } -marzano_messenger = { path = "../../vendor/gritql/crates/marzano_messenger", features = [ -] } -ai_builtins = { path = "../../apps/marzano/ai_builtins", features = [] } [build-dependencies] napi-build = "2.0.1" diff --git a/js/node-api/__generated__/index.d.ts b/js/node-api/__generated__/index.d.ts new file mode 100644 index 000000000..4bf85df8b --- /dev/null +++ b/js/node-api/__generated__/index.d.ts @@ -0,0 +1,65 @@ +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +export interface Position { + /** 1-based line number in the source file. */ + line: number + /** 1-based column number in the source file. */ + column: number +} +export interface RangeWithoutByte { + start: Position + end: Position +} +export interface RangePair { + before: RangeWithoutByte + after: RangeWithoutByte +} +export interface FileDiff { + oldPath?: string + newPath?: string + ranges: Array +} +export interface RichFile { + path: string + content: string +} +export class UncompiledPatternBuilder { + static new_snippet(text: string): UncompiledPatternBuilder + /** Filter this pattern to only match instances that contain the other pattern */ + contains(other: UncompiledPatternBuilder): UncompiledPatternBuilder + /** Filter the pattern to only match instances that match a provided callback */ + filter(callback: (arg: ResultBinding) => any): UncompiledPatternBuilder + /** Run the pattern on a list of files and return the number of matching files found */ + runOnFiles(files: Array): Promise + /** + * Apply the query to a single file and return the modified file (if any) + * @param file The file to apply the query to + * @returns The modified file (if it was modified) + */ + runOnFile(file: RichFile): Promise +} +/** + * Resolved bindings provide hooks for JavaScript code to interact with a bit of code that has been matched by a pattern. + * The binding corresponds to a specific piece of text, which can be further filtered against or modified inside a JavaScript callback. + */ +export class ResultBinding { + /** Retrieves the stringified representation of the binding (ie. the actual source code) */ + text(): string + /** If the binding was found in a source file, return the position of the binding. */ + range(): RangeWithoutByte | null + /** Retrieves the absolute file name of the file containing the current binding. */ + filename(): string + /** + * Inserts the provided text after the binding. + * The GritQL engine handles the insertion of the text in transformed output code. + */ + append(text: string): void + /** + * Retrieve a variable's text from the current scope. + * @hidden This API is not stable yet. + */ + findVarText(name: string): string | null +} diff --git a/js/node-api/__generated__/index.js b/js/node-api/__generated__/index.js new file mode 100644 index 000000000..eebd384b6 --- /dev/null +++ b/js/node-api/__generated__/index.js @@ -0,0 +1,316 @@ +/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') + +const { platform, arch } = process + +let nativeBinding = null +let localFileExisted = false +let loadError = null + +function isMusl() { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') + } catch (e) { + return true + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime + } +} + +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'gritql.android-arm64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.android-arm64.node') + } else { + nativeBinding = require('@getgrit/gritql-android-arm64') + } + } catch (e) { + loadError = e + } + break + case 'arm': + localFileExisted = existsSync(join(__dirname, 'gritql.android-arm-eabi.node')) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.android-arm-eabi.node') + } else { + nativeBinding = require('@getgrit/gritql-android-arm-eabi') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Android ${arch}`) + } + break + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'gritql.win32-x64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.win32-x64-msvc.node') + } else { + nativeBinding = require('@getgrit/gritql-win32-x64-msvc') + } + } catch (e) { + loadError = e + } + break + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'gritql.win32-ia32-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.win32-ia32-msvc.node') + } else { + nativeBinding = require('@getgrit/gritql-win32-ia32-msvc') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'gritql.win32-arm64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.win32-arm64-msvc.node') + } else { + nativeBinding = require('@getgrit/gritql-win32-arm64-msvc') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`) + } + break + case 'darwin': + localFileExisted = existsSync(join(__dirname, 'gritql.darwin-universal.node')) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.darwin-universal.node') + } else { + nativeBinding = require('@getgrit/gritql-darwin-universal') + } + break + } catch {} + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'gritql.darwin-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.darwin-x64.node') + } else { + nativeBinding = require('@getgrit/gritql-darwin-x64') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'gritql.darwin-arm64.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.darwin-arm64.node') + } else { + nativeBinding = require('@getgrit/gritql-darwin-arm64') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`) + } + break + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } + localFileExisted = existsSync(join(__dirname, 'gritql.freebsd-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.freebsd-x64.node') + } else { + nativeBinding = require('@getgrit/gritql-freebsd-x64') + } + } catch (e) { + loadError = e + } + break + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-x64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-x64-musl.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-x64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-x64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-x64-gnu.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-x64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-arm64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-arm64-musl.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-arm64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-arm64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-arm64-gnu.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-arm64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-arm-musleabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-arm-musleabihf.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-arm-musleabihf') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-arm-gnueabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-arm-gnueabihf.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-arm-gnueabihf') + } + } catch (e) { + loadError = e + } + } + break + case 'riscv64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-riscv64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-riscv64-musl.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-riscv64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-riscv64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-riscv64-gnu.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-riscv64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 's390x': + localFileExisted = existsSync( + join(__dirname, 'gritql.linux-s390x-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gritql.linux-s390x-gnu.node') + } else { + nativeBinding = require('@getgrit/gritql-linux-s390x-gnu') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`) + } + break + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) +} + +if (!nativeBinding) { + if (loadError) { + throw loadError + } + throw new Error(`Failed to load native binding`) +} + +const { UncompiledPatternBuilder, ResultBinding } = nativeBinding + +module.exports.UncompiledPatternBuilder = UncompiledPatternBuilder +module.exports.ResultBinding = ResultBinding diff --git a/js/node-api/src/lib.rs b/js/node-api/src/lib.rs index d6112c8b9..da579620a 100644 --- a/js/node-api/src/lib.rs +++ b/js/node-api/src/lib.rs @@ -1,20 +1,11 @@ #[macro_use] extern crate napi_derive; -use marzano_gritmodule::{ - config::{get_stdlib_modules, DefinitionSource, ResolvedGritDefinition}, - fetcher::ModuleRepo, - resolver::resolve_patterns, - searcher::find_grit_dir_from, -}; use napi::bindgen_prelude::*; use std::path::{Path, PathBuf}; use std::str::FromStr; use anyhow::Context; -use api::SharedGritConfig; - -use marzano_gritmodule::api::{parse_grit_config, read_grit_config}; +// Exported API pub use marzano_core::UncompiledPatternBuilder; -pub use search::QueryBuilder; From feac0ddbc0c351f7ca4abfd942ed23fa30da92fc Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Mon, 23 Sep 2024 23:26:38 -0500 Subject: [PATCH 04/12] [skip ci] yolo --- js/node-api/test/builder.spec.mts | 162 ++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 js/node-api/test/builder.spec.mts diff --git a/js/node-api/test/builder.spec.mts b/js/node-api/test/builder.spec.mts new file mode 100644 index 000000000..473f4ee82 --- /dev/null +++ b/js/node-api/test/builder.spec.mts @@ -0,0 +1,162 @@ +import { expect, describe, it } from 'bun:test'; + +import { UncompiledPatternBuilder } from '../__generated__/index.js'; + +describe('Grit builder bindings', () => { + it('can save text from the repo', async () => { + const query = UncompiledPatternBuilder.new_snippet('console.log'); + const result = await query.runOnFiles([ + { path: 'test/file1.js', content: 'console.log("hello")' }, + { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + ]); + expect(result).toBe(3); + const result2 = await query.runOnFiles([ + { path: 'test/file1.js', content: 'console.error("hello")' }, + { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + ]); + expect(result2).toBe(2); + }); + + it('can use nested contains', async () => { + const query = UncompiledPatternBuilder.new_snippet('console.log').contains( + UncompiledPatternBuilder.new_snippet('log'), + ); + const result = await query.runOnFiles([ + { path: 'test', content: 'console.log("hello")' }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result).toBe(3); + // This one shoud not match, because there is not a fun inside; + const result2 = await UncompiledPatternBuilder.new_snippet('console.log($_)') + .contains(UncompiledPatternBuilder.new_snippet('fun')) + .runOnFiles([ + { path: 'test', content: 'console.log(food); fun' }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result2).toBe(2); + }); + + it('can use a callback', async () => { + let callbackCalledCounter = { + value: 0, + }; + const query = UncompiledPatternBuilder.new_snippet('console.log').filter((data) => { + console.log('The callback was called'); + callbackCalledCounter.value++; + return true; + }); + const result = await query.runOnFiles([ + { + path: 'test', + content: 'function foo() { console.log("hello"); console.log("world"); } foo();', + }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result).toBe(3); + expect(callbackCalledCounter.value).toBe(2); + }); + + it('can apply a rewrite with a pattern', async () => { + const query = UncompiledPatternBuilder.new_snippet('import $_').filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +<<>> + +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; +<<>> + + +function foo() { + console.log('foo'); +}`); + }); + + it('can filter content correctly in callbacks', async () => { + const query = UncompiledPatternBuilder.new_snippet('import $_') + .filter((arg: any) => { + const text = arg.text(); + if (text.includes('baz')) { + return true; + } + return false; + }) + .filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`); + }); + + it('can find variables inside the context ', async () => { + const query = UncompiledPatternBuilder.new_snippet('import { $items } from "$source"') + .filter((arg: any) => { + const source = arg.findVarText('$source'); + if (source === 'qux') { + return true; + } + return false; + }) + .filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`); + }); +}); From bda39c6c6b3aed4f39a49c108709c7295f0c1eda Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Mon, 23 Sep 2024 23:39:18 -0500 Subject: [PATCH 05/12] [skip ci] yolo --- Cargo.lock | 26 +-- js/node-api/Cargo.toml | 2 +- js/node-api/__generated__/index.d.ts | 61 +----- js/node-api/__generated__/index.js | 113 +++++----- js/node-api/package.json | 4 +- js/node-api/src/lib.rs | 5 + js/node-api/test/builder.spec.mts | 311 ++++++++++++++------------- js/node-api/test/sum.spec.mts | 10 + 8 files changed, 244 insertions(+), 288 deletions(-) create mode 100644 js/node-api/test/sum.spec.mts diff --git a/Cargo.lock b/Cargo.lock index baa81eab5..a1a56dfe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,6 +1433,19 @@ dependencies = [ "regex-syntax 0.8.2", ] +[[package]] +name = "grit-node-api" +version = "0.0.0" +dependencies = [ + "anyhow", + "marzano-core", + "marzano-util", + "napi", + "napi-build", + "napi-derive", + "serde_json", +] + [[package]] name = "grit-pattern-matcher" version = "0.3.0" @@ -1495,19 +1508,6 @@ name = "grit_cloud_client" version = "0.1.0" source = "git+https://github.com/getgrit/grit_cloud_client.git#7221ad63b697fd2c14e14a1872de602a7b3c135a" -[[package]] -name = "gritql_js" -version = "0.0.0" -dependencies = [ - "anyhow", - "marzano-core", - "marzano-util", - "napi", - "napi-build", - "napi-derive", - "serde_json", -] - [[package]] name = "h2" version = "0.3.24" diff --git a/js/node-api/Cargo.toml b/js/node-api/Cargo.toml index 668442d7d..0c2489996 100644 --- a/js/node-api/Cargo.toml +++ b/js/node-api/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "gritql_js" +name = "grit-node-api" version = "0.0.0" [lib] diff --git a/js/node-api/__generated__/index.d.ts b/js/node-api/__generated__/index.d.ts index 4bf85df8b..6d85d51f9 100644 --- a/js/node-api/__generated__/index.d.ts +++ b/js/node-api/__generated__/index.d.ts @@ -3,63 +3,4 @@ /* auto-generated by NAPI-RS */ -export interface Position { - /** 1-based line number in the source file. */ - line: number - /** 1-based column number in the source file. */ - column: number -} -export interface RangeWithoutByte { - start: Position - end: Position -} -export interface RangePair { - before: RangeWithoutByte - after: RangeWithoutByte -} -export interface FileDiff { - oldPath?: string - newPath?: string - ranges: Array -} -export interface RichFile { - path: string - content: string -} -export class UncompiledPatternBuilder { - static new_snippet(text: string): UncompiledPatternBuilder - /** Filter this pattern to only match instances that contain the other pattern */ - contains(other: UncompiledPatternBuilder): UncompiledPatternBuilder - /** Filter the pattern to only match instances that match a provided callback */ - filter(callback: (arg: ResultBinding) => any): UncompiledPatternBuilder - /** Run the pattern on a list of files and return the number of matching files found */ - runOnFiles(files: Array): Promise - /** - * Apply the query to a single file and return the modified file (if any) - * @param file The file to apply the query to - * @returns The modified file (if it was modified) - */ - runOnFile(file: RichFile): Promise -} -/** - * Resolved bindings provide hooks for JavaScript code to interact with a bit of code that has been matched by a pattern. - * The binding corresponds to a specific piece of text, which can be further filtered against or modified inside a JavaScript callback. - */ -export class ResultBinding { - /** Retrieves the stringified representation of the binding (ie. the actual source code) */ - text(): string - /** If the binding was found in a source file, return the position of the binding. */ - range(): RangeWithoutByte | null - /** Retrieves the absolute file name of the file containing the current binding. */ - filename(): string - /** - * Inserts the provided text after the binding. - * The GritQL engine handles the insertion of the text in transformed output code. - */ - append(text: string): void - /** - * Retrieve a variable's text from the current scope. - * @hidden This API is not stable yet. - */ - findVarText(name: string): string | null -} +export function sum(a: number, b: number): number diff --git a/js/node-api/__generated__/index.js b/js/node-api/__generated__/index.js index eebd384b6..36ee02509 100644 --- a/js/node-api/__generated__/index.js +++ b/js/node-api/__generated__/index.js @@ -32,24 +32,24 @@ switch (platform) { case 'android': switch (arch) { case 'arm64': - localFileExisted = existsSync(join(__dirname, 'gritql.android-arm64.node')) + localFileExisted = existsSync(join(__dirname, 'grit-node-api.android-arm64.node')) try { if (localFileExisted) { - nativeBinding = require('./gritql.android-arm64.node') + nativeBinding = require('./grit-node-api.android-arm64.node') } else { - nativeBinding = require('@getgrit/gritql-android-arm64') + nativeBinding = require('@getgrit/node-api-android-arm64') } } catch (e) { loadError = e } break case 'arm': - localFileExisted = existsSync(join(__dirname, 'gritql.android-arm-eabi.node')) + localFileExisted = existsSync(join(__dirname, 'grit-node-api.android-arm-eabi.node')) try { if (localFileExisted) { - nativeBinding = require('./gritql.android-arm-eabi.node') + nativeBinding = require('./grit-node-api.android-arm-eabi.node') } else { - nativeBinding = require('@getgrit/gritql-android-arm-eabi') + nativeBinding = require('@getgrit/node-api-android-arm-eabi') } } catch (e) { loadError = e @@ -63,13 +63,13 @@ switch (platform) { switch (arch) { case 'x64': localFileExisted = existsSync( - join(__dirname, 'gritql.win32-x64-msvc.node') + join(__dirname, 'grit-node-api.win32-x64-msvc.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.win32-x64-msvc.node') + nativeBinding = require('./grit-node-api.win32-x64-msvc.node') } else { - nativeBinding = require('@getgrit/gritql-win32-x64-msvc') + nativeBinding = require('@getgrit/node-api-win32-x64-msvc') } } catch (e) { loadError = e @@ -77,13 +77,13 @@ switch (platform) { break case 'ia32': localFileExisted = existsSync( - join(__dirname, 'gritql.win32-ia32-msvc.node') + join(__dirname, 'grit-node-api.win32-ia32-msvc.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.win32-ia32-msvc.node') + nativeBinding = require('./grit-node-api.win32-ia32-msvc.node') } else { - nativeBinding = require('@getgrit/gritql-win32-ia32-msvc') + nativeBinding = require('@getgrit/node-api-win32-ia32-msvc') } } catch (e) { loadError = e @@ -91,13 +91,13 @@ switch (platform) { break case 'arm64': localFileExisted = existsSync( - join(__dirname, 'gritql.win32-arm64-msvc.node') + join(__dirname, 'grit-node-api.win32-arm64-msvc.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.win32-arm64-msvc.node') + nativeBinding = require('./grit-node-api.win32-arm64-msvc.node') } else { - nativeBinding = require('@getgrit/gritql-win32-arm64-msvc') + nativeBinding = require('@getgrit/node-api-win32-arm64-msvc') } } catch (e) { loadError = e @@ -108,23 +108,23 @@ switch (platform) { } break case 'darwin': - localFileExisted = existsSync(join(__dirname, 'gritql.darwin-universal.node')) + localFileExisted = existsSync(join(__dirname, 'grit-node-api.darwin-universal.node')) try { if (localFileExisted) { - nativeBinding = require('./gritql.darwin-universal.node') + nativeBinding = require('./grit-node-api.darwin-universal.node') } else { - nativeBinding = require('@getgrit/gritql-darwin-universal') + nativeBinding = require('@getgrit/node-api-darwin-universal') } break } catch {} switch (arch) { case 'x64': - localFileExisted = existsSync(join(__dirname, 'gritql.darwin-x64.node')) + localFileExisted = existsSync(join(__dirname, 'grit-node-api.darwin-x64.node')) try { if (localFileExisted) { - nativeBinding = require('./gritql.darwin-x64.node') + nativeBinding = require('./grit-node-api.darwin-x64.node') } else { - nativeBinding = require('@getgrit/gritql-darwin-x64') + nativeBinding = require('@getgrit/node-api-darwin-x64') } } catch (e) { loadError = e @@ -132,13 +132,13 @@ switch (platform) { break case 'arm64': localFileExisted = existsSync( - join(__dirname, 'gritql.darwin-arm64.node') + join(__dirname, 'grit-node-api.darwin-arm64.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.darwin-arm64.node') + nativeBinding = require('./grit-node-api.darwin-arm64.node') } else { - nativeBinding = require('@getgrit/gritql-darwin-arm64') + nativeBinding = require('@getgrit/node-api-darwin-arm64') } } catch (e) { loadError = e @@ -152,12 +152,12 @@ switch (platform) { if (arch !== 'x64') { throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) } - localFileExisted = existsSync(join(__dirname, 'gritql.freebsd-x64.node')) + localFileExisted = existsSync(join(__dirname, 'grit-node-api.freebsd-x64.node')) try { if (localFileExisted) { - nativeBinding = require('./gritql.freebsd-x64.node') + nativeBinding = require('./grit-node-api.freebsd-x64.node') } else { - nativeBinding = require('@getgrit/gritql-freebsd-x64') + nativeBinding = require('@getgrit/node-api-freebsd-x64') } } catch (e) { loadError = e @@ -168,26 +168,26 @@ switch (platform) { case 'x64': if (isMusl()) { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-x64-musl.node') + join(__dirname, 'grit-node-api.linux-x64-musl.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-x64-musl.node') + nativeBinding = require('./grit-node-api.linux-x64-musl.node') } else { - nativeBinding = require('@getgrit/gritql-linux-x64-musl') + nativeBinding = require('@getgrit/node-api-linux-x64-musl') } } catch (e) { loadError = e } } else { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-x64-gnu.node') + join(__dirname, 'grit-node-api.linux-x64-gnu.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-x64-gnu.node') + nativeBinding = require('./grit-node-api.linux-x64-gnu.node') } else { - nativeBinding = require('@getgrit/gritql-linux-x64-gnu') + nativeBinding = require('@getgrit/node-api-linux-x64-gnu') } } catch (e) { loadError = e @@ -197,26 +197,26 @@ switch (platform) { case 'arm64': if (isMusl()) { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-arm64-musl.node') + join(__dirname, 'grit-node-api.linux-arm64-musl.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-arm64-musl.node') + nativeBinding = require('./grit-node-api.linux-arm64-musl.node') } else { - nativeBinding = require('@getgrit/gritql-linux-arm64-musl') + nativeBinding = require('@getgrit/node-api-linux-arm64-musl') } } catch (e) { loadError = e } } else { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-arm64-gnu.node') + join(__dirname, 'grit-node-api.linux-arm64-gnu.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-arm64-gnu.node') + nativeBinding = require('./grit-node-api.linux-arm64-gnu.node') } else { - nativeBinding = require('@getgrit/gritql-linux-arm64-gnu') + nativeBinding = require('@getgrit/node-api-linux-arm64-gnu') } } catch (e) { loadError = e @@ -226,26 +226,26 @@ switch (platform) { case 'arm': if (isMusl()) { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-arm-musleabihf.node') + join(__dirname, 'grit-node-api.linux-arm-musleabihf.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-arm-musleabihf.node') + nativeBinding = require('./grit-node-api.linux-arm-musleabihf.node') } else { - nativeBinding = require('@getgrit/gritql-linux-arm-musleabihf') + nativeBinding = require('@getgrit/node-api-linux-arm-musleabihf') } } catch (e) { loadError = e } } else { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-arm-gnueabihf.node') + join(__dirname, 'grit-node-api.linux-arm-gnueabihf.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-arm-gnueabihf.node') + nativeBinding = require('./grit-node-api.linux-arm-gnueabihf.node') } else { - nativeBinding = require('@getgrit/gritql-linux-arm-gnueabihf') + nativeBinding = require('@getgrit/node-api-linux-arm-gnueabihf') } } catch (e) { loadError = e @@ -255,26 +255,26 @@ switch (platform) { case 'riscv64': if (isMusl()) { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-riscv64-musl.node') + join(__dirname, 'grit-node-api.linux-riscv64-musl.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-riscv64-musl.node') + nativeBinding = require('./grit-node-api.linux-riscv64-musl.node') } else { - nativeBinding = require('@getgrit/gritql-linux-riscv64-musl') + nativeBinding = require('@getgrit/node-api-linux-riscv64-musl') } } catch (e) { loadError = e } } else { localFileExisted = existsSync( - join(__dirname, 'gritql.linux-riscv64-gnu.node') + join(__dirname, 'grit-node-api.linux-riscv64-gnu.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-riscv64-gnu.node') + nativeBinding = require('./grit-node-api.linux-riscv64-gnu.node') } else { - nativeBinding = require('@getgrit/gritql-linux-riscv64-gnu') + nativeBinding = require('@getgrit/node-api-linux-riscv64-gnu') } } catch (e) { loadError = e @@ -283,13 +283,13 @@ switch (platform) { break case 's390x': localFileExisted = existsSync( - join(__dirname, 'gritql.linux-s390x-gnu.node') + join(__dirname, 'grit-node-api.linux-s390x-gnu.node') ) try { if (localFileExisted) { - nativeBinding = require('./gritql.linux-s390x-gnu.node') + nativeBinding = require('./grit-node-api.linux-s390x-gnu.node') } else { - nativeBinding = require('@getgrit/gritql-linux-s390x-gnu') + nativeBinding = require('@getgrit/node-api-linux-s390x-gnu') } } catch (e) { loadError = e @@ -310,7 +310,6 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { UncompiledPatternBuilder, ResultBinding } = nativeBinding +const { sum } = nativeBinding -module.exports.UncompiledPatternBuilder = UncompiledPatternBuilder -module.exports.ResultBinding = ResultBinding +module.exports.sum = sum diff --git a/js/node-api/package.json b/js/node-api/package.json index b4d750c9e..f2d35bb2e 100644 --- a/js/node-api/package.json +++ b/js/node-api/package.json @@ -1,10 +1,10 @@ { - "name": "@getgrit/gritql", + "name": "@getgrit/node-api", "version": "0.0.0", "main": "__generated__/index.js", "types": "__generated__/index.d.ts", "napi": { - "name": "gritql", + "name": "grit-node-api", "triples": {} }, "license": "UNLICENSED", diff --git a/js/node-api/src/lib.rs b/js/node-api/src/lib.rs index da579620a..9a3f0e382 100644 --- a/js/node-api/src/lib.rs +++ b/js/node-api/src/lib.rs @@ -9,3 +9,8 @@ use anyhow::Context; // Exported API pub use marzano_core::UncompiledPatternBuilder; + +#[napi] +pub fn sum(a: i32, b: i32) -> Result { + Ok(a + b) +} diff --git a/js/node-api/test/builder.spec.mts b/js/node-api/test/builder.spec.mts index 473f4ee82..c2bd2ae3a 100644 --- a/js/node-api/test/builder.spec.mts +++ b/js/node-api/test/builder.spec.mts @@ -1,162 +1,163 @@ import { expect, describe, it } from 'bun:test'; -import { UncompiledPatternBuilder } from '../__generated__/index.js'; +const { UncompiledPatternBuilder } = require('../__generated__/grit-node-api.darwin-arm64.node'); describe('Grit builder bindings', () => { - it('can save text from the repo', async () => { - const query = UncompiledPatternBuilder.new_snippet('console.log'); - const result = await query.runOnFiles([ - { path: 'test/file1.js', content: 'console.log("hello")' }, - { path: 'test/file2.js', content: 'throw new Error("oh no")' }, - ]); - expect(result).toBe(3); - const result2 = await query.runOnFiles([ - { path: 'test/file1.js', content: 'console.error("hello")' }, - { path: 'test/file2.js', content: 'throw new Error("oh no")' }, - ]); - expect(result2).toBe(2); + it.skip('can save text from the repo', async () => { + console.log('class', UncompiledPatternBuilder, UncompiledPatternBuilder.new_snippet); + // const thing = new UncompiledPatternBuilder(); + // const query = UncompiledPatternBuilder.new_snippet('console.log'); + // const result = await query.runOnFiles([ + // { path: 'test/file1.js', content: 'console.log("hello")' }, + // { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + // ]); + // expect(result).toBe(3); + // const result2 = await query.runOnFiles([ + // { path: 'test/file1.js', content: 'console.error("hello")' }, + // { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + // ]); + // expect(result2).toBe(2); }); - it('can use nested contains', async () => { - const query = UncompiledPatternBuilder.new_snippet('console.log').contains( - UncompiledPatternBuilder.new_snippet('log'), - ); - const result = await query.runOnFiles([ - { path: 'test', content: 'console.log("hello")' }, - { path: 'not_a_file', content: 'throw new Error("oh no")' }, - ]); - expect(result).toBe(3); - // This one shoud not match, because there is not a fun inside; - const result2 = await UncompiledPatternBuilder.new_snippet('console.log($_)') - .contains(UncompiledPatternBuilder.new_snippet('fun')) - .runOnFiles([ - { path: 'test', content: 'console.log(food); fun' }, - { path: 'not_a_file', content: 'throw new Error("oh no")' }, - ]); - expect(result2).toBe(2); - }); - - it('can use a callback', async () => { - let callbackCalledCounter = { - value: 0, - }; - const query = UncompiledPatternBuilder.new_snippet('console.log').filter((data) => { - console.log('The callback was called'); - callbackCalledCounter.value++; - return true; - }); - const result = await query.runOnFiles([ - { - path: 'test', - content: 'function foo() { console.log("hello"); console.log("world"); } foo();', - }, - { path: 'not_a_file', content: 'throw new Error("oh no")' }, - ]); - expect(result).toBe(3); - expect(callbackCalledCounter.value).toBe(2); - }); - - it('can apply a rewrite with a pattern', async () => { - const query = UncompiledPatternBuilder.new_snippet('import $_').filter((arg: any) => { - arg.append(`<<>>\n`); - return true; - }); - - const modified = await query.runOnFile({ - path: 'test.js', - content: `import { foo } from 'bar'; -import { baz } from 'qux'; -import { quux } from 'corge'; - -function foo() { - console.log('foo'); -}`, - }); - expect(modified).toBeDefined(); - expect(modified!.content).toBe(`import { foo } from 'bar'; -<<>> - -import { baz } from 'qux'; -<<>> - -import { quux } from 'corge'; -<<>> - - -function foo() { - console.log('foo'); -}`); - }); - - it('can filter content correctly in callbacks', async () => { - const query = UncompiledPatternBuilder.new_snippet('import $_') - .filter((arg: any) => { - const text = arg.text(); - if (text.includes('baz')) { - return true; - } - return false; - }) - .filter((arg: any) => { - arg.append(`<<>>\n`); - return true; - }); - - const modified = await query.runOnFile({ - path: 'test.js', - content: `import { foo } from 'bar'; -import { baz } from 'qux'; -import { quux } from 'corge'; - -function foo() { - console.log('foo'); -}`, - }); - expect(modified).toBeDefined(); - expect(modified!.content).toBe(`import { foo } from 'bar'; -import { baz } from 'qux'; -<<>> - -import { quux } from 'corge'; - -function foo() { - console.log('foo'); -}`); - }); - - it('can find variables inside the context ', async () => { - const query = UncompiledPatternBuilder.new_snippet('import { $items } from "$source"') - .filter((arg: any) => { - const source = arg.findVarText('$source'); - if (source === 'qux') { - return true; - } - return false; - }) - .filter((arg: any) => { - arg.append(`<<>>\n`); - return true; - }); - - const modified = await query.runOnFile({ - path: 'test.js', - content: `import { foo } from 'bar'; -import { baz } from 'qux'; -import { quux } from 'corge'; - -function foo() { - console.log('foo'); -}`, - }); - expect(modified).toBeDefined(); - expect(modified!.content).toBe(`import { foo } from 'bar'; -import { baz } from 'qux'; -<<>> - -import { quux } from 'corge'; - -function foo() { - console.log('foo'); -}`); - }); + // it('can use nested contains', async () => { + // const query = UncompiledPatternBuilder.new_snippet('console.log').contains( + // UncompiledPatternBuilder.new_snippet('log'), + // ); + // const result = await query.runOnFiles([ + // { path: 'test', content: 'console.log("hello")' }, + // { path: 'not_a_file', content: 'throw new Error("oh no")' }, + // ]); + // expect(result).toBe(3); + // // This one shoud not match, because there is not a fun inside; + // const result2 = await UncompiledPatternBuilder.new_snippet('console.log($_)') + // .contains(UncompiledPatternBuilder.new_snippet('fun')) + // .runOnFiles([ + // { path: 'test', content: 'console.log(food); fun' }, + // { path: 'not_a_file', content: 'throw new Error("oh no")' }, + // ]); + // expect(result2).toBe(2); + // }); + + // it('can use a callback', async () => { + // let callbackCalledCounter = { + // value: 0, + // }; + // const query = UncompiledPatternBuilder.new_snippet('console.log').filter((data) => { + // console.log('The callback was called'); + // callbackCalledCounter.value++; + // return true; + // }); + // const result = await query.runOnFiles([ + // { + // path: 'test', + // content: 'function foo() { console.log("hello"); console.log("world"); } foo();', + // }, + // { path: 'not_a_file', content: 'throw new Error("oh no")' }, + // ]); + // expect(result).toBe(3); + // expect(callbackCalledCounter.value).toBe(2); + // }); + + // it('can apply a rewrite with a pattern', async () => { + // const query = UncompiledPatternBuilder.new_snippet('import $_').filter((arg: any) => { + // arg.append(`<<>>\n`); + // return true; + // }); + + // const modified = await query.runOnFile({ + // path: 'test.js', + // content: `import { foo } from 'bar'; + // import { baz } from 'qux'; + // import { quux } from 'corge'; + + // function foo() { + // console.log('foo'); + // }`, + // }); + // expect(modified).toBeDefined(); + // expect(modified!.content).toBe(`import { foo } from 'bar'; + // <<>> + + // import { baz } from 'qux'; + // <<>> + + // import { quux } from 'corge'; + // <<>> + + // function foo() { + // console.log('foo'); + // }`); + // }); + + // it('can filter content correctly in callbacks', async () => { + // const query = UncompiledPatternBuilder.new_snippet('import $_') + // .filter((arg: any) => { + // const text = arg.text(); + // if (text.includes('baz')) { + // return true; + // } + // return false; + // }) + // .filter((arg: any) => { + // arg.append(`<<>>\n`); + // return true; + // }); + + // const modified = await query.runOnFile({ + // path: 'test.js', + // content: `import { foo } from 'bar'; + // import { baz } from 'qux'; + // import { quux } from 'corge'; + + // function foo() { + // console.log('foo'); + // }`, + // }); + // expect(modified).toBeDefined(); + // expect(modified!.content).toBe(`import { foo } from 'bar'; + // import { baz } from 'qux'; + // <<>> + + // import { quux } from 'corge'; + + // function foo() { + // console.log('foo'); + // }`); + // }); + + // it('can find variables inside the context ', async () => { + // const query = UncompiledPatternBuilder.new_snippet('import { $items } from "$source"') + // .filter((arg: any) => { + // const source = arg.findVarText('$source'); + // if (source === 'qux') { + // return true; + // } + // return false; + // }) + // .filter((arg: any) => { + // arg.append(`<<>>\n`); + // return true; + // }); + + // const modified = await query.runOnFile({ + // path: 'test.js', + // content: `import { foo } from 'bar'; + // import { baz } from 'qux'; + // import { quux } from 'corge'; + + // function foo() { + // console.log('foo'); + // }`, + // }); + // expect(modified).toBeDefined(); + // expect(modified!.content).toBe(`import { foo } from 'bar'; + // import { baz } from 'qux'; + // <<>> + + // import { quux } from 'corge'; + + // function foo() { + // console.log('foo'); + // }`); + // }); }); diff --git a/js/node-api/test/sum.spec.mts b/js/node-api/test/sum.spec.mts new file mode 100644 index 000000000..539cf7118 --- /dev/null +++ b/js/node-api/test/sum.spec.mts @@ -0,0 +1,10 @@ +import { expect, describe, it } from 'bun:test'; + +import { sum } from '../__generated__/index.js'; + +describe('Node bindings', () => { + it('work with a basic function', async () => { + const result = sum(1, 2); + expect(result).toBe(3); + }); +}); From d0ff16cf24b833a771b7a2e8659dc095f59cb839 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:04:39 -0500 Subject: [PATCH 06/12] [skip ci] yolo --- js/node-api/__generated__/index.d.ts | 62 ++++++++++++++++++++++++++++ js/node-api/__generated__/index.js | 5 ++- js/node-api/src/lib.rs | 7 ++++ js/node-api/test/builder.spec.mts | 2 +- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/js/node-api/__generated__/index.d.ts b/js/node-api/__generated__/index.d.ts index 6d85d51f9..23c4fe1ad 100644 --- a/js/node-api/__generated__/index.d.ts +++ b/js/node-api/__generated__/index.d.ts @@ -3,4 +3,66 @@ /* auto-generated by NAPI-RS */ +export interface Position { + /** 1-based line number in the source file. */ + line: number + /** 1-based column number in the source file. */ + column: number +} +export interface RangeWithoutByte { + start: Position + end: Position +} +export interface RangePair { + before: RangeWithoutByte + after: RangeWithoutByte +} +export interface FileDiff { + oldPath?: string + newPath?: string + ranges: Array +} +export interface RichFile { + path: string + content: string +} export function sum(a: number, b: number): number +/** We need this to make sure the builder is actually exposed to JS */ +export function debugBuilder(builder: UncompiledPatternBuilder): string +export class UncompiledPatternBuilder { + static new_snippet(text: string): UncompiledPatternBuilder + /** Filter this pattern to only match instances that contain the other pattern */ + contains(other: UncompiledPatternBuilder): UncompiledPatternBuilder + /** Filter the pattern to only match instances that match a provided callback */ + filter(callback: (arg: ResultBinding) => any): UncompiledPatternBuilder + /** Run the pattern on a list of files and return the number of matching files found */ + runOnFiles(files: Array): Promise + /** + * Apply the query to a single file and return the modified file (if any) + * @param file The file to apply the query to + * @returns The modified file (if it was modified) + */ + runOnFile(file: RichFile): Promise +} +/** + * Resolved bindings provide hooks for JavaScript code to interact with a bit of code that has been matched by a pattern. + * The binding corresponds to a specific piece of text, which can be further filtered against or modified inside a JavaScript callback. + */ +export class ResultBinding { + /** Retrieves the stringified representation of the binding (ie. the actual source code) */ + text(): string + /** If the binding was found in a source file, return the position of the binding. */ + range(): RangeWithoutByte | null + /** Retrieves the absolute file name of the file containing the current binding. */ + filename(): string + /** + * Inserts the provided text after the binding. + * The GritQL engine handles the insertion of the text in transformed output code. + */ + append(text: string): void + /** + * Retrieve a variable's text from the current scope. + * @hidden This API is not stable yet. + */ + findVarText(name: string): string | null +} diff --git a/js/node-api/__generated__/index.js b/js/node-api/__generated__/index.js index 36ee02509..229b9e72b 100644 --- a/js/node-api/__generated__/index.js +++ b/js/node-api/__generated__/index.js @@ -310,6 +310,9 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { sum } = nativeBinding +const { UncompiledPatternBuilder, ResultBinding, sum, debugBuilder } = nativeBinding +module.exports.UncompiledPatternBuilder = UncompiledPatternBuilder +module.exports.ResultBinding = ResultBinding module.exports.sum = sum +module.exports.debugBuilder = debugBuilder diff --git a/js/node-api/src/lib.rs b/js/node-api/src/lib.rs index 9a3f0e382..3b26bd651 100644 --- a/js/node-api/src/lib.rs +++ b/js/node-api/src/lib.rs @@ -14,3 +14,10 @@ pub use marzano_core::UncompiledPatternBuilder; pub fn sum(a: i32, b: i32) -> Result { Ok(a + b) } + +/// We need this to make sure the builder is actually exposed to JS +#[napi] +pub fn debug_builder(builder: &UncompiledPatternBuilder) -> Result { + let builder_str = format!("{:?}", builder); + Ok(builder_str) +} diff --git a/js/node-api/test/builder.spec.mts b/js/node-api/test/builder.spec.mts index c2bd2ae3a..a123ed99d 100644 --- a/js/node-api/test/builder.spec.mts +++ b/js/node-api/test/builder.spec.mts @@ -3,7 +3,7 @@ import { expect, describe, it } from 'bun:test'; const { UncompiledPatternBuilder } = require('../__generated__/grit-node-api.darwin-arm64.node'); describe('Grit builder bindings', () => { - it.skip('can save text from the repo', async () => { + it('can save text from the repo', async () => { console.log('class', UncompiledPatternBuilder, UncompiledPatternBuilder.new_snippet); // const thing = new UncompiledPatternBuilder(); // const query = UncompiledPatternBuilder.new_snippet('console.log'); From 41847b73cce1b5c3bb6bbe793512926fab08efe5 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:08:57 -0500 Subject: [PATCH 07/12] add default bindings --- js/node-api/Cargo.toml | 1 + js/node-api/test/builder.spec.mts | 310 +++++++++++++++--------------- 2 files changed, 156 insertions(+), 155 deletions(-) diff --git a/js/node-api/Cargo.toml b/js/node-api/Cargo.toml index 0c2489996..7bdc67ad8 100644 --- a/js/node-api/Cargo.toml +++ b/js/node-api/Cargo.toml @@ -21,6 +21,7 @@ marzano-core = { path = "../../crates/core", features = [ "network_requests", "non_wasm", "napi", + "language-parsers", ], default-features = false } [build-dependencies] diff --git a/js/node-api/test/builder.spec.mts b/js/node-api/test/builder.spec.mts index a123ed99d..d28c43fd2 100644 --- a/js/node-api/test/builder.spec.mts +++ b/js/node-api/test/builder.spec.mts @@ -1,163 +1,163 @@ import { expect, describe, it } from 'bun:test'; -const { UncompiledPatternBuilder } = require('../__generated__/grit-node-api.darwin-arm64.node'); +import { UncompiledPatternBuilder } from '../__generated__/index.js'; describe('Grit builder bindings', () => { it('can save text from the repo', async () => { - console.log('class', UncompiledPatternBuilder, UncompiledPatternBuilder.new_snippet); - // const thing = new UncompiledPatternBuilder(); - // const query = UncompiledPatternBuilder.new_snippet('console.log'); - // const result = await query.runOnFiles([ - // { path: 'test/file1.js', content: 'console.log("hello")' }, - // { path: 'test/file2.js', content: 'throw new Error("oh no")' }, - // ]); - // expect(result).toBe(3); - // const result2 = await query.runOnFiles([ - // { path: 'test/file1.js', content: 'console.error("hello")' }, - // { path: 'test/file2.js', content: 'throw new Error("oh no")' }, - // ]); - // expect(result2).toBe(2); + const query = UncompiledPatternBuilder.new_snippet('console.log'); + const result = await query.runOnFiles([ + { path: 'test/file1.js', content: 'console.log("hello")' }, + { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + ]); + console.log('result', result); + expect(result).toBe(1); + const result2 = await query.runOnFiles([ + { path: 'test/file1.js', content: 'console.error("hello")' }, + { path: 'test/file2.js', content: 'throw new Error("oh no")' }, + ]); + expect(result2).toBe(0); }); - // it('can use nested contains', async () => { - // const query = UncompiledPatternBuilder.new_snippet('console.log').contains( - // UncompiledPatternBuilder.new_snippet('log'), - // ); - // const result = await query.runOnFiles([ - // { path: 'test', content: 'console.log("hello")' }, - // { path: 'not_a_file', content: 'throw new Error("oh no")' }, - // ]); - // expect(result).toBe(3); - // // This one shoud not match, because there is not a fun inside; - // const result2 = await UncompiledPatternBuilder.new_snippet('console.log($_)') - // .contains(UncompiledPatternBuilder.new_snippet('fun')) - // .runOnFiles([ - // { path: 'test', content: 'console.log(food); fun' }, - // { path: 'not_a_file', content: 'throw new Error("oh no")' }, - // ]); - // expect(result2).toBe(2); - // }); - - // it('can use a callback', async () => { - // let callbackCalledCounter = { - // value: 0, - // }; - // const query = UncompiledPatternBuilder.new_snippet('console.log').filter((data) => { - // console.log('The callback was called'); - // callbackCalledCounter.value++; - // return true; - // }); - // const result = await query.runOnFiles([ - // { - // path: 'test', - // content: 'function foo() { console.log("hello"); console.log("world"); } foo();', - // }, - // { path: 'not_a_file', content: 'throw new Error("oh no")' }, - // ]); - // expect(result).toBe(3); - // expect(callbackCalledCounter.value).toBe(2); - // }); - - // it('can apply a rewrite with a pattern', async () => { - // const query = UncompiledPatternBuilder.new_snippet('import $_').filter((arg: any) => { - // arg.append(`<<>>\n`); - // return true; - // }); - - // const modified = await query.runOnFile({ - // path: 'test.js', - // content: `import { foo } from 'bar'; - // import { baz } from 'qux'; - // import { quux } from 'corge'; - - // function foo() { - // console.log('foo'); - // }`, - // }); - // expect(modified).toBeDefined(); - // expect(modified!.content).toBe(`import { foo } from 'bar'; - // <<>> - - // import { baz } from 'qux'; - // <<>> - - // import { quux } from 'corge'; - // <<>> - - // function foo() { - // console.log('foo'); - // }`); - // }); - - // it('can filter content correctly in callbacks', async () => { - // const query = UncompiledPatternBuilder.new_snippet('import $_') - // .filter((arg: any) => { - // const text = arg.text(); - // if (text.includes('baz')) { - // return true; - // } - // return false; - // }) - // .filter((arg: any) => { - // arg.append(`<<>>\n`); - // return true; - // }); - - // const modified = await query.runOnFile({ - // path: 'test.js', - // content: `import { foo } from 'bar'; - // import { baz } from 'qux'; - // import { quux } from 'corge'; - - // function foo() { - // console.log('foo'); - // }`, - // }); - // expect(modified).toBeDefined(); - // expect(modified!.content).toBe(`import { foo } from 'bar'; - // import { baz } from 'qux'; - // <<>> - - // import { quux } from 'corge'; - - // function foo() { - // console.log('foo'); - // }`); - // }); - - // it('can find variables inside the context ', async () => { - // const query = UncompiledPatternBuilder.new_snippet('import { $items } from "$source"') - // .filter((arg: any) => { - // const source = arg.findVarText('$source'); - // if (source === 'qux') { - // return true; - // } - // return false; - // }) - // .filter((arg: any) => { - // arg.append(`<<>>\n`); - // return true; - // }); - - // const modified = await query.runOnFile({ - // path: 'test.js', - // content: `import { foo } from 'bar'; - // import { baz } from 'qux'; - // import { quux } from 'corge'; - - // function foo() { - // console.log('foo'); - // }`, - // }); - // expect(modified).toBeDefined(); - // expect(modified!.content).toBe(`import { foo } from 'bar'; - // import { baz } from 'qux'; - // <<>> - - // import { quux } from 'corge'; - - // function foo() { - // console.log('foo'); - // }`); - // }); + it('can use nested contains', async () => { + const query = UncompiledPatternBuilder.new_snippet('console.log').contains( + UncompiledPatternBuilder.new_snippet('log'), + ); + const result = await query.runOnFiles([ + { path: 'test', content: 'console.log("hello")' }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result).toBe(1); + // This one shoud not match, because there is not a fun inside; + const result2 = await UncompiledPatternBuilder.new_snippet('console.log($_)') + .contains(UncompiledPatternBuilder.new_snippet('fun')) + .runOnFiles([ + { path: 'test', content: 'console.log(food); fun' }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result2).toBe(0); + }); + + it('can use a callback', async () => { + let callbackCalledCounter = { + value: 0, + }; + const query = UncompiledPatternBuilder.new_snippet('console.log').filter((data) => { + console.log('The callback was called'); + callbackCalledCounter.value++; + return true; + }); + const result = await query.runOnFiles([ + { + path: 'test', + content: 'function foo() { console.log("hello"); console.log("world"); } foo();', + }, + { path: 'not_a_file', content: 'throw new Error("oh no")' }, + ]); + expect(result).toBe(1); + expect(callbackCalledCounter.value).toBe(2); + }); + + it('can apply a rewrite with a pattern', async () => { + const query = UncompiledPatternBuilder.new_snippet('import $_').filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +<<>> + +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; +<<>> + + +function foo() { + console.log('foo'); +}`); + }); + + it('can filter content correctly in callbacks', async () => { + const query = UncompiledPatternBuilder.new_snippet('import $_') + .filter((arg: any) => { + const text = arg.text(); + if (text.includes('baz')) { + return true; + } + return false; + }) + .filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`); + }); + + it('can find variables inside the context ', async () => { + const query = UncompiledPatternBuilder.new_snippet('import { $items } from "$source"') + .filter((arg: any) => { + const source = arg.findVarText('$source'); + if (source === 'qux') { + return true; + } + return false; + }) + .filter((arg: any) => { + arg.append(`<<>>\n`); + return true; + }); + + const modified = await query.runOnFile({ + path: 'test.js', + content: `import { foo } from 'bar'; +import { baz } from 'qux'; +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`, + }); + expect(modified).toBeDefined(); + expect(modified!.content).toBe(`import { foo } from 'bar'; +import { baz } from 'qux'; +<<>> + +import { quux } from 'corge'; + +function foo() { + console.log('foo'); +}`); + }); }); From 9ba41ccde4b73714657a2120dda882d224b775a7 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:18:28 -0500 Subject: [PATCH 08/12] add test --- .github/workflows/js-sdk.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/js-sdk.yaml diff --git a/.github/workflows/js-sdk.yaml b/.github/workflows/js-sdk.yaml new file mode 100644 index 000000000..e83455049 --- /dev/null +++ b/.github/workflows/js-sdk.yaml @@ -0,0 +1,30 @@ +name: JavaScript SDK + +on: + push: {} + pull_request: + branches: [main] + +jobs: + test: + runs-on: "nscloud-ubuntu-22.04-amd64-4x16" + + steps: + - name: clone code + uses: namespacelabs/nscloud-checkout-action@v4 + with: + submodules: recursive + - name: install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2023-11-16 + override: true + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: build Node API + working-directory: ./js/node-api + run: bun run build + - name: test Node API + working-directory: ./js/node-api + run: bun test From fad98c6b988aef613a47742ec969f623a9d797be Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:20:39 -0500 Subject: [PATCH 09/12] crates please --- .github/workflows/js-sdk.yaml | 3 +-- .github/workflows/main.yaml | 2 ++ .github/workflows/python-tests.yml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/js-sdk.yaml b/.github/workflows/js-sdk.yaml index e83455049..d6ae2ac83 100644 --- a/.github/workflows/js-sdk.yaml +++ b/.github/workflows/js-sdk.yaml @@ -7,8 +7,7 @@ on: jobs: test: - runs-on: "nscloud-ubuntu-22.04-amd64-4x16" - + runs-on: namespace-profile-standard-ubuntu22-amd64 steps: - name: clone code uses: namespacelabs/nscloud-checkout-action@v4 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index a5c8179b7..4e328f150 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -5,6 +5,8 @@ on: branches: [main] pull_request: branches: [main] + paths: + - "crates/**" jobs: test-rust: diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index de79f9354..aff03d5d7 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -12,7 +12,7 @@ on: jobs: test: - runs-on: "nscloud-ubuntu-22.04-amd64-4x16" + runs-on: namespace-profile-standard-ubuntu22-amd64 strategy: matrix: python-version: ["3.10"] From 49855068385aa4b03257ba8c1dfe6f16c0de8812 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:28:15 -0500 Subject: [PATCH 10/12] run better --- .github/workflows/js-sdk.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/js-sdk.yaml b/.github/workflows/js-sdk.yaml index d6ae2ac83..888c0de65 100644 --- a/.github/workflows/js-sdk.yaml +++ b/.github/workflows/js-sdk.yaml @@ -1,7 +1,8 @@ name: JavaScript SDK on: - push: {} + push: + branches: [main] pull_request: branches: [main] From 16387922ea8215eb2f64f2e20ca5bb2c4949dea8 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:29:39 -0500 Subject: [PATCH 11/12] revert --- .github/workflows/js-sdk.yaml | 2 +- .github/workflows/main.yaml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/js-sdk.yaml b/.github/workflows/js-sdk.yaml index 888c0de65..b5dc06b06 100644 --- a/.github/workflows/js-sdk.yaml +++ b/.github/workflows/js-sdk.yaml @@ -11,7 +11,7 @@ jobs: runs-on: namespace-profile-standard-ubuntu22-amd64 steps: - name: clone code - uses: namespacelabs/nscloud-checkout-action@v4 + uses: actions/checkout@v4 with: submodules: recursive - name: install Rust diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 4e328f150..a5c8179b7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -5,8 +5,6 @@ on: branches: [main] pull_request: branches: [main] - paths: - - "crates/**" jobs: test-rust: From a48898f36988b26524e6696ea5e2ed8b59f047a4 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 24 Sep 2024 21:48:49 -0500 Subject: [PATCH 12/12] install first --- .github/workflows/js-sdk.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/js-sdk.yaml b/.github/workflows/js-sdk.yaml index b5dc06b06..be17c9a30 100644 --- a/.github/workflows/js-sdk.yaml +++ b/.github/workflows/js-sdk.yaml @@ -22,6 +22,9 @@ jobs: - uses: oven-sh/setup-bun@v1 with: bun-version: latest + - name: install deps + working-directory: ./js/node-api + run: bun install - name: build Node API working-directory: ./js/node-api run: bun run build