From 5929585d6c75f326d0ec76723300f341cd342671 Mon Sep 17 00:00:00 2001 From: Slim B Date: Sat, 14 Feb 2026 09:47:16 +0300 Subject: [PATCH] Fix josh-proxy rejecting stored filter path with slash Josh-proxy's parser treats "/" in :+ paths as a filter separator, so :+.josh-filters/backend fails. Use flat naming at repo root: .josh-filter-.josh referenced as :+.josh-filter-. Co-Authored-By: Claude Opus 4.6 --- bin/josh-sync | 2 +- docs/guide.md | 10 ++++++---- lib/config.sh | 14 +++++++------- schema/config-schema.json | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/bin/josh-sync b/bin/josh-sync index 898defe..7cd14c6 100755 --- a/bin/josh-sync +++ b/bin/josh-sync @@ -431,7 +431,7 @@ cmd_preflight() { echo "4. Exclude filter files" while IFS= read -r target_name; do - local filter_file=".josh-filters/${target_name}.josh" + local filter_file=".josh-filter-${target_name}.josh" if [ -f "$filter_file" ]; then if git ls-files --error-unmatch "$filter_file" >/dev/null 2>&1; then pass "${filter_file} exists and is tracked" diff --git a/docs/guide.md b/docs/guide.md index a663978..664327a 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -541,7 +541,7 @@ targets: ### How it works -When `exclude` is present, josh-sync generates a `.josh-filters/.josh` file containing a [josh stored filter](https://josh-project.github.io/josh/reference/filters.html) with `:exclude` clauses: +When `exclude` is present, josh-sync generates a `.josh-filter-.josh` file at the monorepo root containing a [josh stored filter](https://josh-project.github.io/josh/reference/filters.html) with `:exclude` clauses: ``` :/services/billing:exclude[ @@ -551,6 +551,8 @@ When `exclude` is present, josh-sync generates a `.josh-filters/.josh` f ] ``` +The file is referenced in the josh-proxy URL as `:+.josh-filter-` (flat naming — josh-proxy's parser treats `/` in `:+` paths as a filter separator, so subdirectory paths don't work). + Josh-proxy reads this file from the monorepo and applies the filter at the transport layer. This means: - **Forward sync**: the filtered clone already excludes the files - **Reverse sync**: pushes through josh also respect the exclusion @@ -572,11 +574,11 @@ Josh uses `::` patterns inside `:exclude[...]`: ### Setup 1. Add `exclude` to the target in `.josh-sync.yml` -2. Run `josh-sync preflight` — this generates `.josh-filters/.josh` -3. Commit the generated file: `git add .josh-filters/ && git commit` +2. Run `josh-sync preflight` — this generates `.josh-filter-.josh` +3. Commit the generated file: `git add .josh-filter-*.josh && git commit` 4. Forward sync will now exclude the specified files -If you change the `exclude` list, re-run `preflight` and commit the updated `.josh-filters/` file. +If you change the `exclude` list, re-run `preflight` and commit the updated `.josh-filter-*.josh` file. ## Adding a New Target diff --git a/lib/config.sh b/lib/config.sh index 2b1c017..fe22fdc 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -8,9 +8,11 @@ # Requires: lib/core.sh sourced first, yq and jq on PATH # ─── Josh Filter File Generation ────────────────────────────────── -# Generates .josh-filters/.josh for targets with exclude patterns. +# Generates .josh-filter-.josh for targets with exclude patterns. # These files must be committed to the monorepo — josh-proxy reads them # from the repo at clone time via the :+ stored filter syntax. +# Files are at the repo root (flat naming) because josh-proxy's parser +# treats "/" in :+ paths as a filter separator. _generate_josh_filters() { local has_excludes @@ -20,8 +22,6 @@ _generate_josh_filters() { return fi - mkdir -p .josh-filters - local target_name subfolder exclude_patterns filter_content while IFS= read -r target_name; do subfolder=$(echo "$JOSH_SYNC_TARGETS" | jq -r --arg n "$target_name" \ @@ -33,7 +33,7 @@ _generate_josh_filters() { ${exclude_patterns} ]" - local filter_file=".josh-filters/${target_name}.josh" + local filter_file=".josh-filter-${target_name}.josh" local existing="" if [ -f "$filter_file" ]; then existing=$(cat "$filter_file") @@ -75,9 +75,9 @@ parse_config() { export JOSH_SYNC_TARGETS JOSH_SYNC_TARGETS=$(echo "$config_json" | jq '[.targets[] | . + # Auto-derive josh_filter from subfolder if not set - # When exclude patterns are present, use a stored josh filter (:+.josh-filters/) + # When exclude patterns are present, use a stored josh filter (:+.josh-filter-) (if (.exclude // [] | length) > 0 then - {josh_filter: (":+.josh-filters/" + .name)} + {josh_filter: (":+.josh-filter-" + .name)} elif (.josh_filter // "") == "" then {josh_filter: (":/" + .subfolder)} else {} end) + @@ -98,7 +98,7 @@ parse_config() { ) ]') - # Generate .josh-filters/*.josh for targets with exclude patterns + # Generate .josh-filter-*.josh for targets with exclude patterns _generate_josh_filters # Load .env credentials (if present, not required — CI sets these via secrets) diff --git a/schema/config-schema.json b/schema/config-schema.json index d81ab28..c86c19b 100644 --- a/schema/config-schema.json +++ b/schema/config-schema.json @@ -75,7 +75,7 @@ "type": "array", "items": { "type": "string" }, "default": [], - "description": "File/directory patterns to exclude from sync via josh :exclude filter. Josh pattern syntax: 'dir/' for directories, '*.ext' for globs, '**/dir/' for nested matches. Generates a .josh-filters/.josh file that must be committed." + "description": "File/directory patterns to exclude from sync via josh :exclude filter. Josh pattern syntax: 'dir/' for directories, '*.ext' for globs, '**/dir/' for nested matches. Generates a .josh-filter-.josh file that must be committed." } } }