From 09d51708d36fc773c69a4644fb03ca097df2613d Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sun, 24 Sep 2023 17:23:02 +0100 Subject: [PATCH] WIP: Add infra to publish as platform-dependent npm packages --- .circleci/config.yml | 24 ++++ .github/workflows/ci.yml | 20 +++ .gitignore | 3 +- .prebuildrc | 2 +- binding.gyp | 82 ++++++++---- install/can-compile.js | 14 -- install/check.js | 18 +++ install/dll-copy.js | 40 ------ install/libvips.js | 222 ------------------------------- lib/agent.js | 44 ------ lib/constructor.js | 1 - lib/index.d.ts | 6 - lib/libvips.js | 117 ++++++++-------- lib/platform.js | 30 ----- lib/sharp.js | 71 ++++++---- lib/utility.js | 28 +--- npm/darwin-arm64/package.json | 42 ++++++ npm/darwin-x64/package.json | 39 ++++++ npm/from-github-release.js | 51 +++++++ npm/from-local-build.js | 26 ++++ npm/linux-arm/package.json | 45 +++++++ npm/linux-arm64/package.json | 45 +++++++ npm/linux-x64/package.json | 45 +++++++ npm/linuxmusl-arm64/package.json | 45 +++++++ npm/linuxmusl-x64/package.json | 45 +++++++ npm/package.json | 16 +++ npm/win32-ia32/package.json | 41 ++++++ npm/win32-x64/package.json | 41 ++++++ package.json | 62 ++++----- test/types/sharp.test-d.ts | 2 - test/unit/agent.js | 52 -------- test/unit/libvips.js | 87 +++++------- test/unit/platform.js | 91 ------------- test/unit/util.js | 8 -- 34 files changed, 780 insertions(+), 725 deletions(-) delete mode 100644 install/can-compile.js create mode 100644 install/check.js delete mode 100644 install/dll-copy.js delete mode 100644 install/libvips.js delete mode 100644 lib/agent.js delete mode 100644 lib/platform.js create mode 100644 npm/darwin-arm64/package.json create mode 100644 npm/darwin-x64/package.json create mode 100644 npm/from-github-release.js create mode 100644 npm/from-local-build.js create mode 100644 npm/linux-arm/package.json create mode 100644 npm/linux-arm64/package.json create mode 100644 npm/linux-x64/package.json create mode 100644 npm/linuxmusl-arm64/package.json create mode 100644 npm/linuxmusl-x64/package.json create mode 100644 npm/package.json create mode 100644 npm/win32-ia32/package.json create mode 100644 npm/win32-x64/package.json delete mode 100644 test/unit/agent.js delete mode 100644 test/unit/platform.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 9b209eabb..fbec44bb5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,6 +36,12 @@ jobs: sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs" - run: sudo docker exec sharp sh -c "npm install --build-from-source" - run: sudo docker exec sharp sh -c "npm test" + - run: | + sudo docker exec sharp sh -c "npm run package-from-local-build" + sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linux-arm64=file:./npm/linux-arm64\"" + sudo docker exec sharp sh -c "npm run clean" + sudo docker exec sharp sh -c "npm install --ignore-scripts" + sudo docker exec sharp sh -c "npm test" - run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --upload=$prebuild_upload\" || true" linux-arm64-glibc-node-20: resource_class: arm.medium @@ -54,6 +60,12 @@ jobs: sudo docker cp . sharp:/mnt/sharp/. - run: sudo docker exec sharp sh -c "npm install --build-from-source" - run: sudo docker exec sharp sh -c "npm test" + - run: | + sudo docker exec sharp sh -c "npm run package-from-local-build" + sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linux-arm64=file:./npm/linux-arm64\"" + sudo docker exec sharp sh -c "npm run clean" + sudo docker exec sharp sh -c "npm install --ignore-scripts" + sudo docker exec sharp sh -c "npm test" linux-arm64-musl-node-18: resource_class: arm.medium machine: @@ -65,6 +77,12 @@ jobs: sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache" - run: sudo docker exec sharp sh -c "npm install --build-from-source" - run: sudo docker exec sharp sh -c "npm test" + - run: | + sudo docker exec sharp sh -c "npm run package-from-local-build" + sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\"" + sudo docker exec sharp sh -c "npm run clean" + sudo docker exec sharp sh -c "npm install --ignore-scripts" + sudo docker exec sharp sh -c "npm test" - run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --upload=$prebuild_upload\" || true" linux-arm64-musl-node-20: resource_class: arm.medium @@ -79,3 +97,9 @@ jobs: sudo docker cp . sharp:/mnt/sharp/. - run: sudo docker exec sharp sh -c "npm install --build-from-source" - run: sudo docker exec sharp sh -c "npm test" + - run: | + sudo docker exec sharp sh -c "npm run package-from-local-build" + sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\"" + sudo docker exec sharp sh -c "npm run clean" + sudo docker exec sharp sh -c "npm install --ignore-scripts" + sudo docker exec sharp sh -c "npm test" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b320fc053..31f493b0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: - os: macos-11 nodejs_arch: x64 nodejs_version: "^18.17.0" + nodejs_version_major: 18 platform: darwin-x64 prebuild: true - os: macos-11 @@ -105,6 +106,13 @@ jobs: run: npm install --build-from-source - name: Test run: npm test + - name: Test packaging + run: | + npm run package-from-local-build + npm pkg set "optionalDependencies.@sharpen/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" + npm run clean + npm install --ignore-scripts + npm test - name: Prebuild if: matrix.prebuild && startsWith(github.ref, 'refs/tags/') env: @@ -133,6 +141,11 @@ jobs: chown root.root . npm install --build-from-source npx mocha --no-config --spec=test/unit/io.js + npm run package-from-local-build + npm pkg set "optionalDependencies.@sharpen/sharp-linux-arm=file:./npm/linux-arm" + npm run clean + npm install --ignore-scripts + npx mocha --no-config --spec=test/unit/io.js [[ -n $prebuild_upload ]] && npx prebuild || true macstadium-runner: permissions: @@ -167,6 +180,13 @@ jobs: run: npm install --build-from-source - name: Test run: npm test + - name: Test packaging + run: | + npm run package-from-local-build + npm pkg set "optionalDependencies.@sharpen/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" + npm run clean + npm install --ignore-scripts + npm test - name: Prebuild if: matrix.prebuild && startsWith(github.ref, 'refs/tags/') env: diff --git a/.gitignore b/.gitignore index 769e9e38e..3a85452db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build node_modules /coverage +npm/*/* +!npm/*/package.json test/bench/node_modules test/fixtures/output* test/fixtures/vips-properties.xml @@ -9,7 +11,6 @@ test/saliency/report.json test/saliency/Image* test/saliency/[Uu]serData* !test/saliency/userData.js -vendor .gitattributes .DS_Store .nyc_output diff --git a/.prebuildrc b/.prebuildrc index c01a5a496..0d95c2ed2 100644 --- a/.prebuildrc +++ b/.prebuildrc @@ -1,6 +1,6 @@ { "runtime": "napi", - "include-regex": "(sharp-.+\\.node|libvips-cpp\\.dll)", + "include-regex": "(sharp-.+\\.node|libvips-.+\\.dll|libglib-.+\\.dll|libgobject-.+\\.dll)", "prerelease": true, "strip": true } diff --git a/binding.gyp b/binding.gyp index fd05b45eb..083908647 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,11 +1,16 @@ +# Copyright 2013 Lovell Fuller and others. +# SPDX-License-Identifier: Apache-2.0 + { 'variables': { 'vips_version': ' { + log(msg); + log('Building from source via node-gyp'); + process.exitCode = 1; +}; + +if (process.env.npm_config_build_from_source) { + buildFromSource('Detected --build-from-source flag'); +} else if (useGlobalLibvips()) { + buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`); +} diff --git a/install/dll-copy.js b/install/dll-copy.js deleted file mode 100644 index 58c80a396..000000000 --- a/install/dll-copy.js +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -const libvips = require('../lib/libvips'); -const platform = require('../lib/platform'); - -const minimumLibvipsVersion = libvips.minimumLibvipsVersion; - -const platformAndArch = platform(); - -if (platformAndArch.startsWith('win32')) { - const buildReleaseDir = path.join(__dirname, '..', 'build', 'Release'); - libvips.log(`Creating ${buildReleaseDir}`); - try { - libvips.mkdirSync(buildReleaseDir); - } catch (err) {} - const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch, 'lib'); - libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`); - try { - fs - .readdirSync(vendorLibDir) - .filter(function (filename) { - return /\.dll$/.test(filename); - }) - .forEach(function (filename) { - fs.copyFileSync( - path.join(vendorLibDir, filename), - path.join(buildReleaseDir, filename) - ); - }); - } catch (err) { - libvips.log(err); - process.exit(1); - } -} diff --git a/install/libvips.js b/install/libvips.js deleted file mode 100644 index 339f22284..000000000 --- a/install/libvips.js +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const stream = require('stream'); -const zlib = require('zlib'); -const { createHash } = require('crypto'); - -const detectLibc = require('detect-libc'); -const semverCoerce = require('semver/functions/coerce'); -const semverLessThan = require('semver/functions/lt'); -const semverSatisfies = require('semver/functions/satisfies'); -const simpleGet = require('simple-get'); -const tarFs = require('tar-fs'); - -const agent = require('../lib/agent'); -const libvips = require('../lib/libvips'); -const platform = require('../lib/platform'); - -const minimumGlibcVersionByArch = { - arm: '2.28', - arm64: '2.17', - x64: '2.17' -}; - -const hasSharpPrebuild = [ - 'darwin-x64', - 'darwin-arm64', - 'linux-arm64', - 'linux-x64', - 'linuxmusl-x64', - 'linuxmusl-arm64', - 'win32-ia32', - 'win32-x64' -]; - -const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips; -const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || ''; -const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download'; -const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`; -const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE); - -const fail = function (err) { - libvips.log(err); - if (err.code === 'EACCES') { - libvips.log('Are you trying to install as a root or sudo user?'); - libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag'); - libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in'); - } - libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies'); - process.exit(1); -}; - -const handleError = function (err) { - if (installationForced) { - libvips.log(`Installation warning: ${err.message}`); - } else { - throw err; - } -}; - -const verifyIntegrity = function (platformAndArch) { - const expected = libvips.integrity(platformAndArch); - if (installationForced || !expected) { - libvips.log(`Integrity check skipped for ${platformAndArch}`); - return new stream.PassThrough(); - } - const hash = createHash('sha512'); - return new stream.Transform({ - transform: function (chunk, _encoding, done) { - hash.update(chunk); - done(null, chunk); - }, - flush: function (done) { - const digest = `sha512-${hash.digest('base64')}`; - if (expected !== digest) { - try { - libvips.removeVendoredLibvips(); - } catch (err) { - libvips.log(err.message); - } - libvips.log(`Integrity expected: ${expected}`); - libvips.log(`Integrity received: ${digest}`); - done(new Error(`Integrity check failed for ${platformAndArch}`)); - } else { - libvips.log(`Integrity check passed for ${platformAndArch}`); - done(); - } - } - }); -}; - -const extractTarball = function (tarPath, platformAndArch) { - const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch); - libvips.mkdirSync(versionedVendorPath); - - const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source; - const ignore = function (name) { - return ignoreVendorInclude && name.includes('include/'); - }; - - stream.pipeline( - fs.createReadStream(tarPath), - verifyIntegrity(platformAndArch), - new zlib.BrotliDecompress(), - tarFs.extract(versionedVendorPath, { ignore }), - function (err) { - if (err) { - if (/unexpected end of file/.test(err.message)) { - fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`)); - } - fail(err); - } - } - ); -}; - -try { - const useGlobalLibvips = libvips.useGlobalLibvips(); - - if (useGlobalLibvips) { - const globalLibvipsVersion = libvips.globalLibvipsVersion(); - libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`); - libvips.log('Building from source via node-gyp'); - process.exit(1); - } else if (libvips.hasVendoredLibvips()) { - libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`); - } else { - // Is this arch/platform supported? - const arch = process.env.npm_config_arch || process.arch; - const platformAndArch = platform(); - if (arch === 'ia32' && !platformAndArch.startsWith('win32')) { - throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); - } - if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') { - throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`); - } - // Linux libc version check - const libcVersionRaw = detectLibc.versionSync(); - if (libcVersionRaw) { - const libcFamily = detectLibc.familySync(); - const libcVersion = semverCoerce(libcVersionRaw).version; - if (libcFamily === detectLibc.GLIBC && minimumGlibcVersionByArch[arch]) { - if (semverLessThan(libcVersion, semverCoerce(minimumGlibcVersionByArch[arch]).version)) { - handleError(new Error(`Use with glibc ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`)); - } - } - if (libcFamily === detectLibc.MUSL) { - if (semverLessThan(libcVersion, '1.1.24')) { - handleError(new Error(`Use with musl ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`)); - } - } - } - // Node.js minimum version check - const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node; - if (!semverSatisfies(process.versions.node, supportedNodeVersion)) { - handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`)); - } - // Download to per-process temporary file - const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br'; - const tarPathCache = path.join(libvips.cachePath(), tarFilename); - if (fs.existsSync(tarPathCache)) { - libvips.log(`Using cached ${tarPathCache}`); - extractTarball(tarPathCache, platformAndArch); - } else if (localLibvipsDir) { - // If localLibvipsDir is given try to use binaries from local directory - const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename); - libvips.log(`Using local libvips from ${tarPathLocal}`); - extractTarball(tarPathLocal, platformAndArch); - } else { - const url = distBaseUrl + tarFilename; - libvips.log(`Downloading ${url}`); - simpleGet({ url: url, agent: agent(libvips.log) }, function (err, response) { - if (err) { - fail(err); - } else if (response.statusCode === 404) { - fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`)); - } else if (response.statusCode !== 200) { - fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`)); - } else { - const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`); - const tmpFileStream = fs.createWriteStream(tarPathTemp); - response - .on('error', function (err) { - tmpFileStream.destroy(err); - }) - .on('close', function () { - if (!response.complete) { - tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)')); - } - }) - .pipe(tmpFileStream); - tmpFileStream - .on('error', function (err) { - // Clean up temporary file - try { - fs.unlinkSync(tarPathTemp); - } catch (e) {} - fail(err); - }) - .on('close', function () { - try { - // Attempt to rename - fs.renameSync(tarPathTemp, tarPathCache); - } catch (err) { - // Fall back to copy and unlink - fs.copyFileSync(tarPathTemp, tarPathCache); - fs.unlinkSync(tarPathTemp); - } - extractTarball(tarPathCache, platformAndArch); - }); - } - }); - } - } -} catch (err) { - fail(err); -} diff --git a/lib/agent.js b/lib/agent.js deleted file mode 100644 index 74b6f47e5..000000000 --- a/lib/agent.js +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const url = require('url'); -const tunnelAgent = require('tunnel-agent'); - -const is = require('./is'); - -const proxies = [ - 'HTTPS_PROXY', - 'https_proxy', - 'HTTP_PROXY', - 'http_proxy', - 'npm_config_https_proxy', - 'npm_config_proxy' -]; - -function env (key) { - return process.env[key]; -} - -module.exports = function (log) { - try { - const proxy = new url.URL(proxies.map(env).find(is.string)); - const tunnel = proxy.protocol === 'https:' - ? tunnelAgent.httpsOverHttps - : tunnelAgent.httpsOverHttp; - const proxyAuth = proxy.username && proxy.password - ? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}` - : null; - log(`Via proxy ${proxy.protocol}//${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`); - return tunnel({ - proxy: { - port: Number(proxy.port), - host: proxy.hostname, - proxyAuth - } - }); - } catch (err) { - return null; - } -}; diff --git a/lib/constructor.js b/lib/constructor.js index 1fbd6e34b..bbf2534eb 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -7,7 +7,6 @@ const util = require('util'); const stream = require('stream'); const is = require('./is'); -require('./libvips').hasVendoredLibvips(); require('./sharp'); // Use NODE_DEBUG=sharp to enable libvips warnings diff --git a/lib/index.d.ts b/lib/index.d.ts index cf504a99c..2db30a803 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -91,12 +91,6 @@ declare namespace sharp { zlib?: string | undefined; }; - /** An Object containing the platform and architecture of the current and installed vendored binaries. */ - const vendor: { - current: string; - installed: string[]; - }; - /** An Object containing the available interpolators and their proper values */ const interpolators: Interpolators; diff --git a/lib/libvips.js b/lib/libvips.js index ca001d953..583c17b40 100644 --- a/lib/libvips.js +++ b/lib/libvips.js @@ -3,61 +3,74 @@ 'use strict'; -const fs = require('fs'); -const os = require('os'); -const path = require('path'); const spawnSync = require('child_process').spawnSync; const semverCoerce = require('semver/functions/coerce'); const semverGreaterThanOrEqualTo = require('semver/functions/gte'); +const detectLibc = require('detect-libc'); -const platform = require('./platform'); -const { config } = require('../package.json'); +const { engines } = require('../package.json'); -const env = process.env; -const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */ - config.libvips; +const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */ + engines.libvips; const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version; +const prebuiltPlatforms = [ + 'darwin-arm64', 'darwin-x64', + 'linux-arm', 'linux-arm64', 'linux-x64', + 'linuxmusl-arm64', 'linuxmusl-x64', + 'win32-ia32', 'win32-x64' +]; + const spawnSyncOptions = { encoding: 'utf8', shell: true }; -const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform()); - -const mkdirSync = function (dirPath) { - try { - fs.mkdirSync(dirPath, { recursive: true }); - } catch (err) { - /* istanbul ignore next */ - if (err.code !== 'EEXIST') { - throw err; - } +const log = (item) => { + if (item instanceof Error) { + console.error(`sharp: Installation error: ${item.message}`); + } else { + console.log(`sharp: ${item}`); } }; -const cachePath = function () { - const npmCachePath = env.npm_config_cache || /* istanbul ignore next */ - (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm')); - mkdirSync(npmCachePath); - const libvipsCachePath = path.join(npmCachePath, '_libvips'); - mkdirSync(libvipsCachePath); - return libvipsCachePath; +/* istanbul ignore next */ +const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : ''; + +const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`; + +/* istanbul ignore next */ +const buildPlatformArch = () => { + /* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */ + const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env; + return `${npm_config_platform || process.platform}${npm_config_libc || runtimeLibc()}-${npm_config_arch || process.arch}`; }; -const integrity = function (platformAndArch) { - return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch]; +const buildSharpLibvipsIncludeDir = () => { + try { + return require('@sharpen/sharp-libvips-dev/include'); + } catch {} + /* istanbul ignore next */ + return ''; }; -const log = function (item) { - if (item instanceof Error) { - console.error(`sharp: Installation error: ${item.message}`); - } else { - console.log(`sharp: ${item}`); - } +const buildSharpLibvipsCPlusPlusDir = () => { + try { + return require('@sharpen/sharp-libvips-dev/cplusplus'); + } catch {} + /* istanbul ignore next */ + return ''; }; -const isRosetta = function () { +const buildSharpLibvipsLibDir = () => { + try { + return require(`@sharpen/sharp-libvips-${buildPlatformArch()}/lib`); + } catch {} + /* istanbul ignore next */ + return ''; +}; + +const isRosetta = () => { /* istanbul ignore next */ if (process.platform === 'darwin' && process.arch === 'x64') { const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout; @@ -66,12 +79,12 @@ const isRosetta = function () { return false; }; -const globalLibvipsVersion = function () { +const globalLibvipsVersion = () => { if (process.platform !== 'win32') { const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', { ...spawnSyncOptions, env: { - ...env, + ...process.env, PKG_CONFIG_PATH: pkgConfigPath() } }).stdout; @@ -82,17 +95,8 @@ const globalLibvipsVersion = function () { } }; -const hasVendoredLibvips = function () { - return fs.existsSync(vendorPath); -}; - -/* istanbul ignore next */ -const removeVendoredLibvips = function () { - fs.rmSync(vendorPath, { recursive: true, maxRetries: 3, force: true }); -}; - /* istanbul ignore next */ -const pkgConfigPath = function () { +const pkgConfigPath = () => { if (process.platform !== 'win32') { const brewPkgConfigPath = spawnSync( 'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2', @@ -100,7 +104,7 @@ const pkgConfigPath = function () { ).stdout || ''; return [ brewPkgConfigPath.trim(), - env.PKG_CONFIG_PATH, + process.env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig', '/usr/local/libdata/pkgconfig', @@ -111,8 +115,9 @@ const pkgConfigPath = function () { } }; -const useGlobalLibvips = function () { - if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { +const useGlobalLibvips = () => { + if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { + log('Detected SHARP_IGNORE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips'); return false; } /* istanbul ignore next */ @@ -127,14 +132,14 @@ const useGlobalLibvips = function () { module.exports = { minimumLibvipsVersion, - minimumLibvipsVersionLabelled, - cachePath, - integrity, + prebuiltPlatforms, + buildPlatformArch, + buildSharpLibvipsIncludeDir, + buildSharpLibvipsCPlusPlusDir, + buildSharpLibvipsLibDir, + runtimePlatformArch, log, globalLibvipsVersion, - hasVendoredLibvips, - removeVendoredLibvips, pkgConfigPath, - useGlobalLibvips, - mkdirSync + useGlobalLibvips }; diff --git a/lib/platform.js b/lib/platform.js deleted file mode 100644 index 71454e3ea..000000000 --- a/lib/platform.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const detectLibc = require('detect-libc'); - -const env = process.env; - -module.exports = function () { - const arch = env.npm_config_arch || process.arch; - const platform = env.npm_config_platform || process.platform; - const libc = process.env.npm_config_libc || - /* istanbul ignore next */ - (detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : ''); - const libcId = platform !== 'linux' || libc === detectLibc.GLIBC ? '' : libc; - - const platformId = [`${platform}${libcId}`]; - - if (arch === 'arm') { - const fallback = process.versions.electron ? '7' : '6'; - platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || fallback}`); - } else if (arch === 'arm64') { - platformId.push(`arm64v${env.npm_config_arm_version || '8'}`); - } else { - platformId.push(arch); - } - - return platformId.join('-'); -}; diff --git a/lib/sharp.js b/lib/sharp.js index a41e83d0b..6b72c931b 100644 --- a/lib/sharp.js +++ b/lib/sharp.js @@ -3,36 +3,55 @@ 'use strict'; -const platformAndArch = require('./platform')(); +// Inspects the runtime environment and exports the relevant sharp.node binary + +const { familySync, versionSync } = require('detect-libc'); + +const { runtimePlatformArch, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips'); +const runtimePlatform = runtimePlatformArch(); /* istanbul ignore next */ try { - module.exports = require(`../build/Release/sharp-${platformAndArch}.node`); -} catch (err) { - // Bail early if bindings aren't available - const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:']; - if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) { - help.push('- Update Homebrew: "brew update && brew upgrade vips"'); - } else { - const [platform, arch] = platformAndArch.split('-'); - if (platform === 'linux' && /Module did not self-register/.test(err.message)) { - help.push('- Using worker threads? See https://sharp.pixelplumbing.com/install#worker-threads'); + // Check for local build + module.exports = require(`../build/Release/sharp-${runtimePlatform}.node`); +} catch (errLocal) { + try { + // Check for runtime package + module.exports = require(`@sharpen/sharp-${runtimePlatform}/sharp.node`); + } catch (errPackage) { + const help = ['Could not load the "sharp" module at runtime']; + if (errLocal.code !== 'MODULE_NOT_FOUND') { + help.push(errLocal.message); } - help.push( - '- Install with verbose logging and look for errors: "npm install --ignore-scripts=false --foreground-scripts --verbose sharp"', - `- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"` - ); - } - help.push( - '- Consult the installation documentation: https://sharp.pixelplumbing.com/install' - ); - // Check loaded - if (process.platform === 'win32' || /symbol/.test(err.message)) { - const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i)); - if (loadedModule) { - const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/); - help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`); + if (errPackage.code !== 'MODULE_NOT_FOUND') { + help.push(errPackage.message); + } + help.push('Possible solutions:'); + // Common error messages + if (prebuiltPlatforms.includes(runtimePlatform)) { + help.push(`- Add an explicit dependency for the runtime platform: "npm install --force @sharpen/sharp-${runtimePlatform}"`); + } else { + help.push(`- The ${runtimePlatform} platform requires manual installation of libvips >= ${minimumLibvipsVersion}`); + } + if (runtimePlatform.startsWith('linux') && /symbol not found/i.test(errPackage)) { + try { + const { engines } = require(`@sharpen/sharp-libvips-${runtimePlatform}/package`); + const libcFound = `${familySync()} ${versionSync()}`; + const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`; + help.push(`- Update your OS: found ${libcFound}, requires ${libcRequires}`); + } catch (errEngines) {} + } + if (runtimePlatform.startsWith('darwin') && /Incompatible library version/.test(errLocal.message)) { + help.push('- Update Homebrew: "brew update && brew upgrade vips"'); + } + // Link to installation docs + if (runtimePlatform.startsWith('linux') && /Module did not self-register/.test(errLocal.message + errPackage.message)) { + help.push('- Using worker threads on Linux? See https://sharp.pixelplumbing.com/install#worker-threads'); + } else if (runtimePlatform.startsWith('win32') && /The specified procedure could not be found/.test(errPackage.message)) { + help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows'); + } else { + help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install'); } + throw new Error(help.join('\n')); } - throw new Error(help.join('\n')); } diff --git a/lib/utility.js b/lib/utility.js index 8b1a42e54..548ecd901 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -3,13 +3,11 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); const events = require('events'); const detectLibc = require('detect-libc'); const is = require('./is'); -const platformAndArch = require('./platform')(); +const { runtimePlatformArch } = require('./libvips'); const sharp = require('./sharp'); /** @@ -55,25 +53,14 @@ let versions = { vips: sharp.libvipsVersion() }; try { - versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`); -} catch (_err) { /* ignore */ } + versions = require(`@sharpen/sharp-${runtimePlatformArch()}/versions`); +} catch (_) { + try { + versions = require(`@sharpen/sharp-libvips-${runtimePlatformArch()}/versions`); + } catch (_) {} +} versions.sharp = require('../package.json').version; -/** - * An Object containing the platform and architecture - * of the current and installed vendored binaries. - * @member - * @example - * console.log(sharp.vendor); - */ -const vendor = { - current: platformAndArch, - installed: [] -}; -try { - vendor.installed = fs.readdirSync(path.join(__dirname, `../vendor/${versions.vips}`)); -} catch (_err) { /* ignore */ } - /** * Gets or, when options are provided, sets the limits of _libvips'_ operation cache. * Existing entries in the cache will be trimmed after any change in limits. @@ -280,7 +267,6 @@ module.exports = function (Sharp) { Sharp.format = format; Sharp.interpolators = interpolators; Sharp.versions = versions; - Sharp.vendor = vendor; Sharp.queue = queue; Sharp.block = block; Sharp.unblock = unblock; diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json new file mode 100644 index 000000000..353720a88 --- /dev/null +++ b/npm/darwin-arm64/package.json @@ -0,0 +1,42 @@ +{ + "name": "@sharpen/sharp-darwin-arm64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with macOS ARM64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/darwin-arm64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-darwin-arm64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-darwin-arm64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "glibc": ">=2.26" + }, + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ] +} diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json new file mode 100644 index 000000000..89e4adb59 --- /dev/null +++ b/npm/darwin-x64/package.json @@ -0,0 +1,39 @@ +{ + "name": "@sharpen/sharp-darwin-x64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with macOS x64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/darwin-x64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-darwin-x64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "exports": { + "./sharp.node": "./lib/sharp-darwin-x64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "glibc": ">=2.26" + }, + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ] +} diff --git a/npm/from-github-release.js b/npm/from-github-release.js new file mode 100644 index 000000000..da71ce44d --- /dev/null +++ b/npm/from-github-release.js @@ -0,0 +1,51 @@ +// Copyright 2013 Lovell Fuller and others. +// SPDX-License-Identifier: Apache-2.0 + +'use strict'; + +// Populate contents of all packages with the current GitHub release + +const { writeFile, copyFile } = require('node:fs/promises'); +const path = require('node:path'); +const { Readable } = require('node:stream'); +const { pipeline } = require('node:stream/promises'); +const { createGunzip } = require('node:zlib'); +const { extract } = require('tar-fs'); + +const { workspaces } = require('./package.json'); +const { version } = require('../package.json'); + +const mapTarballEntry = (header) => { + header.name = path.basename(header.name); + return header; +}; + +workspaces.map(async platform => { + const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v7-${platform}.tar.gz`; + const dir = path.join(__dirname, platform); + const response = await fetch(url); + if (!response.ok) { + console.log(`Skipping ${platform}: ${response.statusText}`); + return; + } + // Extract prebuild tarball + await pipeline( + Readable.fromWeb(response.body), + createGunzip(), + extract(path.join(dir, 'lib'), { map: mapTarballEntry }) + ); + // Generate README + const { name, description } = require(`./${platform}/package.json`); + await writeFile(path.join(dir, 'README.md'), `# ${name}\n${description}`); + // Copy Apache-2.0 LICENSE + await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE')); + // Copy Windows-specific files + if (platform.startsWith('win32-')) { + const sharpLibvipsDir = path.join(require(`@sharpen/sharp-libvips-${platform}/lib`), '..'); + await Promise.all( + ['versions.json', 'THIRD-PARTY-NOTICES.md'].map( + filename => copyFile(path.join(sharpLibvipsDir, filename), path.join(dir, filename)) + ) + ); + } +}); diff --git a/npm/from-local-build.js b/npm/from-local-build.js new file mode 100644 index 000000000..c53713e66 --- /dev/null +++ b/npm/from-local-build.js @@ -0,0 +1,26 @@ +// Copyright 2013 Lovell Fuller and others. +// SPDX-License-Identifier: Apache-2.0 + +'use strict'; + +// Populate contents of a single npm/sharpen-sharp- package +// with the local/CI build directory for local/CI prebuild testing + +const fs = require('node:fs'); +const path = require('node:path'); + +const { buildPlatformArch } = require('../lib/libvips'); +const platform = buildPlatformArch(); +const dest = path.join(__dirname, platform); + +// Use same config as prebuild to copy binary files +const release = path.join(__dirname, '..', 'build', 'Release'); +const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8')); +const include = new RegExp(prebuildrc['include-regex'], 'i'); +fs.cpSync(release, path.join(dest, 'lib'), { + recursive: true, + filter: (file) => { + const name = path.basename(file); + return name === 'Release' || include.test(name); + } +}); diff --git a/npm/linux-arm/package.json b/npm/linux-arm/package.json new file mode 100644 index 000000000..90a0c8093 --- /dev/null +++ b/npm/linux-arm/package.json @@ -0,0 +1,45 @@ +{ + "name": "@sharpen/sharp-linux-arm", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linux-arm" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-linux-arm": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-linux-arm.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "glibc": ">=2.28" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ], + "cpu": [ + "arm" + ] +} diff --git a/npm/linux-arm64/package.json b/npm/linux-arm64/package.json new file mode 100644 index 000000000..43b30070b --- /dev/null +++ b/npm/linux-arm64/package.json @@ -0,0 +1,45 @@ +{ + "name": "@sharpen/sharp-linux-arm64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Linux (glibc) ARM64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linux-arm64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-linux-arm64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-linux-arm64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "glibc": ">=2.26" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ], + "cpu": [ + "arm64" + ] +} diff --git a/npm/linux-x64/package.json b/npm/linux-x64/package.json new file mode 100644 index 000000000..27e456ce1 --- /dev/null +++ b/npm/linux-x64/package.json @@ -0,0 +1,45 @@ +{ + "name": "@sharpen/sharp-linux-x64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Linux (glibc) x64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linux-x64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-linux-x64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-linux-x64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "glibc": ">=2.26" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ], + "cpu": [ + "x64" + ] +} diff --git a/npm/linuxmusl-arm64/package.json b/npm/linuxmusl-arm64/package.json new file mode 100644 index 000000000..4c07688a1 --- /dev/null +++ b/npm/linuxmusl-arm64/package.json @@ -0,0 +1,45 @@ +{ + "name": "@sharpen/sharp-linuxmusl-arm64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Linux (musl) ARM64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linuxmusl-arm64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-linuxmusl-arm64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-linuxmusl-arm64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "musl": ">=1.2.2" + }, + "os": [ + "linux" + ], + "libc": [ + "musl" + ], + "cpu": [ + "arm64" + ] +} diff --git a/npm/linuxmusl-x64/package.json b/npm/linuxmusl-x64/package.json new file mode 100644 index 000000000..4433bcfa9 --- /dev/null +++ b/npm/linuxmusl-x64/package.json @@ -0,0 +1,45 @@ +{ + "name": "@sharpen/sharp-linuxmusl-x64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Linux (musl) x64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linuxmusl-x64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-linuxmusl-x64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-linuxmusl-x64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0", + "musl": ">=1.2.2" + }, + "os": [ + "linux" + ], + "libc": [ + "musl" + ], + "cpu": [ + "x64" + ] +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 000000000..aaa627bc1 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sharpen/sharp", + "version": "0.0.1-alpha.1", + "private": "true", + "workspaces": [ + "darwin-x64", + "darwin-arm64", + "linux-arm", + "linux-arm64", + "linuxmusl-arm64", + "linuxmusl-x64", + "linux-x64", + "win32-ia32", + "win32-x64" + ] +} diff --git a/npm/win32-ia32/package.json b/npm/win32-ia32/package.json new file mode 100644 index 000000000..f9d685c90 --- /dev/null +++ b/npm/win32-ia32/package.json @@ -0,0 +1,41 @@ +{ + "name": "@sharpen/sharp-win32-ia32", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Windows x86 (32-bit)", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/win32-ia32" + }, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-win32-ia32": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-win32-ia32.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0" + }, + "os": [ + "win32" + ], + "cpu": [ + "ia32" + ] +} diff --git a/npm/win32-x64/package.json b/npm/win32-x64/package.json new file mode 100644 index 000000000..c994fecc9 --- /dev/null +++ b/npm/win32-x64/package.json @@ -0,0 +1,41 @@ +{ + "name": "@sharpen/sharp-win32-x64", + "version": "0.0.1-alpha.1", + "description": "Prebuilt sharp for use with Windows x64", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/win32-x64" + }, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@sharpen/sharp-libvips-win32-x64": "0.0.1-alpha.1" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + "./sharp.node": "./lib/sharp-win32-x64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "yarn": ">=3.2.0", + "pnpm": ">=7.1.0" + }, + "os": [ + "win32" + ], + "cpu": [ + "x64" + ] +} diff --git a/package.json b/package.json index fe62da083..0fc6f7b6d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sharp", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images", - "version": "0.32.6", + "version": "0.33.0-alpha.1", "author": "Lovell Fuller ", "homepage": "https://github.com/lovell/sharp", "contributors": [ @@ -89,14 +89,16 @@ "Lachlan Newman " ], "scripts": { - "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)", - "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", + "install": "node install/check || node-gyp rebuild", + "clean": "rm -rf build/ .nyc_output/ coverage/ test/fixtures/output.*", "test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types", "test-lint": "semistandard && cpplint", "test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha", - "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"", + "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"", "test-leak": "./test/leak/leak.sh", "test-types": "tsd", + "package-from-local-build": "node npm/from-local-build", + "package-from-github-release": "node npm/from-github-release", "docs-build": "node docs/build && node docs/search-index/build", "docs-serve": "cd docs && npx serve", "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" @@ -111,7 +113,7 @@ ], "repository": { "type": "git", - "url": "git://github.com/lovell/sharp" + "url": "git://github.com/lovell/sharp.git" }, "keywords": [ "jpeg", @@ -135,13 +137,30 @@ "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" + "semver": "^7.5.4" + }, + "optionalDependencies": { + "@sharpen/sharp-darwin-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-darwin-x64": "0.0.1-alpha.1", + "@sharpen/sharp-linux-arm": "0.0.1-alpha.1", + "@sharpen/sharp-linux-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-linux-x64": "0.0.1-alpha.1", + "@sharpen/sharp-linuxmusl-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-linuxmusl-x64": "0.0.1-alpha.1", + "@sharpen/sharp-win32-ia32": "0.0.1-alpha.1", + "@sharpen/sharp-win32-x64": "0.0.1-alpha.1" }, "devDependencies": { + "@sharpen/sharp-libvips-darwin-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-darwin-x64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-dev": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-linux-arm": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-linux-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-linux-x64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-linuxmusl-arm64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-linuxmusl-x64": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-win32-ia32": "0.0.1-alpha.1", + "@sharpen/sharp-libvips-win32-x64": "0.0.1-alpha.1", "@types/node": "*", "async": "^3.2.4", "cc": "^3.0.1", @@ -151,33 +170,16 @@ "jsdoc-to-markdown": "^8.0.0", "license-checker": "^25.0.1", "mocha": "^10.2.0", - "mock-fs": "^5.2.0", "nyc": "^15.1.0", "prebuild": "^12.1.0", "semistandard": "^16.0.1", + "tar-fs": "^3.0.4", "tsd": "^0.29.0" }, "license": "Apache-2.0", - "config": { - "libvips": "8.14.5", - "integrity": { - "darwin-arm64v8": "sha512-1QZzICfCJd4wAO0P6qmYI5e5VFMt9iCE4QgefI8VMMbdSzjIXA9L/ARN6pkMQPZ3h20Y9RtJ2W1skgCsvCIccw==", - "darwin-x64": "sha512-sMIKMYXsdU9FlIfztj6Kt/SfHlhlDpP0Ups7ftVFqwjaszmYmpI9y/d/q3mLb4jrzuSiSUEislSWCwBnW7MPTw==", - "linux-arm64v8": "sha512-CD8owELzkDumaom+O3jJ8fKamILAQdj+//KK/VNcHK3sngUcFpdjx36C8okwbux9sml/T7GTB/gzpvReDrAejQ==", - "linux-armv6": "sha512-wk6IPHatDFVWKJy7lI1TJezHGHPQut1wF2bwx256KlZwXUQU3fcVcMpV1zxXjgLFewHq2+uhyMkoSGBPahWzlA==", - "linux-armv7": "sha512-HEZC9KYtkmBK5rUR2MqBhrVarnQVZ/TwLUeLkKq0XuoM2pc/eXI6N0Fh5NGEFwdXI2XE8g1ySf+OYS6DDi+xCQ==", - "linux-x64": "sha512-SlFWrITSW5XVUkaFPQOySAaSGXnhkGJCj8X2wGYYta9hk5piZldQyMp4zwy0z6UeRu1qKTKtZvmq28W3Gnh9xA==", - "linuxmusl-arm64v8": "sha512-ga9iX7WUva3sG/VsKkOD318InLlCfPIztvzCZKZ2/+izQXRbQi8VoXWMHgEN4KHACv45FTl7mJ/8CRqUzhS8wQ==", - "linuxmusl-x64": "sha512-yeaHnpfee1hrZLok2l4eFceHzlfq8gN3QOu0R4Mh8iMK5O5vAUu97bdtxeZZeJJvHw8tfh2/msGi0qysxKN8bw==", - "win32-arm64v8": "sha512-kR91hy9w1+GEXK56hLh51+hBCBo7T+ijM4Slkmvb/2PsYZySq5H7s61n99iDYl6kTJP2y9sW5Xcvm3uuXDaDgg==", - "win32-ia32": "sha512-HrnofEbzHNpHJ0vVnjsTj5yfgVdcqdWshXuwFO2zc8xlEjA83BvXZ0lVj9MxPxkxJ2ta+/UlLr+CFzc5bOceMw==", - "win32-x64": "sha512-BwKckinJZ0Fu/EcunqiLPwOLEBWp4xf8GV7nvmVuKKz5f6B+GxoA2k9aa2wueqv4r4RJVgV/aWXZWFKOIjre/Q==" - }, - "runtime": "napi", - "target": 9 - }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "libvips": ">=8.14.5" }, "funding": { "url": "https://opencollective.com/libvips" diff --git a/test/types/sharp.test-d.ts b/test/types/sharp.test-d.ts index b1f28de8c..2500b9191 100644 --- a/test/types/sharp.test-d.ts +++ b/test/types/sharp.test-d.ts @@ -73,8 +73,6 @@ readableStream.pipe(transformer).pipe(writableStream); console.log(sharp.format); console.log(sharp.versions); -console.log(sharp.vendor.current); -console.log(sharp.vendor.installed.join(', ')); sharp.queue.on('change', (queueLength: number) => { console.log(`Queue contains ${queueLength} task(s)`); diff --git a/test/unit/agent.js b/test/unit/agent.js deleted file mode 100644 index 95d587311..000000000 --- a/test/unit/agent.js +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const assert = require('assert'); -const agent = require('../../lib/agent'); - -describe('HTTP agent', function () { - it('Without proxy', function () { - assert.strictEqual(null, agent()); - }); - - it('HTTPS proxy with auth from HTTPS_PROXY', function () { - process.env.HTTPS_PROXY = 'https://user:pass@secure:123'; - let logMsg = ''; - const proxy = agent(msg => { logMsg = msg; }); - delete process.env.HTTPS_PROXY; - assert.strictEqual('object', typeof proxy); - assert.strictEqual('secure', proxy.options.proxy.host); - assert.strictEqual(123, proxy.options.proxy.port); - assert.strictEqual('user:pass', proxy.options.proxy.proxyAuth); - assert.strictEqual(443, proxy.defaultPort); - assert.strictEqual(logMsg, 'Via proxy https://secure:123 with credentials'); - }); - - it('HTTPS proxy with auth from HTTPS_PROXY using credentials containing special characters', function () { - process.env.HTTPS_PROXY = 'https://user,:pass=@secure:789'; - let logMsg = ''; - const proxy = agent(msg => { logMsg = msg; }); - delete process.env.HTTPS_PROXY; - assert.strictEqual('object', typeof proxy); - assert.strictEqual('secure', proxy.options.proxy.host); - assert.strictEqual(789, proxy.options.proxy.port); - assert.strictEqual('user,:pass=', proxy.options.proxy.proxyAuth); - assert.strictEqual(443, proxy.defaultPort); - assert.strictEqual(logMsg, 'Via proxy https://secure:789 with credentials'); - }); - - it('HTTP proxy without auth from npm_config_proxy', function () { - process.env.npm_config_proxy = 'http://plaintext:456'; - let logMsg = ''; - const proxy = agent(msg => { logMsg = msg; }); - delete process.env.npm_config_proxy; - assert.strictEqual('object', typeof proxy); - assert.strictEqual('plaintext', proxy.options.proxy.host); - assert.strictEqual(456, proxy.options.proxy.port); - assert.strictEqual(null, proxy.options.proxy.proxyAuth); - assert.strictEqual(443, proxy.defaultPort); - assert.strictEqual(logMsg, 'Via proxy http://plaintext:456 no credentials'); - }); -}); diff --git a/test/unit/libvips.js b/test/unit/libvips.js index 31ba655ff..7ab720fd0 100644 --- a/test/unit/libvips.js +++ b/test/unit/libvips.js @@ -7,7 +7,6 @@ const assert = require('assert'); const fs = require('fs'); const semver = require('semver'); const libvips = require('../../lib/libvips'); -const mockFS = require('mock-fs'); const originalPlatform = process.platform; @@ -59,10 +58,6 @@ describe('libvips binaries', function () { assert.strictEqual('string', typeof minimumLibvipsVersion); assert.notStrictEqual(null, semver.valid(minimumLibvipsVersion)); }); - it('hasVendoredLibvips returns a boolean', function () { - const hasVendoredLibvips = libvips.hasVendoredLibvips(); - assert.strictEqual('boolean', typeof hasVendoredLibvips); - }); it('useGlobalLibvips can be ignored via an env var', function () { process.env.SHARP_IGNORE_GLOBAL_LIBVIPS = 1; @@ -71,62 +66,48 @@ describe('libvips binaries', function () { delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS; }); - it('cachePath returns a valid path ending with _libvips', function () { - const cachePath = libvips.cachePath(); - assert.strictEqual('string', typeof cachePath); - assert.strictEqual('_libvips', cachePath.substr(-8)); - assert.strictEqual(true, fs.existsSync(cachePath)); - }); }); - describe('integrity', function () { - it('reads value from environment variable', function () { - const prev = process.env.npm_package_config_integrity_platform_arch; - process.env.npm_package_config_integrity_platform_arch = 'sha512-test'; - - const integrity = libvips.integrity('platform-arch'); - assert.strictEqual('sha512-test', integrity); - - process.env.npm_package_config_integrity_platform_arch = prev; - }); - it('reads value from package.json', function () { - const prev = process.env.npm_package_config_integrity_linux_x64; - delete process.env.npm_package_config_integrity_linux_x64; - - const integrity = libvips.integrity('linux-x64'); - assert.strictEqual('sha512-', integrity.substr(0, 7)); - - process.env.npm_package_config_integrity_linux_x64 = prev; + describe('Build time platform detection', () => { + it('Can override platform with npm_config_platform and npm_config_libc', () => { + process.env.npm_config_platform = 'testplatform'; + process.env.npm_config_libc = 'testlibc'; + const [platform] = libvips.buildPlatformArch().split('-'); + assert.strictEqual(platform, 'testplatformtestlibc'); + delete process.env.npm_config_platform; + delete process.env.npm_config_libc; + }); + it('Can override arch with npm_config_arch', () => { + process.env.npm_config_arch = 'test'; + const [, arch] = libvips.buildPlatformArch().split('-'); + assert.strictEqual(arch, 'test'); + delete process.env.npm_config_arch; }); }); - describe('safe directory creation', function () { - before(function () { - mockFS({ - exampleDirA: { - exampleDirB: { - exampleFile: 'Example test file' - } - } - }); + describe('Build time directories', () => { + it('sharp-libvips include', () => { + const dir = libvips.buildSharpLibvipsIncludeDir(); + assert.strictEqual(fs.statSync(dir).isDirectory(), true); }); - after(function () { mockFS.restore(); }); - - it('mkdirSync creates a directory', function () { - const dirPath = 'createdDir'; - - libvips.mkdirSync(dirPath); - assert.strictEqual(true, fs.existsSync(dirPath)); + it('sharp-libvips cplusplus', () => { + const dir = libvips.buildSharpLibvipsCPlusPlusDir(); + assert.strictEqual(fs.statSync(dir).isDirectory(), true); }); - it('mkdirSync does not throw error or overwrite an existing dir', function () { - const dirPath = 'exampleDirA'; - const nestedDirPath = 'exampleDirA/exampleDirB'; - assert.strictEqual(true, fs.existsSync(dirPath)); - - libvips.mkdirSync(dirPath); + it('sharp-libvips lib', () => { + const dir = libvips.buildSharpLibvipsLibDir(); + assert.strictEqual(fs.statSync(dir).isDirectory(), true); + }); + }); - assert.strictEqual(true, fs.existsSync(dirPath)); - assert.strictEqual(true, fs.existsSync(nestedDirPath)); + describe('Runtime detection', () => { + it('platform', () => { + const [platform] = libvips.runtimePlatformArch().split('-'); + assert.strict(['darwin', 'linux', 'linuxmusl', 'win32'].includes(platform)); + }); + it('arch', () => { + const [, arch] = libvips.runtimePlatformArch().split('-'); + assert.strict(['arm', 'arm64', 'ia32', 'x64'].includes(arch)); }); }); diff --git a/test/unit/platform.js b/test/unit/platform.js deleted file mode 100644 index 402ee28bb..000000000 --- a/test/unit/platform.js +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const assert = require('assert'); -const platform = require('../../lib/platform'); - -describe('Platform-detection', function () { - it('Can override arch with npm_config_arch', function () { - process.env.npm_config_arch = 'test'; - assert.strictEqual('test', platform().split('-')[1]); - delete process.env.npm_config_arch; - }); - - it('Can override platform with npm_config_platform', function () { - process.env.npm_config_platform = 'test'; - assert.strictEqual('test', platform().split('-')[0]); - delete process.env.npm_config_platform; - }); - - it('Can override ARM version via --arm-version', function () { - process.env.npm_config_arch = 'arm'; - process.env.npm_config_arm_version = 'test'; - assert.strictEqual('armvtest', platform().split('-')[1]); - delete process.env.npm_config_arm_version; - delete process.env.npm_config_arch; - }); - - it('Can override ARM64 version via --arm-version', function () { - process.env.npm_config_arch = 'arm64'; - process.env.npm_config_arm_version = 'test'; - assert.strictEqual('arm64vtest', platform().split('-')[1]); - delete process.env.npm_config_arm_version; - delete process.env.npm_config_arch; - }); - - if (process.config.variables.arm_version) { - it('Can detect ARM version via process.config', function () { - process.env.npm_config_arch = 'arm'; - assert.strictEqual(`armv${process.config.variables.arm_version}`, platform().split('-')[1]); - delete process.env.npm_config_arch; - }); - } - - if (!process.config.variables.arm_version) { - it('Defaults to ARMv6 for 32-bit', function () { - process.env.npm_config_arch = 'arm'; - assert.strictEqual('armv6', platform().split('-')[1]); - delete process.env.npm_config_arch; - }); - } - - it('Defaults to ARMv8 for 64-bit', function () { - process.env.npm_config_arch = 'arm64'; - assert.strictEqual('arm64v8', platform().split('-')[1]); - delete process.env.npm_config_arch; - }); - - it('Can ensure version ARMv7 if electron version is present', function () { - process.env.npm_config_arch = 'arm'; - process.versions.electron = 'test'; - assert.strictEqual('armv7', platform().split('-')[1]); - delete process.env.npm_config_arch; - delete process.versions.electron; - }); - - it('Can override libc if platform is linux', function () { - process.env.npm_config_platform = 'linux'; - process.env.npm_config_libc = 'test'; - assert.strictEqual('linuxtest', platform().split('-')[0]); - delete process.env.npm_config_platform; - delete process.env.npm_config_libc; - }); - - it('Handles libc value "glibc" as default linux', function () { - process.env.npm_config_platform = 'linux'; - process.env.npm_config_libc = 'glibc'; - assert.strictEqual('linux', platform().split('-')[0]); - delete process.env.npm_config_platform; - delete process.env.npm_config_libc; - }); - - it('Discards libc value on non-linux platform', function () { - process.env.npm_config_platform = 'win32'; - process.env.npm_config_libc = 'gnuwin32'; - assert.strictEqual('win32', platform().split('-')[0]); - delete process.env.npm_config_platform; - delete process.env.npm_config_libc; - }); -}); diff --git a/test/unit/util.js b/test/unit/util.js index 817bc6ca7..fd881a80d 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -146,14 +146,6 @@ describe('Utilities', function () { }); }); - describe('Vendor', function () { - it('Contains expected attributes', function () { - assert.strictEqual('object', typeof sharp.vendor); - assert.strictEqual('string', typeof sharp.vendor.current); - assert.strictEqual(true, Array.isArray(sharp.vendor.installed)); - }); - }); - describe('Block', () => { it('Can block a named operation', () => { sharp.block({ operation: ['test'] });