@monkeyplus/flow 6.0.6 → 6.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/modules/content/module.mjs +17 -2
  2. package/modules/content/query.d.ts +25 -0
  3. package/modules/content/query.mjs +105 -19
  4. package/modules/content/runtime/client.d.ts +2 -0
  5. package/modules/content/runtime/client.mjs +1 -0
  6. package/modules/images/ipx.d.ts +5 -0
  7. package/modules/images/ipx.mjs +31 -0
  8. package/modules/images/module.d.ts +4 -0
  9. package/modules/images/module.mjs +96 -0
  10. package/modules/images/plugin.d.ts +2 -0
  11. package/modules/images/plugin.mjs +19 -0
  12. package/modules/images/runtime/build.d.ts +6 -0
  13. package/modules/images/runtime/build.mjs +142 -0
  14. package/modules/images/runtime/helpers.d.ts +16 -0
  15. package/modules/images/runtime/helpers.mjs +45 -0
  16. package/modules/images/runtime/image.d.ts +7 -0
  17. package/modules/images/runtime/image.mjs +235 -0
  18. package/modules/images/runtime/renames.d.ts +2 -0
  19. package/modules/images/runtime/renames.mjs +79 -0
  20. package/modules/images/runtime/server.d.ts +3 -0
  21. package/modules/images/runtime/server.mjs +68 -0
  22. package/modules/images/runtime/types.d.ts +77 -0
  23. package/modules/images/runtime/types.mjs +0 -0
  24. package/package.json +7 -1
  25. package/server/lib/pages.mjs +20 -21
  26. package/server/lib/render.mjs +16 -0
  27. package/server/renderer.d.ts +1 -1
  28. package/server/renderer.mjs +2 -1
  29. package/src/public/components.d.ts +3 -0
  30. package/src/public/components.mjs +3 -0
  31. package/src/public/index.d.ts +5 -3
  32. package/src/public/index.mjs +1 -0
  33. package/src/public/modules/images.d.ts +2 -0
  34. package/src/public/modules/images.mjs +1 -0
  35. package/src/public/query-content.d.ts +7 -0
  36. package/src/public/query-content.mjs +127 -0
  37. package/src/public/vite.mjs +18 -2
  38. package/src/runtime/components/MkImage.d.ts +188 -0
  39. package/src/runtime/components/MkImage.mjs +130 -0
  40. package/src/runtime/components/MkLink.d.ts +22 -0
  41. package/src/runtime/components/MkLink.mjs +72 -0
  42. package/src/runtime/components/MkPicture.d.ts +199 -0
  43. package/src/runtime/components/MkPicture.mjs +171 -0
  44. package/src/runtime/components/image-shared.d.ts +27 -0
  45. package/src/runtime/components/image-shared.mjs +51 -0
  46. package/src/runtime/config.d.ts +18 -0
  47. package/src/runtime/config.mjs +5 -2
  48. package/src/runtime/locale-routing.d.ts +12 -0
  49. package/src/runtime/locale-routing.mjs +93 -0
  50. package/src/runtime/page-discovery.mjs +8 -15
  51. package/src/runtime/pages.d.ts +16 -0
  52. package/src/runtime/virtual.d.ts +17 -0
  53. package/src/runtime/vue.mjs +6 -0
@@ -0,0 +1,235 @@
1
+ import { joinURL, encodeParam, encodePath } from "ufo";
2
+ import { getNormalName, getPreset, guessExt, parseSize } from "./helpers.mjs";
3
+ const modifierKeyMap = {
4
+ background: "b",
5
+ fit: "fit",
6
+ format: "f",
7
+ height: "h",
8
+ position: "pos",
9
+ quality: "q",
10
+ resize: "s",
11
+ width: "w"
12
+ };
13
+ function resolveIpxUrl(src, modifiers = {}, baseURL = "/_ipx") {
14
+ const normalizedModifiers = { ...modifiers };
15
+ if (normalizedModifiers.width && normalizedModifiers.height) {
16
+ normalizedModifiers.resize = `${normalizedModifiers.width}x${normalizedModifiers.height}`;
17
+ delete normalizedModifiers.width;
18
+ delete normalizedModifiers.height;
19
+ }
20
+ const params = Object.entries(normalizedModifiers).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${encodeParam(modifierKeyMap[key] || key)}_${encodeParam(String(value))}`).join(",") || "_";
21
+ const serializedModifiers = Object.entries(normalizedModifiers).filter(([, value]) => value !== void 0 && value !== null && value !== "").reduce((result, [key, value]) => {
22
+ result[key] = String(value);
23
+ return result;
24
+ }, {});
25
+ return {
26
+ modifiers: serializedModifiers,
27
+ url: joinURL(baseURL, params, encodePath(src))
28
+ };
29
+ }
30
+ function isRemoteUrl(value) {
31
+ return value.startsWith("http://") || value.startsWith("https://");
32
+ }
33
+ function normalizeDirImages(dirImages) {
34
+ return dirImages.replace(/^\/+/, "").split("/")[0] || "images";
35
+ }
36
+ function getPathname(value) {
37
+ if (isRemoteUrl(value)) {
38
+ return new URL(value).pathname;
39
+ }
40
+ return value;
41
+ }
42
+ function getBaseDir(value, dirImages) {
43
+ return getPathname(value).replace(/^\/+/, "").split("/")[0] || normalizeDirImages(dirImages);
44
+ }
45
+ function getDirectory(value) {
46
+ const pathname = getPathname(value).split(/[?#]/, 1)[0] || "";
47
+ const lastSlash = pathname.lastIndexOf("/");
48
+ return lastSlash >= 0 ? pathname.slice(0, lastSlash) : "";
49
+ }
50
+ function resolveRemoteRename(input, domains = {}) {
51
+ if (!isRemoteUrl(input)) {
52
+ return void 0;
53
+ }
54
+ const inputUrl = new URL(input);
55
+ for (const [base, target] of Object.entries(domains)) {
56
+ const normalizedBase = decodeURIComponent(base);
57
+ if (!normalizedBase) {
58
+ continue;
59
+ }
60
+ if (normalizedBase.startsWith("http://") || normalizedBase.startsWith("https://")) {
61
+ if (input.startsWith(normalizedBase)) {
62
+ return input.replace(normalizedBase, target);
63
+ }
64
+ const baseUrl = new URL(normalizedBase);
65
+ if (inputUrl.origin === baseUrl.origin && inputUrl.pathname.startsWith(baseUrl.pathname)) {
66
+ const suffix = inputUrl.pathname.slice(baseUrl.pathname.length).replace(/^\/+/, "");
67
+ return joinURL(target, suffix);
68
+ }
69
+ continue;
70
+ }
71
+ if (inputUrl.host === normalizedBase || inputUrl.hostname === normalizedBase) {
72
+ return joinURL(target, inputUrl.pathname);
73
+ }
74
+ }
75
+ return void 0;
76
+ }
77
+ function resolveRenamePath(path, rename) {
78
+ const normalizedRename = rename?.trim();
79
+ if (!normalizedRename) {
80
+ return path;
81
+ }
82
+ const directory = getDirectory(path);
83
+ let resolvedPath = normalizedRename.startsWith("/") ? normalizedRename : joinURL(directory || "/", normalizedRename);
84
+ const extension = guessExt(getPathname(path));
85
+ if (!guessExt(resolvedPath) && extension) {
86
+ resolvedPath += extension;
87
+ }
88
+ return resolvedPath;
89
+ }
90
+ export function createImageResolver(imagesOptions, stateImages, runtime = {}) {
91
+ const getImage = ((input, modifiers = {}, options = {}) => {
92
+ if (typeof input !== "string" || !input.length) {
93
+ throw new TypeError(`input must be a non-empty string (received ${typeof input})`);
94
+ }
95
+ const preset = getPreset({ options: imagesOptions }, options?.preset);
96
+ const resolvedOptions = {
97
+ ...options,
98
+ modifiers: {
99
+ ...preset?.modifiers,
100
+ ...modifiers
101
+ }
102
+ };
103
+ if (input.startsWith("data:")) {
104
+ return {
105
+ url: input
106
+ };
107
+ }
108
+ if (!input.startsWith("/") && !input.startsWith("https://") && !input.startsWith("http://")) {
109
+ input = joinURL(imagesOptions.dirImages, input);
110
+ }
111
+ const expectedFormat = resolvedOptions.modifiers?.format;
112
+ if (resolvedOptions.modifiers?.width) {
113
+ resolvedOptions.modifiers.width = parseSize(resolvedOptions.modifiers.width);
114
+ }
115
+ if (resolvedOptions.modifiers?.height) {
116
+ resolvedOptions.modifiers.height = parseSize(resolvedOptions.modifiers.height);
117
+ }
118
+ const image = resolveIpxUrl(input, resolvedOptions.modifiers || {}, imagesOptions.baseURL || "/_ipx");
119
+ image.format = image.format || expectedFormat || "";
120
+ image.src = input;
121
+ image.ext = image.format && `.${image.format}` || guessExt(input);
122
+ let baseDir = getBaseDir(input, imagesOptions.dirImages);
123
+ const originalExt = guessExt(input);
124
+ let renamedImage = stateImages.all[input]?.rename;
125
+ if (!renamedImage && imagesOptions.domains) {
126
+ renamedImage = resolveRemoteRename(input, imagesOptions.domains);
127
+ }
128
+ const replacedPath = decodeURIComponent(resolveRenamePath(renamedImage || input, resolvedOptions.rename));
129
+ if (isRemoteUrl(input) && replacedPath.startsWith("/")) {
130
+ baseDir = getBaseDir(replacedPath, imagesOptions.dirImages);
131
+ }
132
+ image.generate = decodeURIComponent(image.url).replace(decodeURIComponent(input), replacedPath).replace(`/${baseDir}`, "").replace("_ipx", baseDir).split("?")[0];
133
+ if (image.generate.startsWith("/")) {
134
+ image.generate = image.generate.replace(/\/+/g, "/");
135
+ }
136
+ if (originalExt) {
137
+ image.generate = image.generate.replace(originalExt, image.ext);
138
+ } else if (!image.generate.includes(".") && image.ext) {
139
+ image.generate += image.ext;
140
+ }
141
+ stateImages.generate[image.url] = image;
142
+ if (image.src && image.generate && image.modifiers) {
143
+ runtime.onGenerate?.({
144
+ ...image,
145
+ src: image.src,
146
+ generate: image.generate,
147
+ modifiers: image.modifiers
148
+ });
149
+ }
150
+ const src = runtime.generateOutput && image.generate ? image.generate : image.url;
151
+ if (resolvedOptions._meta) {
152
+ const meta = { ...stateImages.all[input] || { name: input, alt: void 0, title: void 0 } };
153
+ if (!meta.alt) {
154
+ meta.alt = getNormalName(meta.name);
155
+ }
156
+ if (!meta.title) {
157
+ meta.title = getNormalName(meta.name);
158
+ }
159
+ delete meta.name;
160
+ return { src, ...meta };
161
+ }
162
+ return src;
163
+ });
164
+ getImage.getSizes = (input, opts) => {
165
+ const width = parseSize(opts.modifiers?.width);
166
+ const height = parseSize(opts.modifiers?.height);
167
+ const hwRatio = width && height ? height / width : 0;
168
+ const variants = [];
169
+ const sizes = {};
170
+ if (typeof opts.sizes === "string") {
171
+ for (const entry of opts.sizes.split(/[\s,]+/).filter(Boolean)) {
172
+ const sizeEntry = entry.split(":");
173
+ if (sizeEntry.length !== 2) {
174
+ continue;
175
+ }
176
+ sizes[sizeEntry[0].trim()] = sizeEntry[1].trim();
177
+ }
178
+ } else {
179
+ Object.assign(sizes, opts.sizes);
180
+ }
181
+ for (const key in sizes) {
182
+ const screenMaxWidth = imagesOptions.screens?.[key] || parseInt(key, 10);
183
+ let size = String(sizes[key]);
184
+ const isFluid = size.endsWith("vw");
185
+ if (!isFluid && /^\d+$/.test(size)) {
186
+ size = `${size}px`;
187
+ }
188
+ if (!isFluid && !size.endsWith("px")) {
189
+ continue;
190
+ }
191
+ let calculatedWidth = parseInt(size, 10);
192
+ if (!screenMaxWidth || !calculatedWidth) {
193
+ continue;
194
+ }
195
+ if (isFluid) {
196
+ calculatedWidth = Math.round(calculatedWidth / 100 * screenMaxWidth);
197
+ }
198
+ const calculatedHeight = hwRatio ? Math.round(calculatedWidth * hwRatio) : height;
199
+ const resizeConfig = opts.modifiers?.resize;
200
+ const resize = resizeConfig?.[screenMaxWidth] || resizeConfig?.["*"];
201
+ const positionConfig = opts.modifiers?.position;
202
+ const position = positionConfig?.[screenMaxWidth] || positionConfig?.["*"] || positionConfig;
203
+ const currentModifiers = {
204
+ ...opts.modifiers,
205
+ width: calculatedWidth,
206
+ height: calculatedHeight,
207
+ resize,
208
+ position
209
+ };
210
+ if (resize) {
211
+ delete currentModifiers.width;
212
+ }
213
+ variants.push({
214
+ width: calculatedWidth,
215
+ size,
216
+ screenMaxWidth,
217
+ media: `(max-width: ${screenMaxWidth}px)`,
218
+ src: getImage(input, currentModifiers, opts)
219
+ });
220
+ }
221
+ variants.sort((left, right) => left.screenMaxWidth - right.screenMaxWidth);
222
+ const defaultVariant = variants[variants.length - 1];
223
+ if (defaultVariant) {
224
+ defaultVariant.media = "";
225
+ }
226
+ return {
227
+ sizes: variants.map((variant) => `${variant.media ? `${variant.media} ` : ""}${variant.size}`).join(", "),
228
+ srcset: variants.map((variant) => `${variant.src} ${variant.width}w`).join(", "),
229
+ src: defaultVariant?.src
230
+ };
231
+ };
232
+ return {
233
+ getImage
234
+ };
235
+ }
@@ -0,0 +1,2 @@
1
+ import type { FlowImageMeta } from './types.ts';
2
+ export declare function loadImageRenames(sources: string[]): Record<string, FlowImageMeta>;
@@ -0,0 +1,79 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
3
+ import { joinURL } from "ufo";
4
+ const RENAME_FILE = "_rename";
5
+ function normalizePublicPath(path) {
6
+ return `/${path}`.replace(/\\/g, "/").replace(/\/+/g, "/");
7
+ }
8
+ function isHeaderLine(original, renamed) {
9
+ return original.toLowerCase().includes("nombre") && renamed.toLowerCase().includes("nuevo");
10
+ }
11
+ function walkRenameFiles(rootDir, currentDir = rootDir, files = []) {
12
+ if (!existsSync(currentDir)) {
13
+ return files;
14
+ }
15
+ for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
16
+ const entryPath = join(currentDir, entry.name);
17
+ if (entry.isDirectory()) {
18
+ walkRenameFiles(rootDir, entryPath, files);
19
+ continue;
20
+ }
21
+ if (entry.isFile() && entry.name === RENAME_FILE) {
22
+ files.push(entryPath);
23
+ }
24
+ }
25
+ return files;
26
+ }
27
+ function toPublicImagePath(rootDir, manifestDir, fileName) {
28
+ const relativeDir = relative(rootDir, manifestDir).split(sep).join("/");
29
+ return normalizePublicPath(joinURL(relativeDir, fileName));
30
+ }
31
+ function parseRenameLine(rootDir, filePath, line) {
32
+ const trimmed = line.trim();
33
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) {
34
+ return void 0;
35
+ }
36
+ const [original, renamed, title, ...altParts] = trimmed.split(/\s+/);
37
+ if (!original || !renamed || isHeaderLine(original, renamed)) {
38
+ return void 0;
39
+ }
40
+ const sourceExt = extname(original);
41
+ const renamedWithExt = extname(renamed) ? renamed : `${renamed}${sourceExt}`;
42
+ const manifestDir = dirname(filePath);
43
+ const sourcePath = toPublicImagePath(rootDir, manifestDir, original);
44
+ const renamedPath = toPublicImagePath(rootDir, manifestDir, renamedWithExt);
45
+ return {
46
+ key: sourcePath,
47
+ value: {
48
+ rename: renamedPath,
49
+ name: basename(renamedWithExt, extname(renamedWithExt)),
50
+ title,
51
+ alt: altParts.length ? altParts.join(" ") : void 0
52
+ }
53
+ };
54
+ }
55
+ function readRenameDirectory(rootDir) {
56
+ const entries = {};
57
+ for (const filePath of walkRenameFiles(resolve(rootDir))) {
58
+ const raw = readFileSync(filePath, "utf8");
59
+ for (const line of raw.split(/\r?\n/)) {
60
+ const parsed = parseRenameLine(rootDir, filePath, line);
61
+ if (!parsed) {
62
+ continue;
63
+ }
64
+ entries[parsed.key] = parsed.value;
65
+ }
66
+ }
67
+ return entries;
68
+ }
69
+ export function loadImageRenames(sources) {
70
+ return sources.reduce((collection, source) => {
71
+ if (!source || !existsSync(source)) {
72
+ return collection;
73
+ }
74
+ return {
75
+ ...collection,
76
+ ...readRenameDirectory(source)
77
+ };
78
+ }, {});
79
+ }
@@ -0,0 +1,3 @@
1
+ import type { FlowImagesRuntimeConfig, FlowImageRuntimeUtils } from './types.ts';
2
+ export declare function getFlowImageRuntimeUtils(config?: FlowImagesRuntimeConfig | undefined): FlowImageRuntimeUtils | undefined;
3
+ export declare function resetFlowImageRuntimeState(): void;
@@ -0,0 +1,68 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { loadImageRenames } from "./renames.mjs";
5
+ import { createImageResolver } from "./image.mjs";
6
+ let runtimeConfigRequire;
7
+ let cachedKey;
8
+ let cachedUtils;
9
+ let generatedEntryKeys = /* @__PURE__ */ new Set();
10
+ function getFlowImagesRuntimeConfig() {
11
+ try {
12
+ runtimeConfigRequire ??= createRequire(import.meta.url);
13
+ const runtime = runtimeConfigRequire("nitro/runtime-config");
14
+ return runtime.useRuntimeConfig?.().flow?.images;
15
+ } catch {
16
+ return void 0;
17
+ }
18
+ }
19
+ export function getFlowImageRuntimeUtils(config = getFlowImagesRuntimeConfig()) {
20
+ if (!config) {
21
+ return void 0;
22
+ }
23
+ const cacheKey = JSON.stringify(config);
24
+ const useCache = process.env.NODE_ENV === "production";
25
+ if (useCache && cachedKey === cacheKey && cachedUtils) {
26
+ return cachedUtils;
27
+ }
28
+ const renameSources = [config.dirRenames, config.publicDir].filter(Boolean);
29
+ const images = {
30
+ all: loadImageRenames(renameSources),
31
+ generate: {}
32
+ };
33
+ const resolver = createImageResolver(config.options, images, {
34
+ generateOutput: config.generate,
35
+ onGenerate(image) {
36
+ if (!config.generatedManifestPath) {
37
+ return;
38
+ }
39
+ const dedupeKey = `${image.url}:${image.generate}`;
40
+ if (generatedEntryKeys.has(dedupeKey)) {
41
+ return;
42
+ }
43
+ generatedEntryKeys.add(dedupeKey);
44
+ mkdirSync(dirname(config.generatedManifestPath), { recursive: true });
45
+ appendFileSync(config.generatedManifestPath, `${JSON.stringify(image)}
46
+ `, "utf8");
47
+ }
48
+ });
49
+ const utils = {
50
+ getImage: resolver.getImage,
51
+ getImageMeta(src) {
52
+ return images.all[src];
53
+ },
54
+ getImageOptions() {
55
+ return { ...config.options };
56
+ }
57
+ };
58
+ if (useCache) {
59
+ cachedKey = cacheKey;
60
+ cachedUtils = utils;
61
+ }
62
+ return utils;
63
+ }
64
+ export function resetFlowImageRuntimeState() {
65
+ generatedEntryKeys = /* @__PURE__ */ new Set();
66
+ cachedKey = void 0;
67
+ cachedUtils = void 0;
68
+ }
@@ -0,0 +1,77 @@
1
+ export interface FlowImagesModuleOptions {
2
+ dirRenames: string;
3
+ dirFiles: string[];
4
+ lazy: boolean;
5
+ screens: Record<string, number>;
6
+ domains?: Record<string, string>;
7
+ presets: Record<string, Record<string, unknown>>;
8
+ baseURL: string;
9
+ dirImages: string;
10
+ }
11
+ export type FlowImageOptions = Omit<FlowImagesModuleOptions, 'dirRenames' | 'dirFiles'>;
12
+ export interface FlowImageMeta {
13
+ rename?: string;
14
+ name: string;
15
+ alt?: string;
16
+ title?: string;
17
+ }
18
+ export interface ImageModifiers {
19
+ width?: number;
20
+ height?: number;
21
+ fit?: string;
22
+ format?: string;
23
+ [key: string]: any;
24
+ }
25
+ export interface ImageOptions {
26
+ provider?: string;
27
+ preset?: string;
28
+ rename?: string;
29
+ modifiers?: Partial<ImageModifiers>;
30
+ _meta?: boolean;
31
+ [key: string]: any;
32
+ }
33
+ export interface ImageSizesOptions extends ImageOptions {
34
+ sizes: Record<string, string | number> | string;
35
+ }
36
+ export interface ImageSizes {
37
+ srcset: string;
38
+ sizes: string;
39
+ src?: string;
40
+ }
41
+ export interface ResolvedImage {
42
+ url: string;
43
+ format?: string;
44
+ src?: string;
45
+ ext?: string;
46
+ generate?: string;
47
+ modifiers?: Record<string, string>;
48
+ }
49
+ export interface GeneratedImageEntry extends ResolvedImage {
50
+ src: string;
51
+ generate: string;
52
+ modifiers: Record<string, string>;
53
+ }
54
+ export type GetImageFunction = ((input: string, modifiers?: Record<string, any>, options?: ImageOptions) => string | Record<string, unknown>) & {
55
+ getSizes: (input: string, options: ImageSizesOptions) => ImageSizes;
56
+ };
57
+ export interface FlowImagesState {
58
+ all: Record<string, FlowImageMeta>;
59
+ generate: Record<string, unknown>;
60
+ }
61
+ export interface FlowImagesRuntimeConfig {
62
+ dirRenames: string;
63
+ dirFiles: string[];
64
+ publicDir: string;
65
+ outputDir: string;
66
+ generatedManifestPath: string;
67
+ generatedCacheDir?: string;
68
+ generatedCacheManifestPath?: string;
69
+ generate: boolean;
70
+ netlifyCache: boolean;
71
+ options: FlowImageOptions;
72
+ }
73
+ export interface FlowImageRuntimeUtils {
74
+ getImage: GetImageFunction;
75
+ getImageMeta: (src: string) => FlowImageMeta | undefined;
76
+ getImageOptions: () => FlowImageOptions;
77
+ }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monkeyplus/flow",
3
- "version": "6.0.6",
3
+ "version": "6.0.7",
4
4
  "description": "@monkeyplus/flow package-first runtime with Vite, Nitro, Vue and a workspace playground.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -40,6 +40,10 @@
40
40
  "types": "./modules/strapi/runtime/client.d.ts",
41
41
  "import": "./modules/strapi/runtime/client.mjs"
42
42
  },
43
+ "./modules/images": {
44
+ "types": "./src/public/modules/images.d.ts",
45
+ "import": "./src/public/modules/images.mjs"
46
+ },
43
47
  "./modules/sitemap": {
44
48
  "types": "./src/public/modules/sitemap.d.ts",
45
49
  "import": "./src/public/modules/sitemap.mjs"
@@ -72,8 +76,10 @@
72
76
  "@unhead/vue": "^3.1.0",
73
77
  "@vitejs/plugin-vue": "^6.0.5",
74
78
  "@vue/server-renderer": "^3.5.13",
79
+ "ipx": "^3.1.1",
75
80
  "nitro": "3.0.260429-beta",
76
81
  "std-env": "^4.0.0",
82
+ "ufo": "^1.6.1",
77
83
  "unhead": "^3.1.0",
78
84
  "unplugin-icons": "^23.0.1",
79
85
  "vite": "^8.0.5",
@@ -1,5 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import pageDefinitions from "virtual:flow/pages";
3
+ import { getFlowImageRuntimeUtils } from "../../modules/images/runtime/server.mjs";
4
+ import { localizeRoutePattern, normalizePath, toPublicRoute } from "../../src/runtime/locale-routing.mjs";
3
5
  const dynamicRouteCache = /* @__PURE__ */ new Map();
4
6
  let runtimeConfigRequire;
5
7
  function getFlowRuntimeConfig() {
@@ -11,12 +13,6 @@ function getFlowRuntimeConfig() {
11
13
  return {};
12
14
  }
13
15
  }
14
- function normalizePath(value) {
15
- if (!value || value === "/") {
16
- return "/";
17
- }
18
- return value.length > 1 && value.endsWith("/") ? value.slice(0, -1) : value;
19
- }
20
16
  function scorePattern(pattern) {
21
17
  return normalizePath(pattern).split("/").filter(Boolean).reduce((score, segment) => {
22
18
  if (segment === "**") {
@@ -69,6 +65,7 @@ function createLocale(code) {
69
65
  };
70
66
  }
71
67
  function createContext(definition, locale, localePage, pathname, params, dynamic) {
68
+ const imageUtils = getFlowImageRuntimeUtils();
72
69
  return {
73
70
  dynamic,
74
71
  locale,
@@ -82,11 +79,12 @@ function createContext(definition, locale, localePage, pathname, params, dynamic
82
79
  return locale;
83
80
  },
84
81
  async getUrl(namePage, localeCode, options) {
85
- return await getUrl(namePage, localeCode, options);
82
+ return await getUrl(namePage, localeCode || locale.code, options);
86
83
  },
87
84
  async getUrls(withLocale, omitNoPublish) {
88
85
  return await getUrls(withLocale, omitNoPublish);
89
- }
86
+ },
87
+ ...imageUtils || {}
90
88
  }
91
89
  };
92
90
  }
@@ -109,9 +107,6 @@ function replacePath(pattern, url) {
109
107
  }
110
108
  return normalizePath(resolved.replaceAll("**", ""));
111
109
  }
112
- function toPublicRoutePattern(url) {
113
- return normalizePath(url.replaceAll("*", ""));
114
- }
115
110
  function combineName(name, dynamicName) {
116
111
  return `${name.replace(/\/*$/, "")}/${dynamicName.replace(/^\/*/, "")}`;
117
112
  }
@@ -147,6 +142,7 @@ async function resolveDynamicEntry(definition, localeCode, localePage, ctx, path
147
142
  export async function getUrl(namePage, localeCode, options = {}) {
148
143
  const enabledLocales = getEnabledLocaleCodes();
149
144
  const targetLocale = localeCode || enabledLocales[0];
145
+ const runtimeConfig = getFlowRuntimeConfig();
150
146
  for (const definition of pageDefinitions) {
151
147
  if (definition.name !== namePage) {
152
148
  continue;
@@ -154,32 +150,34 @@ export async function getUrl(namePage, localeCode, options = {}) {
154
150
  const localeEntries = targetLocale ? [targetLocale] : Object.keys(definition.locales);
155
151
  for (const code of localeEntries) {
156
152
  const localePage = definition.locales[code];
153
+ const localizedRoute = localizeRoutePattern(runtimeConfig.flow, code, localePage?.url || "/");
157
154
  if (!localePage) {
158
155
  continue;
159
156
  }
160
157
  if (options.url) {
161
- return replacePath(localePage.url, options.url);
158
+ return replacePath(localizedRoute, options.url);
162
159
  }
163
160
  if (options.params) {
164
- return replacePath(localePage.url, options.params);
161
+ return replacePath(localizedRoute, options.params);
165
162
  }
166
163
  if (localePage.dynamic && options.dynamicName) {
167
164
  const locale = createLocale(code);
168
- const ctx = createContext(definition, locale, localePage, localePage.url, {});
165
+ const ctx = createContext(definition, locale, localePage, toPublicRoute(runtimeConfig.flow, code, localePage.url), {});
169
166
  const entries = await getDynamicEntries(definition, code, ctx);
170
167
  const entry = entries.find((candidate) => candidate.name === options.dynamicName);
171
168
  if (!entry) {
172
169
  return void 0;
173
170
  }
174
- return replacePath(localePage.url, entry.url);
171
+ return replacePath(localizedRoute, entry.url);
175
172
  }
176
- return toPublicRoutePattern(localePage.url);
173
+ return toPublicRoute(runtimeConfig.flow, code, localePage.url);
177
174
  }
178
175
  }
179
176
  return void 0;
180
177
  }
181
178
  export async function getUrls(withLocale = false, omitNoPublish = false) {
182
179
  const enabledLocales = getEnabledLocaleCodes();
180
+ const runtimeConfig = getFlowRuntimeConfig();
183
181
  const urls = [];
184
182
  for (const definition of pageDefinitions) {
185
183
  if (omitNoPublish && definition.noPublish) {
@@ -191,15 +189,15 @@ export async function getUrls(withLocale = false, omitNoPublish = false) {
191
189
  }
192
190
  if (localePage.dynamic) {
193
191
  const locale = createLocale(localeCode);
194
- const ctx = createContext(definition, locale, localePage, localePage.url, {});
192
+ const ctx = createContext(definition, locale, localePage, toPublicRoute(runtimeConfig.flow, localeCode, localePage.url), {});
195
193
  const entries = await getDynamicEntries(definition, localeCode, ctx);
196
194
  for (const entry of entries) {
197
- const url2 = replacePath(localePage.url, entry.url);
195
+ const url2 = replacePath(localizeRoutePattern(runtimeConfig.flow, localeCode, localePage.url), entry.url);
198
196
  urls.push(withLocale ? { url: url2, locale: localeCode, name: combineName(definition.name, entry.name) } : url2);
199
197
  }
200
198
  continue;
201
199
  }
202
- const url = toPublicRoutePattern(localePage.url);
200
+ const url = toPublicRoute(runtimeConfig.flow, localeCode, localePage.url);
203
201
  urls.push(withLocale ? { url, locale: localeCode, name: definition.name } : url);
204
202
  }
205
203
  }
@@ -214,14 +212,15 @@ export async function resolvePage(pathname) {
214
212
  if (enabledLocales.length && !enabledLocales.includes(localeCode)) {
215
213
  continue;
216
214
  }
217
- const params = matchPattern(localePage.url, pathname);
215
+ const localizedRoute = localizeRoutePattern(runtimeConfig.flow, localeCode, localePage.url);
216
+ const params = matchPattern(localizedRoute, pathname);
218
217
  if (params) {
219
218
  candidates.push({
220
219
  definition,
221
220
  localeCode,
222
221
  localePage,
223
222
  params,
224
- score: scorePattern(localePage.url)
223
+ score: scorePattern(localizedRoute)
225
224
  });
226
225
  }
227
226
  }
@@ -5,7 +5,9 @@ import bases from "virtual:flow/bases";
5
5
  import layouts from "virtual:flow/layouts";
6
6
  import templates from "virtual:flow/templates";
7
7
  import { createSSRApp, defineComponent, h } from "vue";
8
+ import { getFlowImageRuntimeUtils } from "../../modules/images/runtime/server.mjs";
8
9
  import { installFlowVuePlugins } from "../../src/runtime/vue";
10
+ import { getUrl, getUrls } from "./pages.mjs";
9
11
  function escapeHtml(value) {
10
12
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11
13
  }
@@ -104,6 +106,19 @@ async function renderBody(page) {
104
106
  params: page.params,
105
107
  path: page.pathname
106
108
  };
109
+ const imageUtils = getFlowImageRuntimeUtils();
110
+ const utils = {
111
+ getLocale() {
112
+ return page.locale;
113
+ },
114
+ async getUrl(namePage, localeCode, options) {
115
+ return await getUrl(namePage, localeCode || page.locale.code, options);
116
+ },
117
+ async getUrls(withLocale, omitNoPublish) {
118
+ return await getUrls(withLocale, omitNoPublish);
119
+ },
120
+ ...imageUtils || {}
121
+ };
107
122
  const TemplateRoot = defineComponent({
108
123
  render() {
109
124
  const viewNode = h(TemplateComponent, context);
@@ -117,6 +132,7 @@ async function renderBody(page) {
117
132
  });
118
133
  const app = createSSRApp(TemplateRoot);
119
134
  app.provide("context", context);
135
+ app.provide("utils", utils);
120
136
  const head = createHead();
121
137
  head.push(normalizeSeoToHead(page.head, fallbackTitle, fallbackDescription));
122
138
  app.use(head);
@@ -1,3 +1,3 @@
1
1
  export default function renderPage(input: Request | {
2
2
  req?: Request;
3
- }): Promise<Response | undefined>;
3
+ }): Promise<any>;