@joycostudio/iris 0.0.0-stage → 0.0.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/LICENSE +15 -0
- package/README.md +137 -2
- package/dist/browser.cjs +466 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +69 -0
- package/dist/browser.d.ts +69 -0
- package/dist/browser.js +439 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.cjs +681 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +658 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.cjs +355 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +8 -0
- package/dist/core.d.ts +8 -0
- package/dist/core.js +328 -0
- package/dist/core.js.map +1 -0
- package/dist/next.cjs +380 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +16 -0
- package/dist/next.d.ts +16 -0
- package/dist/next.js +352 -0
- package/dist/next.js.map +1 -0
- package/dist/node.cjs +580 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +47 -0
- package/dist/node.d.ts +47 -0
- package/dist/node.js +542 -0
- package/dist/node.js.map +1 -0
- package/dist/policy-types-LoD2Ldcl.d.cts +147 -0
- package/dist/policy-types-LoD2Ldcl.d.ts +147 -0
- package/dist/react.cjs +628 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +42 -0
- package/dist/react.d.ts +42 -0
- package/dist/react.js +611 -0
- package/dist/react.js.map +1 -0
- package/dist/types-Dv3DS4OX.d.cts +17 -0
- package/dist/types-Dv3DS4OX.d.ts +17 -0
- package/package.json +102 -4
- package/skills/iris-audit/SKILL.md +77 -0
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"use strict";
|
|
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 __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// packages/node/index.ts
|
|
31
|
+
var node_exports = {};
|
|
32
|
+
__export(node_exports, {
|
|
33
|
+
generateIris: () => generateIris,
|
|
34
|
+
prepareIrisAssets: () => prepareIrisAssets
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(node_exports);
|
|
37
|
+
|
|
38
|
+
// packages/node/generate.ts
|
|
39
|
+
var import_promises3 = require("fs/promises");
|
|
40
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
41
|
+
var import_sharp2 = __toESM(require("sharp"), 1);
|
|
42
|
+
|
|
43
|
+
// packages/node/discover.ts
|
|
44
|
+
var import_promises = require("fs/promises");
|
|
45
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
46
|
+
var import_parser = require("@babel/parser");
|
|
47
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
48
|
+
var import_minimatch = require("minimatch");
|
|
49
|
+
var ignored = [
|
|
50
|
+
"**/node_modules/**",
|
|
51
|
+
"**/.next/**",
|
|
52
|
+
"**/.git/**",
|
|
53
|
+
"**/dist/**",
|
|
54
|
+
"**/coverage/**",
|
|
55
|
+
"**/generated/**",
|
|
56
|
+
"**/*.generated.*"
|
|
57
|
+
];
|
|
58
|
+
var codeFile = /\.(?:[cm]?[jt]s|[jt]sx)$/i;
|
|
59
|
+
var imageFile = /\.(?:png|jpe?g|webp|avif|gif)$/i;
|
|
60
|
+
async function discoverIrisSources(options, rootDir) {
|
|
61
|
+
const publicDir = import_node_path.default.resolve(rootDir, options.publicDir ?? "public");
|
|
62
|
+
const exclude = options.exclude ?? [];
|
|
63
|
+
const outputDir = import_node_path.default.resolve(rootDir, options.output);
|
|
64
|
+
const explicit = [];
|
|
65
|
+
const patterns = [];
|
|
66
|
+
const found = /* @__PURE__ */ new Set();
|
|
67
|
+
for (const source of options.sources ?? []) {
|
|
68
|
+
if (typeof source === "string" && !/^(?:https?:\/\/|\/)/i.test(source) && import_fast_glob.default.isDynamicPattern(source))
|
|
69
|
+
patterns.push(source);
|
|
70
|
+
else explicit.push(source);
|
|
71
|
+
}
|
|
72
|
+
const globOptions = {
|
|
73
|
+
cwd: rootDir,
|
|
74
|
+
onlyFiles: true,
|
|
75
|
+
followSymbolicLinks: false,
|
|
76
|
+
ignore: [...ignored, ...exclude]
|
|
77
|
+
};
|
|
78
|
+
for (const file of await (0, import_fast_glob.default)(patterns, globOptions)) {
|
|
79
|
+
if (!imageFile.test(file)) continue;
|
|
80
|
+
const relative = import_node_path.default.relative(publicDir, import_node_path.default.resolve(rootDir, file));
|
|
81
|
+
if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Iris source pattern matched ${file} outside publicDir. Image globs must stay inside ${publicDir}.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
found.add(`/${relative.split(import_node_path.default.sep).map(encodeURIComponent).join("/")}`);
|
|
87
|
+
}
|
|
88
|
+
for (const file of (await (0, import_fast_glob.default)(options.scan ?? [], globOptions)).sort()) {
|
|
89
|
+
if (!codeFile.test(file) || /\.d\.[cm]?ts$/i.test(file) || import_node_path.default.resolve(rootDir, file).startsWith(outputDir + import_node_path.default.sep))
|
|
90
|
+
continue;
|
|
91
|
+
const code = await (0, import_promises.readFile)(import_node_path.default.resolve(rootDir, file), "utf8");
|
|
92
|
+
try {
|
|
93
|
+
for (const source of scanImageLiterals(code, file)) found.add(source);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Iris could not scan ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const explicitUrls = new Set(
|
|
101
|
+
explicit.map((source) => typeof source === "string" ? source : source.src)
|
|
102
|
+
);
|
|
103
|
+
const excluded = (src) => exclude.some(
|
|
104
|
+
(pattern) => src === pattern || (0, import_minimatch.minimatch)(src, pattern, { dot: true, nonegate: true, nocomment: true })
|
|
105
|
+
);
|
|
106
|
+
return [
|
|
107
|
+
...[...found].sort().filter((src) => !explicitUrls.has(src) && !excluded(src)),
|
|
108
|
+
...explicit.filter(
|
|
109
|
+
(source) => !excluded(typeof source === "string" ? source : source.src)
|
|
110
|
+
)
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function scanImageLiterals(code, filename) {
|
|
114
|
+
const ast = (0, import_parser.parse)(code, {
|
|
115
|
+
sourceType: "unambiguous",
|
|
116
|
+
sourceFilename: filename,
|
|
117
|
+
attachComment: false,
|
|
118
|
+
plugins: [
|
|
119
|
+
.../\.[cm]?tsx?$/i.test(filename) ? ["typescript"] : [],
|
|
120
|
+
.../\.[jt]sx$|\.[cm]?js$/i.test(filename) ? ["jsx"] : []
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
const literals = [];
|
|
124
|
+
function walk(value) {
|
|
125
|
+
if (!value || typeof value !== "object") return;
|
|
126
|
+
if (Array.isArray(value)) {
|
|
127
|
+
value.forEach(walk);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const node = value;
|
|
131
|
+
if (typeof node.type !== "string") return;
|
|
132
|
+
if (node.type.startsWith("Comment") || node.type === "BinaryExpression" || node.type === "TaggedTemplateExpression" || node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || node.type === "TSLiteralType")
|
|
133
|
+
return;
|
|
134
|
+
if (node.type === "StringLiteral") {
|
|
135
|
+
if (typeof node.value === "string") literals.push(node.value);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (node.type === "TemplateLiteral") {
|
|
139
|
+
const template = node;
|
|
140
|
+
if (template.expressions.length === 0 && template.quasis[0]?.value.cooked)
|
|
141
|
+
literals.push(template.quasis[0].value.cooked);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
for (const [key, child] of Object.entries(node)) {
|
|
145
|
+
if (![
|
|
146
|
+
"comments",
|
|
147
|
+
"leadingComments",
|
|
148
|
+
"trailingComments",
|
|
149
|
+
"innerComments",
|
|
150
|
+
"loc",
|
|
151
|
+
"extra"
|
|
152
|
+
].includes(key))
|
|
153
|
+
walk(child);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
walk(ast.program);
|
|
157
|
+
return [
|
|
158
|
+
...new Set(
|
|
159
|
+
literals.filter(
|
|
160
|
+
(value) => /^(?:https?:\/\/|\/).+\.(?:png|jpe?g|webp|avif|gif)(?:[?#].*)?$/i.test(
|
|
161
|
+
value
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
].sort();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// packages/node/prepare.ts
|
|
169
|
+
var import_promises2 = require("fs/promises");
|
|
170
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
171
|
+
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
172
|
+
var import_sharp = __toESM(require("sharp"), 1);
|
|
173
|
+
|
|
174
|
+
// packages/core/policy.ts
|
|
175
|
+
var import_minimatch2 = require("minimatch");
|
|
176
|
+
function irisFailure(code, message) {
|
|
177
|
+
return { ok: false, error: { code, message } };
|
|
178
|
+
}
|
|
179
|
+
function positive(value) {
|
|
180
|
+
return Number.isFinite(value) && value > 0;
|
|
181
|
+
}
|
|
182
|
+
function validVariant(value) {
|
|
183
|
+
return value && typeof value.url === "string" && !!value.url && positive(value.width) && positive(value.height) && Number.isFinite(value.bytes) && value.bytes >= 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// packages/node/prepare.ts
|
|
187
|
+
async function prepareIrisAssets({
|
|
188
|
+
iris,
|
|
189
|
+
rootDir = process.cwd(),
|
|
190
|
+
publicDir = "public",
|
|
191
|
+
fetch: fetcher = fetch
|
|
192
|
+
}) {
|
|
193
|
+
const publicRoot = import_node_path2.default.resolve(rootDir, publicDir);
|
|
194
|
+
try {
|
|
195
|
+
let inspect2 = function(src) {
|
|
196
|
+
let pending = inspected.get(src);
|
|
197
|
+
if (!pending) {
|
|
198
|
+
pending = (async () => {
|
|
199
|
+
let bytes;
|
|
200
|
+
if (/^https?:\/\//i.test(src)) {
|
|
201
|
+
const response = await fetcher(src, {
|
|
202
|
+
signal: AbortSignal.timeout(3e4)
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok)
|
|
205
|
+
throw new Error(`Cannot inspect ${src}: HTTP ${response.status}`);
|
|
206
|
+
bytes = Buffer.from(await response.arrayBuffer());
|
|
207
|
+
} else {
|
|
208
|
+
const pathname = decodeURIComponent(
|
|
209
|
+
src.split(/[?#]/, 1)[0]
|
|
210
|
+
).replace(/^\/+/, "");
|
|
211
|
+
const file = import_node_path2.default.resolve(publicRoot, pathname);
|
|
212
|
+
within(publicRoot, file);
|
|
213
|
+
within(await (0, import_promises2.realpath)(publicRoot), await (0, import_promises2.realpath)(file));
|
|
214
|
+
bytes = await (0, import_promises2.readFile)(file);
|
|
215
|
+
}
|
|
216
|
+
const meta = await (0, import_sharp.default)(bytes, { animated: true }).metadata();
|
|
217
|
+
const swapped = (meta.orientation ?? 1) >= 5;
|
|
218
|
+
const width = swapped ? meta.height : meta.width;
|
|
219
|
+
const height = swapped ? meta.width : meta.pageHeight ?? meta.height;
|
|
220
|
+
if (!width || !height)
|
|
221
|
+
throw new Error(`Cannot inspect dimensions of ${src}`);
|
|
222
|
+
return {
|
|
223
|
+
url: src,
|
|
224
|
+
width,
|
|
225
|
+
height,
|
|
226
|
+
bytes: bytes.length,
|
|
227
|
+
format: meta.format,
|
|
228
|
+
progressive: meta.isProgressive ?? false,
|
|
229
|
+
alpha: Boolean(
|
|
230
|
+
meta.hasAlpha && ((meta.pages ?? 1) > 1 || !(await (0, import_sharp.default)(bytes).stats()).isOpaque)
|
|
231
|
+
),
|
|
232
|
+
animated: (meta.pages ?? 1) > 1
|
|
233
|
+
};
|
|
234
|
+
})();
|
|
235
|
+
inspected.set(src, pending);
|
|
236
|
+
}
|
|
237
|
+
return pending;
|
|
238
|
+
};
|
|
239
|
+
var inspect = inspect2;
|
|
240
|
+
const entries = /* @__PURE__ */ new Map();
|
|
241
|
+
const explicit = [];
|
|
242
|
+
for (const input of iris.assets) {
|
|
243
|
+
const src = typeof input === "string" ? input : input.src;
|
|
244
|
+
if (/^https?:\/\//i.test(src)) {
|
|
245
|
+
if (import_fast_glob2.default.isDynamicPattern(new URL(src).pathname))
|
|
246
|
+
return irisFailure(
|
|
247
|
+
"remote_pattern",
|
|
248
|
+
"Remote URLs must be explicit; a glob cannot enumerate a bucket"
|
|
249
|
+
);
|
|
250
|
+
explicit.push(input);
|
|
251
|
+
} else if (typeof input === "string" && import_fast_glob2.default.isDynamicPattern(src.startsWith("/") ? src.split(/[?#]/, 1)[0] : src)) {
|
|
252
|
+
const files = await (0, import_fast_glob2.default)(
|
|
253
|
+
src.startsWith("/") ? src.split(/[?#]/, 1)[0].slice(1) : src,
|
|
254
|
+
{
|
|
255
|
+
cwd: src.startsWith("/") ? publicRoot : rootDir,
|
|
256
|
+
onlyFiles: true,
|
|
257
|
+
followSymbolicLinks: false,
|
|
258
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/.next/**"]
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
for (const file of files.sort()) {
|
|
262
|
+
const full = import_node_path2.default.resolve(
|
|
263
|
+
src.startsWith("/") ? publicRoot : rootDir,
|
|
264
|
+
file
|
|
265
|
+
);
|
|
266
|
+
const relative = within(publicRoot, full);
|
|
267
|
+
const url = `/${relative.split(import_node_path2.default.sep).map(encodeURIComponent).join("/")}`;
|
|
268
|
+
entries.set(url, url);
|
|
269
|
+
}
|
|
270
|
+
} else explicit.push(input);
|
|
271
|
+
}
|
|
272
|
+
for (const input of explicit) {
|
|
273
|
+
const src = typeof input === "string" ? input : input.src;
|
|
274
|
+
entries.set(src, input);
|
|
275
|
+
}
|
|
276
|
+
const inspected = /* @__PURE__ */ new Map();
|
|
277
|
+
const catalog = [];
|
|
278
|
+
const identities = /* @__PURE__ */ new Set();
|
|
279
|
+
for (const input of entries.values()) {
|
|
280
|
+
const entry = typeof input === "string" ? { src: input } : input;
|
|
281
|
+
const metadata = "width" in entry ? entry : await inspect2(entry.src);
|
|
282
|
+
const { width, height, bytes, format, progressive, alpha, animated } = metadata;
|
|
283
|
+
const variants = await Promise.all(
|
|
284
|
+
(entry.variants ?? []).map(
|
|
285
|
+
(variant) => typeof variant === "string" ? inspect2(variant) : variant
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
if (!validVariant({ url: entry.src, width, height, bytes }) || !variants.every(validVariant))
|
|
289
|
+
return irisFailure("invalid_asset", `Invalid metadata for ${entry.src}`);
|
|
290
|
+
for (const identity of /* @__PURE__ */ new Set([entry.src, entry.id ?? entry.src])) {
|
|
291
|
+
if (identities.has(identity))
|
|
292
|
+
return irisFailure(
|
|
293
|
+
"duplicate_asset",
|
|
294
|
+
`Ambiguous asset identity: ${identity}`
|
|
295
|
+
);
|
|
296
|
+
identities.add(identity);
|
|
297
|
+
}
|
|
298
|
+
catalog.push({
|
|
299
|
+
src: entry.src,
|
|
300
|
+
id: entry.id,
|
|
301
|
+
width,
|
|
302
|
+
height,
|
|
303
|
+
bytes,
|
|
304
|
+
format,
|
|
305
|
+
progressive,
|
|
306
|
+
alpha,
|
|
307
|
+
animated,
|
|
308
|
+
variants,
|
|
309
|
+
placeholder: entry.placeholder,
|
|
310
|
+
placeholders: entry.placeholders
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return { ok: true, data: catalog };
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return irisFailure(
|
|
316
|
+
"prepare_failed",
|
|
317
|
+
error instanceof Error ? error.message : String(error)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function within(root, file) {
|
|
322
|
+
const relative = import_node_path2.default.relative(root, file);
|
|
323
|
+
if (relative === ".." || relative.startsWith(`..${import_node_path2.default.sep}`) || import_node_path2.default.isAbsolute(relative)) {
|
|
324
|
+
throw new Error(`Image source escapes publicDir: ${file}`);
|
|
325
|
+
}
|
|
326
|
+
return relative;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// packages/node/generate.ts
|
|
330
|
+
async function generateIris({
|
|
331
|
+
iris,
|
|
332
|
+
generation,
|
|
333
|
+
rootDir = process.cwd(),
|
|
334
|
+
configFile = "iris.config.ts",
|
|
335
|
+
allowStale = false,
|
|
336
|
+
fetch: fetcher = fetch
|
|
337
|
+
}) {
|
|
338
|
+
if (!generation.output?.trim())
|
|
339
|
+
throw new Error("Iris requires an output directory");
|
|
340
|
+
const directory = import_node_path3.default.resolve(rootDir, generation.output);
|
|
341
|
+
const publicDir = import_node_path3.default.resolve(rootDir, generation.publicDir ?? "public");
|
|
342
|
+
const adapter = generation.adapter !== false;
|
|
343
|
+
try {
|
|
344
|
+
const sources = await discoverIrisSources(
|
|
345
|
+
{
|
|
346
|
+
...generation,
|
|
347
|
+
sources: [...iris.assets, ...generation.sources ?? []]
|
|
348
|
+
},
|
|
349
|
+
rootDir
|
|
350
|
+
);
|
|
351
|
+
const entries = /* @__PURE__ */ new Map();
|
|
352
|
+
for (const value of sources) {
|
|
353
|
+
const source = typeof value === "string" ? { src: value } : value;
|
|
354
|
+
const previous = entries.get(source.src);
|
|
355
|
+
entries.set(source.src, {
|
|
356
|
+
...previous,
|
|
357
|
+
...source,
|
|
358
|
+
optional: Boolean(
|
|
359
|
+
previous ? "optional" in previous && previous.optional && "optional" in source && source.optional : "optional" in source && source.optional
|
|
360
|
+
)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const catalog = {};
|
|
364
|
+
const placeholders = {};
|
|
365
|
+
const queue = [...entries.values()];
|
|
366
|
+
let skipped = 0;
|
|
367
|
+
let cursor = 0;
|
|
368
|
+
async function worker() {
|
|
369
|
+
while (cursor < queue.length) {
|
|
370
|
+
const source = queue[cursor++];
|
|
371
|
+
if ("width" in source) {
|
|
372
|
+
const prepared = await prepareIrisAssets({
|
|
373
|
+
iris: { assets: [source] },
|
|
374
|
+
rootDir,
|
|
375
|
+
publicDir,
|
|
376
|
+
fetch: fetcher
|
|
377
|
+
});
|
|
378
|
+
if (!prepared.ok) throw new Error(prepared.error.message);
|
|
379
|
+
catalog[source.src] = prepared.data[0];
|
|
380
|
+
if (source.placeholder) placeholders[source.src] = source.placeholder;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
let bytes;
|
|
384
|
+
if (/^https?:\/\//i.test(source.src)) {
|
|
385
|
+
const response = await fetcher(source.src, {
|
|
386
|
+
signal: AbortSignal.timeout(3e4)
|
|
387
|
+
});
|
|
388
|
+
if (source.optional && response.status === 404) {
|
|
389
|
+
skipped++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (!response.ok)
|
|
393
|
+
throw new Error(`${response.status} fetching ${source.src}`);
|
|
394
|
+
bytes = Buffer.from(await response.arrayBuffer());
|
|
395
|
+
} else {
|
|
396
|
+
const pathname = decodeURIComponent(
|
|
397
|
+
source.src.split(/[?#]/, 1)[0]
|
|
398
|
+
).replace(/^\/+/, "");
|
|
399
|
+
const file = import_node_path3.default.resolve(publicDir, pathname);
|
|
400
|
+
for (const relative of [
|
|
401
|
+
import_node_path3.default.relative(publicDir, file),
|
|
402
|
+
import_node_path3.default.relative(await (0, import_promises3.realpath)(publicDir), await (0, import_promises3.realpath)(file))
|
|
403
|
+
]) {
|
|
404
|
+
if (relative === ".." || relative.startsWith(`..${import_node_path3.default.sep}`) || import_node_path3.default.isAbsolute(relative))
|
|
405
|
+
throw new Error(
|
|
406
|
+
`Iris local source escapes publicDir: ${source.src}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
bytes = await (0, import_promises3.readFile)(file);
|
|
410
|
+
}
|
|
411
|
+
const meta = await (0, import_sharp2.default)(bytes, { animated: true }).metadata();
|
|
412
|
+
const swapped = (meta.orientation ?? 1) >= 5;
|
|
413
|
+
const width = swapped ? meta.height : meta.width;
|
|
414
|
+
const height = swapped ? meta.width : meta.pageHeight ?? meta.height;
|
|
415
|
+
if (!width || !height)
|
|
416
|
+
throw new Error(`Cannot inspect dimensions of ${source.src}`);
|
|
417
|
+
const placeholderOptions = source.placeholderOptions ?? {};
|
|
418
|
+
const format = placeholderOptions.format ?? "webp";
|
|
419
|
+
const size = placeholderOptions.width ?? 10;
|
|
420
|
+
const quality = placeholderOptions.quality ?? 40;
|
|
421
|
+
async function encodePlaceholder({
|
|
422
|
+
width: width2,
|
|
423
|
+
quality: quality2
|
|
424
|
+
}) {
|
|
425
|
+
const buffer = await (0, import_sharp2.default)(bytes, { page: 0, pages: 1 }).rotate().resize({
|
|
426
|
+
width: width2,
|
|
427
|
+
height: width2,
|
|
428
|
+
fit: "inside",
|
|
429
|
+
withoutEnlargement: true
|
|
430
|
+
}).toFormat(format, { quality: quality2 }).toBuffer();
|
|
431
|
+
return `data:image/${format};base64,${buffer.toString("base64")}`;
|
|
432
|
+
}
|
|
433
|
+
const placeholder = source.placeholder ?? await encodePlaceholder({ width: size, quality });
|
|
434
|
+
placeholders[source.src] = placeholder;
|
|
435
|
+
if (source.placeholderOnly) continue;
|
|
436
|
+
const preparedPlaceholders = [...source.placeholders ?? []];
|
|
437
|
+
for (const request of iris.placeholderRequests) {
|
|
438
|
+
if (preparedPlaceholders.some(
|
|
439
|
+
(item) => item.width === request.width && item.quality === request.quality
|
|
440
|
+
))
|
|
441
|
+
continue;
|
|
442
|
+
const url = request.width === size && request.quality === quality ? placeholder : await encodePlaceholder(request);
|
|
443
|
+
preparedPlaceholders.push({ ...request, url });
|
|
444
|
+
}
|
|
445
|
+
const animated = (meta.pages ?? 1) > 1;
|
|
446
|
+
const alpha = Boolean(
|
|
447
|
+
meta.hasAlpha && (animated || !(await (0, import_sharp2.default)(bytes).stats()).isOpaque)
|
|
448
|
+
);
|
|
449
|
+
const metadata = {
|
|
450
|
+
src: source.src,
|
|
451
|
+
id: source.id,
|
|
452
|
+
width,
|
|
453
|
+
height,
|
|
454
|
+
bytes: bytes.length,
|
|
455
|
+
format: meta.format,
|
|
456
|
+
alpha,
|
|
457
|
+
animated,
|
|
458
|
+
progressive: meta.isProgressive ?? false,
|
|
459
|
+
placeholder,
|
|
460
|
+
placeholders: preparedPlaceholders,
|
|
461
|
+
variants: []
|
|
462
|
+
};
|
|
463
|
+
if (source.variants?.length) {
|
|
464
|
+
const prepared = await prepareIrisAssets({
|
|
465
|
+
iris: {
|
|
466
|
+
assets: [
|
|
467
|
+
{ ...metadata, variants: [] },
|
|
468
|
+
...source.variants.filter(
|
|
469
|
+
(item) => typeof item === "string"
|
|
470
|
+
)
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
rootDir,
|
|
474
|
+
publicDir,
|
|
475
|
+
fetch: fetcher
|
|
476
|
+
});
|
|
477
|
+
if (!prepared.ok) throw new Error(prepared.error.message);
|
|
478
|
+
metadata.variants = [
|
|
479
|
+
...metadata.variants,
|
|
480
|
+
...source.variants.map((variant) => {
|
|
481
|
+
if (typeof variant !== "string") return variant;
|
|
482
|
+
const asset = prepared.data.find((item) => item.src === variant);
|
|
483
|
+
return { ...asset, url: asset.src };
|
|
484
|
+
})
|
|
485
|
+
];
|
|
486
|
+
}
|
|
487
|
+
catalog[source.src] = metadata;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const concurrency = generation.concurrency ?? 4;
|
|
491
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1)
|
|
492
|
+
throw new Error("concurrency must be a positive integer");
|
|
493
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
494
|
+
const sorted = (data) => Object.fromEntries(
|
|
495
|
+
Object.entries(data).sort(([a], [b]) => a.localeCompare(b))
|
|
496
|
+
);
|
|
497
|
+
const files = [
|
|
498
|
+
["catalog.json", sorted(catalog)],
|
|
499
|
+
["placeholders.json", sorted(placeholders)]
|
|
500
|
+
];
|
|
501
|
+
if (generation.report)
|
|
502
|
+
files.push([
|
|
503
|
+
"report.json",
|
|
504
|
+
{
|
|
505
|
+
inspected: Object.keys(catalog).length,
|
|
506
|
+
skipped,
|
|
507
|
+
assets: Object.values(catalog).map((asset) => ({
|
|
508
|
+
src: asset.src,
|
|
509
|
+
result: iris.resolve({ asset })
|
|
510
|
+
}))
|
|
511
|
+
}
|
|
512
|
+
]);
|
|
513
|
+
await (0, import_promises3.mkdir)(directory, { recursive: true });
|
|
514
|
+
for (const [name, data] of files)
|
|
515
|
+
await atomicWrite(
|
|
516
|
+
import_node_path3.default.join(directory, name),
|
|
517
|
+
JSON.stringify(data, null, 2) + "\n"
|
|
518
|
+
);
|
|
519
|
+
if (adapter) {
|
|
520
|
+
let configImport = import_node_path3.default.relative(directory, import_node_path3.default.resolve(rootDir, configFile)).split(import_node_path3.default.sep).join("/").replace(/\.[cm]?[jt]s$/, "");
|
|
521
|
+
if (!configImport.startsWith(".")) configImport = `./${configImport}`;
|
|
522
|
+
await atomicWrite(
|
|
523
|
+
import_node_path3.default.join(directory, "image.tsx"),
|
|
524
|
+
`"use client"
|
|
525
|
+
|
|
526
|
+
// Generated by Iris. Do not edit.
|
|
527
|
+
import { createIrisNextImage } from "@joycostudio/iris/next"
|
|
528
|
+
import iris from ${JSON.stringify(configImport)}
|
|
529
|
+
import catalog from "./catalog.json"
|
|
530
|
+
|
|
531
|
+
export const IrisImage = createIrisNextImage({ iris, catalog })
|
|
532
|
+
`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
stale: false,
|
|
537
|
+
placeholders: Object.keys(placeholders).length,
|
|
538
|
+
inspected: Object.keys(catalog).length,
|
|
539
|
+
skipped
|
|
540
|
+
};
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (allowStale && await usableOutput({ directory, adapter })) {
|
|
543
|
+
console.warn("Iris generation failed; keeping existing files:", error);
|
|
544
|
+
return { stale: true, placeholders: 0, inspected: 0, skipped: 0 };
|
|
545
|
+
}
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async function atomicWrite(file, content) {
|
|
550
|
+
await (0, import_promises3.writeFile)(`${file}.tmp`, content);
|
|
551
|
+
await (0, import_promises3.rename)(`${file}.tmp`, file);
|
|
552
|
+
}
|
|
553
|
+
async function usableOutput({
|
|
554
|
+
directory,
|
|
555
|
+
adapter
|
|
556
|
+
}) {
|
|
557
|
+
try {
|
|
558
|
+
const catalog = JSON.parse(
|
|
559
|
+
await (0, import_promises3.readFile)(import_node_path3.default.join(directory, "catalog.json"), "utf8")
|
|
560
|
+
);
|
|
561
|
+
const placeholders = JSON.parse(
|
|
562
|
+
await (0, import_promises3.readFile)(import_node_path3.default.join(directory, "placeholders.json"), "utf8")
|
|
563
|
+
);
|
|
564
|
+
if (!catalog || Array.isArray(catalog) || !Object.keys(catalog).length || !placeholders || Array.isArray(placeholders))
|
|
565
|
+
return false;
|
|
566
|
+
if (adapter && !(await (0, import_promises3.readFile)(import_node_path3.default.join(directory, "image.tsx"), "utf8")).includes(
|
|
567
|
+
"createIrisNextImage"
|
|
568
|
+
))
|
|
569
|
+
return false;
|
|
570
|
+
return true;
|
|
571
|
+
} catch {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
576
|
+
0 && (module.exports = {
|
|
577
|
+
generateIris,
|
|
578
|
+
prepareIrisAssets
|
|
579
|
+
});
|
|
580
|
+
//# sourceMappingURL=node.cjs.map
|