Added pHYs
to import/export of png images (#1200)
Added the following - Always export pngs with a pixel-ratio of `2` - Added the `pHYs` png metadata chunk describing the pixel ratio so it opens with the correct size - When importing PNGs read the `pHYs` chunk for the sizing info All the exporting is done via just modifying the bytes from the browsers native image handling. https://user-images.githubusercontent.com/235915/234309015-19f39f3a-66ce-4ec2-b7d0-b34a07ed346b.mov I've also added `ANALYZE=true` option to get the build metadata from esbuild on boot of `yarn dev` which allow me to see the bundle size info in https://esbuild.github.io/analyze/ ![esbuild github io_analyze_](https://user-images.githubusercontent.com/235915/234310302-c6fe8109-c82d-480a-8c65-c7638b09e71e.png) You can see that `crc` adds about `4.4kb` <img width="280" alt="Screenshot 2023-04-25 at 15 33 26" src="https://user-images.githubusercontent.com/235915/234310669-99e3e787-ddca-4ad2-81cf-b4a541631d62.png"> --------- Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
731da1bc77
commit
77175a9dc4
9 changed files with 191 additions and 61 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -75,3 +75,5 @@ packages/*/api
|
||||||
apps/examples/www/index.css
|
apps/examples/www/index.css
|
||||||
apps/examples/www/index.js
|
apps/examples/www/index.js
|
||||||
.tsbuild
|
.tsbuild
|
||||||
|
|
||||||
|
apps/examples/build.esbuild.json
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { createServer, request } from 'http'
|
||||||
import ip from 'ip'
|
import ip from 'ip'
|
||||||
import chalk from 'kleur'
|
import chalk from 'kleur'
|
||||||
import * as url from 'url'
|
import * as url from 'url'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
const LOG_REQUEST_PATHS = false
|
const LOG_REQUEST_PATHS = false
|
||||||
|
|
||||||
|
@ -22,7 +23,9 @@ const OUT_DIR = dirname + '/../www/'
|
||||||
const clients = []
|
const clients = []
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await esbuild.build({
|
const isAnalyzeEnabled = process.env.ANALYZE === 'true'
|
||||||
|
|
||||||
|
const result = await esbuild.build({
|
||||||
entryPoints: ['src/index.tsx'],
|
entryPoints: ['src/index.tsx'],
|
||||||
outdir: OUT_DIR,
|
outdir: OUT_DIR,
|
||||||
bundle: true,
|
bundle: true,
|
||||||
|
@ -32,6 +35,7 @@ async function main() {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
external: ['*.woff'],
|
external: ['*.woff'],
|
||||||
target: browserslist(['defaults']),
|
target: browserslist(['defaults']),
|
||||||
|
metafile: isAnalyzeEnabled,
|
||||||
define: {
|
define: {
|
||||||
process: '{ "env": { "NODE_ENV": "development"} }',
|
process: '{ "env": { "NODE_ENV": "development"} }',
|
||||||
},
|
},
|
||||||
|
@ -53,6 +57,13 @@ async function main() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (isAnalyzeEnabled) {
|
||||||
|
await fs.promises.writeFile('build.esbuild.json', JSON.stringify(result.metafile));
|
||||||
|
console.log(await esbuild.analyzeMetafile(result.metafile, {
|
||||||
|
verbose: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
esbuild.serve({ servedir: OUT_DIR, port: 8009 }, {}).then(({ host, port: esbuildPort }) => {
|
esbuild.serve({ servedir: OUT_DIR, port: 8009 }, {}).then(({ host, port: esbuildPort }) => {
|
||||||
const handler = async (req, res) => {
|
const handler = async (req, res) => {
|
||||||
const { url, method, headers } = req
|
const { url, method, headers } = req
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"@tldraw/utils": "workspace:*",
|
"@tldraw/utils": "workspace:*",
|
||||||
"@use-gesture/react": "^10.2.24",
|
"@use-gesture/react": "^10.2.24",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
|
"crc": "^4.3.2",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"is-plain-object": "^5.0.0",
|
"is-plain-object": "^5.0.0",
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@types/benchmark": "^2.1.2",
|
"@types/benchmark": "^2.1.2",
|
||||||
|
"@types/crc": "^3.8.0",
|
||||||
"@types/lodash.throttle": "^4.1.7",
|
"@types/lodash.throttle": "^4.1.7",
|
||||||
"@types/lodash.uniq": "^4.5.7",
|
"@types/lodash.uniq": "^4.5.7",
|
||||||
"@types/react-test-renderer": "^18.0.0",
|
"@types/react-test-renderer": "^18.0.0",
|
||||||
|
|
|
@ -16,7 +16,7 @@ import uniq from 'lodash.uniq'
|
||||||
import { App } from '../app/App'
|
import { App } from '../app/App'
|
||||||
import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../constants'
|
import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../constants'
|
||||||
import { isAnimated } from './is-gif-animated'
|
import { isAnimated } from './is-gif-animated'
|
||||||
import { getPngDataView, getPngPixelRatio } from './png'
|
import { findChunk, isPng, parsePhys } from './png'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const ACCEPTED_IMG_TYPE = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']
|
export const ACCEPTED_IMG_TYPE = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']
|
||||||
|
@ -47,6 +47,18 @@ export async function getVideoSizeFromSrc(src: string): Promise<{ w: number; h:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dataURL - The file as a string.
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* from https://stackoverflow.com/a/53817185
|
||||||
|
*/
|
||||||
|
export async function base64ToFile(dataURL: string) {
|
||||||
|
return fetch(dataURL).then(function (result) {
|
||||||
|
return result.arrayBuffer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the size of an image from its source.
|
* Get the size of an image from its source.
|
||||||
*
|
*
|
||||||
|
@ -56,33 +68,33 @@ export async function getVideoSizeFromSrc(src: string): Promise<{ w: number; h:
|
||||||
export async function getImageSizeFromSrc(dataURL: string): Promise<{ w: number; h: number }> {
|
export async function getImageSizeFromSrc(dataURL: string): Promise<{ w: number; h: number }> {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
|
|
||||||
// When the image loads, get its size using the image dimensions
|
|
||||||
// and, if possible, the pixel ratio derived from the image. Pngs
|
|
||||||
// have a pixel
|
|
||||||
img.onload = async () => {
|
img.onload = async () => {
|
||||||
let pixelRatio = 1
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await fetch(dataURL).then((d) => d.arrayBuffer())
|
const blob = await base64ToFile(dataURL)
|
||||||
const dataView = getPngDataView(buffer)
|
const view = new DataView(blob)
|
||||||
if (dataView) {
|
if (isPng(view, 0)) {
|
||||||
pixelRatio = getPngPixelRatio(dataView)
|
const physChunk = findChunk(view, 'pHYs')
|
||||||
|
if (physChunk) {
|
||||||
|
const physData = parsePhys(view, physChunk.dataOffset)
|
||||||
|
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
||||||
|
const pixelRatio = Math.round(physData.ppux / 2834.5)
|
||||||
|
resolve({ w: img.width / pixelRatio, h: img.height / pixelRatio })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({ w: img.width, h: img.height })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
resolve({ w: img.width, h: img.height })
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({ w: img.width / pixelRatio, h: img.height / pixelRatio })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img.onerror = (err) => {
|
img.onerror = (err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
reject(new Error('Could not get image size'))
|
reject(new Error('Could not get image size'))
|
||||||
}
|
}
|
||||||
|
|
||||||
img.crossOrigin = 'anonymous'
|
img.crossOrigin = 'anonymous'
|
||||||
|
|
||||||
img.src = dataURL
|
img.src = dataURL
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { TLGeoShape, TLNoteShape, TLShape } from '@tldraw/tlschema'
|
import { TLGeoShape, TLNoteShape, TLShape } from '@tldraw/tlschema'
|
||||||
import { debugFlags } from './debug-flags'
|
import { debugFlags } from './debug-flags'
|
||||||
|
import { setPhysChunk } from './png'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLCopyType = 'svg' | 'png' | 'jpeg' | 'json'
|
export type TLCopyType = 'svg' | 'png' | 'jpeg' | 'json'
|
||||||
|
@ -86,7 +87,12 @@ export async function getSvgAsImage(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return blob
|
if (!blob) return null
|
||||||
|
|
||||||
|
const view = new DataView(await blob.arrayBuffer())
|
||||||
|
return setPhysChunk(view, scale, {
|
||||||
|
type: 'image/' + type,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
|
@ -1,45 +1,119 @@
|
||||||
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
|
import crc32 from 'crc/crc32'
|
||||||
const PIXELS_PER_METER = 2834.5
|
|
||||||
|
|
||||||
/**
|
export function isPng(view: DataView, offset: number) {
|
||||||
* Returns a data view for a PNG image.
|
|
||||||
* @param arrayBuffer - The ArrayBuffer containing the PNG image.
|
|
||||||
* @returns A DataView for the PNG image, or null if the image is not a PNG.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function getPngDataView(arrayBuffer: ArrayBuffer): DataView | null {
|
|
||||||
const dataView = new DataView(arrayBuffer)
|
|
||||||
|
|
||||||
for (let i = 0; i < PNG_SIGNATURE.length; i++) {
|
|
||||||
if (dataView.getUint8(i) !== PNG_SIGNATURE[i]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataView
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the pixel ratio of a PNG data from its pHYs data.
|
|
||||||
*
|
|
||||||
* @param dataView - A dataview created from the image's array buffer.
|
|
||||||
* @returns The pixel ratio.
|
|
||||||
*/
|
|
||||||
export function getPngPixelRatio(dataView: DataView): number {
|
|
||||||
let offset = 8 // Start after PNG signature
|
|
||||||
|
|
||||||
while (offset < dataView.byteLength) {
|
|
||||||
if (
|
if (
|
||||||
dataView.getUint8(offset + 4) === 0x70 &&
|
view.getUint8(offset + 0) === 0x89 &&
|
||||||
dataView.getUint8(offset + 5) === 0x48 &&
|
view.getUint8(offset + 1) === 0x50 &&
|
||||||
dataView.getUint8(offset + 6) === 0x59 &&
|
view.getUint8(offset + 2) === 0x4e &&
|
||||||
dataView.getUint8(offset + 7) === 0x73
|
view.getUint8(offset + 3) === 0x47 &&
|
||||||
|
view.getUint8(offset + 4) === 0x0d &&
|
||||||
|
view.getUint8(offset + 5) === 0x0a &&
|
||||||
|
view.getUint8(offset + 6) === 0x1a &&
|
||||||
|
view.getUint8(offset + 7) === 0x0a
|
||||||
) {
|
) {
|
||||||
return Math.ceil(dataView.getUint32(offset + 8) / PIXELS_PER_METER)
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
offset = offset + 8 + dataView.getUint32(offset) + 4 // Move to next chunk (4 bytes for CRC)
|
}
|
||||||
}
|
|
||||||
|
function getChunkType(view: DataView, offset: number) {
|
||||||
return 1 // Didn't find a pixel ratio, so return default (1)
|
return [
|
||||||
|
String.fromCharCode(view.getUint8(offset)),
|
||||||
|
String.fromCharCode(view.getUint8(offset + 1)),
|
||||||
|
String.fromCharCode(view.getUint8(offset + 2)),
|
||||||
|
String.fromCharCode(view.getUint8(offset + 3)),
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function crc(arrayBuffer: ArrayBuffer) {
|
||||||
|
return crc32(arrayBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const LEN_SIZE = 4
|
||||||
|
const CRC_SIZE = 4
|
||||||
|
|
||||||
|
export function readChunks(view: DataView, offset = 0) {
|
||||||
|
const chunks: Record<string, { dataOffset: number; size: number; start: number }> = {}
|
||||||
|
if (!isPng(view, offset)) {
|
||||||
|
throw new Error('Not a PNG')
|
||||||
|
}
|
||||||
|
offset += 8
|
||||||
|
|
||||||
|
while (offset <= view.buffer.byteLength) {
|
||||||
|
const start = offset
|
||||||
|
const len = view.getInt32(offset)
|
||||||
|
offset += 4
|
||||||
|
const chunkType = getChunkType(view, offset)
|
||||||
|
|
||||||
|
if (chunkType === 'IDAT' && chunks[chunkType]) {
|
||||||
|
offset += len + LEN_SIZE + CRC_SIZE
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType === 'IEND') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks[chunkType] = {
|
||||||
|
start,
|
||||||
|
dataOffset: offset + 4,
|
||||||
|
size: len,
|
||||||
|
}
|
||||||
|
offset += len + LEN_SIZE + CRC_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parsePhys(view: DataView, offset: number) {
|
||||||
|
return {
|
||||||
|
ppux: view.getUint32(offset),
|
||||||
|
ppuy: view.getUint32(offset + 4),
|
||||||
|
unit: view.getUint8(offset + 4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findChunk(view: DataView, type: string) {
|
||||||
|
const chunks = readChunks(view)
|
||||||
|
return chunks[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPhysChunk(view: DataView, dpr = 1, options?: BlobPropertyBag) {
|
||||||
|
let offset = 46
|
||||||
|
let size = 0
|
||||||
|
const res1 = findChunk(view, 'pHYs')
|
||||||
|
if (res1) {
|
||||||
|
offset = res1.start
|
||||||
|
size = res1.size
|
||||||
|
}
|
||||||
|
|
||||||
|
const res2 = findChunk(view, 'IDAT')
|
||||||
|
if (res2) {
|
||||||
|
offset = res2.start
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const pHYsData = new ArrayBuffer(21)
|
||||||
|
const pHYsDataView = new DataView(pHYsData)
|
||||||
|
|
||||||
|
pHYsDataView.setUint32(0, 9)
|
||||||
|
|
||||||
|
pHYsDataView.setUint8(4, 'p'.charCodeAt(0))
|
||||||
|
pHYsDataView.setUint8(5, 'H'.charCodeAt(0))
|
||||||
|
pHYsDataView.setUint8(6, 'Y'.charCodeAt(0))
|
||||||
|
pHYsDataView.setUint8(7, 's'.charCodeAt(0))
|
||||||
|
|
||||||
|
const DPI_96 = 2835.5
|
||||||
|
|
||||||
|
pHYsDataView.setInt32(8, DPI_96 * dpr)
|
||||||
|
pHYsDataView.setInt32(12, DPI_96 * dpr)
|
||||||
|
pHYsDataView.setInt8(16, 1)
|
||||||
|
|
||||||
|
const crcBit = new Uint8Array(pHYsData.slice(4, 17))
|
||||||
|
pHYsDataView.setInt32(17, crc(crcBit))
|
||||||
|
|
||||||
|
const startBuf = view.buffer.slice(0, offset)
|
||||||
|
const endBuf = view.buffer.slice(offset + size)
|
||||||
|
|
||||||
|
return new Blob([startBuf, pHYsData, endBuf], options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function useCopyAs() {
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
fallbackWriteTextAsync(async () =>
|
fallbackWriteTextAsync(async () =>
|
||||||
getSvgAsString(await getExportSvgElement(app, ids, format))
|
getSvgAsString(await getExportSvgElement(app, ids))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,9 +100,9 @@ export function useCopyAs() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getExportSvgElement(app: App, ids: TLShapeId[], format: TLCopyType) {
|
async function getExportSvgElement(app: App, ids: TLShapeId[]) {
|
||||||
const svg = await app.getSvg(ids, {
|
const svg = await app.getSvg(ids, {
|
||||||
scale: format === 'svg' ? 1 : 2,
|
scale: 1,
|
||||||
background: app.instanceState.exportBackground,
|
background: app.instanceState.exportBackground,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -112,16 +112,16 @@ async function getExportSvgElement(app: App, ids: TLShapeId[], format: TLCopyTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getExportedSvgBlob(app: App, ids: TLShapeId[]) {
|
async function getExportedSvgBlob(app: App, ids: TLShapeId[]) {
|
||||||
return new Blob([getSvgAsString(await getExportSvgElement(app, ids, 'svg'))], {
|
return new Blob([getSvgAsString(await getExportSvgElement(app, ids))], {
|
||||||
type: 'text/plain',
|
type: 'text/plain',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getExportedImageBlob(app: App, ids: TLShapeId[], format: 'png' | 'jpeg') {
|
async function getExportedImageBlob(app: App, ids: TLShapeId[], format: 'png' | 'jpeg') {
|
||||||
return await getSvgAsImage(await getExportSvgElement(app, ids, format), {
|
return await getSvgAsImage(await getExportSvgElement(app, ids), {
|
||||||
type: format,
|
type: format,
|
||||||
quality: 1,
|
quality: 1,
|
||||||
scale: 1,
|
scale: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function useExportAs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const svg = await app.getSvg(ids, {
|
const svg = await app.getSvg(ids, {
|
||||||
scale: format === 'svg' ? 1 : 2,
|
scale: 1,
|
||||||
background: app.instanceState.exportBackground,
|
background: app.instanceState.exportBackground,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ export function useExportAs() {
|
||||||
const image = await getSvgAsImage(svg, {
|
const image = await getSvgAsImage(svg, {
|
||||||
type: format,
|
type: format,
|
||||||
quality: 1,
|
quality: 1,
|
||||||
scale: 1,
|
scale: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
|
|
|
@ -4333,6 +4333,7 @@ __metadata:
|
||||||
"@tldraw/tlvalidate": "workspace:*"
|
"@tldraw/tlvalidate": "workspace:*"
|
||||||
"@tldraw/utils": "workspace:*"
|
"@tldraw/utils": "workspace:*"
|
||||||
"@types/benchmark": ^2.1.2
|
"@types/benchmark": ^2.1.2
|
||||||
|
"@types/crc": ^3.8.0
|
||||||
"@types/lodash.throttle": ^4.1.7
|
"@types/lodash.throttle": ^4.1.7
|
||||||
"@types/lodash.uniq": ^4.5.7
|
"@types/lodash.uniq": ^4.5.7
|
||||||
"@types/react-test-renderer": ^18.0.0
|
"@types/react-test-renderer": ^18.0.0
|
||||||
|
@ -4340,6 +4341,7 @@ __metadata:
|
||||||
"@use-gesture/react": ^10.2.24
|
"@use-gesture/react": ^10.2.24
|
||||||
benchmark: ^2.1.4
|
benchmark: ^2.1.4
|
||||||
classnames: ^2.3.2
|
classnames: ^2.3.2
|
||||||
|
crc: ^4.3.2
|
||||||
escape-string-regexp: ^5.0.0
|
escape-string-regexp: ^5.0.0
|
||||||
eventemitter3: ^4.0.7
|
eventemitter3: ^4.0.7
|
||||||
fake-indexeddb: ^4.0.0
|
fake-indexeddb: ^4.0.0
|
||||||
|
@ -4783,6 +4785,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/crc@npm:^3.8.0":
|
||||||
|
version: 3.8.0
|
||||||
|
resolution: "@types/crc@npm:3.8.0"
|
||||||
|
dependencies:
|
||||||
|
"@types/node": "*"
|
||||||
|
checksum: bcf040c3026ec812f4ac87423f42e7ef869483e8b87967184bd2bd26c9a9c358fce41217383adf83a36b53e821a608e784f6a973fd02192b540f55a5395c9180
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/debug@npm:^4.0.0":
|
"@types/debug@npm:^4.0.0":
|
||||||
version: 4.1.7
|
version: 4.1.7
|
||||||
resolution: "@types/debug@npm:4.1.7"
|
resolution: "@types/debug@npm:4.1.7"
|
||||||
|
@ -7422,6 +7433,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"crc@npm:^4.3.2":
|
||||||
|
version: 4.3.2
|
||||||
|
resolution: "crc@npm:4.3.2"
|
||||||
|
peerDependencies:
|
||||||
|
buffer: ">=6.0.3"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
buffer:
|
||||||
|
optional: true
|
||||||
|
checksum: 8231cc25331727083ffd22da3575110fc49b4dc8725de973bd43261d4426aba134ed3a75cc247f7c5e97a6e171f87dffc3325b82890e86d032de2e6bcef09c32
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"create-require@npm:^1.1.0":
|
"create-require@npm:^1.1.0":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "create-require@npm:1.1.1"
|
resolution: "create-require@npm:1.1.1"
|
||||||
|
|
Loading…
Reference in a new issue