@decocms/start 2.0.0 → 2.0.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cms/loader.ts +36 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
package/src/cms/loader.ts CHANGED
@@ -127,9 +127,25 @@ export function withBlocksOverride<T>(override: Record<string, unknown>, fn: ()
127
127
  return blocksOverrideStorage.run(override, fn);
128
128
  }
129
129
 
130
+ // Higher key wins. Compared lexicographically:
131
+ // [literalSegments, paramSegments, hasNoSplat]
132
+ // So `/foo/bar` > `/foo/:x` > `/foo/*` > `/*`, and `/my-account/*` > `/*`.
133
+ function pathSpecificityKey(path: string): [number, number, number] {
134
+ const parts = path.split("/").filter(Boolean);
135
+ let literals = 0;
136
+ let params = 0;
137
+ let hasSplat = false;
138
+ for (const part of parts) {
139
+ if (part === "*") hasSplat = true;
140
+ else if (part.startsWith(":") || part.startsWith("$")) params++;
141
+ else literals++;
142
+ }
143
+ return [literals, params, hasSplat ? 0 : 1];
144
+ }
145
+
130
146
  export function getAllPages(): Array<{ key: string; page: DecoPage }> {
131
147
  const blocks = loadBlocks();
132
- const pages: Array<{ key: string; page: DecoPage; specificity: number }> = [];
148
+ const pages: Array<{ key: string; page: DecoPage; key2: [number, number, number] }> = [];
133
149
 
134
150
  for (const [key, block] of Object.entries(blocks)) {
135
151
  if (!key.startsWith("pages-")) continue;
@@ -137,16 +153,16 @@ export function getAllPages(): Array<{ key: string; page: DecoPage }> {
137
153
  if (!page.sections) continue;
138
154
  if (!page.path) continue;
139
155
 
140
- let specificity = 0;
141
- if (page.path === "/*") specificity = 0;
142
- else if (page.path.includes(":") || page.path.includes("$")) specificity = 1;
143
- else specificity = 2;
144
-
145
- pages.push({ key, page, specificity });
156
+ pages.push({ key, page, key2: pathSpecificityKey(page.path) });
146
157
  }
147
158
 
148
159
  return pages
149
- .sort((a, b) => b.specificity - a.specificity)
160
+ .sort((a, b) => {
161
+ for (let i = 0; i < a.key2.length; i++) {
162
+ if (a.key2[i] !== b.key2[i]) return b.key2[i] - a.key2[i];
163
+ }
164
+ return 0;
165
+ })
150
166
  .map(({ key, page }) => ({ key, page }));
151
167
  }
152
168
 
@@ -156,16 +172,26 @@ function matchPath(pattern: string, urlPath: string): Record<string, string> | n
156
172
  const patternParts = pattern.split("/").filter(Boolean);
157
173
  const urlParts = urlPath.split("/").filter(Boolean);
158
174
 
159
- if (patternParts.length !== urlParts.length) return null;
175
+ // Trailing `*` means "match this prefix and any remaining segments".
176
+ const hasSplat = patternParts[patternParts.length - 1] === "*";
177
+ const fixedLen = hasSplat ? patternParts.length - 1 : patternParts.length;
178
+
179
+ if (hasSplat) {
180
+ if (urlParts.length < fixedLen) return null;
181
+ } else if (urlParts.length !== fixedLen) {
182
+ return null;
183
+ }
160
184
 
161
185
  const params: Record<string, string> = {};
162
- for (let i = 0; i < patternParts.length; i++) {
186
+ for (let i = 0; i < fixedLen; i++) {
163
187
  const pp = patternParts[i];
164
188
  const up = urlParts[i];
165
189
  if (pp.startsWith(":")) params[pp.slice(1)] = up;
166
190
  else if (pp !== up) return null;
167
191
  }
168
192
 
193
+ if (hasSplat) params._splat = urlParts.slice(fixedLen).join("/");
194
+
169
195
  return params;
170
196
  }
171
197