1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3    return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.getUserDefinedDeepDynamicRoute = exports.getExactRoutesAsync = exports.getExactRoutes = exports.getRoutesAsync = exports.getRoutes = exports.assertDuplicateRoutes = exports.generateDynamic = exports.generateDynamicFromSegment = exports.getRecursiveTree = void 0;
7const import_mode_1 = __importDefault(require("./import-mode"));
8const matchers_1 = require("./matchers");
9/** Convert a flat map of file nodes into a nested tree of files. */
10function getRecursiveTree(files) {
11    const tree = {
12        name: '',
13        children: [],
14        parents: [],
15        node: null,
16    };
17    for (const file of files) {
18        // ['(tab)', 'settings', '[...another]']
19        const parts = file.normalizedName.split('/');
20        let currentNode = tree;
21        for (let i = 0; i < parts.length; i++) {
22            const part = parts[i];
23            if (i === parts.length - 1 && part === '_layout') {
24                if (currentNode.node) {
25                    const overwritten = currentNode.node.contextKey;
26                    throw new Error(`Higher priority Layout Route "${file.contextKey}" overriding redundant Layout Route "${overwritten}". Remove the Layout Route "${overwritten}" to fix this.`);
27                }
28                continue;
29            }
30            const existing = currentNode.children.find((item) => item.name === part);
31            if (existing) {
32                currentNode = existing;
33            }
34            else {
35                const newNode = {
36                    name: part,
37                    children: [],
38                    parents: [...currentNode.parents, currentNode.name],
39                    node: null,
40                };
41                currentNode.children.push(newNode);
42                currentNode = newNode;
43            }
44        }
45        currentNode.node = file;
46    }
47    if (process.env.NODE_ENV !== 'production') {
48        assertDeprecatedFormat(tree);
49    }
50    return tree;
51}
52exports.getRecursiveTree = getRecursiveTree;
53function assertDeprecatedFormat(tree) {
54    for (const child of tree.children) {
55        if (child.node && child.children.length && !child.node.normalizedName.endsWith('_layout')) {
56            const ext = child.node.contextKey.split('.').pop();
57            throw new Error(`Using deprecated Layout Route format: Move \`./app/${child.node.normalizedName}.${ext}\` to \`./app/${child.node.normalizedName}/_layout.${ext}\``);
58        }
59        assertDeprecatedFormat(child);
60    }
61}
62function getTreeNodesAsRouteNodes(nodes) {
63    return nodes
64        .map((node) => treeNodeToRouteNode(node))
65        .flat()
66        .filter(Boolean);
67}
68function generateDynamicFromSegment(name) {
69    const deepDynamicName = (0, matchers_1.matchDeepDynamicRouteName)(name);
70    const dynamicName = deepDynamicName ?? (0, matchers_1.matchDynamicName)(name);
71    return dynamicName ? { name: dynamicName, deep: !!deepDynamicName } : null;
72}
73exports.generateDynamicFromSegment = generateDynamicFromSegment;
74function generateDynamic(name) {
75    const description = name
76        .split('/')
77        .map((segment) => generateDynamicFromSegment(segment))
78        .filter(Boolean);
79    return description.length === 0 ? null : description;
80}
81exports.generateDynamic = generateDynamic;
82function collapseRouteSegments(route) {
83    return (0, matchers_1.stripGroupSegmentsFromPath)(route.replace(/\/index$/, ''));
84}
85/**
86 * Given a route node and a name representing the group name,
87 * find the nearest child matching the name.
88 *
89 * Doesn't support slashes in the name.
90 * Routes like `explore/(something)/index` will be matched against `explore`.
91 *
92 */
93function getDefaultInitialRoute(node, name) {
94    return node.children.find((node) => collapseRouteSegments(node.route) === name);
95}
96function applyDefaultInitialRouteName(node) {
97    const groupName = (0, matchers_1.matchGroupName)(node.route);
98    if (!node.children?.length) {
99        return node;
100    }
101    // Guess at the initial route based on the group name.
102    // TODO(EvanBacon): Perhaps we should attempt to warn when the group doesn't match any child routes.
103    let initialRouteName = groupName ? getDefaultInitialRoute(node, groupName)?.route : undefined;
104    const loaded = node.loadRoute();
105    if (loaded?.unstable_settings) {
106        // Allow unstable_settings={ initialRouteName: '...' } to override the default initial route name.
107        initialRouteName = loaded.unstable_settings.initialRouteName ?? initialRouteName;
108        if (groupName) {
109            // Allow unstable_settings={ 'custom': { initialRouteName: '...' } } to override the less specific initial route name.
110            const groupSpecificInitialRouteName = loaded.unstable_settings?.[groupName]?.initialRouteName;
111            initialRouteName = groupSpecificInitialRouteName ?? initialRouteName;
112        }
113    }
114    return {
115        ...node,
116        initialRouteName,
117    };
118}
119function folderNodeToRouteNode({ name, children }) {
120    // Empty folder, skip it.
121    if (!children.length) {
122        return null;
123    }
124    // When there's a directory, but no layout route file (with valid export), the child routes won't be grouped.
125    // This pushes all children into the nearest layout route.
126    return getTreeNodesAsRouteNodes(children.map((child) => {
127        return {
128            ...child,
129            name: [name, child.name].filter(Boolean).join('/'),
130        };
131    }));
132}
133function fileNodeToRouteNode(tree) {
134    const { name, node, children } = tree;
135    if (!node)
136        throw new Error('node must be defined');
137    const dynamic = generateDynamic(name);
138    const clones = extrapolateGroupRoutes(name, node.contextKey);
139    clones.delete(name);
140    const output = {
141        loadRoute: node.loadRoute,
142        route: name,
143        contextKey: node.contextKey,
144        children: getTreeNodesAsRouteNodes(children),
145        dynamic,
146    };
147    if (clones.size) {
148        return [...clones].map((clone) => applyDefaultInitialRouteName({
149            ...output,
150            contextKey: node.contextKey.replace(output.route, clone),
151            route: clone,
152        }));
153    }
154    return [
155        applyDefaultInitialRouteName({
156            loadRoute: node.loadRoute,
157            route: name,
158            contextKey: node.contextKey,
159            children: getTreeNodesAsRouteNodes(children),
160            dynamic,
161        }),
162    ];
163}
164function extrapolateGroupRoutes(route, contextKey, routes = new Set()) {
165    const match = (0, matchers_1.matchGroupName)(route);
166    if (!match) {
167        routes.add(route);
168        return routes;
169    }
170    const groups = match?.split(',');
171    const groupsSet = new Set(groups);
172    if (groupsSet.size !== groups.length) {
173        throw new Error(`Array syntax cannot contain duplicate group name "${groups}" in "${contextKey}".`);
174    }
175    if (groups.length === 1) {
176        routes.add(route);
177        return routes;
178    }
179    for (const group of groups) {
180        extrapolateGroupRoutes(route.replace(match, group.trim()), contextKey, routes);
181    }
182    return routes;
183}
184function treeNodeToRouteNode(tree) {
185    if (tree.node) {
186        return fileNodeToRouteNode(tree);
187    }
188    return folderNodeToRouteNode(tree);
189}
190function contextModuleToFileNodes(contextModule, options = {}, files = contextModule.keys()) {
191    const nodes = files.map((key) => {
192        // In development, check if the file exports a default component
193        // this helps keep things snappy when creating files. In production we load all screens lazily.
194        try {
195            if (process.env.NODE_ENV === 'development') {
196                // If the user has set the `EXPO_ROUTER_IMPORT_MODE` to `sync` then we should
197                // filter the missing routes.
198                if (import_mode_1.default === 'sync') {
199                    const isApi = key.match(/\+api\.[jt]sx?$/);
200                    if (!isApi && !contextModule(key)?.default) {
201                        return null;
202                    }
203                }
204            }
205            const node = {
206                loadRoute() {
207                    if (options.ignoreRequireErrors) {
208                        try {
209                            return contextModule(key);
210                        }
211                        catch {
212                            return {};
213                        }
214                    }
215                    else {
216                        return contextModule(key);
217                    }
218                },
219                normalizedName: (0, matchers_1.getNameFromFilePath)(key),
220                contextKey: key,
221            };
222            return node;
223        }
224        catch (error) {
225            // Probably this won't stop metro from freaking out but it's worth a try.
226            console.warn('Error loading route "' + key + '"', error);
227        }
228        return null;
229    });
230    return nodes.filter(Boolean);
231}
232function hasCustomRootLayoutNode(routes) {
233    if (routes.length !== 1) {
234        return false;
235    }
236    // This could either be the root _layout or an app with a single file.
237    const route = routes[0];
238    if (route.route === '' && route.contextKey.match(/^\.\/_layout\.([jt]sx?)$/)) {
239        return true;
240    }
241    return false;
242}
243function treeNodesToRootRoute(treeNode) {
244    const routes = treeNodeToRouteNode(treeNode);
245    return withOptionalRootLayout(routes);
246}
247function processKeys(files, options) {
248    const { ignore } = options;
249    return files.filter((file) => {
250        return !ignore?.some((pattern) => pattern.test(file));
251    });
252}
253/**
254 * Asserts if the require.context has files that share the same name but have different extensions. Exposed for testing.
255 * @private
256 */
257function assertDuplicateRoutes(filenames) {
258    if (process.env.NODE_ENV === 'production') {
259        return;
260    }
261    const duplicates = filenames
262        .map((filename) => (0, matchers_1.removeSupportedExtensions)(filename))
263        .reduce((acc, filename) => {
264        acc[filename] = acc[filename] ? acc[filename] + 1 : 1;
265        return acc;
266    }, {});
267    Object.entries(duplicates).forEach(([filename, count]) => {
268        if (count > 1) {
269            throw new Error(`Multiple files match the route name "${filename}".`);
270        }
271    });
272}
273exports.assertDuplicateRoutes = assertDuplicateRoutes;
274/** Given a Metro context module, return an array of nested routes. */
275function getRoutes(contextModule, options) {
276    const route = getExactRoutes(contextModule, options);
277    // If there is no route, return an empty route.
278    if (!route) {
279        return null;
280    }
281    appendSitemapRoute(route);
282    // Auto add not found route if it doesn't exist
283    appendUnmatchedRoute(route);
284    return route;
285}
286exports.getRoutes = getRoutes;
287async function getRoutesAsync(contextModule, options) {
288    const route = await getExactRoutesAsync(contextModule, options);
289    if (!route) {
290        return null;
291    }
292    appendSitemapRoute(route);
293    // Auto add not found route if it doesn't exist
294    appendUnmatchedRoute(route);
295    return route;
296}
297exports.getRoutesAsync = getRoutesAsync;
298function getIgnoreList(options) {
299    const ignore = [/^\.\/\+html\.[tj]sx?$/, ...(options?.ignore ?? [])];
300    if (options?.preserveApiRoutes !== true) {
301        ignore.push(/\+api\.[tj]sx?$/);
302    }
303    return ignore;
304}
305/** Get routes without unmatched or sitemap. */
306function getExactRoutes(contextModule, options) {
307    const treeNodes = contextModuleToTree(contextModule, options);
308    const route = treeNodesToRootRoute(treeNodes);
309    return route || null;
310}
311exports.getExactRoutes = getExactRoutes;
312function contextModuleToTree(contextModule, options) {
313    const allowed = processKeys(contextModule.keys(), {
314        ...options,
315        ignore: getIgnoreList(options),
316    });
317    assertDuplicateRoutes(allowed);
318    const files = contextModuleToFileNodes(contextModule, options, allowed);
319    return getRecursiveTree(files);
320}
321async function getExactRoutesAsync(contextModule, options) {
322    const treeNodes = contextModuleToTree(contextModule, options);
323    const route = treeNodesToRootRoute(treeNodes);
324    return route || null;
325}
326exports.getExactRoutesAsync = getExactRoutesAsync;
327function appendSitemapRoute(routes) {
328    if (!routes.children.length ||
329        // Allow overriding the sitemap route
330        routes.children.some((route) => route.route === '_sitemap')) {
331        return routes;
332    }
333    routes.children.push({
334        loadRoute() {
335            const { Sitemap, getNavOptions } = require('./views/Sitemap');
336            return { default: Sitemap, getNavOptions };
337        },
338        route: '_sitemap',
339        contextKey: './_sitemap.tsx',
340        generated: true,
341        internal: true,
342        dynamic: null,
343        children: [],
344    });
345    return routes;
346}
347function appendUnmatchedRoute(routes) {
348    // Auto add not found route if it doesn't exist
349    const userDefinedDynamicRoute = getUserDefinedDeepDynamicRoute(routes);
350    if (!userDefinedDynamicRoute) {
351        routes.children.push({
352            loadRoute() {
353                return { default: require('./views/Unmatched').Unmatched };
354            },
355            route: '[...404]',
356            contextKey: './[...404].tsx',
357            dynamic: [{ name: '404', deep: true }],
358            children: [],
359            generated: true,
360            internal: true,
361        });
362    }
363    return routes;
364}
365/**
366 * Exposed for testing.
367 * @returns a top-level deep dynamic route if it exists, otherwise null.
368 */
369function getUserDefinedDeepDynamicRoute(routes) {
370    // Auto add not found route if it doesn't exist
371    for (const route of routes.children ?? []) {
372        if (route.generated)
373            continue;
374        const opaqueRoute = (0, matchers_1.stripInvisibleSegmentsFromPath)(route.route);
375        const isDeepDynamic = (0, matchers_1.matchDeepDynamicRouteName)(opaqueRoute);
376        if (isDeepDynamic) {
377            return route;
378        }
379        // Recurse through group routes
380        if ((0, matchers_1.matchGroupName)(route.route)) {
381            const child = getUserDefinedDeepDynamicRoute(route);
382            if (child) {
383                return child;
384            }
385        }
386    }
387    return null;
388}
389exports.getUserDefinedDeepDynamicRoute = getUserDefinedDeepDynamicRoute;
390function withOptionalRootLayout(routes) {
391    if (!routes?.length) {
392        return null;
393    }
394    if (hasCustomRootLayoutNode(routes)) {
395        return routes[0];
396    }
397    return {
398        loadRoute: () => ({
399            default: require('./views/Navigator')
400                .DefaultNavigator,
401        }),
402        // Generate a fake file name for the directory
403        contextKey: './_layout.tsx',
404        route: '',
405        generated: true,
406        dynamic: null,
407        children: routes,
408    };
409}
410//# sourceMappingURL=getRoutes.js.map