@navita/next-plugin 0.4.4 → 3.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_virtual/_rolldown/runtime.cjs +23 -0
- package/fromServerLoader.cjs +10 -17
- package/fromServerLoader.d.ts +4 -3
- package/index.cjs +88 -139
- package/index.d.ts +7 -7
- package/optimizeCSSOutput.cjs +54 -92
- package/package.json +2 -2
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.__toESM = __toESM;
|
package/fromServerLoader.cjs
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _navita_webpack_plugin = require("@navita/webpack-plugin");
|
|
3
|
+
//#region src/fromServerLoader.ts
|
|
5
4
|
function pitch() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Our modules are empty and aren't used for anything other than for collecting information.
|
|
13
|
-
// And are not even included in the final bundle.
|
|
14
|
-
// https://github.com/vercel/next.js/blob/8c6532fa7045879feb13bb21c530bb1517378e29/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L404
|
|
15
|
-
// https://github.com/vercel/next.js/blob/8c6532fa7045879feb13bb21c530bb1517378e29/packages/next/src/lib/constants.ts#L146
|
|
16
|
-
this._module.layer = 'not-app-pages-browser';
|
|
17
|
-
return '';
|
|
5
|
+
this._module.loaders = [];
|
|
6
|
+
const dependency = (0, _navita_webpack_plugin.getNavitaDependency)(this._compiler.webpack);
|
|
7
|
+
const { cssHash, issuerPath } = Object.fromEntries(new URLSearchParams(this.resourceQuery).entries());
|
|
8
|
+
this._module.addDependency(new dependency(issuerPath, cssHash));
|
|
9
|
+
this._module.layer = "not-app-pages-browser";
|
|
10
|
+
return "";
|
|
18
11
|
}
|
|
19
|
-
|
|
12
|
+
//#endregion
|
|
20
13
|
exports.pitch = pitch;
|
package/fromServerLoader.d.ts
CHANGED
package/index.cjs
CHANGED
|
@@ -1,142 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Object.keys(e).forEach(function (k) {
|
|
14
|
-
if (k !== 'default') {
|
|
15
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
get: function () { return e[k]; }
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
n.default = e;
|
|
24
|
-
return Object.freeze(n);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
28
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_optimizeCSSOutput = require("./optimizeCSSOutput.cjs");
|
|
4
|
+
let _navita_webpack_plugin = require("@navita/webpack-plugin");
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
node_fs = require_runtime.__toESM(node_fs);
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
node_path = require_runtime.__toESM(node_path);
|
|
9
|
+
let next_dist_build_webpack_plugins_mini_css_extract_plugin = require("next/dist/build/webpack/plugins/mini-css-extract-plugin");
|
|
10
|
+
next_dist_build_webpack_plugins_mini_css_extract_plugin = require_runtime.__toESM(next_dist_build_webpack_plugins_mini_css_extract_plugin);
|
|
11
|
+
let next_dist_lib_find_pages_dir = require("next/dist/lib/find-pages-dir");
|
|
12
|
+
//#region src/index.ts
|
|
29
13
|
let renderer;
|
|
30
14
|
let lastCache;
|
|
31
|
-
const MiniCssExtractPlugin =
|
|
32
|
-
const createNavitaStylePlugin = (navitaConfig = {})=>(nextConfig)=>Object.assign({}, nextConfig, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// But the same should happen with the webpack cache.
|
|
108
|
-
}
|
|
109
|
-
// If the user has provided their own onRenderInitialized function,
|
|
110
|
-
// we'll do it after the cache is loaded.
|
|
111
|
-
return navitaConfig.onRenderInitialized?.(renderer);
|
|
112
|
-
};
|
|
113
|
-
config.plugins?.push({
|
|
114
|
-
apply (compiler) {
|
|
115
|
-
compiler.hooks.afterEmit.tapPromise(`${webpackPlugin.NavitaPlugin.pluginName}-nextjs-custom-cache`, async ()=>{
|
|
116
|
-
if (!renderer) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const newCache = renderer.engine.serialize();
|
|
120
|
-
if (newCache === lastCache) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
lastCache = newCache;
|
|
124
|
-
await fs__namespace.promises.writeFile(cacheDestination, newCache);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
config.plugins?.push(new webpackPlugin.NavitaPlugin({
|
|
129
|
-
useWebpackCache: false,
|
|
130
|
-
outputCss,
|
|
131
|
-
...navitaConfig,
|
|
132
|
-
onRenderInitialized,
|
|
133
|
-
optimizeCSSOutput: optimizeCSSOutput.optimizeCSSOutput
|
|
134
|
-
}));
|
|
135
|
-
if (typeof nextConfig.webpack === "function") {
|
|
136
|
-
return nextConfig.webpack(config, options);
|
|
137
|
-
}
|
|
138
|
-
return config;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
15
|
+
const MiniCssExtractPlugin = next_dist_build_webpack_plugins_mini_css_extract_plugin.default["default"];
|
|
16
|
+
const createNavitaStylePlugin = (navitaConfig = {}) => (nextConfig) => Object.assign({}, nextConfig, { webpack(config, options) {
|
|
17
|
+
const { dir, dev } = options;
|
|
18
|
+
config.plugins?.push({ apply(compiler) {
|
|
19
|
+
(0, _navita_webpack_plugin.getNavitaModule)(compiler.webpack, ({ cssHash, issuerPath }) => ({
|
|
20
|
+
resourceResolveData: {
|
|
21
|
+
path: "",
|
|
22
|
+
query: "?" + new URLSearchParams({
|
|
23
|
+
cssHash,
|
|
24
|
+
issuerPath
|
|
25
|
+
}).toString()
|
|
26
|
+
},
|
|
27
|
+
resource: ".css"
|
|
28
|
+
}));
|
|
29
|
+
} });
|
|
30
|
+
config.module?.rules.unshift({
|
|
31
|
+
resourceQuery: [/cssHash/, /issuerPath/],
|
|
32
|
+
loader: require.resolve("@navita/next-plugin/fromServerLoader")
|
|
33
|
+
});
|
|
34
|
+
const findPagesDirResult = (0, next_dist_lib_find_pages_dir.findPagesDir)(dir);
|
|
35
|
+
const hasAppDir = !!(findPagesDirResult && findPagesDirResult.appDir);
|
|
36
|
+
const isServer = options.isServer && !(options.nextRuntime === "edge");
|
|
37
|
+
const outputCss = !isServer || hasAppDir;
|
|
38
|
+
if (!hasAppDir && !isServer) {
|
|
39
|
+
const filename = dev ? "static/css/[name].css" : "static/css/[contenthash].css";
|
|
40
|
+
config.plugins.push(new MiniCssExtractPlugin({
|
|
41
|
+
filename,
|
|
42
|
+
chunkFilename: filename,
|
|
43
|
+
ignoreOrder: true
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
if (navitaConfig?.singleCssFile) config.optimization.splitChunks = {
|
|
47
|
+
...config.optimization.splitChunks || {},
|
|
48
|
+
cacheGroups: {
|
|
49
|
+
...config.optimization.splitChunks["cacheGroups"] || {},
|
|
50
|
+
navita: {
|
|
51
|
+
chunks: "all",
|
|
52
|
+
enforce: true,
|
|
53
|
+
name: "navita",
|
|
54
|
+
type: _navita_webpack_plugin.NAVITA_MODULE_TYPE
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const { cache, mode } = config;
|
|
59
|
+
const cacheDirectory = typeof cache !== "boolean" && cache.type === "filesystem" ? node_path.default.resolve(cache.cacheDirectory, `navita-${mode}`) : void 0;
|
|
60
|
+
const cacheDestination = cacheDirectory ? node_path.default.resolve(cacheDirectory, "data.txt") : void 0;
|
|
61
|
+
const onRenderInitialized = async (createdRenderer) => {
|
|
62
|
+
renderer = createdRenderer;
|
|
63
|
+
if (cacheDirectory && cacheDestination) try {
|
|
64
|
+
await node_fs.promises.mkdir(cacheDirectory, { recursive: true });
|
|
65
|
+
const content = await node_fs.promises.readFile(cacheDestination, "utf-8");
|
|
66
|
+
await renderer.engine.deserialize(content);
|
|
67
|
+
lastCache = renderer.engine.serialize();
|
|
68
|
+
} catch {}
|
|
69
|
+
return navitaConfig.onRenderInitialized?.(renderer);
|
|
70
|
+
};
|
|
71
|
+
config.plugins?.push({ apply(compiler) {
|
|
72
|
+
compiler.hooks.afterEmit.tapPromise(`${_navita_webpack_plugin.NavitaPlugin.pluginName}-nextjs-custom-cache`, async () => {
|
|
73
|
+
if (!renderer || !cacheDestination) return;
|
|
74
|
+
const newCache = renderer.engine.serialize();
|
|
75
|
+
if (newCache === lastCache) return;
|
|
76
|
+
lastCache = newCache;
|
|
77
|
+
await node_fs.promises.writeFile(cacheDestination, newCache);
|
|
78
|
+
});
|
|
79
|
+
} });
|
|
80
|
+
config.plugins?.push(new _navita_webpack_plugin.NavitaPlugin({
|
|
81
|
+
useWebpackCache: false,
|
|
82
|
+
outputCss,
|
|
83
|
+
...navitaConfig,
|
|
84
|
+
onRenderInitialized,
|
|
85
|
+
optimizeCSSOutput: require_optimizeCSSOutput.optimizeCSSOutput
|
|
86
|
+
}));
|
|
87
|
+
if (typeof nextConfig.webpack === "function") return nextConfig.webpack(config, options);
|
|
88
|
+
return config;
|
|
89
|
+
} });
|
|
90
|
+
//#endregion
|
|
142
91
|
exports.createNavitaStylePlugin = createNavitaStylePlugin;
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Options } from
|
|
2
|
-
|
|
3
|
-
import { NextConfig } from 'next';
|
|
1
|
+
import { Options, Renderer } from "@navita/webpack-plugin";
|
|
2
|
+
import { NextConfig } from "next";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
interface Config extends Omit<Options, "useWebpackCache"> {
|
|
6
|
+
singleCssFile?: boolean;
|
|
7
7
|
}
|
|
8
8
|
declare const createNavitaStylePlugin: (navitaConfig?: Config) => (nextConfig: NextConfig) => NextConfig;
|
|
9
|
-
|
|
10
|
-
export { createNavitaStylePlugin };
|
|
9
|
+
//#endregion
|
|
10
|
+
export { type Renderer, createNavitaStylePlugin };
|
package/optimizeCSSOutput.cjs
CHANGED
|
@@ -1,98 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const merge = (...merge)=>{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
...result[key] || [],
|
|
9
|
-
...usedIds[key] || []
|
|
10
|
-
];
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
for(const key in result){
|
|
14
|
-
result[key] = [
|
|
15
|
-
...new Set(result[key])
|
|
16
|
-
];
|
|
17
|
-
}
|
|
18
|
-
return result;
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/optimizeCSSOutput.ts
|
|
3
|
+
const merge = (...merge) => {
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const usedIds of merge) for (const key in usedIds) result[key] = [...result[key] || [], ...usedIds[key] || []];
|
|
6
|
+
for (const key in result) result[key] = [...new Set(result[key])];
|
|
7
|
+
return result;
|
|
19
8
|
};
|
|
20
|
-
const intersect = (...intersect)=>{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
result[key] = result[key] ? result[key].filter((id)=>usedIds[key].includes(id)) : usedIds[key];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
9
|
+
const intersect = (...intersect) => {
|
|
10
|
+
const result = {};
|
|
11
|
+
for (const usedIds of intersect) for (const key in usedIds) result[key] = result[key] ? result[key].filter((id) => usedIds[key].includes(id)) : usedIds[key];
|
|
12
|
+
return result;
|
|
28
13
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const removeParentFromCurrent = (usedIds, parentUsedIds)=>{
|
|
33
|
-
for(const key in parentUsedIds){
|
|
34
|
-
if (usedIds[key] && parentUsedIds[key]) {
|
|
35
|
-
usedIds[key] = usedIds[key].filter((id)=>!parentUsedIds[key].includes(id));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
14
|
+
const copy = (source) => JSON.parse(JSON.stringify(source));
|
|
15
|
+
const removeParentFromCurrent = (usedIds, parentUsedIds) => {
|
|
16
|
+
for (const key in parentUsedIds) if (usedIds[key] && parentUsedIds[key]) usedIds[key] = usedIds[key].filter((id) => !parentUsedIds[key].includes(id));
|
|
38
17
|
};
|
|
39
18
|
function optimizeCSSOutput(output) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
parents.push(copy(output.get(parentChunk).usedIds));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return parents;
|
|
83
|
-
};
|
|
84
|
-
const newOutput = new Map();
|
|
85
|
-
for (const chunk of output.keys()){
|
|
86
|
-
const currentUsedIds = copy(output.get(chunk).usedIds);
|
|
87
|
-
for (const parent of getAllParentUsedIds(chunk)){
|
|
88
|
-
removeParentFromCurrent(currentUsedIds, parent);
|
|
89
|
-
}
|
|
90
|
-
newOutput.set(chunk, {
|
|
91
|
-
...output.get(chunk),
|
|
92
|
-
usedIds: currentUsedIds
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return newOutput;
|
|
19
|
+
const nameToChunk = Object.fromEntries(Array.from(output.keys()).filter((x) => x.name).map((x) => [x.name, x]));
|
|
20
|
+
const getAllParentUsedIds = (chunk) => {
|
|
21
|
+
const { name: route } = chunk;
|
|
22
|
+
if (!route) {
|
|
23
|
+
const value = output.get(chunk);
|
|
24
|
+
if (value.parents.length === 0) return [];
|
|
25
|
+
return [intersect(...value.parents.map((parent) => merge(...getAllParentUsedIds(parent), output.get(parent).usedIds)))];
|
|
26
|
+
}
|
|
27
|
+
if (route.startsWith("pages/")) {
|
|
28
|
+
const routes = [
|
|
29
|
+
"pages/_document",
|
|
30
|
+
"pages/_app",
|
|
31
|
+
route
|
|
32
|
+
];
|
|
33
|
+
const currentRouteIndex = routes.indexOf(route);
|
|
34
|
+
return routes.filter((_, index) => currentRouteIndex > index).map((x) => output.get(nameToChunk[x])).filter(Boolean).map((x) => copy(x.usedIds));
|
|
35
|
+
}
|
|
36
|
+
const parts = route.split("/");
|
|
37
|
+
const parents = [];
|
|
38
|
+
let currentPart = "";
|
|
39
|
+
for (const part of parts) {
|
|
40
|
+
currentPart = [currentPart, part].filter(Boolean).join("/");
|
|
41
|
+
const possibleParent = `${currentPart}/layout`;
|
|
42
|
+
if (route === possibleParent) continue;
|
|
43
|
+
const parentChunk = nameToChunk[possibleParent];
|
|
44
|
+
if (output.has(parentChunk)) parents.push(copy(output.get(parentChunk).usedIds));
|
|
45
|
+
}
|
|
46
|
+
return parents;
|
|
47
|
+
};
|
|
48
|
+
const newOutput = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const chunk of output.keys()) {
|
|
50
|
+
const currentUsedIds = copy(output.get(chunk).usedIds);
|
|
51
|
+
for (const parent of getAllParentUsedIds(chunk)) removeParentFromCurrent(currentUsedIds, parent);
|
|
52
|
+
newOutput.set(chunk, {
|
|
53
|
+
...output.get(chunk),
|
|
54
|
+
usedIds: currentUsedIds
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return newOutput;
|
|
96
58
|
}
|
|
97
|
-
|
|
59
|
+
//#endregion
|
|
98
60
|
exports.optimizeCSSOutput = optimizeCSSOutput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navita/next-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "3.0.0-next.1",
|
|
4
4
|
"description": "Next.js integration for Navita",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"next",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"browserslist": "^4.21.5",
|
|
28
|
-
"@navita/webpack-plugin": "0.
|
|
28
|
+
"@navita/webpack-plugin": "3.0.0-next.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"next": ">=12 || >=13",
|