@monkeyplus/flow 6.0.5 → 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 +28 -10
  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
@@ -1,6 +1,6 @@
1
1
  import { resolve } from "node:path";
2
- import { defineFlowModule } from "../../src/runtime/config.mjs";
3
2
  import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.mjs";
3
+ import { defineFlowModule } from "../../src/runtime/config.mjs";
4
4
  export default defineFlowModule({
5
5
  meta: {
6
6
  name: "content",
@@ -12,21 +12,36 @@ export default defineFlowModule({
12
12
  },
13
13
  setup(options, context) {
14
14
  const contentDir = resolve(context.projectRoot, options.dir);
15
+ const publicConfig = {
16
+ apiBase: options.apiBase
17
+ };
15
18
  const queryHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/content/query.ts") : resolvePackageFile("modules/content/query.ts", "modules/content/query.mjs", "modules/content/query.js");
16
19
  context.nitro.handlers.push({
17
20
  method: "get",
18
21
  route: `${options.apiBase}/query`,
19
22
  handler: queryHandlerPath
20
23
  });
24
+ context.nitro.handlers.push({
25
+ method: "get",
26
+ route: `${options.apiBase}/tree`,
27
+ handler: queryHandlerPath
28
+ });
21
29
  context.nitro.routeRules[`${options.apiBase}/**`] = {
22
30
  cors: true
23
31
  };
24
32
  context.nitro.runtimeConfig.flow = {
25
33
  ...typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {},
26
34
  content: {
27
- apiBase: options.apiBase,
35
+ ...publicConfig,
28
36
  dir: contentDir
29
37
  }
30
38
  };
39
+ context.nitro.runtimeConfig.public = {
40
+ ...typeof context.nitro.runtimeConfig.public === "object" && context.nitro.runtimeConfig.public ? context.nitro.runtimeConfig.public : {},
41
+ flow: {
42
+ ...typeof context.nitro.runtimeConfig.public?.flow === "object" && context.nitro.runtimeConfig.public.flow ? context.nitro.runtimeConfig.public.flow : {},
43
+ content: publicConfig
44
+ }
45
+ };
31
46
  }
32
47
  });
@@ -1,2 +1,27 @@
1
+ export interface ContentEntry {
2
+ path: string;
3
+ stem: string;
4
+ dir: string;
5
+ name: string;
6
+ extension: string;
7
+ title?: string;
8
+ body: string;
9
+ data: Record<string, unknown>;
10
+ }
11
+ export interface ContentDirectoryNode {
12
+ kind: 'directory';
13
+ name: string;
14
+ path: string;
15
+ stem: string;
16
+ children: ContentTreeNode[];
17
+ }
18
+ export interface ContentFileNode extends ContentEntry {
19
+ kind: 'file';
20
+ }
21
+ export type ContentTreeNode = ContentDirectoryNode | ContentFileNode;
22
+ export declare function findContentEntries(entries: ContentEntry[], path?: string): ContentEntry[];
23
+ export declare function readContentEntries(contentDir: string): ContentEntry[];
24
+ export declare function buildContentTree(entries: ContentEntry[]): ContentTreeNode[];
25
+ export declare function findContentTree(tree: ContentTreeNode[], path?: string): ContentTreeNode[];
1
26
  declare const _default: any;
2
27
  export default _default;
@@ -1,6 +1,6 @@
1
- import { existsSync, readFileSync, readdirSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { extname, relative, resolve } from "node:path";
3
- import { defineEventHandler, getQuery } from "nitro/h3";
3
+ import { defineEventHandler, getQuery, getRequestURL } from "nitro/h3";
4
4
  import { useRuntimeConfig } from "nitro/runtime-config";
5
5
  function collectFiles(rootDir, currentDir = rootDir) {
6
6
  const entries = readdirSync(currentDir, { withFileTypes: true });
@@ -22,9 +22,30 @@ function collectFiles(rootDir, currentDir = rootDir) {
22
22
  }
23
23
  return files.sort((left, right) => left.localeCompare(right));
24
24
  }
25
+ function normalizeQueryPath(path) {
26
+ if (!path) {
27
+ return "/";
28
+ }
29
+ const normalized = `/${path}`.replace(/\/+/g, "/");
30
+ return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
31
+ }
32
+ export function findContentEntries(entries, path) {
33
+ const normalizedPath = normalizeQueryPath(path);
34
+ if (normalizedPath === "/") {
35
+ return entries;
36
+ }
37
+ return entries.filter((entry) => entry.path === normalizedPath || entry.path.startsWith(`${normalizedPath}/`));
38
+ }
25
39
  function normalizeContentPath(rootDir, filePath) {
26
40
  const shortPath = relative(rootDir, filePath).replaceAll("\\", "/");
27
- return `/${shortPath.replace(/\.(md|json|ya?ml|txt)$/i, "")}`;
41
+ const stem = shortPath.replace(/\.(md|json|ya?ml|txt)$/i, "");
42
+ const segments = stem.split("/").filter(Boolean);
43
+ return {
44
+ path: `/${stem}`,
45
+ stem,
46
+ dir: segments.length > 1 ? `/${segments.slice(0, -1).join("/")}` : "/",
47
+ name: segments[segments.length - 1] || stem
48
+ };
28
49
  }
29
50
  function parseKeyValueBlock(block) {
30
51
  return block.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reduce((data, line) => {
@@ -41,26 +62,21 @@ function parseKeyValueBlock(block) {
41
62
  function parseContentFile(rootDir, filePath) {
42
63
  const raw = readFileSync(filePath, "utf8");
43
64
  const extension = extname(filePath).toLowerCase();
44
- const path = normalizeContentPath(rootDir, filePath);
65
+ const normalizedPath = normalizeContentPath(rootDir, filePath);
45
66
  if (extension === ".json") {
46
67
  const parsed = JSON.parse(raw);
47
68
  return {
48
- path,
69
+ ...normalizedPath,
49
70
  extension,
50
71
  title: typeof parsed.title === "string" ? parsed.title : void 0,
51
- body: JSON.stringify(parsed, null, 2),
52
- data: Object.entries(parsed).reduce((result, [key, value]) => {
53
- if (typeof value === "string") {
54
- result[key] = value;
55
- }
56
- return result;
57
- }, {})
72
+ body: raw,
73
+ data: parsed
58
74
  };
59
75
  }
60
76
  if ((extension === ".yml" || extension === ".yaml") && raw.trim()) {
61
77
  const data = parseKeyValueBlock(raw);
62
78
  return {
63
- path,
79
+ ...normalizedPath,
64
80
  extension,
65
81
  title: data.title,
66
82
  body: raw,
@@ -74,7 +90,7 @@ function parseContentFile(rootDir, filePath) {
74
90
  const body = raw.slice(end + 5).trim();
75
91
  const data = parseKeyValueBlock(frontmatter);
76
92
  return {
77
- path,
93
+ ...normalizedPath,
78
94
  extension,
79
95
  title: data.title,
80
96
  body,
@@ -83,22 +99,92 @@ function parseContentFile(rootDir, filePath) {
83
99
  }
84
100
  }
85
101
  return {
86
- path,
102
+ ...normalizedPath,
87
103
  extension,
88
104
  body: raw,
89
105
  data: {}
90
106
  };
91
107
  }
108
+ export function readContentEntries(contentDir) {
109
+ if (!contentDir || !existsSync(contentDir)) {
110
+ return [];
111
+ }
112
+ return collectFiles(contentDir).map((filePath) => parseContentFile(contentDir, filePath));
113
+ }
114
+ function sortTree(nodes) {
115
+ nodes.sort((left, right) => {
116
+ if (left.kind !== right.kind) {
117
+ return left.kind === "directory" ? -1 : 1;
118
+ }
119
+ return left.name.localeCompare(right.name);
120
+ });
121
+ for (const node of nodes) {
122
+ if (node.kind === "directory") {
123
+ sortTree(node.children);
124
+ }
125
+ }
126
+ return nodes;
127
+ }
128
+ export function buildContentTree(entries) {
129
+ const roots = [];
130
+ const directories = /* @__PURE__ */ new Map();
131
+ for (const entry of entries) {
132
+ const segments = entry.stem.split("/").filter(Boolean);
133
+ const dirSegments = segments.slice(0, -1);
134
+ let siblings = roots;
135
+ for (let index = 0; index < dirSegments.length; index += 1) {
136
+ const stem = dirSegments.slice(0, index + 1).join("/");
137
+ let directory = directories.get(stem);
138
+ if (!directory) {
139
+ directory = {
140
+ kind: "directory",
141
+ name: dirSegments[index],
142
+ path: `/${stem}`,
143
+ stem,
144
+ children: []
145
+ };
146
+ directories.set(stem, directory);
147
+ siblings.push(directory);
148
+ }
149
+ siblings = directory.children;
150
+ }
151
+ siblings.push({
152
+ kind: "file",
153
+ ...entry
154
+ });
155
+ }
156
+ return sortTree(roots);
157
+ }
158
+ export function findContentTree(tree, path) {
159
+ const normalizedPath = normalizeQueryPath(path);
160
+ if (normalizedPath === "/") {
161
+ return tree;
162
+ }
163
+ const stack = [...tree];
164
+ while (stack.length) {
165
+ const node = stack.shift();
166
+ if (node.path === normalizedPath) {
167
+ return [node];
168
+ }
169
+ if (node.kind === "directory") {
170
+ stack.unshift(...node.children);
171
+ }
172
+ }
173
+ return [];
174
+ }
92
175
  export default defineEventHandler((event) => {
93
176
  const runtimeConfig = useRuntimeConfig();
94
177
  const query = getQuery(event);
95
178
  const contentDir = runtimeConfig.flow?.content?.dir;
96
- if (!contentDir || !existsSync(contentDir)) {
97
- return [];
179
+ const requestUrl = getRequestURL(event);
180
+ const isTreeRequest = requestUrl.pathname.endsWith("/tree") || query.tree === true || query.tree === "true" || query.tree === "1";
181
+ const entries = readContentEntries(contentDir || "");
182
+ if (isTreeRequest) {
183
+ const tree = buildContentTree(entries);
184
+ return findContentTree(tree, query.path);
98
185
  }
99
- const entries = collectFiles(contentDir).map((filePath) => parseContentFile(contentDir, filePath));
100
186
  if (query.path) {
101
- return entries.filter((entry) => entry.path === query.path || entry.path.startsWith(`${query.path}/`));
187
+ return findContentEntries(entries, query.path);
102
188
  }
103
189
  return entries;
104
190
  });
@@ -0,0 +1,2 @@
1
+ export { queryContent } from '../../../src/public/query-content.ts';
2
+ export type { QueryContentBuilder } from '../../../src/public/query-content.ts';
@@ -0,0 +1 @@
1
+ export { queryContent } from "../../../src/public/query-content.mjs";
@@ -0,0 +1,5 @@
1
+ export default function ipxHandler(event: any): Promise<string | void | Buffer<ArrayBufferLike> | {
2
+ error: {
3
+ message: string;
4
+ };
5
+ }> | Response;
@@ -0,0 +1,31 @@
1
+ import { createIPX, createIPXH3Handler, ipxFSStorage, ipxHttpStorage } from "ipx";
2
+ import { useRuntimeConfig } from "nitro/runtime-config";
3
+ let cachedDir = "";
4
+ let cachedDomainsKey = "";
5
+ let cachedHandler;
6
+ function resolveHandler() {
7
+ const config = useRuntimeConfig();
8
+ const publicDir = config.flow?.images?.publicDir;
9
+ const domains = Object.keys(config.flow?.images?.options?.domains || {});
10
+ const domainsKey = domains.join("|");
11
+ if (!publicDir) {
12
+ return void 0;
13
+ }
14
+ if (!cachedHandler || cachedDir !== publicDir || cachedDomainsKey !== domainsKey) {
15
+ cachedDir = publicDir;
16
+ cachedDomainsKey = domainsKey;
17
+ const ipx = createIPX({
18
+ storage: ipxFSStorage({ dir: publicDir }),
19
+ ...domains.length ? { httpStorage: ipxHttpStorage({ domains }) } : {}
20
+ });
21
+ cachedHandler = createIPXH3Handler(ipx);
22
+ }
23
+ return cachedHandler;
24
+ }
25
+ export default function ipxHandler(event) {
26
+ const handler = resolveHandler();
27
+ if (!handler) {
28
+ return new Response("Not Found", { status: 404 });
29
+ }
30
+ return handler(event);
31
+ }
@@ -0,0 +1,4 @@
1
+ import type { FlowImagesModuleOptions } from './runtime/types.ts';
2
+ export type { FlowImagesModuleOptions as ImagesModuleOptions } from './runtime/types.ts';
3
+ declare const _default: import("../../src/runtime/config.ts").FlowModuleDefinition<FlowImagesModuleOptions>;
4
+ export default _default;
@@ -0,0 +1,96 @@
1
+ import { rmSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { joinURL } from "ufo";
4
+ import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.mjs";
5
+ import { defineFlowModule } from "../../src/runtime/config.mjs";
6
+ import { screens } from "./runtime/helpers.mjs";
7
+ function withoutTrailingSlash(value) {
8
+ return value.replace(/\/+$/, "");
9
+ }
10
+ function resolveStrapiDomains(flowConfig, dirImages) {
11
+ const strapi = flowConfig.strapi;
12
+ if (!strapi || typeof strapi.url !== "string" || !strapi.url) {
13
+ return {};
14
+ }
15
+ try {
16
+ const url = new URL(strapi.url);
17
+ const target = joinURL(dirImages, "strapi");
18
+ const values = /* @__PURE__ */ new Set([
19
+ withoutTrailingSlash(url.origin),
20
+ withoutTrailingSlash(url.toString())
21
+ ]);
22
+ if (url.pathname && url.pathname !== "/") {
23
+ values.add(withoutTrailingSlash(`${url.origin}${url.pathname}`));
24
+ }
25
+ return [...values].reduce((result, value) => {
26
+ if (value) {
27
+ result[value] = target;
28
+ }
29
+ return result;
30
+ }, {});
31
+ } catch {
32
+ return {};
33
+ }
34
+ }
35
+ export default defineFlowModule({
36
+ meta: {
37
+ name: "images",
38
+ configKey: "images"
39
+ },
40
+ defaults: {
41
+ dirRenames: "shared/seo_images",
42
+ dirFiles: ["images", "media"],
43
+ lazy: true,
44
+ screens,
45
+ baseURL: "/_ipx",
46
+ dirImages: "/images",
47
+ domains: {},
48
+ presets: {}
49
+ },
50
+ setup(options, context) {
51
+ const resolvedDomains = {
52
+ ...resolveStrapiDomains(context.flowConfig, options.dirImages),
53
+ ...options.domains || {}
54
+ };
55
+ const isSsg = context.flowConfig.build.preset === "ssg";
56
+ const isNetlify = !!process.env.NETLIFY;
57
+ const publicDir = resolve(context.projectRoot, "public");
58
+ const renameDir = resolve(context.projectRoot, options.dirRenames);
59
+ const imagesCacheRoot = resolve(context.projectRoot, "node_modules/.cache-images");
60
+ const generatedManifestPath = resolve(imagesCacheRoot, "generated-images.jsonl");
61
+ const generatedCacheDir = isNetlify ? resolve(imagesCacheRoot, "netlify") : void 0;
62
+ const generatedCacheManifestPath = generatedCacheDir ? resolve(generatedCacheDir, "manifest.json") : void 0;
63
+ const ipxHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/images/ipx.ts") : resolvePackageFile("modules/images/ipx.ts", "modules/images/ipx.mjs", "modules/images/ipx.js");
64
+ const buildPluginPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/images/plugin.ts") : resolvePackageFile("modules/images/plugin.ts", "modules/images/plugin.mjs", "modules/images/plugin.js");
65
+ if (isSsg) {
66
+ rmSync(generatedManifestPath, { force: true });
67
+ context.nitro.plugins.push(buildPluginPath);
68
+ }
69
+ context.nitro.handlers.push({
70
+ route: "/_ipx/**",
71
+ handler: ipxHandlerPath
72
+ });
73
+ context.nitro.runtimeConfig.flow = {
74
+ ...typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {},
75
+ images: {
76
+ dirRenames: renameDir,
77
+ dirFiles: options.dirFiles,
78
+ publicDir,
79
+ outputDir: resolve(context.projectRoot, ".output/public"),
80
+ generatedManifestPath,
81
+ generatedCacheDir,
82
+ generatedCacheManifestPath,
83
+ generate: isSsg,
84
+ netlifyCache: isNetlify,
85
+ options: {
86
+ lazy: options.lazy,
87
+ screens: options.screens,
88
+ domains: resolvedDomains,
89
+ presets: options.presets,
90
+ baseURL: options.baseURL,
91
+ dirImages: options.dirImages
92
+ }
93
+ }
94
+ };
95
+ }
96
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import process from "node:process";
2
+ import { defineNitroPlugin } from "nitro/runtime";
3
+ import { materializeGeneratedImages } from "./runtime/build.mjs";
4
+ import { resetFlowImageRuntimeState } from "./runtime/server.mjs";
5
+ export default defineNitroPlugin((nitro) => {
6
+ nitro.hooks.hook("prerender:done", async () => {
7
+ const imagesConfig = nitro.options.runtimeConfig.flow?.images;
8
+ if (!imagesConfig?.generate) {
9
+ return;
10
+ }
11
+ const result = await materializeGeneratedImages(imagesConfig);
12
+ if (result.total > 0) {
13
+ console.log(
14
+ `[flow:images] materialized ${result.total} images (${result.generated} generated, ${result.cacheHits} cache hits${imagesConfig.netlifyCache && process.env.NETLIFY ? ", netlify cache enabled" : ""})`
15
+ );
16
+ }
17
+ resetFlowImageRuntimeState();
18
+ });
19
+ });
@@ -0,0 +1,6 @@
1
+ import type { FlowImagesRuntimeConfig } from './types.ts';
2
+ export declare function materializeGeneratedImages(config: FlowImagesRuntimeConfig): Promise<{
3
+ cacheHits: number;
4
+ generated: number;
5
+ total: number;
6
+ }>;
@@ -0,0 +1,142 @@
1
+ import { createHash } from "node:crypto";
2
+ import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { createIPX, ipxFSStorage, ipxHttpStorage } from "ipx";
6
+ function sortRecord(value) {
7
+ return Object.keys(value).sort().reduce((result, key) => {
8
+ result[key] = value[key];
9
+ return result;
10
+ }, {});
11
+ }
12
+ function hashRecord(value) {
13
+ return createHash("sha1").update(JSON.stringify(sortRecord(value))).digest("hex");
14
+ }
15
+ function normalizeOutputPath(path) {
16
+ return path.startsWith("/") ? path.slice(1) : path;
17
+ }
18
+ function outputFilePath(baseDir, path) {
19
+ return resolve(baseDir, normalizeOutputPath(path));
20
+ }
21
+ async function ensureParentDir(path) {
22
+ await mkdir(dirname(path), { recursive: true });
23
+ }
24
+ async function readJson(path, fallback) {
25
+ try {
26
+ return JSON.parse(await readFile(path, "utf8"));
27
+ } catch {
28
+ return fallback;
29
+ }
30
+ }
31
+ async function readGeneratedManifest(path) {
32
+ if (!path || !existsSync(path)) {
33
+ return [];
34
+ }
35
+ const raw = await readFile(path, "utf8");
36
+ const collection = /* @__PURE__ */ new Map();
37
+ for (const line of raw.split(/\r?\n/)) {
38
+ if (!line.trim()) {
39
+ continue;
40
+ }
41
+ const parsed = JSON.parse(line);
42
+ collection.set(parsed.url, parsed);
43
+ }
44
+ return [...collection.values()];
45
+ }
46
+ async function createSignature(entry, publicDir) {
47
+ if (entry.src.startsWith("http://") || entry.src.startsWith("https://")) {
48
+ return hashRecord({
49
+ generate: entry.generate,
50
+ modifiers: entry.modifiers,
51
+ src: entry.src
52
+ });
53
+ }
54
+ const sourcePath = resolve(publicDir, entry.src.replace(/^\//, ""));
55
+ const sourceStat = await stat(sourcePath);
56
+ return hashRecord({
57
+ generate: entry.generate,
58
+ modifiers: entry.modifiers,
59
+ mtimeMs: sourceStat.mtimeMs,
60
+ size: sourceStat.size,
61
+ src: entry.src
62
+ });
63
+ }
64
+ async function walkFiles(rootDir, currentDir = rootDir, files = []) {
65
+ if (!existsSync(currentDir)) {
66
+ return files;
67
+ }
68
+ for (const entry of await readdir(currentDir, { withFileTypes: true })) {
69
+ const entryPath = join(currentDir, entry.name);
70
+ if (entry.isDirectory()) {
71
+ await walkFiles(rootDir, entryPath, files);
72
+ continue;
73
+ }
74
+ if (entry.isFile()) {
75
+ files.push(entryPath);
76
+ }
77
+ }
78
+ return files;
79
+ }
80
+ async function pruneCache(cacheDir, activeFiles) {
81
+ for (const filePath of await walkFiles(cacheDir)) {
82
+ const relativePath = normalizeOutputPath(filePath.replace(`${cacheDir}/`, "").replace(`${cacheDir}\\`, ""));
83
+ if (!activeFiles.has(relativePath)) {
84
+ await rm(filePath, { force: true });
85
+ }
86
+ }
87
+ }
88
+ export async function materializeGeneratedImages(config) {
89
+ if (!config.generate) {
90
+ return { cacheHits: 0, generated: 0, total: 0 };
91
+ }
92
+ const images = await readGeneratedManifest(config.generatedManifestPath);
93
+ if (!images.length) {
94
+ return { cacheHits: 0, generated: 0, total: 0 };
95
+ }
96
+ const domains = Object.keys(config.options.domains || {});
97
+ const ipx = createIPX({
98
+ storage: ipxFSStorage({ dir: config.publicDir }),
99
+ ...domains.length ? { httpStorage: ipxHttpStorage({ domains }) } : {}
100
+ });
101
+ const previousManifest = config.netlifyCache && config.generatedCacheManifestPath ? await readJson(config.generatedCacheManifestPath, {}) : {};
102
+ const nextManifest = {};
103
+ const activeCacheFiles = /* @__PURE__ */ new Set();
104
+ let cacheHits = 0;
105
+ let generated = 0;
106
+ for (const image of images) {
107
+ const signature = await createSignature(image, config.publicDir);
108
+ nextManifest[image.url] = {
109
+ ...image,
110
+ signature
111
+ };
112
+ const outputPath = outputFilePath(config.outputDir, image.generate);
113
+ const cacheRelativePath = normalizeOutputPath(image.generate);
114
+ const cachePath = config.generatedCacheDir ? outputFilePath(config.generatedCacheDir, cacheRelativePath) : void 0;
115
+ activeCacheFiles.add(cacheRelativePath);
116
+ await ensureParentDir(outputPath);
117
+ if (config.netlifyCache && cachePath && previousManifest[image.url]?.signature === signature && existsSync(cachePath)) {
118
+ await copyFile(cachePath, outputPath);
119
+ cacheHits += 1;
120
+ continue;
121
+ }
122
+ const processed = await ipx(image.src, image.modifiers).process();
123
+ const data = typeof processed.data === "string" ? Buffer.from(processed.data) : processed.data;
124
+ await writeFile(outputPath, data);
125
+ if (config.netlifyCache && cachePath) {
126
+ await ensureParentDir(cachePath);
127
+ await writeFile(cachePath, data);
128
+ }
129
+ generated += 1;
130
+ }
131
+ if (config.netlifyCache && config.generatedCacheDir && config.generatedCacheManifestPath) {
132
+ await mkdir(config.generatedCacheDir, { recursive: true });
133
+ await pruneCache(config.generatedCacheDir, activeCacheFiles);
134
+ await writeFile(config.generatedCacheManifestPath, `${JSON.stringify(nextManifest, null, 2)}
135
+ `, "utf8");
136
+ }
137
+ return {
138
+ cacheHits,
139
+ generated,
140
+ total: images.length
141
+ };
142
+ }
@@ -0,0 +1,16 @@
1
+ import type { FlowImageOptions, ImageOptions } from './types.ts';
2
+ export declare const screens: {
3
+ xs: number;
4
+ sm: number;
5
+ md: number;
6
+ lg: number;
7
+ xl: number;
8
+ xxl: number;
9
+ };
10
+ export declare function parseSize(input?: string | number | undefined): number | undefined;
11
+ export declare function getNormalName(originalName: string): string;
12
+ export declare function getFileExtension(url?: string): string;
13
+ export declare function guessExt(input?: string): string;
14
+ export declare function getPreset(ctx: {
15
+ options: FlowImageOptions;
16
+ }, name?: string): ImageOptions;
@@ -0,0 +1,45 @@
1
+ function extname(value) {
2
+ const fileName = value.split("/").pop() || value;
3
+ const index = fileName.lastIndexOf(".");
4
+ return index > 0 ? fileName.slice(index) : "";
5
+ }
6
+ function basename(value) {
7
+ return value.split("/").pop() || value;
8
+ }
9
+ export const screens = {
10
+ xs: 320,
11
+ sm: 640,
12
+ md: 768,
13
+ lg: 1024,
14
+ xl: 1280,
15
+ xxl: 1536
16
+ };
17
+ export function parseSize(input = "") {
18
+ if (typeof input === "number") {
19
+ return input;
20
+ }
21
+ if (typeof input === "string" && input.replace("px", "").match(/^\d+$/g)) {
22
+ return parseInt(input, 10);
23
+ }
24
+ return void 0;
25
+ }
26
+ export function getNormalName(originalName) {
27
+ const name = basename(originalName).replace(extname(originalName), "");
28
+ return name.replace(/-/g, " ");
29
+ }
30
+ export function getFileExtension(url = "") {
31
+ return url.split(/[?#]/).shift().split("/").pop().split(".").pop();
32
+ }
33
+ export function guessExt(input = "") {
34
+ const ext = input.split(".").pop()?.split("?")[0];
35
+ if (ext && /^[\w0-9]+$/.test(ext)) {
36
+ return `.${ext}`;
37
+ }
38
+ return "";
39
+ }
40
+ export function getPreset(ctx, name) {
41
+ if (!name) {
42
+ return {};
43
+ }
44
+ return ctx.options?.presets?.[name] || {};
45
+ }
@@ -0,0 +1,7 @@
1
+ import type { FlowImagesState, FlowImageOptions, GeneratedImageEntry, GetImageFunction } from './types.ts';
2
+ export declare function createImageResolver(imagesOptions: FlowImageOptions, stateImages: FlowImagesState, runtime?: {
3
+ generateOutput?: boolean;
4
+ onGenerate?: (image: GeneratedImageEntry) => void;
5
+ }): {
6
+ getImage: GetImageFunction;
7
+ };