@dereekb/dbx-cli 13.19.0 → 13.20.0

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.
package/index.cjs.js CHANGED
@@ -57227,17 +57227,38 @@ function _array_like_to_array$3(arr, len) {
57227
57227
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
57228
57228
  return arr2;
57229
57229
  }
57230
- function _array_without_holes$1(arr) {
57231
- if (Array.isArray(arr)) return _array_like_to_array$3(arr);
57230
+ function _array_with_holes$1(arr) {
57231
+ if (Array.isArray(arr)) return arr;
57232
57232
  }
57233
- function _iterable_to_array$1(iter) {
57234
- if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
57233
+ function _iterable_to_array_limit$1(arr, i) {
57234
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
57235
+ if (_i == null) return;
57236
+ var _arr = [];
57237
+ var _n = true;
57238
+ var _d = false;
57239
+ var _s, _e;
57240
+ try {
57241
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
57242
+ _arr.push(_s.value);
57243
+ if (i && _arr.length === i) break;
57244
+ }
57245
+ } catch (err) {
57246
+ _d = true;
57247
+ _e = err;
57248
+ } finally{
57249
+ try {
57250
+ if (!_n && _i["return"] != null) _i["return"]();
57251
+ } finally{
57252
+ if (_d) throw _e;
57253
+ }
57254
+ }
57255
+ return _arr;
57235
57256
  }
57236
- function _non_iterable_spread$1() {
57237
- throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57257
+ function _non_iterable_rest$1() {
57258
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57238
57259
  }
57239
- function _to_consumable_array$1(arr) {
57240
- return _array_without_holes$1(arr) || _iterable_to_array$1(arr) || _unsupported_iterable_to_array$3(arr) || _non_iterable_spread$1();
57260
+ function _sliced_to_array$1(arr, i) {
57261
+ return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$3(arr, i) || _non_iterable_rest$1();
57241
57262
  }
57242
57263
  function _unsupported_iterable_to_array$3(o, minLen) {
57243
57264
  if (!o) return;
@@ -57248,74 +57269,93 @@ function _unsupported_iterable_to_array$3(o, minLen) {
57248
57269
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
57249
57270
  }
57250
57271
  /**
57251
- * Builds a parent-linked {@link RouteTree} from a flat list of {@link RouteNode}s.
57252
- *
57253
- * Linkage strategy:
57272
+ * Pure URL route matcher, shared by the dev-server `dbx_route_resolve_url`
57273
+ * tool and (mirrored, not imported) by the firebase-server/mcp `url-models`
57274
+ * runtime tool.
57254
57275
  *
57255
- * 1. Use the explicit `parent` field if present.
57256
- * 2. Otherwise derive parent from the dot-prefix of `name`. State `a.b.c`
57257
- * becomes a child of `a.b`. Trailing `.**` is stripped before lookup.
57258
- * 3. Nodes whose parent doesn't resolve are reported as orphan warnings and
57259
- * placed at the tree root.
57276
+ * No ts-morph, no node:fs operates on a flat list of candidates each carrying
57277
+ * a composed `fullUrl` and an opaque `value`. Matching mirrors UIRouter's
57278
+ * literal-then-param preference: a literal segment-for-segment match wins over a
57279
+ * parameterised one, and ties at either tier collapse to an `ambiguous` result.
57280
+ */ /**
57281
+ * Collapses an empty pathname to `/` and strips a single trailing slash so
57282
+ * `/a/b/` and `/a/b` compare equal.
57260
57283
  *
57261
- * Issues surfaced:
57284
+ * @param pathname - The raw pathname to normalize.
57285
+ * @returns The normalized pathname.
57286
+ */ function normalizePathname(pathname) {
57287
+ var result;
57288
+ if (pathname.length === 0) {
57289
+ result = '/';
57290
+ } else if (pathname.length > 1 && pathname.endsWith('/')) {
57291
+ result = pathname.slice(0, -1);
57292
+ } else {
57293
+ result = pathname;
57294
+ }
57295
+ return result;
57296
+ }
57297
+ /**
57298
+ * Strips a UIRouter query string (`?…`) and/or hash (`#…`) suffix off a URL or
57299
+ * URL pattern, keeping only the path portion. Mirrors the runtime
57300
+ * `parseUrlModelsPathname` normalization (`firebase-server/mcp`) so build-time
57301
+ * param extraction and runtime matching agree: a state url like
57302
+ * `/:schoolJob?slotIndex` reduces to `/:schoolJob` rather than leaking the
57303
+ * `?slotIndex` query param into the last path segment.
57262
57304
  *
57263
- * - `DUPLICATE_STATE_NAME` error; second declaration is dropped.
57264
- * - `CYCLE_DETECTED` error; the cycle is broken by reverting the tail to
57265
- * the root list.
57266
- * - `ORPHAN_STATE` warning.
57267
- */ /**
57268
- * Builds the parent-child UIRouter tree from a flat node list, computing each
57269
- * state's full URL while preserving any extraction issues so callers see one
57270
- * combined diagnostics view.
57305
+ * @param url - The URL or composed URL pattern to normalize.
57306
+ * @returns The url with any `?…` / `#…` suffix removed.
57307
+ */ function stripUrlQueryAndHash(url) {
57308
+ var hashStripped = url.split('#', 1)[0];
57309
+ return hashStripped.split('?', 1)[0];
57310
+ }
57311
+ /**
57312
+ * Splits a path into its non-empty segments (the leading slash is dropped).
57271
57313
  *
57272
- * @param nodes - The flat route nodes extracted from sources.
57273
- * @param extractIssues - Issues already discovered during extraction to forward.
57274
- * @returns The constructed tree alongside the merged issue list.
57314
+ * @param path - The path or URL pattern to split.
57315
+ * @returns The ordered segment list, empty for `/`.
57316
+ */ function splitSegments(path) {
57317
+ var normalized = path.startsWith('/') ? path.slice(1) : path;
57318
+ return normalized.length === 0 ? [] : normalized.split('/');
57319
+ }
57320
+ /**
57321
+ * Whether a composed URL contains at least one `:param` or `{param}` segment.
57275
57322
  *
57276
- * @__NO_SIDE_EFFECTS__
57277
- */ function buildRouteTree(nodes, extractIssues) {
57278
- var issues = _to_consumable_array$1(extractIssues);
57279
- var byName = new Map();
57280
- insertNodes(nodes, byName, issues);
57281
- wireParentLinks(byName, issues);
57282
- detectCycles(byName, issues);
57283
- composeFullUrls(byName);
57284
- var roots = collectRoots(byName);
57285
- sortChildren(byName, roots);
57286
- var _freezeTree = freezeTree(roots), frozen = _freezeTree.frozen, frozenRoots = _freezeTree.frozenRoots;
57287
- var result = {
57288
- roots: frozenRoots,
57289
- byName: frozen,
57290
- issues: issues,
57291
- filesChecked: 0,
57292
- nodeCount: byName.size
57293
- };
57294
- return result;
57323
+ * @param path - The composed URL pattern.
57324
+ * @returns `true` when the pattern declares a parameter.
57325
+ */ function hasParamSegment(path) {
57326
+ return path.includes(':') || path.includes('{');
57295
57327
  }
57296
- // (1) Insert nodes; report duplicates
57297
- function insertNodes(nodes, byName, issues) {
57328
+ /**
57329
+ * Attempts a segment-for-segment match of a route pattern against concrete
57330
+ * input segments. `:name` and `{name}` / `{name:type}` segments capture the
57331
+ * input value (URL-decoded); literal segments must match exactly.
57332
+ *
57333
+ * @param route - The route pattern's segments.
57334
+ * @param input - The concrete URL's segments.
57335
+ * @returns The captured params when every segment matches, else `undefined`.
57336
+ */ function tryMatchSegments(route, input) {
57337
+ if (route.length !== input.length) {
57338
+ return undefined;
57339
+ }
57340
+ var params = {};
57341
+ var result = params;
57298
57342
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57299
57343
  try {
57300
- for(var _iterator = nodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57301
- var node = _step.value;
57302
- if (byName.has(node.name)) {
57303
- issues.push({
57304
- code: 'DUPLICATE_STATE_NAME',
57305
- severity: 'error',
57306
- message: "State `".concat(node.name, "` is declared in multiple places. Keeping the first declaration."),
57307
- file: node.file,
57308
- line: node.line,
57309
- stateName: node.name
57310
- });
57311
- continue;
57344
+ for(var _iterator = route.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57345
+ var _step_value = _sliced_to_array$1(_step.value, 2), i = _step_value[0], r = _step_value[1];
57346
+ var v = input[i];
57347
+ if (r.startsWith(':')) {
57348
+ var key = r.slice(1);
57349
+ params[key] = decodeURIComponent(v);
57350
+ } else if (r.startsWith('{') && r.endsWith('}')) {
57351
+ var inner = r.slice(1, -1);
57352
+ var colonIdx = inner.indexOf(':');
57353
+ var key1 = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
57354
+ params[key1] = decodeURIComponent(v);
57355
+ } else if (r !== v) {
57356
+ result = undefined;
57357
+ break;
57312
57358
  }
57313
- byName.set(node.name, {
57314
- data: node,
57315
- fullUrl: undefined,
57316
- parent: undefined,
57317
- children: []
57318
- });
57319
57359
  }
57320
57360
  } catch (err) {
57321
57361
  _didIteratorError = true;
@@ -57331,29 +57371,32 @@ function insertNodes(nodes, byName, issues) {
57331
57371
  }
57332
57372
  }
57333
57373
  }
57374
+ return result;
57334
57375
  }
57335
- // (2) Wire parent links
57336
- function wireParentLinks(byName, issues) {
57376
+ /**
57377
+ * Extracts param-name segments from a composed UIRouter URL. Recognises:
57378
+ * - `:name` (Express-style)
57379
+ * - `{name}` (UIRouter type-less)
57380
+ * - `{name:type}` and `{name:regex}` (UIRouter typed / regex)
57381
+ * Order is preserved; duplicates are de-duplicated by first occurrence.
57382
+ *
57383
+ * @param fullUrl - Composed URL (e.g. `/{orgId}/users/:userId`) or undefined.
57384
+ * @returns The param key list in declaration order, or an empty array.
57385
+ */ function extractUrlParamKeys(fullUrl) {
57386
+ if (fullUrl === undefined || fullUrl.length === 0) {
57387
+ return [];
57388
+ }
57389
+ var seen = new Set();
57390
+ var keys = [];
57337
57391
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57338
57392
  try {
57339
- for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57340
- var treeNode = _step.value;
57341
- var parentName = resolveParentName(treeNode.data, byName);
57342
- if (!parentName) continue;
57343
- var parent = byName.get(parentName);
57344
- if (!parent) {
57345
- issues.push({
57346
- code: 'ORPHAN_STATE',
57347
- severity: 'warning',
57348
- message: "State `".concat(treeNode.data.name, "` references parent `").concat(parentName, "` which is not declared in the analyzed sources."),
57349
- file: treeNode.data.file,
57350
- line: treeNode.data.line,
57351
- stateName: treeNode.data.name
57352
- });
57353
- continue;
57393
+ for(var _iterator = fullUrl.split('/')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57394
+ var segment = _step.value;
57395
+ var key = extractParamKeyFromSegment(segment);
57396
+ if (key !== undefined && !seen.has(key)) {
57397
+ seen.add(key);
57398
+ keys.push(key);
57354
57399
  }
57355
- treeNode.parent = parent;
57356
- parent.children.push(treeNode);
57357
57400
  }
57358
57401
  } catch (err) {
57359
57402
  _didIteratorError = true;
@@ -57369,17 +57412,67 @@ function wireParentLinks(byName, issues) {
57369
57412
  }
57370
57413
  }
57371
57414
  }
57415
+ return keys;
57372
57416
  }
57373
- // (3) Detect cycles. A cycle is impossible via dot-prefix linkage but the
57374
- // explicit `parent` field can introduce one (`a.parent = 'b'`,
57375
- // `b.parent = 'a'`). Walk from each node and break the chain on revisits.
57376
- function detectCycles(byName, issues) {
57417
+ function extractParamKeyFromSegment(rawSegment) {
57418
+ // Defense-in-depth: a `:param?query` / `:param#hash` segment that slips past
57419
+ // `composeFullUrl` normalization must not leak its suffix into the param key.
57420
+ var segment = stripUrlQueryAndHash(rawSegment);
57421
+ if (segment.startsWith(':')) {
57422
+ var key = segment.slice(1);
57423
+ return key.length > 0 ? key : undefined;
57424
+ }
57425
+ if (segment.startsWith('{') && segment.endsWith('}')) {
57426
+ var inner = segment.slice(1, -1);
57427
+ var colonIdx = inner.indexOf(':');
57428
+ var rawKey = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
57429
+ var key1 = rawKey.trim();
57430
+ return key1.length > 0 ? key1 : undefined;
57431
+ }
57432
+ return undefined;
57433
+ }
57434
+ /**
57435
+ * Matches a pathname against a flat candidate list, preferring a literal
57436
+ * (exact composed-URL) match over a parameterised one. A tie at either tier
57437
+ * collapses to `ambiguous`; otherwise the closest near-misses are scored and
57438
+ * returned in a `none` result.
57439
+ *
57440
+ * @param input - The candidate entries and the pathname to resolve.
57441
+ * @returns A discriminated match / ambiguous / none result.
57442
+ */ function matchUrlAgainstEntries(input) {
57443
+ var pathname = normalizePathname(input.pathname);
57444
+ var literal = matchLiteral(input.entries, pathname);
57445
+ var result;
57446
+ if (literal.length === 1) {
57447
+ var _literal__fullUrl;
57448
+ result = {
57449
+ kind: 'match',
57450
+ via: 'literal',
57451
+ value: literal[0].value,
57452
+ matchedFullUrl: normalizePathname((_literal__fullUrl = literal[0].fullUrl) !== null && _literal__fullUrl !== void 0 ? _literal__fullUrl : pathname),
57453
+ params: {}
57454
+ };
57455
+ } else if (literal.length > 1) {
57456
+ result = {
57457
+ kind: 'ambiguous',
57458
+ values: literal.map(function(e) {
57459
+ return e.value;
57460
+ })
57461
+ };
57462
+ } else {
57463
+ result = matchParamOrNone(input.entries, pathname);
57464
+ }
57465
+ return result;
57466
+ }
57467
+ function matchLiteral(entries, pathname) {
57468
+ var out = [];
57377
57469
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57378
57470
  try {
57379
- for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57380
- var treeNode = _step.value;
57381
- if (!treeNode.parent) continue;
57382
- detectCycleFor(treeNode, issues);
57471
+ for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57472
+ var entry = _step.value;
57473
+ if (entry.fullUrl !== undefined && normalizePathname(entry.fullUrl) === pathname) {
57474
+ out.push(entry);
57475
+ }
57383
57476
  }
57384
57477
  } catch (err) {
57385
57478
  _didIteratorError = true;
@@ -57395,45 +57488,25 @@ function detectCycles(byName, issues) {
57395
57488
  }
57396
57489
  }
57397
57490
  }
57491
+ return out;
57398
57492
  }
57399
- function detectCycleFor(treeNode, issues) {
57400
- var seen = new Set();
57401
- seen.add(treeNode.data.name);
57402
- var cursor = treeNode.parent;
57403
- while(cursor){
57404
- if (seen.has(cursor.data.name)) {
57405
- issues.push({
57406
- code: 'CYCLE_DETECTED',
57407
- severity: 'error',
57408
- message: "Cycle detected involving `".concat(treeNode.data.name, "` and `").concat(cursor.data.name, "`. Severing the parent link."),
57409
- file: treeNode.data.file,
57410
- line: treeNode.data.line,
57411
- stateName: treeNode.data.name
57412
- });
57413
- detachFromParent(treeNode);
57414
- return;
57415
- }
57416
- seen.add(cursor.data.name);
57417
- cursor = cursor.parent;
57418
- }
57419
- }
57420
- function detachFromParent(treeNode) {
57421
- if (!treeNode.parent) return;
57422
- var idx = treeNode.parent.children.indexOf(treeNode);
57423
- if (idx >= 0) {
57424
- treeNode.parent.children.splice(idx, 1);
57425
- }
57426
- treeNode.parent = undefined;
57427
- }
57428
- // (4) Compose full URLs (parent walk; root url is not prefixed). UIRouter's
57429
- // own logic is more nuanced (some states overwrite their parent's URL with
57430
- // a leading `^`), but this gives a useful approximation for the common case.
57431
- function composeFullUrls(byName) {
57493
+ function matchParamOrNone(entries, pathname) {
57494
+ var inputSegments = splitSegments(pathname);
57495
+ var hits = [];
57432
57496
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57433
57497
  try {
57434
- for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57435
- var treeNode = _step.value;
57436
- treeNode.fullUrl = composeFullUrl(treeNode);
57498
+ for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57499
+ var entry = _step.value;
57500
+ if (entry.fullUrl === undefined || !hasParamSegment(entry.fullUrl)) {
57501
+ continue;
57502
+ }
57503
+ var params = tryMatchSegments(splitSegments(entry.fullUrl), inputSegments);
57504
+ if (params) {
57505
+ hits.push({
57506
+ entry: entry,
57507
+ params: params
57508
+ });
57509
+ }
57437
57510
  }
57438
57511
  } catch (err) {
57439
57512
  _didIteratorError = true;
@@ -57449,42 +57522,67 @@ function composeFullUrls(byName) {
57449
57522
  }
57450
57523
  }
57451
57524
  }
57525
+ var result;
57526
+ if (hits.length === 1) {
57527
+ var _hits__entry_fullUrl;
57528
+ result = {
57529
+ kind: 'match',
57530
+ via: 'param',
57531
+ value: hits[0].entry.value,
57532
+ matchedFullUrl: normalizePathname((_hits__entry_fullUrl = hits[0].entry.fullUrl) !== null && _hits__entry_fullUrl !== void 0 ? _hits__entry_fullUrl : pathname),
57533
+ params: hits[0].params
57534
+ };
57535
+ } else if (hits.length > 1) {
57536
+ result = {
57537
+ kind: 'ambiguous',
57538
+ values: hits.map(function(h) {
57539
+ return h.entry.value;
57540
+ })
57541
+ };
57542
+ } else {
57543
+ result = {
57544
+ kind: 'none',
57545
+ candidates: scoreCandidates(entries, pathname)
57546
+ };
57547
+ }
57548
+ return result;
57452
57549
  }
57453
- // (5) Identify roots
57454
- function collectRoots(byName) {
57455
- var roots = [];
57550
+ /**
57551
+ * Scores every candidate by shared leading segments (literal segment = 2,
57552
+ * param segment = 1, mismatch stops scoring) and returns the top 5 values.
57553
+ *
57554
+ * @param entries - The candidate entries to score.
57555
+ * @param pathname - The pathname being resolved.
57556
+ * @returns Up to five closest candidate values, best first.
57557
+ */ function scoreCandidates(entries, pathname) {
57558
+ var segments = splitSegments(normalizePathname(pathname));
57559
+ var scored = [];
57456
57560
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57457
57561
  try {
57458
- for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57459
- var treeNode = _step.value;
57460
- if (!treeNode.parent) {
57461
- roots.push(treeNode);
57562
+ for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57563
+ var entry = _step.value;
57564
+ if (entry.fullUrl === undefined) {
57565
+ continue;
57462
57566
  }
57463
- }
57464
- } catch (err) {
57465
- _didIteratorError = true;
57466
- _iteratorError = err;
57467
- } finally{
57468
- try {
57469
- if (!_iteratorNormalCompletion && _iterator.return != null) {
57470
- _iterator.return();
57567
+ var candidateSegments = splitSegments(entry.fullUrl);
57568
+ var score = 0;
57569
+ var maxIndex = Math.min(segments.length, candidateSegments.length);
57570
+ for(var i = 0; i < maxIndex; i += 1){
57571
+ if (segments[i] === candidateSegments[i]) {
57572
+ score += 2;
57573
+ } else if (candidateSegments[i].startsWith(':') || candidateSegments[i].startsWith('{')) {
57574
+ score += 1;
57575
+ } else {
57576
+ break;
57577
+ }
57471
57578
  }
57472
- } finally{
57473
- if (_didIteratorError) {
57474
- throw _iteratorError;
57579
+ if (score > 0) {
57580
+ scored.push({
57581
+ value: entry.value,
57582
+ score: score
57583
+ });
57475
57584
  }
57476
57585
  }
57477
- }
57478
- return roots;
57479
- }
57480
- // (6) Sort children deterministically by name
57481
- function sortChildren(byName, roots) {
57482
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57483
- try {
57484
- for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57485
- var treeNode = _step.value;
57486
- treeNode.children.sort(compareByName);
57487
- }
57488
57586
  } catch (err) {
57489
57587
  _didIteratorError = true;
57490
57588
  _iteratorError = err;
@@ -57499,133 +57597,91 @@ function sortChildren(byName, roots) {
57499
57597
  }
57500
57598
  }
57501
57599
  }
57502
- roots.sort(compareByName);
57600
+ scored.sort(function(a, b) {
57601
+ return b.score - a.score;
57602
+ });
57603
+ return scored.slice(0, 5).map(function(s) {
57604
+ return s.value;
57605
+ });
57503
57606
  }
57504
- // (7) Freeze: convert MutableTreeNode → RouteTreeNode (children: readonly).
57505
- function freezeTree(roots) {
57506
- var frozen = new Map();
57507
- var freeze = function freeze1(mut) {
57508
- var existing = frozen.get(mut.data.name);
57509
- if (existing) return existing;
57510
- var placeholder = {
57511
- data: mut.data,
57512
- fullUrl: mut.fullUrl,
57513
- parent: undefined,
57514
- children: []
57515
- };
57516
- frozen.set(mut.data.name, placeholder);
57517
- placeholder.parent = mut.parent ? freeze(mut.parent) : undefined;
57518
- placeholder.children = mut.children.map(freeze);
57519
- return placeholder;
57520
- };
57521
- var frozenRoots = roots.map(freeze);
57522
- return {
57523
- frozen: frozen,
57524
- frozenRoots: frozenRoots
57525
- };
57607
+
57608
+ function _array_like_to_array$2(arr, len) {
57609
+ if (len == null || len > arr.length) len = arr.length;
57610
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
57611
+ return arr2;
57526
57612
  }
57527
- function resolveParentName(node, byName) {
57528
- if (node.explicitParent) {
57529
- return node.explicitParent;
57530
- }
57531
- var lookupName = node.name.endsWith('.**') ? node.name.slice(0, -3) : node.name;
57532
- var lastDot = lookupName.lastIndexOf('.');
57533
- if (lastDot < 0) {
57534
- return undefined;
57535
- }
57536
- var candidate = lookupName.slice(0, lastDot);
57537
- // Confirm the candidate exists; otherwise fall back to undefined so the
57538
- // orphan check fires.
57539
- if (byName.has(candidate)) {
57540
- return candidate;
57541
- }
57542
- // Even if the candidate doesn't exist, return it so the orphan logic can
57543
- // surface an informative message.
57544
- return candidate;
57613
+ function _array_without_holes$1(arr) {
57614
+ if (Array.isArray(arr)) return _array_like_to_array$2(arr);
57545
57615
  }
57546
- function composeFullUrl(node) {
57547
- var segments = [];
57548
- var cursor = node;
57549
- while(cursor){
57550
- if (cursor.data.url !== undefined) {
57551
- segments.unshift(cursor.data.url);
57552
- }
57553
- cursor = cursor.parent;
57554
- }
57555
- if (segments.length === 0) {
57556
- return undefined;
57557
- }
57558
- // Join segments and collapse double slashes (root url '/' followed by
57559
- // child '/foo' would otherwise yield '//foo').
57560
- var joined = segments.join('');
57561
- var collapsed = joined.replaceAll(/\/{2,}/g, '/');
57562
- return collapsed.length === 0 ? '/' : collapsed;
57616
+ function _iterable_to_array$1(iter) {
57617
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
57563
57618
  }
57564
- function compareByName(a, b) {
57565
- if (a.data.name < b.data.name) {
57566
- return -1;
57567
- }
57568
- if (a.data.name > b.data.name) {
57569
- return 1;
57570
- }
57571
- return 0;
57619
+ function _non_iterable_spread$1() {
57620
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57621
+ }
57622
+ function _to_consumable_array$1(arr) {
57623
+ return _array_without_holes$1(arr) || _iterable_to_array$1(arr) || _unsupported_iterable_to_array$2(arr) || _non_iterable_spread$1();
57624
+ }
57625
+ function _unsupported_iterable_to_array$2(o, minLen) {
57626
+ if (!o) return;
57627
+ if (typeof o === "string") return _array_like_to_array$2(o, minLen);
57628
+ var n = Object.prototype.toString.call(o).slice(8, -1);
57629
+ if (n === "Object" && o.constructor) n = o.constructor.name;
57630
+ if (n === "Map" || n === "Set") return Array.from(n);
57631
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$2(o, minLen);
57572
57632
  }
57573
-
57574
57633
  /**
57575
- * Walks every supplied source through the extractor and emits a flat node and
57576
- * issue list, used when the caller has already gathered a complete glob/file
57577
- * set in memory.
57634
+ * Builds the parent-child UIRouter tree from a flat node list, computing each
57635
+ * state's full URL while preserving any extraction issues so callers see one
57636
+ * combined diagnostics view.
57578
57637
  *
57579
- * @param sources - The in-memory sources to extract from.
57580
- * @returns The merged extraction nodes, issues, and processed file count.
57581
- */ function resolveRouteSources(sources) {
57582
- var nodes = [];
57583
- var issues = [];
57638
+ * @param nodes - The flat route nodes extracted from sources.
57639
+ * @param extractIssues - Issues already discovered during extraction to forward.
57640
+ * @returns The constructed tree alongside the merged issue list.
57641
+ *
57642
+ * @__NO_SIDE_EFFECTS__
57643
+ */ function buildRouteTree(nodes, extractIssues) {
57644
+ var issues = _to_consumable_array$1(extractIssues);
57645
+ var byName = new Map();
57646
+ insertNodes(nodes, byName, issues);
57647
+ wireParentLinks(byName, issues);
57648
+ detectCycles(byName, issues);
57649
+ composeFullUrls(byName);
57650
+ var roots = collectRoots(byName);
57651
+ sortChildren(byName, roots);
57652
+ var _freezeTree = freezeTree(roots), frozen = _freezeTree.frozen, frozenRoots = _freezeTree.frozenRoots;
57653
+ var result = {
57654
+ roots: frozenRoots,
57655
+ byName: frozen,
57656
+ issues: issues,
57657
+ filesChecked: 0,
57658
+ nodeCount: byName.size
57659
+ };
57660
+ return result;
57661
+ }
57662
+ // (1) Insert nodes; report duplicates
57663
+ function insertNodes(nodes, byName, issues) {
57584
57664
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57585
57665
  try {
57586
- for(var _iterator = sources[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57587
- var source = _step.value;
57588
- var extracted = extractFile(source);
57589
- var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
57590
- try {
57591
- for(var _iterator1 = extracted.nodes[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
57592
- var node = _step1.value;
57593
- nodes.push(node);
57594
- }
57595
- } catch (err) {
57596
- _didIteratorError1 = true;
57597
- _iteratorError1 = err;
57598
- } finally{
57599
- try {
57600
- if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
57601
- _iterator1.return();
57602
- }
57603
- } finally{
57604
- if (_didIteratorError1) {
57605
- throw _iteratorError1;
57606
- }
57607
- }
57608
- }
57609
- var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
57610
- try {
57611
- for(var _iterator2 = extracted.issues[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
57612
- var issue = _step2.value;
57613
- issues.push(issue);
57614
- }
57615
- } catch (err) {
57616
- _didIteratorError2 = true;
57617
- _iteratorError2 = err;
57618
- } finally{
57619
- try {
57620
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
57621
- _iterator2.return();
57622
- }
57623
- } finally{
57624
- if (_didIteratorError2) {
57625
- throw _iteratorError2;
57626
- }
57627
- }
57666
+ for(var _iterator = nodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57667
+ var node = _step.value;
57668
+ if (byName.has(node.name)) {
57669
+ issues.push({
57670
+ code: 'DUPLICATE_STATE_NAME',
57671
+ severity: 'error',
57672
+ message: "State `".concat(node.name, "` is declared in multiple places. Keeping the first declaration."),
57673
+ file: node.file,
57674
+ line: node.line,
57675
+ stateName: node.name
57676
+ });
57677
+ continue;
57628
57678
  }
57679
+ byName.set(node.name, {
57680
+ data: node,
57681
+ fullUrl: undefined,
57682
+ parent: undefined,
57683
+ children: []
57684
+ });
57629
57685
  }
57630
57686
  } catch (err) {
57631
57687
  _didIteratorError = true;
@@ -57641,28 +57697,29 @@ function compareByName(a, b) {
57641
57697
  }
57642
57698
  }
57643
57699
  }
57644
- var result = {
57645
- nodes: nodes,
57646
- issues: issues,
57647
- filesChecked: sources.length
57648
- };
57649
- return result;
57650
57700
  }
57651
- /**
57652
- * Returns the relative module specifiers imported by `source` — used to plan
57653
- * the next round of file reads in transitive walking. Specifiers are
57654
- * left untouched (no `.ts` resolution); the caller normalizes them.
57655
- *
57656
- * @param source - The in-memory source to inspect.
57657
- * @returns The relative specifiers in original-source order.
57658
- */ function computeRelativeSpecifiers(source) {
57659
- var extracted = extractFile(source);
57660
- var out = [];
57701
+ // (2) Wire parent links
57702
+ function wireParentLinks(byName, issues) {
57661
57703
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57662
57704
  try {
57663
- for(var _iterator = extracted.importedFromRelative[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57664
- var imp = _step.value;
57665
- out.push(imp.moduleSpecifier);
57705
+ for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57706
+ var treeNode = _step.value;
57707
+ var parentName = resolveParentName(treeNode.data, byName);
57708
+ if (!parentName) continue;
57709
+ var parent = byName.get(parentName);
57710
+ if (!parent) {
57711
+ issues.push({
57712
+ code: 'ORPHAN_STATE',
57713
+ severity: 'warning',
57714
+ message: "State `".concat(treeNode.data.name, "` references parent `").concat(parentName, "` which is not declared in the analyzed sources."),
57715
+ file: treeNode.data.file,
57716
+ line: treeNode.data.line,
57717
+ stateName: treeNode.data.name
57718
+ });
57719
+ continue;
57720
+ }
57721
+ treeNode.parent = parent;
57722
+ parent.children.push(treeNode);
57666
57723
  }
57667
57724
  } catch (err) {
57668
57725
  _didIteratorError = true;
@@ -57678,148 +57735,71 @@ function compareByName(a, b) {
57678
57735
  }
57679
57736
  }
57680
57737
  }
57681
- return out;
57682
- }
57683
-
57684
- /**
57685
- * Pure tree-loading entry point. Resolves the supplied source list and builds
57686
- * the parent/child tree in one step so callers can stay thin.
57687
- *
57688
- * @param args - The in-memory sources to process.
57689
- * @returns The constructed route tree with extraction issues attached.
57690
- */ function loadRouteTree(args) {
57691
- var resolved = resolveRouteSources(args.sources);
57692
- var tree = buildRouteTree(resolved.nodes, resolved.issues);
57693
- var result = {
57694
- roots: tree.roots,
57695
- byName: tree.byName,
57696
- issues: tree.issues,
57697
- filesChecked: resolved.filesChecked,
57698
- nodeCount: tree.nodeCount
57699
- };
57700
- return result;
57701
- }
57702
-
57703
- function _array_like_to_array$2(arr, len) {
57704
- if (len == null || len > arr.length) len = arr.length;
57705
- for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
57706
- return arr2;
57707
- }
57708
- function _array_with_holes$1(arr) {
57709
- if (Array.isArray(arr)) return arr;
57710
57738
  }
57711
- function _iterable_to_array_limit$1(arr, i) {
57712
- var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
57713
- if (_i == null) return;
57714
- var _arr = [];
57715
- var _n = true;
57716
- var _d = false;
57717
- var _s, _e;
57739
+ // (3) Detect cycles. A cycle is impossible via dot-prefix linkage but the
57740
+ // explicit `parent` field can introduce one (`a.parent = 'b'`,
57741
+ // `b.parent = 'a'`). Walk from each node and break the chain on revisits.
57742
+ function detectCycles(byName, issues) {
57743
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57718
57744
  try {
57719
- for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
57720
- _arr.push(_s.value);
57721
- if (i && _arr.length === i) break;
57745
+ for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57746
+ var treeNode = _step.value;
57747
+ if (!treeNode.parent) continue;
57748
+ detectCycleFor(treeNode, issues);
57722
57749
  }
57723
57750
  } catch (err) {
57724
- _d = true;
57725
- _e = err;
57751
+ _didIteratorError = true;
57752
+ _iteratorError = err;
57726
57753
  } finally{
57727
57754
  try {
57728
- if (!_n && _i["return"] != null) _i["return"]();
57755
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
57756
+ _iterator.return();
57757
+ }
57729
57758
  } finally{
57730
- if (_d) throw _e;
57759
+ if (_didIteratorError) {
57760
+ throw _iteratorError;
57761
+ }
57731
57762
  }
57732
57763
  }
57733
- return _arr;
57734
- }
57735
- function _non_iterable_rest$1() {
57736
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57737
57764
  }
57738
- function _sliced_to_array$1(arr, i) {
57739
- return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$2(arr, i) || _non_iterable_rest$1();
57740
- }
57741
- function _unsupported_iterable_to_array$2(o, minLen) {
57742
- if (!o) return;
57743
- if (typeof o === "string") return _array_like_to_array$2(o, minLen);
57744
- var n = Object.prototype.toString.call(o).slice(8, -1);
57745
- if (n === "Object" && o.constructor) n = o.constructor.name;
57746
- if (n === "Map" || n === "Set") return Array.from(n);
57747
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$2(o, minLen);
57748
- }
57749
- /**
57750
- * Pure URL ↔ route matcher, shared by the dev-server `dbx_route_resolve_url`
57751
- * tool and (mirrored, not imported) by the firebase-server/mcp `url-models`
57752
- * runtime tool.
57753
- *
57754
- * No ts-morph, no node:fs — operates on a flat list of candidates each carrying
57755
- * a composed `fullUrl` and an opaque `value`. Matching mirrors UIRouter's
57756
- * literal-then-param preference: a literal segment-for-segment match wins over a
57757
- * parameterised one, and ties at either tier collapse to an `ambiguous` result.
57758
- */ /**
57759
- * Collapses an empty pathname to `/` and strips a single trailing slash so
57760
- * `/a/b/` and `/a/b` compare equal.
57761
- *
57762
- * @param pathname - The raw pathname to normalize.
57763
- * @returns The normalized pathname.
57764
- */ function normalizePathname(pathname) {
57765
- var result;
57766
- if (pathname.length === 0) {
57767
- result = '/';
57768
- } else if (pathname.length > 1 && pathname.endsWith('/')) {
57769
- result = pathname.slice(0, -1);
57770
- } else {
57771
- result = pathname;
57765
+ function detectCycleFor(treeNode, issues) {
57766
+ var seen = new Set();
57767
+ seen.add(treeNode.data.name);
57768
+ var cursor = treeNode.parent;
57769
+ while(cursor){
57770
+ if (seen.has(cursor.data.name)) {
57771
+ issues.push({
57772
+ code: 'CYCLE_DETECTED',
57773
+ severity: 'error',
57774
+ message: "Cycle detected involving `".concat(treeNode.data.name, "` and `").concat(cursor.data.name, "`. Severing the parent link."),
57775
+ file: treeNode.data.file,
57776
+ line: treeNode.data.line,
57777
+ stateName: treeNode.data.name
57778
+ });
57779
+ detachFromParent(treeNode);
57780
+ return;
57781
+ }
57782
+ seen.add(cursor.data.name);
57783
+ cursor = cursor.parent;
57772
57784
  }
57773
- return result;
57774
- }
57775
- /**
57776
- * Splits a path into its non-empty segments (the leading slash is dropped).
57777
- *
57778
- * @param path - The path or URL pattern to split.
57779
- * @returns The ordered segment list, empty for `/`.
57780
- */ function splitSegments(path) {
57781
- var normalized = path.startsWith('/') ? path.slice(1) : path;
57782
- return normalized.length === 0 ? [] : normalized.split('/');
57783
- }
57784
- /**
57785
- * Whether a composed URL contains at least one `:param` or `{param}` segment.
57786
- *
57787
- * @param path - The composed URL pattern.
57788
- * @returns `true` when the pattern declares a parameter.
57789
- */ function hasParamSegment(path) {
57790
- return path.includes(':') || path.includes('{');
57791
57785
  }
57792
- /**
57793
- * Attempts a segment-for-segment match of a route pattern against concrete
57794
- * input segments. `:name` and `{name}` / `{name:type}` segments capture the
57795
- * input value (URL-decoded); literal segments must match exactly.
57796
- *
57797
- * @param route - The route pattern's segments.
57798
- * @param input - The concrete URL's segments.
57799
- * @returns The captured params when every segment matches, else `undefined`.
57800
- */ function tryMatchSegments(route, input) {
57801
- if (route.length !== input.length) {
57802
- return undefined;
57786
+ function detachFromParent(treeNode) {
57787
+ if (!treeNode.parent) return;
57788
+ var idx = treeNode.parent.children.indexOf(treeNode);
57789
+ if (idx >= 0) {
57790
+ treeNode.parent.children.splice(idx, 1);
57803
57791
  }
57804
- var params = {};
57805
- var result = params;
57792
+ treeNode.parent = undefined;
57793
+ }
57794
+ // (4) Compose full URLs (parent walk; root url is not prefixed). UIRouter's
57795
+ // own logic is more nuanced (some states overwrite their parent's URL with
57796
+ // a leading `^`), but this gives a useful approximation for the common case.
57797
+ function composeFullUrls(byName) {
57806
57798
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57807
57799
  try {
57808
- for(var _iterator = route.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57809
- var _step_value = _sliced_to_array$1(_step.value, 2), i = _step_value[0], r = _step_value[1];
57810
- var v = input[i];
57811
- if (r.startsWith(':')) {
57812
- var key = r.slice(1);
57813
- params[key] = decodeURIComponent(v);
57814
- } else if (r.startsWith('{') && r.endsWith('}')) {
57815
- var inner = r.slice(1, -1);
57816
- var colonIdx = inner.indexOf(':');
57817
- var key1 = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
57818
- params[key1] = decodeURIComponent(v);
57819
- } else if (r !== v) {
57820
- result = undefined;
57821
- break;
57822
- }
57800
+ for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57801
+ var treeNode = _step.value;
57802
+ treeNode.fullUrl = composeFullUrl(treeNode);
57823
57803
  }
57824
57804
  } catch (err) {
57825
57805
  _didIteratorError = true;
@@ -57835,31 +57815,16 @@ function _unsupported_iterable_to_array$2(o, minLen) {
57835
57815
  }
57836
57816
  }
57837
57817
  }
57838
- return result;
57839
57818
  }
57840
- /**
57841
- * Extracts param-name segments from a composed UIRouter URL. Recognises:
57842
- * - `:name` (Express-style)
57843
- * - `{name}` (UIRouter type-less)
57844
- * - `{name:type}` and `{name:regex}` (UIRouter typed / regex)
57845
- * Order is preserved; duplicates are de-duplicated by first occurrence.
57846
- *
57847
- * @param fullUrl - Composed URL (e.g. `/{orgId}/users/:userId`) or undefined.
57848
- * @returns The param key list in declaration order, or an empty array.
57849
- */ function extractUrlParamKeys(fullUrl) {
57850
- if (fullUrl === undefined || fullUrl.length === 0) {
57851
- return [];
57852
- }
57853
- var seen = new Set();
57854
- var keys = [];
57819
+ // (5) Identify roots
57820
+ function collectRoots(byName) {
57821
+ var roots = [];
57855
57822
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57856
57823
  try {
57857
- for(var _iterator = fullUrl.split('/')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57858
- var segment = _step.value;
57859
- var key = extractParamKeyFromSegment(segment);
57860
- if (key !== undefined && !seen.has(key)) {
57861
- seen.add(key);
57862
- keys.push(key);
57824
+ for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57825
+ var treeNode = _step.value;
57826
+ if (!treeNode.parent) {
57827
+ roots.push(treeNode);
57863
57828
  }
57864
57829
  }
57865
57830
  } catch (err) {
@@ -57876,64 +57841,15 @@ function _unsupported_iterable_to_array$2(o, minLen) {
57876
57841
  }
57877
57842
  }
57878
57843
  }
57879
- return keys;
57880
- }
57881
- function extractParamKeyFromSegment(segment) {
57882
- if (segment.startsWith(':')) {
57883
- var key = segment.slice(1);
57884
- return key.length > 0 ? key : undefined;
57885
- }
57886
- if (segment.startsWith('{') && segment.endsWith('}')) {
57887
- var inner = segment.slice(1, -1);
57888
- var colonIdx = inner.indexOf(':');
57889
- var rawKey = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
57890
- var key1 = rawKey.trim();
57891
- return key1.length > 0 ? key1 : undefined;
57892
- }
57893
- return undefined;
57894
- }
57895
- /**
57896
- * Matches a pathname against a flat candidate list, preferring a literal
57897
- * (exact composed-URL) match over a parameterised one. A tie at either tier
57898
- * collapses to `ambiguous`; otherwise the closest near-misses are scored and
57899
- * returned in a `none` result.
57900
- *
57901
- * @param input - The candidate entries and the pathname to resolve.
57902
- * @returns A discriminated match / ambiguous / none result.
57903
- */ function matchUrlAgainstEntries(input) {
57904
- var pathname = normalizePathname(input.pathname);
57905
- var literal = matchLiteral(input.entries, pathname);
57906
- var result;
57907
- if (literal.length === 1) {
57908
- var _literal__fullUrl;
57909
- result = {
57910
- kind: 'match',
57911
- via: 'literal',
57912
- value: literal[0].value,
57913
- matchedFullUrl: normalizePathname((_literal__fullUrl = literal[0].fullUrl) !== null && _literal__fullUrl !== void 0 ? _literal__fullUrl : pathname),
57914
- params: {}
57915
- };
57916
- } else if (literal.length > 1) {
57917
- result = {
57918
- kind: 'ambiguous',
57919
- values: literal.map(function(e) {
57920
- return e.value;
57921
- })
57922
- };
57923
- } else {
57924
- result = matchParamOrNone(input.entries, pathname);
57925
- }
57926
- return result;
57844
+ return roots;
57927
57845
  }
57928
- function matchLiteral(entries, pathname) {
57929
- var out = [];
57846
+ // (6) Sort children deterministically by name
57847
+ function sortChildren(byName, roots) {
57930
57848
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57931
57849
  try {
57932
- for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57933
- var entry = _step.value;
57934
- if (entry.fullUrl !== undefined && normalizePathname(entry.fullUrl) === pathname) {
57935
- out.push(entry);
57936
- }
57850
+ for(var _iterator = byName.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57851
+ var treeNode = _step.value;
57852
+ treeNode.children.sort(compareByName);
57937
57853
  }
57938
57854
  } catch (err) {
57939
57855
  _didIteratorError = true;
@@ -57949,24 +57865,135 @@ function matchLiteral(entries, pathname) {
57949
57865
  }
57950
57866
  }
57951
57867
  }
57952
- return out;
57868
+ roots.sort(compareByName);
57953
57869
  }
57954
- function matchParamOrNone(entries, pathname) {
57955
- var inputSegments = splitSegments(pathname);
57956
- var hits = [];
57870
+ // (7) Freeze: convert MutableTreeNode → RouteTreeNode (children: readonly).
57871
+ function freezeTree(roots) {
57872
+ var frozen = new Map();
57873
+ var freeze = function freeze1(mut) {
57874
+ var existing = frozen.get(mut.data.name);
57875
+ if (existing) return existing;
57876
+ var placeholder = {
57877
+ data: mut.data,
57878
+ fullUrl: mut.fullUrl,
57879
+ parent: undefined,
57880
+ children: []
57881
+ };
57882
+ frozen.set(mut.data.name, placeholder);
57883
+ placeholder.parent = mut.parent ? freeze(mut.parent) : undefined;
57884
+ placeholder.children = mut.children.map(freeze);
57885
+ return placeholder;
57886
+ };
57887
+ var frozenRoots = roots.map(freeze);
57888
+ return {
57889
+ frozen: frozen,
57890
+ frozenRoots: frozenRoots
57891
+ };
57892
+ }
57893
+ function resolveParentName(node, byName) {
57894
+ if (node.explicitParent) {
57895
+ return node.explicitParent;
57896
+ }
57897
+ var lookupName = node.name.endsWith('.**') ? node.name.slice(0, -3) : node.name;
57898
+ var lastDot = lookupName.lastIndexOf('.');
57899
+ if (lastDot < 0) {
57900
+ return undefined;
57901
+ }
57902
+ var candidate = lookupName.slice(0, lastDot);
57903
+ // Confirm the candidate exists; otherwise fall back to undefined so the
57904
+ // orphan check fires.
57905
+ if (byName.has(candidate)) {
57906
+ return candidate;
57907
+ }
57908
+ // Even if the candidate doesn't exist, return it so the orphan logic can
57909
+ // surface an informative message.
57910
+ return candidate;
57911
+ }
57912
+ function composeFullUrl(node) {
57913
+ var segments = [];
57914
+ var cursor = node;
57915
+ while(cursor){
57916
+ if (cursor.data.url !== undefined) {
57917
+ segments.unshift(cursor.data.url);
57918
+ }
57919
+ cursor = cursor.parent;
57920
+ }
57921
+ if (segments.length === 0) {
57922
+ return undefined;
57923
+ }
57924
+ // Join segments and collapse double slashes (root url '/' followed by
57925
+ // child '/foo' would otherwise yield '//foo').
57926
+ var joined = segments.join('');
57927
+ // Strip any UIRouter query/hash suffix (e.g. `/:schoolJob?slotIndex`) so the
57928
+ // stored `fullUrl` is path-only — keeping build-time param extraction and the
57929
+ // runtime pathname matcher in agreement.
57930
+ var collapsed = stripUrlQueryAndHash(joined).replaceAll(/\/{2,}/g, '/');
57931
+ return collapsed.length === 0 ? '/' : collapsed;
57932
+ }
57933
+ function compareByName(a, b) {
57934
+ if (a.data.name < b.data.name) {
57935
+ return -1;
57936
+ }
57937
+ if (a.data.name > b.data.name) {
57938
+ return 1;
57939
+ }
57940
+ return 0;
57941
+ }
57942
+
57943
+ /**
57944
+ * Walks every supplied source through the extractor and emits a flat node and
57945
+ * issue list, used when the caller has already gathered a complete glob/file
57946
+ * set in memory.
57947
+ *
57948
+ * @param sources - The in-memory sources to extract from.
57949
+ * @returns The merged extraction nodes, issues, and processed file count.
57950
+ */ function resolveRouteSources(sources) {
57951
+ var nodes = [];
57952
+ var issues = [];
57957
57953
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
57958
57954
  try {
57959
- for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57960
- var entry = _step.value;
57961
- if (entry.fullUrl === undefined || !hasParamSegment(entry.fullUrl)) {
57962
- continue;
57955
+ for(var _iterator = sources[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
57956
+ var source = _step.value;
57957
+ var extracted = extractFile(source);
57958
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
57959
+ try {
57960
+ for(var _iterator1 = extracted.nodes[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
57961
+ var node = _step1.value;
57962
+ nodes.push(node);
57963
+ }
57964
+ } catch (err) {
57965
+ _didIteratorError1 = true;
57966
+ _iteratorError1 = err;
57967
+ } finally{
57968
+ try {
57969
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
57970
+ _iterator1.return();
57971
+ }
57972
+ } finally{
57973
+ if (_didIteratorError1) {
57974
+ throw _iteratorError1;
57975
+ }
57976
+ }
57963
57977
  }
57964
- var params = tryMatchSegments(splitSegments(entry.fullUrl), inputSegments);
57965
- if (params) {
57966
- hits.push({
57967
- entry: entry,
57968
- params: params
57969
- });
57978
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
57979
+ try {
57980
+ for(var _iterator2 = extracted.issues[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
57981
+ var issue = _step2.value;
57982
+ issues.push(issue);
57983
+ }
57984
+ } catch (err) {
57985
+ _didIteratorError2 = true;
57986
+ _iteratorError2 = err;
57987
+ } finally{
57988
+ try {
57989
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
57990
+ _iterator2.return();
57991
+ }
57992
+ } finally{
57993
+ if (_didIteratorError2) {
57994
+ throw _iteratorError2;
57995
+ }
57996
+ }
57970
57997
  }
57971
57998
  }
57972
57999
  } catch (err) {
@@ -57983,66 +58010,28 @@ function matchParamOrNone(entries, pathname) {
57983
58010
  }
57984
58011
  }
57985
58012
  }
57986
- var result;
57987
- if (hits.length === 1) {
57988
- var _hits__entry_fullUrl;
57989
- result = {
57990
- kind: 'match',
57991
- via: 'param',
57992
- value: hits[0].entry.value,
57993
- matchedFullUrl: normalizePathname((_hits__entry_fullUrl = hits[0].entry.fullUrl) !== null && _hits__entry_fullUrl !== void 0 ? _hits__entry_fullUrl : pathname),
57994
- params: hits[0].params
57995
- };
57996
- } else if (hits.length > 1) {
57997
- result = {
57998
- kind: 'ambiguous',
57999
- values: hits.map(function(h) {
58000
- return h.entry.value;
58001
- })
58002
- };
58003
- } else {
58004
- result = {
58005
- kind: 'none',
58006
- candidates: scoreCandidates(entries, pathname)
58007
- };
58008
- }
58013
+ var result = {
58014
+ nodes: nodes,
58015
+ issues: issues,
58016
+ filesChecked: sources.length
58017
+ };
58009
58018
  return result;
58010
58019
  }
58011
58020
  /**
58012
- * Scores every candidate by shared leading segments (literal segment = 2,
58013
- * param segment = 1, mismatch stops scoring) and returns the top 5 values.
58021
+ * Returns the relative module specifiers imported by `source` used to plan
58022
+ * the next round of file reads in transitive walking. Specifiers are
58023
+ * left untouched (no `.ts` resolution); the caller normalizes them.
58014
58024
  *
58015
- * @param entries - The candidate entries to score.
58016
- * @param pathname - The pathname being resolved.
58017
- * @returns Up to five closest candidate values, best first.
58018
- */ function scoreCandidates(entries, pathname) {
58019
- var segments = splitSegments(normalizePathname(pathname));
58020
- var scored = [];
58025
+ * @param source - The in-memory source to inspect.
58026
+ * @returns The relative specifiers in original-source order.
58027
+ */ function computeRelativeSpecifiers(source) {
58028
+ var extracted = extractFile(source);
58029
+ var out = [];
58021
58030
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
58022
58031
  try {
58023
- for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
58024
- var entry = _step.value;
58025
- if (entry.fullUrl === undefined) {
58026
- continue;
58027
- }
58028
- var candidateSegments = splitSegments(entry.fullUrl);
58029
- var score = 0;
58030
- var maxIndex = Math.min(segments.length, candidateSegments.length);
58031
- for(var i = 0; i < maxIndex; i += 1){
58032
- if (segments[i] === candidateSegments[i]) {
58033
- score += 2;
58034
- } else if (candidateSegments[i].startsWith(':') || candidateSegments[i].startsWith('{')) {
58035
- score += 1;
58036
- } else {
58037
- break;
58038
- }
58039
- }
58040
- if (score > 0) {
58041
- scored.push({
58042
- value: entry.value,
58043
- score: score
58044
- });
58045
- }
58032
+ for(var _iterator = extracted.importedFromRelative[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
58033
+ var imp = _step.value;
58034
+ out.push(imp.moduleSpecifier);
58046
58035
  }
58047
58036
  } catch (err) {
58048
58037
  _didIteratorError = true;
@@ -58058,12 +58047,26 @@ function matchParamOrNone(entries, pathname) {
58058
58047
  }
58059
58048
  }
58060
58049
  }
58061
- scored.sort(function(a, b) {
58062
- return b.score - a.score;
58063
- });
58064
- return scored.slice(0, 5).map(function(s) {
58065
- return s.value;
58066
- });
58050
+ return out;
58051
+ }
58052
+
58053
+ /**
58054
+ * Pure tree-loading entry point. Resolves the supplied source list and builds
58055
+ * the parent/child tree in one step so callers can stay thin.
58056
+ *
58057
+ * @param args - The in-memory sources to process.
58058
+ * @returns The constructed route tree with extraction issues attached.
58059
+ */ function loadRouteTree(args) {
58060
+ var resolved = resolveRouteSources(args.sources);
58061
+ var tree = buildRouteTree(resolved.nodes, resolved.issues);
58062
+ var result = {
58063
+ roots: tree.roots,
58064
+ byName: tree.byName,
58065
+ issues: tree.issues,
58066
+ filesChecked: resolved.filesChecked,
58067
+ nodeCount: tree.nodeCount
58068
+ };
58069
+ return result;
58067
58070
  }
58068
58071
 
58069
58072
  function _tagged_template_literal(strings, raw) {
@@ -58290,6 +58293,13 @@ function _unsupported_iterable_to_array$1(o, minLen) {
58290
58293
  * promoted to `<collectionName>/<id>` at runtime via the model identity).
58291
58294
  * - `gb/:id/gbe/{authUid}` — alternating literal / placeholder segments (even
58292
58295
  * count) → `kind: 'key'` (a full FirestoreModelKey for a subcollection model).
58296
+ * An odd (id) segment may also be a `{const:<id>}` token for a fixed/singleton
58297
+ * id (e.g. `wk/:uid/wkn/{const:0}`); it is normalized to the bare literal in
58298
+ * the parsed `keyTemplate` so the runtime emits it verbatim, while a forgotten
58299
+ * `:` (a bare `note`) still fails as malformed.
58300
+ * - `{flatKey:<param>}` — single token → `kind: 'flatKey'`: the `<param>` URL
58301
+ * value IS a whole two-way-flat FirestoreModelKey (`r_<id>_cs_<id>_d_<id>`),
58302
+ * un-flattened at runtime. For pages that pack a full key into one URL segment.
58293
58303
  * - (absent, list tag) → `kind: 'list'`.
58294
58304
  *
58295
58305
  * This module is deliberately runtime-dependency-free (no ts-morph): the same
@@ -58300,6 +58310,14 @@ function _unsupported_iterable_to_array$1(o, minLen) {
58300
58310
  */ var MODEL_TYPE_RE = RegExp("^[a-zA-Z][a-zA-Z0-9]*$", "u");
58301
58311
  var LITERAL_SEGMENT_RE = RegExp("^[a-zA-Z0-9][a-zA-Z0-9_-]*$", "u");
58302
58312
  var AUTH_UID_PLACEHOLDER = '{authUid}';
58313
+ /**
58314
+ * Matches a `{const:<id>}` fixed-id token (e.g. `{const:0}`), capturing the
58315
+ * literal id. The id obeys the same shape as a literal collection segment.
58316
+ */ var CONST_TOKEN_RE = RegExp("^\\{const:([a-zA-Z0-9][a-zA-Z0-9_-]*)\\}$", "u");
58317
+ /**
58318
+ * Matches a `{flatKey:<param>}` token (e.g. `{flatKey:region}`), capturing the
58319
+ * route param name whose URL value holds a whole two-way-flat FirestoreModelKey.
58320
+ */ var FLAT_KEY_TOKEN_RE = RegExp("^\\{flatKey:([a-zA-Z_][a-zA-Z0-9_]*)\\}$", "u");
58303
58321
  /**
58304
58322
  * The bare `@dbxRouteModel` tag name (without the leading `@`).
58305
58323
  */ var ROUTE_MODEL_TAG = 'dbxRouteModel';
@@ -58379,7 +58397,7 @@ function parseModelTag(tokens, description) {
58379
58397
  model: {
58380
58398
  modelType: tokens[0],
58381
58399
  kind: parsedKey.kind,
58382
- keyTemplate: tokens[1],
58400
+ keyTemplate: parsedKey.keyTemplate,
58383
58401
  description: description,
58384
58402
  routeParams: parsedKey.routeParams
58385
58403
  }
@@ -58414,17 +58432,29 @@ function parseKeyTemplate(keyTemplate) {
58414
58432
  return result;
58415
58433
  }
58416
58434
  function parseSingleSegmentKey(segment, keyTemplate) {
58435
+ var flatKeyParam = flatKeyTokenParam(segment);
58417
58436
  var placeholder = placeholderParam(segment);
58418
58437
  var result;
58419
- if (placeholder === undefined) {
58438
+ if (flatKeyParam !== undefined) {
58439
+ // The whole key lives in one URL param; the runtime un-flattens it.
58440
+ result = {
58441
+ ok: true,
58442
+ kind: 'flatKey',
58443
+ keyTemplate: keyTemplate,
58444
+ routeParams: [
58445
+ flatKeyParam
58446
+ ]
58447
+ };
58448
+ } else if (placeholder === undefined) {
58420
58449
  result = {
58421
58450
  ok: false,
58422
- message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).")
58451
+ message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a flattened-key token (`{flatKey:<param>}`).")
58423
58452
  };
58424
58453
  } else {
58425
58454
  result = {
58426
58455
  ok: true,
58427
58456
  kind: 'id',
58457
+ keyTemplate: keyTemplate,
58428
58458
  routeParams: placeholder.routeParam === undefined ? [] : [
58429
58459
  placeholder.routeParam
58430
58460
  ]
@@ -58434,6 +58464,10 @@ function parseSingleSegmentKey(segment, keyTemplate) {
58434
58464
  }
58435
58465
  function parseAlternatingKey(segments, keyTemplate) {
58436
58466
  var routeParams = [];
58467
+ // The normalized template substitutes any `{const:<id>}` token back to its
58468
+ // bare literal so the runtime `resolveFullKey` (which emits non-placeholder
58469
+ // segments verbatim) round-trips without needing to understand the token.
58470
+ var normalizedSegments = [];
58437
58471
  var message;
58438
58472
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
58439
58473
  try {
@@ -58444,14 +58478,20 @@ function parseAlternatingKey(segments, keyTemplate) {
58444
58478
  message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a literal collection name.");
58445
58479
  break;
58446
58480
  }
58481
+ normalizedSegments.push(segment);
58447
58482
  } else {
58448
58483
  var placeholder = placeholderParam(segment);
58449
- if (placeholder === undefined) {
58450
- message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).");
58484
+ var constId = constTokenId(segment);
58485
+ if (placeholder !== undefined) {
58486
+ if (placeholder.routeParam !== undefined) {
58487
+ routeParams.push(placeholder.routeParam);
58488
+ }
58489
+ normalizedSegments.push(segment);
58490
+ } else if (constId === undefined) {
58491
+ message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a fixed id (`{const:<id>}`).");
58451
58492
  break;
58452
- }
58453
- if (placeholder.routeParam !== undefined) {
58454
- routeParams.push(placeholder.routeParam);
58493
+ } else {
58494
+ normalizedSegments.push(constId);
58455
58495
  }
58456
58496
  }
58457
58497
  }
@@ -58472,6 +58512,7 @@ function parseAlternatingKey(segments, keyTemplate) {
58472
58512
  return message === undefined ? {
58473
58513
  ok: true,
58474
58514
  kind: 'key',
58515
+ keyTemplate: normalizedSegments.join('/'),
58475
58516
  routeParams: routeParams
58476
58517
  } : {
58477
58518
  ok: false,
@@ -58501,6 +58542,28 @@ function parseAlternatingKey(segments, keyTemplate) {
58501
58542
  }
58502
58543
  return result;
58503
58544
  }
58545
+ /**
58546
+ * Extracts the literal id from a `{const:<id>}` fixed-id token, used for a
58547
+ * fixed/singleton subcollection id at an odd key-template position. Returns
58548
+ * `undefined` for any non-`{const:…}` segment.
58549
+ *
58550
+ * @param segment - The single key-template segment to classify.
58551
+ * @returns The captured literal id, or `undefined` when not a const token.
58552
+ */ function constTokenId(segment) {
58553
+ var match = CONST_TOKEN_RE.exec(segment);
58554
+ return match === null ? undefined : match[1];
58555
+ }
58556
+ /**
58557
+ * Extracts the route param name from a `{flatKey:<param>}` token, whose URL
58558
+ * value is a whole two-way-flat FirestoreModelKey un-flattened at runtime.
58559
+ * Returns `undefined` for any non-`{flatKey:…}` segment.
58560
+ *
58561
+ * @param segment - The single key-template segment to classify.
58562
+ * @returns The captured route param name, or `undefined` when not a flatKey token.
58563
+ */ function flatKeyTokenParam(segment) {
58564
+ var match = FLAT_KEY_TOKEN_RE.exec(segment);
58565
+ return match === null ? undefined : match[1];
58566
+ }
58504
58567
 
58505
58568
  // MARK: Component extraction
58506
58569
  /**
@@ -58643,7 +58706,7 @@ function _unsupported_iterable_to_array(o, minLen) {
58643
58706
  * Version stamp embedded in `route.manifest.json`. Runtime loaders refuse
58644
58707
  * manifests whose `version` does not match. Mirror in firebase-server/mcp's
58645
58708
  * `ROUTE_MANIFEST_VERSION` — bump both together.
58646
- */ var ROUTE_MANIFEST_VERSION = 1;
58709
+ */ var ROUTE_MANIFEST_VERSION = 2;
58647
58710
  /**
58648
58711
  * Builds the route manifest from a set of app sources.
58649
58712
  *
@@ -60216,6 +60279,7 @@ exports.setCliTimeoutMs = setCliTimeoutMs;
60216
60279
  exports.setCliVerbose = setCliVerbose;
60217
60280
  exports.splitListTagText = splitListTagText$1;
60218
60281
  exports.splitSegments = splitSegments;
60282
+ exports.stripUrlQueryAndHash = stripUrlQueryAndHash;
60219
60283
  exports.toActionEntryInfo = toActionEntryInfo;
60220
60284
  exports.toFilterEntryInfo = toFilterEntryInfo;
60221
60285
  exports.toFormFieldInfo = toFormFieldInfo;