From cde403e5b9e14fc0a11b39f965fedb1d62932c4d Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 14 Jan 2026 17:26:42 +0000 Subject: [PATCH 01/38] Push cache --- .github/workflows/ci.yml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df5ee3d..58823e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,10 +37,10 @@ jobs: fetch-depth: 0 - name: Use Node.js - if: ${{ matrix.os == 'ubuntu-latest' }} uses: actions/setup-node@v4 with: node-version: 22.x + cache: 'npm' - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -48,13 +48,24 @@ jobs: ocaml-compiler: ${{ env.OCAML_COMPILER }} dune-cache: true opam-disable-sandboxing: true + opam-download-cache: true + cache-prefix: v1-${{ runner.arch }} + + - name: Cache opam switch + id: opam-cache + uses: actions/cache@v4 + with: + path: _opam + key: opam-switch-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + restore-keys: | + opam-switch-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- - name: Pin some deps - if: matrix.os != 'windows-latest' + if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os != 'windows-latest' run: make pin - name: Pin some deps (Windows with retry) - if: matrix.os == 'windows-latest' + if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os == 'windows-latest' shell: pwsh run: | $maxRetries = 3 @@ -70,11 +81,11 @@ jobs: if ($LASTEXITCODE -ne 0) { exit 1 } - name: Install deps - if: matrix.os != 'windows-latest' + if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os != 'windows-latest' run: make install - name: Install deps (Windows with retry) - if: matrix.os == 'windows-latest' + if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os == 'windows-latest' shell: pwsh run: | $maxRetries = 3 @@ -138,6 +149,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22.x + cache: 'npm' - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -145,11 +157,24 @@ jobs: ocaml-compiler: ${{ env.OCAML_COMPILER }} dune-cache: true opam-disable-sandboxing: true + opam-download-cache: true + cache-prefix: v1-x64 + + - name: Cache opam switch + id: opam-cache + uses: actions/cache@v4 + with: + path: _opam + key: opam-switch-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + restore-keys: | + opam-switch-Linux-x64-${{ env.OCAML_COMPILER }}- - name: Pin some deps + if: steps.opam-cache.outputs.cache-hit != 'true' run: make pin - name: Install opam deps + if: steps.opam-cache.outputs.cache-hit != 'true' run: make install - name: Install npm deps From ea6b74d4a77ffaf6960f04dc0d801735e5246b02 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 14 Jan 2026 18:37:13 +0000 Subject: [PATCH 02/38] Remove death code to test the cache --- cli/Help.ml | 1 - source/Ast.ml | 1 - 2 files changed, 2 deletions(-) diff --git a/cli/Help.ml b/cli/Help.ml index 332883e..56be093 100644 --- a/cli/Help.ml +++ b/cli/Help.ml @@ -1,5 +1,4 @@ let find_group = Language.find_category -let available_categories = Language.category_names let format_function_doc ~colorize (f : Language.function_info) = let t = Console_style.make ~colorize in diff --git a/source/Ast.ml b/source/Ast.ml index dac6db6..9c0fd5c 100644 --- a/source/Ast.ml +++ b/source/Ast.ml @@ -181,7 +181,6 @@ type fn1_expr = type compiled_regex = { pattern : string; regex : Str.regexp } -let show_compiled_regex r = Printf.sprintf "/%s/" r.pattern let pp_compiled_regex fmt r = Format.fprintf fmt "/%s/" r.pattern type fn2 = From dd1880e5142db4ad17314034b06ffce83d74906b Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 14 Jan 2026 19:25:55 +0000 Subject: [PATCH 03/38] Let's try with something more agresive --- .github/workflows/ci.yml | 112 ++++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58823e2..6e68bf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,23 +42,50 @@ jobs: node-version: 22.x cache: 'npm' + - name: Load opam cache + id: opam-cache + uses: actions/cache/restore@v4 + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 + if: steps.opam-cache.outputs.cache-hit != 'true' with: ocaml-compiler: ${{ env.OCAML_COMPILER }} dune-cache: true opam-disable-sandboxing: true - opam-download-cache: true - cache-prefix: v1-${{ runner.arch }} - - name: Cache opam switch - id: opam-cache - uses: actions/cache@v4 + - name: Get OPAM executable path + if: steps.opam-cache.outputs.cache-hit != 'true' + uses: actions/github-script@v7 with: - path: _opam - key: opam-switch-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - restore-keys: | - opam-switch-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- + script: | + const opam_path = await io.which('opam', true); + console.log('opam executable found: %s', opam_path); + const Fs = require('fs/promises'); + await Fs.writeFile('.opam-path', opam_path, 'utf-8'); + console.log('stored path to .opam-path'); + + - name: Use cached OPAM environment + shell: bash + run: | + echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" + echo "OPAMCOLOR=always" >> "$GITHUB_ENV" + echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" + echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" + echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" + echo "OPAMYES=1" >> "$GITHUB_ENV" + echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" + + OPAM_PATH="$(cat .opam-path)" + chmod +x "$OPAM_PATH" + dirname "$OPAM_PATH" >> "$GITHUB_PATH" - name: Pin some deps if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os != 'windows-latest' @@ -134,6 +161,17 @@ jobs: name: query-json-js path: _build/default/js/js.bc.js + - name: Save opam cache + uses: actions/cache/save@v4 + if: steps.opam-cache.outputs.cache-hit != 'true' + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + publish: if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' needs: build @@ -151,23 +189,50 @@ jobs: node-version: 22.x cache: 'npm' + - name: Load opam cache + id: opam-cache + uses: actions/cache/restore@v4 + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: opam-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 + if: steps.opam-cache.outputs.cache-hit != 'true' with: ocaml-compiler: ${{ env.OCAML_COMPILER }} dune-cache: true opam-disable-sandboxing: true - opam-download-cache: true - cache-prefix: v1-x64 - - name: Cache opam switch - id: opam-cache - uses: actions/cache@v4 + - name: Get OPAM executable path + if: steps.opam-cache.outputs.cache-hit != 'true' + uses: actions/github-script@v7 with: - path: _opam - key: opam-switch-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - restore-keys: | - opam-switch-Linux-x64-${{ env.OCAML_COMPILER }}- + script: | + const opam_path = await io.which('opam', true); + console.log('opam executable found: %s', opam_path); + const Fs = require('fs/promises'); + await Fs.writeFile('.opam-path', opam_path, 'utf-8'); + console.log('stored path to .opam-path'); + + - name: Use cached OPAM environment + shell: bash + run: | + echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" + echo "OPAMCOLOR=always" >> "$GITHUB_ENV" + echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" + echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" + echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" + echo "OPAMYES=1" >> "$GITHUB_ENV" + echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" + + OPAM_PATH="$(cat .opam-path)" + chmod +x "$OPAM_PATH" + dirname "$OPAM_PATH" >> "$GITHUB_PATH" - name: Pin some deps if: steps.opam-cache.outputs.cache-hit != 'true' @@ -183,6 +248,17 @@ jobs: - name: Build run: make build-prod + - name: Save opam cache + uses: actions/cache/save@v4 + if: steps.opam-cache.outputs.cache-hit != 'true' + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: opam-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + # Release to NPM and GitHub (only on tags) - name: Download JavaScript artifact if: startsWith(github.ref, 'refs/tags/') From b71b1c003d9293d7a8436a6fae119d82191a7c5e Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 14 Jan 2026 20:12:55 +0000 Subject: [PATCH 04/38] ci: fix opam cache on windows --- .github/workflows/ci.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e68bf4..9e27c55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,8 @@ jobs: ~/.opam _opam .opam-path + C:\cygwin + C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - name: Use OCaml ${{ env.OCAML_COMPILER }} @@ -72,7 +74,8 @@ jobs: await Fs.writeFile('.opam-path', opam_path, 'utf-8'); console.log('stored path to .opam-path'); - - name: Use cached OPAM environment + - name: Use cached OPAM environment (non-Windows) + if: matrix.os != 'windows-latest' shell: bash run: | echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" @@ -87,6 +90,22 @@ jobs: chmod +x "$OPAM_PATH" dirname "$OPAM_PATH" >> "$GITHUB_PATH" + - name: Use cached OPAM environment (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + Add-Content -Path $env:GITHUB_ENV -Value "OPAMVERBOSE=$env:RUNNER_DEBUG" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMCOLOR=always" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMCONFIRMLEVEL=unsafe-yes" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMERRLOGLEN=0" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMPRECISETRACKING=1" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMYES=1" + Add-Content -Path $env:GITHUB_ENV -Value "OPAMROOT=C:\\.opam" + + $opamPath = Get-Content .opam-path + $opamDir = Split-Path $opamPath + Add-Content -Path $env:GITHUB_PATH -Value $opamDir + - name: Pin some deps if: steps.opam-cache.outputs.cache-hit != 'true' && matrix.os != 'windows-latest' run: make pin @@ -170,6 +189,8 @@ jobs: ~/.opam _opam .opam-path + C:\cygwin + C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} publish: From a8378b2163417f2a04db537e7040aced10062e99 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 14 Jan 2026 21:09:33 +0000 Subject: [PATCH 05/38] Delete unused code to test the CI --- source/Language.ml | 62 --------------------------------------------- source/Language.mli | 3 --- 2 files changed, 65 deletions(-) diff --git a/source/Language.ml b/source/Language.ml index bc91d9c..ba9cafe 100644 --- a/source/Language.ml +++ b/source/Language.ml @@ -1305,56 +1305,6 @@ let all_function_names () = let aliases = List.concat_map (fun (f : function_info) -> f.aliases) funcs in List.sort_uniq Stdlib.String.compare (names @ aliases) -(* Levenshtein distance for fuzzy matching *) -let levenshtein s1 s2 = - let len1 = String.length s1 in - let len2 = String.length s2 in - if len1 = 0 then len2 - else if len2 = 0 then len1 - else - let matrix = Array.make_matrix (len1 + 1) (len2 + 1) 0 in - for i = 0 to len1 do - matrix.(i).(0) <- i - done; - for j = 0 to len2 do - matrix.(0).(j) <- j - done; - for i = 1 to len1 do - for j = 1 to len2 do - let cost = if s1.[i - 1] = s2.[j - 1] then 0 else 1 in - matrix.(i).(j) <- - min - (min (matrix.(i - 1).(j) + 1) (matrix.(i).(j - 1) + 1)) - (matrix.(i - 1).(j - 1) + cost) - done - done; - matrix.(len1).(len2) - -(* Find similar function names for suggestions *) -let suggest_function_name (typo : string) : string list = - let all_names = all_function_names () in - let typo_lower = String.lowercase_ascii typo in - let typo_len = String.length typo in - (* Calculate distance and filter candidates *) - let candidates = - all_names - |> List.map (fun name -> - let name_lower = String.lowercase_ascii name in - let dist = levenshtein typo_lower name_lower in - (name, dist)) - |> List.filter (fun (name, dist) -> - (* Accept if distance is reasonable relative to length *) - let name_len = String.length name in - let max_dist = max 2 (min typo_len name_len / 3) in - dist <= max_dist) - |> List.sort (fun (_, d1) (_, d2) -> compare d1 d2) - |> List.map fst - in - (* Return top 3 suggestions *) - match candidates with - | [] -> [] - | _ -> List.filteri (fun i _ -> i < 3) candidates - let type_name_of_applicable = function | String -> "string" | Array -> "array" @@ -1364,15 +1314,6 @@ let type_name_of_applicable = function | Nil -> "null" | Any -> "any" -let applicable_of_type_name = function - | "string" -> Some String - | "array" -> Some Array - | "object" -> Some Object - | "number" -> Some Number - | "boolean" -> Some Bool - | "null" -> Some Nil - | _ -> None - let functions_for_type type_name = let all = all_functions () in List.filter @@ -1382,9 +1323,6 @@ let functions_for_type type_name = f.applicable_to) all -let function_names_for_type type_name = - functions_for_type type_name |> List.map (fun (f : function_info) -> f.name) - let applicable_of_json_type = function | "string" -> String | "array" -> Array diff --git a/source/Language.mli b/source/Language.mli index c10e313..e478ff2 100644 --- a/source/Language.mli +++ b/source/Language.mli @@ -28,11 +28,8 @@ val find_category : string -> category option val category_names : unit -> string list val all_functions : unit -> function_info list val all_function_names : unit -> string list -val suggest_function_name : string -> string list val functions_for_type : string -> function_info list -val function_names_for_type : string -> string list val type_name_of_applicable : applicable_to -> string -val applicable_of_type_name : string -> applicable_to option val applicable_of_json_type : string -> applicable_to val find_function : string -> function_info option val error_for_missing_arg : string -> Query_error.t From 3bef555ffd2fcc797e22eec1e94c32344da93ef1 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 05:27:26 +0000 Subject: [PATCH 06/38] Delete unused code to test the CI --- source/Lexer.ml | 1 - 1 file changed, 1 deletion(-) diff --git a/source/Lexer.ml b/source/Lexer.ml index 427b233..75a65a2 100644 --- a/source/Lexer.ml +++ b/source/Lexer.ml @@ -8,7 +8,6 @@ let space = [%sedlex.regexp? Plus ('\n' | '\t' | ' ')] let identifier = [%sedlex.regexp? (alphabetic | '_'), Star (alphabetic | digit | '_')] -let not_double_quotes = [%sedlex.regexp? Compl '"'] let comment = [%sedlex.regexp? '#', Star (Compl '\n')] (* Custom pp for Z.t since ppx_deriving can't derive it *) From f245ae72ae8f0c68015b81fb4efeed3ccb4f467c Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 08:44:55 +0000 Subject: [PATCH 07/38] Delete unused code to test the CI --- source/Parse_errors.ml | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/Parse_errors.ml b/source/Parse_errors.ml index 849a03c..10708b3 100644 --- a/source/Parse_errors.ml +++ b/source/Parse_errors.ml @@ -1,8 +1,5 @@ exception Parse_error of string * Lexing.position * Lexing.position exception Rich_parse_error of Query_error.t * Lexing.position * Lexing.position -let raise_error msg start_pos end_pos = - raise (Parse_error (msg, start_pos, end_pos)) - let raise_rich_error err start_pos end_pos = raise (Rich_parse_error (err, start_pos, end_pos)) From 4471c8737ebe47a05a7af5d25902e451d14e79cc Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 08:56:36 +0000 Subject: [PATCH 08/38] ci: add restore-keys fallback for cache resilience --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e27c55..8f22937 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,8 @@ jobs: C:\cygwin C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + restore-keys: | + opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -220,6 +222,8 @@ jobs: _opam .opam-path key: opam-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + restore-keys: | + opam-Linux-x64-${{ env.OCAML_COMPILER }}- - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 From 47723764f797c86967865a70ac5b6a7d00c7683b Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 08:59:47 +0000 Subject: [PATCH 09/38] ci: fix cache key case mismatch (x64 -> X64) --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f22937..9352070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -221,9 +221,7 @@ jobs: ~/.opam _opam .opam-path - key: opam-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - restore-keys: | - opam-Linux-x64-${{ env.OCAML_COMPILER }}- + key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -282,7 +280,7 @@ jobs: ~/.opam _opam .opam-path - key: opam-Linux-x64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} # Release to NPM and GitHub (only on tags) - name: Download JavaScript artifact From e5f6d56e1701e256756fc7d014428d690bee1200 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 09:20:08 +0000 Subject: [PATCH 10/38] ci: merge website deploy into build, simplify release job --- .github/workflows/ci.yml | 216 +++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 124 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9352070..5e9880f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,8 +54,6 @@ jobs: C:\cygwin C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - restore-keys: | - opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -158,7 +156,7 @@ jobs: run: make test - name: Install benchmark tools - if: ${{ matrix.os == 'ubuntu-latest' }} + if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y jq @@ -166,7 +164,7 @@ jobs: sudo dpkg -i hyperfine_1.18.0_amd64.deb - name: Run benchmarks - if: ${{ matrix.os == 'ubuntu-latest' }} + if: matrix.os == 'ubuntu-latest' run: BENCH_JQ=1 MIN_RUNS=5 ./benchmarks/bench.sh - name: Upload artifacts for ${{ matrix.os }}-${{ matrix.arch }} @@ -176,7 +174,7 @@ jobs: path: _build/default/cli/cli.exe - name: Upload JavaScript artifacts for NPM - if: ${{ matrix.os == 'ubuntu-latest' }} + if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v4 with: name: query-json-js @@ -195,10 +193,49 @@ jobs: C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - publish: - if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' + # Website build and deploy (ubuntu-latest only, on main or PR) + - name: Check should_publish + if: matrix.os == 'ubuntu-latest' + id: should_publish + run: | + if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "website=true" >> "$GITHUB_OUTPUT" + echo "website_env=prod" >> "$GITHUB_OUTPUT" + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "website=true" >> "$GITHUB_OUTPUT" + echo "website_env=preview" >> "$GITHUB_OUTPUT" + else + echo "website=false" >> "$GITHUB_OUTPUT" + fi + + - name: Build website + if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website == 'true' + run: make web-build + + - name: Deploy to prod + if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website_env == 'prod' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy ../dist --project-name=query-json + workingDirectory: ./website + + - name: Deploy preview + if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website_env == 'preview' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy ../dist --project-name=query-json --branch=${{ github.head_ref }} + workingDirectory: ./website + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + + # Release job - only runs on tags, after all builds complete + release: + if: startsWith(github.ref, 'refs/tags/') needs: build - name: Publish and Deploy + name: Release runs-on: ubuntu-latest steps: @@ -212,129 +249,49 @@ jobs: node-version: 22.x cache: 'npm' - - name: Load opam cache - id: opam-cache - uses: actions/cache/restore@v4 - with: - path: | - ${{ runner.tool_cache }}/opam - ~/.opam - _opam - .opam-path - key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - - - name: Use OCaml ${{ env.OCAML_COMPILER }} - uses: ocaml/setup-ocaml@v3 - if: steps.opam-cache.outputs.cache-hit != 'true' - with: - ocaml-compiler: ${{ env.OCAML_COMPILER }} - dune-cache: true - opam-disable-sandboxing: true - - - name: Get OPAM executable path - if: steps.opam-cache.outputs.cache-hit != 'true' - uses: actions/github-script@v7 - with: - script: | - const opam_path = await io.which('opam', true); - console.log('opam executable found: %s', opam_path); - const Fs = require('fs/promises'); - await Fs.writeFile('.opam-path', opam_path, 'utf-8'); - console.log('stored path to .opam-path'); - - - name: Use cached OPAM environment - shell: bash - run: | - echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" - echo "OPAMCOLOR=always" >> "$GITHUB_ENV" - echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" - echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" - echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" - echo "OPAMYES=1" >> "$GITHUB_ENV" - echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" - - OPAM_PATH="$(cat .opam-path)" - chmod +x "$OPAM_PATH" - dirname "$OPAM_PATH" >> "$GITHUB_PATH" - - - name: Pin some deps - if: steps.opam-cache.outputs.cache-hit != 'true' - run: make pin - - - name: Install opam deps - if: steps.opam-cache.outputs.cache-hit != 'true' - run: make install - - - name: Install npm deps - run: make npm-install - - - name: Build - run: make build-prod - - - name: Save opam cache - uses: actions/cache/save@v4 - if: steps.opam-cache.outputs.cache-hit != 'true' - with: - path: | - ${{ runner.tool_cache }}/opam - ~/.opam - _opam - .opam-path - key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - - # Release to NPM and GitHub (only on tags) - name: Download JavaScript artifact - if: startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: name: query-json-js path: _release/query-json-js - name: Download linux artifacts - if: startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: name: ubuntu-latest-x64-artifact path: _release/platform-linux-x64 - name: Download macOS x64 artifacts - if: startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: name: macos-15-intel-x64-artifact path: _release/platform-darwin-x64 - name: Download macOS arm64 artifacts - if: startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: name: macos-latest-arm64-artifact path: _release/platform-darwin-arm64 - name: Download windows artifacts - if: startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: name: windows-latest-x64-artifact path: _release/platform-windows-x64 - name: Allow execution of query-json - if: startsWith(github.ref, 'refs/tags/') run: chmod +x _release/platform-linux-x64/cli.exe - name: Get version - if: startsWith(github.ref, 'refs/tags/') id: version run: | VERSION=$(_release/platform-linux-x64/cli.exe --version) echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Generate NPM package - if: startsWith(github.ref, 'refs/tags/') run: node scripts/make-npm-release.js ${{ steps.version.outputs.version }} - name: Check if version exists on NPM - if: startsWith(github.ref, 'refs/tags/') id: npm_check run: | if npm view @davesnx/query-json@${{ steps.version.outputs.version }} version 2>/dev/null; then @@ -346,7 +303,6 @@ jobs: fi - name: Create archives for Github release - if: startsWith(github.ref, 'refs/tags/') run: | cd _release/platform-darwin-x64 mv cli.exe query-json @@ -369,7 +325,7 @@ jobs: cd ../.. - name: Publish to NPM - if: startsWith(github.ref, 'refs/tags/') && steps.npm_check.outputs.should_publish_to_npm == 'true' + if: steps.npm_check.outputs.should_publish_to_npm == 'true' run: | npm config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN npm config set scope "@davesnx" @@ -379,7 +335,6 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} - name: Create Github release - if: startsWith(github.ref, 'refs/tags/') id: create_release uses: ncipollo/release-action@v1.16.0 env: @@ -392,7 +347,6 @@ jobs: makeLatest: true - name: Upload query-json-darwin-x64.zip to Github release - if: startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -403,7 +357,6 @@ jobs: asset_content_type: application/zip - name: Upload query-json-darwin-arm64.zip to Github release - if: startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -414,7 +367,6 @@ jobs: asset_content_type: application/zip - name: Upload query-json-linux-x64.zip to Github release - if: startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -425,7 +377,6 @@ jobs: asset_content_type: application/zip - name: Upload query-json-windows-x64.zip to Github release - if: startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -435,13 +386,56 @@ jobs: asset_name: query-json-windows-x64.zip asset_content_type: application/zip - # Publish to opam (only on tags) + # Publish to opam (needs dune-release) + - name: Load opam cache + id: opam-cache + uses: actions/cache/restore@v4 + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + + - name: Use OCaml ${{ env.OCAML_COMPILER }} + uses: ocaml/setup-ocaml@v3 + if: steps.opam-cache.outputs.cache-hit != 'true' + with: + ocaml-compiler: ${{ env.OCAML_COMPILER }} + dune-cache: true + opam-disable-sandboxing: true + + - name: Get OPAM executable path + if: steps.opam-cache.outputs.cache-hit != 'true' + uses: actions/github-script@v7 + with: + script: | + const opam_path = await io.which('opam', true); + console.log('opam executable found: %s', opam_path); + const Fs = require('fs/promises'); + await Fs.writeFile('.opam-path', opam_path, 'utf-8'); + console.log('stored path to .opam-path'); + + - name: Use cached OPAM environment + shell: bash + run: | + echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" + echo "OPAMCOLOR=always" >> "$GITHUB_ENV" + echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" + echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" + echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" + echo "OPAMYES=1" >> "$GITHUB_ENV" + echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" + + OPAM_PATH="$(cat .opam-path)" + chmod +x "$OPAM_PATH" + dirname "$OPAM_PATH" >> "$GITHUB_PATH" + - name: Install dune-release - if: startsWith(github.ref, 'refs/tags/') run: opam install dune-release -y - name: Create source distribution archive - if: startsWith(github.ref, 'refs/tags/') run: | opam exec -- dune-release distrib -p query-json echo "Files created:" @@ -449,7 +443,6 @@ jobs: ls -la _build/*.tbz || echo "No .tbz files found in _build" - name: Upload source archive to Github release - if: startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -460,7 +453,6 @@ jobs: asset_content_type: application/x-tar - name: Publish to opam - if: startsWith(github.ref, 'refs/tags/') uses: davesnx/dune-release-action@v0.2.1 with: packages: 'query-json' @@ -468,27 +460,3 @@ jobs: github-token: ${{ secrets.GH_TOKEN }} to-opam-repository: true to-github-releases: false - - # Deploy website (only on main or PR) - - name: Build website - if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' - run: make web-build - - - name: Deploy to prod - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy ../dist --project-name=query-json - workingDirectory: ./website - - - name: Deploy preview - if: github.event_name == 'pull_request' - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy ../dist --project-name=query-json --branch=${{ github.head_ref }} - workingDirectory: ./website - gitHubToken: ${{ secrets.GITHUB_TOKEN }} From e4aa86a885d35caebfcde755b9c190a0b88113e5 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 09:29:13 +0000 Subject: [PATCH 11/38] ci: merge into single job with artifact polling --- .github/workflows/ci.yml | 176 ++++++++++++++------------------------- 1 file changed, 64 insertions(+), 112 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e9880f..46c5904 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: name: Build and test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: include: - os: ubuntu-latest @@ -193,27 +194,15 @@ jobs: C:\.opam key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - # Website build and deploy (ubuntu-latest only, on main or PR) - - name: Check should_publish - if: matrix.os == 'ubuntu-latest' - id: should_publish - run: | - if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then - echo "website=true" >> "$GITHUB_OUTPUT" - echo "website_env=prod" >> "$GITHUB_OUTPUT" - elif [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "website=true" >> "$GITHUB_OUTPUT" - echo "website_env=preview" >> "$GITHUB_OUTPUT" - else - echo "website=false" >> "$GITHUB_OUTPUT" - fi - + # ======================================== + # Website deploy (ubuntu-latest, main/PR) + # ======================================== - name: Build website - if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website == 'true' + if: matrix.os == 'ubuntu-latest' && (github.ref == 'refs/heads/main' || github.event_name == 'pull_request') run: make web-build - name: Deploy to prod - if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website_env == 'prod' + if: matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/main' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -222,7 +211,7 @@ jobs: workingDirectory: ./website - name: Deploy preview - if: matrix.os == 'ubuntu-latest' && steps.should_publish.outputs.website_env == 'preview' + if: matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -231,67 +220,66 @@ jobs: workingDirectory: ./website gitHubToken: ${{ secrets.GITHUB_TOKEN }} - # Release job - only runs on tags, after all builds complete - release: - if: startsWith(github.ref, 'refs/tags/') - needs: build - name: Release - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Use Node.js 22.x - uses: actions/setup-node@v4 - with: - node-version: 22.x - cache: 'npm' - - - name: Download JavaScript artifact - uses: actions/download-artifact@v4 - with: - name: query-json-js - path: _release/query-json-js - - - name: Download linux artifacts - uses: actions/download-artifact@v4 - with: - name: ubuntu-latest-x64-artifact - path: _release/platform-linux-x64 - - - name: Download macOS x64 artifacts - uses: actions/download-artifact@v4 - with: - name: macos-15-intel-x64-artifact - path: _release/platform-darwin-x64 - - - name: Download macOS arm64 artifacts - uses: actions/download-artifact@v4 - with: - name: macos-latest-arm64-artifact - path: _release/platform-darwin-arm64 - - - name: Download windows artifacts + # ======================================== + # Release (ubuntu-latest, tags only) + # ======================================== + - name: Wait for other platform artifacts + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Waiting for artifacts from other platforms..." + ARTIFACTS="macos-15-intel-x64-artifact macos-latest-arm64-artifact windows-latest-x64-artifact" + for artifact in $ARTIFACTS; do + echo "Waiting for $artifact..." + for i in {1..60}; do + if gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts | jq -e ".artifacts[] | select(.name == \"$artifact\")" > /dev/null 2>&1; then + echo "✓ $artifact is ready" + break + fi + if [ $i -eq 60 ]; then + echo "✗ Timeout waiting for $artifact" + exit 1 + fi + echo " Attempt $i/60, waiting 10s..." + sleep 10 + done + done + echo "All artifacts ready!" + + - name: Download all platform artifacts + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/download-artifact@v4 with: - name: windows-latest-x64-artifact - path: _release/platform-windows-x64 + path: _release - - name: Allow execution of query-json - run: chmod +x _release/platform-linux-x64/cli.exe + - name: Prepare release files + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') + run: | + # Reorganize downloaded artifacts + mkdir -p _release/platform-linux-x64 + mkdir -p _release/platform-darwin-x64 + mkdir -p _release/platform-darwin-arm64 + mkdir -p _release/platform-windows-x64 + mv _release/ubuntu-latest-x64-artifact/cli.exe _release/platform-linux-x64/ + mv _release/macos-15-intel-x64-artifact/cli.exe _release/platform-darwin-x64/ + mv _release/macos-latest-arm64-artifact/cli.exe _release/platform-darwin-arm64/ + mv _release/windows-latest-x64-artifact/cli.exe _release/platform-windows-x64/ + chmod +x _release/platform-linux-x64/cli.exe - name: Get version + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') id: version run: | VERSION=$(_release/platform-linux-x64/cli.exe --version) echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Generate NPM package + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') run: node scripts/make-npm-release.js ${{ steps.version.outputs.version }} - name: Check if version exists on NPM + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') id: npm_check run: | if npm view @davesnx/query-json@${{ steps.version.outputs.version }} version 2>/dev/null; then @@ -303,6 +291,7 @@ jobs: fi - name: Create archives for Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') run: | cd _release/platform-darwin-x64 mv cli.exe query-json @@ -325,7 +314,7 @@ jobs: cd ../.. - name: Publish to NPM - if: steps.npm_check.outputs.should_publish_to_npm == 'true' + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') && steps.npm_check.outputs.should_publish_to_npm == 'true' run: | npm config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN npm config set scope "@davesnx" @@ -335,6 +324,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} - name: Create Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') id: create_release uses: ncipollo/release-action@v1.16.0 env: @@ -347,6 +337,7 @@ jobs: makeLatest: true - name: Upload query-json-darwin-x64.zip to Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -357,6 +348,7 @@ jobs: asset_content_type: application/zip - name: Upload query-json-darwin-arm64.zip to Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -367,6 +359,7 @@ jobs: asset_content_type: application/zip - name: Upload query-json-linux-x64.zip to Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -377,6 +370,7 @@ jobs: asset_content_type: application/zip - name: Upload query-json-windows-x64.zip to Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -386,56 +380,12 @@ jobs: asset_name: query-json-windows-x64.zip asset_content_type: application/zip - # Publish to opam (needs dune-release) - - name: Load opam cache - id: opam-cache - uses: actions/cache/restore@v4 - with: - path: | - ${{ runner.tool_cache }}/opam - ~/.opam - _opam - .opam-path - key: opam-Linux-X64-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - - - name: Use OCaml ${{ env.OCAML_COMPILER }} - uses: ocaml/setup-ocaml@v3 - if: steps.opam-cache.outputs.cache-hit != 'true' - with: - ocaml-compiler: ${{ env.OCAML_COMPILER }} - dune-cache: true - opam-disable-sandboxing: true - - - name: Get OPAM executable path - if: steps.opam-cache.outputs.cache-hit != 'true' - uses: actions/github-script@v7 - with: - script: | - const opam_path = await io.which('opam', true); - console.log('opam executable found: %s', opam_path); - const Fs = require('fs/promises'); - await Fs.writeFile('.opam-path', opam_path, 'utf-8'); - console.log('stored path to .opam-path'); - - - name: Use cached OPAM environment - shell: bash - run: | - echo "OPAMVERBOSE=$RUNNER_DEBUG" >> "$GITHUB_ENV" - echo "OPAMCOLOR=always" >> "$GITHUB_ENV" - echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" - echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" - echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" - echo "OPAMYES=1" >> "$GITHUB_ENV" - echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" - - OPAM_PATH="$(cat .opam-path)" - chmod +x "$OPAM_PATH" - dirname "$OPAM_PATH" >> "$GITHUB_PATH" - - name: Install dune-release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') run: opam install dune-release -y - name: Create source distribution archive + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') run: | opam exec -- dune-release distrib -p query-json echo "Files created:" @@ -443,6 +393,7 @@ jobs: ls -la _build/*.tbz || echo "No .tbz files found in _build" - name: Upload source archive to Github release + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -453,6 +404,7 @@ jobs: asset_content_type: application/x-tar - name: Publish to opam + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') uses: davesnx/dune-release-action@v0.2.1 with: packages: 'query-json' From 2c4ec652bbb64d55d722b663df292e6c9492556c Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 10:04:39 +0000 Subject: [PATCH 12/38] Delete unused code to test the CI --- source/Query_error.ml | 17 ----------------- source/Query_error.mli | 5 ----- 2 files changed, 22 deletions(-) diff --git a/source/Query_error.ml b/source/Query_error.ml index 328b1ef..e1b48ac 100644 --- a/source/Query_error.ml +++ b/source/Query_error.ml @@ -29,7 +29,6 @@ let with_location ~input ~start_pos ~end_pos err = { err with location = Some { input; start_pos; end_pos } } let with_context ctx err = { err with contexts = ctx :: err.contexts } -let with_suggestion s err = { err with suggestion = Some s } let format_location ~colorize loc = let t = Console_style.make ~colorize in @@ -169,22 +168,6 @@ let missing_argument ~fn_name ?message ~usage ~description ?applicable_to in make ~kind:"missing_argument" ~message:msg ~contexts () -let empty_collection ~operation = - make ~kind:"empty_collection" - ~message:(Printf.sprintf "cannot apply `%s` to empty collection" operation) - () - -let null_access ~key = - make ~kind:"null_access" - ~message:(Printf.sprintf "cannot access key `%s` on null" key) - ~suggestion:"check if value exists before accessing" () - -let index_out_of_bounds ~index ~length = - make ~kind:"index_out_of_bounds" - ~message: - (Printf.sprintf "index `%d` out of bounds (length: %d)" index length) - ~suggestion:"use `.[index]?` for optional access" () - let parse_error ~message ~input ~start_pos ~end_pos = make ~kind:"parse_error" ~message ~location:{ input; start_pos; end_pos } () diff --git a/source/Query_error.mli b/source/Query_error.mli index a0ce9e1..5a713b1 100644 --- a/source/Query_error.mli +++ b/source/Query_error.mli @@ -20,7 +20,6 @@ type t = { val with_location : input:string -> start_pos:int -> end_pos:int -> t -> t val with_context : context -> t -> t -val with_suggestion : string -> t -> t val format : colorize:bool -> t -> string (** Format error with colors and Rust-style layout *) @@ -64,10 +63,6 @@ val missing_argument : unit -> t -val empty_collection : operation:string -> t -val null_access : key:string -> t -val index_out_of_bounds : index:int -> length:int -> t - val parse_error : message:string -> input:string -> start_pos:int -> end_pos:int -> t From c552832460ae35cab027952e14b6d28670b4e43a Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 11:47:02 +0000 Subject: [PATCH 13/38] Delete unused code to test the CI --- source/console-style/Console_style.ml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/source/console-style/Console_style.ml b/source/console-style/Console_style.ml index 9e32dd4..99e1621 100644 --- a/source/console-style/Console_style.ml +++ b/source/console-style/Console_style.ml @@ -2,7 +2,6 @@ open Ansi let indent n = String.make (n * 1) ' ' let enter n = String.make n '\n' -let double_quotes str = "\"" ^ str ^ "\"" let single_quotes str = "'" ^ str ^ "'" type t = { @@ -31,30 +30,6 @@ let make ~colorize = styled; } -let bold ~colorize text = - if colorize then Ansi.styled ~reset:true ~bold:true text else text - -let red ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.red text else text - -let green ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.green text else text - -let yellow ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.yellow text else text - -let blue ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.blue text else text - -let gray ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.bright_black text else text - -let cyan ~colorize text = - if colorize then Ansi.styled ~reset:true ~fg:Color.cyan text else text - -let styled ~colorize ?fg ?bold text = - if colorize then Ansi.styled ~reset:true ?fg ?bold text else text - module Buffer = struct let green ~colorize buf = if colorize then Stdlib.Buffer.add_string buf "\027[32m" From 76a13cb7b4b7e935ae87ba118c9dd0ffa35cc211 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 13:15:04 +0000 Subject: [PATCH 14/38] Only run CI on pr and main --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46c5904..a8d99d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - '**' + - 'main' tags: - '*' pull_request: From 7c6564cb13dc9c69a559f2ee8975157a3a9668ea Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 13:21:49 +0000 Subject: [PATCH 15/38] Use matrix.os for cache key --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8d99d3..eec8d50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -192,7 +192,7 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} # ======================================== # Website deploy (ubuntu-latest, main/PR) From fdfd92170a182e1a7bd15ef2590b3833201fd66a Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 14:00:13 +0000 Subject: [PATCH 16/38] Delete unused code to test the CI --- source/jotason/Common.ml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/jotason/Common.ml b/source/jotason/Common.ml index 989d075..176934a 100644 --- a/source/jotason/Common.ml +++ b/source/jotason/Common.ml @@ -29,11 +29,6 @@ let init_lexer ?buf ?fname ?(lnum = 1) () = let buf = match buf with None -> Buffer.create 256 | Some buf -> buf in { buf; lnum; bol = 0; fname } -(** Check if a JSON value is truthy (jq semantics: false and null are falsy, - everything else is truthy) *) -let is_truthy (json : t) : bool = - match json with `Bool false | `Null -> false | _ -> true - (** Get numeric value as float for comparison, None for non-numeric types *) let to_float (json : t) : float option = match json with From f91024f2588fc03d5a178c153cdda037575947f3 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 14:18:36 +0000 Subject: [PATCH 17/38] ci: fix cache key mismatch (matrix.* vs runner.*) --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eec8d50..f80a04f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,10 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + restore-keys: | + opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- + opam-${{ runner.os }}-${{ runner.arch }}- - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -192,7 +195,7 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} # ======================================== # Website deploy (ubuntu-latest, main/PR) From 5f0577cd95d1cec44ff5d9b86d50de0710b68df3 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 15:26:59 +0000 Subject: [PATCH 18/38] Fix keys to be matrix --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f80a04f..eec8d50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,10 +54,7 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - restore-keys: | - opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}- - opam-${{ runner.os }}-${{ runner.arch }}- + key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} - name: Use OCaml ${{ env.OCAML_COMPILER }} uses: ocaml/setup-ocaml@v3 @@ -195,7 +192,7 @@ jobs: .opam-path C:\cygwin C:\.opam - key: opam-${{ runner.os }}-${{ runner.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} + key: opam-${{ matrix.os }}-${{ matrix.arch }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'Makefile') }} # ======================================== # Website deploy (ubuntu-latest, main/PR) From ace74623821c9e49013c060bf26f57f31b922ef2 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 15:30:46 +0000 Subject: [PATCH 19/38] Dont upload unless ubuntu-latest --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eec8d50..1121fd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,6 +169,7 @@ jobs: run: BENCH_JQ=1 MIN_RUNS=5 ./benchmarks/bench.sh - name: Upload artifacts for ${{ matrix.os }}-${{ matrix.arch }} + if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v4 with: name: ${{ matrix.os }}-${{ matrix.arch }}-artifact From c4a77ff51614a196f540478545b7286587e89789 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 17:29:11 +0000 Subject: [PATCH 20/38] Add changes entry --- CHANGES.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 06071d0..1f70464 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Unreleased +## 1.0.0~beta-1 + - Rename functions for clarity - Deprecate `..` syntax, use `descend` - Improve REPL completions @@ -10,7 +12,6 @@ - Huge integers use arbitrary precision (`zarith`) for values beyond Int64 - `1152921504606846976 + 1` now correctly returns `1152921504606846977` (jq returns `1152921504606847000`) - Removed `Intlit`/`Floatlit`/`Stringlit` variants (string representations no longer needed) - - [BREAKING CHANGE] **Stricter null handling**: Operations on null now error instead of silently propagating - `null + 1` errors instead of returning `1` - `.missing.key` errors instead of returning `null` @@ -41,22 +42,19 @@ - [FEATURE] **Interactive REPL with autocomplete**: `query-json --repl data.json` - Context-aware suggestions based on data type - Tab completion for functions and keys + - Supports stdin input: `cat data.json | query-json --repl` - [FEATURE] **Built-in function reference**: `query-json --functions [category]` - [FEATURE] **`--null-input` / `-n` flag**: Run filter with `null` as input, useful for constructing JSON or as a calculator - [FEATURE] **Helpful error messages** with context, hints, and suggestions - Key not found shows available keys and suggests `?` - Type mismatch shows expected vs found types - Missing argument shows usage and examples - - [FEATURE] Functions added - **Traversal**: `descend`, `dive`, `find_all(expr)`, `find_first(expr)`, `paths_to(expr)` - **Collection**: `filter(expr)` (alias for `map(select(expr))`), `flat_map(expr)`, `find(expr)`, `some(expr)`, `pluck(expr)`, `partition(expr)` - **Predicates**: `is_empty`, `is_blank` - **Control flow**: `assert(cond)`, `assert(cond; msg)`, `raise(kind; msg)` - -### Improvements - -- [] Remove easy-format dependency +- Remove easy-format dependency - Add `=` assignment operator (`.a = .b`, `(.a, .b) = expr`) - Fix `|=` to create paths that don't exist (`.a |= 42` on `null` creates `{"a": 42}`) - Fix `|=` to take first value when transform produces multiple results From 9f4f7d714fdfcfde78f6056c6ff2ebeadccb4e6f Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 17:50:07 +0000 Subject: [PATCH 21/38] Remove dune-cache --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1121fd2..da361fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: if: steps.opam-cache.outputs.cache-hit != 'true' with: ocaml-compiler: ${{ env.OCAML_COMPILER }} - dune-cache: true + dune-cache: false opam-disable-sandboxing: true - name: Get OPAM executable path From 657ae66e793be66f802dca98cc5cbc6caf1ef564 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 17:50:57 +0000 Subject: [PATCH 22/38] Revert "Remove dune-cache" This reverts commit 9078c528f5fa4ec5fe4a2274a38c152176726201. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da361fb..1121fd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: if: steps.opam-cache.outputs.cache-hit != 'true' with: ocaml-compiler: ${{ env.OCAML_COMPILER }} - dune-cache: false + dune-cache: true opam-disable-sandboxing: true - name: Get OPAM executable path From 884ede4295fb12d6c64eaba4fc7e213020853e3f Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 15 Jan 2026 18:29:30 +0000 Subject: [PATCH 23/38] Make sure functions is a separate page for the SPA --- website/App.mlx | 85 +++++++++++++++++++++++++++------------------- website/Router.ml | 8 +++++ website/Router.mli | 2 ++ website/web/Web.ml | 10 ++++++ 4 files changed, 71 insertions(+), 34 deletions(-) diff --git a/website/App.mlx b/website/App.mlx index ad8b1ba..c2c909b 100644 --- a/website/App.mlx +++ b/website/App.mlx @@ -35,9 +35,12 @@ let initial_json = } |} -type page = Playground | FunctionsPage +type page = Playground | Functions -type t = { +let path_of_page = function Playground -> "/" | Functions -> "/functions" +let navigate_to page = Router.navigate (path_of_page page) + +type state = { query : string; json : string option; copied : bool; @@ -66,9 +69,9 @@ module Query_params = struct let decode json = of_json json let encode t = to_json t - let toString t = t |> encode |> Melange_json.to_string - let fromString str = str |> Melange_json.of_string |> decode - let toHash state = state |> toString |> Base64.encode + let to_string t = t |> encode |> Melange_json.to_string + let from_string str = str |> Melange_json.of_string |> decode + let to_hash state = state |> to_string |> Base64.encode end module Option = struct @@ -106,11 +109,9 @@ module Clipboard = struct [@@mel.scope "navigator", "clipboard"] end -module PlaygroundPage = struct - let[@react.component] make ~state ~dispatch ~is_ready ~on_navigate_functions = - let on_query_change = fun value -> dispatch (Update_query value) in - let on_json_change = fun value -> dispatch (Update_json value) in - +module Playground_page = struct + let[@react.component] make ~state ~is_ready ~update_query ~update_json + ~set_copied ~set_modal_open ~on_navigate_functions = let output = React.useMemo3 (fun () -> @@ -126,23 +127,18 @@ module PlaygroundPage = struct React.useCallback2 (fun _ -> let hash = - Query_params.toHash { query = state.query; json = state.json } + Query_params.to_hash { query = state.query; json = state.json } in match hash with | Some hash -> - let url = - Web.window |> Web.Window.location |> Web.Location.origin - in - let fullUrl = url ^ "#" ^ hash in + let fullUrl = Router.get_origin () ^ "#" ^ hash in Router.set_hash hash; let _promise = Clipboard.write_text fullUrl |> Js.Promise.then_ (fun () -> - dispatch (Set_copied true); + set_copied true; let _timeout = - Js.Global.setTimeout - ~f:(fun () -> dispatch (Set_copied false)) - 2000 + Js.Global.setTimeout ~f:(fun () -> set_copied false) 2000 in Js.Promise.resolve ()) |> Js.Promise.catch (fun _err -> @@ -154,12 +150,12 @@ module PlaygroundPage = struct (state.query, state.json) in - let on_examples_click = fun _ -> dispatch (Set_modal_open true) in - let on_close_modal = fun () -> dispatch (Set_modal_open false) in + let on_examples_click = fun _ -> set_modal_open true in + let on_close_modal = fun () -> set_modal_open false in let on_select_example = fun query json -> - dispatch (Update_query query); - dispatch (Update_json json) + update_query query; + update_json json in
@@ -194,11 +190,13 @@ module PlaygroundPage = struct