@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.17 → 3.9.0-ultramodern.18
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/dist/cjs/cli/artifacts.js +13 -0
- package/dist/cjs/cli/index.js +3 -0
- package/dist/cjs/cli/routeMetadata.js +131 -0
- package/dist/esm/cli/artifacts.mjs +11 -1
- package/dist/esm/cli/index.mjs +2 -2
- package/dist/esm/cli/routeMetadata.mjs +71 -0
- package/dist/esm-node/cli/artifacts.mjs +11 -1
- package/dist/esm-node/cli/index.mjs +2 -2
- package/dist/esm-node/cli/routeMetadata.mjs +72 -0
- package/dist/types/cli/artifacts.d.ts +13 -5
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/cli/routeMetadata.d.ts +18 -0
- package/package.json +13 -13
- package/rstest.config.mts +1 -0
- package/src/cli/artifacts.ts +47 -5
- package/src/cli/index.ts +1 -2
- package/src/cli/routeMetadata.ts +121 -0
- package/tests/router/generateRouteArtifacts.test.ts +38 -0
- package/tests/router/prefetchLinkPreload.test.tsx +0 -1
- package/tests/router/routeMetadata.test.ts +171 -0
|
@@ -38,12 +38,14 @@ var __webpack_exports__ = {};
|
|
|
38
38
|
__webpack_require__.r(__webpack_exports__);
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
40
|
generateTanstackRouteArtifacts: ()=>generateTanstackRouteArtifacts,
|
|
41
|
+
writeRouteMetadataManifest: ()=>writeRouteMetadataManifest,
|
|
41
42
|
writeTanstackRegisterFile: ()=>writeTanstackRegisterFile,
|
|
42
43
|
writeTanstackRouterTypesForEntries: ()=>writeTanstackRouterTypesForEntries
|
|
43
44
|
});
|
|
44
45
|
const external_node_path_namespaceObject = require("node:path");
|
|
45
46
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
46
47
|
const utils_namespaceObject = require("@modern-js/utils");
|
|
48
|
+
const external_routeMetadata_js_namespaceObject = require("./routeMetadata.js");
|
|
47
49
|
const external_tanstackTypes_js_namespaceObject = require("./tanstackTypes.js");
|
|
48
50
|
const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
|
|
49
51
|
async function writeFileIfChanged(filePath, content) {
|
|
@@ -121,7 +123,16 @@ async function writeTanstackRouterTypesForEntries(opts) {
|
|
|
121
123
|
canonicalRoutes
|
|
122
124
|
});
|
|
123
125
|
}
|
|
126
|
+
async function writeRouteMetadataManifest(opts) {
|
|
127
|
+
const routesDirectory = external_node_path_default().join(opts.appDirectory, 'src', 'routes');
|
|
128
|
+
const manifestPath = external_node_path_default().join(routesDirectory, external_routeMetadata_js_namespaceObject.ROUTE_METADATA_MANIFEST_FILE_NAME);
|
|
129
|
+
const manifest = (0, external_routeMetadata_js_namespaceObject.renderRouteMetadataManifest)(await (0, external_routeMetadata_js_namespaceObject.findRouteMetaFiles)(routesDirectory));
|
|
130
|
+
if (null !== manifest) return void await writeFileIfChanged(manifestPath, manifest);
|
|
131
|
+
const previous = await utils_namespaceObject.fs.pathExists(manifestPath) ? await utils_namespaceObject.fs.readFile(manifestPath, 'utf-8') : null;
|
|
132
|
+
if (previous?.startsWith(`${external_routeMetadata_js_namespaceObject.ROUTE_METADATA_MANIFEST_HEADER}\n`)) throw new Error(`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.`);
|
|
133
|
+
}
|
|
124
134
|
async function generateTanstackRouteArtifacts(opts) {
|
|
135
|
+
await writeRouteMetadataManifest(opts);
|
|
125
136
|
const [{ createRunOptions }, { cli }, utils] = await Promise.all([
|
|
126
137
|
import("@modern-js/app-tools/cli/run"),
|
|
127
138
|
import("@modern-js/plugin/cli"),
|
|
@@ -146,10 +157,12 @@ async function generateTanstackRouteArtifacts(opts) {
|
|
|
146
157
|
await appContext.hooks.onBeforeExit.call();
|
|
147
158
|
}
|
|
148
159
|
exports.generateTanstackRouteArtifacts = __webpack_exports__.generateTanstackRouteArtifacts;
|
|
160
|
+
exports.writeRouteMetadataManifest = __webpack_exports__.writeRouteMetadataManifest;
|
|
149
161
|
exports.writeTanstackRegisterFile = __webpack_exports__.writeTanstackRegisterFile;
|
|
150
162
|
exports.writeTanstackRouterTypesForEntries = __webpack_exports__.writeTanstackRouterTypesForEntries;
|
|
151
163
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
152
164
|
"generateTanstackRouteArtifacts",
|
|
165
|
+
"writeRouteMetadataManifest",
|
|
153
166
|
"writeTanstackRegisterFile",
|
|
154
167
|
"writeTanstackRouterTypesForEntries"
|
|
155
168
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
package/dist/cjs/cli/index.js
CHANGED
|
@@ -59,6 +59,7 @@ var __webpack_exports__ = {};
|
|
|
59
59
|
generateTanstackRouterTypesSourceForEntry: ()=>external_tanstackTypes_js_namespaceObject.generateTanstackRouterTypesSourceForEntry,
|
|
60
60
|
resolveTanstackRouteCodeSplittingEnabled: ()=>external_routeSplitting_js_namespaceObject.resolveTanstackRouteCodeSplittingEnabled,
|
|
61
61
|
tanstackRouterPlugin: ()=>tanstackRouterPlugin,
|
|
62
|
+
writeRouteMetadataManifest: ()=>external_artifacts_js_namespaceObject.writeRouteMetadataManifest,
|
|
62
63
|
writeTanstackRegisterFile: ()=>external_artifacts_js_namespaceObject.writeTanstackRegisterFile,
|
|
63
64
|
writeTanstackRouterTypesForEntries: ()=>external_artifacts_js_namespaceObject.writeTanstackRouterTypesForEntries
|
|
64
65
|
});
|
|
@@ -248,6 +249,7 @@ exports.generateTanstackRouteArtifacts = __webpack_exports__.generateTanstackRou
|
|
|
248
249
|
exports.generateTanstackRouterTypesSourceForEntry = __webpack_exports__.generateTanstackRouterTypesSourceForEntry;
|
|
249
250
|
exports.resolveTanstackRouteCodeSplittingEnabled = __webpack_exports__.resolveTanstackRouteCodeSplittingEnabled;
|
|
250
251
|
exports.tanstackRouterPlugin = __webpack_exports__.tanstackRouterPlugin;
|
|
252
|
+
exports.writeRouteMetadataManifest = __webpack_exports__.writeRouteMetadataManifest;
|
|
251
253
|
exports.writeTanstackRegisterFile = __webpack_exports__.writeTanstackRegisterFile;
|
|
252
254
|
exports.writeTanstackRouterTypesForEntries = __webpack_exports__.writeTanstackRouterTypesForEntries;
|
|
253
255
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
@@ -258,6 +260,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
258
260
|
"generateTanstackRouterTypesSourceForEntry",
|
|
259
261
|
"resolveTanstackRouteCodeSplittingEnabled",
|
|
260
262
|
"tanstackRouterPlugin",
|
|
263
|
+
"writeRouteMetadataManifest",
|
|
261
264
|
"writeTanstackRegisterFile",
|
|
262
265
|
"writeTanstackRouterTypesForEntries"
|
|
263
266
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
12
|
+
(()=>{
|
|
13
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
14
|
+
var define = (defs, kind)=>{
|
|
15
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
[kind]: defs[key]
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
define(getters, "get");
|
|
21
|
+
define(values, "value");
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
(()=>{
|
|
25
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
26
|
+
})();
|
|
27
|
+
(()=>{
|
|
28
|
+
__webpack_require__.r = (exports1)=>{
|
|
29
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
30
|
+
value: 'Module'
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
33
|
+
value: true
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
var __webpack_exports__ = {};
|
|
38
|
+
__webpack_require__.r(__webpack_exports__);
|
|
39
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
40
|
+
ROUTE_METADATA_MANIFEST_FILE_NAME: ()=>ROUTE_METADATA_MANIFEST_FILE_NAME,
|
|
41
|
+
ROUTE_METADATA_MANIFEST_HEADER: ()=>ROUTE_METADATA_MANIFEST_HEADER,
|
|
42
|
+
ROUTE_META_FILE_NAME: ()=>ROUTE_META_FILE_NAME,
|
|
43
|
+
findRouteMetaFiles: ()=>findRouteMetaFiles,
|
|
44
|
+
renderRouteMetadataManifest: ()=>renderRouteMetadataManifest
|
|
45
|
+
});
|
|
46
|
+
const promises_namespaceObject = require("node:fs/promises");
|
|
47
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
48
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
49
|
+
const ROUTE_META_FILE_NAME = 'route.meta.ts';
|
|
50
|
+
const ROUTE_METADATA_MANIFEST_FILE_NAME = 'ultramodern-route-metadata.ts';
|
|
51
|
+
const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
|
|
52
|
+
const PRINT_WIDTH = 120;
|
|
53
|
+
const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
54
|
+
ultramodernRouteMetadata
|
|
55
|
+
.filter((route) => route.canonicalPath !== '/')
|
|
56
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
57
|
+
);
|
|
58
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
59
|
+
.filter((route) => route.public && route.indexable)
|
|
60
|
+
.map((route) => ({
|
|
61
|
+
canonicalPath: route.canonicalPath,
|
|
62
|
+
descriptionKey: route.descriptionKey,
|
|
63
|
+
id: route.id,
|
|
64
|
+
localisedPaths: route.localisedPaths,
|
|
65
|
+
namespace: route.namespace,
|
|
66
|
+
ownerAppId: route.ownerAppId,
|
|
67
|
+
titleKey: route.titleKey,
|
|
68
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
69
|
+
}));
|
|
70
|
+
export const ultramodernRouteConfig = {
|
|
71
|
+
authoring: 'colocated-route-meta',
|
|
72
|
+
generatedManifest: true,
|
|
73
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
74
|
+
namespace: ultramodernRouteNamespace,
|
|
75
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
76
|
+
routes: ultramodernRouteMetadata,
|
|
77
|
+
source: 'route-owned',
|
|
78
|
+
} as const;
|
|
79
|
+
`;
|
|
80
|
+
const compareCodeUnits = (left, right)=>left < right ? -1 : left > right ? 1 : 0;
|
|
81
|
+
const quoteSpecifier = (specifier)=>specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
|
|
82
|
+
function renderRouteMetadataManifest(routeMetaFiles) {
|
|
83
|
+
const specifiers = routeMetaFiles.map((file)=>`./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`).sort(compareCodeUnits);
|
|
84
|
+
if (0 === specifiers.length) return null;
|
|
85
|
+
const bindings = specifiers.map((_, index)=>`route${index}`);
|
|
86
|
+
const imports = specifiers.map((specifier, index)=>`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`);
|
|
87
|
+
const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
|
|
88
|
+
const routes = inlineRoutes.length <= PRINT_WIDTH ? inlineRoutes : `export const ultramodernRouteMetadata = [\n${bindings.map((binding)=>` ${binding},\n`).join('')}] as const;`;
|
|
89
|
+
return `${ROUTE_METADATA_MANIFEST_HEADER}
|
|
90
|
+
${imports.join('\n')}
|
|
91
|
+
|
|
92
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
93
|
+
${routes}
|
|
94
|
+
${MANIFEST_BODY}`;
|
|
95
|
+
}
|
|
96
|
+
async function findRouteMetaFiles(routesDirectory) {
|
|
97
|
+
const files = [];
|
|
98
|
+
const visit = async (relativeDirectory)=>{
|
|
99
|
+
const entries = await (0, promises_namespaceObject.readdir)(external_node_path_default().join(routesDirectory, relativeDirectory), {
|
|
100
|
+
withFileTypes: true
|
|
101
|
+
});
|
|
102
|
+
for (const entry of entries){
|
|
103
|
+
const relativePath = external_node_path_default().join(relativeDirectory, entry.name);
|
|
104
|
+
if (entry.isDirectory()) await visit(relativePath);
|
|
105
|
+
else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) files.push(relativePath);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
await visit('');
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const { code, path: missingPath } = error;
|
|
112
|
+
if ('ENOENT' === code && missingPath === routesDirectory) return [];
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
return files;
|
|
116
|
+
}
|
|
117
|
+
exports.ROUTE_METADATA_MANIFEST_FILE_NAME = __webpack_exports__.ROUTE_METADATA_MANIFEST_FILE_NAME;
|
|
118
|
+
exports.ROUTE_METADATA_MANIFEST_HEADER = __webpack_exports__.ROUTE_METADATA_MANIFEST_HEADER;
|
|
119
|
+
exports.ROUTE_META_FILE_NAME = __webpack_exports__.ROUTE_META_FILE_NAME;
|
|
120
|
+
exports.findRouteMetaFiles = __webpack_exports__.findRouteMetaFiles;
|
|
121
|
+
exports.renderRouteMetadataManifest = __webpack_exports__.renderRouteMetadataManifest;
|
|
122
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
123
|
+
"ROUTE_METADATA_MANIFEST_FILE_NAME",
|
|
124
|
+
"ROUTE_METADATA_MANIFEST_HEADER",
|
|
125
|
+
"ROUTE_META_FILE_NAME",
|
|
126
|
+
"findRouteMetaFiles",
|
|
127
|
+
"renderRouteMetadataManifest"
|
|
128
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
129
|
+
Object.defineProperty(exports, '__esModule', {
|
|
130
|
+
value: true
|
|
131
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import node_path from "node:path";
|
|
2
2
|
import { fs } from "@modern-js/utils";
|
|
3
|
+
import { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, findRouteMetaFiles, renderRouteMetadataManifest } from "./routeMetadata.mjs";
|
|
3
4
|
import { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
|
|
4
5
|
const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
|
|
5
6
|
async function writeFileIfChanged(filePath, content) {
|
|
@@ -77,7 +78,16 @@ async function writeTanstackRouterTypesForEntries(opts) {
|
|
|
77
78
|
canonicalRoutes
|
|
78
79
|
});
|
|
79
80
|
}
|
|
81
|
+
async function writeRouteMetadataManifest(opts) {
|
|
82
|
+
const routesDirectory = node_path.join(opts.appDirectory, 'src', 'routes');
|
|
83
|
+
const manifestPath = node_path.join(routesDirectory, ROUTE_METADATA_MANIFEST_FILE_NAME);
|
|
84
|
+
const manifest = renderRouteMetadataManifest(await findRouteMetaFiles(routesDirectory));
|
|
85
|
+
if (null !== manifest) return void await writeFileIfChanged(manifestPath, manifest);
|
|
86
|
+
const previous = await fs.pathExists(manifestPath) ? await fs.readFile(manifestPath, 'utf-8') : null;
|
|
87
|
+
if (previous?.startsWith(`${ROUTE_METADATA_MANIFEST_HEADER}\n`)) throw new Error(`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.`);
|
|
88
|
+
}
|
|
80
89
|
async function generateTanstackRouteArtifacts(opts) {
|
|
90
|
+
await writeRouteMetadataManifest(opts);
|
|
81
91
|
const [{ createRunOptions }, { cli }, utils] = await Promise.all([
|
|
82
92
|
import("@modern-js/app-tools/cli/run"),
|
|
83
93
|
import("@modern-js/plugin/cli"),
|
|
@@ -101,4 +111,4 @@ async function generateTanstackRouteArtifacts(opts) {
|
|
|
101
111
|
});
|
|
102
112
|
await appContext.hooks.onBeforeExit.call();
|
|
103
113
|
}
|
|
104
|
-
export { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
114
|
+
export { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
package/dist/esm/cli/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import node_path from "node:path";
|
|
2
2
|
import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer } from "@modern-js/utils";
|
|
3
|
-
import { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
|
|
3
|
+
import { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
|
|
4
4
|
import { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled } from "./routeSplitting.mjs";
|
|
5
5
|
const DEFAULT_ROUTES_DIR = 'routes';
|
|
6
6
|
const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
|
|
@@ -176,4 +176,4 @@ function tanstackRouterPlugin(options = {}) {
|
|
|
176
176
|
const src_cli = tanstackRouterPlugin;
|
|
177
177
|
export { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
|
|
178
178
|
export default src_cli;
|
|
179
|
-
export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
179
|
+
export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import node_path from "node:path";
|
|
3
|
+
const ROUTE_META_FILE_NAME = 'route.meta.ts';
|
|
4
|
+
const ROUTE_METADATA_MANIFEST_FILE_NAME = 'ultramodern-route-metadata.ts';
|
|
5
|
+
const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
|
|
6
|
+
const PRINT_WIDTH = 120;
|
|
7
|
+
const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
8
|
+
ultramodernRouteMetadata
|
|
9
|
+
.filter((route) => route.canonicalPath !== '/')
|
|
10
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
11
|
+
);
|
|
12
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
13
|
+
.filter((route) => route.public && route.indexable)
|
|
14
|
+
.map((route) => ({
|
|
15
|
+
canonicalPath: route.canonicalPath,
|
|
16
|
+
descriptionKey: route.descriptionKey,
|
|
17
|
+
id: route.id,
|
|
18
|
+
localisedPaths: route.localisedPaths,
|
|
19
|
+
namespace: route.namespace,
|
|
20
|
+
ownerAppId: route.ownerAppId,
|
|
21
|
+
titleKey: route.titleKey,
|
|
22
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
23
|
+
}));
|
|
24
|
+
export const ultramodernRouteConfig = {
|
|
25
|
+
authoring: 'colocated-route-meta',
|
|
26
|
+
generatedManifest: true,
|
|
27
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
28
|
+
namespace: ultramodernRouteNamespace,
|
|
29
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
30
|
+
routes: ultramodernRouteMetadata,
|
|
31
|
+
source: 'route-owned',
|
|
32
|
+
} as const;
|
|
33
|
+
`;
|
|
34
|
+
const compareCodeUnits = (left, right)=>left < right ? -1 : left > right ? 1 : 0;
|
|
35
|
+
const quoteSpecifier = (specifier)=>specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
|
|
36
|
+
function renderRouteMetadataManifest(routeMetaFiles) {
|
|
37
|
+
const specifiers = routeMetaFiles.map((file)=>`./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`).sort(compareCodeUnits);
|
|
38
|
+
if (0 === specifiers.length) return null;
|
|
39
|
+
const bindings = specifiers.map((_, index)=>`route${index}`);
|
|
40
|
+
const imports = specifiers.map((specifier, index)=>`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`);
|
|
41
|
+
const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
|
|
42
|
+
const routes = inlineRoutes.length <= PRINT_WIDTH ? inlineRoutes : `export const ultramodernRouteMetadata = [\n${bindings.map((binding)=>` ${binding},\n`).join('')}] as const;`;
|
|
43
|
+
return `${ROUTE_METADATA_MANIFEST_HEADER}
|
|
44
|
+
${imports.join('\n')}
|
|
45
|
+
|
|
46
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
47
|
+
${routes}
|
|
48
|
+
${MANIFEST_BODY}`;
|
|
49
|
+
}
|
|
50
|
+
async function findRouteMetaFiles(routesDirectory) {
|
|
51
|
+
const files = [];
|
|
52
|
+
const visit = async (relativeDirectory)=>{
|
|
53
|
+
const entries = await readdir(node_path.join(routesDirectory, relativeDirectory), {
|
|
54
|
+
withFileTypes: true
|
|
55
|
+
});
|
|
56
|
+
for (const entry of entries){
|
|
57
|
+
const relativePath = node_path.join(relativeDirectory, entry.name);
|
|
58
|
+
if (entry.isDirectory()) await visit(relativePath);
|
|
59
|
+
else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) files.push(relativePath);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
await visit('');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const { code, path: missingPath } = error;
|
|
66
|
+
if ('ENOENT' === code && missingPath === routesDirectory) return [];
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
return files;
|
|
70
|
+
}
|
|
71
|
+
export { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, ROUTE_META_FILE_NAME, findRouteMetaFiles, renderRouteMetadataManifest };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { fs } from "@modern-js/utils";
|
|
4
|
+
import { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, findRouteMetaFiles, renderRouteMetadataManifest } from "./routeMetadata.mjs";
|
|
4
5
|
import { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
|
|
5
6
|
const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
|
|
6
7
|
async function writeFileIfChanged(filePath, content) {
|
|
@@ -78,7 +79,16 @@ async function writeTanstackRouterTypesForEntries(opts) {
|
|
|
78
79
|
canonicalRoutes
|
|
79
80
|
});
|
|
80
81
|
}
|
|
82
|
+
async function writeRouteMetadataManifest(opts) {
|
|
83
|
+
const routesDirectory = node_path.join(opts.appDirectory, 'src', 'routes');
|
|
84
|
+
const manifestPath = node_path.join(routesDirectory, ROUTE_METADATA_MANIFEST_FILE_NAME);
|
|
85
|
+
const manifest = renderRouteMetadataManifest(await findRouteMetaFiles(routesDirectory));
|
|
86
|
+
if (null !== manifest) return void await writeFileIfChanged(manifestPath, manifest);
|
|
87
|
+
const previous = await fs.pathExists(manifestPath) ? await fs.readFile(manifestPath, 'utf-8') : null;
|
|
88
|
+
if (previous?.startsWith(`${ROUTE_METADATA_MANIFEST_HEADER}\n`)) throw new Error(`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.`);
|
|
89
|
+
}
|
|
81
90
|
async function generateTanstackRouteArtifacts(opts) {
|
|
91
|
+
await writeRouteMetadataManifest(opts);
|
|
82
92
|
const [{ createRunOptions }, { cli }, utils] = await Promise.all([
|
|
83
93
|
import("@modern-js/app-tools/cli/run"),
|
|
84
94
|
import("@modern-js/plugin/cli"),
|
|
@@ -102,4 +112,4 @@ async function generateTanstackRouteArtifacts(opts) {
|
|
|
102
112
|
});
|
|
103
113
|
await appContext.hooks.onBeforeExit.call();
|
|
104
114
|
}
|
|
105
|
-
export { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
115
|
+
export { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer } from "@modern-js/utils";
|
|
4
|
-
import { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
|
|
4
|
+
import { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
|
|
5
5
|
import { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled } from "./routeSplitting.mjs";
|
|
6
6
|
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
7
7
|
import { dirname as __rspack_dirname } from "node:path";
|
|
@@ -182,4 +182,4 @@ function tanstackRouterPlugin(options = {}) {
|
|
|
182
182
|
const src_cli = tanstackRouterPlugin;
|
|
183
183
|
export { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
|
|
184
184
|
export default src_cli;
|
|
185
|
-
export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
185
|
+
export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
const ROUTE_META_FILE_NAME = 'route.meta.ts';
|
|
5
|
+
const ROUTE_METADATA_MANIFEST_FILE_NAME = 'ultramodern-route-metadata.ts';
|
|
6
|
+
const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
|
|
7
|
+
const PRINT_WIDTH = 120;
|
|
8
|
+
const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
9
|
+
ultramodernRouteMetadata
|
|
10
|
+
.filter((route) => route.canonicalPath !== '/')
|
|
11
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
12
|
+
);
|
|
13
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
14
|
+
.filter((route) => route.public && route.indexable)
|
|
15
|
+
.map((route) => ({
|
|
16
|
+
canonicalPath: route.canonicalPath,
|
|
17
|
+
descriptionKey: route.descriptionKey,
|
|
18
|
+
id: route.id,
|
|
19
|
+
localisedPaths: route.localisedPaths,
|
|
20
|
+
namespace: route.namespace,
|
|
21
|
+
ownerAppId: route.ownerAppId,
|
|
22
|
+
titleKey: route.titleKey,
|
|
23
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
24
|
+
}));
|
|
25
|
+
export const ultramodernRouteConfig = {
|
|
26
|
+
authoring: 'colocated-route-meta',
|
|
27
|
+
generatedManifest: true,
|
|
28
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
29
|
+
namespace: ultramodernRouteNamespace,
|
|
30
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
31
|
+
routes: ultramodernRouteMetadata,
|
|
32
|
+
source: 'route-owned',
|
|
33
|
+
} as const;
|
|
34
|
+
`;
|
|
35
|
+
const compareCodeUnits = (left, right)=>left < right ? -1 : left > right ? 1 : 0;
|
|
36
|
+
const quoteSpecifier = (specifier)=>specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
|
|
37
|
+
function renderRouteMetadataManifest(routeMetaFiles) {
|
|
38
|
+
const specifiers = routeMetaFiles.map((file)=>`./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`).sort(compareCodeUnits);
|
|
39
|
+
if (0 === specifiers.length) return null;
|
|
40
|
+
const bindings = specifiers.map((_, index)=>`route${index}`);
|
|
41
|
+
const imports = specifiers.map((specifier, index)=>`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`);
|
|
42
|
+
const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
|
|
43
|
+
const routes = inlineRoutes.length <= PRINT_WIDTH ? inlineRoutes : `export const ultramodernRouteMetadata = [\n${bindings.map((binding)=>` ${binding},\n`).join('')}] as const;`;
|
|
44
|
+
return `${ROUTE_METADATA_MANIFEST_HEADER}
|
|
45
|
+
${imports.join('\n')}
|
|
46
|
+
|
|
47
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
48
|
+
${routes}
|
|
49
|
+
${MANIFEST_BODY}`;
|
|
50
|
+
}
|
|
51
|
+
async function findRouteMetaFiles(routesDirectory) {
|
|
52
|
+
const files = [];
|
|
53
|
+
const visit = async (relativeDirectory)=>{
|
|
54
|
+
const entries = await readdir(node_path.join(routesDirectory, relativeDirectory), {
|
|
55
|
+
withFileTypes: true
|
|
56
|
+
});
|
|
57
|
+
for (const entry of entries){
|
|
58
|
+
const relativePath = node_path.join(relativeDirectory, entry.name);
|
|
59
|
+
if (entry.isDirectory()) await visit(relativePath);
|
|
60
|
+
else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) files.push(relativePath);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
await visit('');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const { code, path: missingPath } = error;
|
|
67
|
+
if ('ENOENT' === code && missingPath === routesDirectory) return [];
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
return files;
|
|
71
|
+
}
|
|
72
|
+
export { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, ROUTE_META_FILE_NAME, findRouteMetaFiles, renderRouteMetadataManifest };
|
|
@@ -21,11 +21,19 @@ export declare function writeTanstackRouterTypesForEntries(opts: {
|
|
|
21
21
|
i18nPluginInstalled?: boolean;
|
|
22
22
|
}): Promise<void>;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
* Writes `src/routes/ultramodern-route-metadata.ts` from the `route.meta.ts`
|
|
25
|
+
* files below `src/routes`. Reads only the file system, never the app config.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeRouteMetadataManifest(opts: {
|
|
28
|
+
appDirectory: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Headless regeneration of the route metadata manifest and of `router.gen.ts` /
|
|
32
|
+
* `register.gen.d.ts` for a generated app, without running a full dev/build.
|
|
33
|
+
* Drives the app-tools analyze `onPrepare` (which fires `generateEntryCode`)
|
|
34
|
+
* and returns before any bundling — the app's own registered plugin-tanstack
|
|
35
|
+
* writes the router artifacts via its `generateEntryCode` subscriber. Reuses
|
|
36
|
+
* the exact hook dev/build use, so there is no scanning drift.
|
|
29
37
|
*/
|
|
30
38
|
export declare function generateTanstackRouteArtifacts(opts: {
|
|
31
39
|
appDirectory: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AppTools, CliPlugin } from '@modern-js/app-tools';
|
|
2
2
|
import { type TanstackRouteCodeSplittingOption } from './routeSplitting.js';
|
|
3
|
-
export { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries, } from './artifacts.js';
|
|
3
|
+
export { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries, } from './artifacts.js';
|
|
4
4
|
export type { TanstackRouteCodeSplittingOption, TanstackRsbuildRouteSplittingProfile, } from './routeSplitting.js';
|
|
5
5
|
export { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled, } from './routeSplitting.js';
|
|
6
6
|
export { type CollectCanonicalRoutesOptions, collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry, } from './tanstackTypes.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const ROUTE_META_FILE_NAME = "route.meta.ts";
|
|
2
|
+
export declare const ROUTE_METADATA_MANIFEST_FILE_NAME = "ultramodern-route-metadata.ts";
|
|
3
|
+
export declare const ROUTE_METADATA_MANIFEST_HEADER = "// @generated by @modern-js/plugin-tanstack from route.meta.ts files. Run routes-generate; do not edit.";
|
|
4
|
+
/**
|
|
5
|
+
* Renders the route metadata manifest for the `route.meta.ts` files of one
|
|
6
|
+
* routes directory. Each path is relative to that directory (either separator).
|
|
7
|
+
* Every `route.meta.ts` must export `routeMeta`; the manifest imports it
|
|
8
|
+
* statically, so the metadata is never evaluated while generating. Returns
|
|
9
|
+
* `null` when there are no route metadata files. Output depends only on the
|
|
10
|
+
* set of paths, never on their order or on the file system.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderRouteMetadataManifest(routeMetaFiles: readonly string[]): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Lists the `route.meta.ts` files below `routesDirectory`, relative to it.
|
|
15
|
+
* Symbolic links are not followed: linked metadata belongs to the tree it
|
|
16
|
+
* lives in. A missing directory has no route metadata.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findRouteMetaFiles(routesDirectory: string): Promise<string[]>;
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"modern.js",
|
|
19
19
|
"tanstack-router"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.9.0-ultramodern.
|
|
21
|
+
"version": "3.9.0-ultramodern.18",
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
@@ -94,19 +94,19 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.
|
|
98
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.
|
|
99
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
100
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.
|
|
101
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.
|
|
102
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.
|
|
97
|
+
"@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.18",
|
|
98
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.18",
|
|
99
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.18",
|
|
100
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.18",
|
|
101
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.18",
|
|
102
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.18",
|
|
103
103
|
"@swc/helpers": "^0.5.23",
|
|
104
104
|
"@tanstack/react-router": "1.170.39",
|
|
105
105
|
"@tanstack/router-core": "1.171.32"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@modern-js/app-tools": "3.9.0-ultramodern.
|
|
109
|
-
"@modern-js/runtime": "3.9.0-ultramodern.
|
|
108
|
+
"@modern-js/app-tools": "3.9.0-ultramodern.18",
|
|
109
|
+
"@modern-js/runtime": "3.9.0-ultramodern.18",
|
|
110
110
|
"react": "^19.2.8",
|
|
111
111
|
"react-dom": "^19.2.8",
|
|
112
112
|
"react-server-dom-rspack": "0.1.0"
|
|
@@ -117,9 +117,9 @@
|
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.
|
|
121
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.
|
|
122
|
-
"@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.9.0-ultramodern.
|
|
120
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.18",
|
|
121
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.18",
|
|
122
|
+
"@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.9.0-ultramodern.18",
|
|
123
123
|
"@rslib/core": "1.0.2",
|
|
124
124
|
"@scripts/rstest-config": "2.66.0",
|
|
125
125
|
"@tanstack/history": "1.162.4",
|
|
@@ -153,6 +153,6 @@
|
|
|
153
153
|
"scripts": {
|
|
154
154
|
"dev": "rslib build --watch",
|
|
155
155
|
"build": "rslib build",
|
|
156
|
-
"test": "rstest
|
|
156
|
+
"test": "rstest"
|
|
157
157
|
}
|
|
158
158
|
}
|
package/rstest.config.mts
CHANGED
|
@@ -29,6 +29,7 @@ export default {
|
|
|
29
29
|
'tests/router/loaderBridge.test.ts',
|
|
30
30
|
'tests/router/localisedIdentity.test.tsx',
|
|
31
31
|
'tests/router/routeHooks.test.ts',
|
|
32
|
+
'tests/router/routeMetadata.test.ts',
|
|
32
33
|
'tests/router/serverPlugin.test.ts',
|
|
33
34
|
'tests/router/rsc.test.tsx',
|
|
34
35
|
'tests/router/slotUsageSanitizer.test.ts',
|
package/src/cli/artifacts.ts
CHANGED
|
@@ -5,6 +5,12 @@ import type { AppToolsContext } from '@modern-js/app-tools';
|
|
|
5
5
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
6
6
|
import { fs } from '@modern-js/utils';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
findRouteMetaFiles,
|
|
10
|
+
ROUTE_METADATA_MANIFEST_FILE_NAME,
|
|
11
|
+
ROUTE_METADATA_MANIFEST_HEADER,
|
|
12
|
+
renderRouteMetadataManifest,
|
|
13
|
+
} from './routeMetadata';
|
|
8
14
|
import {
|
|
9
15
|
collectCanonicalRoutesForEntry,
|
|
10
16
|
generateTanstackRouterTypesSourceForEntry,
|
|
@@ -193,16 +199,52 @@ export async function writeTanstackRouterTypesForEntries(opts: {
|
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
* Writes `src/routes/ultramodern-route-metadata.ts` from the `route.meta.ts`
|
|
203
|
+
* files below `src/routes`. Reads only the file system, never the app config.
|
|
204
|
+
*/
|
|
205
|
+
export async function writeRouteMetadataManifest(opts: {
|
|
206
|
+
appDirectory: string;
|
|
207
|
+
}): Promise<void> {
|
|
208
|
+
const routesDirectory = path.join(opts.appDirectory, 'src', 'routes');
|
|
209
|
+
const manifestPath = path.join(
|
|
210
|
+
routesDirectory,
|
|
211
|
+
ROUTE_METADATA_MANIFEST_FILE_NAME,
|
|
212
|
+
);
|
|
213
|
+
const manifest = renderRouteMetadataManifest(
|
|
214
|
+
await findRouteMetaFiles(routesDirectory),
|
|
215
|
+
);
|
|
216
|
+
if (manifest !== null) {
|
|
217
|
+
await writeFileIfChanged(manifestPath, manifest);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const previous = (await fs.pathExists(manifestPath))
|
|
221
|
+
? await fs.readFile(manifestPath, 'utf-8')
|
|
222
|
+
: null;
|
|
223
|
+
// No valid manifest exists without routes (its namespace comes from them),
|
|
224
|
+
// and the app config imports it, so the app cannot load in this state.
|
|
225
|
+
if (previous?.startsWith(`${ROUTE_METADATA_MANIFEST_HEADER}\n`)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. ` +
|
|
228
|
+
'Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.',
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Headless regeneration of the route metadata manifest and of `router.gen.ts` /
|
|
235
|
+
* `register.gen.d.ts` for a generated app, without running a full dev/build.
|
|
236
|
+
* Drives the app-tools analyze `onPrepare` (which fires `generateEntryCode`)
|
|
237
|
+
* and returns before any bundling — the app's own registered plugin-tanstack
|
|
238
|
+
* writes the router artifacts via its `generateEntryCode` subscriber. Reuses
|
|
239
|
+
* the exact hook dev/build use, so there is no scanning drift.
|
|
201
240
|
*/
|
|
202
241
|
export async function generateTanstackRouteArtifacts(opts: {
|
|
203
242
|
appDirectory: string;
|
|
204
243
|
version?: string;
|
|
205
244
|
}): Promise<void> {
|
|
245
|
+
// The app config imports the manifest. Writing it first means a manifest
|
|
246
|
+
// that still imports a deleted route.meta.ts cannot fail the config load.
|
|
247
|
+
await writeRouteMetadataManifest(opts);
|
|
206
248
|
const [{ createRunOptions }, { cli }, utils] = await Promise.all([
|
|
207
249
|
import('@modern-js/app-tools/cli/run'),
|
|
208
250
|
import('@modern-js/plugin/cli'),
|
package/src/cli/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import type {
|
|
4
4
|
AppNormalizedConfig,
|
|
5
5
|
AppTools,
|
|
6
|
-
AppToolsContext,
|
|
7
6
|
CliPlugin,
|
|
8
7
|
} from '@modern-js/app-tools';
|
|
9
8
|
import type { CLIPluginAPI } from '@modern-js/plugin';
|
|
@@ -15,7 +14,6 @@ import type {
|
|
|
15
14
|
} from '@modern-js/types';
|
|
16
15
|
import {
|
|
17
16
|
filterRoutesForServer,
|
|
18
|
-
fs,
|
|
19
17
|
NESTED_ROUTE_SPEC_FILE,
|
|
20
18
|
} from '@modern-js/utils';
|
|
21
19
|
import { writeTanstackRouterTypesForEntries } from './artifacts';
|
|
@@ -26,6 +24,7 @@ import {
|
|
|
26
24
|
|
|
27
25
|
export {
|
|
28
26
|
generateTanstackRouteArtifacts,
|
|
27
|
+
writeRouteMetadataManifest,
|
|
29
28
|
writeTanstackRegisterFile,
|
|
30
29
|
writeTanstackRouterTypesForEntries,
|
|
31
30
|
} from './artifacts';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off nodeBuiltinImport:off
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const ROUTE_META_FILE_NAME = 'route.meta.ts';
|
|
6
|
+
export const ROUTE_METADATA_MANIFEST_FILE_NAME =
|
|
7
|
+
'ultramodern-route-metadata.ts';
|
|
8
|
+
|
|
9
|
+
export const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
|
|
10
|
+
|
|
11
|
+
// The manifest is emitted formatted (oxfmt/Prettier style: print width 120,
|
|
12
|
+
// single quotes, trailing commas) and is generated output, so formatters
|
|
13
|
+
// ignore it like router.gen.ts; no formatter pass is needed to keep it stable.
|
|
14
|
+
const PRINT_WIDTH = 120;
|
|
15
|
+
|
|
16
|
+
const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
17
|
+
ultramodernRouteMetadata
|
|
18
|
+
.filter((route) => route.canonicalPath !== '/')
|
|
19
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
20
|
+
);
|
|
21
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
22
|
+
.filter((route) => route.public && route.indexable)
|
|
23
|
+
.map((route) => ({
|
|
24
|
+
canonicalPath: route.canonicalPath,
|
|
25
|
+
descriptionKey: route.descriptionKey,
|
|
26
|
+
id: route.id,
|
|
27
|
+
localisedPaths: route.localisedPaths,
|
|
28
|
+
namespace: route.namespace,
|
|
29
|
+
ownerAppId: route.ownerAppId,
|
|
30
|
+
titleKey: route.titleKey,
|
|
31
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
32
|
+
}));
|
|
33
|
+
export const ultramodernRouteConfig = {
|
|
34
|
+
authoring: 'colocated-route-meta',
|
|
35
|
+
generatedManifest: true,
|
|
36
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
37
|
+
namespace: ultramodernRouteNamespace,
|
|
38
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
39
|
+
routes: ultramodernRouteMetadata,
|
|
40
|
+
source: 'route-owned',
|
|
41
|
+
} as const;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const compareCodeUnits = (left: string, right: string) =>
|
|
45
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
46
|
+
|
|
47
|
+
const quoteSpecifier = (specifier: string) =>
|
|
48
|
+
specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Renders the route metadata manifest for the `route.meta.ts` files of one
|
|
52
|
+
* routes directory. Each path is relative to that directory (either separator).
|
|
53
|
+
* Every `route.meta.ts` must export `routeMeta`; the manifest imports it
|
|
54
|
+
* statically, so the metadata is never evaluated while generating. Returns
|
|
55
|
+
* `null` when there are no route metadata files. Output depends only on the
|
|
56
|
+
* set of paths, never on their order or on the file system.
|
|
57
|
+
*/
|
|
58
|
+
export function renderRouteMetadataManifest(
|
|
59
|
+
routeMetaFiles: readonly string[],
|
|
60
|
+
): string | null {
|
|
61
|
+
const specifiers = routeMetaFiles
|
|
62
|
+
.map(file => `./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`)
|
|
63
|
+
.sort(compareCodeUnits);
|
|
64
|
+
if (specifiers.length === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const bindings = specifiers.map((_, index) => `route${index}`);
|
|
68
|
+
const imports = specifiers.map(
|
|
69
|
+
(specifier, index) =>
|
|
70
|
+
`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`,
|
|
71
|
+
);
|
|
72
|
+
const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
|
|
73
|
+
const routes =
|
|
74
|
+
inlineRoutes.length <= PRINT_WIDTH
|
|
75
|
+
? inlineRoutes
|
|
76
|
+
: `export const ultramodernRouteMetadata = [\n${bindings
|
|
77
|
+
.map(binding => ` ${binding},\n`)
|
|
78
|
+
.join('')}] as const;`;
|
|
79
|
+
|
|
80
|
+
return `${ROUTE_METADATA_MANIFEST_HEADER}
|
|
81
|
+
${imports.join('\n')}
|
|
82
|
+
|
|
83
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
84
|
+
${routes}
|
|
85
|
+
${MANIFEST_BODY}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Lists the `route.meta.ts` files below `routesDirectory`, relative to it.
|
|
90
|
+
* Symbolic links are not followed: linked metadata belongs to the tree it
|
|
91
|
+
* lives in. A missing directory has no route metadata.
|
|
92
|
+
*/
|
|
93
|
+
export async function findRouteMetaFiles(
|
|
94
|
+
routesDirectory: string,
|
|
95
|
+
): Promise<string[]> {
|
|
96
|
+
const files: string[] = [];
|
|
97
|
+
const visit = async (relativeDirectory: string): Promise<void> => {
|
|
98
|
+
const entries = await readdir(
|
|
99
|
+
path.join(routesDirectory, relativeDirectory),
|
|
100
|
+
{ withFileTypes: true },
|
|
101
|
+
);
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
await visit(relativePath);
|
|
106
|
+
} else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) {
|
|
107
|
+
files.push(relativePath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
await visit('');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const { code, path: missingPath } = error as NodeJS.ErrnoException;
|
|
115
|
+
if (code === 'ENOENT' && missingPath === routesDirectory) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
@@ -165,6 +165,44 @@ describe('generateTanstackRouteArtifacts (headless routes-generate)', () => {
|
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
+
it('regenerates the route metadata manifest before loading the config', async () => {
|
|
169
|
+
const appDirectory = makeAppDir(true);
|
|
170
|
+
const routesDirectory = path.join(appDirectory, 'src', 'routes');
|
|
171
|
+
const manifestPath = path.join(
|
|
172
|
+
routesDirectory,
|
|
173
|
+
'ultramodern-route-metadata.ts',
|
|
174
|
+
);
|
|
175
|
+
fs.mkdirSync(path.join(routesDirectory, '[lang]'), { recursive: true });
|
|
176
|
+
fs.writeFileSync(
|
|
177
|
+
path.join(routesDirectory, '[lang]', 'route.meta.ts'),
|
|
178
|
+
"export const routeMeta = { namespace: 'app' } as const;\n",
|
|
179
|
+
);
|
|
180
|
+
// A manifest that still imports a deleted route.meta.ts makes the config
|
|
181
|
+
// (which imports the manifest) fail to load.
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
manifestPath,
|
|
184
|
+
"import { routeMeta as route0 } from './[lang]/deleted/route.meta';\n",
|
|
185
|
+
);
|
|
186
|
+
const configLoadFailure = new Error(
|
|
187
|
+
"Cannot find module './[lang]/deleted/route.meta'",
|
|
188
|
+
);
|
|
189
|
+
cliInitMock.mockImplementationOnce(async () => {
|
|
190
|
+
throw configLoadFailure;
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
await expect(
|
|
194
|
+
generateTanstackRouteArtifacts({ appDirectory }),
|
|
195
|
+
).rejects.toBe(configLoadFailure);
|
|
196
|
+
const manifest = fs.readFileSync(manifestPath, 'utf8');
|
|
197
|
+
expect(manifest).toContain(
|
|
198
|
+
"import { routeMeta as route0 } from './[lang]/route.meta';",
|
|
199
|
+
);
|
|
200
|
+
expect(manifest).not.toContain('deleted');
|
|
201
|
+
} finally {
|
|
202
|
+
fs.rmSync(appDirectory, { force: true, recursive: true });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
168
206
|
it('throws a clear error when no config file exists', async () => {
|
|
169
207
|
const appDirectory = makeAppDir(false);
|
|
170
208
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { writeRouteMetadataManifest } from '../../src/cli/artifacts';
|
|
5
|
+
import { renderRouteMetadataManifest } from '../../src/cli/routeMetadata';
|
|
6
|
+
|
|
7
|
+
const routeMeta = (id: string) =>
|
|
8
|
+
`export const routeMeta = { id: '${id}', namespace: 'shop' } as const;\n`;
|
|
9
|
+
|
|
10
|
+
const EXPECTED_MANIFEST = `// @generated by @modern-js/plugin-tanstack from route.meta.ts files. Run routes-generate; do not edit.
|
|
11
|
+
import { routeMeta as route0 } from './[lang]/about/route.meta';
|
|
12
|
+
import { routeMeta as route1 } from './[lang]/route.meta';
|
|
13
|
+
import { routeMeta as route2 } from './[lang]/stores/[id]/route.meta';
|
|
14
|
+
|
|
15
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
16
|
+
export const ultramodernRouteMetadata = [route0, route1, route2] as const;
|
|
17
|
+
export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
18
|
+
ultramodernRouteMetadata
|
|
19
|
+
.filter((route) => route.canonicalPath !== '/')
|
|
20
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
21
|
+
);
|
|
22
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
23
|
+
.filter((route) => route.public && route.indexable)
|
|
24
|
+
.map((route) => ({
|
|
25
|
+
canonicalPath: route.canonicalPath,
|
|
26
|
+
descriptionKey: route.descriptionKey,
|
|
27
|
+
id: route.id,
|
|
28
|
+
localisedPaths: route.localisedPaths,
|
|
29
|
+
namespace: route.namespace,
|
|
30
|
+
ownerAppId: route.ownerAppId,
|
|
31
|
+
titleKey: route.titleKey,
|
|
32
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
33
|
+
}));
|
|
34
|
+
export const ultramodernRouteConfig = {
|
|
35
|
+
authoring: 'colocated-route-meta',
|
|
36
|
+
generatedManifest: true,
|
|
37
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
38
|
+
namespace: ultramodernRouteNamespace,
|
|
39
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
40
|
+
routes: ultramodernRouteMetadata,
|
|
41
|
+
source: 'route-owned',
|
|
42
|
+
} as const;
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
describe('route metadata manifest', () => {
|
|
46
|
+
let appDirectory: string;
|
|
47
|
+
let routesDirectory: string;
|
|
48
|
+
const write = (relativePath: string, content: string) => {
|
|
49
|
+
const file = path.join(routesDirectory, relativePath);
|
|
50
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
51
|
+
fs.writeFileSync(file, content);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
appDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'route-metadata-'));
|
|
56
|
+
routesDirectory = path.join(appDirectory, 'src', 'routes');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
fs.rmSync(appDirectory, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('writes a byte-stable, formatted manifest from route.meta.ts files', async () => {
|
|
64
|
+
write('[lang]/stores/[id]/route.meta.ts', routeMeta('store'));
|
|
65
|
+
write('[lang]/route.meta.ts', routeMeta('home'));
|
|
66
|
+
write('[lang]/about/route.meta.ts', routeMeta('about'));
|
|
67
|
+
write('[lang]/about/page.tsx', 'export default () => null;\n');
|
|
68
|
+
// Linked metadata belongs to another tree; following it would make the
|
|
69
|
+
// manifest depend on where the link points.
|
|
70
|
+
fs.mkdirSync(path.join(routesDirectory, '[lang]/linked-file'));
|
|
71
|
+
fs.symlinkSync(
|
|
72
|
+
path.join(routesDirectory, '[lang]/route.meta.ts'),
|
|
73
|
+
path.join(routesDirectory, '[lang]/linked-file/route.meta.ts'),
|
|
74
|
+
);
|
|
75
|
+
fs.symlinkSync(
|
|
76
|
+
path.join(routesDirectory, '[lang]/about'),
|
|
77
|
+
path.join(routesDirectory, '[lang]/linked'),
|
|
78
|
+
);
|
|
79
|
+
const manifestPath = path.join(
|
|
80
|
+
routesDirectory,
|
|
81
|
+
'ultramodern-route-metadata.ts',
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
85
|
+
expect(fs.readFileSync(manifestPath, 'utf8')).toBe(EXPECTED_MANIFEST);
|
|
86
|
+
|
|
87
|
+
const writtenAt = fs.statSync(manifestPath).mtimeMs;
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
89
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
90
|
+
expect(fs.readFileSync(manifestPath, 'utf8')).toBe(EXPECTED_MANIFEST);
|
|
91
|
+
expect(fs.statSync(manifestPath).mtimeMs).toBe(writtenAt);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('drops deleted route metadata from an existing manifest', async () => {
|
|
95
|
+
write('[lang]/route.meta.ts', routeMeta('home'));
|
|
96
|
+
write('[lang]/gone/route.meta.ts', routeMeta('gone'));
|
|
97
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
98
|
+
fs.rmSync(path.join(routesDirectory, '[lang]/gone'), { recursive: true });
|
|
99
|
+
|
|
100
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
101
|
+
|
|
102
|
+
const manifest = fs.readFileSync(
|
|
103
|
+
path.join(routesDirectory, 'ultramodern-route-metadata.ts'),
|
|
104
|
+
'utf8',
|
|
105
|
+
);
|
|
106
|
+
expect(manifest).not.toContain('gone');
|
|
107
|
+
expect(manifest).toContain(
|
|
108
|
+
"import { routeMeta as route0 } from './[lang]/route.meta';\n\n",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('names the fix once the last route metadata is deleted', async () => {
|
|
113
|
+
write('[lang]/route.meta.ts', routeMeta('home'));
|
|
114
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
115
|
+
const manifestPath = path.join(
|
|
116
|
+
routesDirectory,
|
|
117
|
+
'ultramodern-route-metadata.ts',
|
|
118
|
+
);
|
|
119
|
+
const manifest = fs.readFileSync(manifestPath, 'utf8');
|
|
120
|
+
fs.rmSync(path.join(routesDirectory, '[lang]/route.meta.ts'));
|
|
121
|
+
|
|
122
|
+
await expect(writeRouteMetadataManifest({ appDirectory })).rejects.toThrow(
|
|
123
|
+
/none remain below .*Restore at least one route\.meta\.ts/u,
|
|
124
|
+
);
|
|
125
|
+
expect(fs.readFileSync(manifestPath, 'utf8')).toBe(manifest);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('keeps an authored file at the manifest path', async () => {
|
|
129
|
+
write('ultramodern-route-metadata.ts', 'export const authored = true;\n');
|
|
130
|
+
|
|
131
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
132
|
+
|
|
133
|
+
expect(
|
|
134
|
+
fs.readFileSync(
|
|
135
|
+
path.join(routesDirectory, 'ultramodern-route-metadata.ts'),
|
|
136
|
+
'utf8',
|
|
137
|
+
),
|
|
138
|
+
).toBe('export const authored = true;\n');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('writes nothing for an app without route metadata', async () => {
|
|
142
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
143
|
+
write('[lang]/page.tsx', 'export default () => null;\n');
|
|
144
|
+
await writeRouteMetadataManifest({ appDirectory });
|
|
145
|
+
|
|
146
|
+
expect(fs.readdirSync(routesDirectory)).toEqual(['[lang]']);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('renders independently of input order and separators', () => {
|
|
150
|
+
expect(
|
|
151
|
+
renderRouteMetadataManifest([
|
|
152
|
+
'[lang]\\stores\\[id]\\route.meta.ts',
|
|
153
|
+
'[lang]/route.meta.ts',
|
|
154
|
+
'[lang]/about/route.meta.ts',
|
|
155
|
+
]),
|
|
156
|
+
).toBe(EXPECTED_MANIFEST);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('breaks the route list one binding per line past the print width', () => {
|
|
160
|
+
const manifest = renderRouteMetadataManifest(
|
|
161
|
+
Array.from({ length: 12 }, (_, index) => `r${index}/route.meta.ts`),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(manifest).toContain(
|
|
165
|
+
`export const ultramodernRouteMetadata = [\n${Array.from(
|
|
166
|
+
{ length: 12 },
|
|
167
|
+
(_, index) => ` route${index},\n`,
|
|
168
|
+
).join('')}] as const;\n`,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|