@monkeyplus/flow 6.0.16 → 6.0.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/README.md +2 -0
- package/cms/server/database/schema.d.ts +648 -0
- package/cms/server/database/schema.mjs +43 -0
- package/cms/server/routes/api/auth/[...auth].d.ts +2 -0
- package/cms/server/routes/api/auth/[...auth].mjs +5 -0
- package/cms/server/routes/api/draft.d.ts +3 -0
- package/cms/server/routes/api/draft.mjs +26 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/[file]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/[file]/index.mjs +20 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/blobs/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/branches/[branch]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/branches/[branch]/index.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/commits/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/commits/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/[branch]/index.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/[branch]/index.mjs +38 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/index.post.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/files/index.post.mjs +8 -0
- package/cms/server/routes/api/repos/remote/[repo]/refs/heads/[branch]/index.patch.d.ts +2 -0
- package/cms/server/routes/api/repos/remote/[repo]/refs/heads/[branch]/index.patch.mjs +9 -0
- package/cms/server/utils/auth.d.ts +3 -0
- package/cms/server/utils/auth.mjs +16 -0
- package/cms/server/utils/db.d.ts +3 -0
- package/cms/server/utils/db.mjs +5 -0
- package/cms/server/utils/github.d.ts +15 -0
- package/cms/server/utils/github.mjs +160 -0
- package/cms/server/utils/github_token.d.ts +71 -0
- package/cms/server/utils/github_token.mjs +377 -0
- package/modules/cms/module.d.ts +11 -0
- package/modules/cms/module.mjs +163 -0
- package/modules/cms/server/api/admin.d.ts +2 -0
- package/modules/cms/server/api/admin.mjs +18 -0
- package/modules/cms/server/api/config.d.ts +2 -0
- package/modules/cms/server/api/config.mjs +88 -0
- package/modules/cms/server/api/localFs.d.ts +2 -0
- package/modules/cms/server/api/localFs.mjs +88 -0
- package/modules/cms/server/api/meta.d.ts +2 -0
- package/modules/cms/server/api/meta.mjs +12 -0
- package/modules/cms/server/lib/composables.d.ts +116 -0
- package/modules/cms/server/lib/composables.mjs +82 -0
- package/modules/cms/server/lib/fs.d.ts +1 -0
- package/modules/cms/server/lib/fs.mjs +18 -0
- package/modules/cms/server/lib/helpers.d.ts +14 -0
- package/modules/cms/server/lib/helpers.mjs +78 -0
- package/modules/cms/server/lib/types.d.ts +120 -0
- package/modules/cms/server/lib/types.mjs +0 -0
- package/modules/cms/server/lib/widgets.d.ts +82 -0
- package/modules/cms/server/lib/widgets.mjs +200 -0
- package/modules/cms/server/trial.d.ts +2 -0
- package/modules/cms/server/trial.mjs +8 -0
- package/modules/content/query.mjs +31 -3
- package/modules/images/ipx.mjs +4 -2
- package/modules/images/module.d.ts +0 -1
- package/modules/images/module.mjs +5 -3
- package/modules/images/runtime/build.mjs +12 -0
- package/modules/images/runtime/image.mjs +4 -3
- package/modules/images/runtime/renames.mjs +59 -8
- package/modules/images/runtime/types.d.ts +27 -4
- package/modules/images/watermark.d.ts +1 -0
- package/modules/images/watermark.mjs +113 -0
- package/modules/netlify-cms/handler.mjs +2 -1
- package/modules/netlify-cms/module.mjs +1 -1
- package/modules/netlify-cms/server/api/config.mjs +25 -1
- package/modules/netlify-cms/server/api/local-fs.d.ts +51 -0
- package/modules/netlify-cms/server/api/local-fs.mjs +81 -77
- package/modules/netlify-cms/server/lib/cms/handler.d.ts +1 -1
- package/modules/netlify-cms/server/lib/cms/handlerV1.d.ts +1 -1
- package/modules/netlify-cms/server/lib/composables.d.ts +8 -0
- package/modules/netlify-cms/server/lib/composables.mjs +2 -1
- package/package.json +2 -2
- package/server/lib/context.d.ts +3 -0
- package/server/lib/context.mjs +5 -0
- package/server/lib/handler.mjs +58 -23
- package/server/lib/pages.d.ts +2 -2
- package/server/lib/pages.mjs +8 -6
- package/server/lib/render.mjs +20 -4
- package/server/plugins/00.lifecycle.mjs +2 -1
- package/src/public/index.d.ts +1 -0
- package/src/public/index.mjs +1 -0
- package/src/public/nitro.mjs +2 -0
- package/src/public/query-content.mjs +9 -2
- package/src/public/shared.d.ts +1 -0
- package/src/public/shared.mjs +3 -0
- package/src/public/vite.mjs +63 -8
- package/src/runtime/components/FlowIsland.mjs +32 -5
- package/src/runtime/components/MkImage.d.ts +100 -22
- package/src/runtime/components/MkImage.mjs +20 -12
- package/src/runtime/components/MkLink.d.ts +8 -5
- package/src/runtime/components/MkLink.mjs +9 -3
- package/src/runtime/components/MkPicture.d.ts +92 -7
- package/src/runtime/components/MkPicture.mjs +8 -2
- package/src/runtime/components/image-shared.d.ts +0 -1
- package/src/runtime/components/image-shared.mjs +9 -18
- package/src/runtime/config.d.ts +6 -15
- package/src/runtime/head.d.ts +2 -1
- package/src/runtime/head.mjs +5 -2
- package/src/runtime/islands.mjs +20 -2
- package/src/runtime/page-discovery.mjs +9 -1
- package/src/runtime/pages.d.ts +14 -13
- package/src/runtime/virtual-pages.mjs +2 -2
- package/src/runtime/vue.mjs +10 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { mapObjIndexed } from "./helpers.mjs";
|
|
3
|
+
function returnWidget(widget, options = {}) {
|
|
4
|
+
return (name, _ctx) => ({
|
|
5
|
+
name,
|
|
6
|
+
...widget,
|
|
7
|
+
label: name,
|
|
8
|
+
...options
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function boolean(options) {
|
|
12
|
+
return returnWidget(
|
|
13
|
+
{ widget: "boolean" },
|
|
14
|
+
options
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
export function editor(options) {
|
|
18
|
+
return returnWidget(
|
|
19
|
+
{ widget: "editor" },
|
|
20
|
+
options
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
export function color(options) {
|
|
24
|
+
return returnWidget(
|
|
25
|
+
{ widget: "color" },
|
|
26
|
+
options
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
export function dateTime(options) {
|
|
30
|
+
return returnWidget(
|
|
31
|
+
{ widget: "date" },
|
|
32
|
+
options
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
export function list(options) {
|
|
36
|
+
return (id, ctx) => {
|
|
37
|
+
return object(
|
|
38
|
+
{ ...options, widget: "list", fields: options.fields }
|
|
39
|
+
)(id, ctx);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function number(options) {
|
|
43
|
+
return returnWidget(
|
|
44
|
+
{ widget: "number" },
|
|
45
|
+
options
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
export function object(options) {
|
|
49
|
+
return (id, ctx) => {
|
|
50
|
+
const _fields = mapObjIndexed((buildField, id2) => {
|
|
51
|
+
return buildField(id2, ctx);
|
|
52
|
+
}, options.fields);
|
|
53
|
+
return {
|
|
54
|
+
name: id,
|
|
55
|
+
widget: "object",
|
|
56
|
+
...options,
|
|
57
|
+
fields: Object.values(_fields)
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function select(options) {
|
|
62
|
+
return returnWidget(
|
|
63
|
+
{ widget: "select" },
|
|
64
|
+
options
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
export function text(options) {
|
|
68
|
+
return returnWidget({ widget: "text" }, options);
|
|
69
|
+
}
|
|
70
|
+
export function string(options) {
|
|
71
|
+
return returnWidget(
|
|
72
|
+
{ widget: "string" },
|
|
73
|
+
options
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
export function file(options) {
|
|
77
|
+
return returnWidget({ widget: "file" }, options);
|
|
78
|
+
}
|
|
79
|
+
export function image(options) {
|
|
80
|
+
return (name, ctx) => {
|
|
81
|
+
if (options?.mediaFolder)
|
|
82
|
+
options.mediaFolder = join("", options.mediaFolder);
|
|
83
|
+
return returnWidget(
|
|
84
|
+
{ widget: "image" },
|
|
85
|
+
options
|
|
86
|
+
)(name, ctx);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function imageObject(options) {
|
|
90
|
+
return (name, ctx) => {
|
|
91
|
+
if (options?.mediaFolder)
|
|
92
|
+
options.mediaFolder = join("", options.mediaFolder);
|
|
93
|
+
return {
|
|
94
|
+
name,
|
|
95
|
+
widget: "object",
|
|
96
|
+
label: options?.label,
|
|
97
|
+
collapsed: false,
|
|
98
|
+
fields: [
|
|
99
|
+
returnWidget(
|
|
100
|
+
{ widget: "image" },
|
|
101
|
+
options
|
|
102
|
+
)("src", ctx),
|
|
103
|
+
returnWidget(
|
|
104
|
+
{ widget: "string" },
|
|
105
|
+
{
|
|
106
|
+
required: false
|
|
107
|
+
}
|
|
108
|
+
)("alt", {
|
|
109
|
+
...ctx
|
|
110
|
+
}),
|
|
111
|
+
returnWidget(
|
|
112
|
+
{ widget: "string" },
|
|
113
|
+
{
|
|
114
|
+
required: false
|
|
115
|
+
}
|
|
116
|
+
)("title", ctx),
|
|
117
|
+
returnWidget(
|
|
118
|
+
{ widget: "string" },
|
|
119
|
+
{
|
|
120
|
+
required: false
|
|
121
|
+
}
|
|
122
|
+
)("rename", ctx)
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function link(options) {
|
|
128
|
+
return (name, ctx) => {
|
|
129
|
+
return {
|
|
130
|
+
name,
|
|
131
|
+
widget: "object",
|
|
132
|
+
label: options?.label,
|
|
133
|
+
...options,
|
|
134
|
+
fields: [
|
|
135
|
+
// Label
|
|
136
|
+
returnWidget(
|
|
137
|
+
{ widget: "string" },
|
|
138
|
+
{ label: "Texto" }
|
|
139
|
+
)("label", ctx),
|
|
140
|
+
// Link
|
|
141
|
+
returnWidget(
|
|
142
|
+
{ widget: "select" },
|
|
143
|
+
{
|
|
144
|
+
required: false,
|
|
145
|
+
items: ["Interna", "Externa"]
|
|
146
|
+
}
|
|
147
|
+
)("type", {
|
|
148
|
+
...ctx
|
|
149
|
+
}),
|
|
150
|
+
// Link
|
|
151
|
+
returnWidget(
|
|
152
|
+
{ widget: "string" },
|
|
153
|
+
{
|
|
154
|
+
required: false,
|
|
155
|
+
label: "Link",
|
|
156
|
+
options: {
|
|
157
|
+
show: {
|
|
158
|
+
type: "Externa"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
)("link", {
|
|
163
|
+
...ctx
|
|
164
|
+
}),
|
|
165
|
+
// Link 2
|
|
166
|
+
returnWidget(
|
|
167
|
+
{ widget: "select" },
|
|
168
|
+
{
|
|
169
|
+
label: "P\xE1gina",
|
|
170
|
+
required: false,
|
|
171
|
+
items: ["uno", "dos"],
|
|
172
|
+
options: {
|
|
173
|
+
alias: "link",
|
|
174
|
+
list: "pages",
|
|
175
|
+
show: {
|
|
176
|
+
type: "Interna"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
)("link2", ctx)
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export const widgets = {
|
|
186
|
+
string,
|
|
187
|
+
color,
|
|
188
|
+
number,
|
|
189
|
+
file,
|
|
190
|
+
dateTime,
|
|
191
|
+
image,
|
|
192
|
+
editor,
|
|
193
|
+
object,
|
|
194
|
+
boolean,
|
|
195
|
+
list,
|
|
196
|
+
text,
|
|
197
|
+
select,
|
|
198
|
+
imageObject,
|
|
199
|
+
link
|
|
200
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extname } from "node:path";
|
|
2
|
+
import { consola } from "consola";
|
|
2
3
|
import { defineEventHandler, getQuery, getRequestURL } from "nitro/h3";
|
|
3
4
|
import { useStorage } from "nitro/storage";
|
|
4
5
|
function normalizeQueryPath(path) {
|
|
@@ -85,6 +86,31 @@ function parseContentFile(keyPath, raw) {
|
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
export async function readContentEntries() {
|
|
89
|
+
const applyCmsPreview = (entries2) => {
|
|
90
|
+
try {
|
|
91
|
+
const previewData = globalThis.__cmsPreviewData;
|
|
92
|
+
if (previewData) {
|
|
93
|
+
for (const [rawKey, payload] of Object.entries(previewData)) {
|
|
94
|
+
if (!payload || typeof payload !== "object")
|
|
95
|
+
continue;
|
|
96
|
+
let targetPath = rawKey;
|
|
97
|
+
if (targetPath.startsWith("/content/")) {
|
|
98
|
+
targetPath = targetPath.slice(8);
|
|
99
|
+
}
|
|
100
|
+
targetPath = targetPath.replace(/\.(json|md|yaml|yml|txt)$/i, "");
|
|
101
|
+
const entry = entries2.find((e) => e.path === targetPath);
|
|
102
|
+
if (entry) {
|
|
103
|
+
entry.data = { ...entry.data, ...payload };
|
|
104
|
+
if ("title" in payload) {
|
|
105
|
+
entry.title = payload.title;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
}
|
|
112
|
+
return entries2;
|
|
113
|
+
};
|
|
88
114
|
const storage = useStorage("content");
|
|
89
115
|
const keys = await storage.getKeys();
|
|
90
116
|
const entries = [];
|
|
@@ -116,9 +142,10 @@ export async function readContentEntries() {
|
|
|
116
142
|
await walkDir(contentDir, contentDir);
|
|
117
143
|
} catch (e) {
|
|
118
144
|
}
|
|
119
|
-
|
|
145
|
+
const sorted = entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
146
|
+
return applyCmsPreview(sorted);
|
|
120
147
|
} catch (e) {
|
|
121
|
-
|
|
148
|
+
consola.error("[Flow Content] fs fallback failed:", e);
|
|
122
149
|
}
|
|
123
150
|
}
|
|
124
151
|
for (const key of keys) {
|
|
@@ -133,7 +160,8 @@ export async function readContentEntries() {
|
|
|
133
160
|
entries.push(entry);
|
|
134
161
|
}
|
|
135
162
|
}
|
|
136
|
-
|
|
163
|
+
const sortedEntries = entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
164
|
+
return applyCmsPreview(sortedEntries);
|
|
137
165
|
}
|
|
138
166
|
function sortTree(nodes) {
|
|
139
167
|
nodes.sort((left, right) => {
|
package/modules/images/ipx.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
+
import { consola } from "consola";
|
|
2
3
|
import { createIPX, createIPXH3Handler, ipxFSStorage, ipxHttpStorage } from "ipx";
|
|
3
4
|
import { defineHandler } from "nitro";
|
|
4
5
|
import { getRequestURL } from "nitro/h3";
|
|
5
6
|
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
7
|
+
import { withWatermark } from "./watermark.mjs";
|
|
6
8
|
let cachedDir = "";
|
|
7
9
|
let cachedDomainsKey = "";
|
|
8
10
|
let cachedHandler;
|
|
@@ -36,13 +38,13 @@ function resolveHandler() {
|
|
|
36
38
|
httpStorage: ipxHttpStorage({ domains })
|
|
37
39
|
} : {}
|
|
38
40
|
});
|
|
39
|
-
cachedHandler = createIPXH3Handler(ipx);
|
|
41
|
+
cachedHandler = createIPXH3Handler(withWatermark(ipx));
|
|
40
42
|
}
|
|
41
43
|
return cachedHandler;
|
|
42
44
|
}
|
|
43
45
|
export default defineHandler(async (event) => {
|
|
44
46
|
if (!getRequestURL(event)?.pathname.startsWith("/_ipx")) {
|
|
45
|
-
|
|
47
|
+
consola.log("route", event.req.url.toString());
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
const handler = resolveHandler();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { FlowImagesModuleOptions } from './runtime/types.ts';
|
|
2
|
-
export type { FlowImagesModuleOptions as ImagesModuleOptions } from './runtime/types.ts';
|
|
3
2
|
declare const _default: import("../../src/runtime/config.ts").FlowModuleDefinition<FlowImagesModuleOptions>;
|
|
4
3
|
export default _default;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { rmSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import { consola } from "consola";
|
|
4
5
|
import { createIPX, createIPXNodeServer, ipxFSStorage, ipxHttpStorage } from "ipx";
|
|
5
6
|
import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.mjs";
|
|
6
7
|
import { defineFlowModule } from "../../src/runtime/config.mjs";
|
|
7
8
|
import { materializeGeneratedImages } from "./runtime/build.mjs";
|
|
8
9
|
import { screens } from "./runtime/helpers.mjs";
|
|
9
10
|
import { resetFlowImageRuntimeState } from "./runtime/server.mjs";
|
|
11
|
+
import { withWatermark } from "./watermark.mjs";
|
|
10
12
|
function withoutTrailingSlash(value) {
|
|
11
13
|
return value.replace(/\/+$/, "");
|
|
12
14
|
}
|
|
@@ -51,7 +53,7 @@ function createIpxDevServerPlugin(imagesRuntimeConfig) {
|
|
|
51
53
|
storage: ipxFSStorage({ dir: imagesRuntimeConfig.publicDir }),
|
|
52
54
|
...domains.length ? { httpStorage: ipxHttpStorage({ domains }) } : {}
|
|
53
55
|
});
|
|
54
|
-
middleware = createIPXNodeServer(ipx);
|
|
56
|
+
middleware = createIPXNodeServer(withWatermark(ipx));
|
|
55
57
|
}
|
|
56
58
|
server.middlewares.use("/_ipx", (req, res, next) => {
|
|
57
59
|
try {
|
|
@@ -78,7 +80,7 @@ export default defineFlowModule({
|
|
|
78
80
|
lazy: true,
|
|
79
81
|
screens,
|
|
80
82
|
baseURL: "/_ipx",
|
|
81
|
-
dirImages: "/images",
|
|
83
|
+
dirImages: "/assets/images",
|
|
82
84
|
domains: {},
|
|
83
85
|
presets: {}
|
|
84
86
|
},
|
|
@@ -129,7 +131,7 @@ export default defineFlowModule({
|
|
|
129
131
|
}
|
|
130
132
|
const result = await materializeGeneratedImages(imagesRuntimeConfig);
|
|
131
133
|
if (result.total > 0) {
|
|
132
|
-
|
|
134
|
+
consola.log(
|
|
133
135
|
`[flow:images] materialized ${result.total} images (${result.generated} generated, ${result.cacheHits} cache hits${imagesRuntimeConfig.netlifyCache && process.env.NETLIFY ? ", netlify cache enabled" : ""})`
|
|
134
136
|
);
|
|
135
137
|
}
|
|
@@ -129,6 +129,12 @@ export async function materializeGeneratedImages(config) {
|
|
|
129
129
|
let cacheHits = 0;
|
|
130
130
|
let generated = 0;
|
|
131
131
|
const batchSize = resolveConfiguredBatchSize(config.buildBatchSize);
|
|
132
|
+
const originalSourcesToDelete = /* @__PURE__ */ new Set();
|
|
133
|
+
for (const image of images) {
|
|
134
|
+
if (image.isRenamed) {
|
|
135
|
+
originalSourcesToDelete.add(image.src);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
132
138
|
for (const batch of createBatches(images, batchSize)) {
|
|
133
139
|
const results = await Promise.all(batch.map(async (image) => {
|
|
134
140
|
const signature = await createSignature(image, config.publicDir);
|
|
@@ -166,6 +172,12 @@ export async function materializeGeneratedImages(config) {
|
|
|
166
172
|
await writeFile(config.generatedCacheManifestPath, `${JSON.stringify(nextManifest, null, 2)}
|
|
167
173
|
`, "utf8");
|
|
168
174
|
}
|
|
175
|
+
for (const src of originalSourcesToDelete) {
|
|
176
|
+
const originalPath = outputFilePath(config.outputDir, src);
|
|
177
|
+
if (existsSync(originalPath)) {
|
|
178
|
+
await rm(originalPath, { force: true });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
169
181
|
return {
|
|
170
182
|
cacheHits,
|
|
171
183
|
generated,
|
|
@@ -158,7 +158,7 @@ export function createImageResolver(imagesOptions, stateImages, runtime = {}) {
|
|
|
158
158
|
image.ext = image.format && `.${image.format}` || guessExt(logicalInput);
|
|
159
159
|
let baseDir = isStrapi ? normalizeDirImages(imagesOptions.dirImages) : getBaseDir(sourceInput, imagesOptions.dirImages);
|
|
160
160
|
const originalExt = guessExt(logicalInput);
|
|
161
|
-
let renamedImage = stateImages.all[logicalInput]?.rename;
|
|
161
|
+
let renamedImage = stateImages.all[sourceInput]?.rename || stateImages.all[logicalInput]?.rename;
|
|
162
162
|
if (!renamedImage && imagesOptions.domains) {
|
|
163
163
|
renamedImage = resolveRemoteRename(sourceInput, imagesOptions.domains);
|
|
164
164
|
}
|
|
@@ -181,12 +181,13 @@ export function createImageResolver(imagesOptions, stateImages, runtime = {}) {
|
|
|
181
181
|
...image,
|
|
182
182
|
src: image.src,
|
|
183
183
|
generate: image.generate,
|
|
184
|
-
modifiers: image.modifiers
|
|
184
|
+
modifiers: image.modifiers,
|
|
185
|
+
isRenamed: !!resolvedOptions.rename || !!renamedImage
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
const src = runtime.generateOutput && image.generate ? image.generate : image.url;
|
|
188
189
|
if (resolvedOptions._meta) {
|
|
189
|
-
const meta = { ...stateImages.all[logicalInput] || { name: logicalInput, alt: void 0, title: void 0 } };
|
|
190
|
+
const meta = { ...stateImages.all[sourceInput] || stateImages.all[logicalInput] || { name: logicalInput, alt: void 0, title: void 0 } };
|
|
190
191
|
if (!meta.alt) {
|
|
191
192
|
meta.alt = getNormalName(meta.name);
|
|
192
193
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { joinURL } from "ufo";
|
|
4
|
+
import { consola } from "consola";
|
|
4
5
|
const RENAME_FILE = "_rename";
|
|
5
6
|
function normalizePublicPath(path) {
|
|
6
7
|
return `/${path}`.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
@@ -28,27 +29,57 @@ function toPublicImagePath(rootDir, manifestDir, fileName) {
|
|
|
28
29
|
const relativeDir = relative(rootDir, manifestDir).split(sep).join("/");
|
|
29
30
|
return normalizePublicPath(joinURL(relativeDir, fileName));
|
|
30
31
|
}
|
|
32
|
+
function formatKebabCase(str) {
|
|
33
|
+
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-]/g, "").replace(/-+/g, "-");
|
|
34
|
+
}
|
|
35
|
+
function findOriginalExtension(manifestDir, baseName) {
|
|
36
|
+
try {
|
|
37
|
+
for (const entry of readdirSync(manifestDir, { withFileTypes: true })) {
|
|
38
|
+
if (entry.isFile() && entry.name !== RENAME_FILE) {
|
|
39
|
+
const ext = extname(entry.name);
|
|
40
|
+
const nameWithoutExt = basename(entry.name, ext);
|
|
41
|
+
if (nameWithoutExt === baseName) {
|
|
42
|
+
return ext;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
31
50
|
function parseRenameLine(rootDir, filePath, line) {
|
|
32
51
|
const trimmed = line.trim();
|
|
33
52
|
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) {
|
|
34
53
|
return void 0;
|
|
35
54
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
55
|
+
const parts = trimmed.split("|").map((s) => s.trim());
|
|
56
|
+
let original = parts[0];
|
|
57
|
+
const rawRenamed = parts[1];
|
|
58
|
+
const title = parts[2];
|
|
59
|
+
const alt = parts[3];
|
|
60
|
+
if (!original || !rawRenamed || isHeaderLine(original, rawRenamed)) {
|
|
38
61
|
return void 0;
|
|
39
62
|
}
|
|
40
|
-
const sourceExt = extname(original);
|
|
41
|
-
const renamedWithExt = extname(renamed) ? renamed : `${renamed}${sourceExt}`;
|
|
42
63
|
const manifestDir = dirname(filePath);
|
|
64
|
+
let sourceExt = extname(original);
|
|
65
|
+
if (!sourceExt) {
|
|
66
|
+
sourceExt = findOriginalExtension(manifestDir, original);
|
|
67
|
+
if (sourceExt) {
|
|
68
|
+
original = `${original}${sourceExt}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const rawRenamedExt = extname(rawRenamed);
|
|
72
|
+
const renamedBase = formatKebabCase(rawRenamedExt ? basename(rawRenamed, rawRenamedExt) : rawRenamed);
|
|
73
|
+
const renamedWithExt = rawRenamedExt ? `${renamedBase}${rawRenamedExt}` : `${renamedBase}${sourceExt}`;
|
|
43
74
|
const sourcePath = toPublicImagePath(rootDir, manifestDir, original);
|
|
44
75
|
const renamedPath = toPublicImagePath(rootDir, manifestDir, renamedWithExt);
|
|
45
76
|
return {
|
|
46
77
|
key: sourcePath,
|
|
47
78
|
value: {
|
|
48
79
|
rename: renamedPath,
|
|
49
|
-
name:
|
|
50
|
-
title,
|
|
51
|
-
alt:
|
|
80
|
+
name: renamedBase,
|
|
81
|
+
title: title || void 0,
|
|
82
|
+
alt: alt || void 0
|
|
52
83
|
}
|
|
53
84
|
};
|
|
54
85
|
}
|
|
@@ -67,7 +98,7 @@ function readRenameDirectory(rootDir) {
|
|
|
67
98
|
return entries;
|
|
68
99
|
}
|
|
69
100
|
export function loadImageRenames(sources) {
|
|
70
|
-
|
|
101
|
+
const allEntries = sources.reduce((collection, source) => {
|
|
71
102
|
if (!source || !existsSync(source)) {
|
|
72
103
|
return collection;
|
|
73
104
|
}
|
|
@@ -76,4 +107,24 @@ export function loadImageRenames(sources) {
|
|
|
76
107
|
...readRenameDirectory(source)
|
|
77
108
|
};
|
|
78
109
|
}, {});
|
|
110
|
+
const renamedToOriginal = {};
|
|
111
|
+
for (const [originalPath, meta] of Object.entries(allEntries)) {
|
|
112
|
+
const renamedPath = meta.rename;
|
|
113
|
+
if (!renamedToOriginal[renamedPath]) {
|
|
114
|
+
renamedToOriginal[renamedPath] = [];
|
|
115
|
+
}
|
|
116
|
+
renamedToOriginal[renamedPath].push(originalPath);
|
|
117
|
+
}
|
|
118
|
+
for (const [renamedPath, originalPaths] of Object.entries(renamedToOriginal)) {
|
|
119
|
+
if (originalPaths.length > 1) {
|
|
120
|
+
consola.warn(
|
|
121
|
+
`
|
|
122
|
+
[Flow Images] \u26A0\uFE0F ALERTA: M\xFAltiples im\xE1genes intentan renombrarse al mismo destino "${renamedPath}".
|
|
123
|
+
Im\xE1genes originales:
|
|
124
|
+
${originalPaths.map((p) => ` - ${p}`).join("\n")}
|
|
125
|
+
`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return allEntries;
|
|
79
130
|
}
|
|
@@ -16,11 +16,33 @@ export interface FlowImageMeta {
|
|
|
16
16
|
alt?: string;
|
|
17
17
|
title?: string;
|
|
18
18
|
}
|
|
19
|
+
export interface WatermarkOptions {
|
|
20
|
+
text?: string;
|
|
21
|
+
image?: string;
|
|
22
|
+
opacity?: number;
|
|
23
|
+
position?: string;
|
|
24
|
+
x?: number;
|
|
25
|
+
y?: number;
|
|
26
|
+
color?: string;
|
|
27
|
+
background?: string;
|
|
28
|
+
fontSize?: number;
|
|
29
|
+
fontWeight?: string;
|
|
30
|
+
ratio?: number;
|
|
31
|
+
}
|
|
19
32
|
export interface ImageModifiers {
|
|
20
|
-
width?: number;
|
|
21
|
-
height?: number;
|
|
22
|
-
fit?: string;
|
|
23
|
-
format?: string;
|
|
33
|
+
width?: number | string;
|
|
34
|
+
height?: number | string;
|
|
35
|
+
fit?: 'contain' | 'cover' | 'fill' | 'inside' | 'outside' | string;
|
|
36
|
+
format?: 'auto' | 'webp' | 'jpeg' | 'png' | 'avif' | 'gif' | string;
|
|
37
|
+
quality?: number | string;
|
|
38
|
+
background?: string;
|
|
39
|
+
blur?: number | string;
|
|
40
|
+
trim?: number | string;
|
|
41
|
+
position?: 'top' | 'right top' | 'right' | 'right bottom' | 'bottom' | 'left bottom' | 'left' | 'left top' | 'center' | string;
|
|
42
|
+
grayscale?: boolean | string;
|
|
43
|
+
rotate?: number | string;
|
|
44
|
+
size?: string;
|
|
45
|
+
watermark?: boolean | string | WatermarkOptions;
|
|
24
46
|
[key: string]: any;
|
|
25
47
|
}
|
|
26
48
|
export interface ImageOptions {
|
|
@@ -51,6 +73,7 @@ export interface GeneratedImageEntry extends ResolvedImage {
|
|
|
51
73
|
src: string;
|
|
52
74
|
generate: string;
|
|
53
75
|
modifiers: Record<string, string>;
|
|
76
|
+
isRenamed?: boolean;
|
|
54
77
|
}
|
|
55
78
|
export type GetImageFunction = ((input: string, modifiers?: Record<string, any>, options?: ImageOptions) => string | Record<string, unknown>) & {
|
|
56
79
|
getSizes: (input: string, options: ImageSizesOptions) => ImageSizes;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withWatermark(ipxInstance: any): (id: string, modifiers?: Record<string, string>, opts?: any) => any;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
const positionsMap = {
|
|
6
|
+
"top": "north",
|
|
7
|
+
"bottom": "south",
|
|
8
|
+
"center": "center",
|
|
9
|
+
"centre": "centre",
|
|
10
|
+
"left": "west",
|
|
11
|
+
"right": "east",
|
|
12
|
+
"top-left": "northwest",
|
|
13
|
+
"top-right": "northeast",
|
|
14
|
+
"bottom-left": "southwest",
|
|
15
|
+
"bottom-right": "southeast",
|
|
16
|
+
"southeast": "southeast",
|
|
17
|
+
"southwest": "southwest",
|
|
18
|
+
"northeast": "northeast",
|
|
19
|
+
"northwest": "northwest",
|
|
20
|
+
"north": "north",
|
|
21
|
+
"south": "south",
|
|
22
|
+
"east": "east",
|
|
23
|
+
"west": "west"
|
|
24
|
+
};
|
|
25
|
+
function getAlpha(opacity) {
|
|
26
|
+
if (opacity === void 0)
|
|
27
|
+
return "e6";
|
|
28
|
+
return Math.round(opacity * 255).toString(16).padStart(2, "0");
|
|
29
|
+
}
|
|
30
|
+
export function withWatermark(ipxInstance) {
|
|
31
|
+
return (id, modifiers = {}, opts = {}) => {
|
|
32
|
+
const hasWatermark = modifiers.watermark !== void 0;
|
|
33
|
+
const watermarkRaw = typeof modifiers.watermark === "string" && modifiers.watermark !== "true" && modifiers.watermark !== "" ? modifiers.watermark : "WATERMARK";
|
|
34
|
+
if (hasWatermark) {
|
|
35
|
+
delete modifiers.watermark;
|
|
36
|
+
}
|
|
37
|
+
const instance = ipxInstance(id, modifiers, opts);
|
|
38
|
+
const origProcess = instance.process;
|
|
39
|
+
instance.process = async () => {
|
|
40
|
+
const res = await origProcess();
|
|
41
|
+
if (hasWatermark && res.data && typeof res.data !== "string" && res.format !== "gif" && res.format !== "svg") {
|
|
42
|
+
try {
|
|
43
|
+
const sharp = (await import("sharp")).default;
|
|
44
|
+
let watermarkOptions = {
|
|
45
|
+
text: "WATERMARK",
|
|
46
|
+
opacity: 0.9,
|
|
47
|
+
position: "southeast",
|
|
48
|
+
color: "#ffffff",
|
|
49
|
+
background: "#00000080",
|
|
50
|
+
fontWeight: "bold"
|
|
51
|
+
};
|
|
52
|
+
if (modifiers.watermark !== "true") {
|
|
53
|
+
const decodedText = decodeURIComponent(watermarkRaw).replace(/_/g, " ");
|
|
54
|
+
if (decodedText.startsWith("b64")) {
|
|
55
|
+
try {
|
|
56
|
+
const jsonStr = Buffer.from(decodedText.slice(3), "base64").toString("utf-8");
|
|
57
|
+
const parsed = JSON.parse(jsonStr);
|
|
58
|
+
watermarkOptions = { ...watermarkOptions, ...parsed };
|
|
59
|
+
} catch (e) {
|
|
60
|
+
consola.error("[flow:images] Error parsing watermark b64:", e);
|
|
61
|
+
watermarkOptions.text = decodedText;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
watermarkOptions.text = decodedText;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
consola.log("[flow:images] Aplicando marca de agua din\xE1mica:", watermarkOptions);
|
|
68
|
+
const gravity = positionsMap[watermarkOptions.position || "southeast"] || "southeast";
|
|
69
|
+
if (watermarkOptions.image) {
|
|
70
|
+
const watermarkPath = resolve(process.cwd(), watermarkOptions.image);
|
|
71
|
+
let watermarkBuffer = await sharp(watermarkPath).toBuffer();
|
|
72
|
+
if (watermarkOptions.ratio) {
|
|
73
|
+
const mainMeta = await sharp(res.data).metadata();
|
|
74
|
+
const waterMarkWidth = Math.round((mainMeta.width || 1e3) * watermarkOptions.ratio);
|
|
75
|
+
watermarkBuffer = await sharp(watermarkBuffer).resize(waterMarkWidth).toBuffer();
|
|
76
|
+
}
|
|
77
|
+
res.data = await sharp(res.data).composite([{
|
|
78
|
+
input: watermarkBuffer,
|
|
79
|
+
gravity,
|
|
80
|
+
top: watermarkOptions.y,
|
|
81
|
+
left: watermarkOptions.x,
|
|
82
|
+
blend: "over"
|
|
83
|
+
}]).toBuffer();
|
|
84
|
+
} else {
|
|
85
|
+
const text = watermarkOptions.text || "WATERMARK";
|
|
86
|
+
const color = watermarkOptions.color || "#ffffff";
|
|
87
|
+
const weight = watermarkOptions.fontWeight || "bold";
|
|
88
|
+
const pangoMarkup = `<span foreground="${color}" font_weight="${weight}"> ${text} </span>`;
|
|
89
|
+
res.data = await sharp(res.data).composite([{
|
|
90
|
+
input: {
|
|
91
|
+
text: {
|
|
92
|
+
text: pangoMarkup,
|
|
93
|
+
rgba: true,
|
|
94
|
+
dpi: watermarkOptions.fontSize ? watermarkOptions.fontSize * 5 : 150,
|
|
95
|
+
// rough dpi estimation based on size
|
|
96
|
+
align: "center"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
gravity,
|
|
100
|
+
top: watermarkOptions.y,
|
|
101
|
+
left: watermarkOptions.x,
|
|
102
|
+
blend: "over"
|
|
103
|
+
}]).toBuffer();
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
consola.error("[flow:images] Error applying watermark:", err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
};
|
|
111
|
+
return instance;
|
|
112
|
+
};
|
|
113
|
+
}
|