@uploadista/flow-images-photon 0.0.20-beta.9 → 0.1.0-beta.5
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/dist/common-ChU3HbEt.mjs.map +1 -1
- package/dist/node.mjs.map +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common-ChU3HbEt.mjs","names":[
|
|
1
|
+
{"version":3,"file":"common-ChU3HbEt.mjs","names":[],"sources":["../src/common.ts"],"sourcesContent":["export const round2dp = (num: number) =>\n Math.round((num + Number.EPSILON) * 100) / 100;\n\nexport const calculateImageSize = (\n objectFit: \"contain\" | \"cover\" | \"fill\" | \"none\" | \"scale-down\",\n currentWidth: number,\n currentHeight: number,\n containerWidth: number,\n containerHeight: number,\n) => {\n let newSize: {\n width?: number;\n height?: number;\n } = {};\n\n switch (objectFit) {\n case \"contain\": {\n // Calculate dimensions while maintaining aspect ratio\n const widthRatioContain = containerWidth / currentWidth;\n const heightRatioContain = containerHeight / currentHeight;\n const scaleContain = Math.min(widthRatioContain, heightRatioContain);\n newSize = {\n width: currentWidth * scaleContain,\n height: currentHeight * scaleContain,\n };\n break;\n }\n case \"cover\": {\n // Calculate dimensions while maintaining aspect ratio\n const widthRatioCover = containerWidth / currentWidth;\n const heightRatioCover = containerHeight / currentHeight;\n const scaleCover = Math.max(widthRatioCover, heightRatioCover);\n const newWidthCover = currentWidth * scaleCover;\n const newHeightCover = currentHeight * scaleCover;\n // Adjust dimensions if it doesn't cover the container completely\n if (newWidthCover < containerWidth || newHeightCover < containerHeight) {\n const scaleCoverAdjusted = Math.max(\n containerWidth / currentWidth,\n containerHeight / currentHeight,\n );\n newSize = {\n width: currentWidth * scaleCoverAdjusted,\n height: currentHeight * scaleCoverAdjusted,\n };\n break;\n }\n newSize = {\n width: newWidthCover,\n height: newHeightCover,\n };\n break;\n }\n case \"fill\":\n // Stretch image to fill container\n newSize = {\n width: containerWidth,\n height: containerHeight,\n };\n break;\n case \"none\":\n // Keep original image size\n newSize = {\n width: currentWidth,\n height: currentHeight,\n };\n break;\n case \"scale-down\": {\n // Calculate dimensions based on contain and none values\n const widthRatioScaleDown = containerWidth / currentWidth;\n const heightRatioScaleDown = containerHeight / currentHeight;\n const scaleScaleDown = Math.min(\n 1,\n Math.min(widthRatioScaleDown, heightRatioScaleDown),\n );\n newSize = {\n width: currentWidth * scaleScaleDown,\n height: currentHeight * scaleScaleDown,\n };\n break;\n }\n default:\n throw new Error(\"Invalid object fit value\");\n }\n\n if (!newSize?.width || !newSize?.height) {\n throw new Error(\"Invalid dimensions\");\n }\n\n return {\n width: round2dp(newSize.width),\n height: round2dp(newSize.height),\n };\n\n // return newSize;\n};\n"],"mappings":"AAAA,MAAa,EAAY,GACvB,KAAK,OAAO,UAAwB,IAAI,CAAG,IAEhC,GACX,EACA,EACA,EACA,EACA,IACG,CACH,IAAI,EAGA,EAAE,CAEN,OAAQ,EAAR,CACE,IAAK,UAAW,CAEd,IAAM,EAAoB,EAAiB,EACrC,EAAqB,EAAkB,EACvC,EAAe,KAAK,IAAI,EAAmB,EAAmB,CACpE,EAAU,CACR,MAAO,EAAe,EACtB,OAAQ,EAAgB,EACzB,CACD,MAEF,IAAK,QAAS,CAEZ,IAAM,EAAkB,EAAiB,EACnC,EAAmB,EAAkB,EACrC,EAAa,KAAK,IAAI,EAAiB,EAAiB,CACxD,EAAgB,EAAe,EAC/B,EAAiB,EAAgB,EAEvC,GAAI,EAAgB,GAAkB,EAAiB,EAAiB,CACtE,IAAM,EAAqB,KAAK,IAC9B,EAAiB,EACjB,EAAkB,EACnB,CACD,EAAU,CACR,MAAO,EAAe,EACtB,OAAQ,EAAgB,EACzB,CACD,MAEF,EAAU,CACR,MAAO,EACP,OAAQ,EACT,CACD,MAEF,IAAK,OAEH,EAAU,CACR,MAAO,EACP,OAAQ,EACT,CACD,MACF,IAAK,OAEH,EAAU,CACR,MAAO,EACP,OAAQ,EACT,CACD,MACF,IAAK,aAAc,CAEjB,IAAM,EAAsB,EAAiB,EACvC,EAAuB,EAAkB,EACzC,EAAiB,KAAK,IAC1B,EACA,KAAK,IAAI,EAAqB,EAAqB,CACpD,CACD,EAAU,CACR,MAAO,EAAe,EACtB,OAAQ,EAAgB,EACzB,CACD,MAEF,QACE,MAAU,MAAM,2BAA2B,CAG/C,GAAI,CAAC,GAAS,OAAS,CAAC,GAAS,OAC/B,MAAU,MAAM,qBAAqB,CAGvC,MAAO,CACL,MAAO,EAAS,EAAQ,MAAM,CAC9B,OAAQ,EAAS,EAAQ,OAAO,CACjC"}
|
package/dist/node.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.mjs","names":["outputBytes: Uint8Array"],"sources":["../src/image-plugin-node.ts"],"sourcesContent":["import {\n adjust_brightness,\n adjust_contrast,\n crop,\n fliph,\n flipv,\n gaussian_blur,\n grayscale,\n PhotonImage,\n padding_bottom,\n padding_left,\n padding_right,\n padding_top,\n Rgba,\n resize,\n sepia,\n sharpen,\n} from \"@cf-wasm/photon/node\";\nimport { UploadistaError } from \"@uploadista/core/errors\";\nimport { ImagePlugin } from \"@uploadista/core/flow\";\nimport { withOperationSpan } from \"@uploadista/observability\";\nimport { Effect, Layer } from \"effect\";\nimport type tinycolor from \"tinycolor2\";\nimport { calculateImageSize } from \"./common\";\n\nexport const tinyColorToPhotonRGBA = (color: tinycolor.Instance) => {\n const rgba = color.toRgb();\n return new Rgba(rgba.r, rgba.g, rgba.b, rgba.a);\n};\n\nexport const autoResize = (\n image: PhotonImage,\n newWidth?: number,\n newHeight?: number,\n options?: {\n fit?: \"contain\" | \"cover\" | \"fill\";\n fit_cover_letterbox_color?: tinycolor.Instance;\n },\n) => {\n const currentWidth = image.get_width();\n const currentHeight = image.get_height();\n\n if (!newWidth && !newHeight) {\n throw new Error(\"At least one of width or height is required\");\n }\n\n if (newWidth && !newHeight) {\n newHeight = Math.floor((newWidth / currentWidth) * currentHeight);\n } else if (newHeight && !newWidth) {\n newWidth = Math.floor((newHeight / currentHeight) * currentWidth);\n }\n\n if (!newWidth || !newHeight) {\n throw new Error(\"Invalid width or height\");\n }\n\n if (newWidth === currentWidth && newHeight === currentHeight) {\n return image;\n }\n\n const fit = options?.fit || \"cover\";\n\n const dem = calculateImageSize(\n fit,\n currentWidth,\n currentHeight,\n newWidth,\n newHeight,\n );\n\n image = resize(image, dem.width, dem.height, 1);\n\n const [updatedWidth, updatedHeight] = [image.get_width(), image.get_height()];\n\n if (fit === \"contain\" && options?.fit_cover_letterbox_color) {\n const paddingX = Math.floor((newWidth - updatedWidth) / 2);\n const paddingY = Math.floor((newHeight - updatedHeight) / 2);\n\n if (paddingY > 0) {\n image = padding_top(\n image,\n paddingY,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n image = padding_bottom(\n image,\n paddingY,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n }\n\n if (paddingX > 0) {\n image = padding_left(\n image,\n paddingX,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n image = padding_right(\n image,\n paddingX,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n }\n } else if (fit === \"cover\") {\n //crop to center\n const cropX = Math.floor(updatedWidth - newWidth) / 2;\n const cropY = Math.floor(updatedHeight - newHeight) / 2;\n\n //top left to down right\n image = crop(image, cropX, cropY, newWidth + cropX, newHeight + cropY);\n }\n\n return image;\n};\n\nexport const imageToFormat = (\n image: PhotonImage,\n format: \"webp\" | \"jpeg\" | \"png\",\n options?: {\n jpeg_quality?: number;\n },\n) => {\n let outputBytes: Uint8Array;\n\n switch (format) {\n case \"webp\":\n outputBytes = image.get_bytes_webp();\n break;\n case \"jpeg\":\n outputBytes = image.get_bytes_jpeg(options?.jpeg_quality || 100);\n break;\n case \"png\":\n outputBytes = image.get_bytes();\n break;\n default:\n outputBytes = image.get_bytes_jpeg(options?.jpeg_quality || 100);\n }\n\n return outputBytes;\n};\n\nexport const imagePluginNode = Layer.succeed(\n ImagePlugin,\n ImagePlugin.of({\n optimize: (inputBytes, { quality }) => {\n // create a PhotonImage instance\n const inputImage = PhotonImage.new_from_byteslice(inputBytes);\n\n // get jpeg bytes\n const outputBytes = inputImage.get_bytes_jpeg(quality);\n\n // call free() method to free memory\n inputImage.free();\n\n return Effect.succeed(outputBytes).pipe(\n withOperationSpan(\"image\", \"optimize\", {\n \"image.quality\": quality,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n resize: (inputBytes, { width, height, fit }) => {\n if (!width && !height) {\n throw new Error(\"Either width or height must be specified for resize\");\n }\n // create a PhotonImage instance\n const inputImage = PhotonImage.new_from_byteslice(inputBytes);\n\n // resize image using photon\n const outputImage = autoResize(\n inputImage,\n width ?? inputImage.get_width(),\n height ?? inputImage.get_height(),\n { fit },\n );\n\n // get webp bytes\n const outputBytes = outputImage.get_bytes_webp();\n\n // for other formats\n // png : outputImage.get_bytes();\n // jpeg : outputImage.get_bytes_jpeg(quality);\n\n // call free() method to free memory\n inputImage.free();\n outputImage.free();\n\n return Effect.succeed(outputBytes).pipe(\n withOperationSpan(\"image\", \"resize\", {\n \"image.width\": width,\n \"image.height\": height,\n \"image.fit\": fit,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n transform: (inputBytes, transformation) => {\n return Effect.gen(function* () {\n // List of unsupported transformations in photon\n const unsupportedTransformations: string[] = [\n \"watermark\",\n \"logo\",\n \"text\",\n ];\n\n if (unsupportedTransformations.includes(transformation.type)) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: `Photon plugin does not support '${transformation.type}'. Use sharp plugin or remove this transformation.`,\n }),\n );\n }\n\n // Create a PhotonImage instance\n let image = PhotonImage.new_from_byteslice(inputBytes);\n\n try {\n switch (transformation.type) {\n case \"resize\": {\n image = autoResize(\n image,\n transformation.width,\n transformation.height,\n { fit: transformation.fit },\n );\n break;\n }\n\n case \"blur\": {\n // Photon uses a radius parameter for gaussian blur\n // Convert sigma to a rough radius approximation\n const radius = Math.round(transformation.sigma);\n gaussian_blur(image, radius);\n break;\n }\n\n case \"rotate\": {\n // Photon doesn't have a straightforward rotate with angle function\n // We'll need to implement this using available functions or mark as unsupported\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: \"Rotate transformation is not fully supported in photon plugin. Use sharp plugin instead.\",\n }),\n );\n }\n\n case \"flip\": {\n // Photon has fliph() and flipv() functions\n if (transformation.direction === \"horizontal\") {\n fliph(image);\n } else {\n flipv(image);\n }\n break;\n }\n\n case \"grayscale\": {\n grayscale(image);\n break;\n }\n\n case \"sepia\": {\n sepia(image);\n break;\n }\n\n case \"brightness\": {\n // Photon's adjust_brightness function takes a value to add to each pixel\n // Convert our -100 to +100 range to photon's expected range\n const adjustValue = Math.round(transformation.value * 2.55);\n adjust_brightness(image, adjustValue);\n break;\n }\n\n case \"contrast\": {\n // Photon's adjust_contrast takes a contrast value\n // Convert our -100 to +100 range to a suitable value\n const contrastValue = transformation.value;\n adjust_contrast(image, contrastValue);\n break;\n }\n\n case \"sharpen\": {\n sharpen(image);\n break;\n }\n\n default: {\n // TypeScript exhaustiveness check\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: `Unknown transformation type: ${(transformation as { type: string }).type}`,\n }),\n );\n }\n }\n\n // Get output bytes (using webp format)\n const outputBytes = image.get_bytes_webp();\n\n return outputBytes;\n } finally {\n // Always free the image to prevent memory leaks\n image.free();\n }\n }).pipe(\n withOperationSpan(\"image\", \"transform\", {\n \"image.transformation_type\": transformation.type,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n }),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.mjs","names":[],"sources":["../src/image-plugin-node.ts"],"sourcesContent":["import {\n adjust_brightness,\n adjust_contrast,\n crop,\n fliph,\n flipv,\n gaussian_blur,\n grayscale,\n PhotonImage,\n padding_bottom,\n padding_left,\n padding_right,\n padding_top,\n Rgba,\n resize,\n sepia,\n sharpen,\n} from \"@cf-wasm/photon/node\";\nimport { UploadistaError } from \"@uploadista/core/errors\";\nimport { ImagePlugin } from \"@uploadista/core/flow\";\nimport { withOperationSpan } from \"@uploadista/observability\";\nimport { Effect, Layer } from \"effect\";\nimport type tinycolor from \"tinycolor2\";\nimport { calculateImageSize } from \"./common\";\n\nexport const tinyColorToPhotonRGBA = (color: tinycolor.Instance) => {\n const rgba = color.toRgb();\n return new Rgba(rgba.r, rgba.g, rgba.b, rgba.a);\n};\n\nexport const autoResize = (\n image: PhotonImage,\n newWidth?: number,\n newHeight?: number,\n options?: {\n fit?: \"contain\" | \"cover\" | \"fill\";\n fit_cover_letterbox_color?: tinycolor.Instance;\n },\n) => {\n const currentWidth = image.get_width();\n const currentHeight = image.get_height();\n\n if (!newWidth && !newHeight) {\n throw new Error(\"At least one of width or height is required\");\n }\n\n if (newWidth && !newHeight) {\n newHeight = Math.floor((newWidth / currentWidth) * currentHeight);\n } else if (newHeight && !newWidth) {\n newWidth = Math.floor((newHeight / currentHeight) * currentWidth);\n }\n\n if (!newWidth || !newHeight) {\n throw new Error(\"Invalid width or height\");\n }\n\n if (newWidth === currentWidth && newHeight === currentHeight) {\n return image;\n }\n\n const fit = options?.fit || \"cover\";\n\n const dem = calculateImageSize(\n fit,\n currentWidth,\n currentHeight,\n newWidth,\n newHeight,\n );\n\n image = resize(image, dem.width, dem.height, 1);\n\n const [updatedWidth, updatedHeight] = [image.get_width(), image.get_height()];\n\n if (fit === \"contain\" && options?.fit_cover_letterbox_color) {\n const paddingX = Math.floor((newWidth - updatedWidth) / 2);\n const paddingY = Math.floor((newHeight - updatedHeight) / 2);\n\n if (paddingY > 0) {\n image = padding_top(\n image,\n paddingY,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n image = padding_bottom(\n image,\n paddingY,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n }\n\n if (paddingX > 0) {\n image = padding_left(\n image,\n paddingX,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n image = padding_right(\n image,\n paddingX,\n tinyColorToPhotonRGBA(options.fit_cover_letterbox_color),\n );\n }\n } else if (fit === \"cover\") {\n //crop to center\n const cropX = Math.floor(updatedWidth - newWidth) / 2;\n const cropY = Math.floor(updatedHeight - newHeight) / 2;\n\n //top left to down right\n image = crop(image, cropX, cropY, newWidth + cropX, newHeight + cropY);\n }\n\n return image;\n};\n\nexport const imageToFormat = (\n image: PhotonImage,\n format: \"webp\" | \"jpeg\" | \"png\",\n options?: {\n jpeg_quality?: number;\n },\n) => {\n let outputBytes: Uint8Array;\n\n switch (format) {\n case \"webp\":\n outputBytes = image.get_bytes_webp();\n break;\n case \"jpeg\":\n outputBytes = image.get_bytes_jpeg(options?.jpeg_quality || 100);\n break;\n case \"png\":\n outputBytes = image.get_bytes();\n break;\n default:\n outputBytes = image.get_bytes_jpeg(options?.jpeg_quality || 100);\n }\n\n return outputBytes;\n};\n\nexport const imagePluginNode = Layer.succeed(\n ImagePlugin,\n ImagePlugin.of({\n optimize: (inputBytes, { quality }) => {\n // create a PhotonImage instance\n const inputImage = PhotonImage.new_from_byteslice(inputBytes);\n\n // get jpeg bytes\n const outputBytes = inputImage.get_bytes_jpeg(quality);\n\n // call free() method to free memory\n inputImage.free();\n\n return Effect.succeed(outputBytes).pipe(\n withOperationSpan(\"image\", \"optimize\", {\n \"image.quality\": quality,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n resize: (inputBytes, { width, height, fit }) => {\n if (!width && !height) {\n throw new Error(\"Either width or height must be specified for resize\");\n }\n // create a PhotonImage instance\n const inputImage = PhotonImage.new_from_byteslice(inputBytes);\n\n // resize image using photon\n const outputImage = autoResize(\n inputImage,\n width ?? inputImage.get_width(),\n height ?? inputImage.get_height(),\n { fit },\n );\n\n // get webp bytes\n const outputBytes = outputImage.get_bytes_webp();\n\n // for other formats\n // png : outputImage.get_bytes();\n // jpeg : outputImage.get_bytes_jpeg(quality);\n\n // call free() method to free memory\n inputImage.free();\n outputImage.free();\n\n return Effect.succeed(outputBytes).pipe(\n withOperationSpan(\"image\", \"resize\", {\n \"image.width\": width,\n \"image.height\": height,\n \"image.fit\": fit,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n transform: (inputBytes, transformation) => {\n return Effect.gen(function* () {\n // List of unsupported transformations in photon\n const unsupportedTransformations: string[] = [\n \"watermark\",\n \"logo\",\n \"text\",\n ];\n\n if (unsupportedTransformations.includes(transformation.type)) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: `Photon plugin does not support '${transformation.type}'. Use sharp plugin or remove this transformation.`,\n }),\n );\n }\n\n // Create a PhotonImage instance\n let image = PhotonImage.new_from_byteslice(inputBytes);\n\n try {\n switch (transformation.type) {\n case \"resize\": {\n image = autoResize(\n image,\n transformation.width,\n transformation.height,\n { fit: transformation.fit },\n );\n break;\n }\n\n case \"blur\": {\n // Photon uses a radius parameter for gaussian blur\n // Convert sigma to a rough radius approximation\n const radius = Math.round(transformation.sigma);\n gaussian_blur(image, radius);\n break;\n }\n\n case \"rotate\": {\n // Photon doesn't have a straightforward rotate with angle function\n // We'll need to implement this using available functions or mark as unsupported\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: \"Rotate transformation is not fully supported in photon plugin. Use sharp plugin instead.\",\n }),\n );\n }\n\n case \"flip\": {\n // Photon has fliph() and flipv() functions\n if (transformation.direction === \"horizontal\") {\n fliph(image);\n } else {\n flipv(image);\n }\n break;\n }\n\n case \"grayscale\": {\n grayscale(image);\n break;\n }\n\n case \"sepia\": {\n sepia(image);\n break;\n }\n\n case \"brightness\": {\n // Photon's adjust_brightness function takes a value to add to each pixel\n // Convert our -100 to +100 range to photon's expected range\n const adjustValue = Math.round(transformation.value * 2.55);\n adjust_brightness(image, adjustValue);\n break;\n }\n\n case \"contrast\": {\n // Photon's adjust_contrast takes a contrast value\n // Convert our -100 to +100 range to a suitable value\n const contrastValue = transformation.value;\n adjust_contrast(image, contrastValue);\n break;\n }\n\n case \"sharpen\": {\n sharpen(image);\n break;\n }\n\n default: {\n // TypeScript exhaustiveness check\n return yield* Effect.fail(\n UploadistaError.fromCode(\"UNKNOWN_ERROR\", {\n body: `Unknown transformation type: ${(transformation as { type: string }).type}`,\n }),\n );\n }\n }\n\n // Get output bytes (using webp format)\n const outputBytes = image.get_bytes_webp();\n\n return outputBytes;\n } finally {\n // Always free the image to prevent memory leaks\n image.free();\n }\n }).pipe(\n withOperationSpan(\"image\", \"transform\", {\n \"image.transformation_type\": transformation.type,\n \"image.input_size\": inputBytes.byteLength,\n }),\n );\n },\n }),\n);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/flow-images-photon",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0-beta.5",
|
|
5
5
|
"description": "Photon image processing service for Uploadista Flow",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@cf-wasm/photon": "0.3.4",
|
|
24
24
|
"tinycolor2": "1.6.0",
|
|
25
|
-
"@uploadista/core": "0.0
|
|
26
|
-
"@uploadista/observability": "0.0
|
|
25
|
+
"@uploadista/core": "0.1.0-beta.5",
|
|
26
|
+
"@uploadista/observability": "0.1.0-beta.5"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"effect": "^3.0.0",
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@effect/vitest": "0.27.0",
|
|
34
|
-
"@types/node": "24.10.
|
|
34
|
+
"@types/node": "24.10.8",
|
|
35
35
|
"@types/tinycolor2": "1.4.6",
|
|
36
|
-
"effect": "3.19.
|
|
37
|
-
"tsdown": "0.
|
|
38
|
-
"vitest": "4.0.
|
|
39
|
-
"zod": "4.
|
|
40
|
-
"@uploadista/typescript-config": "0.0
|
|
36
|
+
"effect": "3.19.14",
|
|
37
|
+
"tsdown": "0.19.0",
|
|
38
|
+
"vitest": "4.0.17",
|
|
39
|
+
"zod": "4.3.5",
|
|
40
|
+
"@uploadista/typescript-config": "0.1.0-beta.5"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc --noEmit && tsdown",
|
|
44
44
|
"format": "biome format --write ./src",
|
|
45
45
|
"lint": "biome lint --write ./src",
|
|
46
46
|
"check": "biome check --write ./src",
|
|
47
|
-
"test": "vitest",
|
|
47
|
+
"test": "vitest run",
|
|
48
48
|
"test:run": "vitest run",
|
|
49
49
|
"test:watch": "vitest watch"
|
|
50
50
|
}
|