start-vibing 4.3.1 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: detect-apps.sh [<repo_root>]
3
+ #
4
+ # Detects monorepo layout for super-design per-app audit state (artifact §11
5
+ # line 902). Reads workspace declarations from the known manifests:
6
+ # - pnpm-workspace.yaml (pnpm)
7
+ # - package.json#workspaces (npm / yarn / bun)
8
+ # - turbo.json (Turborepo — infers apps from pipeline scope)
9
+ # - nx.json / workspace.json (Nx)
10
+ # - bunfig.toml (Bun workspaces)
11
+ # For each matched glob we enumerate directories that also contain a
12
+ # package.json; those are the apps. When no workspace config is found we
13
+ # emit a single "single"-layout entry with path ".".
14
+ #
15
+ # Output JSON:
16
+ # {
17
+ # "layout": "monorepo" | "single",
18
+ # "apps": [
19
+ # { "name": "web",
20
+ # "path": "apps/web",
21
+ # "state_path": "apps/web/docs/super-design/.audit-state.json" }
22
+ # ]
23
+ # }
24
+ #
25
+ # POSIX-ish bash. Requires: jq. Works under git-bash on Windows (no GNU
26
+ # find -printf, no readarray).
27
+ set -euo pipefail
28
+
29
+ ROOT="${1:-.}"
30
+ cd "$ROOT"
31
+
32
+ log() { printf '[detect-apps] %s\n' "$*" >&2; }
33
+
34
+ # --- Collect workspace globs from whichever manifests exist ------------------
35
+ GLOBS=""
36
+
37
+ append_glob() {
38
+ # $1 = glob string; skip empty, comments, and negative (!) patterns for now.
39
+ local g
40
+ g="$(printf '%s' "$1" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
41
+ [ -z "$g" ] && return 0
42
+ case "$g" in
43
+ '#'*) return 0 ;;
44
+ '!'*) return 0 ;;
45
+ esac
46
+ GLOBS="${GLOBS}
47
+ ${g}"
48
+ }
49
+
50
+ # pnpm-workspace.yaml — very small YAML subset parser: lines under
51
+ # "packages:" that start with "- ".
52
+ if [ -f "pnpm-workspace.yaml" ]; then
53
+ in_pkgs=0
54
+ while IFS= read -r line; do
55
+ case "$line" in
56
+ packages:*) in_pkgs=1; continue ;;
57
+ esac
58
+ if [ "$in_pkgs" = "1" ]; then
59
+ case "$line" in
60
+ -\ *|\ *-\ *)
61
+ entry="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*-[[:space:]]*//' -e 's/^["'\'']//' -e 's/["'\'']$//')"
62
+ append_glob "$entry"
63
+ ;;
64
+ [A-Za-z]*:) in_pkgs=0 ;;
65
+ esac
66
+ fi
67
+ done < "pnpm-workspace.yaml"
68
+ fi
69
+
70
+ # package.json#workspaces (array or { packages: [...] })
71
+ if [ -f "package.json" ] && command -v jq >/dev/null 2>&1; then
72
+ WS_JSON="$(jq -r '
73
+ (.workspaces // empty)
74
+ | if type == "array" then .[]
75
+ elif type == "object" then (.packages // [])[]
76
+ else empty end
77
+ ' package.json 2>/dev/null || true)"
78
+ if [ -n "$WS_JSON" ]; then
79
+ while IFS= read -r g; do
80
+ [ -n "$g" ] && append_glob "$g"
81
+ done <<EOF
82
+ $WS_JSON
83
+ EOF
84
+ fi
85
+ fi
86
+
87
+ # turbo.json — Turborepo does not declare apps itself but relies on the
88
+ # package manager's workspaces. Treat its presence as confirmation that
89
+ # we are in a monorepo; globs come from package.json/pnpm-workspace.yaml
90
+ # which we've already read. If only turbo.json exists (rare), fall back
91
+ # to the conventional apps/* + packages/* layout.
92
+ if [ -f "turbo.json" ] && [ -z "$GLOBS" ]; then
93
+ append_glob "apps/*"
94
+ append_glob "packages/*"
95
+ fi
96
+
97
+ # nx.json / workspace.json — Nx. Apps live under apps/, libs/ (or the
98
+ # workspaceLayout override).
99
+ if [ -f "nx.json" ]; then
100
+ apps_dir="apps"
101
+ libs_dir="libs"
102
+ if command -v jq >/dev/null 2>&1; then
103
+ override_apps="$(jq -r '.workspaceLayout.appsDir // empty' nx.json 2>/dev/null || true)"
104
+ override_libs="$(jq -r '.workspaceLayout.libsDir // empty' nx.json 2>/dev/null || true)"
105
+ [ -n "$override_apps" ] && apps_dir="$override_apps"
106
+ [ -n "$override_libs" ] && libs_dir="$override_libs"
107
+ fi
108
+ append_glob "${apps_dir}/*"
109
+ append_glob "${libs_dir}/*"
110
+ fi
111
+
112
+ # bunfig.toml — Bun workspaces. Bun primarily reads workspaces from
113
+ # package.json#workspaces (already handled); bunfig.toml presence alone
114
+ # is a monorepo hint only if nothing else matched.
115
+ if [ -f "bunfig.toml" ] && [ -z "$GLOBS" ]; then
116
+ append_glob "apps/*"
117
+ append_glob "packages/*"
118
+ fi
119
+
120
+ # --- Expand globs to real directories that contain a package.json ------------
121
+ APPS_JSON='[]'
122
+ seen_paths=""
123
+
124
+ expand_glob() {
125
+ # $1 = glob like "apps/*" or "packages/marketing-site"
126
+ local g="$1"
127
+ # shellcheck disable=SC2086
128
+ # We deliberately let the shell expand the glob. POSIX shells don't set
129
+ # nullglob, so an unmatched pattern comes back as the literal string —
130
+ # we filter that out below by checking directory existence.
131
+ for d in $g; do
132
+ [ -d "$d" ] || continue
133
+ [ -f "$d/package.json" ] || continue
134
+ # Normalize: strip trailing slash, leading ./
135
+ p="$(printf '%s' "$d" | sed -e 's#^\./##' -e 's#/$##')"
136
+ case "$seen_paths" in
137
+ *"|${p}|"*) continue ;;
138
+ esac
139
+ seen_paths="${seen_paths}|${p}|"
140
+ # Name = package.json "name" field (last path segment for scoped names),
141
+ # fallback to basename.
142
+ name="$(basename "$p")"
143
+ if command -v jq >/dev/null 2>&1; then
144
+ pkg_name="$(jq -r '.name // empty' "$p/package.json" 2>/dev/null || true)"
145
+ if [ -n "$pkg_name" ]; then
146
+ # Strip @scope/ prefix so "web" beats "@acme/web".
147
+ name="$(printf '%s' "$pkg_name" | sed -e 's#^@[^/]*/##')"
148
+ fi
149
+ fi
150
+ APPS_JSON="$(printf '%s' "$APPS_JSON" | jq --arg name "$name" --arg path "$p" \
151
+ --arg state "$p/docs/super-design/.audit-state.json" \
152
+ '. + [{name:$name, path:$path, state_path:$state}]')"
153
+ done
154
+ }
155
+
156
+ if [ -n "$GLOBS" ]; then
157
+ while IFS= read -r g; do
158
+ [ -z "$g" ] && continue
159
+ expand_glob "$g"
160
+ done <<EOF
161
+ $GLOBS
162
+ EOF
163
+ fi
164
+
165
+ APP_COUNT="$(printf '%s' "$APPS_JSON" | jq 'length')"
166
+
167
+ if [ "$APP_COUNT" -eq 0 ]; then
168
+ # No monorepo config matched OR matched but produced no apps with
169
+ # package.json. Emit single-app layout.
170
+ jq -n '{
171
+ layout: "single",
172
+ apps: [ {name: ".", path: ".", state_path: "docs/super-design/.audit-state.json"} ]
173
+ }'
174
+ exit 0
175
+ fi
176
+
177
+ jq -n --argjson apps "$APPS_JSON" '{
178
+ layout: "monorepo",
179
+ apps: $apps
180
+ }'
@@ -1,6 +1,17 @@
1
1
  #!/usr/bin/env bash
2
- # Usage: detect-changes.sh <last_sha> [<last_iso>]
3
- # Emits JSON: { mode, range_start, commits, files, classified }
2
+ # Usage:
3
+ # detect-changes.sh <last_sha> [<last_iso>] (single app)
4
+ # detect-changes.sh --app <path> <last_sha> [<last_iso>] (one app, path-scoped)
5
+ # detect-changes.sh --all-apps (monorepo loop)
6
+ #
7
+ # Emits JSON. Single-app form:
8
+ # { mode, range_start, commits, files, classified }
9
+ #
10
+ # Monorepo form (--all-apps) — one wrapper with per-app results; each
11
+ # app reads its own `.audit-state.json` via detect-apps.sh:
12
+ # { "layout": "monorepo"|"single",
13
+ # "apps": [ { "name", "path", "state_path",
14
+ # "last_sha", "changes": {<single-app form>} } ] }
4
15
  #
5
16
  # Implements the fallback ladder from artifact §6 + §14:
6
17
  # 1. Anchor exists → diff with rename detection (-M90%).
@@ -11,16 +22,59 @@
11
22
  # 4b825dc642cb6eb9a060e54bf8d69288fbee4904 (artifact line 900).
12
23
  # Also uses --first-parent + --cherry-pick --right-only when walking
13
24
  # history (artifact §2.5 line 167-172).
25
+ #
26
+ # Monorepo per-app state (artifact §11 line 902): when --app is given,
27
+ # `git diff --name-status` is narrowed via `-- <app_path>/` pathspec so
28
+ # only files under that app show up. `--all-apps` discovers apps via
29
+ # detect-apps.sh and runs the per-app path for each.
14
30
  set -euo pipefail
15
31
 
16
- LAST_SHA="${1:-}"
17
- LAST_ISO="${2:-}"
32
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
18
33
  EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
19
34
 
20
35
  log() { printf '[detect-changes] %s\n' "$*" >&2; }
21
36
 
37
+ # --- Arg parsing -------------------------------------------------------------
38
+ MODE_SELECT="single" # single | app | all
39
+ APP_PATH="."
40
+ if [ "${1:-}" = "--all-apps" ]; then
41
+ MODE_SELECT="all"; shift
42
+ elif [ "${1:-}" = "--app" ]; then
43
+ MODE_SELECT="app"; APP_PATH="${2:-.}"; shift 2
44
+ fi
45
+ LAST_SHA="${1:-}"
46
+ LAST_ISO="${2:-}"
47
+
22
48
  if ! git rev-parse --git-dir >/dev/null 2>&1; then echo '{"error":"not-a-git-repo"}'; exit 3; fi
23
49
 
50
+ # --- --all-apps: discover via detect-apps.sh, recurse per app ----------------
51
+ if [ "$MODE_SELECT" = "all" ]; then
52
+ APPS_DOC="$(bash "$SCRIPT_DIR/detect-apps.sh" .)"
53
+ LAYOUT="$(printf '%s' "$APPS_DOC" | jq -r '.layout')"
54
+ RESULT='[]'
55
+ APP_COUNT="$(printf '%s' "$APPS_DOC" | jq '.apps | length')"
56
+ i=0
57
+ while [ "$i" -lt "$APP_COUNT" ]; do
58
+ APP_NAME="$(printf '%s' "$APPS_DOC" | jq -r ".apps[$i].name")"
59
+ A_PATH="$(printf '%s' "$APPS_DOC" | jq -r ".apps[$i].path")"
60
+ A_STATE="$(printf '%s' "$APPS_DOC" | jq -r ".apps[$i].state_path")"
61
+ A_LAST_SHA=""; A_LAST_ISO=""
62
+ if [ -f "$A_STATE" ]; then
63
+ A_LAST_SHA="$(jq -r '.git_sha_at_audit // ""' "$A_STATE" 2>/dev/null || true)"
64
+ A_LAST_ISO="$(jq -r '.last_audit_at // ""' "$A_STATE" 2>/dev/null || true)"
65
+ fi
66
+ A_CHANGES="$(bash "$0" --app "$A_PATH" "$A_LAST_SHA" "$A_LAST_ISO")"
67
+ RESULT="$(printf '%s' "$RESULT" | jq \
68
+ --arg name "$APP_NAME" --arg path "$A_PATH" --arg state "$A_STATE" \
69
+ --arg lsha "$A_LAST_SHA" --argjson ch "$A_CHANGES" '
70
+ . + [{name:$name, path:$path, state_path:$state, last_sha:$lsha, changes:$ch}]
71
+ ')"
72
+ i=$((i + 1))
73
+ done
74
+ jq -n --arg layout "$LAYOUT" --argjson apps "$RESULT" '{layout:$layout, apps:$apps}'
75
+ exit 0
76
+ fi
77
+
24
78
  # Determine HEAD availability — empty repos have no commits at all.
25
79
  if ! git rev-parse --verify --quiet HEAD >/dev/null; then
26
80
  log "no HEAD commit; using empty-tree SHA fallback"
@@ -67,11 +121,19 @@ if [[ -z "${RANGE_START:-}" ]]; then
67
121
  resolve_anchor || true
68
122
  fi
69
123
 
124
+ # Build the pathspec used to narrow diff/log to a single app (monorepo
125
+ # per-app state, artifact §11 line 902). For the default "." the scope
126
+ # is the repo root, preserving pre-existing single-app behavior.
127
+ case "$APP_PATH" in
128
+ .|"") SCOPE_PATH="." ;;
129
+ *) SCOPE_PATH="${APP_PATH%/}" ;;
130
+ esac
131
+
70
132
  # Ladder step (c): time-based fallback.
71
133
  if [[ -z "${RANGE_START:-}" && -n "$LAST_ISO" ]]; then
72
- log "using --since=$LAST_ISO time-based fallback"
73
- FILES="$(git log --since="$LAST_ISO" --name-only --pretty=format: 2>/dev/null | sort -u | sed '/^$/d' || true)"
74
- COMMITS="$(git log --first-parent --since="$LAST_ISO" --pretty=format:'%H|%s|%an|%aI' 2>/dev/null || true)"
134
+ log "using --since=$LAST_ISO time-based fallback (scope=$SCOPE_PATH)"
135
+ FILES="$(git log --since="$LAST_ISO" --name-only --pretty=format: -- "$SCOPE_PATH" 2>/dev/null | sort -u | sed '/^$/d' || true)"
136
+ COMMITS="$(git log --first-parent --since="$LAST_ISO" --pretty=format:'%H|%s|%an|%aI' -- "$SCOPE_PATH" 2>/dev/null || true)"
75
137
  MODE="since-time"
76
138
  RANGE_START=""
77
139
  elif [[ -z "${RANGE_START:-}" ]]; then
@@ -91,7 +153,7 @@ else
91
153
  # the current file layout.
92
154
  FILES="$(
93
155
  git diff --name-status -M90% -z \
94
- -- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml' ':!yarn.lock' \
156
+ -- "$SCOPE_PATH" ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml' ':!yarn.lock' \
95
157
  ':!.github/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/*.stories.*' \
96
158
  $RANGE 2>/dev/null |
97
159
  awk -v RS='\0' '
@@ -136,6 +198,7 @@ while IFS= read -r p; do
136
198
  done <<< "$FILES"
137
199
 
138
200
  jq -Rn --arg mode "$MODE" --arg range_start "${RANGE_START:-}" --arg last_iso "$LAST_ISO" \
201
+ --arg app_path "$APP_PATH" \
139
202
  --arg tokens "${CLASSIFIED[tokens]:-}" --arg components "${CLASSIFIED[components]:-}" \
140
203
  --arg routes "${CLASSIFIED[routes]:-}" --arg imagery "${CLASSIFIED[imagery]:-}" \
141
204
  --arg deps "${CLASSIFIED[deps]:-}" --arg theory "${CLASSIFIED[theory]:-}" \
@@ -143,7 +206,8 @@ jq -Rn --arg mode "$MODE" --arg range_start "${RANGE_START:-}" --arg last_iso "$
143
206
  --argjson files "$(printf '%s\n' "$FILES" | jq -R . | jq -s .)" \
144
207
  --argjson commits "$(printf '%s\n' "$COMMITS" | jq -R . | jq -s .)" '
145
208
  def tolist(s): s | split(",") | map(select(length>0));
146
- {mode:$mode, range_start:$range_start, since_iso:$last_iso,
209
+ {app_path:$app_path,
210
+ mode:$mode, range_start:$range_start, since_iso:$last_iso,
147
211
  commits:$commits, files:$files,
148
212
  classified: {
149
213
  tokens: tolist($tokens), components: tolist($components),
@@ -151,6 +215,3 @@ jq -Rn --arg mode "$MODE" --arg range_start "${RANGE_START:-}" --arg last_iso "$
151
215
  deps: tolist($deps), theory: tolist($theory), content: tolist($content)
152
216
  }
153
217
  }'
154
-
155
- # TODO(sd-audit-state §11/artifact line 902): monorepo per-app state
156
- # (apps/*/docs/super-design/.audit-state.json) not yet supported.
@@ -1,13 +1,26 @@
1
1
  #!/usr/bin/env bash
2
- # TODO(sd-audit-state artifact §14): dynamic routes like `/posts/[slug]`
3
- # should be expanded to `/posts/@fixture-<id>` using a fixtures manifest
4
- # (discovered from tests or a user-configured JSON) before being passed
5
- # to hash-pages/sd-audit. Current impl emits the raw pattern.
6
- # TODO(sd-audit-state artifact §8): madge-based import-graph builder
7
- # (`madge --json --ts-config tsconfig.json src`) to compute N-hop
8
- # component → page blast radius. Expected alongside this script.
2
+ # discover-routes.sh discover route URLs for the detected framework.
3
+ #
4
+ # Dynamic routes (Next `[slug]` / `[[...foo]]` / `[...bar]`, Remix `$slug`,
5
+ # React-Router/Vue `:slug`) are emitted with a `@fixture-<id>` suffix so
6
+ # the route map has a stable identity across audits (artifact §2.7 + §7:
7
+ # "/posts/[id]@fixture-post-123"). Fixtures are resolved via:
8
+ #
9
+ # 1. Sibling file: <route-dir>/<name>.fixture.json|ts (array of IDs or
10
+ # objects with {id|slug|key}).
11
+ # 2. Nearby directory: <route-dir>/fixtures/*.json.
12
+ # 3. Env override: $SUPER_DESIGN_FIXTURES — a JSON object mapping the
13
+ # raw pattern (e.g. "/posts/[slug]") to an array of fixture IDs.
14
+ # 4. Placeholder fallback: `@fixture-default` (with stderr warning).
15
+ #
16
+ # Consumers (hash-pages.sh, sd-audit) MUST strip the `@fixture-<id>`
17
+ # suffix before navigating; the suffix is identity-only.
18
+ #
19
+ # Output: JSON array of URL strings.
9
20
  set -euo pipefail
10
21
 
22
+ log() { printf '[discover-routes] %s\n' "$*" >&2; }
23
+
11
24
  detect_framework() {
12
25
  if [[ -f next.config.js || -f next.config.ts || -f next.config.mjs ]]; then echo "next"
13
26
  elif [[ -f remix.config.js || -d app/routes ]]; then echo "remix"
@@ -20,6 +33,96 @@ detect_framework() {
20
33
  else echo "unknown"; fi
21
34
  }
22
35
 
36
+ # is_dynamic <url> → 0 if URL contains a dynamic segment, else 1.
37
+ is_dynamic() {
38
+ local u="$1"
39
+ # Next-style: [foo], [[foo]], [...foo], [[...foo]]
40
+ [[ "$u" == *"["*"]"* ]] && return 0
41
+ # Remix-style: $foo
42
+ [[ "$u" == *"$"* ]] && return 0
43
+ # RR/Vue-style: :foo
44
+ [[ "$u" == *":"* ]] && return 0
45
+ return 1
46
+ }
47
+
48
+ # Look for a fixtures source for a given route URL and emit IDs one per
49
+ # line. Echo `__DEFAULT__` if nothing found.
50
+ resolve_fixture_ids() {
51
+ local url="$1"
52
+ local segment
53
+ # Strip leading / and use the last dynamic-bearing segment to find a file.
54
+ # Rough heuristic: pick the last path component that is a bare identifier
55
+ # (strip brackets/dollar/colon) to look up fixtures/<name>.*.
56
+ segment="$(printf '%s' "$url" \
57
+ | sed -E 's|^/||' \
58
+ | tr '/' '\n' \
59
+ | grep -E '^(\[\[?\.\.\.?.+\]\]?|\[.+\]|\$.+|:.+)$' \
60
+ | tail -1 \
61
+ | sed -E 's|^\[\[?\.?\.?\.?||; s|\]\]?$||; s|^\$||; s|^:||')"
62
+ [[ -z "$segment" ]] && { echo "__DEFAULT__"; return; }
63
+
64
+ # 1. Env override.
65
+ if [[ -n "${SUPER_DESIGN_FIXTURES:-}" ]]; then
66
+ local ids
67
+ ids="$(printf '%s' "$SUPER_DESIGN_FIXTURES" \
68
+ | jq -r --arg k "$url" '.[$k][]? // empty' 2>/dev/null || true)"
69
+ if [[ -n "$ids" ]]; then
70
+ printf '%s\n' "$ids"; return
71
+ fi
72
+ fi
73
+
74
+ # 2. Sibling / nearby fixture files. Search in common locations.
75
+ local candidate ids=""
76
+ for candidate in \
77
+ "${segment}.fixture.json" \
78
+ "fixtures/${segment}.json" \
79
+ "tests/fixtures/${segment}.json" \
80
+ "__fixtures__/${segment}.json" \
81
+ ".super-design/fixtures/${segment}.json"; do
82
+ local found
83
+ found="$(find . -type f -name "$(basename "$candidate")" 2>/dev/null \
84
+ | grep -F "/$candidate" | head -1 || true)"
85
+ if [[ -n "$found" && -f "$found" ]]; then
86
+ ids="$(jq -r '
87
+ if type == "array" then
88
+ .[] | if type == "object" then (.id // .slug // .key // empty) else . end
89
+ elif type == "object" and (.fixtures|type=="array") then
90
+ .fixtures[] | if type=="object" then (.id // .slug // .key // empty) else . end
91
+ else empty end
92
+ ' "$found" 2>/dev/null | head -5 || true)"
93
+ if [[ -n "$ids" ]]; then
94
+ printf '%s\n' "$ids"; return
95
+ fi
96
+ fi
97
+ done
98
+
99
+ # 3. Give up → default placeholder with warning.
100
+ log "no fixtures for $url; using @fixture-default"
101
+ echo "__DEFAULT__"
102
+ }
103
+
104
+ # suffix_dynamic_routes: read raw URLs on stdin, write suffixed URLs on
105
+ # stdout. Static routes pass through untouched.
106
+ suffix_dynamic_routes() {
107
+ local url ids id
108
+ while IFS= read -r url; do
109
+ [[ -z "$url" ]] && continue
110
+ if is_dynamic "$url"; then
111
+ ids="$(resolve_fixture_ids "$url")"
112
+ if [[ "$ids" == "__DEFAULT__" ]]; then
113
+ printf '%s@fixture-default\n' "$url"
114
+ else
115
+ while IFS= read -r id; do
116
+ [[ -z "$id" ]] && continue
117
+ printf '%s@fixture-%s\n' "$url" "$id"
118
+ done <<<"$ids"
119
+ fi
120
+ else
121
+ printf '%s\n' "$url"
122
+ fi
123
+ done
124
+ }
125
+
23
126
  FW="$(detect_framework)"
24
127
  case "$FW" in
25
128
  next)
@@ -29,24 +132,28 @@ case "$FW" in
29
132
  PG="$(find pages src/pages -type f \( -name '*.tsx' -o -name '*.ts' -o -name '*.jsx' -o -name '*.js' -o -name '*.md' -o -name '*.mdx' \) 2>/dev/null \
30
133
  | grep -vE '(^|/)(pages|src/pages)/(_app|_document|_error|404|500|api/)' \
31
134
  | sed -E 's|(^|/)(pages|src/pages)/||; s|\.(tsx|ts|jsx|js|md|mdx)$||; s|index$||' | sort -u || true)"
32
- printf '%s\n%s\n' "$APP" "$PG" | awk 'NF' | sed -E 's|^|/|; s|//|/|g' | sort -u | jq -Rn '[inputs]';;
135
+ printf '%s\n%s\n' "$APP" "$PG" | awk 'NF' | sed -E 's|^|/|; s|//|/|g' | sort -u \
136
+ | suffix_dynamic_routes | jq -Rn '[inputs]';;
33
137
  sveltekit)
34
138
  find src/routes -type f -name '+page.svelte' 2>/dev/null \
35
139
  | sed -E 's|^src/routes||; s|/\+page\.svelte$||; s|\([^)]+\)/||g' \
36
- | awk 'NF==0 {print "/"; next} {print}' | sort -u | jq -Rn '[inputs]';;
140
+ | awk 'NF==0 {print "/"; next} {print}' | sort -u \
141
+ | suffix_dynamic_routes | jq -Rn '[inputs]';;
37
142
  astro)
38
143
  find src/pages -type f \( -name '*.astro' -o -name '*.md' -o -name '*.mdx' \) 2>/dev/null \
39
- | sed -E 's|^src/pages||; s|\.(astro|md|mdx)$||; s|/index$||' | sort -u | jq -Rn '[inputs]';;
144
+ | sed -E 's|^src/pages||; s|\.(astro|md|mdx)$||; s|/index$||' | sort -u \
145
+ | suffix_dynamic_routes | jq -Rn '[inputs]';;
40
146
  nuxt)
41
147
  find pages app/pages -type f -name '*.vue' 2>/dev/null \
42
- | sed -E 's|^(app/)?pages||; s|\.vue$||; s|/index$||' | sort -u | jq -Rn '[inputs]';;
148
+ | sed -E 's|^(app/)?pages||; s|\.vue$||; s|/index$||' | sort -u \
149
+ | suffix_dynamic_routes | jq -Rn '[inputs]';;
43
150
  remix)
44
151
  find app/routes -type f \( -name '*.tsx' -o -name '*.ts' -o -name '*.jsx' -o -name '*.js' -o -name '*.md' -o -name '*.mdx' \) 2>/dev/null \
45
152
  | sed -E 's|^app/routes/||; s|\.(tsx|ts|jsx|js|md|mdx)$||; s|\.|/|g; s|_index$||; s|^|/|' \
46
- | sort -u | jq -Rn '[inputs]';;
153
+ | sort -u | suffix_dynamic_routes | jq -Rn '[inputs]';;
47
154
  solid-start)
48
155
  find src/routes -type f \( -name '*.tsx' -o -name '*.ts' -o -name '*.jsx' -o -name '*.js' \) 2>/dev/null \
49
156
  | sed -E 's|^src/routes||; s|\.(tsx|ts|jsx|js)$||; s|/index$||; s|\([^)]+\)/||g' \
50
- | sort -u | jq -Rn '[inputs]';;
157
+ | sort -u | suffix_dynamic_routes | jq -Rn '[inputs]';;
51
158
  *) echo '[]';;
52
159
  esac